123
返回列表 发新帖
楼主: Cynnie - 

MTForum-更新

[复制链接]

124

主题

2507

回帖

9022

积分

硕士生

秋枫Mod

Rank: 6Rank: 6

金币
2898
好评
63
信誉
98

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

QQ
发表于 1 小时前 来自手机  | 显示全部楼层  来自 河南
Cynnie 发表于 2026-8-26 11:19
感谢

bug喜+1


这里无法完整预览别人的回复,而且别人的回复中如果带图片也没办法看哦




而且消息通知列表里是分页数的,论坛APP好像还不能自动加载下一页


这一点从已过滤消息数量上就能看出来的
这是我的脚本
这是论坛APP


最后脚本可能对你有用,给你参考参考
  1. // ==UserScript==
  2. // [url=home.php?mod=space&uid=121064]@name[/url]         [MT论坛]消息预览++
  3. // @namespace    https://github.com/qcxs/mtbbs
  4. // @version      2026-06-15
  5. // @description  基于青春向上大佬的消息预览和自动加载进行优化修改,增加和优化了消息过滤功能。
  6. // @author       青春向上&秋枫Mod
  7. // @match        *://bbs.binmt.cc/home.php?mod=space&do=notice*
  8. // @match        *://bbs.binmt.cc/home.php?*type=reply*
  9. // @icon         https://bbs.binmt.cc/favicon.ico
  10. // @grant        none
  11. // @run-at       document-idline
  12. // @license      MIT
  13. // ==/UserScript==

  14. (async function () {
  15.     'use strict';

  16.     // ==================== 全局配置和工具函数 ====================
  17.    
  18.     const urlObj = new URL(window.location.href);
  19.     const searchParams = urlObj.searchParams;
  20.     const phpFile = urlObj.pathname.split('/').pop();
  21.    
  22.     async function fetchReplyRoot(url, timeout = 3000) {
  23.         try {
  24.             const res = await fetch(url, { signal: AbortSignal.timeout(timeout) });
  25.             if (!res.ok) return '';
  26.             const xml = new DOMParser().parseFromString(await res.text(), 'text/xml');
  27.             return xml.querySelector('root')?.textContent || '';
  28.         } catch {
  29.             return '';
  30.         }
  31.     }

  32.     // ==================== 消息提醒预览功能 ====================
  33.    
  34.     const PREVIEW_CONFIG = {
  35.         PROCESSED_MARK: 'mt-preview-processed',
  36.         notice: {
  37.             noticeSelector: '.comiis_notice_list>ul',
  38.             selectContent: 'div.comiis_messages',
  39.             validCheck: 'a.lit[href*="goto=findpost"]',
  40.             MAX_CACHE_COUNT: 100,
  41.             CACHE_STORAGE_KEY: 'mt_bbs_preview_cache',
  42.             REQUEST_DELAY: 100
  43.         },
  44.         postReply: {
  45.             threadSelector: '.comiis_forumlist>ul',
  46.             threadASelector: '.mmlist_li_box a',
  47.             validCheck: '.mmlist_li_box a',
  48.             loadedMark: 'replyLoaded'
  49.         },
  50.         filter: {
  51.             FILTER_STORAGE_KEY: 'mt_bbs_filter_keywords',
  52.             FILTERED_MARK: 'mt-filtered-hidden',
  53.             FILTERED_CLASS: 'mt-filtered-item',
  54.             SETTINGS_MARK: 'mt-filter-settings'
  55.         }
  56.     };

  57.     // 过滤器相关函数
  58.     function getFilterKeywords() {
  59.         try {
  60.             const data = localStorage.getItem(PREVIEW_CONFIG.filter.FILTER_STORAGE_KEY);
  61.             if (!data) return [];
  62.             return data.split('\n').map(s => s.trim()).filter(s => s.length > 0);
  63.         } catch { return []; }
  64.     }

  65.     function saveFilterKeywords(keywords) {
  66.         localStorage.setItem(PREVIEW_CONFIG.filter.FILTER_STORAGE_KEY, keywords.join('\n'));
  67.     }

  68.     function shouldFilter(content) {
  69.         const keywords = getFilterKeywords();
  70.         if (!keywords.length) return false;
  71.         return keywords.some(kw => content.includes(kw));
  72.     }

  73.     function updateFilterButton() {
  74.         const btn = document.getElementById('mt-filter-toggle-btn');
  75.         if (!btn) return;
  76.         const count = document.querySelectorAll(`li.${PREVIEW_CONFIG.filter.FILTERED_CLASS}`).length;
  77.         btn.textContent = `已过滤消息(${count})`;
  78.     }

  79.     function toggleFilteredMessages() {
  80.         const btn = document.getElementById('mt-filter-toggle-btn');
  81.         if (!btn) return;
  82.         const items = document.querySelectorAll(`li.${PREVIEW_CONFIG.filter.FILTERED_CLASS}`);
  83.         const isCurrentlyHidden = btn.dataset.showing === 'true';

  84.         if (isCurrentlyHidden) {
  85.             items.forEach((li, index) => {
  86.                 li.style.transition = `opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease`;
  87.                 li.style.opacity = '0';
  88.                 li.style.transform = 'translateY(-10px)';
  89.                 li.style.maxHeight = '0';
  90.                 li.style.marginBottom = '0';
  91.                
  92.                 setTimeout(() => {
  93.                     li.style.display = 'none';
  94.                     li.setAttribute(PREVIEW_CONFIG.filter.FILTERED_MARK, 'true');
  95.                 }, 300);
  96.             });
  97.             
  98.             setTimeout(() => {
  99.                 btn.dataset.showing = 'false';
  100.                 btn.textContent = `已过滤消息(${items.length})`;
  101.                 btn.style.color = '#53bcf5';
  102.                
  103.                 // 切换后检查是否需要自动加载
  104.                 setTimeout(checkAndLoadNext, 500);
  105.             }, 300);
  106.         } else {
  107.             items.forEach((li, index) => {
  108.                 li.style.display = '';
  109.                 li.style.transition = `opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease`;
  110.                 li.style.opacity = '0';
  111.                 li.style.transform = 'translateY(-10px)';
  112.                 li.style.maxHeight = '0';
  113.                 li.style.marginBottom = '0';
  114.                
  115.                 setTimeout(() => {
  116.                     li.style.opacity = '1';
  117.                     li.style.transform = 'translateY(0)';
  118.                     li.style.maxHeight = '500px';
  119.                     li.style.marginBottom = '8px';
  120.                 }, index * 50);
  121.             });
  122.             
  123.             btn.dataset.showing = 'true';
  124.             btn.textContent = `收起已过滤消息(${items.length})`;
  125.             btn.style.color = '#e6a23c';
  126.         }
  127.     }

  128.     function createReplyDialog(replyUrl, tid, pid) {
  129.         const existingDialog = document.getElementById('mt-reply-dialog');
  130.         if (existingDialog) existingDialog.remove();

  131.         const dialog = document.createElement('div');
  132.         dialog.id = 'mt-reply-dialog';
  133.         dialog.style.cssText = `
  134.             position: fixed;
  135.             top: 0;
  136.             left: 0;
  137.             right: 0;
  138.             bottom: 0;
  139.             z-index: 9999;
  140.             background: #fff;
  141.             display: flex;
  142.             flex-direction: column;
  143.             opacity: 0;
  144.             transition: opacity 0.3s ease-out;
  145.         `;

  146.         dialog.innerHTML = `
  147.             <div style="display:flex;justify-content:space-between;align-items:center;padding: 12px 16px; border-bottom: 1px solid #eee; background:#f7f8fa;">
  148.                 <strong style="font-size:16px; font-weight:600; color:#333;">回复</strong>
  149.                 <span style="cursor:pointer;font-size:24px;color:#999; line-height:1;" id="mt-reply-close">×</span>
  150.             </div>
  151.             <div id="mt-reply-iframe-container" style="flex: 1; overflow-y: auto;">
  152.             </div>
  153.         `;

  154.         document.body.appendChild(dialog);

  155.         const overlay = document.createElement('div');
  156.         overlay.id = 'mt-reply-overlay';
  157.         overlay.style.cssText = `
  158.             position: fixed;
  159.             top: 0; left: 0; right: 0; bottom: 0;
  160.             z-index: 9998;
  161.             background: rgba(0,0,0,0.4);
  162.             backdrop-filter: blur(2px);
  163.             opacity: 0;
  164.             transition: opacity 0.3s ease-out;
  165.         `;
  166.         overlay.onclick = closeReplyDialog;
  167.         document.body.appendChild(overlay);

  168.         setTimeout(() => {
  169.             dialog.style.opacity = '1';
  170.             overlay.style.opacity = '1';
  171.         }, 10);

  172.         document.getElementById('mt-reply-close').onclick = closeReplyDialog;

  173.         const iframeContainer = document.getElementById('mt-reply-iframe-container');
  174.         const iframe = document.createElement('iframe');
  175.         iframe.src = replyUrl;
  176.         iframe.style.cssText = `
  177.             width: 100%;
  178.             height: 100%;
  179.             border: none;
  180.             min-height: 60vh;
  181.         `;
  182.         iframeContainer.appendChild(iframe);
  183.     }

  184.     function closeReplyDialog() {
  185.         const dialog = document.getElementById('mt-reply-dialog');
  186.         const overlay = document.getElementById('mt-reply-overlay');
  187.         
  188.         if (dialog) {
  189.             dialog.style.opacity = '0';
  190.         }
  191.         if (overlay) {
  192.             overlay.style.opacity = '0';
  193.         }

  194.         setTimeout(() => {
  195.             if (dialog) {
  196.                 dialog.remove();
  197.             }
  198.             if (overlay) {
  199.                 overlay.remove();
  200.             }
  201.         }, 300);
  202.     }

  203.     function createFilterToolbar() {
  204.         if (document.getElementById('mt-filter-toolbar')) return;

  205.         const toolbar = document.createElement('div');
  206.         toolbar.id = 'mt-filter-toolbar';
  207.         toolbar.style.cssText = `
  208.             display: flex;
  209.             gap: 8px;
  210.             align-items: center;
  211.             padding: 8px 12px;
  212.             margin-bottom: 10px;
  213.             background: #f7f8fa;
  214.             border-radius: 6px;
  215.             border: 1px solid #e8e8e8;
  216.         `;

  217.         const settingsBtn = document.createElement('button');
  218.         settingsBtn.id = 'mt-filter-settings-btn';
  219.         settingsBtn.textContent = '⚙ 过滤设置';
  220.         settingsBtn.style.cssText = `
  221.             padding: 5px 12px;
  222.             border: 1px solid #53bcf5;
  223.             background: #fff;
  224.             color: #53bcf5;
  225.             border-radius: 4px;
  226.             cursor: pointer;
  227.             font-size: 13px;
  228.         `;
  229.         settingsBtn.onclick = () => toggleFilterSettings();

  230.         const toggleBtn = document.createElement('button');
  231.         toggleBtn.id = 'mt-filter-toggle-btn';
  232.         toggleBtn.textContent = '已过滤消息(0)';
  233.         toggleBtn.dataset.showing = 'false';
  234.         toggleBtn.style.cssText = `
  235.             padding: 5px 12px;
  236.             border: 1px solid #e6a23c;
  237.             background: #fff;
  238.             color: #53bcf5;
  239.             border-radius: 4px;
  240.             cursor: pointer;
  241.             font-size: 13px;
  242.             transition: all 0.2s ease;
  243.         `;
  244.         toggleBtn.onclick = toggleFilteredMessages;
  245.         
  246.         toggleBtn.addEventListener('mouseenter', () => {
  247.             toggleBtn.style.backgroundColor = '#f0f0f0';
  248.         });
  249.         toggleBtn.addEventListener('mouseleave', () => {
  250.             toggleBtn.style.backgroundColor = '#fff';
  251.         });

  252.         toolbar.appendChild(settingsBtn);
  253.         toolbar.appendChild(toggleBtn);

  254.         const container = document.querySelector(PREVIEW_CONFIG.notice.noticeSelector);
  255.         if (container) {
  256.             container.parentNode.insertBefore(toolbar, container);
  257.         }
  258.     }

  259.     let filterSettingsPanel = null;
  260.     let originalOverflow = '';

  261.     function toggleFilterSettings() {
  262.         if (filterSettingsPanel && filterSettingsPanel.parentNode) {
  263.             closeFilterSettings();
  264.             return;
  265.         }

  266.         originalOverflow = document.body.style.overflow;
  267.         document.body.style.overflow = 'hidden';

  268.         filterSettingsPanel = document.createElement('div');
  269.         filterSettingsPanel.id = 'mt-filter-settings-panel';
  270.         filterSettingsPanel.style.cssText = `
  271.             position: fixed;
  272.             top: 50%;
  273.             left: 50%;
  274.             transform: translate(-50%, -50%) scale(0.95);
  275.             z-index: 9999;
  276.             background: #fff;
  277.             border: 1px solid #53bcf5;
  278.             border-radius: 6px;
  279.             width: 90%;
  280.             max-width: 500px;
  281.             max-height: 80vh;
  282.             overflow-y: auto;
  283.             box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  284.             opacity: 0;
  285.             transition: all 0.3s ease-out;
  286.         `;

  287.         const keywords = getFilterKeywords();

  288.         filterSettingsPanel.innerHTML = `
  289.             <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px; padding: 16px 20px 0;">
  290.                 <strong style="font-size:15px; font-weight:600; color:#333;">评论过滤设置</strong>
  291.                 <span style="cursor:pointer;font-size:22px;color:#999; line-height:1;" id="mt-filter-close">×</span>
  292.             </div>
  293.             <p style="font-size:12px;color:#999;margin:0 0 8px 0; padding: 0 20px;">每行一个关键词,包含该关键词的消息将被隐藏</p>
  294.             <textarea id="mt-filter-keywords-input" style="
  295.                 width: calc(100% - 40px);
  296.                 height: 200px;
  297.                 margin: 0 20px;
  298.                 padding: 12px;
  299.                 border: 1px solid #ddd;
  300.                 border-radius: 4px;
  301.                 font-size: 14px;
  302.                 resize: vertical;
  303.                 box-sizing: border-box;
  304.                 line-height: 1.5;
  305.                 font-family: inherit;
  306.             ">${keywords.join('\n')}</textarea>
  307.             <div style="display:flex;justify-content:flex-end;align-items:center;margin-top:12px; padding: 0 20px 16px;">
  308.                 <span style="font-size:12px;color:#999;">输入后自动保存</span>
  309.             </div>
  310.         `;

  311.         document.body.appendChild(filterSettingsPanel);

  312.         const overlay = document.createElement('div');
  313.         overlay.id = 'mt-filter-overlay';
  314.         overlay.style.cssText = `
  315.             position: fixed;
  316.             top: 0; left: 0; right: 0; bottom: 0;
  317.             z-index: 9998;
  318.             background: rgba(0,0,0,0.4);
  319.             backdrop-filter: blur(2px);
  320.             opacity: 0;
  321.             transition: opacity 0.3s ease-out;
  322.         `;
  323.         overlay.onclick = closeFilterSettings;
  324.         document.body.appendChild(overlay);

  325.         setTimeout(() => {
  326.             filterSettingsPanel.style.transform = 'translate(-50%, -50%) scale(1)';
  327.             filterSettingsPanel.style.opacity = '1';
  328.             overlay.style.opacity = '1';
  329.         }, 10);

  330.         document.getElementById('mt-filter-close').onclick = closeFilterSettings;

  331.         const keywordsInput = document.getElementById('mt-filter-keywords-input');
  332.         keywordsInput.addEventListener('input', autoSaveKeywords);
  333.     }

  334.     function autoSaveKeywords() {
  335.         const text = document.getElementById('mt-filter-keywords-input').value;
  336.         const kws = text.split('\n').map(s => s.trim()).filter(s => s.length > 0);
  337.         saveFilterKeywords(kws);
  338.         reapplyFilter();
  339.     }

  340.     function closeFilterSettings() {
  341.         if (!filterSettingsPanel) return;

  342.         document.body.style.overflow = originalOverflow;

  343.         filterSettingsPanel.style.transform = 'translate(-50%, -50%) scale(0.95)';
  344.         filterSettingsPanel.style.opacity = '0';
  345.         const overlay = document.getElementById('mt-filter-overlay');
  346.         if (overlay) {
  347.             overlay.style.opacity = '0';
  348.         }

  349.         setTimeout(() => {
  350.             if (filterSettingsPanel) {
  351.                 filterSettingsPanel.remove();
  352.                 filterSettingsPanel = null;
  353.             }
  354.             if (overlay) {
  355.                 overlay.remove();
  356.             }
  357.         }, 300);
  358.     }

  359.     function reapplyFilter() {
  360.         const keywords = getFilterKeywords();
  361.         const allItems = document.querySelectorAll('li');
  362.         allItems.forEach(li => {
  363.             if (!li.hasAttribute(PREVIEW_CONFIG.PROCESSED_MARK)) return;
  364.             const contentEl = li.querySelector('.comiis_messages');
  365.             if (!contentEl) return;
  366.             const content = contentEl.innerText || '';

  367.             if (keywords.length === 0) {
  368.                 li.classList.remove(PREVIEW_CONFIG.filter.FILTERED_CLASS);
  369.                 li.style.display = '';
  370.                 li.removeAttribute(PREVIEW_CONFIG.filter.FILTERED_MARK);
  371.                 removeFilterBadge(li);
  372.             } else if (keywords.some(kw => content.includes(kw))) {
  373.                 if (!li.classList.contains(PREVIEW_CONFIG.filter.FILTERED_CLASS)) {
  374.                     li.classList.add(PREVIEW_CONFIG.filter.FILTERED_CLASS);
  375.                 }
  376.                 if (li.getAttribute(PREVIEW_CONFIG.filter.FILTERED_MARK) !== 'true') {
  377.                     li.style.display = 'none';
  378.                     li.setAttribute(PREVIEW_CONFIG.filter.FILTERED_MARK, 'true');
  379.                 }
  380.                 addFilterBadge(li);
  381.             } else {
  382.                 li.classList.remove(PREVIEW_CONFIG.filter.FILTERED_CLASS);
  383.                 li.style.display = '';
  384.                 li.removeAttribute(PREVIEW_CONFIG.filter.FILTERED_MARK);
  385.                 removeFilterBadge(li);
  386.             }
  387.         });
  388.         updateFilterButton();
  389.         
  390.         // 过滤后检查是否需要自动加载
  391.         setTimeout(checkAndLoadNext, 500);
  392.     }

  393.     function applyFilterToItem(li, content) {
  394.         if (shouldFilter(content)) {
  395.             li.classList.add(PREVIEW_CONFIG.filter.FILTERED_CLASS);
  396.             li.style.display = 'none';
  397.             li.setAttribute(PREVIEW_CONFIG.filter.FILTERED_MARK, 'true');
  398.             addFilterBadge(li);
  399.             updateFilterButton();
  400.             return true;
  401.         }
  402.         removeFilterBadge(li);
  403.         return false;
  404.     }

  405.     function addFilterBadge(li) {
  406.         if (li.querySelector('.mt-filter-badge')) return;

  407.         const badge = document.createElement('span');
  408.         badge.className = 'mt-filter-badge';
  409.         badge.textContent = '已过滤';
  410.         badge.style.cssText = `
  411.             position: absolute;
  412.             bottom: 6px;
  413.             right: 6px;
  414.             color: #f56c6c;
  415.             background: #fff;
  416.             border: 1px solid #f56c6c;
  417.             font-size: 11px;
  418.             font-weight: 500;
  419.             padding: 2px 6px;
  420.             border-radius: 3px;
  421.             z-index: 10;
  422.             box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  423.         `;

  424.         if (getComputedStyle(li).position === 'static') {
  425.             li.style.position = 'relative';
  426.         }

  427.         li.appendChild(badge);
  428.     }

  429.     function removeFilterBadge(li) {
  430.         const badge = li.querySelector('.mt-filter-badge');
  431.         if (badge) {
  432.             badge.remove();
  433.         }
  434.     }

  435.     function initCommonListObserver(containerSel, itemSel, validCheck, callback) {
  436.         const container = document.querySelector(containerSel);
  437.         if (!container) {
  438.             console.log('监听失败,没有帖子!')
  439.             return;
  440.         }
  441.         document.querySelectorAll(itemSel).forEach(el => {
  442.             if (validCheck(el))
  443.                 callback(el)
  444.         });
  445.         const observer = new MutationObserver(muts => {
  446.             muts.forEach(mut => {
  447.                 mut.addedNodes.forEach(node => {
  448.                     if (node.nodeType === 1 && node.matches(itemSel)) {
  449.                         if (validCheck(node)) callback(node);
  450.                     }
  451.                 });
  452.             });
  453.         });
  454.         observer.observe(container, { childList: true, subtree: false });
  455.     }

  456.     async function initNoticePreview() {
  457.         const cfg = PREVIEW_CONFIG.notice;

  458.         createFilterToolbar();

  459.         let cacheList = [];
  460.         try {
  461.             const data = localStorage.getItem(cfg.CACHE_STORAGE_KEY);
  462.             if (data) {
  463.                 const parsed = JSON.parse(data);
  464.                 if (Array.isArray(parsed)) {
  465.                     cacheList = parsed;
  466.                 } else {
  467.                     throw new Error('缓存格式有误,自动重置!')
  468.                 }
  469.             }
  470.         } catch (e) {
  471.             console.log('发生错误:', e)
  472.             localStorage.removeItem(cfg.CACHE_STORAGE_KEY);
  473.             cacheList = [];
  474.         }

  475.         const requestQueue = [];
  476.         let isQueueRunning = false;

  477.         initCommonListObserver(cfg.noticeSelector, `${cfg.noticeSelector}>li`,
  478.             li => !li.hasAttribute(PREVIEW_CONFIG.PROCESSED_MARK) && li.querySelector(cfg.validCheck),
  479.             li => {
  480.                 if (!li.hasAttribute(PREVIEW_CONFIG.PROCESSED_MARK)) {
  481.                     processNoticeItem(li);
  482.                 }
  483.             });

  484.         function processNoticeItem(li) {
  485.             li.setAttribute(PREVIEW_CONFIG.PROCESSED_MARK, 'true');
  486.             const viewLink = li.querySelector(cfg.validCheck);
  487.             if (!viewLink) return;

  488.             const { tid, pid } = getTidPidFromUrl(viewLink.href);
  489.             if (!tid || !pid) return;
  490.             const cacheKey = `${tid}_${pid}`;
  491.             const cacheData = cacheList.find(item => item.key === cacheKey);

  492.             if (cacheData) {
  493.                 insertContentToLi(li, cacheData.content, cacheData.replyHref, tid, pid);
  494.                 applyFilterToItem(li, cacheData.content);
  495.             } else {
  496.                 requestQueue.push({ li, tid, pid, cacheKey });
  497.                 if (!isQueueRunning) runQueue();
  498.             }
  499.         }

  500.         async function runQueue() {
  501.             if (requestQueue.length === 0) { isQueueRunning = false; return; }
  502.             isQueueRunning = true;
  503.             const task = requestQueue.shift();

  504.             try {
  505.                 const url = `https://bbs.binmt.cc/forum.php?mod=viewthread&tid=${task.tid}&viewpid=${task.pid}&mobile=2&inajax=1`;
  506.                 const rootHtml = await fetchReplyRoot(url);
  507.                 const result = parseContentAndReplyHref(rootHtml);
  508.                 insertContentToLi(task.li, result.content, result.replyHref, task.tid, task.pid);

  509.                 applyFilterToItem(task.li, result.content);

  510.                 if (result.content && !result.content.includes('失败') && !result.content.includes('[空内容]')) {
  511.                     cacheList = cacheList.filter(item => item.key !== task.cacheKey);
  512.                     cacheList.unshift({ key: task.cacheKey, ...result, time: Date.now() });
  513.                     if (cacheList.length > cfg.MAX_CACHE_COUNT) cacheList = cacheList.slice(0, cfg.MAX_CACHE_COUNT);
  514.                     localStorage.setItem(cfg.CACHE_STORAGE_KEY, JSON.stringify(cacheList));
  515.                 }
  516.             } catch (e) {
  517.                 insertContentToLi(task.li, '[加载失败]', '', task.tid, task.pid);
  518.             } finally {
  519.                 setTimeout(runQueue, cfg.REQUEST_DELAY);
  520.             }
  521.         }

  522.         function getTidPidFromUrl(url) {
  523.             const p = new URLSearchParams(url);
  524.             return { tid: p.get('ptid'), pid: p.get('pid') };
  525.         }

  526.         function parseContentAndReplyHref(rootHtml) {
  527.             try {
  528.                 if (!rootHtml) return { content: '[获取失败]', replyHref: '' };
  529.                 const div = document.createElement('div');
  530.                 div.innerHTML = rootHtml;
  531.                 const content = div.querySelector(cfg.selectContent)?.innerHTML?.trim() || '[空内容]';
  532.                 const replyHref = div.querySelector('a[href*="action=reply"]')?.href || '';
  533.                 div.remove();
  534.                 return { content, replyHref };
  535.             } catch { return { content: '[解析失败]', replyHref: '' }; }
  536.         }

  537.         function insertContentToLi(li, html, replyHref, tid, pid) {
  538.             const box = document.createElement('div');
  539.             box.style.width = '100%';
  540.             li.appendChild(box);
  541.             const root = box.attachShadow({ mode: 'open' });
  542.             root.innerHTML = `
  543.                 <style>
  544.                     .comiis_postli img[smilieid]{max-height:22px;margin:1px;vertical-align:top;}
  545.                     .comiis_messages {
  546.                         font-size: 16px !important;
  547.                         line-height: 1.6 !important;
  548.                     }
  549.                     .comiis_a {
  550.                         font-size: 16px !important;
  551.                         line-height: 1.6 !important;
  552.                     }
  553.                     .comiis_postli {
  554.                         font-size: 16px !important;
  555.                     }
  556.                 </style>
  557.                 <link rel="stylesheet" href="https://cdn.binmt.cc/template/comiis_app/comiis/css/comiis.css">
  558.                 <link rel="stylesheet" href="https://bbs.binmt.cc/source/plugin/comiis_app/cache/comiis_1_style.css">
  559.                 <div class="comiis_postli"><div class="comiis_messages"><div class="comiis_a">${html}</div></div></div>`;

  560.             if (replyHref) {
  561.                 const btn = document.createElement('span');
  562.                 btn.textContent = '回复';
  563.                 btn.style.cssText = 'float:right;color:#53bcf5;margin-right:20px;cursor:pointer';
  564.                 btn.onclick = e => {
  565.                     e.stopPropagation();
  566.                     createReplyDialog(replyHref, tid, pid);
  567.                 };
  568.                 li.querySelector('h2')?.appendChild(btn);
  569.             }
  570.         }
  571.     }

  572.     function initPostReply() {
  573.         const cfg = PREVIEW_CONFIG.postReply;

  574.         initCommonListObserver(cfg.threadSelector, `${cfg.threadSelector}>li`,
  575.             li => !li.hasAttribute(PREVIEW_CONFIG.PROCESSED_MARK) && li.querySelector(cfg.validCheck),
  576.             li => { createCheckReplyButton(li); });

  577.         function createCheckReplyButton(li) {
  578.             li.setAttribute(PREVIEW_CONFIG.PROCESSED_MARK, 'true');

  579.             const btn = document.createElement('span');
  580.             btn.textContent = '查看回复';
  581.             btn.style.cssText = `
  582.                 display: block;
  583.                 width: 100%;
  584.                 text-align: center;
  585.                 padding: 6px 0;
  586.                 color:#53bcf5;
  587.                 background: #f7f8fa;
  588.                 border-radius: 4px;
  589.                 font-size: 14px;
  590.             `;

  591.             btn.onclick = async () => {
  592.                 if (li.dataset[cfg.loadedMark]) return;
  593.                 li.dataset[cfg.loadedMark] = "true";
  594.                 li.dataset.replyLoaded = '';
  595.                 btn.textContent = '加载中...';
  596.                 li.querySelectorAll('div[id^="pid"]').forEach(el => el.remove());

  597.                 try {
  598.                     const threadA = li.querySelector(cfg.threadASelector);
  599.                     if (!threadA) throw new Error('获取帖子信息失败');

  600.                     const tid = threadA.href.match(/thread-(\d+)-/)?.[1];
  601.                     const authorid = searchParams.get('uid') || window.discuz_uid;
  602.                     if (!tid || !authorid) throw new Error('tid/uid 获取失败');

  603.                     const apiUrl = `https://bbs.binmt.cc/forum.php?mod=viewthread&tid=${tid}&page=1&authorid=${authorid}&inajax=1`;
  604.                     const rootHtml = await fetchReplyRoot(apiUrl);
  605.                     const pidElements = parseAllPidElements(rootHtml);

  606.                     if (pidElements.length) {
  607.                         pidElements.forEach(el => li.appendChild(el));
  608.                         btn.textContent = `刷新回复 (${pidElements.length}条)`;
  609.                     } else {
  610.                         btn.textContent = '无回复';
  611.                     }
  612.                 } catch (err) {
  613.                     btn.textContent = '重试';
  614.                     console.warn('加载异常', err);
  615.                 } finally {
  616.                     li.dataset[cfg.loadedMark] = "";
  617.                 }
  618.             };
  619.             li.appendChild(btn);
  620.         }

  621.         function parseAllPidElements(rootHtml) {
  622.             if (!rootHtml) throw new Error('获取回复数据失败');
  623.             const div = document.createElement('div');
  624.             div.innerHTML = rootHtml;
  625.             return Array.from(div.querySelectorAll('div[id^="pid"]'));
  626.         }
  627.     }

  628.     // ==================== 自动下一页功能 ====================
  629.    
  630.     const PAGINATION_ENUM = {
  631.         noticeSelector: '.comiis_notice_list>ul',
  632.         postListSelector: '.comiis_forumlist>ul',
  633.         unknownPage: 999,
  634.         loadThreshold: 1000,
  635.         requestTimeout: 3000,
  636.     }

  637.     let paginationState = {
  638.         currentPage: 1,
  639.         totalPage: 999,
  640.         isLoading: false,
  641.         isFailed: false,
  642.         observedMarkers: new Set(),
  643.         mode: '',
  644.         listSelector: ''
  645.     };

  646.     function initAutoNextPage() {
  647.         // 判断页面类型
  648.         const url = new URL(window.location.href);
  649.         
  650.         if (url.searchParams.get('mod') === 'space' && url.searchParams.get('do') === 'notice') {
  651.             paginationState.mode = 'notice';
  652.             paginationState.listSelector = PAGINATION_ENUM.noticeSelector;
  653.         } else if (url.searchParams.get('type') === 'reply') {
  654.             paginationState.mode = 'postReply';
  655.             paginationState.listSelector = PAGINATION_ENUM.postListSelector;
  656.         } else {
  657.             console.log('自动下一页:当前页面不支持分页功能');
  658.             return;
  659.         }

  660.         // 检查是否有分页元素
  661.         if (!$('.comiis_page').length) {
  662.             console.log('自动下一页:无分页元素,功能终止');
  663.             return;
  664.         }

  665.         // 获取分页信息
  666.         const pageInfos = getPaginationPageInfo();
  667.         paginationState.currentPage = pageInfos.currentPage;
  668.         paginationState.totalPage = pageInfos.totalPage;

  669.         console.log(`自动下一页:初始化分页功能,第${paginationState.currentPage}/${paginationState.totalPage}页`);

  670.         // 初始化分页功能
  671.         initPagination();
  672.     }

  673.     function getPaginationPageInfo() {
  674.         const $select = $('#dumppage');
  675.         const totalPage = $select.length ? $select.find('option').length : PAGINATION_ENUM.unknownPage;
  676.         const urlPage = new URL(window.location.href).searchParams.get('page');
  677.         let currentPage = parseInt(urlPage?.trim()) || ($select.length ? parseInt($select.find('option:selected').val()) : 1);
  678.         
  679.         if (currentPage < 1) currentPage = 1;
  680.         else if (!Number.isInteger(currentPage)) currentPage = Math.floor(currentPage);
  681.         
  682.         return { currentPage, totalPage };
  683.     }

  684.     function initPagination() {
  685.         // 隐藏原始分页选择器
  686.         $('.comiis_page').css('display', 'none');

  687.         // 添加当前页标记
  688.         addPageMarker({ pageNum: paginationState.currentPage });
  689.         
  690.         // 启动滚动监听
  691.         $(window).scroll(handleScroll);
  692.         console.log('自动下一页:滚动加载监听已启动');

  693.         // 延迟检查是否需要自动加载
  694.         setTimeout(() => {
  695.             checkAndLoadNext();
  696.         }, 500);

  697.         // 绑定页码跳转事件
  698.         $(document).off('click', '.page-jump-link').on('click', '.page-jump-link', function () {
  699.             const inputPage = prompt(`请输入要跳转的页码(1-${paginationState.totalPage}):`, paginationState.currentPage);
  700.             if (!inputPage) return;

  701.             const targetPage = parseInt(inputPage.trim());
  702.             if (isNaN(targetPage) || targetPage < 1 || targetPage > paginationState.totalPage) {
  703.                 alert(`请输入1-${paginationState.totalPage}之间的有效数字!`);
  704.                 return;
  705.             }

  706.             window.location.href = buildJumpUrl(targetPage);
  707.         });
  708.     }

  709.     // 智能加载检测:如果内容不足一页,自动加载下一页
  710.     function checkAndLoadNext() {
  711.         if (paginationState.isFailed || paginationState.isLoading || paginationState.currentPage >= paginationState.totalPage) {
  712.             return;
  713.         }

  714.         const $postList = $(paginationState.listSelector).first();
  715.         if (!$postList.length) return;

  716.         // 检查是否还有未加载的标记(表示还有下一页)
  717.         const hasMorePages = paginationState.currentPage < paginationState.totalPage;
  718.         if (!hasMorePages) return;

  719.         // 检查当前显示的内容是否不足一页
  720.         const listHeight = $postList.height();
  721.         const windowHeight = $(window).height();
  722.         
  723.         // 如果列表高度小于窗口高度,说明内容不足一页,无法通过滚动触发
  724.         if (listHeight < windowHeight) {
  725.             console.log('自动下一页:内容不足一页,自动加载下一页');
  726.             loadPage(paginationState.currentPage + 1);
  727.         }
  728.     }

  729.     function addPageMarker({ pageNum = paginationState.currentPage, isLoadingState, errorObject, loadAll }) {
  730.         const $postList = $(paginationState.listSelector).first();
  731.         if (!$postList.length || (paginationState.currentPage == 1 && pageNum == 1)) return;

  732.         // 移除加载中标记
  733.         $postList.find(`li:contains("加载中...")`).remove();

  734.         const totalPageText = paginationState.totalPage != PAGINATION_ENUM.unknownPage ? `/共${paginationState.totalPage}页` : '';
  735.         let prepend = pageNum <= paginationState.currentPage;
  736.         let marker = null;

  737.         if (isLoadingState) {
  738.             marker = $(`<li style="text-align:center; padding: 0; margin:10px 0;"> <span class="page-jump-link" style="color:#507daf;">第${pageNum}页</span>
  739.             ${totalPageText} 加载中...</li>`)
  740.         } else if (errorObject) {
  741.             marker = $(`<li class="retry-marker" style="text-align:center; padding: 0; margin:10px 0; color:red;">
  742.              <span class="page-jump-link" style="color:#507daf;">第${pageNum}页</span>${totalPageText} 加载失败
  743.              <span class="retry-button" style="color:#007bff; margin-left:5px;">点击重试</span>
  744.              <p style="color:red; margin-left:5px;"></p>
  745.             </li>`)
  746.             const errorMessage = typeof (errorObject == 'string') ? errorObject : JSON.stringify(errorObject);
  747.             const truncatedErrorMessage = errorMessage.length > 200 ? `${errorMessage.substring(0, 200)}......` : errorMessage;
  748.             marker.find('p').text(truncatedErrorMessage);
  749.             $(marker).on('click', '.retry-button', function () {
  750.                 paginationState.isFailed = false;
  751.                 loadPage(pageNum);
  752.                 $(this).closest('.retry-marker').remove();
  753.             });
  754.         } else if (loadAll) {
  755.             marker = $(`<li style="text-align:center; padding: 0; margin:10px 0; color:#666;">
  756.                     已全部 <span class="page-jump-link" style="color:#507daf;">共${pageNum}页</span>
  757.                     加载 <a href="${buildJumpUrl(1)}" style="color:#507daf;">回到第1页</a>
  758.                 </li>`)
  759.             prepend = false;
  760.         } else {
  761.             marker = $(`<li style="text-align:center; padding: 0; margin:10px 0;">
  762.             <span class="page-jump-link" style="color:#507daf;">第${pageNum}页</span>${totalPageText}
  763.             ${(prepend && pageNum != 1) ? `<span class="loadPreNext" style="color:#507daf;">上一页</span>` : ''}
  764.         </li>`)
  765.             $(marker).on('click', '.loadPreNext', function () {
  766.                 if (paginationState.isFailed) {
  767.                     alert('下一页加载发生错误,重试后才允许加载上一页。');
  768.                     return;
  769.                 }
  770.                 loadPage(pageNum - 1);
  771.                 $(this).remove();
  772.             });
  773.         }

  774.         if (prepend) {
  775.             $postList.prepend(marker);
  776.         } else {
  777.             $postList.append(marker);
  778.         }
  779.     }

  780.     function buildJumpUrl(targetPage, inajax) {
  781.         const url = new URL(window.location.href);
  782.         url.searchParams.set('page', targetPage);
  783.         if (inajax) {
  784.             url.searchParams.set('inajax', '1');
  785.         } else {
  786.             url.searchParams.delete('inajax');
  787.         }
  788.         return url.toString();
  789.     }

  790.     function loadPage(page) {
  791.         if (paginationState.isLoading || (page > paginationState.totalPage || page < 1) || paginationState.isFailed) return;

  792.         addPageMarker({ pageNum: page, isLoadingState: true });
  793.         const requestUrl = buildJumpUrl(page, true);

  794.         paginationState.isLoading = true;
  795.         $.ajax({
  796.             type: 'GET',
  797.             url: requestUrl,
  798.             dataType: 'xml',
  799.             timeout: PAGINATION_ENUM.requestTimeout
  800.         }).then(function (response) {
  801.             try {
  802.                 const root = response.lastChild.firstChild.nodeValue
  803.                 if (typeof (root) == "undefined" || root == null) throw new Error('数据格式错误!');

  804.                 const htmlContent = parseResponse(root);
  805.                 const $postList = $(paginationState.listSelector).first();

  806.                 if (root.includes('本版块或指定的范围内尚无主题') || (!htmlContent && paginationState.totalPage == PAGINATION_ENUM.unknownPage)) {
  807.                     allLoaded(paginationState.currentPage)
  808.                     return;
  809.                 } else if (!htmlContent) throw new Error('数据解析失败!')

  810.                 if (page < paginationState.currentPage) {
  811.                     $postList.prepend(htmlContent);
  812.                     addPageMarker({ pageNum: page });
  813.                 } else {
  814.                     addPageMarker({ pageNum: page });
  815.                     $postList.append(htmlContent);
  816.                     paginationState.currentPage = page;
  817.                 }

  818.                 console.log(`自动下一页:加载成功第${paginationState.currentPage}/${paginationState.totalPage}页`);

  819.                 // 初始化新增元素的功能按钮
  820.                 window.comiis_recommend_addkey?.();
  821.                 window.comiis_user_gz_key?.();
  822.                 if (window.popup?.init) window.popup.init();

  823.                 // 加载完成后检查是否需要继续自动加载
  824.                 setTimeout(() => {
  825.                     checkAndLoadNext();
  826.                 }, 500);

  827.                 if (page >= paginationState.totalPage || (!root.includes('下一页') && paginationState.totalPage == PAGINATION_ENUM.unknownPage)) {
  828.                     allLoaded(page);
  829.                 }

  830.             } catch (error) {
  831.                 return $.Deferred().reject(error);
  832.             }
  833.         }).then(null, function (e) {
  834.             console.error(`自动下一页:加载失败第${page}页`, e);
  835.             addPageMarker({ pageNum: page, errorObject: e });
  836.             paginationState.isFailed = true;
  837.         }).always(function () {
  838.             paginationState.isLoading = false;
  839.         });
  840.     }

  841.     function parseResponse(root) {
  842.         const $temp = $(`<div>${root}</div>`);
  843.         return $temp.find(paginationState.listSelector).html() || '';
  844.     }

  845.     function handleScroll() {
  846.         if (paginationState.isFailed || paginationState.isLoading || paginationState.currentPage >= paginationState.totalPage) return;

  847.         const scrollTop = $(window).scrollTop();
  848.         const windowHeight = $(window).height();
  849.         const docHeight = $(document).height();

  850.         if (docHeight - (scrollTop + windowHeight) <= PAGINATION_ENUM.loadThreshold) {
  851.             loadPage(paginationState.currentPage + 1);
  852.         }
  853.     }

  854.     function allLoaded(pageNum = paginationState.totalPage) {
  855.         addPageMarker({ pageNum, loadAll: true });
  856.         $(window).off("scroll", handleScroll);
  857.         console.log("自动下一页:所有页已全部加载,解除滚动监听。");
  858.     }

  859.     // ==================== 页面类型判断和功能启动 ====================
  860.    
  861.     let pageType = '未知页面'

  862.     // 判断页面类型并启动相应功能
  863.     if (phpFile === 'home.php' && searchParams.get('mod') === 'space' && searchParams.get('do') === 'notice') {
  864.         // 消息提醒页面 - 启动消息预览和自动下一页功能
  865.         await initNoticePreview();
  866.         pageType = '【消息提醒页】'
  867.         
  868.         // 启动自动下一页功能
  869.         initAutoNextPage();
  870.     } else if (phpFile === 'home.php' && searchParams.get('type') === 'reply') {
  871.         // 帖子回复页面 - 启动帖子回复查看和自动下一页功能
  872.         initPostReply();
  873.         pageType = '【帖子回复页】'
  874.         
  875.         // 启动自动下一页功能
  876.         initAutoNextPage();
  877.     }

  878.     console.log('当前页面类型:', pageType);

  879. })();
