您需要先安装一个扩展,例如 篡改猴、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();
- })();