Загрузка...

UI Отслеживаем наше проведенное время на форуме

Тема в разделе Дополнения создана пользователем Yowori 6 апр 2025. 266 просмотров

  1. Yowori
    Yowori Автор темы 6 апр 2025 Эльфографика грядёт ~ https://lolz.live/threads/7861550/ 15 574 3 июн 2019
    Это расширение для нашего любимого форума с блеском отслеживает, сколько времени ты проводишь на разных разделах форума.
    Впрочем, не стоит ожидать фокусов. Простой, но эффективный инструмент для тех, кто любит данные и статистику.


    [IMG]
    — Следит за временем: Подсчитывает, сколько времени ты проводишь на форуме, в темах, у пользователей или на странице профиля.
    — Показывает статистику: После сбора данных, ты можешь увидеть, сколько времени потратил на различные разделы или темы.
    — Cброс статистики: Если вдруг решишь, что накопленные данные не особо важны, есть опция для сброса (с подтверждением, чтоб не перепутать).
    — Переключение видов: Удобно переключаться между общей статистикой, детальными данными по разделам, темам и пользователям.
    [IMG]
    [IMG]
    [IMG]
    — Расширение ~ Chrome ~ Opera ~ Firefox
    Скрипт ~ https://greasyfork.org/ru/scripts/532007-lolz-time-tracker
    или
    Код
    // ==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);
    })();

    Короче говоря, этот скрипт для тех, кто хочет знать, куда уходит время, и может позволить себе не забывать о нем даже после полудня на форуме.
     
    6 апр 2025 Изменено
  2. рикка
    рикка 6 апр 2025 丰 Мне нехуй делать — я ем круассан 6084 4 апр 2019
    расширение чтобы ещё более отчётливо понять как быстро проходит жизнь в оффтопе
     
    1. Yowori Автор темы
  3. unleash
    unleash 6 апр 2025 :pepeSuicide2: 4507 26 ноя 2020
    там таких цифр больших не будет
     
    1. unleash
  4. Крым
    страшно смотреть будет на это
     
    6 апр 2025 Изменено
  5. БИЛЛИНОГАМИ
    БИЛЛИНОГАМИ 6 апр 2025 Банки|Турция|Верификации|Биржи - lzt.lol/TUR :zerotwo:
    На ios есть вариант поставить ?
     
    6 апр 2025 Изменено
  6. Legitim
    Legitim 6 апр 2025 Мы живём для того, чтобы стать нефтью 13 097 10 фев 2019
    А время на маркете оно показывает?
     
    1. Посмотреть предыдущие комментарии (1)
    2. Legitim
      Yowori, Было бы хорошо
    3. Yowori Автор темы
      Legitim, просто время на маркете или со статистикой по товарам на которых больше всего сидел?
      6 апр 2025 Изменено
    4. Legitim
      Yowori, Та просто время наверн
  7. WhatACat
    WhatACat 6 апр 2025 https://lolz.live/account/upgrades покупайте уники, ау, автобай 17 504 16 дек 2023
    Сбор информации и перенаправление к Yowori встроено :2011_like:
     
    1. Yowori Автор темы
      WhatACat, собственно говоря а где минусы?
Загрузка...
Top