NightTalkMore

提升 NightTalks 论坛体验的用户脚本,提供快捷按钮、自动回复、离线下载推送等多种实用功能。Enhance the NightTalks forum experience with quick access buttons, an auto-reply feature, ed2k copy and 115 download, and more.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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

})();