- // ==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://gf.qytechs.cn/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.result);
- 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);
- })();