您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
轻量版:专注于黑名单功能的微博增强工具。全接口劫持并隐藏黑名单用户所有言论与转发;自动同步黑名单;支持增量更新。
// ==UserScript== // @name Weibo Blacklist Enhanced Lite | 微博黑名单增强工具 // @version 1.0.0-lite // @description 轻量版:专注于黑名单功能的微博增强工具。全接口劫持并隐藏黑名单用户所有言论与转发;自动同步黑名单;支持增量更新。 // @author DanielZenFlow // @license MIT // @namespace https://github.com/DanielZenFlow // @homepage https://github.com/DanielZenFlow/Weibo-Blacklist-Enhanced-Lite // @supportURL https://github.com/DanielZenFlow/Weibo-Blacklist-Enhanced-Lite/issues // @match https://weibo.com/* // @match https://www.weibo.com/* // @match https://weibo.com/set/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_openInTab // @run-at document-start // ==/UserScript== /* * Weibo Blacklist Enhanced Lite * Copyright (c) 2025 DanielZenFlow * Licensed under MIT License * GitHub: https://github.com/DanielZenFlow */ (function() { 'use strict'; // === GM_* 接口封装 === const _GM_getValue = typeof GM_getValue !== 'undefined' ? GM_getValue : () => {}; const _GM_setValue = typeof GM_setValue !== 'undefined' ? GM_setValue : () => {}; const _GM_registerMenuCommand = typeof GM_registerMenuCommand !== 'undefined' ? GM_registerMenuCommand : () => {}; const _GM_openInTab = typeof GM_openInTab !== 'undefined' ? GM_openInTab : null; // === 智能打开链接函数 === function openGitHub() { const url = 'https://github.com/DanielZenFlow/Weibo-Blacklist-Enhanced-Lite'; // 优先使用油猴的专用API(不会被拦截) if (_GM_openInTab) { _GM_openInTab(url, { active: true }); return; } // 降级到普通弹窗 const newWindow = window.open(url, '_blank'); // 检测是否被拦截 if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') { // 延迟检测,有些浏览器需要时间 setTimeout(() => { if (!newWindow || newWindow.closed) { alert( '🚫 弹窗被浏览器拦截了!\n\n' + '📋 GitHub链接:' + url + '\n\n' + '💡 解决方法:\n' + '1. 复制上面的链接到新标签页\n' + '2. 或者允许此网站的弹窗权限' ); } }, 100); } } // === 智能打开链接函数 === function openGitHub() { const url = 'https://github.com/DanielZenFlow/Weibo-Blacklist-Enhanced-Lite'; // 优先使用油猴的专用API(不会被拦截) if (_GM_openInTab) { _GM_openInTab(url, { active: true }); return; } // 降级到普通弹窗 const newWindow = window.open(url, '_blank'); // 检测是否被拦截 if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') { // 延迟检测,有些浏览器需要时间 setTimeout(() => { if (!newWindow || newWindow.closed) { alert( '🚫 弹窗被浏览器拦截了!\n\n' + '📋 GitHub链接:' + url + '\n\n' + '💡 解决方法:\n' + '1. 复制上面的链接到新标签页\n' + '2. 或者允许此网站的弹窗权限' ); } }, 100); } } // === Star提醒检查(时间间隔策略) === function checkStarReminder() { const isDisabled = _GM_getValue(CONFIG.STAR_REMINDER_DISABLED_KEY, false); if (isDisabled) return; const now = Date.now(); const lastReminderTime = _GM_getValue(CONFIG.LAST_STAR_REMINDER_TIME_KEY, 0); const daysSinceLastReminder = (now - lastReminderTime) / (1000 * 60 * 60 * 24); // 检查是否需要提醒 let shouldRemind = false; let currentInterval = 0; if (lastReminderTime === 0) { // 首次运行 shouldRemind = true; } else { // 找到当前应该的间隔 for (let i = 1; i < CONFIG.STAR_REMINDER_INTERVALS.length; i++) { if (daysSinceLastReminder >= CONFIG.STAR_REMINDER_INTERVALS[i]) { currentInterval = i; shouldRemind = true; } } } if (shouldRemind) { setTimeout(() => { showStarReminder(currentInterval); _GM_setValue(CONFIG.LAST_STAR_REMINDER_TIME_KEY, now); }, 3000); // 3秒后弹出 } } // === 显示Star提醒 === function showStarReminder(intervalIndex) { const isFirstTime = intervalIndex === 0; const message = isFirstTime ? '🎉 感谢使用 Weibo Blacklist Enhanced Lite!\n\n如果这个工具对您有帮助,请考虑给我们点个 ⭐ Star!' : '⭐ 再次感谢使用我们的工具!\n\n如果觉得有用,请考虑给项目点个 Star 支持一下!'; const result = confirm( `${message}\n\n` + `点击"确定"打开 GitHub 页面\n` + `点击"取消"${isFirstTime ? '稍后提醒' : '不再提醒'}` ); if (result) { openGitHub(); // 30秒后询问是否已给star setTimeout(() => { const hasStarred = confirm( '感谢访问我们的 GitHub 页面!\n\n' + '如果您已经给了 ⭐ Star,点击"确定"我们将不再提醒\n' + '点击"取消"我们稍后再提醒' ); if (hasStarred) { _GM_setValue(CONFIG.STAR_REMINDER_DISABLED_KEY, true); alert('🎉 感谢您的 Star!我们将不再显示提醒。'); } }, 30000); } else if (!isFirstTime) { // 非首次提醒,用户选择取消就不再提醒 _GM_setValue(CONFIG.STAR_REMINDER_DISABLED_KEY, true); } } function openGitHub() { const url = 'https://github.com/DanielZenFlow/Weibo-Blacklist-Enhanced-Lite'; // 优先使用油猴的专用API(不会被拦截) if (_GM_openInTab) { _GM_openInTab(url, { active: true }); return; } // 降级到普通弹窗 const newWindow = window.open(url, '_blank'); // 检测是否被拦截 if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') { // 延迟检测,有些浏览器需要时间 setTimeout(() => { if (!newWindow || newWindow.closed) { alert( '🚫 弹窗被浏览器拦截了!\n\n' + '📋 GitHub链接:' + url + '\n\n' + '💡 解决方法:\n' + '1. 复制上面的链接到新标签页\n' + '2. 或者允许此网站的弹窗权限' ); } }, 100); } } // === 配置常量 === const CONFIG = { STORAGE_KEY: 'WB_BL_LITE_UIDS', FIRST_RUN_KEY: 'WB_BL_LITE_FIRST_RUN', STAR_REMINDER_DISABLED_KEY: 'WB_BL_LITE_STAR_REMINDER_DISABLED', LAST_STAR_REMINDER_TIME_KEY: 'WB_BL_LITE_LAST_STAR_REMINDER_TIME', THROTTLE_MS: 300, MAX_RETRIES: 3, VERSION: '1.0.0-lite', // Star提醒间隔:首次安装 → 7天后 → 30天后 → 90天后 → 不再提醒 STAR_REMINDER_INTERVALS: [0, 7, 30, 90] // 天数 }; const sleep = ms => new Promise(r => setTimeout(r, ms)); // 保存原生接口 if (!window.WB_BL_NATIVE_LITE) { window.WB_BL_NATIVE_LITE = { fetch: window.fetch, XHROpen: XMLHttpRequest.prototype.open, XHRSend: XMLHttpRequest.prototype.send, WebSocket: window.WebSocket }; } /** * 全量黑名单同步 */ async function fullSyncBlacklist() { console.log('[WB-BL-Lite] Starting full sync...'); const uidList = []; let page = 1, cursor = 0, retries = 0; while (true) { let url = `/ajax/setting/getFilteredUsers?page=${page}`; if (cursor) url += `&cursor=${cursor}`; try { const res = await window.WB_BL_NATIVE_LITE.fetch(url, { credentials: 'include' }); if (res.status === 418) { console.warn(`[WB-BL-Lite] Got 418 on page ${page}, retry ${retries + 1}/${CONFIG.MAX_RETRIES}`); if (++retries > CONFIG.MAX_RETRIES) { console.error('[WB-BL-Lite] Too many 418 errors, stopping sync'); break; } await sleep(5000); // 延长等待时间 continue; } if (!res.ok) { console.error(`[WB-BL-Lite] HTTP ${res.status} on page ${page}`); break; } const data = await res.json(); if (!data.card_group) { console.log(`[WB-BL-Lite] No more data on page ${page}`); break; } let pageCount = 0; data.card_group.forEach(item => { const match = item.scheme?.match(/uid=(\d{5,})/); if (match) { uidList.push(match[1]); pageCount++; } }); console.log(`[WB-BL-Lite] Page ${page}: ${pageCount} UIDs`); if (!data.next_cursor) break; cursor = data.next_cursor; page++; retries = 0; // 重置重试计数 await sleep(CONFIG.THROTTLE_MS); } catch (error) { console.error(`[WB-BL-Lite] Error on page ${page}:`, error); break; } } console.log(`[WB-BL-Lite] Full sync completed: ${uidList.length} UIDs`); _GM_setValue(CONFIG.STORAGE_KEY, uidList.join(',')); return new Set(uidList); } /** * 增量黑名单同步 */ async function deltaSync(existingSet) { try { console.log('[WB-BL-Lite] Starting delta sync...'); const res = await window.WB_BL_NATIVE_LITE.fetch('/ajax/setting/getFilteredUsers?page=1', { credentials: 'include' }); if (res.status === 418) { console.warn('[WB-BL-Lite] Got 418 during delta sync, skipping'); return existingSet; } if (!res.ok) { console.warn(`[WB-BL-Lite] Delta sync failed: HTTP ${res.status}`); return existingSet; } const data = await res.json(); let added = 0; if (data.card_group) { data.card_group.forEach(item => { const match = item.scheme?.match(/uid=(\d{5,})/); if (match && !existingSet.has(match[1])) { existingSet.add(match[1]); added++; } }); } if (added) { _GM_setValue(CONFIG.STORAGE_KEY, Array.from(existingSet).join(',')); console.log(`[WB-BL-Lite] Delta sync: ${added} new UIDs added`); } else { console.log('[WB-BL-Lite] Delta sync: no new UIDs'); } return existingSet; } catch (error) { console.error('[WB-BL-Lite] Delta sync error:', error); return existingSet; } } /** * 多页同步 */ async function syncPages(existingSet, pages = 5) { let page = 1, cursor = 0, retries = 0, added = 0; while (page <= pages) { let url = `/ajax/setting/getFilteredUsers?page=${page}`; if (cursor) url += `&cursor=${cursor}`; const res = await window.WB_BL_NATIVE_LITE.fetch(url, { credentials: 'include' }); if (res.status === 418) { if (++retries > CONFIG.MAX_RETRIES) break; await sleep(3000); continue; } if (!res.ok) break; const data = await res.json(); (data.card_group || []).forEach(item => { const match = item.scheme.match(/uid=(\d{5,})/); if (match && !existingSet.has(match[1])) { existingSet.add(match[1]); added++; } }); if (!data.next_cursor) break; cursor = data.next_cursor; page++; await sleep(CONFIG.THROTTLE_MS); } if (added) _GM_setValue(CONFIG.STORAGE_KEY, Array.from(existingSet).join(',')); return added; } // 首次运行检查和引导 async function checkFirstRun() { const isFirstRun = !_GM_getValue(CONFIG.FIRST_RUN_KEY, false); if (isFirstRun) { const shouldSync = confirm( '欢迎使用 Weibo Blacklist Enhanced Lite!\n\n' + '首次使用建议进行全量黑名单同步以确保最佳效果。\n' + '这个过程可能需要几分钟时间。\n\n' + '点击"确定"开始同步,"取消"跳过同步' ); _GM_setValue(CONFIG.FIRST_RUN_KEY, true); if (shouldSync) { try { const syncedSet = await fullSyncBlacklist(); alert(`黑名单同步完成!共获取到 ${syncedSet.size} 个用户`); return syncedSet; } catch (error) { alert('同步过程中出现错误,将使用缓存数据'); console.error('Blacklist sync error:', error); } } } return null; } // 初始化黑名单 let blacklistSet = new Set(); (async () => { try { // 首次运行检查 const firstRunResult = await checkFirstRun(); if (firstRunResult) { // 首次运行同步成功 blacklistSet = firstRunResult; } else { // 使用缓存数据或进行增量同步 const cachedData = _GM_getValue(CONFIG.STORAGE_KEY, ''); if (cachedData) { blacklistSet = new Set(cachedData.split(',').filter(uid => uid)); // 增量同步 try { blacklistSet = await deltaSync(blacklistSet); } catch (error) { console.warn('Delta sync failed:', error); } } else { // 没有缓存,尝试获取第一页 try { blacklistSet = await deltaSync(new Set()); } catch (error) { console.error('Initial sync failed:', error); blacklistSet = new Set(); // 确保有一个空的但有效的Set } } } // 等待DOM准备好后注入CSS if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => injectCSS(generateCSSRules())); } else { injectCSS(generateCSSRules()); } // 检查是否需要显示Star提醒 checkStarReminder(); } catch (error) { console.error('Blacklist initialization failed:', error); blacklistSet = new Set(); // 确保有一个有效的空集合 } })(); // === CSS注入(仅黑名单相关) === function generateCSSRules() { const rules = Array.from(blacklistSet).map(uid => ` .Feed_body_3R0rO:has([data-user-id="${uid}"]), .card-wrap:has([data-user-id="${uid}"]) { display: none !important; } `).join('\n'); return rules; } function injectCSS(cssText) { const tryInject = () => { try { const head = document.head || document.getElementsByTagName('head')[0]; if (head) { const style = document.createElement('style'); style.textContent = cssText; style.setAttribute('data-source', 'wb-bl-lite'); head.appendChild(style); console.log('[WB-BL-Lite] CSS rules injected successfully'); } else { setTimeout(tryInject, 100); } } catch (error) { console.error('[WB-BL-Lite] CSS injection failed:', error); setTimeout(tryInject, 100); } }; tryInject(); } // === UID提取器 === function extractUIDs(data) { const uids = new Set(); (function traverse(obj) { if (!obj || typeof obj !== 'object') return; Object.entries(obj).forEach(([key, value]) => { if (/^(?:uid|user_id|userId|id|idstr)$/i.test(key) && typeof value === 'string' && /^\d{5,}$/.test(value)) { uids.add(value); } if (key === 'user' && value && value.id) uids.add(String(value.id)); if (Array.isArray(value)) value.forEach(traverse); else if (value && typeof value === 'object') traverse(value); }); })(data); return uids; } // === 数据过滤器 === function filterData(obj) { if (!obj || typeof obj !== 'object') return obj; if (Array.isArray(obj)) { return obj .filter(item => ![...extractUIDs(item)].some(uid => blacklistSet.has(uid))) .map(filterData); } const result = {}; for (const [key, value] of Object.entries(obj)) { result[key] = Array.isArray(value) ? filterData(value) : (value && typeof value === 'object' ? filterData(value) : value); } return result; } // === Fetch 拦截 === window.fetch = async function(input, init) { const url = typeof input === 'string' ? input : input.url; // 黑名单操作监听 if (typeof url === 'string') { if (url.includes('/filterUser')) { const uid = JSON.parse(init?.body || '{}').uid; blacklistSet.add(String(uid)); _GM_setValue(CONFIG.STORAGE_KEY, [...blacklistSet].join(',')); } if (url.includes('/unfilterUser')) { const uid = JSON.parse(init?.body || '{}').uid; blacklistSet.delete(String(uid)); _GM_setValue(CONFIG.STORAGE_KEY, [...blacklistSet].join(',')); } } const res = await window.WB_BL_NATIVE_LITE.fetch(input, init); if (typeof url === 'string' && /\/(?:ajax\/(?:feed|statuses|comment|getCommentList|repost|like)|graphql\/|(?:mymblog|timeline|index))/.test(url)) { try { const data = await res.clone().json(); return new Response(JSON.stringify(filterData(data)), res); } catch {} } return res; }; // === XHR 拦截 === XMLHttpRequest.prototype.open = function(method, url, ...args) { this._bl_url = url; return window.WB_BL_NATIVE_LITE.XHROpen.call(this, method, url, ...args); }; XMLHttpRequest.prototype.send = function(body) { this.addEventListener('readystatechange', () => { if (this.readyState === 4 && this.status === 200 && this._bl_url) { if (/\/(?:ajax\/(?:feed|statuses)|(?:mymblog|timeline))/.test(this._bl_url)) { try { const data = JSON.parse(this.responseText); Object.defineProperty(this, 'responseText', { value: JSON.stringify(filterData(data)) }); } catch {} } } }); return window.WB_BL_NATIVE_LITE.XHRSend.call(this, body); }; // === WebSocket 拦截 === window.WebSocket = class extends window.WB_BL_NATIVE_LITE.WebSocket { constructor(url, protocols) { super(url, protocols); this.addEventListener('message', evt => { try { const data = JSON.parse(evt.data); evt.data = JSON.stringify(filterData(data)); } catch {} }); } }; // === DOM 监听器 === (function() { const observer = new MutationObserver(mutations => { clearTimeout(window._wb_bl_lite_timer); window._wb_bl_lite_timer = setTimeout(() => { mutations.forEach(mutation => { Array.from(mutation.addedNodes).forEach(node => { if (node instanceof HTMLElement && node.matches('.Feed_body_3R0rO')) { if ([...node.querySelectorAll('[data-user-id]')] .some(el => blacklistSet.has(el.getAttribute('data-user-id')))) { node.style.display = 'none'; } } }); }); }, CONFIG.THROTTLE_MS); }); const attachObserver = () => { const root = document.body || document.documentElement; if (root) { observer.observe(root, { childList: true, subtree: true }); window.addEventListener('beforeunload', () => observer.disconnect()); // SPA 路由监听 const originalPushState = history.pushState; history.pushState = function(state, title, url) { originalPushState.call(this, state, title, url); observer.disconnect(); observer.observe(document.body || document.documentElement, { childList: true, subtree: true }); }; } else { setTimeout(attachObserver, 50); } }; attachObserver(); })(); // === 菜单命令 === _GM_registerMenuCommand('🔄 更新黑名单', async () => { const oldSize = blacklistSet.size; blacklistSet = await deltaSync(new Set(blacklistSet)); _GM_setValue(CONFIG.STORAGE_KEY, [...blacklistSet].join(',')); alert(`黑名单更新完成!新增 ${blacklistSet.size - oldSize} 个用户`); }); _GM_registerMenuCommand('📄 同步前五页', async () => { const added = await syncPages(blacklistSet, 5); alert(`同步五页完成!新增 ${added} 个用户`); }); _GM_registerMenuCommand('🔄 全量同步', async () => { const oldSize = blacklistSet.size; blacklistSet = await fullSyncBlacklist(); alert(`全量同步完成!新增 ${blacklistSet.size - oldSize} 个用户(共 ${blacklistSet.size})`); }); _GM_registerMenuCommand('⭐ 给我们 Star', () => { openGitHub(); }); _GM_registerMenuCommand('🔕 不再提醒Star', () => { const shouldDisable = confirm( '确认要关闭 Star 提醒吗?\n\n' + '这将永久停止所有 Star 相关提醒' ); if (shouldDisable) { _GM_setValue(CONFIG.STAR_REMINDER_DISABLED_KEY, true); alert('✅ Star 提醒已关闭'); } }); _GM_registerMenuCommand('ℹ️ 关于', () => { const usageCount = _GM_getValue(CONFIG.USAGE_COUNT_KEY, 0); const hasStarred = _GM_getValue(CONFIG.HAS_STARRED_KEY, false); const isDisabled = _GM_getValue(CONFIG.DISABLE_STAR_REMINDER_KEY, false); let starStatus = ''; if (hasStarred) { starStatus = '✅ 已标记为给过Star'; } else if (isDisabled) { starStatus = '🔕 Star提醒已禁用'; } else { const nextReminder = CONFIG.STAR_REMINDER_COUNTS.find(count => count > usageCount) || '不再提醒'; starStatus = `🔔 下次Star提醒: ${nextReminder === '不再提醒' ? nextReminder : nextReminder + ' 次使用时'}`; } alert( `Weibo Blacklist Enhanced Lite v${CONFIG.VERSION}\n` + `专注于黑名单功能的轻量版微博增强工具\n\n` + `当前缓存: ${blacklistSet.size} 个用户\n` + `使用次数: ${usageCount} 次\n` + `${starStatus}\n\n` + `作者: DanielZenFlow\n` + `许可: MIT License\n` + `GitHub: https://github.com/DanielZenFlow/Weibo-Blacklist-Enhanced-Lite\n\n` + `感谢使用!如果有帮助请给我们 Star ⭐` ); }); _GM_registerMenuCommand('🔢 查看使用统计', () => { const usageCount = _GM_getValue(CONFIG.USAGE_COUNT_KEY, 0); const lastReminder = _GM_getValue(CONFIG.LAST_STAR_REMINDER_KEY, 0); const hasStarred = _GM_getValue(CONFIG.HAS_STARRED_KEY, false); const isDisabled = _GM_getValue(CONFIG.DISABLE_STAR_REMINDER_KEY, false); const triggeredReminders = CONFIG.STAR_REMINDER_COUNTS.filter(count => count <= lastReminder); const nextReminder = CONFIG.STAR_REMINDER_COUNTS.find(count => count > usageCount); let status = ''; if (hasStarred) { status = '✅ 已标记为给过Star,不再提醒'; } else if (isDisabled) { status = '🔕 Star提醒已禁用'; } else { status = `🔔 下次提醒: ${nextReminder ? nextReminder + ' 次使用时' : '不再提醒'}`; } alert( `📊 使用统计\n\n` + `总使用次数: ${usageCount}\n` + `已触发提醒: ${triggeredReminders.length}/${CONFIG.STAR_REMINDER_COUNTS.length} 次\n` + `${status}\n\n` + `💡 使用次数在每次成功过滤黑名单内容时增加\n` + `🎯 提醒触发点: ${CONFIG.STAR_REMINDER_COUNTS.join(', ')}` ); }); // 启动完成 setTimeout(() => { const isDisabled = _GM_getValue(CONFIG.STAR_REMINDER_DISABLED_KEY, false); const starStatus = isDisabled ? '已关闭' : '已开启'; console.log(`[WB-BL-Lite] v${CONFIG.VERSION} 启动完成,专注黑名单功能,已缓存 ${blacklistSet.size} UIDs`); console.log(`[WB-BL-Lite] Star提醒: ${starStatus}`); console.log(`[WB-BL-Lite] Author: DanielZenFlow | GitHub: https://github.com/DanielZenFlow/Weibo-Blacklist-Enhanced-Lite`); }, 1000); })();