复制代码

本帖子中包含更多资源

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

x
回复

使用道具 举报

0

主题

216

回帖

524

积分

初中生

Rank: 3Rank: 3

金币
294
好评
0
信誉
100
发表于 1 小时前 来自手机  | 显示全部楼层  来自 江西
能否加热帖排行

本帖子中包含更多资源

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

x
回复

使用道具 举报

0

主题

93

回帖

247

积分

小学生

Rank: 2

金币
110
好评
0
信誉
100
发表于 1 小时前 | 显示全部楼层  来自 河南
看看隐藏
回复

使用道具 举报

13

主题

2533

回帖

8826

积分

硕士生

Rank: 6Rank: 6

金币
1142
好评
2
信誉
100
发表于 1 小时前 来自手机  | 显示全部楼层  来自 广东
谢谢分享
回复

使用道具 举报

0

主题

416

回帖

955

积分

高中生

Rank: 4

金币
187
好评
0
信誉
100
发表于 1 小时前 来自手机  | 显示全部楼层  来自 广东
看看隐藏
回复

使用道具 举报

9

主题

1089

回帖

3803

积分

大学生

Rank: 5Rank: 5

金币
2740
好评
1
信誉
100

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

发表于 1 小时前 来自手机  | 显示全部楼层  来自 安徽
看看隐藏,感谢分享
回复

