// ==UserScript==
// @name 快捷输入助手 for DIC Music Reports
// @namespace http://tampermonkey.net/
// @version 1.4
// @description 在报告页面的"说明 (必填):"输入框上方添加快捷输入按钮,可自定义按钮和文本。
// @author Your Name (You can change this)
// @match https://dicmusic.com/reportsv2.php?action=report&id=*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @require https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==
/* globals $, GM_setValue, GM_getValue, GM_addStyle, GM_registerMenuCommand */
(function() {
'use strict';
const MAX_BUTTONS = 50;
const BUTTON_CONFIG_KEY = 'dicmusic_quick_input_buttons';
const SCRIPT_BUTTON_CONTAINER_ID = 'quick-input-buttons-container'; // ID for the div holding buttons
const SCRIPT_BUTTON_TR_ID = 'quick-input-buttons-tr'; // ID for the TR holding the button container
const SETTINGS_MODAL_ID = 'quick-input-settings-modal';
const defaultButtons = [
{ name: '格式错误', text: '种子格式不符合规则,具体说明:' },
{ name: '信息不全', text: '种子信息描述不完整或有误,具体说明:' }
];
let buttonsConfig = GM_getValue(BUTTON_CONFIG_KEY, defaultButtons);
function addGlobalStyles() {
GM_addStyle(`
/* Div that holds all buttons */
#${SCRIPT_BUTTON_CONTAINER_ID} {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 6px;
margin-bottom: 10px; /* Applies if in fallback mode */
}
/* The Table Cell that will contain the button div, if placed in table */
#${SCRIPT_BUTTON_TR_ID} td {
padding-bottom: 10px; /* Space below button bar */
border: none;
}
#${SCRIPT_BUTTON_CONTAINER_ID} button {
background-color: #f0f0f0; border: 1px solid #bbb; color: #333;
padding: 5px 10px; border-radius: 3px; cursor: pointer;
font-size: 0.9em; line-height: 1.2; white-space: nowrap;
}
#${SCRIPT_BUTTON_CONTAINER_ID} button:hover { background-color: #e0e0e0; border-color: #999; }
#${SCRIPT_BUTTON_CONTAINER_ID} .settings-cog-btn { padding: 5px 8px; font-size: 1em; }
#${SETTINGS_MODAL_ID} {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
background-color: white; border: 1px solid #ccc; padding: 20px;
z-index: 10001; box-shadow: 0 4px 8px rgba(0,0,0,0.1);
width: 500px; max-height: 80vh; overflow-y: auto; font-size: 14px;
}
#${SETTINGS_MODAL_ID} h3 { margin-top: 0; border-bottom: 1px solid #eee; padding-bottom: 10px; }
#${SETTINGS_MODAL_ID} .button-entry { display: flex; margin-bottom: 10px; align-items: center; }
#${SETTINGS_MODAL_ID} .button-entry input[type="text"] {
flex-grow: 1; margin-right: 10px; padding: 6px;
border: 1px solid #ccc; border-radius: 3px;
}
#${SETTINGS_MODAL_ID} .button-entry input[name="btn-name"] { max-width: 120px; }
#${SETTINGS_MODAL_ID} .action-buttons button, #${SETTINGS_MODAL_ID} .button-entry button {
padding: 6px 12px; background-color: #5cb85c; color: white;
border: none; border-radius: 3px; cursor: pointer; margin-left: 5px;
}
#${SETTINGS_MODAL_ID} .button-entry .remove-btn { background-color: #d9534f; }
#${SETTINGS_MODAL_ID} .action-buttons button:hover, #${SETTINGS_MODAL_ID} .button-entry button:hover { opacity: 0.9; }
#${SETTINGS_MODAL_ID} .action-buttons { margin-top: 20px; text-align: right; }
#${SETTINGS_MODAL_ID} #add-button-row { background-color: #4682B4; }
.settings-modal-backdrop {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(0,0,0,0.5); z-index: 10000;
}
`);
}
function findCommentsLabelAndTextarea() {
const dynamicForm = $('#dynamic_form');
if (!dynamicForm.length) return null;
let commentsLabelTd = null;
dynamicForm.find('table.layout td.label').each(function() {
if ($(this).text().includes('说明')) {
commentsLabelTd = $(this);
return false;
}
});
if (!commentsLabelTd || !commentsLabelTd.length) return null;
const textarea = commentsLabelTd.next('td').find('textarea');
if (!textarea.length) return null;
return { labelTd: commentsLabelTd, textarea: textarea };
}
function insertTextIntoTextarea(textToInsert) {
const elements = findCommentsLabelAndTextarea();
if (elements && elements.textarea) {
const textarea = elements.textarea[0];
const currentText = textarea.value;
if (currentText.length > 0 && !currentText.endsWith('\n')) {
textarea.value += '\n' + textToInsert;
} else {
textarea.value += textToInsert;
}
textarea.focus();
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true }));
} else {
console.warn("QuickInputHelper: Textarea not found for insertion.");
}
}
function createButtonElement(config) {
const button = $('<button type="button"></button>');
button.text(config.name);
button.attr('title', `点击插入: "${config.text}"`);
button.on('click', function() {
insertTextIntoTextarea(config.text);
});
return button;
}
function renderButtons() {
// Only remove elements specifically created by this script
$(`#${SCRIPT_BUTTON_TR_ID}`).remove(); // Removes the TR if it exists
$(`#${SCRIPT_BUTTON_CONTAINER_ID}`).remove(); // Removes the div if it exists (e.g. from fallback)
const $buttonHostDiv = $(`<div id="${SCRIPT_BUTTON_CONTAINER_ID}"></div>`);
const $settingsButton = $('<button type="button" class="settings-cog-btn" title="设置快捷输入按钮"><i class="fas fa-cog"></i></button>');
$settingsButton.on('click', openSettingsPanel);
$buttonHostDiv.append($settingsButton);
buttonsConfig.forEach(config => {
$buttonHostDiv.append(createButtonElement(config));
});
const elements = findCommentsLabelAndTextarea();
if (elements && elements.labelTd) {
const $parentTR = elements.labelTd.closest('tr');
if ($parentTR.length) {
let colspan = $parentTR.children('td').length;
if (colspan === 0 && $parentTR.parent().children('tr:first-child').length) {
colspan = $parentTR.parent().children('tr:first-child').children('td').length;
}
if (colspan < 1) colspan = 1;
const $containerTR = $(`<tr id="${SCRIPT_BUTTON_TR_ID}"><td colspan="${colspan}"></td></tr>`);
$containerTR.children('td').append($buttonHostDiv);
$parentTR.before($containerTR);
return;
}
}
const $dynamicForm = $('#dynamic_form');
if ($dynamicForm.length) {
$dynamicForm.prepend($buttonHostDiv);
} else {
const $reportForm = $('form[name="report"]');
if ($reportForm.length) {
$reportForm.prepend($buttonHostDiv);
}
}
}
// --- Settings Panel Functions (openSettingsPanel, createSettingEntry, closeSettingsPanel) ---
// These are unchanged from version 1.3, so they are kept concise here for brevity.
// Assume they are correctly implemented as in the previous version.
function openSettingsPanel() { /* ... same as v1.3 ... */
$(`#${SETTINGS_MODAL_ID}`).remove(); $('.settings-modal-backdrop').remove();
const backdrop = $('<div class="settings-modal-backdrop"></div>'); $('body').append(backdrop);
const modal = $(`<div id="${SETTINGS_MODAL_ID}"></div>`);
modal.append('<h3>快捷输入按钮设置</h3>');
const formContainer = $('<div></div>');
buttonsConfig.forEach((btn, index) => { formContainer.append(createSettingEntry(btn, index)); });
modal.append(formContainer);
const addButtonRow = $('<button type="button" id="add-button-row">添加新按钮</button>');
addButtonRow.on('click', function() {
if (formContainer.children('.button-entry').length < MAX_BUTTONS) {
const newIndex = formContainer.children('.button-entry').length;
formContainer.append(createSettingEntry({ name: '', text: '' }, newIndex, true));
} else { alert(`最多只能添加 ${MAX_BUTTONS} 个按钮。`); }
});
const actions = $('<div class="action-buttons"></div>');
const saveButton = $('<button type="button">保存设置</button>');
saveButton.on('click', function() {
const newConfig = [];
formContainer.find('.button-entry').each(function() {
const name = $(this).find('input[name="btn-name"]').val().trim();
const text = $(this).find('input[name="btn-text"]').val().trim();
if (name && text) { newConfig.push({ name, text }); }
});
buttonsConfig = newConfig.slice(0, MAX_BUTTONS);
GM_setValue(BUTTON_CONFIG_KEY, buttonsConfig);
renderButtons(); closeSettingsPanel();
});
const cancelButton = $('<button type="button" style="background-color: #aaa;">取消</button>');
cancelButton.on('click', closeSettingsPanel);
actions.append(addButtonRow).append(saveButton).append(cancelButton);
modal.append(actions); $('body').append(modal);
backdrop.on('click', closeSettingsPanel);
}
function createSettingEntry(btn, index, isNew = false) { /* ... same as v1.3 ... */
const entry = $('<div class="button-entry"></div>');
entry.append(`<label style="margin-right:5px;">${index + 1}.</label>`);
const nameInput = $(`<input type="text" name="btn-name" placeholder="按钮名称" value="${btn.name}">`);
const textInput = $(`<input type="text" name="btn-text" placeholder="快捷文本" value="${btn.text}">`);
const removeButton = $('<button type="button" class="remove-btn">移除</button>');
removeButton.on('click', function() {
$(this).closest('.button-entry').remove();
$(`#${SETTINGS_MODAL_ID} .button-entry`).each(function(i){ $(this).find('label:first-child').text(`${i+1}.`); });
});
entry.append(nameInput).append(textInput).append(removeButton);
if (isNew) nameInput.focus(); return entry;
}
function closeSettingsPanel() { /* ... same as v1.3 ... */ $(`#${SETTINGS_MODAL_ID}`).remove(); $('.settings-modal-backdrop').remove(); }
function initialize() {
addGlobalStyles();
GM_registerMenuCommand('设置快捷输入按钮 (DIC Music)', openSettingsPanel, 's');
renderButtons();
const dynamicFormNode = document.getElementById('dynamic_form');
if (dynamicFormNode) {
const observer = new MutationObserver(function(mutationsList, observerInstance) {
for(let mutation of mutationsList) {
if (mutation.type === 'childList') { // Check if children of dynamic_form changed
const myTableButtonsExist = $(`#${SCRIPT_BUTTON_TR_ID}`).length > 0;
// Check if the div exists and is a direct child of dynamic_form (fallback scenario)
const myFallbackButtonsExist = $(`#dynamic_form > #${SCRIPT_BUTTON_CONTAINER_ID}`).length > 0;
if (!myTableButtonsExist && !myFallbackButtonsExist) {
// This script's buttons are not present in either their primary (table TR)
// or fallback (direct child of dynamic_form) locations.
const reportFormExists = $('form[name="report"]').length > 0;
const dynamicFormStillExists = $('#dynamic_form').length > 0;
if (reportFormExists && dynamicFormStillExists) {
// console.log("QuickInputHelper: Buttons not found. Attempting to re-render.");
renderButtons();
}
}
break; // Process only one relevant mutation from the list
}
}
});
observer.observe(dynamicFormNode, { childList: true, subtree: true });
} else {
let attempts = 0;
const interval = setInterval(() => {
attempts++;
if (document.getElementById('dynamic_form') || attempts > 20) { // Max 10 seconds wait
clearInterval(interval);
if (document.getElementById('dynamic_form')) {
initialize(); // Re-run initialize to attach observer to the now existing form
} else {
console.warn("QuickInputHelper: #dynamic_form not found after waiting. Buttons may not appear correctly.");
}
}
}, 500);
}
}
$(document).ready(function() {
setTimeout(initialize, 750); // Slightly increased delay to allow other scripts potentially more time
});
})();