返回列表 发新帖

【二改优化】消息预览+

  [复制链接]

124

主题

2511

回帖

9030

积分

硕士生

秋枫Mod

Rank: 6Rank: 6

金币
2905
好评
63
信誉
98

MT论坛最佳新人MT论坛帅哥MT论坛新人考神MT论坛活跃会员MT论坛灌水老大

QQ
发表于 2026-5-1 10:54:21 来自手机  | 显示全部楼层 | 阅读模式  来自 河南
本帖最后由 秋枫Mod 于 2026-5-1 13:15 编辑

本脚本基于 @青春向上 的作品进行二次修改与优化,仅用于个人学习、技术交流与研究。若原作者对本次修改存在异议,请联系删除,本人无任何侵权意图。

原帖:https://bbs.binmt.cc/thread-165883-1-1.html

新增评论过滤器功能

下面效果图:

未过滤:


过滤后:


脚本UI:




  1. //// ==UserScript==
  2. // [url=home.php?mod=space&uid=121064]@name[/url]         [MT论坛]消息预览+
  3. // @namespace   
  4. // @version      2026-05-01
  5. // @description  基于青春向上大佬的消息预览脚本进行修改,新增了评论过滤器功能
  6. // @author       青春向上&秋枫Mod
  7. // @match        https://bbs.binmt.cc/home.php?mod=space&do=notice&view=mypost*
  8. // @icon         https://bbs.binmt.cc/favicon.ico
  9. // @grant        GM_setValue
  10. // @grant        GM_getValue
  11. // @grant        GM_deleteValue
  12. // @run-at       document-idle
  13. // ==/UserScript==

  14. // 二次修改声明:
  15. // 本脚本基于原作者作品二次修改,仅用于个人学习、技术交流与研究,非商用
  16. // 原始版权归原作者所有,禁止未经授权转载、分发、商用,侵权请联系删除

  17. (function () {
  18.     'use strict';

  19.     const noticeSelector = '.comiis_notice_list>ul';
  20.     const itemSelector = `${noticeSelector}>li`;
  21.     const selectContent = 'div.comiis_messages.comiis_aimg_show.cl';
  22.     const MAX_CACHE_COUNT = 50;
  23.     const MAX_LENGTH = 100;
  24.     const PROCESSED_MARK = 'mt-preview-processed';
  25.     const CACHE_STORAGE_KEY = 'mt_bbs_preview_cache';
  26.     const FILTER_CONFIG_KEY = 'mt_bbs_comment_filter_config';

  27.     let currentPageFiltered = [];

  28.     const defaultFilterConfig = {
  29.         minLength: 0,
  30.         maxLength: 6,
  31.         keywords: []
  32.     };

  33.     function disableBackground() {
  34.         document.body.style.overflow = 'hidden';
  35.         document.body.style.touchAction = 'none';
  36.     }
  37.     function enableBackground() {
  38.         document.body.style.overflow = '';
  39.         document.body.style.touchAction = '';
  40.     }

  41.     function getFilterConfig() {
  42.         try {
  43.             const saved = GM_getValue(FILTER_CONFIG_KEY);
  44.             return saved ? JSON.parse(saved) : { ...defaultFilterConfig };
  45.         } catch {
  46.             return { ...defaultFilterConfig };
  47.         }
  48.     }

  49.     function saveFilterConfig(config) {
  50.         GM_setValue(FILTER_CONFIG_KEY, JSON.stringify(config));
  51.     }

  52.     function autoSaveConfig() {
  53.         const min = parseInt(document.querySelector('#f-min')?.value) || 0;
  54.         const max = parseInt(document.querySelector('#f-max')?.value) || 999;
  55.         const kw = (document.querySelector('#f-kw')?.value || '')
  56.             .split('\n').map(x => x.trim()).filter(Boolean);
  57.         saveFilterConfig({ minLength: min, maxLength: max, keywords: kw });
  58.         processAllNotices(true);
  59.     }

  60.     function shouldFilterComment(content) {
  61.         if (!content) return false;
  62.         const cfg = getFilterConfig();
  63.         const len = content.length;
  64.         if (len < cfg.minLength || len > cfg.maxLength) return false;
  65.         return cfg.keywords.some(kw => kw && content.includes(kw));
  66.     }

  67.     function processAllNotices(force = false) {
  68.         currentPageFiltered = [];
  69.         document.querySelectorAll(itemSelector).forEach(li => {
  70.             if (!force && li.hasAttribute(PROCESSED_MARK)) return;
  71.             processNoticeItem(li, force);
  72.         });
  73.         updateFilterButton();
  74.     }

  75.     async function processNoticeItem(li, force) {
  76.         if (!force) li.setAttribute(PROCESSED_MARK, 'true');
  77.         const a = li.querySelector('a.lit[href*="goto=findpost"]');
  78.         if (!a) return;
  79.         const { tid, pid } = getTidPidFromUrl(a.href);
  80.         if (!tid || !pid) return;

  81.         const key = `${tid}_${pid}`;
  82.         let content = getCache(key);
  83.         if (!content) {
  84.             content = await fetchReplyContent(tid, pid);
  85.             if (content && !content.includes('失败')) setCache(key, content);
  86.         }

  87.         const needFilter = shouldFilterComment(content);
  88.         if (needFilter) {
  89.             li.style.display = 'none';
  90.             currentPageFiltered.push({
  91.                 content: content,
  92.                 url: a.href
  93.             });
  94.         } else {
  95.             li.style.display = '';
  96.         }

  97.         const show = truncateText(content, MAX_LENGTH);
  98.         insertContentToLi(li, show);
  99.     }

  100.     function getTidPidFromUrl(url) {
  101.         const p = new URLSearchParams(url);
  102.         return { tid: p.get('ptid'), pid: p.get('pid') };
  103.     }

  104.     async function fetchReplyContent(tid, pid) {
  105.         try {
  106.             const r = await fetch(
  107.                 `https://bbs.binmt.cc/forum.php?mod=viewthread&tid=${tid}&viewpid=${pid}&mobile=2&inajax=1`,
  108.                 { signal: AbortSignal.timeout(5000) }
  109.             );
  110.             if (!r.ok) throw new Error('请求失败');
  111.             const txt = await r.text();
  112.             const xml = new DOMParser().parseFromString(txt, 'text/xml');
  113.             if (xml.querySelector('parsererror')) throw new Error('XML错误');
  114.             const html = xml.lastChild?.firstChild?.nodeValue || '';
  115.             const div = document.createElement('div');
  116.             div.innerHTML = html;
  117.             const c = div.querySelector(selectContent)?.textContent.trim() || '';
  118.             div.remove();
  119.             return c || '[空内容]';
  120.         } catch (e) {
  121.             return `[获取失败:${e.message}]`;
  122.         }
  123.     }

  124.     function truncateText(t, max) {
  125.         if (!t) return '';
  126.         return t.length <= max ? t : '...' + t.slice(-max);
  127.     }

  128.     function insertContentToLi(li, txt) {
  129.         const old = li.querySelector('.mt-preview');
  130.         if (old) old.remove();
  131.         const s = document.createElement('span');
  132.         s.className = 'mt-preview';
  133.         s.textContent = `(预览:${txt})`;
  134.         s.style.cssText = `
  135.             color:#666;
  136.             font-size:14px;
  137.             line-height:1.5;
  138.             margin-left:6px;
  139.             display:inline-block;
  140.             margin-top:2px;
  141.         `;
  142.         li.appendChild(s);
  143.     }

  144.     function getCacheData() {
  145.         try {
  146.             const d = localStorage.getItem(CACHE_STORAGE_KEY);
  147.             return d ? JSON.parse(d) : { list: [] };
  148.         } catch { return { list: [] } }
  149.     }
  150.     function saveCacheData(d) {
  151.         localStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(d));
  152.     }
  153.     function getCache(k) {
  154.         const c = getCacheData();
  155.         const i = c.list.find(x => x.key === k);
  156.         return i ? i.content : null;
  157.     }
  158.     function setCache(k, v) {
  159.         const c = getCacheData();
  160.         const idx = c.list.findIndex(x => x.key === k);
  161.         if (idx > -1) c.list.splice(idx, 1);
  162.         c.list.unshift({ key: k, content: v, time: Date.now() });
  163.         if (c.list.length > MAX_CACHE_COUNT) c.list.pop();
  164.         saveCacheData(c);
  165.     }

  166.     function animateIn(el) {
  167.         el.style.transition = 'transform 0.28s cubic-bezier(0.25,0.8,0.25,1), opacity 0.28s ease';
  168.         el.style.opacity = '0';
  169.         el.style.transform = 'scale(0.94)';
  170.         requestAnimationFrame(() => {
  171.             el.style.opacity = '1';
  172.             el.style.transform = 'scale(1)';
  173.         });
  174.     }
  175.     function animateOut(el, cb) {
  176.         el.style.transition = 'transform 0.22s ease, opacity 0.22s ease';
  177.         el.style.opacity = '0';
  178.         el.style.transform = 'scale(0.96)';
  179.         setTimeout(() => { el.remove(); cb?.(); }, 230);
  180.     }

  181.     function createFilterUI() {
  182.         disableBackground();
  183.         const cfg = getFilterConfig();
  184.         const overlay = document.createElement('div');
  185.         overlay.style.cssText = `
  186.             position:fixed;top:0;left:0;width:100vw;height:100vh;
  187.             background:rgba(0,0,0,0.5);z-index:9999;display:flex;
  188.             align-items:center;justify-content:center;
  189.         `;
  190.         const box = document.createElement('div');
  191.         box.style.cssText = `
  192.             background:#fff;border-radius:12px;width:90%;max-width:380px;
  193.             padding:20px;box-sizing:border-box;box-shadow:0 10px 30px rgba(0,0,0,0.2);
  194.         `;
  195.         box.innerHTML = `
  196.             <h3 style="margin:0 0 16px 0;text-align:center;font-size:17px;">评论过滤器</h3>
  197.             <div style="margin-bottom:12px;">
  198.                 <label style="font-size:14px;font-weight:bold;display:block;">最小长度</label>
  199.                 <input type="number" id="f-min" value="${cfg.minLength}" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ddd;box-sizing:border-box;">
  200.             </div>
  201.             <div style="margin-bottom:12px;">
  202.                 <label style="font-size:14px;font-weight:bold;display:block;">最大长度</label>
  203.                 <input type="number" id="f-max" value="${cfg.maxLength}" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ddd;box-sizing:border-box;">
  204.             </div>
  205.             <div style="margin-bottom:16px;">
  206.                 <label style="font-size:14px;font-weight:bold;display:block;">关键字(换行分隔)</label>
  207.                 <textarea id="f-kw" rows="4" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ddd;box-sizing:border-box;">${cfg.keywords.join('\n')}</textarea>
  208.             </div>
  209.             <button id="f-close" style="width:100%;padding:10px;border:none;border-radius:6px;background:#eee;">关闭</button>
  210.         `;
  211.         overlay.appendChild(box);
  212.         document.body.appendChild(overlay);
  213.         animateIn(box);
  214.         box.querySelectorAll('input,textarea').forEach(i => {
  215.             i.addEventListener('input', autoSaveConfig);
  216.         });
  217.         function close() {
  218.             animateOut(box, () => { overlay.remove(); enableBackground(); });
  219.         }
  220.         box.querySelector('#f-close').onclick = close;
  221.         overlay.onclick = e => e.target === overlay && close();
  222.     }

  223.     function showFilteredList() {
  224.         disableBackground();
  225.         const list = currentPageFiltered;
  226.         const cnt = list.length;

  227.         const overlay = document.createElement('div');
  228.         overlay.style.cssText = `
  229.             position:fixed;top:0;left:0;width:100vw;height:100vh;
  230.             background:rgba(0,0,0,0.5);z-index:9999;display:flex;
  231.             align-items:center;justify-content:center;
  232.         `;
  233.         const wrap = document.createElement('div');
  234.         wrap.style.cssText = `
  235.             background:#fff;border-radius:12px;width:90%;max-width:380px;
  236.             max-height:80vh;display:flex;flex-direction:column;
  237.             box-shadow:0 10px 30px rgba(0,0,0,0.2);overflow:hidden;
  238.         `;
  239.         const head = document.createElement('div');
  240.         head.style.cssText = `padding:16px 20px;border-bottom:1px solid #eee;`;
  241.         head.innerHTML = `<h3 style="margin:0;text-align:center;">当前页已过滤(${cnt}条)</h3>`;

  242.         const content = document.createElement('div');
  243.         content.style.cssText = `padding:10px 20px;flex:1;overflow-y:auto;max-height:50vh;`;

  244.         if (cnt === 0) {
  245.             content.innerHTML = `<div style="padding:40px 0;text-align:center;color:#999;">暂无过滤</div>`;
  246.         } else {
  247.             let html = '';
  248.             list.forEach(it => {
  249.                 html += `
  250.                 <div style="padding:10px 0;border-bottom:1px solid #f5f5f5;">
  251.                     <div style="font-size:14px;line-height:1.5;word-break:break-all;margin-bottom:6px;">${it.content}</div>
  252.                     <a href="${it.url}" target="_blank" style="font-size:13px;color:#007bff;text-decoration:none;">查看原帖</a>
  253.                 </div>`;
  254.             });
  255.             content.innerHTML = html;
  256.         }

  257.         const foot = document.createElement('div');
  258.         foot.style.cssText = `padding:12px 20px;border-top:1px solid #eee;background:#fff;`;
  259.         foot.innerHTML = `<button id="c-close" style="width:100%;padding:10px;border:none;border-radius:6px;background:#eee;">关闭</button>`;

  260.         wrap.appendChild(head);
  261.         wrap.appendChild(content);
  262.         wrap.appendChild(foot);
  263.         overlay.appendChild(wrap);
  264.         document.body.appendChild(overlay);
  265.         animateIn(wrap);

  266.         function close() {
  267.             animateOut(wrap, () => { overlay.remove(); enableBackground(); });
  268.         }
  269.         foot.querySelector('#c-close').onclick = close;
  270.         overlay.onclick = e => e.target === overlay && close();
  271.     }

  272.     function addButtons() {
  273.         const bar = document.createElement('div');
  274.         bar.style.cssText = `
  275.             position:fixed;bottom:20px;right:20px;display:flex;gap:8px;z-index:9998;
  276.         `;
  277.         const btn1 = document.createElement('button');
  278.         btn1.textContent = '评论过滤器';
  279.         btn1.style.cssText = `padding:8px 12px;background:#007bff;color:#fff;border:none;border-radius:6px;cursor:pointer;`;
  280.         btn1.onclick = createFilterUI;

  281.         const btn2 = document.createElement('button');
  282.         btn2.id = 'filtered-btn';
  283.         btn2.style.cssText = `padding:8px 12px;background:#28a745;color:#fff;border:none;border-radius:6px;cursor:pointer;`;
  284.         btn2.onclick = showFilteredList;

  285.         bar.appendChild(btn1);
  286.         bar.appendChild(btn2);
  287.         document.body.appendChild(bar);
  288.         updateFilterButton();
  289.     }

  290.     function updateFilterButton() {
  291.         const btn = document.getElementById('filtered-btn');
  292.         if (!btn) return;
  293.         btn.textContent = `已过滤(${currentPageFiltered.length})`;
  294.     }

  295.     processAllNotices();
  296.     addButtons();

  297.     const ul = document.querySelector(noticeSelector);
  298.     if (ul) {
  299.         new MutationObserver(() => {
  300.             processAllNotices();
  301.             updateFilterButton();
  302.         }).observe(ul, { childList: true });
  303.     }

  304. })();
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

