DTF User Notes

Add notes to users on dtf.ru

Verze ze dne 26. 02. 2025. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         DTF User Notes
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Add notes to users on dtf.ru
// @author       Avicenna
// @match        https://dtf.ru/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Функция для создания заметки
    function createNote(userId, username) {
        const currentNote = localStorage.getItem(`note_${userId}`);
        const currentColor = localStorage.getItem(`note_color_${userId}`) || 'gray';

        const noteText = prompt(`Введите заметку для пользователя ${username}:`, currentNote || '');
        if (noteText === null) return;

        const useRedText = confirm("Сделать текст заметки красным? (ОК - да, Отмена - нет)");
        const textColor = useRedText ? 'red' : 'gray';

        localStorage.setItem(`note_${userId}`, noteText);
        localStorage.setItem(`note_color_${userId}`, textColor);
        displayUserNotes();
    }

    // Функция для извлечения ID пользователя из ссылки
    function extractUserId(href) {
        const match = href.match(/\/u\/(\d+)-/);
        return match ? match[1] : null;
    }

    // Функция для отображения заметок рядом с ником пользователя
    function displayUserNotes() {
        // Отображение заметок в постах и комментариях
        const authors = document.querySelectorAll('.author__name, .comment__author, .content-header__author a');
        authors.forEach(author => {
            if (author.href) {
                const userId = extractUserId(author.href);
                if (userId) {
                    const note = localStorage.getItem(`note_${userId}`);
                    const textColor = localStorage.getItem(`note_color_${userId}`) || 'gray';

                    if (note) {
                        // Удаляем старую заметку, если она есть
                        const existingNote = author.parentNode.querySelector('.user-note');
                        if (existingNote) existingNote.remove();

                        const noteSpan = document.createElement('span');
                        noteSpan.innerText = ` ${note}`;
                        noteSpan.classList.add('user-note');
                        noteSpan.style.color = textColor;
                        noteSpan.style.marginLeft = '5px';
                        noteSpan.style.padding = '2px 5px';
                        noteSpan.style.borderRadius = '3px';

                        author.parentNode.insertBefore(noteSpan, author.nextSibling);
                    }
                }
            }
        });

        // Отображение заметки в профиле
        const profileName = document.querySelector('.subsite-card__name h1');
        if (profileName) {
            const userId = extractUserId(window.location.pathname);
            if (userId) {
                const note = localStorage.getItem(`note_${userId}`);
                const textColor = localStorage.getItem(`note_color_${userId}`) || 'gray';

                if (note) {
                    // Удаляем старую заметку, если она есть
                    const existingNote = profileName.parentNode.querySelector('.user-note');
                    if (existingNote) existingNote.remove();

                    const noteSpan = document.createElement('span');
                    noteSpan.innerText = ` ${note}`;
                    noteSpan.classList.add('user-note');
                    noteSpan.style.color = textColor;
                    noteSpan.style.marginLeft = '5px';
                    noteSpan.style.padding = '2px 5px';
                    noteSpan.style.borderRadius = '3px';

                    profileName.parentNode.insertBefore(noteSpan, profileName.nextSibling);
                }
            }
        }
    }

    // Функция для экспорта заметок в JSON
    function exportNotes() {
        const notes = {};
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            if (key.startsWith('note_') && !key.startsWith('note_color_')) {
                const userId = key.replace('note_', '');
                const note = localStorage.getItem(key);
                const color = localStorage.getItem(`note_color_${userId}`) || 'gray';
                notes[userId] = { note, color };
            }
        }

        const json = JSON.stringify(notes, null, 2);
        const blob = new Blob([json], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        // Форматируем дату и время для имени файла
        const now = new Date();
        const formattedDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
        const formattedTime = `${String(now.getHours()).padStart(2, '0')}-${String(now.getMinutes()).padStart(2, '0')}-${String(now.getSeconds()).padStart(2, '0')}`;
        const fileName = `dtf_notes_${formattedDate}_${formattedTime}.json`;

        const a = document.createElement('a');
        a.href = url;
        a.download = fileName;
        a.click();
        URL.revokeObjectURL(url);
    }

    // Функция для импорта заметок из JSON
    function importNotes(event) {
        const file = event.target.files[0];
        if (!file) return;

        // Предупреждение перед импортом
        const warningMessage = `
            Внимание! Импорт удалит все текущие заметки.
            Убедитесь, что у вас есть резервная копия.
            Продолжить?
        `;

        const isConfirmed = confirm(warningMessage);
        if (!isConfirmed) return;

        const reader = new FileReader();
        reader.onload = (e) => {
            const json = e.target.result;
            const notes = JSON.parse(json);

            // Очищаем все существующие заметки
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                if (key.startsWith('note_') || key.startsWith('note_color_')) {
                    localStorage.removeItem(key);
                }
            }

            // Добавляем только те заметки, которые есть в файле
            for (const userId in notes) {
                if (notes.hasOwnProperty(userId)) {
                    localStorage.setItem(`note_${userId}`, notes[userId].note);
                    localStorage.setItem(`note_color_${userId}`, notes[userId].color);
                }
            }

            displayUserNotes();
        };
        reader.readAsText(file);
    }

    // Функция для добавления кнопок в существующее выпадающее меню
    function addButtonsToExistingMenu() {
        const existingMenu = document.querySelector('.context-list');
        if (!existingMenu || existingMenu.querySelector('.custom-note-button')) return;

        // Создаем кнопку "Добавить заметку"
        const addNoteOption = document.createElement('div');
        addNoteOption.classList.add('context-list-option', 'context-list-option--with-art', 'custom-note-button');
        addNoteOption.style = '--press-duration: 140ms;';
        addNoteOption.innerHTML = `
            <div class="context-list-option__art context-list-option__art--icon">
                <svg class="icon icon--note" width="20" height="20"><use xlink:href="#note"></use></svg>
            </div>
            <div class="context-list-option__label">Добавить заметку</div>
        `;
        addNoteOption.onclick = () => {
            const userId = extractUserId(window.location.pathname);
            const username = document.querySelector('.subsite-card__name h1').innerText;
            if (userId) createNote(userId, username);
        };

        // Создаем кнопку "Экспорт заметок"
        const exportOption = document.createElement('div');
        exportOption.classList.add('context-list-option', 'context-list-option--with-art', 'custom-note-button');
        exportOption.style = '--press-duration: 140ms;';
        exportOption.innerHTML = `
            <div class="context-list-option__art context-list-option__art--icon">
                <svg class="icon icon--export" width="20" height="20"><use xlink:href="#export"></use></svg>
            </div>
            <div class="context-list-option__label">Экспорт заметок</div>
        `;
        exportOption.onclick = exportNotes;

        // Создаем кнопку "Импорт заметок"
        const importOption = document.createElement('div');
        importOption.classList.add('context-list-option', 'context-list-option--with-art', 'custom-note-button');
        importOption.style = '--press-duration: 140ms;';
        importOption.innerHTML = `
            <div class="context-list-option__art context-list-option__art--icon">
                <svg class="icon icon--import" width="20" height="20"><use xlink:href="#import"></use></svg>
            </div>
            <div class="context-list-option__label">Импорт заметок</div>
        `;
        importOption.onclick = () => {
            const importInput = document.createElement('input');
            importInput.type = 'file';
            importInput.accept = '.json';
            importInput.style.display = 'none';
            importInput.onchange = importNotes;
            importInput.click();
        };

        // Добавляем кнопки в меню
        existingMenu.appendChild(addNoteOption);
        existingMenu.appendChild(exportOption);
        existingMenu.appendChild(importOption);
    }

    // Функция для запуска после загрузки DOM
    function init() {
        if (document.querySelector('.subsite-card__header')) {
            displayUserNotes();
            addButtonsToExistingMenu();
        }
    }

    // Оптимизация: debounce для вызова displayUserNotes
    let debounceTimer;
    function debounceDisplayUserNotes() {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => displayUserNotes(), 300); // Задержка 300 мс
    }

    // Отслеживание изменений на странице
    const observer = new MutationObserver((mutationsList) => {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                if (document.querySelector('.subsite-card__header')) {
                    addButtonsToExistingMenu();
                }

                // Вызываем displayUserNotes с задержкой
                debounceDisplayUserNotes();
            }
        }
    });

    // Начинаем наблюдение за изменениями в DOM
    observer.observe(document.body, { childList: true, subtree: true });

    // Запуск функций при загрузке страницы
    init();
})();