X岛-EX

X岛揭示板增强 快捷切饼/顶部添加页码/默认关闭图片水印/预览区真实饼干/当页回复编号/隐藏空标题与名称

// ==UserScript==
// @name         X岛-EX
// @namespace    http://tampermonkey.net/
// @version      1.2.9
// @description  X岛揭示板增强 快捷切饼/顶部添加页码/默认关闭图片水印/预览区真实饼干/当页回复编号/隐藏空标题与名称
// @author       XY
// @match        https://www.nmbxd1.com/*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_deleteValue
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @license      WTFPL
// @note         致谢:切饼代码来自[XD-Enhance](https://greasyfork.org/zh-CN/scripts/438164-xd-enhance)
// @note         联动:可使[增强x岛匿名版](https://greasyfork.org/zh-CN/scripts/513156-%E5%A2%9E%E5%BC%BAx%E5%B2%9B%E5%8C%BF%E5%90%8D%E7%89%88)添加的预览中显示当前饼名(如ID:cOoKiEs),而非ID:cookies
// ==/UserScript==

(function($) {
    'use strict';

    // -------------------- COOKIE 功能(切换饼干、复制翻页、关闭水印) --------------------

    function toast(msg) {
        let toastDiv = $('#ae-toast-inline');
        if (!toastDiv.length) {
            toastDiv = $('<div id="ae-toast-inline" style="position: fixed; top: 10px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.7); color: white; padding: 10px 20px; border-radius: 5px; z-index: 9999; display: none;"></div>');
            $('body').append(toastDiv);
        }
        toastDiv.text(msg).fadeIn(300).delay(2000).fadeOut(300);
    }

    function getCookiesList() {
        return GM_getValue('cookies', {});
    }
    function getCurrentCookie() {
        return GM_getValue('now-cookie', null);
    }
    // 辅助函数:去掉 cookie.name 末尾 " - 0000-00-00 00:00:00" 部分
    function abbreviateName(name) {
        return name.replace(/\s*-\s*\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/, "");
    }
    function removeDateString() {
        $("#cookie-switcher-ui").find("*").addBack().contents().filter(function() {
            return this.nodeType === 3;
        }).each(function() {
            this.nodeValue = this.nodeValue.replace(/ - 0000-00-00 00:00:00/g, '');
        });
    }

    // 更新页面中用于显示当前饼干的区域
    function updateCurrentCookieDisplay(currentCookie) {
        const cookieDisplay = $('#current-cookie-display');
        if (cookieDisplay.length) {
            if (currentCookie) {
                const displayName = abbreviateName(currentCookie.name);
                const extra = currentCookie.desc ? (' - ' + currentCookie.desc) : '';
                cookieDisplay.text(displayName + extra).css('color', 'black');
            } else {
                cookieDisplay.text('已删除').css('color', 'red');
            }
        }
        removeDateString();
    }

    // 更新下拉菜单中的选项和默认选中
    function updateDropdownUI(cookies) {
        const dropdown = $('#cookie-dropdown');
        dropdown.empty();
        //dropdown.append('<option value="">选择饼干</option>');
        for (let id in cookies) {
            if (cookies.hasOwnProperty(id)) {
                const cookie = cookies[id];
                const displayName = abbreviateName(cookie.name);
                const optionText = displayName + (cookie.desc ? (' - ' + cookie.desc) : '');
                dropdown.append(`<option value="${id}">${optionText}</option>`);
            }
        }
        let currentCookie = getCurrentCookie();
        if (currentCookie && cookies.hasOwnProperty(currentCookie.id)) {
            dropdown.val(currentCookie.id);
        } else {
            dropdown.val("");
        }
        removeDateString();
    }

    // 切换饼干(切换后更新预览区域中的饼干显示)
    function switch_cookie(cookie) {
        if (!cookie || !cookie.id) {
            toast('无效的饼干信息!');
            return;
        }
        const url = 'https://www.nmbxd1.com/Member/User/Cookie/switchTo/id/' + cookie.id + '.html';
        $.ajax({
            type: 'GET',
            url: url,
            success: function() {
                toast('切换成功! 当前饼干为 ' + abbreviateName(cookie.name));
                GM_setValue('now-cookie', cookie);
                updateCurrentCookieDisplay(cookie);
                updateDropdownUI(getCookiesList());
                removeDateString();
                updatePreviewCookieId(); // 新增:更新预览区中当前饼干显示
            },
            error: function() {
                toast('切换失败,请重试');
            }
        });
    }

    // 刷新饼干列表(解析页面后更新)
    function refreshCookies() {
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://www.nmbxd1.com/Member/User/Cookie/index.html',
            onload: function(response) {
                if (response.status === 200) {
                    let parser = new DOMParser();
                    let doc = parser.parseFromString(response.responseText, "text/html");
                    let rows = doc.querySelectorAll('tbody > tr');
                    let newCookies = {};
                    rows.forEach(function(row) {
                        let tds = row.querySelectorAll('td');
                        if (tds.length >= 4) {
                            let id = tds[1].textContent.trim();
                            let nameLink = tds[2].querySelector('a');
                            let nameOriginal = nameLink ? nameLink.textContent.trim() : '';
                            let desc = tds[3].textContent.trim();
                            newCookies[id] = { id: id, name: nameOriginal, desc: desc };
                        }
                    });
                    GM_setValue('cookies', newCookies);
                    updateDropdownUI(newCookies);
                    toast('饼干列表已刷新!');
                    let currentCookie = getCurrentCookie();
                    if (currentCookie && !newCookies[currentCookie.id]) {
                        currentCookie = null;
                    }
                    GM_setValue('now-cookie', currentCookie);
                    updateCurrentCookieDisplay(currentCookie);
                    removeDateString();
                    updatePreviewCookieId();
                } else {
                    toast('刷新失败,HTTP状态码:' + response.status);
                }
            },
            onerror: function() {
                toast('刷新失败,网络错误。');
            }
        });
    }

    // 构造并插入 Cookie 切换 UI——使用页面原有的 uk-grid 布局,确保按钮固定排列向右
    function createCookieSwitcherUI() {
        // 查找"回应模式"标题所在的元素
        const postFormTitle = $('.h-post-form-title:contains("回应模式")').first();
        let postFormGrid = null;

        // 如果找到“回应模式”,则定位到包含它的表单网格
        if (postFormTitle.length) {
            postFormGrid = postFormTitle.closest('.uk-grid.uk-grid-small.h-post-form-grid');
        }

        // 如果没有找到“回应模式”,改为查找“名 称”
        if (!postFormGrid || !postFormGrid.length) {
            const nameLabel = $('.h-post-form-title:contains("名 称")').first();
            if (nameLabel.length) {
                postFormGrid = nameLabel.closest('.uk-grid.uk-grid-small.h-post-form-grid');
            }
        }

        // 如果仍然未找到合适的插入位置,停止函数执行
        if (!postFormGrid || !postFormGrid.length) {
            return;
        }

        // 构造切换饼干的 UI
        const currentCookie = getCurrentCookie();
        const cookiesList = getCookiesList();
        const switcherUI = $(`
        <div class="uk-grid uk-grid-small h-post-form-grid" id="cookie-switcher-ui">
            <div class="uk-width-1-5">
                <div class="h-post-form-title">当前饼干</div>
            </div>
            <div class="uk-width-3-5 h-post-form-input" style="display: flex; align-items: center; justify-content: space-between;">
                <div class="uk-flex uk-flex-middle">
                    <span id="current-cookie-display"></span>
                    <select id="cookie-dropdown" style="margin-left: 10px;">
                        <!-- 饼干选项会通过 updateDropdownUI 填充 -->
                    </select>
                </div>
                <div class="uk-flex uk-flex-right uk-flex-nowrap">
                    <button type="button" id="apply-cookie-button" class="uk-button uk-button-default" style="margin-right: 5px;">应用</button>
                    <button type="button" id="refresh-cookie-button" class="uk-button uk-button-default">刷新</button>
                </div>
            </div>
        </div>
    `);

        // 将切换饼干的 UI 插入到表单网格上方
        postFormGrid.before(switcherUI);

        // 更新当前饼干显示和下拉选项
        updateCurrentCookieDisplay(currentCookie);
        updateDropdownUI(cookiesList);

        // 为“应用”按钮绑定点击事件
        $('#apply-cookie-button').on('click', function(e) {
            e.preventDefault();
            const selectedCookieId = $('#cookie-dropdown').val();
            if (selectedCookieId) {
                let cookiesListUpdated = getCookiesList();
                const selectedCookie = cookiesListUpdated[selectedCookieId];
                if (selectedCookie) {
                    switch_cookie(selectedCookie);
                } else {
                    toast('选择的饼干信息无效!');
                }
            } else {
                toast('请选择要切换的饼干!');
            }
        });

        // 为“刷新”按钮绑定点击事件
        $('#refresh-cookie-button').on('click', function(e) {
            e.preventDefault();
            refreshCookies();
        });
    }

    // -------------------- 分页与水印功能 --------------------

    // 为页首添加翻页页码,以及显示末页具体页码
    function duplicatePagination() {
        const targetElement = document.querySelector('h2.h-title');
        const paginationElement = document.querySelector('ul.uk-pagination.uk-pagination-left.h-pagination');
        if (targetElement && paginationElement) {
            const clonedPagination = paginationElement.cloneNode(true);
            targetElement.parentNode.insertBefore(clonedPagination, targetElement.nextSibling);

            // 遍历克隆分页中的所有链接,查找文本为“末页”的链接
            const anchors = clonedPagination.getElementsByTagName("a");
            for (let i = 0; i < anchors.length; i++) {
                if (anchors[i].textContent.trim() === "末页") {
                    const href = anchors[i].getAttribute("href");
                    // 利用正则提取 URL 中的页码数字
                    const match = href.match(/page=(\d+)/);
                    if (match && match[1]) {
                        anchors[i].textContent = `末页(${match[1]})`;
                    }
                    break;  // 找到后就退出循环
                }
            }
            console.log('Pagination duplicated and last page updated successfully!');
        } else {
            console.log('Could not find target or pagination element.');
        }
    }

    function disableWatermark() {
        const watermarkCheckbox = document.querySelector('input[type="checkbox"][name="water"][value="true"]');
        if (watermarkCheckbox) {
            watermarkCheckbox.checked = false;
            console.log('Watermark checkbox unchecked!');
        } else {
            console.log('Could not find the watermark checkbox.');
        }
    }

    // 程序入口(用于执行部分初始化操作)
    function mainCookie() {
        createCookieSwitcherUI();
        duplicatePagination();
        disableWatermark();
        removeDateString();
    }

    // -------------------- 新增功能 --------------------

    // 检查页面是否存在 .h-preview-box,如果存在,则将其中的 "ID:cookies" 替换为当前实际显示的饼干
    // “实际显示的饼干”取自当前饼干对象的名称经过 abbreviateName() 处理
    function updatePreviewCookieId() {
        if ($('.h-preview-box').length > 0) {
            const currentCookie = getCurrentCookie();
            if (currentCookie && currentCookie.name) {
                const displayName = abbreviateName(currentCookie.name);
                $('.h-preview-box .h-threads-info-uid').text("ID:" + displayName);
            } else {
                $('.h-preview-box .h-threads-info-uid').text("ID:cookies");
            }
        }
    }

    // -------------------- 回复编号功能 --------------------
    // 辅助函数:将数字转换为形如 『数字』 的格式
    function circledNumber(n) {
        return '『' + n + '』';
    }

    // 检查页面中 <div class="h-threads-item-reply-icon">…</div> 的个数,并将其中的 “…” 替换为其所在的『数字』
    function updateReplyNumbers() {
        $('.h-threads-item-reply-icon').each(function(index) {
            $(this).text(circledNumber(index + 1));
        });
        console.log('回复编号更新成功!');
    }

    // -------------------- 隐藏标题与邮箱功能 --------------------
    // 当 <span class="h-threads-info-title"> 为 "无标题" ,或 <span class="h-threads-info-email"> 为 "无名氏" 时隐藏之
    function hideEmptyTitleAndEmail() {
        $('.h-threads-info-title').each(function() {
            if ($(this).text().trim() === '无标题') {
                $(this).hide();
            }
        });
        $('.h-threads-info-email').each(function() {
            if ($(this).text().trim() === '无名氏') {
                $(this).hide();
            }
        });
        console.log('空标题与空邮箱已隐藏!');
    }

    // -------------------- 新增:设置面板模块 --------------------
    // 配置面板模块:提供脚本各功能的开关设置,设置通过 GM_setValue 本地保存
    const SettingPanel = {
        name: 'SettingPanel',
        title: 'X岛-EX设置',
        // 默认设置项(可根据需要扩展)
        defaultSettings: {
            enableCookieSwitch: true,            // 切换饼干
            enablePaginationDuplication: true,   // 添加页首页码
            disableWatermark: true,              // 关闭图片水印
            updatePreviewCookie: true,           // 预览区真实饼干
            updateReplyNumbers: true,            // 当页回复编号
            hideEmptyTitleEmail: true            // 隐藏空标题与名称
        },
        settings: {},
        settingsKey: 'myScriptSettings',  // 设置存储时使用的键

        init: function () {
            // 加载已保存的设置,没有则使用默认设置
            SettingPanel.settings = GM_getValue(SettingPanel.settingsKey, SettingPanel.defaultSettings);
            SettingPanel.renderPanel();
            SettingPanel.renderButton();
        },

        renderPanel: function () {
            // 构造设置面板(覆盖层)
            const $panel = $(`
                <div id="my_setting_panel_cover" style="display:none; position: fixed; top: 0; left: 0; width:100%; height:100%; 
                    background: rgba(0,0,0,0.5); z-index: 10000;">
                    <div id="my_setting_panel" style="position: relative; margin: 10% auto; padding: 20px; background: white;
                        width: 300px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.5);">
                        <h2 style="margin-top:0;">X岛-EX设置</h2>
                        <div class="setting_item" style="margin-bottom: 10px;">
                            <input type="checkbox" id="sp_enableCookieSwitch">
                            <label for="sp_enableCookieSwitch">切换饼干</label>
                        </div>
                        <div class="setting_item" style="margin-bottom: 10px;">
                            <input type="checkbox" id="sp_enablePaginationDuplication">
                            <label for="sp_enablePaginationDuplication">添加页首页码</label>
                        </div>
                        <div class="setting_item" style="margin-bottom: 10px;">
                            <input type="checkbox" id="sp_disableWatermark">
                            <label for="sp_disableWatermark">关闭图片水印</label>
                        </div>
                        <div class="setting_item" style="margin-bottom: 10px;">
                            <input type="checkbox" id="sp_updatePreviewCookie">
                            <label for="sp_updatePreviewCookie">预览区真实饼干</label>
                        </div>
                        <div class="setting_item" style="margin-bottom: 10px;">
                            <input type="checkbox" id="sp_updateReplyNumbers">
                            <label for="sp_updateReplyNumbers">当页回复编号</label>
                        </div>
                        <div class="setting_item" style="margin-bottom: 10px;">
                            <input type="checkbox" id="sp_hideEmptyTitleEmail">
                            <label for="sp_hideEmptyTitleEmail">隐藏空标题与名称</label>
                        </div>
                        <div style="margin-top:20px; text-align: right;">
                            <button id="sp_save_button" style="margin-right:10px;">保存设置</button>
                            <button id="sp_close_button">关闭</button>
                        </div>
                    </div>
                </div>
            `);
            $('body').append($panel);

            // 根据已保存的设置初始化各复选框状态
            $('#sp_enableCookieSwitch').prop('checked', SettingPanel.settings.enableCookieSwitch);
            $('#sp_enablePaginationDuplication').prop('checked', SettingPanel.settings.enablePaginationDuplication);
            $('#sp_disableWatermark').prop('checked', SettingPanel.settings.disableWatermark);
            $('#sp_updatePreviewCookie').prop('checked', SettingPanel.settings.updatePreviewCookie);
            $('#sp_updateReplyNumbers').prop('checked', SettingPanel.settings.updateReplyNumbers);
            $('#sp_hideEmptyTitleEmail').prop('checked', SettingPanel.settings.hideEmptyTitleEmail);

            // 保存设置:读取各复选框状态并保存至 GM 存储,然后刷新页面以应用更改
            $('#sp_save_button').on('click', function () {
                SettingPanel.settings.enableCookieSwitch = $('#sp_enableCookieSwitch').is(':checked');
                SettingPanel.settings.enablePaginationDuplication = $('#sp_enablePaginationDuplication').is(':checked');
                SettingPanel.settings.disableWatermark = $('#sp_disableWatermark').is(':checked');
                SettingPanel.settings.updatePreviewCookie = $('#sp_updatePreviewCookie').is(':checked');
                SettingPanel.settings.updateReplyNumbers = $('#sp_updateReplyNumbers').is(':checked');
                SettingPanel.settings.hideEmptyTitleEmail = $('#sp_hideEmptyTitleEmail').is(':checked');
                GM_setValue(SettingPanel.settingsKey, SettingPanel.settings);
                toast('设置已保存!页面即将刷新');
                $('#my_setting_panel_cover').fadeOut(200, function(){
                    location.reload();
                });
            });

            // 关闭面板
            $('#sp_close_button').on('click', function () {
                $('#my_setting_panel_cover').fadeOut();
            });
        },

        renderButton: function () {
            // 在页面右上角添加设置按钮,按钮文字改为 “EX设置”
            const $btn = $(`
                <button id="sp_config_button" title="打开配置面板" 
                    style="position: fixed; top: 10px; right: 10px; z-index: 10000; padding: 5px 10px; border: none;
                    background: #2196F3; color: #fff; border-radius: 4px; cursor: pointer;">EX设置</button>
            `);
            $btn.on('click', function () {
                $('#my_setting_panel_cover').fadeIn();
            });
            $('body').append($btn);
        }
    };

    // -------------------- 页面入口 --------------------
    $(document).ready(function(){
        // 初始化设置面板并添加设置按钮
        SettingPanel.init();

        // 读取设置,若用户未保存则使用默认配置
        const mySettings = GM_getValue(SettingPanel.settingsKey, SettingPanel.defaultSettings);

        // 根据设置决定是否启用对应功能
        if (mySettings.enableCookieSwitch) {
            createCookieSwitcherUI();
        }
        if (mySettings.enablePaginationDuplication) {
            duplicatePagination();
        }
        if (mySettings.disableWatermark) {
            disableWatermark();
        }
        if (mySettings.updatePreviewCookie) {
            updatePreviewCookieId();
        }
        if (mySettings.updateReplyNumbers) {
            updateReplyNumbers();
        }
        if (mySettings.hideEmptyTitleEmail) {
            hideEmptyTitleAndEmail();
        }
    });

})(jQuery);