124

主题

2511

回帖

9030

积分

硕士生

秋枫Mod

Rank: 6Rank: 6

金币
2905
好评
63
信誉
98

MT论坛最佳新人MT论坛帅哥MT论坛新人考神MT论坛活跃会员MT论坛灌水老大

QQ
发表于 2026-5-1 10:55:58 来自手机  | 显示全部楼层  来自 河南
@青春向上 大佬来看看。我一楼吃瓜
回复

使用道具 举报

65

主题

3824

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
2355
好评
12
信誉
100

MT论坛最佳新人考神MT论坛帅哥MT论坛新人MT论坛活跃会员

发表于 2026-5-1 11:02:45 来自手机  | 显示全部楼层  来自 贵州
这个好,这个就能看到更重要的评论了
回复

使用道具 举报

22

主题

2390

回帖

7307

积分

硕士生

Rank: 6Rank: 6

金币
1433
好评
21
信誉
101

MT论坛新人考神

发表于 2026-5-1 11:03:28 来自手机  | 显示全部楼层  来自 湖南
看看隐藏
回复

使用道具 举报

0

主题

2725

回帖

6053

积分

硕士生

Rank: 6Rank: 6

金币
3357
好评
0
信誉
100
发表于 2026-5-1 11:03:36 来自手机  | 显示全部楼层  来自 日本
支持楼主,感谢分享
回复

