您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
搜索页面的高效操作
// ==UserScript== // @name 抖音搜索结果检测作者是否存在 // @namespace http://tampermonkey.net/ // @version 1.8 // @description 搜索页面的高效操作 // @author You // @match https://www.douyin.com/*/search/* // @match https://www.douyin.com/search/* // @match https://search.bilibili.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=douyin.com // @grant none // @license MIT // ==/UserScript== var aggrx_searchList = []; // document.querySelectorAll('#search-content-area ul[data-e2e="scroll-list"] li'); var aggrx_scrollListHeight = 0; //aggrx_searchList[0].clientHeight + 70; // 复制文本 function aggrx_copyToClipboard(text, copy_success, copy_failed) { navigator.clipboard.writeText(text).then(function() { console.log('Text successfully copied to clipboard'); if (copy_success) { copy_success(text) } }).catch(function(err) { console.error('Unable to copy text to clipboard', err); if (copy_failed) { copy_failed(err) } }); } // 添加 复制合集按钮DOM function aggrx_addCopyCollectionButtonDom(LiElement, page_type) { const divBOX = document.createElement('div'); divBOX.className = 'aggrx_buttonBox'; //divBOX.style.width = '100%'; divBOX.style.height = '40px'; const button1 = document.createElement('button'); // button1.className = 'aggrx_buttonBox_button' button1.classList.add('aggrx_buttonBox_button'); button1.classList.add(`aggrx_buttonBox_button-${page_type}`); // 设置按钮的文本 button1.textContent = '视频连接'; // 设置按钮的宽度和高度 //button1.style.width = '49%'; //button1.style.height = '40px'; //button1.style.float = 'left'; function copy_success(msg) { aggrx_showToast('info', `已复制 ${msg}`) } function copy_failed(error) { aggrx_showToast('error', `复制失败 ${error}`) } if (page_type === 'search-douyin') { if (window.location.href.includes('type=video')) { // 拦截按钮的点击事件 button1.addEventListener('click', (event) => { // 阻止事件冒泡 event.stopPropagation(); // 获取父元素 const parentElement = event.target.closest('li'); let videoUrl = parentElement.querySelector('div.search-result-card a').href; let authorDom = parentElement.querySelectorAll("div.search-result-card > a > div > div > div > div > span > span")[1]; if (!authorDom) { console.warn(`没有找到作者节点 ${parentElement}`) return; } let authorName = authorDom.innerText if (!authorName) { console.warn(`作者名称是空`) return; } authorName = authorName.replace(' 🈚️', '').replace(' 🈶', '') console.info(`检查 ${authorName} 视频URL:${videoUrl}`) // 使用正则表达式从 href 中提取视频 ID const regex = /\/video\/(\d+)/; const match = videoUrl.match(regex); if (!(match && match[1])) { return } let videoId = match[1]; console.log('视频 ID:', videoId); let collectionData = window.collectionIDObject[videoId] // copy_button_ref.textContent = "复制"; let href = new URL(videoUrl); href.search = ''; let hrefString = href.toString() if (collectionData.collectionID) { aggrx_copyToClipboard(`${hrefString},1`, copy_success, copy_failed) } else { aggrx_copyToClipboard(`${hrefString}`, copy_success, copy_failed) } }); } else if (window.location.href.includes('type=general')) { divBOX.classList.add('aggrx-button-box-general') button1.addEventListener('click', (event) => { // 阻止事件冒泡 event.stopPropagation(); // 获取父元素 const parentElement = event.target.closest('div[id^="waterfall_item_"]') let videoUrl = `https://www.douyin.com/video/${parentElement.getAttribute('id').replace('waterfall_item_', '')}`; let authorDom = parentElement.querySelectorAll("div.search-result-card span > span")[1]; if (!authorDom) { console.warn(`没有找到作者节点 ${parentElement}`) return; } let authorName = authorDom.innerText if (!authorName) { console.warn(`作者名称是空`) return; } authorName = authorName.replace(' 🈚️', '').replace(' 🈶', '') console.info(`检查 ${authorName} 视频URL:${videoUrl}`) //let videoId = match[1]; // console.log('视频 ID:', videoId); //let collectionData = window.collectionIDObject[videoId] // copy_button_ref.textContent = "复制"; let href = new URL(videoUrl); href.search = ''; let hrefString = href.toString() let hasCollection = Array.from(parentElement.querySelectorAll('div.search-result-card span')).map((item)=>item.innerText).join('').includes('合集') if (hasCollection) { aggrx_copyToClipboard(`${hrefString},1`, copy_success, copy_failed) } else { aggrx_copyToClipboard(`${hrefString}`, copy_success, copy_failed) } }); } else { aggrx_showToast('error', '不支持的页面, 抖音支支持`视频`和`综合`页面') } } else if (page_type === 'search-bilibili') { // 拦截按钮的点击事件 button1.addEventListener('click', (event) => { // 阻止事件冒泡 event.stopPropagation(); const copy_button_ref = event.currentTarget; // 获取父元素 const parentElement = event.target.closest('div.bili-video-card'); let videoUrl = parentElement.querySelector('div.bili-video-card a').href; let authorDom = parentElement.querySelectorAll("div.bili-video-card .bili-video-card__info--author")[0]; if (!authorDom) { console.warn(`没有找到作者节点 ${parentElement}`) return; } let authorName = authorDom.innerText if (!authorName) { console.warn(`作者名称是空`) return; } authorName = authorName.replace(' 🈚️', '').replace(' 🈶', '') console.info(`检查 ${authorName} 视频URL:${videoUrl}`) // copy_button_ref.textContent = "复制"; let href = new URL(videoUrl); href.search = ''; let hrefString = href.toString() if (0) { aggrx_copyToClipboard(`${hrefString},1`, copy_success, copy_failed) } else { aggrx_copyToClipboard(`${hrefString}`, copy_success, copy_failed) } }); } else { console.warn('不支持的页面类型') } divBOX.appendChild(button1); // 设置 li 元素的高度 LiElement.style.height = `${aggrx_scrollListHeight}px`; LiElement.style.marginBottom = `50px`; // 添加按钮到当前 li 元素 LiElement.appendChild(divBOX); // DOMUpdated() return true } // 拦截请求 function aggrx_requestInterception() { let page_type = aggrx_get_page_type(window.location.href) if (page_type !== 'search-douyin') { // aggrx_showToast('error', '不支持的页面, 请切换到正确的标签再使用本插件. 抖音需要切换到`视频`页面') return; } window.collectionIDObject = {}; // 保存原始的 open 和 send 方法 const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; // 重写 open 方法,记录请求信息 XMLHttpRequest.prototype.open = function(method, url, async, user, password) { this._requestInfo = { method, url }; // 保存请求的 URL 和方法 return originalOpen.apply(this, arguments); }; // 重写 send 方法,拦截响应数据 XMLHttpRequest.prototype.send = function(body) { const xhr = this; // 保存请求体数据 this._requestInfo.body = body; // 保存原始的 onreadystatechange const originalOnReadyStateChange = xhr.onreadystatechange; // 重写 onreadystatechange xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { const { url } = xhr._requestInfo; // 判断是否是特定接口 if (url.includes('/aweme/v1/web/search/item/')) { console.log(`Request to: ${url}`); try { const parsedResponse = JSON.parse(xhr.responseText); parsedResponse.data.forEach(datum => { let aweme_info = datum.aweme_info; window.collectionIDObject[datum.aweme_info.aweme_id] = { videoID: aweme_info.aweme_id, authorName: aweme_info.nickname, authorUID: aweme_info.author.uid, desc: aweme_info.desc, tag: aweme_info.text_extra }; if (datum.aweme_info.mix_info) { let collectionData = datum.aweme_info.mix_info; window.collectionIDObject[datum.aweme_info.aweme_id].collectionID = collectionData.mix_id; window.collectionIDObject[datum.aweme_info.aweme_id].collectionName = collectionData.mix_name; } }) } catch (e) { console.error('Error parsing response:', e); } } } // 调用原始的回调函数 if (originalOnReadyStateChange) { originalOnReadyStateChange.apply(xhr, arguments); } }; return originalSend.apply(this, arguments); }; } // 拦截请求 aggrx_requestInterception(); function aggrx_get(url, success, failed) { // 创建一个新的 XMLHttpRequest 实例 let xhr = new XMLHttpRequest(); // 配置请求类型和目标 URL xhr.open('GET', url, true); // 设置请求头 xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json'); // 定义请求体数据 //var data = JSON.stringify({ // "url": `http://new_bms.yingshidq.com.cn/api/inside/author/nametoid?authorName=#{authorName}`, // 烟火中的水滴 // "type": "text" //}); // 监听请求完成的回调 xhr.onreadystatechange = function() { if (xhr.readyState !== 4) { return } if (xhr.status >= 200 && xhr.status < 300) { // 请求成功时处理响应 // console.log('Response:', xhr.responseText); try { // 尝试将响应文本转换为 JSON var responseJson = JSON.parse(xhr.responseText); success(responseJson) } catch (e) { // 如果 JSON 解析失败,捕获并处理错误 console.error('Error parsing JSON:', e); failed({ 'error': e }); } } else { failed({ 'error': 'other Error' }); } }; // 处理网络错误 xhr.onerror = function() { console.error('Network error or CORS issue'); failed({ 'error': 'other error' }); }; // 发送请求 xhr.send(); } function aggrx_updateAuthorCheckResult(authorName, authorDom, page_type, response) { console.info(`response=`, response) try { let data_obj = response.data || {}; let author_id = `${data_obj.has_author_id}` || 'false'; if (author_id == 'true') { console.info(`检测作者 ${authorName} 请求成功, 🈶 数据 ${author_id}`); authorDom.innerText = `${authorName} 🈶`; authorDom.style.color = 'red'; } else if (author_id == 'false') { authorDom.innerText = `${authorName} 🈚️`; authorDom.style.color = 'green'; console.warn(`检测作者 ${authorName} 请求成功, 🈚️ 数据 `); } else { console.warn(`${authorName} 不支持的数据`, response); } //DOMUpdated() } catch (e) { console.error(`检测作者 ${authorName} 接口数据格式错误. ${e}`); } } let aggrx_queue = [] // 发送队列 let aggrx_queue_running = false function aggrx_queue_start() { if (aggrx_queue_running) { return } queue_send() aggrx_queue_running = true; function queue_send() { let task = aggrx_queue.shift() if (!task) { aggrx_queue_running = false return; } aggrx_queue_running = true; let authorName = task[0] let authorDom = task[1] let page_type = task[2] console.info(`${Date()} ${authorName} 出队`) //const callbackName = `jsonpCallback_${crypto.randomUUID().replace(/-/g, '')}_${Date.now()}`; //console.info(`${authorName} callback: ${callbackName} `) // &callback=${callbackName} function startNext() { setTimeout(() => { queue_send() }, 100) } if (0) { aggrx_queue_check_user_api(authorName, authorDom, page_type, () => { startNext() }, () => { startNext() }) } else { aggrx_queue_check_user_jsonp(authorName, authorDom, page_type, () => { startNext() }, () => { startNext() }) } } } function aggrx_queue_check_user_api(authorName, authorDom, page_type, success, failed) { aggrx_get(`https://openapi.yingshidq.com.cn/api/sv/v1/author/list-out?authorName=${authorName}`, (response) => { setTimeout(() => { aggrx_updateAuthorCheckResult(authorName, authorDom, response) success() }, 100) }, (error) => { console.error(`检测作者 ${authorName} 请求失败`, error) setTimeout(() => { failed() }, 100) }) } function aggrx_queue_check_user_jsonp(authorName, authorDom, page_type, success, failed) { // 定义全局回调函数名称,确保唯一性 const callbackName = `jsonpCallback_${crypto.randomUUID().replace(/-/g, '')}_${Date.now()}`; console.info(`${authorName} callback: ${callbackName} `) // 创建一个全局回调函数 window[callbackName] = function(response) { let timeout = window[`${callbackName}-timeout`] if (timeout) { window[`${callbackName}-timeout`] = undefined clearTimeout(timeout); } // 删除全局回调函数以清理空间 delete window[callbackName]; // 删除动态创建的 script 标签 let script = window[`${callbackName}-script`] if (script) { window[`${callbackName}-script`] = undefined document.body.removeChild(script); } // 解析响应数据并调用回调 setTimeout(() => { aggrx_updateAuthorCheckResult(authorName, authorDom, page_type, response) success() }, 200) }; // 动态创建 script 标签 const script = document.createElement('script'); script.id = callbackName; // 设置 id script.src = `https://openapi.yingshidq.com.cn/api/sv/v1/author/list-out?authorName=${authorName}&callback=${callbackName}`; window[`${callbackName}-script`] = script // 处理加载失败的情况 script.onerror = function() { // 清理回调和 script let timeout = window[`${callbackName}-timeout`] if (timeout) { window[`${callbackName}-timeout`] = undefined clearTimeout(timeout); } delete window[callbackName]; let script = window[`${callbackName}-script`] if (script) { window[`${callbackName}-script`] = undefined document.body.removeChild(script); } console.error(`检测作者 ${authorName} 请求失败`); failed() }; // 将 script 标签添加到文档中以触发请求 document.body.appendChild(script); window[`${callbackName}-timeout`] = setTimeout(() => { console.error(`检测作者 ${authorName} 请求超时`); delete window[callbackName]; let script = window[`${callbackName}-script`] if (script) { window[`${callbackName}-script`] = undefined document.body.removeChild(script); } failed() }, 5000); } // 检查作者是否入库 function aggrx_checkApiUserJSONP(authorName, authorDom, page_type) { console.info(`${Date()} ${authorName} 入队`) aggrx_queue.push([authorName, authorDom, page_type]) aggrx_queue_start() return } /** * 检查作者 * @param {HtmlElement} liDom * @param {string} page_type */ function aggrx_checkAuthor(liDom, page_type) { try { let authorDom = undefined if (page_type === 'search-douyin') { if (window.location.href.includes('type=video')) { authorDom = liDom.querySelectorAll("div.search-result-card > a > div > div > div > div > span > span")[1]; } else if (window.location.href.includes('type=general')) { let parentElement = liDom authorDom = parentElement.querySelectorAll("div.search-result-card span > span")[1]; } else { aggrx_showToast('error', '不支持的页面, 抖音支支持`视频`和`综合`页面') } } else if (page_type === 'search-bilibili') { authorDom = liDom.querySelectorAll("div.bili-video-card .bili-video-card__info--author")[0]; } else { console.warn(`不支持的页面类型 ${page_type}`) return; } if (!authorDom) { console.warn(`没有找到作者节点 ${liDom}`) return; } let authorName = authorDom.innerText if (!authorName) { console.warn(`作者名称是空`) return; } console.info(`检查 ${authorName}`) if (authorName.includes("🈶")) { console.info(`${authorName} 已经检查过`); return }; if (authorName.includes("🈚️")) { console.info(`${authorName} 已经检查过`); return }; aggrx_checkApiUserJSONP(authorName, authorDom, page_type); } catch (error) { console.error('检查作者dom 错误!!!!', error) } } // 监听 列表dom function aggrx_targetListNode(page_type, observer_el_selector) { // 监听 const targetNode = document.querySelector(observer_el_selector); if (!targetNode){ return false } // 创建一个 MutationObserver 实例 const observer = new MutationObserver(mutationsList => { // 遍历所有的 mutations mutationsList.forEach(mutation => { // 只有子节点变动才执行 if (mutation.type !== 'childList') { return } // 为每个 li 元素添加按钮 mutation.addedNodes.forEach((li, index) => { // 确保每个 li 只添加一个按钮 if (li.querySelector('div.aggrx_buttonBox')) { console.info("已经处理过了") return } aggrx_addCopyCollectionButtonDom(li, page_type); aggrx_checkAuthor(li, page_type); }); }); }); // 配置观察器 启动观察 服务 const config = { childList: true, subtree: false }; observer.observe(targetNode, config); return true } let initialized = {}; // 初始化 function aggrxInitialization(event) { event.stopPropagation(); let page_type = aggrx_get_page_type(window.location.href) if (!page_type) { aggrx_showToast('error', '不支持的页面, 请切换到正确的标签再使用本插件. 抖音需要切换到`视频`页面') return; } if (page_type === 'search-douyin') { let video_page_observer_el = '#search-content-area ul[data-e2e="scroll-list"]' if (!(initialized[video_page_observer_el] || false)) { initialized[video_page_observer_el] = aggrx_targetListNode(page_type,video_page_observer_el); } let general_page_observer_el = 'div#waterFallScrollContainer' if (!(initialized[general_page_observer_el] || false)) { initialized[general_page_observer_el] = aggrx_targetListNode(page_type,general_page_observer_el); } } // document.getElementById('aggrxInitializationButton').innerText = '初始化中'; // 初始化遍历每个 li 元素 if (page_type === 'search-douyin') { if (window.location.href.includes('type=video')) { aggrx_searchList = document.querySelectorAll('#search-content-area ul[data-e2e="scroll-list"] li'); } else if (window.location.href.includes('type=general')) { aggrx_searchList = document.querySelectorAll('div#waterFallScrollContainer div[id^="waterfall_item_"]'); } else { aggrx_showToast('error', '不支持的页面, 抖音支支持`视频`和`综合`页面') } aggrx_scrollListHeight = aggrx_searchList[0].clientHeight + 70; } else if (page_type == 'search-bilibili') { // aggrx_searchList = document.querySelectorAll('div.search-all-list > .video-list div.bili-video-card'); aggrx_searchList = document.querySelectorAll('div.video-list div.bili-video-card') aggrx_scrollListHeight = aggrx_searchList[0].clientHeight + 70; } else { aggrx_showToast('error', '不支持的页面, 请切换到正确的标签再使用本插件. 抖音需要切换到`视频`页面') } aggrx_searchList.forEach((li, index) => { if (li.querySelector('div.aggrx_buttonBox')) { console.info("已经处理过了") return } aggrx_addCopyCollectionButtonDom(li, page_type); aggrx_checkAuthor(li, page_type); }); } (function() { 'use strict'; console.info("查询作者是否入库 启动") const css = document.createElement("style"); css.type = "text/css"; css.innerText = ` .aggrx-search-douyin { position: fixed; top: 5px; left: calc(50% + 300px); padding: 10px; background-color: #33333305; color: white; text-align: center; z-index: 1000; } .aggrx-search-douyin button { padding: 5px 10px; font-size: 16px; cursor: pointer; border-radius: 8px; background: transparent; color: white; border: 2px solid #cdcdcd; } .aggrx-search-bilibili { position: fixed; top: 102px; left: calc(50% + 330px); z-index: 1000; } .aggrx-toast { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background-color: #333; color: white; padding: 10px 20px; border-radius: 5px; font-size: 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); opacity: 0; transition: opacity 0.5s ease; z-index: 9999; /* 设置 z-index 为 9999 确保 toast 在最上层 */ } .aggrx-toast.show { opacity: 0.8; } .aggrx_buttonBox{ height: 40px; /*top: 1px;*/ /*margin-left: 150px;*/ position: absolute; background: transparent; /*z-index:99999*/ } .aggrx-button-box-general{ /*height: 40px;*/ /*position: absolute;*/ /*top: 10px;*/ /*right: 10px;*/ } .aggrx_buttonBox_button{ background: transparent; border: 2px solid #cdcdcd; padding: 4px; color: white; margin-right: 4px; cursor: pointer; } .aggrx_buttonBox_button-search-bilibili{ color: #00aeec !important; } `; document.head.appendChild(css); let page_type = aggrx_get_page_type(window.location.href) if (!page_type) { aggrx_showToast('error', '不支持的页面, 请切换到正确的标签再使用本插件. 抖音需要切换到`视频`页面') return; } const pannel = document.createElement('div'); pannel.id = 'aggrx-pannel'; pannel.classList.add(`aggrx-${page_type}`); if (page_type.includes('bilibili')) { pannel.innerHTML = ` <button id="aggrxInitializationButton" class='vui_button vui_button--blue vui_button--lg search-button'>扫描</button> `; } else { pannel.innerHTML = ` <button id="aggrxInitializationButton">扫描</button> `; } document.body.appendChild(pannel); document.getElementById("aggrxInitializationButton").addEventListener("click", aggrxInitialization); })(); function aggrx_get_page_type(url) { let page_type = '' let current_url = new URL(url) if (current_url.hostname.match(/search.bilibili.com/)) { return 'search-bilibili' } else if (current_url.href.match(/\.douyin\.com\/root\/search\/.*type=video.*/) || current_url.href.match(/\.douyin\.com\/root\/search\/.*type=video.*/)) { return 'search-douyin' } else if (current_url.href.match(/\.douyin\.com\/search\/.*/) || current_url.href.match(/\.douyin\.com\/search\/.*/)) { return 'search-douyin' } else { return; } } function aggrx_showToast(type, message) { // 创建一个新的 toast 元素 const toast = document.createElement('div'); toast.classList.add('aggrx-toast'); toast.textContent = message; if (type === 'info') { toast.style.color = 'white' } else { toast.style.color = 'red' } // 将 toast 添加到 body document.body.appendChild(toast); // 显示 toast setTimeout(() => { toast.classList.add('show'); }, 10); // 等待一小段时间(确保元素已经插入 DOM) // 5秒后隐藏并删除 toast setTimeout(() => { toast.classList.remove('show'); // 过渡动画结束后删除 toast 元素 setTimeout(() => { toast.remove(); }, 500); // 等待过渡动画完成 }, 2000); // 5秒后消失 }