您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
支持将 Pixiv “喜欢的标签”添加到搜索栏
// ==UserScript== // @name Pixiv 喜欢标签快捷添加 // @namespace http://tampermonkey.net/ // @version 2025-9-28 // @description 支持将 Pixiv “喜欢的标签”添加到搜索栏 // @author ctrn43062 // @match https://www.pixiv.net/* // @icon https://www.google.com/s2/favicons?sz=64&domain=pixiv.net // @grant none // @license MIT // ==/UserScript== /** * [终极方案] 通过直接调用组件的 onChange prop 来更新 React input 的值。 * 这种方法绕过了事件派发,直接与 React 的内部实例交互。 * @param {HTMLInputElement} element - 目标 input 元素。 * @param {string} value - 你想要设置的新值。 */ function setReactValueByFiber(element, value) { // 1. 查找附加在 DOM 元素上的 React props 对象 const reactPropsKey = Object.keys(element).find(key => key.startsWith('__reactProps$')); if (!reactPropsKey) { console.error("错误: 无法在目标元素上找到 React props。此网站可能使用了不同版本的 React 或有保护措施。"); return; } const props = element[reactPropsKey]; // 2. 检查 onChange 处理器是否存在 if (!props.onChange) { console.error("错误: 目标元素的 props 中没有找到 'onChange' 函数。状态更新可能由父组件处理。"); return; } // 3. 更新 input 的 value 属性 // 这一步至关重要,因为 onChange 函数内部通常会通过 event.target.value 来读取新值 const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, 'value' ).set; nativeInputValueSetter.call(element, value); // 4. 创建一个模拟的事件对象 // 我们不需要派发它,只需要构建一个结构足够让 onChange 函数使用的对象 const simulatedEvent = { target: element, currentTarget: element, }; // 5. 直接调用 onChange 函数! try { props.onChange(simulatedEvent); console.log("成功通过直接调用 onChange 更新了 React 状态。"); } catch (error) { console.error("调用 onChange 函数时出错:", error); } } (function() { 'use strict'; const input = document.querySelector('.charcoal-text-field-input') const waitForElement = (select, max_try=100, interval=20) => { const delay = async () => { return new Promise(resolve => { setTimeout( () => resolve(), interval) } ) } return new Promise(async (resolve, reject) => { let el = null do { console.debug(`Searching ${select}, ${max_try} try remain`) await delay(interval) el = document.querySelectorAll(select) } while ((!el || !el.length) && max_try--) resolve(el) } ) } input.addEventListener('keydown', (event) => { // 检查按下的键是否是 'Enter' // event.key 是现代浏览器推荐的用法 if (event.key === 'Enter') { const newValue = input.value setReactValueByFiber(input, newValue); } }); input.addEventListener('click', async () => { const myTags = await waitForElement('.gtm-my-tag-link') // assert myTags is not empty console.log(myTags) myTags.forEach(tag => { const container = tag.parentElement // 判断是否已有 append 按钮 if(container.getAttribute('data-append')) { return } // 修改 parent 样式 Object.assign(container.style, { 'flex-direction': 'column', 'justify-content': 'center' }) // 添加 append 按钮 const appendTagToSearchButton = document.createElement('span') appendTagToSearchButton.textContent = '+ Append' Object.assign(appendTagToSearchButton.style, { 'text-align': 'center', 'margin-top': '4px', 'cursor': 'pointer', 'white-space': 'nowrap' }) appendTagToSearchButton.className = tag.className appendTagToSearchButton.addEventListener('click', () => { const search = document.querySelector('.charcoal-text-field-input') const tagText = tag.innerText.split(/\s+/).pop().replace(/^#/, ' ') const newValue = search.value + tagText search.value = newValue search.setAttribute('value', newValue) }) container.setAttribute('data-append', true) container.appendChild(appendTagToSearchButton) }) }) })();