使用道具 举报

19

主题

1015

回帖

3422

积分

大学生

王鹏

Rank: 5Rank: 5

金币
1046
好评
1
信誉
100

MT论坛最佳新人考神MT论坛帅哥MT论坛新人

QQ
发表于 2026-5-1 11:04:09 来自手机  | 显示全部楼层  来自 陕西
看看隐藏
回复

使用道具 举报

124

主题

2511

回帖

9030

积分

硕士生

秋枫Mod

Rank: 6Rank: 6

金币
2905
好评
63
信誉
98

MT论坛最佳新人MT论坛帅哥MT论坛新人考神MT论坛活跃会员MT论坛灌水老大

QQ
发表于 2026-5-1 11:05:10 来自手机  | 显示全部楼层  来自 河南
鬼才同学 发表于 2026-5-1 11:02
这个好,这个就能看到更重要的评论了

回复

使用道具 举报

59

主题

1346

回帖

4018

积分

大学生

www.ldpnb.xyz

Rank: 5Rank: 5

金币
660
好评
5
信誉
101

考神MT论坛最佳新人MT论坛新人

发表于 2026-5-1 11:05:28 来自手机  | 显示全部楼层  来自 广东
看看
回复

使用道具 举报

4

主题

2642

回帖

8327

积分

硕士生

