// ==UserScript== // @name 贴吧一键签到R+ // @namespace https://blog.qitongtingyu.online/ // @version 1.0 // @description 一键签到所有关注的贴吧,按钮实时显示已签到/未签到数量,支持悬浮菜单(设置/贴吧/版本);列表三源并行抓取+详情缓存加速,显示等级与经验,吧名可点击在新标签页打开 // @author Zcy1340,原作者:栖桐听雨 // @match *://tieba.baidu.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @connect tieba.baidu.com // @connect c.tieba.baidu.com // @require https://unpkg.com/crypto-js@4.1.1/core.js // @require https://unpkg.com/crypto-js@4.1.1/md5.js // @license MIT // @run-at document-end // ==/UserScript== (function () { 'use strict'; const SCRIPT_VERSION = '1.0'; const colors = { primary: '#a18276', primaryDark: '#8a6f64', primaryLight: '#b89a8f', accent: '#f4b886', bgPrimary: '#fefdfb', bgHighlight: 'rgba(244, 184, 134, 0.3)', textPrimary: '#5c4a42', textWhite: '#ffffff', border: 'rgba(92, 74, 66, 0.12)', success: '#7a9e7e', error: '#c97b7b', info: '#a18276', shadowPrimary: 'rgba(161, 130, 118, 0.35)', shadowPrimaryHover: 'rgba(161, 130, 118, 0.45)', shadowPrimaryActive: 'rgba(161, 130, 118, 0.3)' }; /* ===== HTML 转义,防止吧名含特殊字符时破坏弹窗 DOM ===== */ function escapeHTML(str) { return String(str).replace(/[&<>"']/g, s => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[s])); } const styles = ` body > #tiebaAutoSignBtn { all: initial; } body > #tiebaAutoSignBtn {position: fixed; bottom: 5px; right: 5px; z-index: 99999; padding: 1px 1px; background: linear-gradient(135deg, ${colors.primary} 0%, ${colors.primaryDark} 100%); color: ${colors.textWhite}; border: none; border-radius: 5px; cursor: pointer; font-size: 15px; font-weight: 500; letter-spacing: 0.5px; box-shadow: 0 6px 2px ${colors.shadowPrimary}; transition: transform 0.15s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.15s cubic-bezier(0.2, 0.8, 0.2, 1), background 0.2s ease; -webkit-tap-highlight-color: transparent; touch-action: manipulation; outline: none; display: block; width: 120px; text-align: center; line-height: 1.5; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; } body > #tiebaAutoSignBtn:hover { background: linear-gradient(135deg, ${colors.primaryLight} 0%, ${colors.primary} 100%); box-shadow: 0 8px 28px ${colors.shadowPrimaryHover}; transform: translateY(-2px); } body > #tiebaAutoSignBtn:active { transform: translateY(0) scale(0.98); box-shadow: 0 4px 12px ${colors.shadowPrimaryActive}; } body > #tiebaAutoSignBtn:disabled { background: linear-gradient(135deg, #a89a90 0%, #8a7268 100%); cursor: not-allowed; transform: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } body > #tiebaAutoSignBtn .sign-count { font-size: 13px; opacity: 0.9; margin-top: 2px; display: block; } @media (max-width: 480px) { body > #tiebaAutoSignBtn { padding: 12px 20px; font-size: 14px; bottom: 16px; right: 16px; border-radius: 24px; min-width: 140px; } body > #tiebaAutoSignBtn .sign-count { font-size: 12px; } } @media (orientation: landscape) and (max-height: 500px) { body > #tiebaAutoSignBtn { bottom: 12px; padding: 10px 18px; font-size: 13px; min-width: 140px; } } /* ===== 悬浮按钮面板 ===== */ body > #tiebaAutoSignPopover { all: initial; position: fixed; z-index: 99998; display: flex; flex-direction: column; gap: 6px; opacity: 0; visibility: hidden; pointer-events: none; /* 隐藏时禁止点击 */ transform: translateY(8px); transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; } body > #tiebaAutoSignPopover.visible { opacity: 1; visibility: visible; pointer-events: auto; /* 显示时允许点击 */ transform: translateY(0); } #tiebaAutoSignPopover .pop-btn { all: initial; display: block; width: 120px; box-sizing: border-box; text-align: center; padding: 8px 0; font-size: 14px; letter-spacing: 4px; text-indent: 4px; color: ${colors.textWhite}; background: linear-gradient(135deg, ${colors.primaryLight} 0%, ${colors.primary} 100%); border: none; border-radius: 8px; cursor: pointer; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; box-shadow: 0 4px 12px ${colors.shadowPrimary}; transition: background 0.15s ease, transform 0.1s ease; -webkit-tap-highlight-color: transparent; touch-action: manipulation; } #tiebaAutoSignPopover .pop-btn:hover { background: linear-gradient(135deg, ${colors.primary} 0%, ${colors.primaryDark} 100%); transform: translateY(-1px); } #tiebaAutoSignPopover .pop-btn:active { transform: scale(0.97); } /* ===== 弹窗 ===== */ #tiebaSignModalOverlay { position: fixed; inset: 0; background: rgba(92, 74, 66, 0.35); z-index: 100000; display: flex; align-items: center; justify-content: center; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; } #tiebaSignModal { background: ${colors.bgPrimary}; border-radius: 12px; width: 340px; max-width: calc(100vw - 40px); max-height: 70vh; overflow: auto; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25); padding: 16px 18px; color: ${colors.textPrimary}; box-sizing: border-box; } /* ===== v2.3:宽度自适应内容(贴吧列表用),随最长一行自动加宽 ===== */ #tiebaSignModal.auto-width { width: max-content; min-width: 320px; } #tiebaSignModal .modal-header { display: flex; justify-content: space-between; align-items: center; font-size: 16px; font-weight: 600; margin-bottom: 12px; } #tiebaSignModal .modal-close { border: none; background: transparent; font-size: 16px; cursor: pointer; color: ${colors.textPrimary}; padding: 2px 6px; line-height: 1; } #tiebaSignModal .modal-body { font-size: 14px; line-height: 1.6; } #tiebaSignModal .form-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 10px; font-size: 14px; } #tiebaSignModal input[type=number] { width: 100px; padding: 5px 8px; border: 1px solid ${colors.border}; border-radius: 6px; font-size: 13px; color: ${colors.textPrimary}; box-sizing: border-box; outline: none; } #tiebaSignModal input[type=number]:focus { border-color: ${colors.primary}; } #tiebaSignModal .modal-btn { width: 100%; padding: 9px 0; border: none; border-radius: 8px; background: linear-gradient(135deg, ${colors.primary} 0%, ${colors.primaryDark} 100%); color: #fff; cursor: pointer; font-size: 14px; letter-spacing: 2px; } #tiebaSignModal .modal-btn:active { opacity: 0.85; } /* ===== v2.3:贴吧列表项——单行布局(吧名 + 等级/经验 + 签到状态),压缩总高度 ===== */ #tiebaSignModal .tieba-item { display: flex; align-items: center; gap: 10px; padding: 4px 8px; border: 1px solid ${colors.border}; border-radius: 4px; margin-bottom: 4px; font-size: 13px; white-space: nowrap; } #tiebaSignModal .tieba-item:last-child { margin-bottom: 0; } /* 吧名超链接:点击在新标签页打开对应贴吧;超宽时省略号截断 */ #tiebaSignModal a.tieba-name-link { font-weight: 500; color: ${colors.primary}; text-decoration: none; cursor: pointer; overflow: hidden; text-overflow: ellipsis; min-width: 3em; } #tiebaSignModal a.tieba-name-link:hover { color: ${colors.primaryDark}; text-decoration: underline; } #tiebaSignModal a.tieba-name-link:active { opacity: 0.75; } #tiebaSignModal .tieba-details { font-size: 12px; color: ${colors.info}; } #tiebaSignModal .tieba-sign { margin-left: auto; } `; const CACHE_KEY = 'tieba_sign_cache_v2'; const SETTINGS_KEY = 'tieba_sign_settings_v1'; /* ===== v2.3 新增:贴吧详情缓存(等级/经验变化缓慢,用于加速列表打开) ===== */ const DETAIL_CACHE_KEY = 'tieba_detail_cache_v1'; const DETAIL_CACHE_TTL = 10 * 60 * 1000; /* 缓存有效期:10 分钟 */ const FAKE_VERSION = '9.7.8.0'; function getSettings() { return Object.assign( { batchSize: 5, baseDelay: 300, restInterval: 100 }, JSON.parse(GM_getValue(SETTINGS_KEY, '{}')) ); } function makeFakeParams(obj) { return Object.assign({ _client_type: 4, _client_version: FAKE_VERSION, _phone_imei: '0'.repeat(15), model: 'HUAWEI P40', net_type: 1, stErrorNums: 1, stMethod: 1, stMode: 1, stSize: 320, stTime: 117, stTimesNum: 1, timestamp: Date.now() }, obj); } function sign(payload) { const sortKeys = Object.keys(payload).sort(); let str = sortKeys.reduce((acc, key) => acc += `${key}=${payload[key]}`, ''); str += 'tiebaclient!!!'; return CryptoJS.MD5(str).toString(); } function getTodayCache() { const today = new Date(); const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; const cache = JSON.parse(GM_getValue(CACHE_KEY, '{}')); if (cache.date !== dateStr) { GM_setValue(CACHE_KEY, JSON.stringify({ date: dateStr, signed: {} })); return new Map(); } return new Map(Object.entries(cache.signed || {})); } function saveToCache(forumName) { const today = new Date(); const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; const cache = JSON.parse(GM_getValue(CACHE_KEY, '{}')); if (cache.date !== dateStr) { cache.date = dateStr; cache.signed = {}; } cache.signed[forumName] = true; GM_setValue(CACHE_KEY, JSON.stringify(cache)); } /* ===== v2.3 新增:详情缓存读写/失效 ===== */ function getDetailCache() { try { const cache = JSON.parse(GM_getValue(DETAIL_CACHE_KEY, '{}')); if (cache && cache.time && (Date.now() - cache.time) < DETAIL_CACHE_TTL && Array.isArray(cache.list) && cache.list.length > 0) { return cache.list; } } catch (e) { } return null; } function saveDetailCache(list) { try { GM_setValue(DETAIL_CACHE_KEY, JSON.stringify({ time: Date.now(), list })); } catch (e) { } } function clearDetailCache() { try { if (typeof GM_deleteValue === 'function') GM_deleteValue(DETAIL_CACHE_KEY); else GM_setValue(DETAIL_CACHE_KEY, '{}'); } catch (e) { } } function createStyles() { const style = document.createElement('style'); style.textContent = styles; document.head.appendChild(style); } function isMobile() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768; } function showErrorPrompt(message) { const defaultTieba = 'redstone_machinery_communication'; const input = prompt(`${message}\n\n已为你填充默认贴吧:${defaultTieba}\n如需添加其他贴吧,请用英文逗号分隔`, defaultTieba); return input ? input.split(',').map(name => name.trim()).filter(name => name).map(name => ({ forum_name: name, is_sign: 0 })) : []; } /* ===== v2.3:列表获取——三数据源并行 + 详情缓存 + 轻量模式;已移除头衔相关内容,仅提取等级数值与经验 fetchDetail=false 轻量模式(仅取签到状态);silent=true 静默模式(失败不弹输入框,用于后台预热) ===== */ async function getFollowedTiebaWithSignStatus(fetchDetail = true, silent = false) { const forumMap = new Map(); const addForums = (forums) => { forums.forEach(f => { if (!f) return; const name = (f.forum_name ?? f.name ?? '').toString().trim(); if (!name) return; const isSign = (f.is_sign === 1 || f.is_sign === '1' || f.is_sign === true) ? 1 : 0; /* 头衔等级数值(1~18):优先取 level_id,兼容纯数字型 level 字段 */ let levelId = null; const lidRaw = (f.level_id !== undefined && f.level_id !== null && f.level_id !== '') ? f.level_id : null; if (lidRaw !== null) { const s = String(lidRaw).trim(); const n = parseInt(s, 10); if (!isNaN(n) && String(n) === s && n >= 1 && n <= 18) levelId = n; } if (levelId === null && f.level !== undefined && f.level !== null && String(f.level).trim() !== '') { const s = String(f.level).trim(); const n = parseInt(s, 10); if (!isNaN(n) && String(n) === s && n >= 1 && n <= 18) levelId = n; } /* 当前经验:兼容 cur_score / curScore / exp */ let curScore = null; const cs = f.cur_score ?? f.curScore ?? f.exp ?? null; if (cs !== null && cs !== '') { const n = Number(cs); curScore = isNaN(n) ? cs : n; } /* 升级所需经验:兼容 levelup_score / levelupScore / upgrade_score / upgradeScore */ let upgradeScore = null; const us = f.levelup_score ?? f.levelupScore ?? f.upgrade_score ?? f.upgradeScore ?? null; if (us !== null && us !== '') { const n = Number(us); upgradeScore = isNaN(n) ? us : n; } /* 统一以吧名为唯一键:多数据源正确合并,避免同一贴吧重复出现 */ const key = name; const existing = forumMap.get(key); if (existing) { if (isSign) existing.is_sign = 1; /* 命中已存在贴吧时合并补全等级/经验(而非丢弃) */ const e = existing.extra || {}; if (levelId && !e.levelId) e.levelId = levelId; if (curScore && !e.curScore) e.curScore = curScore; if (upgradeScore && !e.upgradeScore) e.upgradeScore = upgradeScore; existing.extra = e; } else { forumMap.set(key, { forum_name: name, is_sign: isSign, extra: { levelId: levelId || 0, curScore: curScore || 0, upgradeScore: upgradeScore || 0 } }); } }); }; /* 数据源1:移动端首页接口(关注列表 + 签到状态,单请求,最快) */ const fromNewmoindex = async () => { return new Promise(resolve => { GM_xmlhttpRequest({ method: 'POST', url: 'https://tieba.baidu.com/mo/q/newmoindex', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': document.cookie, 'User-Agent': navigator.userAgent }, timeout: 10000, onload: r => { try { const data = JSON.parse(r.responseText || '{}'); if (data.data?.like_forum && Array.isArray(data.data.like_forum)) { addForums(data.data.like_forum); } } catch { } resolve(); }, onerror: () => resolve(), ontimeout: () => resolve() }); }); }; /* 数据源2:贴吧客户端接口(等级/经验等详细数据) */ const fromAppApi = async () => { const appCommonHeader = { 'User-agent': `bdtb for Android ${FAKE_VERSION}`, Accept: '', 'Content-Type': 'application/x-www-form-urlencoded' }; let page = 1; let hasMore = true; while (hasMore && page <= 50) { const baseParams = makeFakeParams({BDUSS: document.cookie.match(/BDUSS=([^;]+)/)?.[1] || '', page_no: page, page_size: 200 }); const params = { ...baseParams, sign: sign(baseParams) }; const dataStr = Object.keys(params).map(k => `${k}=${encodeURIComponent(params[k])}`).join('&'); const response = await new Promise(resolve => { GM_xmlhttpRequest({ method: 'POST', url: 'https://c.tieba.baidu.com/c/f/forum/like', headers: { ...appCommonHeader, 'Cookie': document.cookie }, data: dataStr, timeout: 20000, onload: r => { try { resolve(JSON.parse(r.responseText || '{}')); } catch { resolve({}); } }, onerror: () => resolve({}), ontimeout: () => resolve({}) }); }); if (response.forum_list) { const forums = []; if (response.forum_list.non_gconforum) { const list = Array.isArray(response.forum_list.non_gconforum) ? response.forum_list.non_gconforum : []; forums.push(...list); } if (response.forum_list.gconforum) { const list = Array.isArray(response.forum_list.gconforum) ? response.forum_list.gconforum : []; forums.push(...list); } if (Array.isArray(response.forum_list)) { forums.push(...response.forum_list); } addForums(forums); } hasMore = response.has_more === '1' || response.has_more === 1; page++; /* v2.3:分页间隔 200ms -> 50ms,降低整体抓取延迟 */ await new Promise(r => setTimeout(r, 50)); } }; /* 数据源3:PC 端"我关注的吧"页面,兜底补全等级数值与经验 */ const fromMylike = async () => { const parsePage = (html) => { const records = []; try { const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('table tr').forEach(tr => { const cells = tr.querySelectorAll('td'); if (cells.length < 3) return; const link = cells[0].querySelector('a[href]'); if (!link) return; let tName = (link.textContent || '').trim(); if (!tName) { const m = (link.getAttribute('href') || '').match(/[?&]kw=([^&]+)/); if (m) { try { tName = decodeURIComponent(m[1]); } catch (e) { tName = m[1]; } } } if (!tName) return; const levelNum = parseInt((cells[1].textContent || '').replace(/[^\d]/g, ''), 10); const scoreNum = parseInt((cells[2].textContent || '').replace(/[^\d]/g, ''), 10); const rec = { forum_name: tName }; if (!isNaN(levelNum) && levelNum >= 1 && levelNum <= 18) rec.level_id = levelNum; if (!isNaN(scoreNum) && scoreNum > 0) rec.cur_score = scoreNum; if (rec.level_id !== undefined || rec.cur_score !== undefined) records.push(rec); }); } catch (e) { } return records; }; let pn = 1; while (pn <= 100) { const html = await new Promise(resolve => { GM_xmlhttpRequest({ method: 'GET', url: `https://tieba.baidu.com/f/like/mylike?pn=${pn}`, headers: { 'Cookie': document.cookie }, timeout: 15000, onload: r => resolve(r.responseText || ''), onerror: () => resolve(''), ontimeout: () => resolve('') }); }); if (!html) break; const records = parsePage(html); if (!records.length) break; addForums(records); pn++; /* v2.3:分页间隔 300ms -> 100ms */ await new Promise(r => setTimeout(r, 100)); } }; /* ===== 轻量模式:仅取签到状态(悬浮按钮定时刷新用) ===== */ if (!fetchDetail) { await fromNewmoindex(); if (forumMap.size > 0) return [...forumMap.values()]; await fromAppApi(); return [...forumMap.values()]; } /* ===== 完整模式 ===== */ /* 快速路径:详情缓存有效时,仅拉取一次签到状态并与缓存合并,实现秒开 */ const cachedDetails = getDetailCache(); if (cachedDetails && cachedDetails.length > 0) { await fromNewmoindex(); if (forumMap.size === 0) await fromAppApi(); if (forumMap.size > 0) { const detailMap = new Map(cachedDetails.filter(d => d && d.forum_name).map(d => [d.forum_name, d])); return [...forumMap.values()].map(t => { const d = detailMap.get(t.forum_name); const e = t.extra || {}; if (d) { return { forum_name: t.forum_name, is_sign: t.is_sign, extra: { levelId: e.levelId || d.levelId || 0, curScore: e.curScore || d.curScore || 0, upgradeScore: e.upgradeScore || d.upgradeScore || 0 } }; } return t; /* 新关注的贴吧(缓存中无):显示快速源已有数据 */ }); } /* 快速数据源均失败:回退完整抓取 */ } /* 慢速路径:三数据源并行抓取(原先串行延迟=三者之和,现在≈最慢一个) */ await Promise.all([fromNewmoindex(), fromAppApi(), fromMylike()]); const tiebaList = [...forumMap.values()]; if (tiebaList.length > 0) { /* 抓取成功后写入详情缓存,供下次秒开 */ saveDetailCache(tiebaList.map(t => ({ forum_name: t.forum_name, levelId: t.extra?.levelId || 0, curScore: t.extra?.curScore || 0, upgradeScore: t.extra?.upgradeScore || 0 }))); return tiebaList; } return silent ? [] : showErrorPrompt('自动提取贴吧列表失败'); } async function getTBS() { return new Promise(resolve => { GM_xmlhttpRequest({ method: 'GET', url: 'https://tieba.baidu.com/dc/common/tbs', headers: { 'Cookie': document.cookie }, timeout: 10000, onload: r => { try { resolve(JSON.parse(r.responseText).tbs || ''); } catch { resolve(''); } }, onerror: () => resolve('') }); }); } async function signSingleTieba(name) { return new Promise(async resolve => { const tbs = await getTBS(); const data = `ie=utf-8&kw=${encodeURIComponent(name)}${tbs ? `&tbs=${tbs}` : ''}`; GM_xmlhttpRequest({ method: 'POST', url: 'https://tieba.baidu.com/sign/add', data: data, headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Referer': `https://tieba.baidu.com/f?kw=${encodeURIComponent(name)}`, 'Cookie': document.cookie }, timeout: 15000, onload: r => { try { const res = JSON.parse(r.responseText); switch (res.no) { case 0: resolve({ status: 'success', msg: `${name}:签到成功\n连续签到:${res.data?.uinfo?.cont_sign_num || 0}天 | 总签到:${res.data?.uinfo?.total_sign_num || 0}次` }); break; case 1101: resolve({ status: 'existed', msg: `${name}:今日已签到` }); break; case 100002: resolve({ status: 'fail', msg: `${name}:未登录/登录过期` }); break; case 34004: resolve({ status: 'vcode', msg: `${name}:失败(需要验证码)` }); break; default: { const errorMsg = res.error || '未知错误'; if (errorMsg.includes('vcode')) { resolve({ status: 'vcode', msg: `${name}:失败` }); } else { resolve({ status: 'fail', msg: `${name}:失败(${errorMsg})` }); } } break; } } catch { resolve({ status: 'fail', msg: `${name}:解析失败` }); } }, onerror: () => resolve({ status: 'fail', msg: `${name}:网络错误` }), ontimeout: () => resolve({ status: 'fail', msg: `${name}:请求超时` }) }); }); } /* ==================== 悬浮按钮 & 弹窗 ==================== */ let popHoverTimer = null; let popHideTimer = null; let popLeaveTimer = null; function scheduleHidePopover() { clearTimeout(popLeaveTimer); popLeaveTimer = setTimeout(() => { const btn = document.getElementById('tiebaAutoSignBtn'); const pop = document.getElementById('tiebaAutoSignPopover'); const onBtn = btn && (btn.matches(':hover') || document.activeElement === btn); const onPop = pop && (pop.matches(':hover') || document.activeElement === pop); if (!onBtn && !onPop) hidePopover(); }, 300); } function positionPopover() { const btn = document.getElementById('tiebaAutoSignBtn'); const pop = document.getElementById('tiebaAutoSignPopover'); if (!btn || !pop) return; const rect = btn.getBoundingClientRect(); pop.style.right = (window.innerWidth - rect.right) + 'px'; pop.style.bottom = (window.innerHeight - rect.top + 8) + 'px'; } function showPopover() { const pop = document.getElementById('tiebaAutoSignPopover'); if (!pop) return; positionPopover(); pop.classList.add('visible'); clearTimeout(popHideTimer); popHideTimer = setTimeout(hidePopover, 5000); } function hidePopover() { const pop = document.getElementById('tiebaAutoSignPopover'); if (!pop) return; pop.classList.remove('visible'); clearTimeout(popHideTimer); clearTimeout(popLeaveTimer); } function createPopover() { if (document.getElementById('tiebaAutoSignPopover')) return; const pop = document.createElement('div'); pop.id = 'tiebaAutoSignPopover'; pop.innerHTML = ` `; pop.querySelector('[data-action="settings"]').addEventListener('click', () => { hidePopover(); openSettingsModal(); }); pop.querySelector('[data-action="tieba"]').addEventListener('click', () => { hidePopover(); openTiebaModal(); }); pop.querySelector('[data-action="version"]').addEventListener('click', () => { hidePopover(); openVersionModal(); }); pop.addEventListener('mouseenter', () => { clearTimeout(popHideTimer); clearTimeout(popLeaveTimer); }); pop.addEventListener('mouseleave', scheduleHidePopover); document.body.appendChild(pop); } function bindHoverEvents(btn) { btn.addEventListener('mouseenter', () => { clearTimeout(popHoverTimer); clearTimeout(popLeaveTimer); popHoverTimer = setTimeout(showPopover, 150); }); btn.addEventListener('mouseleave', () => { clearTimeout(popHoverTimer); scheduleHidePopover(); }); btn.addEventListener('click', () => { clearTimeout(popHoverTimer); hidePopover(); }); window.addEventListener('resize', () => { const pop = document.getElementById('tiebaAutoSignPopover'); if (pop && pop.classList.contains('visible')) positionPopover(); }); } /* ===== v2.3:showModal 新增 opts.autoWidth 选项(贴吧列表宽度自适应内容) ===== */ function showModal(title, bodyHTML, onReady, opts = {}) { closeModal(); const overlay = document.createElement('div'); overlay.id = 'tiebaSignModalOverlay'; overlay.innerHTML = `