Загрузка...

UI
We track our time on the forum

Thread in Extentions created by Yeulette feather-wing Apr 6, 2025. 504 views

  1. Yeulette
    Это расширение для нашего любимого форума с блеском отслеживает, сколько времени ты проводишь на разных разделах форума.
    Впрочем, не стоит ожидать фокусов. Простой, но эффективный инструмент для тех, кто любит данные и статистику.


    [IMG]
    — Следит за временем: Подсчитывает, сколько времени ты проводишь на форуме, в темах, у пользователей или на странице профиля.
    — Показывает статистику: После сбора данных, ты можешь увидеть, сколько времени потратил на различные разделы или темы.
    — Cброс статистики: Если вдруг решишь, что накопленные данные не особо важны, есть опция для сброса (с подтверждением, чтоб не перепутать).
    — Переключение видов: Удобно переключаться между общей статистикой, детальными данными по разделам, темам и пользователям.
    [IMG]
    [IMG]
    [IMG]
    — Расширение ~ Chrome ~ Opera ~ Firefox
    Скрипт ~ https://greasyfork.org/ru/scripts/532007-lolz-time-tracker
    или
    Code
    // ==UserScript==
    // @name Lolz Time Tracker
    // @namespace http://tampermonkey.net/
    // @version 1.0
    // @description Track time spent on lolz.live with detailed statistics
    // @author Yowori
    // @match https://lolz.live/*
    // @match https://lolz.guru/*
    // @match https://zelenka.guru/*
    // @grant GM_getValue
    // @grant GM_setValue
    // @grant GM_addStyle
    // @grant GM_registerMenuCommand
    // ==/UserScript==

    (function() {
    'use strict';

    const SECTION_CONFIG = {
    forums: { pattern: /\/forums\//, name: 'Разделы ' },
    threads: { pattern: /\/threads\//, name: 'Темы ' },
    members: { pattern: /\/members\//, name: 'Пользователи ' },
    account: { pattern: /\/account\//, name: 'Профиль ' },
    other: { pattern: /.*/, name: 'Другое ' }
    };

    let trackerState = {
    currentSection: null,
    currentThread: null,
    currentUser: null,
    isPageActive: true,
    lastUpdateTime: Date.now(),
    totalSeconds: 0,
    sectionSeconds: {},
    threadSeconds: {},
    userSeconds: {}
    };

    const storageKey = 'lolzTimeTracker_v4';
    const uiStateKey = 'lolzTimeTrackerUIState';
    let updateInterval;
    let statsVisible = false;
    let detailedView = 'main';
    let confirmationOpen = false;

    GM_addStyle(`
    #time-tracker-container {
    position: fixed;
    left: 10px;
    bottom: 10px;
    z-index: 9999;
    font-family: Arial, sans-serif;
    }

    #time-tracker-toggle {
    background: #4CAF50;
    color: white;
    border: none;
    padding: 8px 12px;
    border-radius: 4px;
    cursor: pointer;
    font-weight: bold;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    transition: all 0.2s ease;
    margin-right: 8px;
    }

    #time-tracker-toggle:hover {
    background: #45a049;
    transform: translateY(-1px);
    }

    #time-tracker-reset {
    background: #f44336;
    color: white;
    border: none;
    padding: 8px 12px;
    border-radius: 4px;
    cursor: pointer;
    font-weight: bold;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    transition: all 0.2s ease;
    }

    #time-tracker-reset:hover {
    background: #d32f2f;
    transform: translateY(-1px);
    }

    #time-tracker-buttons {
    display: flex;
    }

    #time-tracker-widget {
    display: none;
    background: #272727;
    color: #fff;
    padding: 12px;
    border-radius: 6px;
    margin-top: 8px;
    width: 280px;
    backdrop-filter: blur(5px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    border: 1px solid #383838;
    max-height: 60vh;
    overflow-y: auto;
    }

    .time-tracker-header {
    font-size: 16px;
    font-weight: bold;
    margin-bottom: 10px;
    color: #4CAF50;
    border-bottom: 1px solid #444;
    padding-bottom: 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    }

    .time-tracker-back-btn {
    background: none;
    border: none;
    color: #4CAF50;
    cursor: pointer;
    font-size: 14px;
    }

    .time-tracker-confirm-reset {
    background: none;
    border: none;
    color: #f44336;
    cursor: pointer;
    font-size: 14px;
    margin-left: 5px;
    }

    .time-tracker-section {
    margin: 8px 0;
    display: flex;
    justify-content: space-between;
    }

    .time-tracker-section-name {
    color: #ddd;
    cursor: pointer;
    }

    .time-tracker-section-time {
    font-weight: bold;
    color: #fff;
    }

    .time-tracker-detail-item {
    margin: 6px 0;
    padding-left: 10px;
    border-left: 2px solid #444;
    }

    .time-tracker-nav-btn {
    background: none;
    border: none;
    color: #ddd;
    cursor: pointer;
    margin: 0 5px;
    padding: 2px 5px;
    }

    .time-tracker-nav-btn:hover {
    color: #4CAF50;
    }

    .time-tracker-nav-btn.active {
    color: #4CAF50;
    border-bottom: 1px solid #4CAF50;
    }
    `);

    function init() {
    loadData();
    loadUIState();
    createUI();
    setupVisibilityListener();
    setupPageAnalyzers();
    startTracking();
    GM_registerMenuCommand("Показать статистику Lolz.live", toggleStats);
    }

    function loadUIState() {
    const savedState = GM_getValue(uiStateKey, { statsVisible: false });
    statsVisible = savedState.statsVisible;
    }

    function saveUIState() {
    GM_setValue(uiStateKey, { statsVisible: statsVisible });
    }

    function createUI() {
    const container = document.createElement('div');
    container.id = 'time-tracker-container';

    const buttonsContainer = document.createElement('div');
    buttonsContainer.id = 'time-tracker-buttons';

    const toggleBtn = document.createElement('button');
    toggleBtn.id = 'time-tracker-toggle';
    toggleBtn.textContent = statsVisible ? ' Скрыть' : ' Статистика';
    toggleBtn.addEventListener('click', toggleStats);

    const resetBtn = document.createElement('button');
    resetBtn.id = 'time-tracker-reset';
    resetBtn.textContent = ' Сбросить';
    resetBtn.addEventListener('click', confirmResetStats);

    buttonsContainer.appendChild(toggleBtn);
    buttonsContainer.appendChild(resetBtn);

    const widget = document.createElement('div');
    widget.id = 'time-tracker-widget';
    widget.style.display = statsVisible ? 'block' : 'none';

    container.appendChild(buttonsContainer);
    container.appendChild(widget);
    document.body.appendChild(container);

    updateUI();
    }

    function confirmResetStats() {
    const widget = document.getElementById('time-tracker-widget');
    if (!widget) return;

    confirmationOpen = true;
    widget.innerHTML = `
    <div class="time-tracker-header">
    <span>Сброс статистики</span>
    </div>
    <div style="margin: 10px 0;">
    Вы уверены, что хотите сбросить статистику за сегодня?
    </div>
    <div style="display: flex; justify-content: flex-end;">
    <button id="time-tracker-cancel-reset" style="background: #4CAF50; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: all 0.2s ease; margin-right: 8px;">Отмена</button>
    <button id="time-tracker-confirm-reset" style="background: #f44336; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: all 0.2s ease;">Сбросить</button>
    </div>
    `;

    document.getElementById('time-tracker-cancel-reset').addEventListener('click', () => {
    confirmationOpen = false;
    updateUI();
    });

    document.getElementById('time-tracker-confirm-reset').addEventListener('click', resetStats);
    }

    function resetStats() {
    const today = getTodayKey();
    const savedData = GM_getValue(storageKey) || {};
    savedData[today] = {
    totalSeconds: 0,
    sectionSeconds: {},
    threadSeconds: {},
    userSeconds: {}
    };
    GM_setValue(storageKey, savedData);

    trackerState.totalSeconds = 0;
    trackerState.sectionSeconds = {};
    trackerState.threadSeconds = {};
    trackerState.userSeconds = {};


    for (const section in SECTION_CONFIG) {
    trackerState.sectionSeconds[section] = 0;
    }

    confirmationOpen = false;
    updateUI();
    }


    function toggleStats() {
    statsVisible = !statsVisible;
    const widget = document.getElementById('time-tracker-widget');
    if (widget) widget.style.display = statsVisible ? 'block' : 'none';

    const toggleBtn = document.getElementById('time-tracker-toggle');
    if (toggleBtn) toggleBtn.textContent = statsVisible ? ' Скрыть' : ' Статистика';

    saveUIState();

    if (statsVisible && !confirmationOpen) {
    updateUI();
    }
    }


    function loadData() {
    const today = getTodayKey();
    const savedData = GM_getValue(storageKey) || {};
    const todayData = savedData[today] || {
    totalSeconds: 0,
    sectionSeconds: {},
    threadSeconds: {},
    userSeconds: {}
    };

    trackerState = {
    ...trackerState,
    totalSeconds: todayData.totalSeconds || 0,
    sectionSeconds: todayData.sectionSeconds || {},
    threadSeconds: todayData.threadSeconds || {},
    userSeconds: todayData.userSeconds || {}
    };


    for (const section in SECTION_CONFIG) {
    if (!trackerState.sectionSeconds[section]) {
    trackerState.sectionSeconds[section] = 0;
    }
    }
    }


    function saveData() {
    const today = getTodayKey();
    const savedData = GM_getValue(storageKey) || {};
    savedData[today] = {
    totalSeconds: trackerState.totalSeconds,
    sectionSeconds: trackerState.sectionSeconds,
    threadSeconds: trackerState.threadSeconds,
    userSeconds: trackerState.userSeconds
    };
    GM_setValue(storageKey, savedData);
    }


    function detectSection() {
    const path = window.location.pathname;
    for (const [section, config] of Object.entries(SECTION_CONFIG)) {
    if (config.pattern.test(path)) {
    return section;
    }
    }
    return 'other';
    }


    function detectThread() {
    const threadMatch = window.location.pathname.match(/\/threads\/(\d+)/);
    if (threadMatch) {
    return threadMatch[1];
    }
    return null;
    }


    function detectUser() {
    const userMatch = window.location.pathname.match(/\/members\/(\d+)/);
    if (userMatch) {
    return userMatch[1];
    }
    const prettyUrlMatch = window.location.pathname.match(/^\/([^\/]+)\/?$/);
    if (prettyUrlMatch && !['forums', 'threads', 'account', 'members'].includes(prettyUrlMatch[1])) {
    return prettyUrlMatch[1];
    }
    return null;
    }


    function setupPageAnalyzers() {
    trackerState.currentSection = detectSection();
    trackerState.currentThread = detectThread();
    trackerState.currentUser = detectUser();
    }


    function startTracking() {
    if (updateInterval) clearInterval(updateInterval);

    updateInterval = setInterval(() => {
    if (!trackerState.isPageActive) return;

    const now = Date.now();
    const elapsedSeconds = Math.floor((now - trackerState.lastUpdateTime) / 1000);

    if (elapsedSeconds > 0) {
    trackerState.lastUpdateTime = now;

    setupPageAnalyzers();

    trackerState.totalSeconds += elapsedSeconds;

    trackerState.sectionSeconds[trackerState.currentSection] =
    (trackerState.sectionSeconds[trackerState.currentSection] || 0) + elapsedSeconds;

    if (trackerState.currentThread) {
    trackerState.threadSeconds[trackerState.currentThread] =
    (trackerState.threadSeconds[trackerState.currentThread] || 0) + elapsedSeconds;
    }

    if (trackerState.currentUser) {
    trackerState.userSeconds[trackerState.currentUser] =
    (trackerState.userSeconds[trackerState.currentUser] || 0) + elapsedSeconds;
    }

    saveData();

    if (statsVisible && !confirmationOpen) {
    updateUI();
    }
    }
    }, 1000);
    }

    function updateUI() {
    if (confirmationOpen) return;

    const widget = document.getElementById('time-tracker-widget');
    if (!widget) return;

    let html = '';

    switch (detailedView) {
    case 'main':
    html = getMainView();
    break;
    case 'sections':
    html = getSectionsDetailView();
    break;
    case 'threads':
    html = getThreadsDetailView();
    break;
    case 'users':
    html = getUsersDetailView();
    break;
    }

    widget.innerHTML = html;
    addEventHandlers();
    }

    function getMainView() {
    let html = `<div class="time-tracker-header">
    <span>Статистика за сегодня</span>
    </div>`;

    html += `<div class="time-tracker-section">
    <span class="time-tracker-section-name">Всего:</span>
    <span class="time-tracker-section-time">${formatTime(trackerState.totalSeconds)}</span>
    </div>`;

    html += `<div style="margin: 10px 0; display: flex; justify-content: space-around;">
    <button class="time-tracker-nav-btn ${detailedView === 'threads' ? 'active' : ''}" data-view="threads">Темы</button>
    <button class="time-tracker-nav-btn ${detailedView === 'users' ? 'active' : ''}" data-view="users">Пользователи</button>
    </div>`;

    const sortedSections = Object.entries(trackerState.sectionSeconds)
    .sort((a, b) => b[1] - a[1]);

    html += `<div style="margin-top: 10px; font-weight: bold; color: #ddd;">Общая информация :</div>`;
    for (const [sectionId, seconds] of sortedSections.slice(0, 5)) {
    if (seconds > 0) {
    const sectionName = SECTION_CONFIG[sectionId].name;
    html += `<div class="time-tracker-section">
    <span class="time-tracker-section-name">${sectionName}:</span>
    <span class="time-tracker-section-time">${formatTime(seconds)}</span>
    </div>`;
    }
    }

    return html;
    }

    function getSectionsDetailView() {
    let html = `<div class="time-tracker-header">
    <button class="time-tracker-back-btn" data-view="main">Назад</button>
    <span>Статистика по разделам</span>
    </div>`;

    const sortedSections = Object.entries(trackerState.sectionSeconds)
    .sort((a, b) => b[1] - a[1]);

    for (const [sectionId, seconds] of sortedSections) {
    if (seconds > 0) {
    const sectionName = SECTION_CONFIG[sectionId].name;
    html += `<div class="time-tracker-section">
    <span class="time-tracker-section-name">${sectionName}:</span>
    <span class="time-tracker-section-time">${formatTime(seconds)}</span>
    </div>`;
    }
    }

    return html;
    }

    function getThreadsDetailView() {
    let html = `<div class="time-tracker-header">
    <button class="time-tracker-back-btn" data-view="main">Назад</button>
    <span>Темы</span>
    </div>`;

    const sortedThreads = Object.entries(trackerState.threadSeconds)
    .sort((a, b) => b[1] - a[1]);

    for (const [threadId, seconds] of sortedThreads.slice(0, 20)) {
    if (seconds > 0) {
    html += `<div class="time-tracker-section">
    <span class="time-tracker-section-name" data-thread-id="${threadId}">Тема #${threadId}:</span>
    <span class="time-tracker-section-time">${formatTime(seconds)}</span>
    </div>`;
    }
    }

    return html;
    }

    function getUsersDetailView() {
    let html = `<div class="time-tracker-header">
    <button class="time-tracker-back-btn" data-view="main">Назад</button>
    <span>Пользователи</span>
    </div>`;

    const sortedUsers = Object.entries(trackerState.userSeconds)
    .sort((a, b) => b[1] - a[1]);

    for (const [userId, seconds] of sortedUsers.slice(0, 20)) {
    if (seconds > 0) {
    const isNumericId = /^\d+$/.test(userId);
    const userLink = isNumericId ? `/members/${userId}/` : `/${userId}/`;

    html += `<div class="time-tracker-section">
    <span class="time-tracker-section-name" data-user-id="${userId}" data-is-numeric="${isNumericId}">Пользователь ${isNumericId ? '#' + userId : userId}:</span>
    <span class="time-tracker-section-time">${formatTime(seconds)}</span>
    </div>`;
    }
    }

    return html;
    }

    function addEventHandlers() {
    document.querySelectorAll('.time-tracker-nav-btn').forEach(btn => {
    btn.addEventListener('click', () => {
    detailedView = btn.dataset.view;
    updateUI();
    });
    });

    document.querySelectorAll('.time-tracker-back-btn').forEach(btn => {
    btn.addEventListener('click', () => {
    detailedView = btn.dataset.view;
    updateUI();
    });
    });

    document.querySelectorAll('.time-tracker-section-name[data-thread-id]').forEach(el => {
    el.addEventListener('click', (e) => {
    e.preventDefault();
    const threadId = el.dataset.threadId;
    window.location.href = `/threads/${threadId}/`;
    });
    });

    document.querySelectorAll('.time-tracker-section-name[data-user-id]').forEach(el => {
    el.addEventListener('click', (e) => {
    e.preventDefault();
    const userId = el.dataset.userId;
    const isNumeric = el.dataset.isNumeric === 'true';

    if (isNumeric) {
    window.location.href = `/members/${userId}/`;
    } else {
    window.location.href = `/${userId}/`;
    }
    });
    });
    }

    function formatTime(totalSeconds) {
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = totalSeconds % 60;

    if (totalSeconds < 60) {
    return `${seconds} сек`;
    } else {
    return `${hours > 0 ? hours + ' ч ' : ''}${minutes} мин`;
    }
    }

    function setupVisibilityListener() {
    document.addEventListener('visibilitychange', () => {
    trackerState.isPageActive = !document.hidden;
    trackerState.lastUpdateTime = Date.now();
    });
    }

    function getTodayKey() {
    const now = new Date();
    return `${now.getFullYear()}-${now.getMonth()+1}-${now.getDate()}`;
    }

    window.addEventListener('load', init);
    document.addEventListener('DOMContentLoaded', init);
    })();

    Короче говоря, этот скрипт для тех, кто хочет знать, куда уходит время, и может позволить себе не забывать о нем даже после полудня на форуме.
     
    This article was useful for you?
    You can thank the author of the topic by transferring funds to your balance
    Thank the author
  2. рикка
    рикка Apr 6, 2025 call me «spider» with a double «R» 9,531 Apr 4, 2019
    расширение чтобы ещё более отчётливо понять как быстро проходит жизнь в оффтопе
     
    1. Yeulette feather-wing Topic starter
  3. unleash
    unleash Apr 6, 2025 :Pepe_music2: 6,307 Nov 26, 2020
    там таких цифр больших не будет
     
  4. орешник
    страшно смотреть будет на это
     
  5. БИЛЛИНОГАМИ
    БИЛЛИНОГАМИ Apr 6, 2025
    Profile message БИЛЛИНОГАМИ
    158,249 May 3, 2019
    На ios есть вариант поставить ?
     
  6. Legitim
    Legitim Apr 6, 2025 Мы живём для того, чтобы стать нефтью 13,300 Feb 10, 2019
    А время на маркете оно показывает?
     
    1. View previous comments (1)
    2. Legitim
    3. Yeulette feather-wing Topic starter
      avatarLegitim , просто время на маркете или со статистикой по товарам на которых больше всего сидел?
    4. Legitim
  7. WhatACat
    Сбор информации и перенаправление к avatarYeulette feather-wing встроено :2011_like:
     
    1. Yeulette feather-wing Topic starter
      avatarWhatACat , собственно говоря а где минусы?
  8. ememfro
    посмотрим сколько времени оффтоплю :omg:
     
Loading...