您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
给视频或者根评论点踩即可获取并复制隐藏信息,发送<|内容|过期天数||>或者<|内容|过期天数|密码|>到已有评论下可以创建隐藏信息
// ==UserScript== // @name 评论隐藏信息 // @version 2025-08-17-ddd // @description 给视频或者根评论点踩即可获取并复制隐藏信息,发送<|内容|过期天数||>或者<|内容|过期天数|密码|>到已有评论下可以创建隐藏信息 // @author RK // @match *://*.bilibili.com/* // @connect txttool.cn // @icon https://static.hdslb.com/images/favicon.ico // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js#md5=D4Le54swPmFIKP1ejrXgqg== // @require https://cdn.bootcdn.net/ajax/libs/crypto-js/4.1.1/crypto-js.min.js#md5=LKA62HiFq5g1QQkrh62ymQ== // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @grant unsafeWindow // @grant GM_getResourceText // @resource DATA https://api.bilibili.com/x/emote/package?ids=1&business=reply // @license WTFPL // @namespace https://greasyfork.org/users/1503113 // ==/UserScript== (function () { 'use strict'; const oFetch = fetch; var originalSend = XMLHttpRequest.prototype.send; const hateApi = '//api.bilibili.com/x/v2/reply/hate'; const getNoteApi = 'https://api.txttool.cn/netcut/note/info'; const addApi = '//api.bilibili.com/x/v2/reply/add'; const saveNoteApi = 'https://api.txttool.cn/netcut/note/save'; const replyApi = '//api.bilibili.com/x/v2/reply/reply'; const likeApi = '//api.bilibili.com/x/web-interface/archive/like'; const expireTime = new Map(); expireTime.set('1H', 3600); expireTime.set('6H', 21600); expireTime.set('1D', 86400); expireTime.set('3D', 259200); expireTime.set('1W', 604800); expireTime.set('1M', 2592000); expireTime.set('3M', 7776000); expireTime.set('6M', 15552000); expireTime.set('1Y', 31536000); expireTime.set('2Y', 63072000); expireTime.set('3Y', 94608000); function getMessage(oid, rpid) { const params = new URLSearchParams(); params.append('note_name', `_${rpid}`); params.append('note_pwd', oid); GM_xmlhttpRequest({ method: 'GET', url: `${getNoteApi}?${params}`, onload: async (res) => { const jObject = JSON.parse(res.responseText); if (jObject.status == 1) { const note_content = CryptoJS.AES.decrypt(jObject.data.note_content, oid); const escaped = note_content.toString(CryptoJS.enc.Utf8).replaceAll(/\\n/g, '\n'); GM_setClipboard(escaped, 'text'); Swal.fire({ title: '内容已复制', text: escaped, icon: 'success' }); } else if (jObject.status == 4) { const { value: password } = await Swal.fire({ title: '输入密码', input: 'text', inputAttributes: { maxlength: '32', autocapitalize: 'off', autocorrect: 'off', autocomplete: 'off' } }); if (password) { params.set('note_pwd', password); GM_xmlhttpRequest({ method: 'GET', url: `${getNoteApi}?${params}`, onload: (res) => { const jObject = JSON.parse(res.responseText); if (jObject.status == 1) { const note_content = CryptoJS.AES.decrypt(jObject.data.note_content, password); const escaped = note_content.toString(CryptoJS.enc.Utf8).replaceAll(/\\n/g, '\n'); GM_setClipboard(escaped, 'text'); Swal.fire({ title: '内容已复制', text: escaped, icon: 'success' }); } else { Swal.fire({ title: '密码错误', icon: 'error' }); } } }); } } } }); } async function handleHate(urlParams) { const resObject = { code: 111 }; const oid = urlParams.get('oid'); const rpid = urlParams.get('rpid'); getMessage(oid, rpid); return new Response(JSON.stringify(resObject)); } async function handleAdd(urlParams, url, options) { const resObject = { code: 111 }; const oid = urlParams.get('oid'); const message = urlParams.get('message'); const matches = message.match(/<\|([^\|]+)\|([^\|]+)\|([^\|]+)?\|>/); if (matches == null) { return await oFetch(url, options); } let root = urlParams.get('root'); if (root == null || root == '0') { root = oid; } const content = matches[1]; const expire = matches[2].toUpperCase(); const password = matches[3] ?? oid; const params = new URLSearchParams(); params.append('note_name', `_${root}`); params.append('note_content', CryptoJS.AES.encrypt(content, password).toString()); if (expireTime.has(expire)) { params.append('expire_time', expireTime.get(expire)); } else { Swal.fire({ title: '不正确的有效时间', text: `应在[${expireTime.keys().toArray().toString()}]中选择`, icon: 'error' }); resObject.message = '无法发送评论'; return new Response(JSON.stringify(resObject)); } params.append('note_pwd', password); GM_xmlhttpRequest({ method: 'POST', url: saveNoteApi, data: params.toString(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, onload: (res) => { const jObject = JSON.parse(res.responseText); if (jObject.status == 1) { Swal.fire({ title: '成功发送消息', icon: 'success' }); } else { Swal.fire({ title: '消息发送失败', icon: 'error' }); } } }); resObject.message = '已尝试发出消息'; return new Response(JSON.stringify(resObject)); } async function handleReply(urlParams, url, options) { const replyWhiteList = GM_getValue('replyWhiteList', []); const useReply = GM_getValue('useReply', 0); if (useReply == 0 && replyWhiteList.length == 0) { return await oFetch(url, options); } const oid = urlParams.get('oid'); const root = urlParams.get('root'); const params = new URLSearchParams(); params.append('note_name', `_${root}`); params.append('note_pwd', oid); return new Promise((resolve, reject) => { oFetch(url, options) .then(response => response.json()) .then(data => { if (useReply == 0 && !replyWhiteList.includes(data.data.root.mid)) { resolve(new Response(JSON.stringify(data))); return; } GM_xmlhttpRequest({ method: 'GET', url: `${getNoteApi}?${params}`, onload: async (res) => { const jObject = JSON.parse(res.responseText); const moke = { mid: 0, count: 0, rcount: 0, state: 0, ctime: 0, like: 0, replies: null, content: { message: '<无信息>', jump_url: {}, max_line: 32, members: [] }, member: { mid: 0, uname: '匿名', sex: '保密', avatar: 'https://i1.hdslb.com/bfs/face/member/noface.jpg', sign: '', level_info: { current_level: 0, current_min: 0, current_exp: 0, next_exp: 0 } } }; if (jObject.status == 1) { moke.ctime = Math.floor(Date.parse(jObject.data.updated_time) / 1000); const note_content = CryptoJS.AES.decrypt(jObject.data.note_content, oid); const escaped = note_content.toString(CryptoJS.enc.Utf8).replaceAll(/\\n/g, '\n'); moke.content.message = escaped; moke.content.emote = {}; for (let eitem of (escaped.match(/\[[^\[\]]+\]/g) ?? [])) { const emoteInfo = DATA.data.packages[0].emote.find(item => item.text == eitem); if (emoteInfo) { moke.content.emote[emoteInfo.text] = emoteInfo; } } data.data.replies.unshift(moke); } resolve(new Response(JSON.stringify(data))); } }); }); }); } async function handleLike(urlParams) { const like = urlParams.get('like'); if (like != 2) { return false; } const aid = urlParams.get('aid'); getMessage(aid, aid); return true; } async function mFetch(url, options) { const params = url.substring(url.indexOf('?') + 1); if (url.startsWith(hateApi) || url.startsWith(`https:${hateApi}`)) { return await handleHate(new URLSearchParams(options.body ?? params)); } else if (url.startsWith(addApi) || url.startsWith(`https:${addApi}`)) { return await handleAdd(new URLSearchParams(options.body ?? params), url, options); } else if (url.startsWith(replyApi) || url.startsWith(`https:${replyApi}`)) { return await handleReply(new URLSearchParams(options.body ?? params), url, options); } /* else if (url.startsWith(likeApi) || url.startsWith(`https:${likeApi}`)) { return await handleLike(new URLSearchParams(options.body ?? params), url, options); } */ return await oFetch(url, options); } window.unsafeWindow.fetch = mFetch; XMLHttpRequest.prototype.send = function (args) { const url = this._url; const params = new URLSearchParams(args); if (url) { if (url.startsWith(likeApi) || url.startsWith(`https:${likeApi}`)) { if (handleLike(params)) { arguments[0] = arguments[0]?.replace('like=2', 'like=1'); } } } else if (params.has('aid') && params.has('like')) { if (handleLike(params)) { arguments[0] = arguments[0]?.replace('like=2', 'like=1'); } } originalSend.apply(this, arguments); }; const DATA = JSON.parse(GM_getResourceText('DATA')); GM_registerMenuCommand('设置查看回复获取', async function () { const useReply = GM_getValue('useReply', 0); const { value: accept } = await Swal.fire({ title: '设置查看回复获取', input: 'checkbox', inputValue: useReply, inputPlaceholder: '每次查看回复都获取信息', confirmButtonText: '确认', }); GM_setValue('useReply', accept ?? 0); }); GM_registerMenuCommand('设置查看回复白名单', async function () { const replyWhiteList = GM_getValue('replyWhiteList', []); const { value: text } = await Swal.fire({ input: 'textarea', inputLabel: '白名单列表', inputPlaceholder: '输入包含UP主UID的列表,此列表内的评论始终会尝试获取', inputValue: JSON.stringify(replyWhiteList), showCancelButton: true }); if (text) { GM_setValue('replyWhiteList', JSON.parse(text)); } }); })();