您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add notes to users on dtf.ru
当前为
// ==UserScript== // @name DTF User Notes // @version 1.1 // @description Add notes to users on dtf.ru // @author Avicenna // @match https://dtf.ru/* // @license MIT // @grant none // @namespace http://tampermonkey.net/ // ==/UserScript== (function() { 'use strict'; // Функция для проверки, является ли текущая страница профилем пользователя function isUserProfilePage() { const path = window.location.pathname; return /^\/u\/\d+-[^\/]+(\/comments)?$/.test(path); } // Функция для создания заметки 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 hasBlockOption(menu) { const options = menu.querySelectorAll('.context-list-option__label'); for (let option of options) { if (option.textContent.trim() === 'Заблокировать') { return true; } } return false; } // Функция для добавления кнопок в существующее выпадающее меню function addButtonsToExistingMenu() { const existingMenus = document.querySelectorAll('.context-list'); existingMenus.forEach(menu => { // Проверяем, что это меню профиля пользователя и есть пункт "Заблокировать" if (!isUserProfilePage() || !hasBlockOption(menu)) return; // Проверяем, что кнопки еще не добавлены if (menu.querySelector('.custom-note-button')) return; // Получаем имя пользователя из страницы const usernameElement = document.querySelector('.subsite-card__name h1'); if (!usernameElement) return; const username = usernameElement.textContent; // Получаем ID пользователя из URL const userId = extractUserId(window.location.pathname); if (!userId) 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 = () => 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(); }; // Добавляем разделитель перед нашими кнопками const separator = document.createElement('div'); separator.style.height = '1px'; separator.style.backgroundColor = 'var(--color-border)'; separator.style.margin = '4px 0'; // Добавляем элементы в меню menu.appendChild(separator); menu.appendChild(addNoteOption); menu.appendChild(exportOption); menu.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(); })();