您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
提升 NightTalks 论坛体验的用户脚本,提供快捷按钮、自动回复、离线下载推送等多种实用功能。Enhance the NightTalks forum experience with quick access buttons, an auto-reply feature, ed2k copy and 115 download, and more.
// ==UserScript== // @name NightTalkMore // @namespace http://tampermonkey.net/ // @version 1.3 // @description 提升 NightTalks 论坛体验的用户脚本,提供快捷按钮、自动回复、离线下载推送等多种实用功能。Enhance the NightTalks forum experience with quick access buttons, an auto-reply feature, ed2k copy and 115 download, and more. // @author Your name // @match https://nightalks.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_notification // @license MIT // @run-at document-end // ==/UserScript== (function() { 'use strict'; // Define the categories and their corresponding URLs const categories = [ { name: '國產', prefix: 1 }, { name: '日本', prefix: 2 }, { name: '亞洲', prefix: 3 }, { name: '歐美', prefix: 4 } ]; const replyTexts = [ "感谢楼主分享!太喜欢这样的内容了!", "哇塞,这影片质量真不错,感谢大大!", "楼主真是大神,每次分享都不一样!", "帅哥真的赏心悦目,感谢楼主精心挑选~", "支持楼主,内容很棒,辛苦了!", "感谢分享,楼主辛苦了,影片好看!", "这次的内容超赞,果然楼主出品,必属精品!", "大爱这种影片,谢谢楼主的热心分享!", "好片收藏,楼主真有眼光!感谢感谢~", "太喜欢这种题材了,楼主发了宝藏啊,感谢!" ]; const config = { webDownloadFolderId: GM_getValue("webDownloadFolderId", ""), signUrl: "https://115.com/?ct=offline&ac=space&_=", // 获取115 token签名接口 addTaskUrl: "https://115.com/web/lixian/?ct=lixian&ac=add_task_url", // 添加115离线任务接口 }; // Function to send ed2k links to 115 offline download function addEd2kTo115(urls) { return new Promise((resolve, reject) => { const timeout = new Date().getTime(); GM_xmlhttpRequest({ method: "GET", url: config.signUrl + timeout, onload: (responseDetails) => { if (responseDetails.responseText.indexOf("html") >= 0) { window.open("https://115.com/", "_blank"); reject("还没有登录115"); return; } let signData; try { signData = JSON.parse(responseDetails.response); } catch (error) { reject("获取签名失败: 无效的JSON数据"); return; } const { sign } = signData; const encodedUrls = `url=${encodeURIComponent(urls[0])}`; const url = config.addTaskUrl; const addConfig = { method: "POST", url: url, data: `${encodedUrls}&savepath=&wp_path_id=${config.webDownloadFolderId}&sign=${sign}&time=${timeout}`, headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", }, onload: (res) => { let resData; try { resData = JSON.parse(res.response); } catch (error) { reject("添加任务失败: 无效的JSON数据"); return; } if (resData.state === false) { reject(resData.error_msg || "添加任务失败"); } else { resolve("添加成功!"); } }, onerror: () => reject("请求添加离线任务失败"), }; GM_xmlhttpRequest(addConfig); }, onerror: () => reject("获取签名失败"), }); }); } // Function to create a category button function createCategoryButton(category) { const span = document.createElement('span'); span.className = 'label label--lightGreen'; span.style.marginRight = '5px'; span.style.cursor = 'pointer'; span.textContent = category.name; // Add click event span.addEventListener('click', () => { window.location.href = `?prefix_id[0]=${category.prefix}`; }); return span; } // Add "Open All" button function createOpenAllButton() { const button = document.createElement('span'); button.className = 'label label--error'; button.style.marginRight = '5px'; button.style.cursor = 'pointer'; button.textContent = '打开所有帖子'; // Add click event to open all links that do not contain "page" in the URL button.addEventListener('click', () => { document.querySelectorAll('.structItem-title a[href^="https://nightalks.com/threads/"]').forEach(link => { if (!link.href.includes('page')) { window.open(link.href, '_blank'); } }); }); return button; } // Main function to add the buttons function addCategoryButtons() { const filterBar = document.querySelector('.filterBar'); if (!filterBar) return; if (!window.location.pathname.startsWith('/forums/16/') && !window.location.pathname.startsWith('/forums/18/') ) return; // Create container for our buttons const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'inline-block'; buttonContainer.style.marginRight = '10px'; // Add all category buttons categories.forEach(category => { buttonContainer.appendChild(createCategoryButton(category)); }); // Add "Open All" button buttonContainer.appendChild(createOpenAllButton()); // Insert at the beginning of filterBar filterBar.insertBefore(buttonContainer, filterBar.firstChild); } // function to go back to top function addBackToTopButton() { const sidebar = document.querySelector('.p-body-sidebar'); if (!sidebar) return; // Create the button const backToTopButton = document.createElement('button'); backToTopButton.className = 'button'; backToTopButton.style.marginTop = '10px'; backToTopButton.style.width = '100%'; backToTopButton.textContent = '回顶部'; // Add click event to scroll to top backToTopButton.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); // Append the button to the sidebar sidebar.appendChild(backToTopButton); } // Function to automatically fill and submit the reply form function addAutoReplyButton() { const sidebar = document.querySelector('.p-body-sidebar'); if (!sidebar) return; if (!window.location.pathname.startsWith('/threads/')) return; // Create the "Auto Reply" button const autoReplyButton = document.createElement('button'); autoReplyButton.className = 'button'; autoReplyButton.style.marginTop = '10px'; autoReplyButton.style.width = '100%'; autoReplyButton.textContent = '随机回复'; // Add click event to fill and submit the reply form autoReplyButton.addEventListener('click', () => { autoReply(); // Change button color to green and text to "已回复" (Replied) autoReplyButton.style.backgroundColor = 'green'; autoReplyButton.textContent = '已回复'; // Wait 1 second, then hide the button setTimeout(() => { autoReplyButton.style.display = 'none'; }, 1000); }); // Append the button to the sidebar sidebar.appendChild(autoReplyButton); } // Function to automatically fill and submit the reply form function autoReply() { const replyDiv = document.querySelector('.fr-element[contenteditable="true"]'); const replyButton = document.querySelector('button.button--icon--reply'); if (replyDiv && replyButton) { // Randomly select a reply text const randomReply = replyTexts[Math.floor(Math.random() * replyTexts.length)]; // Set the content of the editable div replyDiv.innerHTML = `<p>${randomReply}</p>`; // Wait a bit to make sure the content is set, then click reply button setTimeout(() => { replyButton.click(); }, 500); } } // Function to remove "/unread" from all <a> tag hrefs function removeUnreadFromLinks() { const links = document.querySelectorAll('a[href*="/unread"]'); links.forEach(link => { link.href = link.href.replace('/unread', ''); }); } // Function to add "Copy" buttons below each ed2k link function addCopyButtonToEd2kLinks() { // Find all ed2k links within expandable bbCode blocks document.querySelectorAll('.bbCodeBlock-content .bbCodeBlock-expandContent').forEach(content => { const ed2kLink = content.textContent.trim(); if (ed2kLink.startsWith("ed2k://")) { // Check if a button has already been added if (content.querySelector('.copy-ed2k-button')) return; // Parse the ed2k link for file name and file size const ed2kParts = ed2kLink.split('|'); const fileName = decodeURIComponent(ed2kParts[2] || "未知文件"); // Part 2 is the file name const fileSize = ed2kParts[3] ? `${(parseInt(ed2kParts[3]) / (1024 ** 2)).toFixed(2)} MB` : "未知大小"; // Part 3 is the file size in bytes, converted to MB // Display the file name and size above the link const fileInfo = document.createElement('div'); fileInfo.innerHTML = `文件名: ${fileName}<br>大小: ${fileSize}`; fileInfo.style.marginBottom = '2px'; fileInfo.style.fontSize = '1.1em'; fileInfo.style.color = 'white'; // Wrap the original ed2k link in a span and hide it const ed2kLinkSpan = document.createElement('span'); ed2kLinkSpan.textContent = ed2kLink; ed2kLinkSpan.style.display = 'none'; content.textContent = ''; // Clear the content content.appendChild(ed2kLinkSpan); // Add the hidden ed2k link // Create the "Copy" button const copyButton = document.createElement('button'); copyButton.textContent = `复制链接 (${fileSize})`; copyButton.className = 'button button--link copy-ed2k-button'; copyButton.style.marginTop = '5px'; copyButton.style.cursor = 'pointer'; // Add copy functionality copyButton.addEventListener('click', () => { navigator.clipboard.writeText(ed2kLink).then(() => { copyButton.textContent = '已复制!'; setTimeout(() => copyButton.textContent = `复制链接 (${fileSize})`, 2000); }).catch(() => { copyButton.textContent = '复制失败'; setTimeout(() => copyButton.textContent = `复制链接 (${fileSize})`, 2000); }); }); // Create the "Push to 115 Download" button const pushButton = document.createElement('button'); pushButton.textContent = '推送到115下载'; pushButton.className = 'button button--link push-115-button'; pushButton.style.marginTop = '5px'; pushButton.style.marginLeft = '5px'; pushButton.style.cursor = 'pointer'; // Add push functionality pushButton.addEventListener('click', () => { addEd2kTo115([ed2kLink]) .then(response => { pushButton.textContent = response; setTimeout(() => pushButton.textContent = '推送到115下载', 2000); }) .catch(error => { pushButton.textContent = error; setTimeout(() => pushButton.textContent = '再次尝试推送到115下载', 2000); }); }); // Append file info and the button below the ed2k link content.prepend(fileInfo); content.appendChild(copyButton); content.appendChild(pushButton); } }); } // Wait for page to load and add buttons if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { removeUnreadFromLinks(); addCategoryButtons(); addBackToTopButton(); addAutoReplyButton(); }); } else { removeUnreadFromLinks(); addCategoryButtons(); addBackToTopButton(); addAutoReplyButton(); } // Set up a MutationObserver to monitor for new ed2k links const observer = new MutationObserver(addCopyButtonToEd2kLinks); // Start observing the document for changes in the subtree observer.observe(document.body, { childList: true, subtree: true }); // Initial call to add buttons to existing links addCopyButtonToEd2kLinks(); })();