// ==UserScript== // @name COLG 阅帖助手 // @namespace colg-assistant // @version 0.3.2 // @description 在 colg(bbs.colg.cn)页面右侧叠加看板:分流高价值(收藏≥5)与日常帖,作者黑名单标红/好友标黄。默认收起不挡页面、可全屏、排序切换。⚠ 仅供个人学习交流,请低频使用(建议间隔≥5分钟),风控风险自担。 // @match https://bbs.colg.cn/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js // @license MIT // ==/UserScript== (function () { 'use strict'; if (window.top !== window) return; var FID = 171, LIMIT = 150, MIN_COLLECT = 5; var AUTO_REFRESH = true; // 打开/刷新页面自动抓一次 var CACHE_TTL = 5 * 60 * 1000; // 5 分钟内用缓存,不重复抓(防频繁进帖触发风控) var CACHE_KEY = 'colg-us-data', CACHE_TIME_KEY = 'colg-us-time'; var DEFAULT_COLLAPSED = true; // 默认收起 var CSS = [ '#cusPanel{position:fixed;top:0;right:0;width:380px;height:100%;background:#12141d;color:#e6e8ee;box-shadow:-8px 0 26px rgba(0,0,0,.28);z-index:2147483000;display:flex;flex-direction:column;transition:transform .25s ease;}', '#cusPanel.full{width:100%;box-shadow:none;}', '#cusPanel.collapsed{transform:translateX(100%);box-shadow:none;}', '#cusPanel .cus-head{padding:12px 16px 10px;border-bottom:1px solid #2c3140;}', '#cusPanel .cus-htop{display:flex;justify-content:space-between;align-items:center;gap:6px;}', '#cusPanel .cus-title{font-size:15px;font-weight:700;color:#e6e8ee;flex:1;}', '#cusPanel .cus-sub{font-size:11px;color:#8b91a3;margin-top:3px;}', '#cusPanel .cus-hdbtn{background:#21242f;color:#e6e8ee;border:1px solid #2c3140;border-radius:7px;width:30px;height:30px;cursor:pointer;font-size:15px;line-height:1;}', '#cusPanel .cus-body{flex:1;overflow:hidden;padding:12px;display:flex;flex-direction:column;gap:12px;}', '#cusPanel.full .cus-body{flex-direction:row;align-items:stretch;padding:22px 26px;}', '#cusPanel .cus-phone{background:#1a1d27;border:1px solid #2c3140;border-radius:12px;padding:10px;display:flex;flex-direction:column;min-height:0;}', '#cusPanel:not(.full) .cus-phone{flex:1 1 0;}', '#cusPanel.full .cus-phone{flex:1 1 0;}', '#cusPanel .cus-pt{display:flex;justify-content:space-between;font-size:13px;font-weight:700;color:#e6e8ee;flex:none;}', '#cusPanel .cus-pt .c{font-size:11px;color:#8b91a3;font-weight:400;background:#21242f;border:1px solid #2c3140;padding:1px 8px;border-radius:10px;}', '#cusPanel .cus-cards{flex:1;overflow:auto;margin-top:2px;}', '#cusPanel .cus-card{background:#21242f;border:1px solid #2c3140;border-radius:8px;padding:8px 10px;margin-top:8px;cursor:pointer;}', '#cusPanel .cus-card:hover{border-color:#3C9D40;}', '#cusPanel .cus-card .t{font-weight:600;font-size:12.5px;line-height:1.35;color:#e6e8ee;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}', '#cusPanel .cus-card .b{display:flex;justify-content:space-between;color:#8b91a3;font-size:11px;margin-top:7px;}', '#cusPanel .cus-btns{padding:9px 12px;display:flex;gap:8px;border-top:1px solid #2c3140;background:#0e1017;justify-content:flex-end;}', '#cusPanel .cus-btns button{background:#21242f;color:#e6e8ee;border:1px solid #2c3140;border-radius:7px;padding:5px 10px;font-size:12px;cursor:pointer;}', '#cusPanel .cus-btns button.active{background:#3C9D40;color:#fff;border-color:#3C9D40;}', '#cusPanel .cus-progress{height:3px;background:#21242f;overflow:hidden;display:none;}', '#cusPanel .cus-progress.on{display:block;}', '#cusPanel .cus-progress i{display:block;height:100%;width:40%;background:#3C9D40;animation:cusp 1s linear infinite;}', '@keyframes cusp{from{transform:translateX(-100%)}to{transform:translateX(350%)}}', '.cus-loading{display:flex;align-items:center;justify-content:center;gap:6px;padding:34px 0;color:#8b91a3;font-size:12px;}', '.cus-loading i{width:12px;height:12px;border:2px solid #3C9D40;border-top-color:transparent;border-radius:50%;animation:cuspin .7s linear infinite;}', '@keyframes cuspin{to{transform:rotate(360deg)}}', '#cusTab{position:fixed;right:0;top:42%;width:30px;height:60px;background:#12141d;color:#e6e8ee;border:1px solid #2c3140;border-right:none;border-radius:8px 0 0 8px;cursor:pointer;z-index:2147483001;font-size:16px;}', '.cus-empty{color:#8b91a3;text-align:center;padding:26px 8px;font-size:12px;line-height:1.6;}', '.cus-empty.err{color:#e05f5f;}', '/* 深色滚动条 */', '#cusPanel ::-webkit-scrollbar{width:8px;height:8px;}', '#cusPanel ::-webkit-scrollbar-thumb{background:#2c3140;border-radius:4px;}', '#cusPanel ::-webkit-scrollbar-thumb:hover{background:#3C9D40;}', '#cusPanel ::-webkit-scrollbar-track{background:transparent;}', '/* 板块标题图标 */', '#cusPanel .cus-pt{font-size:14px;}', '#cusPanel .cus-pt .ic{margin-right:5px;}', '/* 帮助气泡 */', '#cusPanel .cus-help{position:relative;}', '#cusPanel .cus-tip{display:none;position:absolute;top:38px;right:0;width:252px;background:#1a1d27;border:1px solid #2c3140;border-radius:10px;padding:12px;font-size:12px;color:#c8ccd8;line-height:1.7;z-index:30;box-shadow:0 8px 24px rgba(0,0,0,.4);}', '#cusPanel .cus-help:hover .cus-tip{display:block;}', '#cusPanel .cus-note{padding:7px 16px;font-size:11px;color:#e0a94a;border-top:1px solid #2c3140;background:#151723;}' ].join('\n'); GM_addStyle(CSS); function injectPanel() { var el = document.createElement('div'); el.id = 'cusPanel'; el.innerHTML = '
COLG 阅帖助手
' + '
· 《 全屏 / 》 还原:两块放大看板
· × 收起:隐藏看板(右侧 ◀ 展开)
· ↻ 刷新:重新抓取,自动分流高价值/日常
· 按发布 / 按最后回复:切换排序
· 作者红色(拉黑)= 对方拉黑了你;黄色(好友)= 好友
· 收藏≥5 归入「高价值」
⚠ 请低频使用(间隔≥5分钟),避免被风控
' + '
' + '
' + '
' + '
① 高价值 · 收藏≥5
' + '
📄② 日常帖
' + '
' + '
⚠ 仅供个人学习 · 请低频使用(间隔≥5分钟),避免被风控
' + '
'; document.body.appendChild(el); var tab = document.createElement('button'); tab.id = 'cusTab'; tab.textContent = '◀'; tab.style.display = 'block'; tab.onclick = function () { el.classList.remove('collapsed'); tab.style.display = 'none'; }; document.body.appendChild(tab); function toggleFull() { var full = el.classList.toggle('full'); document.getElementById('cusFullBtn').textContent = full ? '》' : '《'; } document.getElementById('cusFullBtn').onclick = toggleFull; document.getElementById('cusCollapseBtn').onclick = function () { el.classList.add('collapsed'); tab.style.display = 'block'; }; document.getElementById('cusRefresh').onclick = refresh; function setActive(id) { var p = document.getElementById('cusSortP'), r = document.getElementById('cusSortR'); p.classList.toggle('active', id === 'cusSortP'); r.classList.toggle('active', id === 'cusSortR'); } document.getElementById('cusSortP').onclick = function () { sort = 'published'; setActive('cusSortP'); render(); }; document.getElementById('cusSortR').onclick = function () { sort = 'lastreply'; setActive('cusSortR'); render(); }; setActive('cusSortP'); if (DEFAULT_COLLAPSED) { el.classList.add('collapsed'); tab.style.display = 'block'; } return el; } async function fetchText(url) { var r = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); return await r.text(); } function parseList(html) { var doc = new DOMParser().parseFromString(html, 'text/html'); var $$ = jQuery(doc), posts = [], seen = {}; $$.find('tbody[id^="normalthread_"]').each(function () { var t = jQuery(this), title = t.find('a.xst'); if (!title.length) return; var tid = (t.attr('id') || '').replace('normalthread_', ''); if (!tid || seen[tid]) return; seen[tid] = 1; var href = title.attr('href') || '', uid = (href.match(/uid=(\d+)/) || [])[1] || ''; posts.push({ tid: tid, title: title.text().trim(), author: t.find('td.by > cite > a').first().text().trim(), authorUid: uid, publish: parseInt(t.find('a[id$="_new_show"]').attr('dbdateline'), 10) || 0, lastReply: parseInt(t.find('a[id$="_new_show"]').attr('dblastpost'), 10) || 0, replies: parseInt(t.find('td.num a.xi2').text(), 10) || 0, url: 'https://bbs.colg.cn/thread-' + tid + '-1-1.html', collect: 0, black: null }); }); return posts; } async function fetchCollect(tid) { try { var j = await (await fetch('https://bbs.colg.cn/forum.php?mod=ajax&action=async_get_thread_info&tid=' + tid, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })).json(); return +((j.data && j.data.thread_collect_count) || 0); } catch (e) { return 0; } } async function fetchBlack(uid) { if (!uid) return 'other'; try { var r = await fetch('https://bbs.colg.cn/home.php?mod=spacecp&ac=friend&op=add&uid=' + uid, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); var doc = new DOMParser().parseFromString(await r.text(), 'text/html'); if (jQuery(doc).find('#return_').length) return 'other'; var msg = jQuery(doc).find('#messagetext p:first').text(); if (msg.indexOf('好友') > -1) return 'friend'; if (msg.indexOf('隐私设置') > -1) return 'black'; return 'other'; } catch (e) { return 'other'; } } function setProg(on) { var p = document.getElementById('cusProg'); if (p) p.classList.toggle('on', !!on); } function loading(idx) { jQuery('#cusPanel .cus-phone:eq(' + idx + ') .cus-cards').html('
加载中…
'); } async function refresh() { setProg(true); loading(0); loading(1); try { var all = {}, order = []; for (var p = 1; p <= 4; p++) { try { var html = await fetchText('https://bbs.colg.cn/forum-' + FID + '-' + p + '.html'); var arr = parseList(html); arr.forEach(function (x) { if (!all[x.tid]) { all[x.tid] = x; order.push(x); } }); } catch (e) { console.error('列表页 ' + p + ' 抓取失败:', e); } if (order.length >= LIMIT) break; } if (!order.length) throw new Error('列表页解析到 0 条(可能被风控或需登录)'); var posts = order.slice(0, LIMIT); // 并行补收藏 var i = 0; async function wC() { while (i < posts.length) { var k = i++; posts[k].collect = await fetchCollect(posts[k].tid); } } await Promise.all([wC(), wC(), wC(), wC(), wC(), wC(), wC(), wC()]); // 并行黑名单(作者去重) var uids = {}; posts.forEach(function (x) { if (x.authorUid) uids[x.authorUid] = 1; }); var uidArr = Object.keys(uids), j = 0; async function wB() { while (j < uidArr.length) { var u = uidArr[j++], b = await fetchBlack(u); if (b !== 'other') console.log('黑名单探测 uid=' + u + ' -> ' + b); posts.forEach(function (x) { if (x.authorUid === u) x.black = b; }); } } await Promise.all([wB(), wB(), wB(), wB()]); state.posts = posts; // 全部抓完+分流数据就绪,才一次性显示 saveCache(); // 存缓存,5 分钟内刷新页直接用,不再发请求 console.log('分流完成'); render(); } catch (e) { console.error('刷新失败:', e); jQuery('#cusPanel .cus-phone:eq(0) .cus-cards').html('
刷新失败:' + e.message + '
'); } finally { setProg(false); } } function fmtTime(ts) { if (!ts) return '—'; var d = new Date(+ts * 1000), now = Math.floor(Date.now() / 1000), diff = now - +ts; if (diff < 60) return '刚刚'; if (diff < 3600) return Math.floor(diff / 60) + ' 分钟前'; if (diff < 86400) return Math.floor(diff / 3600) + ' 小时前'; if (diff < 86400 * 7) return Math.floor(diff / 86400) + ' 天前'; return (d.getMonth() + 1) + '/' + d.getDate(); } function esc(s) { return String(s || '').replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function card(p) { var flag = p.black === 'black' ? '(拉黑) ' : (p.black === 'friend' ? '(好友) ' : ''); return '
' + flag + esc(p.title) + '
' + esc(p.author) + ' · ' + fmtTime(p.publish) + '♥' + p.collect + ' · 💬' + p.replies + '
'; } function xtime(p) { return sort === 'lastreply' ? (+p.lastReply || 0) : (+p.publish || 0); } var state = { posts: [] }, sort = 'published'; function render() { var list = state.posts.slice().sort(function (a, b) { return xtime(b) - xtime(a); }); var s1 = list.filter(function (p) { return p.collect >= MIN_COLLECT; }); var s2 = list.filter(function (p) { return p.collect < MIN_COLLECT; }); jQuery('#cusPanel .cus-phone:eq(0) .cus-cards').html(s1.length ? s1.map(card).join('') : '
暂无
'); jQuery('#cusPanel .cus-phone:eq(0) .cus-pt .c').text(s1.length + ' 条'); jQuery('#cusPanel .cus-phone:eq(1) .cus-cards').html(s2.length ? s2.map(card).join('') : '
暂无
'); jQuery('#cusPanel .cus-phone:eq(1) .cus-pt .c').text(s2.length + ' 条'); } function saveCache() { try { GM_setValue(CACHE_KEY, state.posts); GM_setValue(CACHE_TIME_KEY, Date.now()); } catch (e) {} } function loadCache() { try { var t = GM_getValue(CACHE_TIME_KEY, 0); if (Date.now() - t < CACHE_TTL) { var p = GM_getValue(CACHE_KEY, null); if (p && p.length) { state.posts = p; return true; } } return false; } catch (e) { return false; } } if (!document.getElementById('cusPanel')) injectPanel(); if (loadCache()) { render(); } // 5 分钟内有缓存 -> 直接用,不重新抓(防频繁进帖触发风控) // 首次/缓存过期 -> 延迟抓一次;手动点「刷新」则强制抓 refresh = refresh.bind ? refresh : refresh; setTimeout(function () { if (AUTO_REFRESH && !state.posts.length) refresh(); }, 1500); document.getElementById('cusRefresh').onclick = function () { loadCache(); refresh(); }; })();