Based on AttachHowOldtoUserinPosts, to tag users in Bangumi. Now with clickable badges to edit notes.
// ==UserScript== // @name TagBangumiUserPro 班固米用户备注 // @version 3.4.1 // @description Based on AttachHowOldtoUserinPosts, to tag users in Bangumi. Now with clickable badges to edit notes. // @author age_anime // @match https://bgm.tv/* // @match https://chii.in/* // @match https://bangumi.tv/* // @grant none // @license MIT // @namespace https://greasyfork.org/users/1426310 // ==/UserScript== (function () { 'use strict'; // 1.2版本说明:增加详细备注功能,鼠标点击备注可显示详细备注,再次点击备注可以修改备注,支持分别编辑备注和详细备注。删除备注的同时也会清空详细备注。 // 组件函数功能说明:(数据保存在本地,显示和修改在前端,可直接修改和清除) // displayUserNote(userId, note):在指定用户的用户名旁边显示备注标签 // markUserNotes():显示对应的备注标签 // openUserNoteManager():用户备注管理界面 // openColorManager():颜色管理界面 // addButtonFunctions():添加功能按钮 const defaultNoteColors = { "预设0": "#40E0D0", "预设1": "#BB44BB", "预设2": "#000000", "预设3": "#DD6D22", "预设4": "#CC3333", "预设5": "#DD6D22", }; let noteColors = JSON.parse(localStorage.getItem('userNoteColors_')) || defaultNoteColors; // 用户备注配置(默认为空白) let userNoteTable_AGE = JSON.parse(localStorage.getItem('userNoteTable_AGE')) || {}; // 迁移旧版本数据结构 function migrateUserNotes() { let needMigration = false; // 检查是否需要兼容 Object.keys(userNoteTable_AGE).forEach(userId => { if (typeof userNoteTable_AGE[userId] === 'string') { needMigration = true; } }); if (needMigration) { const newUserNoteTable = {}; // 旧版本兼容:兼容v3.4(b1.2)之前的版本 Object.keys(userNoteTable_AGE).forEach(userId => { if (typeof userNoteTable_AGE[userId] === 'string') { newUserNoteTable[userId] = [userNoteTable_AGE[userId], ""]; } else { newUserNoteTable[userId] = userNoteTable_AGE[userId]; } }); userNoteTable_AGE = newUserNoteTable; localStorage.setItem('userNoteTable_AGE', JSON.stringify(userNoteTable_AGE)); } // 旧版本兼容 Object.keys(localStorage).forEach(key => { if (key.startsWith('userNote_')) { const userId = key.substring('userNote_'.length); // 兼容v3.0之前的版本 const noteValue = localStorage.getItem(key); if (noteValue === "bgmer") { // 兼容v2.7以及之前的版本 localStorage.removeItem(key); } else { userNoteTable_AGE[userId] = [noteValue, ""]; localStorage.removeItem(key); } } }); localStorage.setItem('userNoteTable_AGE', JSON.stringify(userNoteTable_AGE)); } migrateUserNotes(); const defaultNote = "…"; const defaultColor = "rgba(255, 255, 255, 0)"; const defaultNoteColor = "rgba(102, 170, 85, 0.2)"; const defaultSecondaryNote = "点击修改详细备注..."; function displayUserNote(userId, notes) { const userAnchor = $("strong a.l[href='/user/" + userId + "']"); if (userAnchor.length > 0 && userAnchor.next(".note-badge").length === 0) { let primaryNote, secondaryNote; if (Array.isArray(notes)) { [primaryNote, secondaryNote] = notes; secondaryNote = secondaryNote || ""; } else { primaryNote = notes; secondaryNote = ""; } const badgeColor = (primaryNote === defaultNote) ? defaultColor : (noteColors[primaryNote] || defaultNoteColor); const noteColorSet = badgeColor === defaultColor ? "#9F9F9F" : "#FFF"; const badge = $(` <span class="note-badge" style=" background: ${badgeColor}; font-size: 10px; padding: 1px 4px; color: ${noteColorSet}; border-radius: 6px; line-height: 150%; display: inline-block; cursor: pointer; position: relative; " title="双击修改备注">${primaryNote}</span>`); const badgeId = 'note-badge-' + userId + '-' + Math.random().toString(36).substr(2, 9); badge.attr('id', badgeId); badge.attr('data-user-id', userId); badge.attr('data-primary-note', primaryNote); badge.attr('data-secondary-note', secondaryNote); badge.attr('data-secondary-loaded', 'false'); // 一级备注点击事件 badge.click(function(e) { const userId = $(this).attr('data-user-id'); const primaryNote = $(this).attr('data-primary-note'); const storedSecondaryNote = $(this).attr('data-secondary-note'); const secondaryLoaded = $(this).attr('data-secondary-loaded') === 'true'; const $target = $(e.target); const isInteractiveElement = $target.is('a, button, [contenteditable="true"], input, select, textarea'); const isSecondaryArea = $target.closest('.secondary-note').length > 0; // 处理二级备注点击 if (isSecondaryArea) { if (isInteractiveElement) { return; } // 处理二级备注编辑 const newSecondaryNote = prompt("请输入详细备注:", storedSecondaryNote || ""); if (newSecondaryNote !== null) { const trimmedSecondaryNote = newSecondaryNote.trim(); if (primaryNote === defaultNote && trimmedSecondaryNote === "") { delete userNoteTable_AGE[userId]; localStorage.setItem('userNoteTable_AGE', JSON.stringify(userNoteTable_AGE)); $(this).remove(); } else { if (Array.isArray(userNoteTable_AGE[userId])) { userNoteTable_AGE[userId][1] = trimmedSecondaryNote; } else { userNoteTable_AGE[userId] = [primaryNote, trimmedSecondaryNote]; } localStorage.setItem('userNoteTable_AGE', JSON.stringify(userNoteTable_AGE)); $target.closest('.secondary-note').text(trimmedSecondaryNote || defaultSecondaryNote) .css('color', trimmedSecondaryNote ? '#333' : '#999'); $(this).attr('data-secondary-note', trimmedSecondaryNote); } } e.stopPropagation(); return; } // 处理一级备注逻辑 let currentSecondaryBadge = $(this).find('.secondary-note'); if (!secondaryLoaded) { currentSecondaryBadge = $(` <div class="secondary-note" style=" display: none; position: absolute; top: 100%; left: 0; width: 200px; background: white; border: 1px solid #ccc; border-radius: 4px; padding: 5px; margin-top: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 1000; font-size: 12px; color: ${storedSecondaryNote ? '#333' : '#999'}; cursor: pointer; max-height: 400px; overflow-y: auto; word-wrap: break-word; resize: both; " title="点击编辑">${storedSecondaryNote || defaultSecondaryNote}</div>`); currentSecondaryBadge.on('click', 'a', function(e) { e.stopPropagation(); }); $(this).append(currentSecondaryBadge); $(this).attr('data-secondary-loaded', 'true'); } if (currentSecondaryBadge.is(':visible')) { const newNote = prompt("请输入新的备注:", primaryNote); if (newNote !== null) { const trimmedNote = newNote.trim(); if (trimmedNote === "" || trimmedNote === defaultNote) { delete userNoteTable_AGE[userId]; localStorage.setItem('userNoteTable_AGE', JSON.stringify(userNoteTable_AGE)); $(this).remove(); } else if (trimmedNote !== primaryNote) { if (Array.isArray(userNoteTable_AGE[userId])) { userNoteTable_AGE[userId][0] = trimmedNote; } else { userNoteTable_AGE[userId] = [trimmedNote, storedSecondaryNote]; } localStorage.setItem('userNoteTable_AGE', JSON.stringify(userNoteTable_AGE)); $(this).contents().first().replaceWith(document.createTextNode(trimmedNote)); const newBadgeColor = noteColors[trimmedNote] || defaultNoteColor; $(this).css("background", newBadgeColor); $(this).attr('data-primary-note', trimmedNote); } } } else { $('.secondary-note').hide(); currentSecondaryBadge.show(); } }); // 点击其他区域关闭二级备注 if (!window.noteClickHandlerAdded) { $(document).on('click', function(e) { if (!$(e.target).closest('.note-badge').length) { $('.secondary-note').hide(); } }); window.noteClickHandlerAdded = true; } userAnchor.after(badge); } } function markUserNotes() { $(".note-badge").remove(); $("strong a.l:not(.avatar)").each(function () { const userLink = $(this).attr("href"); const userId = userLink.split("/").pop(); const notes = userNoteTable_AGE[userId] || defaultNote; displayUserNote(userId, notes); }); } // 按钮颜色管理区 function openColorManager() { const modal = document.createElement('div'); Object.assign(modal.style, { position: 'fixed', left: '50%', top: '50%', transform: 'translate(-50%, -50%)', backgroundColor: 'white', padding: '20px', borderRadius: '5px', boxShadow: '0 0 10px rgba(0,0,0,0.5)', zIndex: '9999', width: '300px' }); // 添加颜色 const form = document.createElement('div'); form.style.marginBottom = '5px'; const nameLabel = document.createElement('label'); nameLabel.textContent = '名称:'; nameLabel.style.color = '#333333'; nameLabel.style.marginRight = '5px'; const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.style.minWidth = '240px'; nameInput.style.width = 'auto'; nameInput.style.height = '22px'; nameInput.style.marginRight = '5px'; nameInput.style.position = 'relative'; nameInput.style.top = '-5px'; const colorLabel = document.createElement('label'); colorLabel.textContent = '颜色:'; colorLabel.style.color = '#333333'; colorLabel.style.marginRight = '5px'; const colorInput = document.createElement('input'); colorInput.type = 'color'; colorInput.style.marginRight = '5px'; const addButton = document.createElement('button'); addButton.textContent = '添加'; addButton.onclick = () => { const name = nameInput.value.trim(); const color = colorInput.value; if (name && color) { noteColors[name] = color; textarea.value = JSON.stringify(noteColors, null, 4); nameInput.value = ''; colorInput.value = '#000000'; } else { alert('名称和颜色不能为空!'); } }; form.append(nameLabel, nameInput, colorLabel, colorInput, addButton); const textarea = document.createElement('textarea'); textarea.style.cssText = 'width: 100%; height: 200px; margin-bottom: 10px;'; textarea.value = JSON.stringify(noteColors, null, 4); const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'text-align: right;'; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.onclick = () => modal.remove(); const saveBtn = document.createElement('button'); saveBtn.textContent = '保存'; saveBtn.style.marginLeft = '8px'; saveBtn.onclick = () => { try { const newColors = JSON.parse(textarea.value); if (typeof newColors !== 'object' || newColors === null) { throw new Error('请输入有效的JSON对象'); } localStorage.setItem('userNoteColors_', JSON.stringify(newColors)); noteColors = newColors; document.querySelectorAll('.note-badge').forEach(badge => { const note = badge.textContent; badge.style.backgroundColor = noteColors[note] || defaultNoteColor; }); modal.remove(); } catch (error) { alert('错误: 请检查JSON格式(其他行的末尾是有英文逗号的,最后一行的末尾是没有逗号的!),还不会就把错误代码和JSON内容放在AI里面问问: ' + error.message); } }; buttonContainer.append(cancelBtn, saveBtn); modal.append(form, textarea, buttonContainer); document.body.appendChild(modal); } // 用户备注管理区 function openUserNoteManager() { const modal = document.createElement('div'); Object.assign(modal.style, { position: 'fixed', left: '50%', top: '50%', transform: 'translate(-50%, -50%)', backgroundColor: 'white', padding: '20px', borderRadius: '5px', boxShadow: '0 0 10px rgba(0,0,0,0.5)', zIndex: '9999', width: '300px' }); // 添加上方输入框 const form = document.createElement('div'); form.style.marginBottom = '10px'; const idInput = document.createElement('input'); idInput.placeholder = '用户ID'; idInput.style.marginRight = '5px'; const noteInput = document.createElement('input'); noteInput.placeholder = '备注'; noteInput.style.marginRight = '5px'; const secondaryNoteInput = document.createElement('input'); secondaryNoteInput.placeholder = '详细备注(可选)'; secondaryNoteInput.style.marginRight = '5px'; secondaryNoteInput.style.width = '100%'; const addButton = document.createElement('button'); addButton.textContent = '添加'; addButton.onclick = () => { const userId = idInput.value.trim(); const note = noteInput.value.trim(); const secondaryNote = secondaryNoteInput.value.trim(); if (userId && note) { userNoteTable_AGE[userId] = [note, secondaryNote]; textarea.value = JSON.stringify(userNoteTable_AGE, null, 4); idInput.value = ''; noteInput.value = ''; secondaryNoteInput.value = ''; } }; form.append(idInput, noteInput, document.createElement('br'), secondaryNoteInput, addButton); // JSON编辑区 const textarea = document.createElement('textarea'); textarea.style.cssText = 'width: 100%; height: 200px; margin: 10px 0;'; textarea.value = JSON.stringify(userNoteTable_AGE, null, 4); const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'text-align: right;'; const importBtn = document.createElement('button'); importBtn.textContent = '导入'; importBtn.onclick = () => { const input = document.createElement("input"); input.type = "file"; input.accept = "application/json"; input.onchange = function (event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function (e) { try { const imported = JSON.parse(e.target.r###lt); userNoteTable_AGE = imported; // 直接替换当前内容 textarea.value = JSON.stringify(userNoteTable_AGE, null, 4); localStorage.setItem('userNoteTable_AGE', JSON.stringify(userNoteTable_AGE)); markUserNotes(); // 刷新页面显示 alert("导入成功!"); } catch (error) { alert("错误: 请检查JSON格式(其他行的末尾是有英文逗号的,最后一行的末尾是没有逗号的!),还不会就把错误代码和JSON内容放在AI里面询问:" + error); } }; reader.readAsText(file); } }; input.click(); }; const exportBtn = document.createElement('button'); exportBtn.textContent = '导出'; exportBtn.style.marginLeft = '5px'; exportBtn.onclick = () => { const blob = new Blob([textarea.value], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `user_notes_${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); }; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.marginLeft = '5px'; cancelBtn.onclick = () => modal.remove(); const saveBtn = document.createElement('button'); saveBtn.textContent = '保存'; saveBtn.style.marginLeft = '5px'; saveBtn.onclick = () => { try { const newNotes = JSON.parse(textarea.value); localStorage.setItem('userNoteTable_AGE', JSON.stringify(newNotes)); userNoteTable_AGE = newNotes; markUserNotes(); modal.remove(); } catch (error) { alert('JSON格式错误:' + error.message); } }; buttonContainer.append(importBtn, exportBtn, cancelBtn, saveBtn); modal.append(form, textarea, buttonContainer); document.body.appendChild(modal); } function addButtonFunctions() { const badgeUserPanel = document.querySelector("ul#badgeUserPanel"); if (badgeUserPanel) { // 用户备注管理按钮 const userNoteLi = document.createElement('li'); const userNoteBtn = document.createElement('a'); userNoteBtn.href = '#'; userNoteBtn.textContent = '管理用户备注'; userNoteBtn.onclick = (e) => { e.preventDefault(); openUserNoteManager(); }; userNoteLi.appendChild(userNoteBtn); // 颜色管理按钮 const colorLi = document.createElement('li'); const colorBtn = document.createElement('a'); colorBtn.href = '#'; colorBtn.textContent = '管理颜色配置'; colorBtn.onclick = (e) => { e.preventDefault(); openColorManager(); }; colorLi.appendChild(colorBtn); badgeUserPanel.append(userNoteLi, colorLi); } } if (document.body) { markUserNotes(); } else { window.onload = markUserNotes; } setTimeout(addButtonFunctions, 200); })();