您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
X岛揭示板增强:快捷切饼 / 添加页首页码 / 关闭水印 / 预览区真实饼干 / 当页回复编号 / 隐藏无标题+无名氏 /「标记饼干」/「屏蔽饼干」/「屏蔽关键词」。
当前为
// ==UserScript== // @name X岛-EX // @namespace http://tampermonkey.net/ // @version 1.2.11 // @description X岛揭示板增强:快捷切饼 / 添加页首页码 / 关闭水印 / 预览区真实饼干 / 当页回复编号 / 隐藏无标题+无名氏 /「标记饼干」/「屏蔽饼干」/「屏蔽关键词」。 // @author XY // @match https://*.nmbxd1.com/*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addValueChangeListener // @grant GM_xmlhttpRequest // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @license WTFPL // @note 致谢:切饼代码来自[XD-Enhance](http://greasyfork.icu/zh-CN/scripts/438164-xd-enhance) // @note 联动:可使[增强x岛匿名版](http://greasyfork.icu/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'; /* -------------------------------------------------- * 0. 通用与工具函数 * -------------------------------------------------- */ const toast = msg => { let $t = $('#ae-toast'); if (!$t.length) { $t = $(` <div id="ae-toast" style=" position:fixed; top:10px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,.75); color:#fff; padding:8px 18px; border-radius:5px; z-index:9999; display:none; font-size:14px; "></div> `); $('body').append($t); } $t.text(msg).stop(true).fadeIn(240).delay(1800).fadeOut(240); }; const Utils = { /** * 将以逗号分隔的字符串解析为列表,支持词中包含逗号时使用反斜杠转义(中英文逗号均可)。 * 例如 "foo,bar\\,baz,qux" => ["foo","bar,baz","qux"] */ strToList: s => { if (!s) return []; const list = []; let cur = ''; for (let i = 0; i < s.length; i++) { const ch = s[i]; if (ch === '\\' && i + 1 < s.length) { const next = s[i + 1]; // 支持转义逗号(英文或中文)和反斜杠自身 if (next === ',' || next === ',' || next === '\\') { cur += next; i++; continue; } else { // 如果不是要转义的字符,也将反斜杠保留 cur += ch; } } else if (ch === ',' || ch === ',') { // 分隔符:结束当前token const t = cur.trim(); if (t) list.push(t); cur = ''; } else { cur += ch; } } const t = cur.trim(); if (t) list.push(t); // 去重,保留顺序 return [...new Set(list)]; }, cookieLegal: s => /^[A-Za-z0-9]{3,7}$/.test(s), cookieMatch: (cid,p) => cid.toLowerCase().includes(p.toLowerCase()), firstHit: (txt,list) => list.find(k=>txt.toLowerCase().includes(k.toLowerCase()))||null, collapse($elem, hint) { if (!$elem.length || $elem.data('xdex-collapsed')) return; const $icons = $elem.find('.h-threads-item-reply-icon'); let nums = ''; if ($icons.length) { const f = $icons.first().text(); const l = $icons.last().text(); nums = $icons.length>1 ? `${f}-${l} ` : `${f} `; } const cap = `${nums}${hint}`; const $ph = $(` <div class="xdex-placeholder" style=" padding:6px 10px; background:#fafafa; color:#888; border:1px dashed #bbb; margin-bottom:3px; cursor:pointer; "> ${cap}(点击展开) </div> `); $elem.before($ph).hide().data('xdex-collapsed',true); $ph.on('click',function(){ if($elem.is(':visible')){ $elem.hide(); $(this).html(`${cap}(点击展开)`); } else { $elem.show(); $(this).text('点击折叠'); } }); } }; /* -------------------------------------------------- * 1. 设置面板 * -------------------------------------------------- */ const SettingPanel = { key: 'myScriptSettings', defaults: { enableCookieSwitch: true, enablePaginationDuplication: true, disableWatermark: true, updatePreviewCookie: true, updateReplyNumbers: true, hideEmptyTitleEmail: true, markedCookies: '', blockedCookies: '', blockedKeywords: '' }, state: {}, init() { this.state = Object.assign({}, this.defaults, GM_getValue(this.key, {})); this.render(); // 跨标签同步 GM_addValueChangeListener(this.key,(k,ov,nv,remote)=>{ if(remote && $('#sp_cover').is(':hidden')){ this.state = Object.assign({}, this.defaults, nv); this.syncInputs(); applyFilters(this.state); } }); }, render() { // 样式 if (!$('#xdex-setting-style').length) { $('<style id="xdex-setting-style">.xdex-inv{opacity:0;pointer-events:none;}</style>').appendTo('head'); } // 弹出按钮 if (!$('#sp_btn').length) { $('<button id="sp_btn" style="position:fixed;top:10px;right:10px;z-index:10000;padding:6px 12px;border:none;background:#2196F3;color:#fff;border-radius:4px;">EX设置</button>') .appendTo('body') .on('click',()=>$('#sp_cover').fadeIn()); } // 折叠段模板 const fold = (id,title,ph)=>` <div class="sp_fold" style="border:1px solid #eee;margin:6px 0;"> <div class="sp_fold_head" data-btn="#btn_${id}" style="display:flex;align-items:center;padding:6px 8px;background:#fafafa;cursor:pointer;"> <span>${title}</span> <button id="btn_${id}" class="sp_save xdex-inv" data-id="${id}" style="margin-left:auto;padding:2px 8px;">保存</button> </div> <div class="sp_fold_body" style="display:none;padding:8px 10px;"> <input id="${id}" style="width:100%;padding:5px;" placeholder="${ph}"> </div> </div>`; // 面板HTML(宽度加宽、自动高度滚动、底部固定按钮) const html = ` <div id="sp_cover" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:9999;"> <div id="sp_panel" style=" position:relative; margin:40px auto; width:480px; max-height:calc(100vh - 80px); background:#fff; border-radius:8px; display:flex; flex-direction:column; box-shadow:0 2px 10px rgba(0,0,0,0.2); "> <div id="sp_panel_content" style=" padding:18px; overflow-y:auto; flex:1; min-height:300px; "> <h2 style="margin:0 0 10px;">X岛-EX 设置</h2> <div id="sp_checkbox_container" style="display:flex;flex-wrap:wrap;"> <div style="width:50%;"><input type="checkbox" id="sp_enableCookieSwitch"><label for="sp_enableCookieSwitch"> 切换饼干</label></div> <div style="width:50%;"><input type="checkbox" id="sp_enablePaginationDuplication"><label for="sp_enablePaginationDuplication"> 添加页首页码</label></div> <div style="width:50%;"><input type="checkbox" id="sp_disableWatermark"><label for="sp_disableWatermark"> 关闭图片水印</label></div> <div style="width:50%;"><input type="checkbox" id="sp_updatePreviewCookie"><label for="sp_updatePreviewCookie"> 预览区真实饼干</label></div> <div style="width:50%;"><input type="checkbox" id="sp_updateReplyNumbers"><label for="sp_updateReplyNumbers"> 当页回复编号</label></div> <div style="width:50%;"><input type="checkbox" id="sp_hideEmptyTitleEmail"><label for="sp_hideEmptyTitleEmail"> 隐藏空标题与名称</label></div> </div> <div style="margin-top:12px;"> ${fold('sp_markedCookies','标记饼干','3-7 位, 用逗号隔开')} ${fold('sp_blockedCookies','屏蔽饼干','3-7 位, 用逗号隔开')} ${fold('sp_blockedKeywords','屏蔽关键词','关键词请用逗号隔开,词中包含逗号请加\\\转义『\\\,』')} </div> </div> <div id="sp_panel_footer" style=" padding:10px 18px; text-align:right; border-top:1px solid #eee; background:#fff; flex-shrink:0; "> <button id="sp_apply" style="margin-right:10px;padding:6px 10px;">应用更改</button> <button id="sp_close" style="padding:6px 10px;">关闭</button> </div> </div> </div>`; // 如果已存在先移除旧的再添加,防止重复 $('#sp_cover').remove(); $('body').append(html); this.syncInputs(); // 折叠头切换 $('.sp_fold_head').off('click').on('click',function(){ $(this).next('.sp_fold_body').slideToggle(150); $($(this).data('btn')).toggleClass('xdex-inv'); }); // 校验函数 const ok = (v, label) => { if (v === '') return true; const arr = Utils.strToList(v); for (const w of arr) { if (label !== '屏蔽关键词') { if (w.length < 3) { toast(`${label}「${w}」过短`); return false; } if (w.length > 7) { toast(`${label}「${w.slice(0, 7)}…」过长`); return false; } if (!Utils.cookieLegal(w)) { toast(`${label}「${w}」需字母数字`); return false; } } } return true; }; // 单项保存并实时生效 const saveOne = id=>{ const key = id.replace('sp_',''); const val = $('#'+id).val().trim(); const label = {markedCookies:'标记饼干', blockedCookies:'屏蔽饼干', blockedKeywords:'屏蔽关键词'}[key]; if(!ok(val,label)) return; this.state[key] = val; GM_setValue(this.key,this.state); toast(`${label} 已保存并生效`); applyFilters(this.state); }; $('.sp_save').off('click').on('click', e=>{ e.stopPropagation(); saveOne($(e.currentTarget).data('id')); }); // 应用更改:保存三项 + 其他勾选项 -> toast + reload $('#sp_apply').off('click').on('click', ()=>{ // 三项保存 ['sp_markedCookies','sp_blockedCookies','sp_blockedKeywords'].forEach(saveOne); // 其他设置项 [ 'enableCookieSwitch', 'enablePaginationDuplication', 'disableWatermark', 'updatePreviewCookie', 'updateReplyNumbers', 'hideEmptyTitleEmail' ].forEach(k=>{ this.state[k] = $('#sp_'+k).is(':checked'); }); // 覆盖保存 GM_setValue(this.key, Object.assign({}, this.defaults, this.state)); toast('保存成功,即将刷新页面'); setTimeout(()=>location.reload(), 500); }); // 关闭 $('#sp_close').off('click').on('click', ()=>$('#sp_cover').fadeOut()); // 点击遮罩层空白区域收起面板 $('#sp_cover').off('click').on('click', e=>{ if (e.target.id === 'sp_cover') { $('#sp_cover').fadeOut(); } }); }, // state → UI syncInputs() { for(const k in this.state){ const $el = $('#sp_'+k); if($el.is(':checkbox')) $el.prop('checked', this.state[k]); else $el.val(this.state[k]); } } }; /* -------------------------------------------------- * 2. 回复编号 * -------------------------------------------------- */ const circledNumber = n => `『${n}』`; const updateReplyNumbers = () => $('.h-threads-item-reply-icon').each((i,el)=>$(el).text(circledNumber(i+1))); /* -------------------------------------------------- * 3. 饼干标记 / 屏蔽 逻辑 * -------------------------------------------------- */ function markAllCookies(list) { if(!list.length) return; $('span.h-threads-info-uid').each(function(){ const cid = ($(this).text().split(':')[1]||'').trim(); if(cid && list.some(p=>Utils.cookieMatch(cid,p))) $(this).css({background:'#66CCFF',padding:'0 3px','border-radius':'2px'}); }); } function applyFilters(cfg) { const markL = Utils.strToList(cfg.markedCookies); const blkCL = Utils.strToList(cfg.blockedCookies); const blkKL = Utils.strToList(cfg.blockedKeywords); markAllCookies(markL); if(!blkCL.length && !blkKL.length) return; const needBlkC = cid=>blkCL.some(p=>Utils.cookieMatch(cid,p)); const isThread = /\/t\/\d{8,}/.test(location.pathname); const check = $el => { const cid = ($el.find('.h-threads-info-uid').first().text().split(':')[1]||'').trim(); const txt = $el.find('.h-threads-content').first().text(); if(cid && needBlkC(cid)){ const hit = blkCL.find(p=>Utils.cookieMatch(cid,p)); return Utils.collapse($el, `饼干屏蔽『${hit}』`); } const kw = Utils.firstHit(txt, blkKL); if(kw) Utils.collapse($el, `关键词屏蔽『${kw}』`); }; if(!isThread){ $('.h-threads-item-index').each(function(){ const $th = $(this); check($th); $th.find('.h-threads-item-reply-main').each(function(){ check($(this)); }); }); } else { $('.h-threads-item-reply-main').each(function(){ check($(this)); }); } } /* -------------------------------------------------- * 4. 饼干 切换 + 页面增强 * -------------------------------------------------- */ const abbreviateName = n => n.replace(/\s*-\s*\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/, ''); const getCookiesList = () => GM_getValue('cookies', {}); const getCurrentCookie = () => GM_getValue('now-cookie', null); 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(cur){ const $d = $('#current-cookie-display'); if(!$d.length) return; if(cur){ const nm = abbreviateName(cur.name); $d.text(nm + (cur.desc ? ' - ' + cur.desc : '')).css('color','#000'); } else { $d.text('已删除').css('color','red'); } removeDateString(); } function updateDropdownUI(list){ const $dd = $('#cookie-dropdown'); $dd.empty(); Object.keys(list).forEach(id=>{ const c=list[id]; const txt=abbreviateName(c.name)+(c.desc?' - '+c.desc:''); $dd.append(`<option value="${id}">${txt}</option>`); }); const cur = getCurrentCookie(); cur && list[cur.id] ? $dd.val(cur.id) : $dd.val(''); removeDateString(); } function switch_cookie(cookie){ if(!cookie || !cookie.id) return toast('无效的饼干信息!'); $.get(`https://www.nmbxd1.com/Member/User/Cookie/switchTo/id/${cookie.id}.html`) .done(()=>{ toast('切换成功! 当前饼干为 '+abbreviateName(cookie.name)); GM_setValue('now-cookie',cookie); updateCurrentCookieDisplay(cookie); updateDropdownUI(getCookiesList()); removeDateString(); updatePreviewCookieId(); }) .fail(()=>toast('切换失败,请重试')); } function refreshCookies(cb){ GM_xmlhttpRequest({ method:'GET', url:'https://www.nmbxd1.com/Member/User/Cookie/index.html', onload:r=>{ if(r.status!==200){ toast('刷新失败 HTTP '+r.status); return cb&&cb(); } const doc=new DOMParser().parseFromString(r.responseText,'text/html'); const rows=doc.querySelectorAll('tbody>tr'), list={}; rows.forEach(row=>{ const tds=row.querySelectorAll('td'); if(tds.length>=4){ const id=tds[1].textContent.trim(); const name=(tds[2].querySelector('a')||{}).textContent.trim(); const desc=tds[3].textContent.trim(); list[id]={id,name,desc}; } }); GM_setValue('cookies',list); updateDropdownUI(list); toast('饼干列表已刷新!'); let cur=getCurrentCookie(); if(cur && !list[cur.id]) cur=null; GM_setValue('now-cookie',cur); updateCurrentCookieDisplay(cur); removeDateString(); updatePreviewCookieId(); cb&&cb(); }, onerror:()=>{ toast('刷新失败,网络错误'); cb&&cb(); } }); } function showLoginPrompt(){ const $m=$(` <div style="position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:10000;" id="login-modal"> <div style="position:relative;margin:20% auto;width:300px;background:#fff;padding:20px;border-radius:8px;"> <h2>提示</h2><p>当前已退出登录,无法切换饼干。</p> <div style="text-align:right;"> <button id="login-open" style="margin-right:10px;">登录</button> <button id="login-close">关闭</button> </div> </div> </div>`); $('body').append($m); $('#login-open').on('click',()=>{ window.open('https://www.nmbxd1.com/Member/User/Index/login.html','_blank'); $m.fadeOut(200,()=>$m.remove()); }); $('#login-close').on('click',()=>$m.fadeOut(200,()=>$m.remove())); } function createCookieSwitcherUI(){ const $title = $('.h-post-form-title:contains("回应模式")').first(); let $grid = $title.closest('.uk-grid.uk-grid-small.h-post-form-grid'); if(!$grid.length) $grid = $('.h-post-form-title:contains("名 称")').first() .closest('.uk-grid.uk-grid-small.h-post-form-grid'); if(!$grid.length) return; const cur=getCurrentCookie(), list=getCookiesList(); const $ui = $(` <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;"></select> </div> <div class="uk-flex uk-flex-right uk-flex-nowrap"> <button id="apply-cookie" class="uk-button uk-button-default" style="margin-right:5px;">应用</button> <button id="refresh-cookie" class="uk-button uk-button-default">刷新</button> </div> </div> </div>`); $grid.before($ui); updateCurrentCookieDisplay(cur); updateDropdownUI(list); $('#apply-cookie').on('click',e=>{ e.preventDefault(); const sel=$('#cookie-dropdown').val(); refreshCookies(()=>{ const l=getCookiesList(); if(!Object.keys(l).length) return showLoginPrompt(); if(!sel) return toast('请选择饼干'); l[sel] ? switch_cookie(l[sel]) : toast('饼干信息无效'); }); }); $('#refresh-cookie').on('click',e=>{e.preventDefault();refreshCookies();}); } /* -------------------------------------------------- * 5. 页面增强:分页复制 / 关闭水印 / 预览区真实饼干 / 隐藏无标题+无名氏 * -------------------------------------------------- */ function duplicatePagination(){ const tit=document.querySelector('h2.h-title'); const pag=document.querySelector('ul.uk-pagination.uk-pagination-left.h-pagination'); if(!tit||!pag)return; const clone=pag.cloneNode(true); tit.parentNode.insertBefore(clone,tit.nextSibling); clone.querySelectorAll('a').forEach(a=>{ if(a.textContent.trim()==='末页'){ const m=a.href.match(/page=(\d+)/); if(m) a.textContent=`末页(${m[1]})`; } }); } const disableWatermark = () => { const c = document.querySelector('input[type="checkbox"][name="water"][value="true"]'); if(c) c.checked = false; }; function updatePreviewCookieId(){ if(!$('.h-preview-box').length) return; const cur=getCurrentCookie(); const name=cur&&cur.name?abbreviateName(cur.name):'cookies'; $('.h-preview-box .h-threads-info-uid').text('ID:'+name); } 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(); }); } /* -------------------------------------------------- * 6. 入口初始化 * -------------------------------------------------- */ $(document).ready(()=>{ SettingPanel.init(); const cfg = GM_getValue(SettingPanel.key,SettingPanel.defaults); if(cfg.enableCookieSwitch) createCookieSwitcherUI(); if(cfg.enablePaginationDuplication) duplicatePagination(); if(cfg.disableWatermark) disableWatermark(); if(cfg.updatePreviewCookie) updatePreviewCookieId(); if(cfg.hideEmptyTitleEmail) hideEmptyTitleAndEmail(); if(cfg.updateReplyNumbers) updateReplyNumbers(); applyFilters(cfg); }); })(jQuery);