// ==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();
})();