DTF User Notes

Add notes to users on dtf.ru

Fra 26.02.2025. Se den seneste versjonen.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==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();
})();