orange

Rank: 6Rank: 6

金币
751
好评
0
信誉
100

MT论坛帅哥MT论坛最佳新人MT论坛新人考神

发表于 2026-5-1 11:08:38 来自手机  | 显示全部楼层  来自 云南
看看是什么
回复

使用道具 举报

44

主题

1558

回帖

5645

积分

硕士生

Rank: 6Rank: 6

金币
3882
好评
2
信誉
242

MT论坛帅哥MT论坛新人考神

发表于 2026-5-1 11:08:57 来自手机  | 显示全部楼层  来自 福建
看看隐藏
回复

使用道具 举报

10

主题

176

回帖

796

积分

初中生

Rank: 3Rank: 3

金币
559
好评
7
信誉
101
发表于 2026-5-1 11:10:13 来自手机  | 显示全部楼层  来自 江苏
感谢分享
回复

使用道具 举报

0

主题

608

回帖

1650

积分

高中生

Rank: 4

金币
1045
好评
0
信誉
100
发表于 2026-5-1 11:11:12 来自手机  | 显示全部楼层  来自 江苏
感谢分享
回复

使用道具 举报

172

主题

4652

回帖

1万

积分

博士生

水怪

Rank: 7Rank: 7Rank: 7

金币
4517
好评
23
信誉
99

MT论坛灌水老大MT论坛活跃会员

发表于 2026-5-1 11:15:08 来自手机  | 显示全部楼层  来自 福建
看看隐藏
回复

使用道具 举报

2

主题

2082

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
6385
好评
0
信誉
104

MT论坛新人考神

发表于 2026-5-1 11:18:05 来自手机  | 显示全部楼层  来自 浙江
确实挺不错的
回复

使用道具 举报

69

主题

3010

回帖

8578

积分

硕士生

logking

Rank: 6Rank: 6

金币
2894
好评
10
信誉
117

MT论坛最佳新人MT论坛帅哥考神MT论坛新人

发表于 2026-5-1 11:29:30 来自手机  | 显示全部楼层  来自 广东
看看
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表