使用道具 举报

1

主题

912

回帖

2475

积分

大学生

Rank: 5Rank: 5

金币
353
好评
0
信誉
100
发表于 半小时前 来自手机  | 显示全部楼层  来自 陕西
感谢分享
回复

使用道具 举报

74

主题

1993

回帖

6987

积分

硕士生

Rank: 6Rank: 6

金币
3882
好评
6
信誉
90
发表于 半小时前 来自手机  | 显示全部楼层  来自 重庆
感谢分享
回复

使用道具 举报

6

主题

478

回帖

2306

积分

大学生

Rank: 5Rank: 5

金币
644
好评
0
信誉
98
发表于 23 分钟前 来自手机  | 显示全部楼层  来自 广西
感谢分享
回复

使用道具 举报

91

主题

1715

回帖

5691

积分

硕士生

Rank: 6Rank: 6

金币
2996
好评
1
信誉
101
发表于 22 分钟前 来自手机  | 显示全部楼层  来自 河南
看看
回复

使用道具 举报

19

主题

6203

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
2119
好评
0
信誉
100
发表于 16 分钟前 来自手机  | 显示全部楼层  来自 安徽
看看隐藏
回复

使用道具 举报

19

主题

6203

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
2119
好评
0
信誉
100
发表于 15 分钟前 来自手机  | 显示全部楼层  来自 安徽
下载试用下
回复

使用道具 举报

发表回复

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

本版积分规则

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