// ==UserScript== // @name 福利吧小助手 // @namespace https://greasyfork.org/zh-CN/users/860681-aoguai // @version 1.0.7.0 // @description 一个用于增强福利吧功能的油猴脚本,提供链接提取、界面美化、编解码等功能。 // @author aoguai // @match http://fulibus.net/* // @match https://fulibus.net/* // @match http://fuliba2025.net/* // @match https://fuliba2025.net/* // @match http://*.fuliba2025.net/* // @match https://*.fuliba2025.net/* // @match http://fuliba2020.net/* // @match https://fuliba2020.net/* // @match http://fuliba2021.net/* // @match https://fuliba2021.net/* // @match http://fuliba2022.net/* // @match https://fuliba2022.net/* // @match http://fuliba2023.net/* // @match https://fuliba2023.net/* // @match http://fuliba2024.net/* // @match https://fuliba2024.net/* // @match http://fuliba2026.net/* // @match https://fuliba2026.net/* // @match http://fuliba2027.net/* // @match https://fuliba2027.net/* // @match http://fuliba2028.net/* // @match https://fuliba2028.net/* // @match http://fuliba2029.net/* // @match https://fuliba2029.net/* // @match http://fuliba2030.net/* // @match https://fuliba2030.net/* // @match http://f.uliba.net/* // @match https://f.uliba.net/* // @match http://*.wnflb2020.com/* // @match https://*.wnflb2020.com/* // @match http://*.wnflb2021.com/* // @match https://*.wnflb2021.com/* // @match http://*.wnflb2022.com/* // @match https://*.wnflb2022.com/* // @match http://*.wnflb2024.com/* // @match https://*.wnflb2024.com/* // @match http://*.wnflb2025.com/* // @match https://*.wnflb2025.com/* // @match http://*.wnflb2026.com/* // @match https://*.wnflb2026.com/* // @match http://*.wnflb2027.com/* // @match https://*.wnflb2027.com/* // @match http://*.wnflb2028.com/* // @match https://*.wnflb2028.com/* // @match http://*.wnflb2029.com/* // @match https://*.wnflb2029.com/* // @match http://*.wnflb2030.com/* // @match https://*.wnflb2030.com/* // @match http://www.wnflb2020.com/* // @match https://www.wnflb2020.com/* // @match http://www.wnflb2021.com/* // @match https://www.wnflb2021.com/* // @match http://www.wnflb2022.com/* // @match https://www.wnflb2022.com/* // @match http://www.wnflb2024.com/* // @match https://www.wnflb2024.com/* // @match http://www.wnflb2025.com/* // @match https://www.wnflb2025.com/* // @match http://www.wnflb2026.com/* // @match https://www.wnflb2026.com/* // @match http://www.wnflb2027.com/* // @match https://www.wnflb2027.com/* // @match http://www.wnflb2028.com/* // @match https://www.wnflb2028.com/* // @match http://www.wnflb2029.com/* // @match https://www.wnflb2029.com/* // @match http://www.wnflb2030.com/* // @match https://www.wnflb2030.com/* // @match http://wnflb2023.com/* // @match https://wnflb2023.com/* // @match http://*.wnflb2023.com/* // @match https://*.wnflb2023.com/* // @icon data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAC1puYAeG/TAHxz1CF5cNJJe3HQBnt1ygJ7c8wIgXy9AXt0zAd4c8oGdm7bAHp9qAAAAAAAAAAAAAAAAAAAAAAAioDaACckvAB4cNSQeW/V1X5z1CZ7cNV9em/Vsnpv1ZR4b9OyeW/UpH1x1SN8cNYArL9QAJOI2wB3a9YAf3LYL31v2YF+cdc5enDWtHlv1cR9cdeHe2/X6H1w2c57b9jzenDVmnlv1vN7b9aWhXfNA39z0gCEe9kAh3/YBHxw2Kp8btn/fG/Y1ntv1915cNXAe2/Y2Hpv1+98b9nwfG/Z+X1x14F4b9XRem/X73lw0kB4b9QAhnvcAI+N1wF9cNqLe27Z/npv1/95b9b/eHDT2Hhv1fJ6cNWxfG/Z1npv1/97cNe5eHHUl3hu1f93b9Ope3TMB7C74wB/ctsAgXTcD3pw15h5b9b8eG7V/3Zu0/x3btT+enDWx3pv1+t5b9b2enHTUHlx00l3b9T4d2/U1Hpy0BkAAAAAfXLWAIF02B98cNejeW/W+npv1/95b9b/eW/W8nlw1KZ8cdeTeW/W83lv1eB6b9fjeW/W/3lw1at+dtAKrafkAHdr1wB8cdd5e2/Y/3pv1v96b9f/em/X/Hlv1el5b9Tae3DX2Xpw1ux6b9f/enDX5Hxx2KB8c9YneG/WAKyn5AB6btcAe3HWTHlv1ud6cNbeeG/U+3pv1vR7cdaleG/U8npw1cx5b9bzeW/V9Xty1FaXiucCgXXaAMK95wAAAAAAfnbWAIR+1gJ7cdZZe3HVq3lw1KB7cNe0f3PZXXpv1uV5cNTKem/W5Xlv1OF5cdE3dnDTAIN1zAAAAAAAAAAAAAAAAACDd9wAmo3xAXty1kl6cNScenDVe31x1298cNjVfXDYwntv1/R5btX/eG/Uw3px0RR6cdEAAAAAAAAAAAAAAAAAAAAAAIF02gCBdNskem/W33lu1v97cNfRe2/X53xu2f58b9j6e2/Y/3lv1ct8c9QVfXPUAAAAAAAAAAAAAAAAAAAAAAB+cdkAf3LZRHlv1fd3btT/eG/V+npv1/58btr/e2/Y/3pv1/94b9Ovf3nMBH52zQAAAAAAAAAAAAAAAAAAAAAAfHLWAH1z1zF4b9Tjd2/U4Hpx1Yp7cdeMe3DYpHlv1vF5b9X+eHDSgW5Z6QB8eMoAAAAAAAAAAAAAAAAAAAAAAH521gB/d9YEeXLUMnly1Cx+e9IEf3vUAn921wd9ctg/e3HWVnp00xJ5ctMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AcAAPgDAADAAQAAgAEAAIAAAADAAAAAwAAAAMABAADAAwAAwAcAAOADAADwAwAA8AMAAPAHAADwBwAA//8AAA== // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js // @require https://greasyfork.org/scripts/403716-gm-config-cn/code/GM_config_CN.js // @connect keyfc.net // @connect fuliba123.com // @grant GM_info // @grant GM_setClipboard // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_addStyle // @run-at document-start // @early-start // @noframes // @license GNU General Public License v3.0 or later // @namespace https://greasyfork.org/scripts/522004 // @supportURL https://greasyfork.org/scripts/522004 // @homepageURL https://greasyfork.org/scripts/522004 // ==/UserScript== (function () { "use strict"; if (typeof GM_config == "undefined") { console.error( "福利吧小助手:\nGM_config 库文件加载失败,脚本无法正常工作,请尝试刷新网页以重新加载。", ); } else if (typeof document.arrive == "undefined") { console.error( "福利吧小助手:\narrive 库文件加载失败,脚本可能无法正常工作,请尝试刷新网页以重新加载。", ); } else if (typeof CryptoJS == "undefined") { console.warn( "福利吧小助手:\nCryptoJS 库文件加载失败,BASE64 编解码功能无法使用。", ); } else { console.debug("福利吧小助手:\n脚本工作环境正常。"); } const icon = { settings: '', code: '', signature: '', collapsedYes: "static/image/common/collapsed_yes.gif", collapsedNo: "static/image/common/collapsed_no.gif", }; const darkColorFields = { darkBgColor: "--good-boy-dark-bg", darkSurfaceColor: "--good-boy-dark-surface", darkInputColor: "--good-boy-dark-input", darkHoverColor: "--good-boy-dark-hover", darkTextColor: "--good-boy-dark-text", darkMutedColor: "--good-boy-dark-muted", darkBorderColor: "--good-boy-dark-border", darkAccentColor: "--good-boy-dark-accent", }; const style = { // 设置面板样式 settings: ` #myGoodBoyConfig { --primary: #4A90E2; --text: #333; --bg: #FFF; --border: #E0E0E0; font-family: system-ui, sans-serif; line-height: 1.6; } #myGoodBoyConfig .section_header { padding: 1rem; background: var(--primary); border-radius: 0.4rem; } #myGoodBoyConfig input[type="checkbox"] { width: 1.2em; height: 1.2em; } #myGoodBoyConfig input[type="color"] { width: 3rem; height: 2rem; padding: 2px; vertical-align: middle; cursor: pointer; } #myGoodBoyConfig button { padding: 0.6em 1.2em; border-radius: 0.4em; transition: opacity 0.2s; } #myGoodBoyConfig button:hover { opacity: 0.9; } `, // 代码面板样式 codePanel: ` #myCodePanel { width: 35vw !important; max-width: 25rem; min-height: 20rem; border-radius: 0.8rem; box-shadow: 0 0.2rem 1rem rgba(0,0,0,0.1); } .nav-tabs { display: flex; width: 100%; justify-content: space-evenly; margin: 10px 0; } .section_header { flex: 1; min-width: 120px; text-align: center; padding: 2px 12px; box-sizing: border-box; border-bottom: 2px solid transparent; } .section_header.active { font-weight: bold; } #myCodePanel textarea { width: 100%; min-height: 10em; padding: 0.8em; border-radius: 0.4em; } #myCodePanel button { padding: 0.6em 1.2em; border-radius: 0.4em; } .config_var { width: 95%; box-sizing: border-box; margin: 10px auto; padding: 5px; } .config_var textarea { width: 100%; box-sizing: border-box; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-family: monospace; resize: vertical; } #myCodePanel .section_desc { width: 100% } `, // 明暗状态由原站维护,插件仅覆盖深色配色。 night: ` html.darking:root { --tb--bdbg: var(--good-boy-dark-bg); --tb--dark: var(--good-boy-dark-bg); --tb--white: var(--good-boy-dark-bg); --tb--tint: var(--good-boy-dark-surface); --tb--base: var(--good-boy-dark-text); --tb--gray: var(--good-boy-dark-muted); --tb--line: var(--good-boy-dark-border); --tb--main: var(--good-boy-dark-accent); scrollbar-color: var(--good-boy-dark-muted) var(--good-boy-dark-bg); } html.darking, html.darking body, html.darking #hd, html.darking #pt.bm, html.darking #pt { background: var(--good-boy-dark-bg) !important; color: var(--good-boy-dark-text) !important; } html.darking .bm, html.darking .bmw, html.darking .bdl, html.darking .bdl dt, html.darking .bdl dd.bdl_a a, html.darking .tb .a a, html.darking .pn, html.darking .fl .bm_h, html.darking .ct2_a, html.darking .ct3_a, html.darking .t_table, html.darking table.plhin { background: var(--good-boy-dark-bg) !important; } html.darking .bdl dl.a, html.darking .tl .th, html.darking .tl .ts th, html.darking .tl .ts td, html.darking .pg a, html.darking .pg strong, html.darking .pgb a, html.darking .pg label, html.darking .bml .bm_h, html.darking #scrolltop a, html.darking .bmn, html.darking .bm_h, html.darking td.pls, html.darking .ad td.plc, html.darking div.exfm, html.darking .tb a, html.darking .tb_h, html.darking .ttp li.a a, html.darking div.uo a, html.darking input#addsubmit_btn, html.darking #gh .bm .bm_h, html.darking .jump_bdl li, html.darking .newthread tr th, html.darking .newthread tr td, html.darking .tl .threadpre td, html.darking .tl .threadpre:hover td, html.darking .nfl .f_c, html.darking #myCodePanel { background: var(--good-boy-dark-bg) !important; } html.darking #threadlist .bm_c, html.darking #threadlisttableid, html.darking #threadlisttableid tbody, html.darking #threadlisttableid tr, html.darking #threadlisttableid th, html.darking #threadlisttableid td, html.darking #ucbody, html.darking .bbda, html.darking #modmenu, html.darking .xi2.pbm, html.darking .mbm.pbm.bbs { background-color: var(--good-boy-dark-bg) !important; } html.darking #nv, html.darking #nv li.a, html.darking #nv li.hover, html.darking .card_gender_0, html.darking .card .o a, html.darking .tbn li.a, html.darking #p_btn a, html.darking #p_btn i { background: var(--good-boy-dark-surface) !important; } html.darking #toptb, html.darking .tedt .bar, html.darking .edt .bar, html.darking .edt .bbar, html.darking #post_extra_tb label.a, html.darking #extcreditmenu.a, html.darking #g_upmine.a, html.darking .tl #forumnewshow, html.darking .jump_bdl .a a, html.darking .jump_bdl .a a:hover, html.darking .psth, html.darking .pl .quote, html.darking .pm_tac, html.darking .pm .c, html.darking .pml .hover, html.darking #uhd, html.darking #flw_header .bar, html.darking .ttp a, html.darking .ttp strong, html.darking .bmw .bm_h, html.darking .GzList ul li a, html.darking .m_c, html.darking .m_c .o, html.darking .dt th, html.darking .section_header_holder p, html.darking #fx_checkin_menu, html.darking #fx_checkin_menub, html.darking .pl .blockcode ol li:hover, html.darking .blk, html.darking .tip, html.darking .flb { background: var(--good-boy-dark-surface) !important; } html.darking #nv li a:hover, html.darking #nv li.hover a, html.darking #nv li.hover a:hover, html.darking #nv > a, html.darking .p_pop, html.darking .p_pof, html.darking .sllt, html.darking .tl #forumnewshow a:hover, html.darking #autopbn:hover, html.darking .pgbtn a:hover, html.darking #hiddenpoststip, html.darking .p_pop a:hover, html.darking .p_pop a.a, html.darking .tps a:hover { background: var(--good-boy-dark-hover) !important; } html.darking #threadlist > div > table > tbody > tr:hover > *, html.darking #threadlist > div > form > table > tbody > tr:not(.threadpre):hover > *, html.darking div.tl > form > table > tbody > tr:hover > *, html.darking #threadlisttableid tbody:hover th, html.darking #threadlisttableid tbody:hover td, html.darking #threadlisttableid tbody[id^="normalthread"]:hover th, html.darking #threadlisttableid tbody[id^="normalthread"]:hover td, html.darking #threadlisttableid tbody[id^="stickthread"]:hover th, html.darking #threadlisttableid tbody[id^="stickthread"]:hover td, html.darking .tl tr:hover th, html.darking .tl tr:hover td { background-color: var(--good-boy-dark-hover) !important; } html.darking a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking #um, html.darking button, html.darking .pg label, html.darking div#forum_rules_37 font, html.darking .article-content, html.darking .article-content h2, html.darking .article-content h3 { color: var(--good-boy-dark-text) !important; } html.darking #um a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking #toptb a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking #toptb .xi2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking #nv li a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking #nv li.a a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking #nv li.hover a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .bm_h h2:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .bm_h h2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .bm_h .xs2:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .bm_h .xs2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .bmw .bm_h a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking a.pn:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .pn span:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .pn strong:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .p_pop a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .excerpt h2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])) { color: var(--good-boy-dark-text) !important; } html.darking .tps a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .chart em:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .xg1:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .xg1 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .xg2:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .xg2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking p .xi2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .by cite a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .by .xi2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .authi em:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .authi .xg1:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .excerpt .note:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .excerpt .meta:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .excerpt .meta a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .pagination span:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])) { color: var(--good-boy-dark-muted) !important; } html.darking .xi2:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .xi2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .xi3 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking p .xi2 a:hover:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking #nv li a:hover:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .p_pop a:hover:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .p_pop a.a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .excerpt h2 a:hover:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .excerpt:hover h2 a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .excerpt .meta a:hover:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .article-content a:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])), html.darking .article-content a:hover:where(:not(.my_good_boy_li > a, [style*="--good-boy-post-color:"])) { color: var(--good-boy-dark-accent) !important; text-decoration-color: currentColor; } html.darking #threadlisttableid th a.xst:hover:where(:not([style*="--good-boy-post-color:"])), html.darking #threadlisttableid th a.s.xst:hover:where(:not([style*="--good-boy-post-color:"])), html.darking #threadlisttableid tbody:hover a.xst:where(:not([style*="--good-boy-post-color:"])), html.darking #threadlisttableid tbody[id^="normalthread"]:hover th a.xst:where(:not([style*="--good-boy-post-color:"])), html.darking #threadlisttableid tbody[id^="stickthread"]:hover th a.xst:where(:not([style*="--good-boy-post-color:"])) { color: var(--good-boy-dark-accent) !important; } html.darking .tedt .pt, html.darking .px, html.darking .pt, html.darking .ps, html.darking select, html.darking input, html.darking textarea, html.darking #myCodePanel textarea, html.darking #myCodePanel button, html.darking .search-input, html.darking .ipt, html.darking #comment, html.darking #scbar, html.darking .scbar_txt, html.darking #scform input.scbar_txt, html.darking #fjump_menu .px { background-color: var(--good-boy-dark-input) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-text) !important; } html.darking .pl .blockcode { background: var(--good-boy-dark-input) !important; } html.darking input::placeholder, html.darking textarea::placeholder, html.darking #comment::placeholder { color: var(--good-boy-dark-muted) !important; } html.darking .search-input:focus, html.darking .ipt:focus, html.darking #comment:focus, html.darking .px:focus, html.darking .pt:focus { border-color: var(--good-boy-dark-accent) !important; box-shadow: 0 0 0 2px var(--good-boy-dark-border); } html.darking input[type="checkbox"], html.darking input[type="radio"] { accent-color: var(--good-boy-dark-accent); } html.darking .bm, html.darking .bmw, html.darking .bm_h, html.darking .fl .bm_h, html.darking #nv, html.darking #nv li, html.darking .pn, html.darking .p_pop, html.darking .p_pop a, html.darking #pt, html.darking #pt.bm, html.darking #uhd, html.darking #ucbody, html.darking .bbda, html.darking #modmenu, html.darking .xi2.pbm, html.darking .mbm.pbm.bbs, html.darking #threadlisttableid, html.darking #threadlisttableid tbody, html.darking #threadlisttableid tr, html.darking #threadlisttableid th, html.darking #threadlisttableid td { border-color: var(--good-boy-dark-border) !important; } html.darking .widget, html.darking .single .content, html.darking .pagecontent, html.darking .page-template-sidebar .content, html.darking .excerpt-minic-index, html.darking .tinycat .excerpt, html.darking .pagemenu, html.darking .container-user, html.darking .loop-flash { background: var(--good-boy-dark-bg) !important; box-shadow: inset 0 0 0 1px var(--good-boy-dark-border), 0 10px 28px rgba(0,0,0,.45); } html.darking .excerpts-title { background: var(--good-boy-dark-bg) !important; box-shadow: inset 0 1px 0 var(--good-boy-dark-border), inset 1px 0 0 var(--good-boy-dark-border), inset -1px 0 0 var(--good-boy-dark-border); } html.darking .excerpt { background: var(--good-boy-dark-bg) !important; border-bottom-color: var(--good-boy-dark-border); box-shadow: inset 1px 0 0 var(--good-boy-dark-border), inset -1px 0 0 var(--good-boy-dark-border); } html.darking .excerpt:hover { background: var(--good-boy-dark-hover) !important; box-shadow: inset 2px 0 0 var(--good-boy-dark-accent), inset -1px 0 0 var(--good-boy-dark-border); } html.darking .article-content blockquote { background: var(--good-boy-dark-surface); border-left-color: var(--good-boy-dark-accent); color: var(--good-boy-dark-text); } html.darking .article-content-fold .-fold { background-image: linear-gradient(transparent, var(--good-boy-dark-bg)); } html.darking .pagination a { background: var(--good-boy-dark-surface) !important; color: var(--good-boy-dark-text) !important; border-color: var(--good-boy-dark-border) !important; } html.darking .pagination a:hover, html.darking .pagination a:focus { background: var(--good-boy-dark-hover) !important; color: var(--good-boy-dark-accent) !important; border-color: var(--good-boy-dark-border) !important; } html.darking .pnc, html.darking a.pnc, html.darking button.pnc, html.darking .pg strong, html.darking .pagination .active a, html.darking .pagination .active span, html.darking .pagination li.next-page a { background: var(--good-boy-dark-accent) !important; border-color: var(--good-boy-dark-accent) !important; } html.darking .pnc, html.darking a.pnc, html.darking button.pnc, html.darking .pnc span, html.darking .pnc strong, html.darking .pnc em, html.darking a.pnc strong, html.darking .pg strong, html.darking .pagination .active a, html.darking .pagination .active span, html.darking .pagination li.next-page a { color: var(--good-boy-dark-bg) !important; } html.darking ul.cl.nav li a, html.darking ul.cl.nav li a:hover { background-repeat: no-repeat !important; background-position: 50% 5px !important; } html.darking .ignore_notice { right: -3px !important; top: -53px !important; } html.darking .signature_switch_div { background-color: var(--good-boy-dark-accent) !important; color: var(--good-boy-dark-bg); box-shadow: 0 0 3px var(--good-boy-dark-accent); } html.darking .header { background: var(--good-boy-dark-surface) !important; box-shadow: 0 1px 0 var(--good-boy-dark-border), 0 8px 24px rgba(0,0,0,.35); } html.darking .site-nav .sub-menu { border-color: var(--good-boy-dark-border) !important; } html.darking #toptb { border-bottom-color: var(--good-boy-dark-border) !important; } html.darking .site-nav > li > a:hover, html.darking .site-nav li.current-menu-item > a, html.darking .site-nav li.current-menu-parent > a { color: var(--good-boy-dark-accent) !important; } html.darking ::-webkit-scrollbar-track, html.darking ::-webkit-scrollbar-corner { background: var(--good-boy-dark-bg); } html.darking ::-webkit-scrollbar-thumb { background: var(--good-boy-dark-muted); border-color: var(--good-boy-dark-bg); } html.darking ::-webkit-scrollbar-thumb:hover { background: var(--good-boy-dark-accent); } /* 原站组件含 ID 和渐变,需同时覆盖优先级及完整背景。 */ html.darking .bm, html.darking .bmw, html.darking #sd_bdl.bdl, html.darking #threadlist.tl, html.darking #postlist.pl { background: var(--good-boy-dark-bg) !important; border-color: var(--good-boy-dark-border) !important; box-shadow: inset 0 1px 0 var(--good-boy-dark-border), 0 10px 28px rgba(0,0,0,.45) !important; } html.darking .pgs, html.darking .bm.bw0, html.darking .bm.pgs { background: var(--good-boy-dark-bg) !important; border-color: var(--good-boy-dark-border) !important; box-shadow: none !important; } html.darking .bdl dt, html.darking .bdl dl.a, html.darking .bdl dd, html.darking .bdl dd a, html.darking .fl_tb, html.darking .fl_tb td, html.darking .fl_tb th { background: var(--good-boy-dark-bg) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-text) !important; } html.darking .bdl_h, html.darking .bm_h, html.darking .fl .bm_h, html.darking .bmw .bm_h, html.darking .bml .bm_h, html.darking #threadlist .th, html.darking #f_pst .bm_h { background: var(--good-boy-dark-surface) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-text) !important; } html.darking .bdl dt a, html.darking .fl_tb h2 a, html.darking #threadlisttableid th { color: var(--good-boy-dark-text) !important; } html.darking .bdl dd.bdl_a a { background: var(--good-boy-dark-surface) !important; border-left-color: var(--good-boy-dark-accent) !important; color: var(--good-boy-dark-accent) !important; } html.darking .bdl dd a:hover, html.darking .fl_tb tr:hover td, html.darking .pg a:hover, html.darking .pgb a:hover { background: var(--good-boy-dark-hover) !important; } html.darking .pg a, html.darking .pg label, html.darking .pgb a { background: var(--good-boy-dark-surface) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-text) !important; } html.darking .pg a:hover, html.darking .pgb a:hover { border-color: var(--good-boy-dark-accent) !important; } html.darking #fjump_menu, html.darking #visitedforums_menu, html.darking #filter_special_menu, html.darking #filter_dateline_menu, html.darking #filter_reward_menu { background: var(--good-boy-dark-surface) !important; border-color: var(--good-boy-dark-border) !important; } html.darking .fl_tb tr:hover td.fl_icn, html.darking .fl_tb tr:hover td.fl_g, html.darking #threadlisttableid tbody[id^="normalthread"]:hover td.icn, html.darking #threadlisttableid tbody[id^="stickthread"]:hover td.icn { box-shadow: inset 2px 0 0 var(--good-boy-dark-accent); } html.darking #threadlisttableid .icn, html.darking #threadlisttableid .o { background-color: var(--good-boy-dark-bg) !important; } html.darking .bm_h .y, html.darking .bm_h .y a, html.darking .bm_h span.o, html.darking #threadlist .tf a, html.darking #threadlist .th .by, html.darking #threadlist .th .num, html.darking #threadlisttableid .by, html.darking #threadlisttableid .num, html.darking #threadlisttableid td em, html.darking #threadlisttableid td em a, html.darking #threadlisttableid .by cite a { color: var(--good-boy-dark-muted) !important; } html.darking .bdl dd a:hover, html.darking .fl_tb tr:hover h2 a, html.darking .fl_tb td.fl_g:hover dt a, html.darking .fl_tb .xi1, html.darking .fl_g .xi1, html.darking .bm_h h2 a:hover, html.darking .bm_h .xs2 a:hover, html.darking .bmw .bm_h a:hover, html.darking .bm_h .xi1, html.darking .bm_h .y .xi1, html.darking #number_favorite, html.darking #threadlist .tf a:hover, html.darking #threadlisttableid .by cite a:hover, html.darking #threadlisttableid .num a, html.darking #threadlisttableid .num a:hover, html.darking .pg a:hover, html.darking .pgb a:hover, html.darking #um .new, html.darking #toptb .new, html.darking #toptb a:hover, html.darking #toptb a.dz-dark-toggle, html.darking #toptb a.dz-dark-toggle:hover { color: var(--good-boy-dark-accent) !important; } html.darking .tl #forumnewshow, html.darking .tl #forumnewshow a, html.darking #threadlist #checknewline tr td, html.darking #threadlist #checknewline tr th { background: var(--good-boy-dark-surface) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-accent) !important; } html.darking .tl #forumnewshow a:hover { background: var(--good-boy-dark-hover) !important; } html.darking #threadlisttableid .newthread tr th, html.darking #threadlisttableid .newthread tr td { background: var(--good-boy-dark-surface) !important; } html.darking #threadlisttableid .newthread .new em, html.darking #threadlisttableid .newthread .new em a, html.darking #threadlist #checknewline a { color: var(--good-boy-dark-accent) !important; } /* 状态图标是透明 GIF,以原图作遮罩着色,不改图片及点击行为。 */ html.darking #threadlisttableid td.icn img:is([src$="/pin_3.gif"], [src$="/folder_lock.gif"], [src$="/folder_common.gif"], [src$="/rewardsmall.gif"]) { object-position: -99999px 0; background: var(--good-boy-dark-accent) !important; } html.darking #threadlisttableid td.icn img[src$="/pin_3.gif"] { mask: url("/static/image/common/pin_3.gif") center / contain no-repeat; } html.darking #threadlisttableid td.icn img[src$="/folder_lock.gif"] { mask: url("/static/image/common/folder_lock.gif") center / contain no-repeat; } html.darking #threadlisttableid td.icn img[src$="/folder_common.gif"] { mask: url("/static/image/common/folder_common.gif") center / contain no-repeat; background: var(--good-boy-dark-muted) !important; } html.darking #threadlisttableid td.icn img[src$="/rewardsmall.gif"] { mask: url("/static/image/common/rewardsmall.gif") center / contain no-repeat; } html.darking #f_pst, html.darking #f_pst .bm_c, html.darking #fastpostform, html.darking #fastposteditor, html.darking #fastpostform .tedt, html.darking #postform, html.darking #postbox, html.darking #editorbox, html.darking #seccheck, html.darking .edt, html.darking .tedt, html.darking #seccheck_fastpost, html.darking #attachlist, html.darking #attach_tblheader td { background: var(--good-boy-dark-bg) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-text) !important; } html.darking #fastpostform .tedt .bar, html.darking .edt .bar, html.darking .tedt .bar, html.darking .edt .bbar, html.darking .tedt .bbar, html.darking .edt .cst, html.darking .tedt .cst, html.darking .edt .btn, html.darking .tedt .btn, html.darking .edt .b2r, html.darking .edt .b1r, html.darking #post_extra_tb { background: var(--good-boy-dark-surface) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-muted) !important; } html.darking #f_pst .px, html.darking #fastpostform input.px, html.darking #fastpostform .tedt .area, html.darking #fastpostform .tedt .pt, html.darking #fastpostmessage, html.darking .edt .area, html.darking .tedt .area, html.darking #e_textarea, html.darking textarea[name="message"], html.darking .editorrow { background: var(--good-boy-dark-input) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-text) !important; } html.darking #fastpostmessage:focus { border-color: var(--good-boy-dark-accent) !important; color: var(--good-boy-dark-text) !important; } html.darking #f_pst .pbt, html.darking #f_pst .pnpost, html.darking #fastpostform .tedt .bar .y a, html.darking #fastpostform .pipe, html.darking .edt a, html.darking .tedt a, html.darking #post_extra_tb label, html.darking #post_extra_tb span { color: var(--good-boy-dark-muted) !important; } html.darking #fastpostform .tedt .bar .y a:hover, html.darking #fastpostform .fpd a:hover, html.darking #fastpostform .fpd a.hover, html.darking .edt a:hover, html.darking .edt a.hover, html.darking .tedt a:hover, html.darking .tedt a.hover, html.darking #post_extra_tb label.a, html.darking #post_extra_tb label:hover { background-color: var(--good-boy-dark-hover) !important; border-color: var(--good-boy-dark-border) !important; color: var(--good-boy-dark-accent) !important; } html.darking .pnc, html.darking a.pnc, html.darking button.pnc { box-shadow: 0 2px 8px rgba(0,0,0,.22) !important; } html.darking .pnc:hover { filter: none; } html.darking .pn:not(.pnc) { background: var(--good-boy-dark-surface) !important; border-color: var(--good-boy-dark-border) !important; } html.darking .pn:not(.pnc), html.darking a.pn:not(.pnc), html.darking .pn:not(.pnc) strong, html.darking .pn:not(.pnc) span, html.darking .pn:not(.pnc) em { color: var(--good-boy-dark-text) !important; } html.darking .pn:not(.pnc):hover { border-color: var(--good-boy-dark-accent) !important; } /* 发帖、回复按钮的文字原在位图中,复用原站标题并保留图片占位。 */ html.darking :is(#newspecial, #newspecialtmp, #post_reply, #post_replytmp)[title] { position: relative; background: var(--good-boy-dark-accent) !important; } html.darking :is(#newspecial, #newspecialtmp, #post_reply, #post_replytmp)[title] > img { opacity: 0; } html.darking :is(#newspecial, #newspecialtmp, #post_reply, #post_replytmp)[title]::after { content: attr(title); position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--good-boy-dark-bg); font-weight: bold; white-space: nowrap; pointer-events: none; } `, blocked: ` .my-blocked { position: relative; opacity: 0.6; transition: opacity 0.3s; } .my-blocked::after { content: "🚫"; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); font-size: 1.2em; } .my-blocked-alert { background: #fff0f0; border-left: 3px solid #ff4d4d; padding: 8px; margin: 5px 0; } `, // 基础样式 basic: ` :root { --link-color: #369; --hover-color: #2B65B7; } #threadlist #threadlisttableid a.xst[style*="--good-boy-post-color:"] { color: var(--good-boy-post-color) !important; } .container.my-custom-width { max-width: none !important; width: var(--custom-content-width) !important; margin-left: auto !important; margin-right: auto !important; } .my_good_boy_div_float { position: absolute; width: 20rem; padding: 1rem; margin: 0 1rem 1rem 0; border: 0.1rem solid var(--border); border-radius: 0.8rem; box-shadow: 0 0.2rem 0.8rem rgba(0,0,0,0.1); } .my_good_boy_div_basic { margin-top: 16px; } .my_good_boy_p { color: #FF4D4F; font-size: 15px; margin: 8px 0; } .my_good_boy_p_float { margin-left: 0 !important; } .my_good_boy_li { list-style-type: disc; white-space: nowrap; margin: 8px 0; } html .my_good_boy_li > a.my_good_boy_a, html .my_good_boy_li > a.my_good_boy_a_copy { color: var(--link-color) !important; } .my_good_boy_a { color: var(--link-color); text-decoration: none; } .my_good_boy_a:hover { color: var(--hover-color); text-decoration: underline; } html .my_good_boy_li > a.my_good_boy_a:not(.a_copy_completed):not(.link_faild):hover { color: var(--hover-color) !important; } .my_good_boy_a_copy { display: inline-block; font-size: 15px; margin-left: 20px; text-decoration: underline; cursor: pointer; } .a_copy_completed, html .my_good_boy_li > a.a_copy_completed, html .my_good_boy_li > a.a_copy_completed:hover { color: #FF6600 !important; animation: fadeIn 0.3s; } .link_faild, html .my_good_boy_li > a.link_faild, html .my_good_boy_li > a.link_faild:hover { color: #999 !important; text-decoration: line-through !important; } .show_more_link { text-decoration: underline; color: var(--primary-color); cursor: pointer; } #settingsIcon { display: block; float: right; cursor: pointer; margin: 4px 5px; width: 17px; height: 17px; transition: transform 0.2s; } #settingsIcon:hover { opacity: 1; } .expand_box { bottom: 0; height: 60px; position: fixed; right: -6vw; transition: all 0.3s ease; width: 12vw; z-index: 999; } .show_expand_box, .expand_box:focus-within { right: 0; width: 6vw; } #myCodeSpan { border-radius: 50%; color: #FFF; cursor: pointer; display: block; height: 38px; width: 38px; position: absolute; right: 1vw; transition: all 0.3s ease; background-color: #787878; bottom: 10px; } #myCodeSpan:hover, #myCodeSpan:focus-visible { box-shadow: 0 0 10px rgba(0,255,0,0.5); color: var(--good-boy-dark-accent, #00A1D6); transform: scale(1.1); } .signature_switch_div { position: absolute; top: 5px; z-index: 100; width: 16px; height: 16px; display: block; position: relative; top: -5px; cursor: pointer; transition: all 0.2s; } .signature_switch_div:hover { transform: scale(1.1); } #settingsIcon > svg, .signature_switch_div > svg { display: block; width: 100%; height: 100%; pointer-events: none; } #myCodeSpan > svg { display: block; width: 32px; height: 32px; margin: 3px; pointer-events: none; } .signature_switch_div .signature_plus { display: none; } .signature_switch_close .signature_plus { display: inline; } #settingsIcon:focus-visible, #myCodeSpan:focus-visible, .signature_switch_div:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } .signature_hide { display: none; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } `, }; // 定义网站地址常量 const SITE_URLS = { HOME: "", FORUM: "", }; const reg = { url: /^[\s\S]*?(magnet:\?xt=urn:btih:(?:[a-z0-9]{40}|[a-z0-9]{32})|ftp:\/\/\S*|ed2k:\/\/\S*|thunder:\/\/\S*|flashget:\/\/\S*|qqdl:\/\/\S*|xfplay:\/\/\S*|https?:\/\/[\w零一二三四五六七八九壹贰叁肆伍陆柒捌玖-]*\.?[\w零一二三四五六七八九壹贰叁肆伍陆柒捌玖-]+\.+\w+\S*)/i, download: /^[\s\S]*?(magnet:\?xt=urn:btih:(?:[a-z0-9]{40}|[a-z0-9]{32})|ed2k:\/\/\S*|thunder:\/\/\S*)/i, www: /^[\s\S]*?(www\.[\w-]+\.[a-z]+[\w#=%+?/-]*)/i, baidupan: /^https?:\/\/pan\.baidu\.com\/s(?:hare)?\/[\w=?&-]+$/i, baidupanIncludeCode: /^https?:\/\/pan\.baidu\.com\/s(?:hare)?\/[\w=?#&-]+$/i, code: /(?:提取)+[^a-z0-9解压]*([a-z0-9]{4})[^a-z0-9]*/i, baidupanCode: /^(?:(?:下载)?链接\S+\s+)?(?:[^解压]+\s+)?[^a-z0-9解压]*([a-z0-9]{4})[^a-z0-9]*(?:app)?[^a-z0-9]*$/i, singleCharCode: /^[a-z0-9]$/i, missingHeaderBaidupan: /^[\s\S]*?[^/\w-]?((? [v, k]), ); // 预编译正则表达式 const MAGNET_PREFIX_REGEX = /^magnet:\?xt=urn:btih:/; const TRIM_REGEX = /^\s+|\s+$/g; const HTTP_PROTOCOL_REGEX = /^https?\/\//i; const URL_PROTOCOL_REGEX = /^(https?:\/\/|ed2k:\/\/)/; const CONFIG = { DEFAULT_ACCESS_NUM: 90, MAX_ACCESS_NUM: 90, LEVEL_ACCESS_MAP: { "-1": 0, 0: 5, }, }; const IMAGE_SELECTOR = [ 'img[src*="avatar"]', 'img[id*="aimg"]', "img.thumb", 'img[decoding="async"]', ".article-content img", ].join(","); const THREAD_LINK_SELECTOR = ".tl th a.xst, .tl td.fn a"; const VISITED_THREAD_IDS_KEY = "visitedThreadIds"; const DEFAULT_VISITED_THREAD_RECORD_LIMIT = 100; const status = { collapsedGwwzEnable: GM_config.getValue("collapsedGwwzEnable", false), accessNum: GM_config.getValue("accessNumber", 90), checkDate: GM_config.getValue("checkDate", "2000-1-1"), oldUrl: location.href, timer1: null, timer2: null, timer3: null, timer4: null, }; class Good_Boy { constructor(config, linkArray = []) { this.config = config; this.floatEnable = this.isMobilePage() ? false : config.get("displayPosition") == "右侧"; this.linkArray = linkArray; this.totalCount = linkArray.length; this.currentCount = 0; this.showMoreStatus = false; this.insertUlStatus = false; this.ulNode = document.createElement("ul"); } insertLinkItem(linkArray = []) { this.linkArray = this.linkArray.concat(linkArray); this.totalCount = this.linkArray.length; const maxLinkNumber = this.config.get("maxLinkNumber"); for ( let i = this.currentCount; i < this.totalCount && i < maxLinkNumber; i++ ) { const tempA = this.createLink(this.linkArray[i]); tempA && this.ulNode.appendChild(tempA); } this.currentCount = Math.min(this.totalCount, maxLinkNumber); if (maxLinkNumber > -1 && this.totalCount > maxLinkNumber) { const message = `[共提取到 ${this.totalCount} 个链接,仅显示前 ${maxLinkNumber} 个]:`; const pElement = this.ulNode.getElementsByTagName("p")[0]; pElement && (pElement.textContent = message); if (!this.showMoreStatus) { this.ulNode.appendChild(this.showMoreLink()); this.showMoreStatus = true; } } else { const message = `[共提取到 ${this.totalCount} 个链接]:`; const pElement = this.ulNode.getElementsByTagName("p")[0]; pElement && (pElement.textContent = message); } } isMobilePage() { return /android|webos|iphone|ipod|blackberry/i.test(navigator.userAgent); } getCutLinkText(linkText) { if (this.isMobilePage()) { return linkText.length >= 35 ? linkText.slice(0, 15) + " ... " + linkText.slice(-10) : linkText; } else if (this.floatEnable) { return linkText.length >= 50 ? linkText.slice(0, 25) + " ... " + linkText.slice(-15) : linkText; } return linkText.length >= 80 ? linkText.slice(0, 45) + " ... " + linkText.slice(-25) : linkText; } managePrefixCode(inputLink) { const { prefixCode, prefixHash, prefixReplace, prefixUrl, redircdn } = reg; if (prefixCode.test(inputLink)) { return inputLink.replace(prefixReplace, ""); } else if (prefixHash.test(inputLink)) { return inputLink.replace(prefixReplace, "magnet:?xt=urn:btih:"); } else if (prefixUrl.test(inputLink)) { return inputLink.replace(prefixReplace, "http://"); } else if (redircdn.test(inputLink)) { return inputLink.replace(redircdn, "$1").replace("______", "."); } return inputLink; } createLink(linkHref) { const { baidupanIncludeCode } = reg; if (linkHref.length > 2000) { return null; } const linkLi = document.createElement("li"); linkLi.className = "my_good_boy_li"; linkLi.style.setProperty("--link-color", this.config.get("linkColor")); const linkA = document.createElement("a"); linkA.title = "点击访问"; linkHref = this.managePrefixCode(linkHref); linkA.className = "my_good_boy_a"; linkA.href = encodeURI(linkHref); linkA.target = "_blank"; linkA.textContent = this.getCutLinkText(linkHref); linkLi.appendChild(linkA); if (this.config.get("copyEnable")) { const copyA = document.createElement("a"); copyA.className = "my_good_boy_a_copy"; copyA.title = "复制链接"; copyA.href = "javascript:;"; copyA.target = "_self"; copyA.textContent = "复制"; copyA.addEventListener( "click", function () { GM_setClipboard(linkHref, "text"); this.classList.add("a_copy_completed"); this.title = "复制成功"; status.timer1 && clearTimeout(status.timer1); status.timer1 = setTimeout(() => { this.classList.remove("a_copy_completed"); this.title = "复制链接"; }, 2000); }, { passive: true }, ); linkLi.appendChild(copyA); if ( baidupanIncludeCode.test(linkHref) && /(?:#|\?pwd=)[a-z0-9]{4}$/i.test(linkHref) ) { const codeMatch = /#([a-z0-9]{4})$/i.exec(linkHref); if (!codeMatch) return linkLi; const codeValue = codeMatch[1]; const codeCopyA = document.createElement("a"); codeCopyA.className = "my_good_boy_a_copy"; codeCopyA.title = "复制提取码"; codeCopyA.href = "javascript:;"; codeCopyA.target = "_self"; codeCopyA.textContent = "复制提取码"; codeCopyA.addEventListener( "click", function () { GM_setClipboard(codeValue, "text"); this.classList.add("a_copy_completed"); this.title = "复制成功"; status.timer2 && clearTimeout(status.timer2); status.timer2 = setTimeout(() => { this.classList.remove("a_copy_completed"); this.title = "复制提取码"; }, 2000); }, { passive: true }, ); linkLi.appendChild(codeCopyA); } } return linkLi; } showMoreLink() { const linkLi = document.createElement("li"); linkLi.className = "my_good_boy_li"; linkLi.style.setProperty("--link-color", this.config.get("linkColor")); const linkA = document.createElement("a"); linkA.className = "my_good_boy_a show_more_link"; linkA.href = "javascript:;"; linkA.title = "显示全部提取链接"; linkA.textContent = "显示全部"; linkA.target = "_self"; linkA.addEventListener( "click", () => { this.ulNode.removeChild(linkLi); for (; this.currentCount < this.totalCount; this.currentCount++) { const tempA = this.createLink(this.linkArray[this.currentCount]); tempA && this.ulNode.appendChild(tempA); } this.ulNode.getElementsByTagName("p")[0].textContent = "[共提取到 " + this.totalCount + " 个链接]:"; this.showMoreStatus = false; }, false, ); linkLi.appendChild(linkA); return linkLi; } createGoodBoyFrame(container, linkArray = []) { if (this.insertUlStatus) { this.insertLinkItem(linkArray); } else { const goodBoyDiv = document.createElement("div"); goodBoyDiv.className = this.floatEnable ? "my_good_boy_div_float" : "my_good_boy_div_basic"; const goodBoyP = document.createElement("p"); goodBoyP.className = this.floatEnable ? "my_good_boy_p my_good_boy_p_float" : "my_good_boy_p"; this.ulNode.appendChild(goodBoyP); this.insertLinkItem(linkArray); goodBoyDiv.appendChild(this.ulNode); container.appendChild(goodBoyDiv); this.insertUlStatus = true; } } createGoodBoyElement(container, linkObj, textObj, nodeTextArray) { const backLinkArray = this.autoEvent( container, linkObj.linkObjArray, linkObj.baiduObjArray, linkObj.linksArray, textObj.hideTextArray, textObj.showTextArray, nodeTextArray, ); backLinkArray.length > 0 && this.createGoodBoyFrame(container, backLinkArray); } managePrefix(inputLinkArray) { const { prefixLinkMagnet, prefixLinkMagnetReplace } = reg; const returnLinkArray = []; inputLinkArray.forEach((item) => { returnLinkArray.push( prefixLinkMagnet.test(item) ? item.replace(prefixLinkMagnetReplace, "") : item, ); }); return returnLinkArray; } pickUpMagnet(item) { const { url, download, hash, md5 } = reg; let tempLink = ""; const item1 = item.replace(/[^a-z0-9:=?/\\.&%|-]/gi, ""); if (download.test(item1)) { tempLink = download.exec(item1)[1]; } else { const item2 = item.replace(/[^a-z0-9:=?/\\.,,。|\s+-]/gi, ""); if (!md5.test(item2) && hash.test(item2) && !url.test(item2)) { const tempHash = hash.exec(item2)[1]; if (!/^(?:[a-z]+|[0-9]+)$/i.test(tempHash)) { tempLink = "magnet:?xt=urn:btih:" + tempHash; } } } return tempLink; } manageText(textArray) { const { url, download, www, missingHeaderBaidupan, missingHeaderBaidupanTest, pan, hash, md5, } = reg; const tempArray = []; textArray.forEach((item) => { let tempLink = ""; if (pan.test(item)) { tempLink = "https://" + pan.exec(item)[1]; } else if (/magnet:|ed2k:|thunder:/i.test(item)) { if (download.test(item)) { tempLink = download.exec(item)[1]; } } else if (url.test(item)) { tempLink = url.exec(item)[1]; } else if (www.test(item)) { tempLink = "http://" + www.exec(item)[1]; } else if (missingHeaderBaidupan.test(item)) { const linkMissValue = missingHeaderBaidupan.exec(item)[1]; tempLink = /^\//i.test(linkMissValue) ? "https://pan.baidu.com" + linkMissValue : "https://pan.baidu.com/" + linkMissValue; } else if (missingHeaderBaidupanTest.test(item)) { tempLink = "https://pan.baidu.com/s/" + missingHeaderBaidupanTest.exec(item)[1]; } else if (!md5.test(item) && hash.test(item)) { tempLink = "magnet:?xt=urn:btih:" + hash.exec(item)[1]; } else if (item.length > 32) { tempLink = this.pickUpMagnet(item); } filterLink(tempLink) && tempArray.push(tempLink); }); return this.managePrefix(tempArray); } decodeLink(inputLinkArray) { const { url } = reg; inputLinkArray.forEach((item, index) => { if (url.test(item)) { inputLinkArray[index] = decodeURI(item); } }); return inputLinkArray; } pickUpBaidupanCode(textArray) { const { code, baidupanCode, singleCharCode } = reg; const codeArray = []; const charArray = []; textArray.forEach((item) => { let tempValue = ""; if (code.test(item)) { tempValue = code.exec(item)[1]; } else if (baidupanCode.test(item)) { tempValue = baidupanCode.exec(item)[1]; } else if (singleCharCode.test(item)) { charArray.push(item); } if ( tempValue && !/\d{4}/i.test(tempValue) && codeArray.indexOf(tempValue) === -1 ) { codeArray.push(tempValue); } }); while (charArray.length > 0 && charArray.length % 4 === 0) { const tempCode = charArray.splice(0, 4); const charValue = tempCode.join(""); if (!/\d{4}/i.test(charValue) && codeArray.indexOf(charValue) === -1) { codeArray.push(charValue); } } return codeArray; } manageRepeatArray(inputLinkArray) { const outputLinkArray = []; for (let i = 0, l = inputLinkArray.length; i < l; i++) { for (let j = i + 1; j < l; j++) { if ( getPureLink(inputLinkArray[i]) === getPureLink(inputLinkArray[j]) ) { ++i; j = i; } } outputLinkArray.push(inputLinkArray[i]); } return outputLinkArray; } splitWrap(textArray) { let tempArray = []; textArray.forEach((text) => { if (text) { tempArray = tempArray.concat(text.split("\n").filter((v) => v)); } }); return tempArray; } autoEvent( container, linkObjArray, baiduObjArray, _linksArray, hideTextArray, showTextArray, nodeTextArray, ) { const { url, baidupan, hash, md5 } = reg; const linkArray = []; const linkTextArray = []; linkObjArray.forEach((item) => { linkArray.push(item.eleValue); linkTextArray.push(item.eleContainer); }); if (hideTextArray.length > 0) { hideTextArray = this.splitWrap(hideTextArray); } if (showTextArray.length > 0) { showTextArray = this.splitWrap(showTextArray); } if (nodeTextArray.length > 0) { nodeTextArray = this.splitWrap(nodeTextArray); } const concatTextArray = this.manageRepeatArray( this.decodeLink(hideTextArray), ); let concatLinkArray = this.manageRepeatArray( this.decodeLink(this.manageText(linkArray.concat(concatTextArray))), ); const generalTextArray = showTextArray.concat( nodeTextArray.concat(linkTextArray), ); const indexA = []; const codeB = this.pickUpBaidupanCode(concatTextArray); let codeC = []; if (codeB.length > 0) { for (let j = 0; j < concatLinkArray.length; j++) { if (baidupan.test(concatLinkArray[j])) { indexA.push(j); } } if (codeB.length === indexA.length) { for (let k = 0; k < indexA.length; k++) { if (!/\?pwd=/.test(concatLinkArray[indexA[k]])) { concatLinkArray[indexA[k]] += "?pwd=" + codeB[k]; } } } else if (codeB.length === baiduObjArray.length) { for (let l = 0; l < baiduObjArray.length; l++) { if (!/\?pwd=/.test(baiduObjArray[l].ele.href)) { baiduObjArray[l].ele.textContent = baiduObjArray[l].eleContainer + "?pwd=" + codeB[l]; baiduObjArray[l].ele.href = baiduObjArray[l].eleValue + "?pwd=" + codeB[l]; } } } } else { codeC = this.pickUpBaidupanCode(generalTextArray); if (codeC.length > 0) { for (let j = 0; j < concatLinkArray.length; j++) { if (baidupan.test(concatLinkArray[j])) { indexA.push(j); } } if (codeC.length === indexA.length) { for (let k = 0; k < indexA.length; k++) { concatLinkArray[indexA[k]] += "?pwd=" + codeC[k]; } } else if (codeC.length === baiduObjArray.length) { for (let l = 0; l < baiduObjArray.length; l++) { baiduObjArray[l].ele.textContent = baiduObjArray[l].eleContainer + "?pwd=" + codeC[l]; baiduObjArray[l].ele.href = baiduObjArray[l].eleValue + "?pwd=" + codeC[l]; } } } } let hashArray = []; generalTextArray.forEach((item) => { if (!url.test(item)) { if (!md5.test(item) && hash.test(item)) { hashArray.push("magnet:?xt=urn:btih:" + hash.exec(item)[1]); } else if (item.length > 32) { let temp_link = this.pickUpMagnet(item); temp_link && hashArray.push(temp_link); } } }); hashArray = this.decodeLink(hashArray); concatLinkArray = this.manageRepeatArray( concatLinkArray.concat(hashArray), ); const hideCodeTextArray = this.decodeCoreValues( container, hideTextArray.concat(generalTextArray), ); if (hideCodeTextArray.length > 0) { hideCodeTextArray.forEach((item, index) => { if (!url.test(item)) { if (!md5.test(item) && hash.test(item)) { hideCodeTextArray[index] = "magnet:?xt=urn:btih:" + hash.exec(item)[1]; } else if (item.length > 32) { const tempLink = this.pickUpMagnet(item); if (tempLink) { hideCodeTextArray[index] = tempLink; } } } }); concatLinkArray = this.manageRepeatArray( concatLinkArray.concat(hideCodeTextArray), ); } concatLinkArray = concatLinkArray .map((item) => { if (generalTextArray.includes(item)) { return null; } return item; }) .filter((v) => v); return concatLinkArray; } foyuPromise(encoded) { return new Promise(function (resolve, _reject) { GM_xmlhttpRequest({ method: "POST", url: "https://keyfc.net/bbs/tools/tudou.aspx", data: "orignalMsg=" + encoded.replace(/\s/g, "") + "&action=Decode", headers: { "Content-Type": "application/x-www-form-urlencoded", }, onload: function (res) { if (res.status == 200 && res.readyState == 4) { resolve( res.responseText.replace(/^<\/Message><\/BUDDHIST>$/g, ""), ); } else { console.warn("福利吧小助手:\n自动解码出错!"); resolve(""); } }, onerror: function () { console.error("福利吧小助手:\n网络链接出错!"); resolve(""); }, }); }); } async foyuDecode(container, encoded) { await this.foyuPromise(encoded).then((data) => { data && this.createGoodBoyFrame(container, [data]); }); } decodeCoreValues(container, textArray) { const { core, baijia, foyu } = reg; const decodeArray = []; textArray.forEach((item) => { if (core.test(item)) { decodeArray.push(coreValuesDecode(core.exec(item)[1])); } else if (baijia.test(item)) { decodeArray.push(baijiaDecode(baijia.exec(item)[1])); } else if (foyu.test(item)) { this.foyuDecode(container, foyu.exec(item)[1]); } }); return decodeArray; } } function randBin() { return Math.random() >= 0.5; } function str2Utf8(str) { const ENCODED_CHARS = /[A-Za-z0-9\-_.!~*'()]/g; return Array.from(str) .map((char) => { if (ENCODED_CHARS.test(char)) { ENCODED_CHARS.lastIndex = 0; return char.codePointAt(0).toString(16); } const code = char.charCodeAt(0); if (code < 128) { return code.toString(16).padStart(2, "0"); } if (code < 2048) { return ( ((code >> 6) | 0xc0).toString(16) + ((code & 0x3f) | 0x80).toString(16) ); } return ( ((code >> 12) | 0xe0).toString(16) + (((code >> 6) & 0x3f) | 0x80).toString(16) + ((code & 0x3f) | 0x80).toString(16) ); }) .join("") .toUpperCase(); } function hex2Duo(hexs) { const duo = new Array(hexs.length * 2); let index = 0; const len = hexs.length; for (let i = 0; i < len; i++) { const n = hexs[i] >= "a" ? hexs.charCodeAt(i) - 87 : hexs[i] >= "A" ? hexs.charCodeAt(i) - 55 : hexs.charCodeAt(i) - 48; if (n < 10) { duo[index++] = n; } else { if (randBin()) { duo[index++] = 10; duo[index++] = n - 10; } else { duo[index++] = 11; duo[index++] = n - 6; } } } return duo.slice(0, index); } function utf82Str(utfs) { const len = utfs.length * 2; const arr = new Array(len); for (let i = 0, j = 0; i < utfs.length; i++) { arr[j++] = "%"; arr[j++] = utfs[i]; } return decodeURIComponent(arr.join("")); } function duo2Hex(duo) { const l = duo.length; const result = new Array(l); let pos = 0; for (let i = 0; i < l; i++) { const curr = duo[i]; if (curr < 10) { result[pos++] = curr.toString(16).toUpperCase(); } else if (curr === 10) { result[pos++] = (duo[++i] + 10).toString(16).toUpperCase(); } else { result[pos++] = (duo[++i] + 6).toString(16).toUpperCase(); } } return result.slice(0, pos).join(""); } function duo2Values(duo) { return duo.map((d) => coreValues[2 * d] + coreValues[2 * d + 1]).join(""); } function coreValuesEncode(str) { return duo2Values(hex2Duo(str2Utf8(str))); } function coreValuesDecode(encoded) { const duo = []; for (let c of encoded) { let i = coreValues.indexOf(c); if (i === -1) { continue; } else if (i & 1) { continue; } else { duo.push(i >> 1); } } const hexs = duo2Hex(duo); let str; try { str = utf82Str(hexs); } catch (e) { throw e; } return str; } function isFloat(str) { return str == "右侧"; } function baijiaEncode(str) { const cleaned = str.replace(TRIM_REGEX, ""); const value = cleaned.replace(MAGNET_PREFIX_REGEX, ""); return Array.from(value) .map((char) => reverseMap.get(char) || "") .join(""); } function baijiaDecode(encoded) { const decoded = Array.from(encoded) .map((char) => baijiaValues.get(char) || "") .join(""); if (HTTP_PROTOCOL_REGEX.test(decoded)) { return decoded.replace(/^(https?)/, "$1:"); } if (URL_PROTOCOL_REGEX.test(decoded)) { return decoded; } return `magnet:?xt=urn:btih:${decoded}`; } function convertChineseNumber(textString) { return textString.replace( /[零一二三四五六七八九壹贰叁肆伍陆柒捌玖]/gi, (arg0) => { let index = "零一二三四五六七八九".indexOf(arg0); if (index === -1) { index = "壹贰叁肆伍陆柒捌玖".indexOf(arg0) + 1; } return index; }, ); } function filterLink(inputLink) { let status = true; if (inputLink) { for (let i in filterReg) { if (filterReg[i].test(inputLink)) { status = false; break; } } } else { status = false; } return status; } function getPureLink(inputLink, depthBoolean) { const { baidupanIncludeCode } = reg; let outputLink = inputLink; if (depthBoolean) { if (baidupanIncludeCode.test(outputLink)) { if (/#[a-z0-9]{4}$/i.test(outputLink)) { outputLink = outputLink.replace(/#[a-z0-9]{4}$/i, ""); } } else if ( /^https?:\/\/www\.bilibili\.com\/video\/av\d+\?from=search/i.test( outputLink, ) ) { outputLink = outputLink.replace(/\?from=search.*/, ""); } } let returnLink = outputLink .replace( /(^(?:\s+)?(?:\[url\])?(?:\s+)?(?:https?:\/\/)?|(?:\/+)?(?:\s+)?(?:\[\/url\])?$|%C2%A0$)/gi, "", ) .replace(/%C2%A0/gi, "%20"); try { returnLink = decodeURI(returnLink); } catch (e) { console.warn("福利吧小助手:\n出现编码转换错误。", outputLink); console.warn(e); returnLink = outputLink .replace( /(^(?:\s+)?(?:\[url\])?(?:\s+)?(?:https?:\/\/)?|(?:\/+)?(?:\s+)?(?:\[\/url\])?$|%C2%A0$)/gi, "", ) .replace(/%C2%A0/gi, "%20"); } return returnLink; } function contrastTextAndLink(textValue, linkValue) { let sta = true; textValue = getPureLink(textValue, true); linkValue = getPureLink(linkValue, true); if (/\s...\s/i.test(textValue)) { sta = false; } else if (textValue === linkValue) { sta = false; } return sta; } function findLink(container) { const { baidupan } = reg; const linkA = container.getElementsByTagName("a"); const tempObj = { linkObjArray: [], baiduObjArray: [], linksArray: [], }; const tempBaiduArray = []; for (let i = 0; i < linkA.length; i++) { if (linkA[i].closest("div.aimg_tip")) { continue; } const tempLink = linkA[i].href; if (filterLink(tempLink)) { const tempImg = linkA[i].querySelectorAll("img"); if ( tempImg.length === 0 || (tempImg.length > 0 && tempImg[0].src !== tempLink && tempImg[0].getAttribute("file") !== tempLink) ) { const tempText = linkA[i].innerText.replace(/^\s+|\s+$/gi, ""); if (contrastTextAndLink(tempText, tempLink)) { const linkObj = { eleValue: tempLink, eleContainer: tempText, ele: linkA[i], }; tempObj.linkObjArray.push(linkObj); } else if (baidupan.test(tempLink)) { const baiduObj = { eleValue: tempLink, eleContainer: tempText, ele: linkA[i], }; if (tempBaiduArray.indexOf(tempLink) === -1) { tempBaiduArray.push(tempLink); tempObj.baiduObjArray.push(baiduObj); } } } tempObj.linksArray.indexOf(tempLink) === -1 && tempObj.linksArray.push(tempLink); } } return tempObj; } // 定义常量,提高可读性和性能 const GRAY_THRESHOLD = 192; const OPACITY_THRESHOLD = 0.2; const GRAY_COEFFICIENTS = { R: 0.299, G: 0.587, B: 0.114, }; function judgeColor(color) { // 使用一个正则表达式同时匹配 rgb 和 rgba const matches = color.match( /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d*\.?\d+))?\)/i, ); if (!matches) return false; const [, r, g, b, a = 1] = matches.map((val) => +val); // 如果是 rgba 且透明度低于阈值,直接返回 if (a <= OPACITY_THRESHOLD) return true; // 计算灰度值 const grayLevel = r * GRAY_COEFFICIENTS.R + g * GRAY_COEFFICIENTS.G + b * GRAY_COEFFICIENTS.B; return grayLevel > GRAY_THRESHOLD; } // rgb 转 rgba 函数 function rgb2Rgba(colorString) { const matches = colorString.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/i); if (!matches) return colorString; const [, r, g, b] = matches; return `rgba(${r}, ${g}, ${b}, 1)`; } function display_Text(container, newTextColor, newTextBackgroundColor) { const { url } = reg; const tempObj = { hideTextArray: [], showTextArray: [], }; // 处理font标签 const textFont = container.getElementsByTagName("font"); for (let i = 0; i < textFont.length; i++) { if ( textFont[i].closest(".quote") || textFont[i].getElementsByClassName("aimg_tip").length !== 0 ) { continue; } const tempText = textFont[i].innerText.replace(/^\s+|\s+$/gi, ""); if (!/^\s*$/i.test(tempText)) { const textColor = window.getComputedStyle(textFont[i]).color; const textBackgroundColor = window.getComputedStyle( textFont[i], ).backgroundColor; if (judgeColor(textColor) && judgeColor(textBackgroundColor)) { textFont[i].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } else if (rgb2Rgba(textBackgroundColor) === rgb2Rgba(textColor)) { textFont[i].style.backgroundColor = newTextBackgroundColor; textFont[i].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } else if (!judgeColor(textBackgroundColor) && !judgeColor(textColor)) { textFont[i].style.backgroundColor = newTextBackgroundColor; textFont[i].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } else if ( textFont[i].childNodes.length === 1 && textFont[i].childNodes[0].nodeType === 3 ) { if (GM_config.get("extractEnable")) { if (url.test(tempText)) { textFont[i].innerHTML = textFont[i].innerHTML.replace( url, (_arg0, arg1) => { return ( '' + convertChineseNumber(arg1) + "" ); }, ); } } !tempObj.showTextArray.includes(tempText) && tempObj.showTextArray.push(tempText); } else if (textFont[i].childNodes.length > 1) { const tFont = textFont[i].childNodes; for (let child_index in tFont) { analysisText(tFont[child_index]); } !tempObj.showTextArray.includes(tempText) && tempObj.showTextArray.push(tempText); } } } // 处理table标签 const tempTable = container.getElementsByTagName("table"); for (let j = 0; j < tempTable.length; j++) { if ( !judgeColor(tempTable[j].style.backgroundColor) && !judgeColor(tempTable[j].style.color) ) { tempTable[j].style.backgroundColor = newTextBackgroundColor; } else if ( judgeColor(tempTable[j].style.color) && judgeColor(tempTable[j].style.backgroundColor) ) { tempTable[j].style.color = newTextColor; } } // 处理blockquote标签 const blockquotes = container.getElementsByTagName("blockquote"); for (let k = 0; k < blockquotes.length; k++) { // 检查blockquote本身的颜色 const blockquoteColor = window.getComputedStyle(blockquotes[k]).color; const blockquoteBgColor = window.getComputedStyle( blockquotes[k], ).backgroundColor; if ( judgeColor(blockquoteColor) || rgb2Rgba(blockquoteBgColor) === rgb2Rgba(blockquoteColor) ) { blockquotes[k].style.color = newTextColor; const tempText = blockquotes[k].innerText.replace(/^\s+|\s+$/gi, ""); if (!/^\s*$/i.test(tempText)) { tempObj.hideTextArray.push(tempText); } } } // 处理strong标签 const strongs = container.getElementsByTagName("strong"); for (let m = 0; m < strongs.length; m++) { const strongColor = window.getComputedStyle(strongs[m]).color; const strongBgColor = window.getComputedStyle(strongs[m]).backgroundColor; const tempText = strongs[m].innerText.replace(/^\s+|\s+$/gi, ""); if (!/^\s*$/i.test(tempText)) { if (judgeColor(strongColor) && judgeColor(strongBgColor)) { strongs[m].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } else if (rgb2Rgba(strongBgColor) === rgb2Rgba(strongColor)) { strongs[m].style.backgroundColor = newTextBackgroundColor; strongs[m].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } else if (!judgeColor(strongBgColor) && !judgeColor(strongColor)) { strongs[m].style.backgroundColor = newTextBackgroundColor; strongs[m].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } } } // 处理span标签 (通常用于隐藏文本) const spans = container.getElementsByTagName("span"); for (let n = 0; n < spans.length; n++) { const spanColor = window.getComputedStyle(spans[n]).color; const spanBgColor = window.getComputedStyle(spans[n]).backgroundColor; const tempText = spans[n].innerText.replace(/^\s+|\s+$/gi, ""); if (!/^\s*$/i.test(tempText)) { if (judgeColor(spanColor) && judgeColor(spanBgColor)) { spans[n].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } else if (rgb2Rgba(spanBgColor) === rgb2Rgba(spanColor)) { spans[n].style.backgroundColor = newTextBackgroundColor; spans[n].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } else if (spanColor === "rgb(255, 255, 255)") { spans[n].style.color = newTextColor; tempObj.hideTextArray.push(tempText); } } } return tempObj; } function text2A(node) { const { url } = reg; const tempSpan = document.createElement("span"); tempSpan.innerHTML = node.nodeValue.replace(url, (arg0, arg1) => { if (arg1.length > 2000) { return arg0; } return arg0.replace( arg1, '' + convertChineseNumber(arg1) + "", ); }); node.parentNode.replaceChild(tempSpan, node); } function analysisText(domPoint) { const { url } = reg; const nodeList = domPoint.childNodes; const nodeTextArray = []; for (let i in nodeList) { if (nodeList[i].nodeType === 3) { const tempText = nodeList[i].nodeValue.replace(/^\s+|\s+$/gi, ""); if (!/^\s*$/i.test(tempText)) { if (GM_config.get("extractEnable")) { if (url.test(tempText)) { text2A(nodeList[i]); } } nodeTextArray.push(tempText); } } else if ( nodeList[i].nodeType === 1 && !nodeList[i].className.includes("quote") && !nodeList[i].className.includes("pstatus") && !nodeList[i].className.includes("aimg_tip") && !nodeList[i].className.includes("blockcode") && nodeList[i].nodeName !== "FONT" && nodeList[i].nodeName !== "A" && nodeList[i].nodeName !== "SCRIPT" && nodeList[i].childNodes.length > 0 ) { const recursiveArray = analysisText(nodeList[i]); for (let j in recursiveArray) { nodeTextArray.push(recursiveArray[j]); } } } return nodeTextArray; } function isMobilePage() { return /android|webos|iphone|ipod|blackberry/i.test(navigator.userAgent); } function getNowDate() { const now = new Date(); return now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate(); } function getUpdateTime(unixTime) { if (!unixTime) return ""; const formatter = new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }); return ` (更新时间: ${formatter .format(new Date(unixTime)) .replace(/\//g, "-")})`; } // 用户权限相关的纯函数 function calculateUserAccess(level) { if (level in CONFIG.LEVEL_ACCESS_MAP) { return CONFIG.LEVEL_ACCESS_MAP[level]; } return level >= 1 && level <= 8 ? level * 10 : CONFIG.DEFAULT_ACCESS_NUM; } // 样式处理相关的纯函数 function getPostStyle(type, color = "") { const styles = { blocked: "color: #999; font-weight: bold; text-decoration-line: line-through; text-decoration-color: #000000;", restricted: "color: #999; font-weight: bold;", highlighted: `font-weight: bold; color: ${color}; --good-boy-post-color: ${color};`, }; return styles[type] || ""; } // 数字提取工具函数 function extractNumber(text) { if (!text) return 0; return Number(text.replace("+", "")); } // 获取最大点赞数 function getMaxAgreeCount(fontElements) { return Array.from(fontElements) .map((el) => extractNumber(el.textContent)) .reduce((max, current) => Math.max(max, current), 0); } function normalizeVisitedThreadIdList(inputList) { const outputList = []; inputList.forEach((item) => { const threadId = String(item); if (/^\d+$/.test(threadId) && !outputList.includes(threadId)) { outputList.push(threadId); } }); return outputList; } function getThreadIdFromUrl(inputUrl) { if (!inputUrl) return ""; const urlText = String(inputUrl); const pathMatch = /(?:^|[/?#&])thread-(\d+)-\d+(?:-\d+)?\.html/i.exec( urlText, ); if (pathMatch) return pathMatch[1]; try { const urlObj = new URL(urlText, location.href); const tid = urlObj.searchParams.get("tid"); return /^\d+$/.test(tid) ? tid : ""; } catch (e) { const tidMatch = /[?&]tid=(\d+)/i.exec(urlText); return tidMatch ? tidMatch[1] : ""; } } function getVisitedThreadIdList() { const storedValue = GM_getValue(VISITED_THREAD_IDS_KEY, []); if (Array.isArray(storedValue)) { return normalizeVisitedThreadIdList(storedValue); } if (typeof storedValue === "string") { try { const parsedValue = JSON.parse(storedValue); if (Array.isArray(parsedValue)) { return normalizeVisitedThreadIdList(parsedValue); } } catch (e) { return normalizeVisitedThreadIdList(storedValue.split(",")); } } return []; } function getVisitedThreadRecordLimit() { const recordLimit = Number(GM_config.get("visitedRecordLimit")); return Number.isFinite(recordLimit) && recordLimit >= 0 ? Math.floor(recordLimit) : DEFAULT_VISITED_THREAD_RECORD_LIMIT; } function rememberVisitedThreadId(threadId) { const normalizedThreadId = String(threadId || ""); if (!/^\d+$/.test(normalizedThreadId)) return; const visitedThreadRecordLimit = getVisitedThreadRecordLimit(); if (visitedThreadRecordLimit === 0) { GM_setValue(VISITED_THREAD_IDS_KEY, []); return; } const visitedThreadIds = getVisitedThreadIdList().filter( (item) => item !== normalizedThreadId, ); visitedThreadIds.unshift(normalizedThreadId); if (visitedThreadIds.length > visitedThreadRecordLimit) { visitedThreadIds.length = visitedThreadRecordLimit; } GM_setValue(VISITED_THREAD_IDS_KEY, visitedThreadIds); } function applySavedVisitedThreadStyle( link, visitedThreadIds = new Set(getVisitedThreadIdList()), ) { const threadId = getThreadIdFromUrl(link?.href); if (!threadId || !visitedThreadIds.has(threadId)) return; const visitedColor = GM_config.get("visitedColor"); if ( link.style.getPropertyValue("color") === visitedColor && link.style.getPropertyPriority("color") === "important" ) { return; } link.style.setProperty("color", visitedColor, "important"); } function applySavedVisitedThreadStyles(container = document) { const visitedThreadIds = new Set(getVisitedThreadIdList()); if (visitedThreadIds.size === 0) return; container .querySelectorAll?.(THREAD_LINK_SELECTOR) .forEach((link) => applySavedVisitedThreadStyle(link, visitedThreadIds)); if (container.matches?.(THREAD_LINK_SELECTOR)) { applySavedVisitedThreadStyle(container, visitedThreadIds); } } function recordVisitedThreadFromEvent(event) { const link = event.target.closest?.("a[href]"); const threadId = getThreadIdFromUrl(link?.href); if (!threadId) return; rememberVisitedThreadId(threadId); applySavedVisitedThreadStyle(link, new Set([threadId])); } function initSavedVisitedThreadMarker() { if ( !document.body || document.body.dataset.goodBoyVisitedReady === "true" ) { return; } document.body.dataset.goodBoyVisitedReady = "true"; applySavedVisitedThreadStyles(); document.body.addEventListener("click", recordVisitedThreadFromEvent, true); document.body.addEventListener( "auxclick", recordVisitedThreadFromEvent, true, ); if (typeof document.arrive === "function") { document.arrive( THREAD_LINK_SELECTOR, { onceOnly: false, existing: true }, function () { applySavedVisitedThreadStyle(this); }, ); } } function highlightPost() { // 确保DOM已经加载 if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => initHighlight()); } else { initHighlight(); } } // 初始化高亮功能 function initHighlight() { try { updateUserAccessLevel(); initializePostHighlighting(); } catch (error) { console.warn("福利吧小助手:\n执行出错:", error.message); } } // 更新用户访问权限 function updateUserAccessLevel() { try { const upMineElement = document.getElementById("g_upmine"); if (!upMineElement) return; const levelMatch = /LV\.(-?[0-9])/.exec(upMineElement.textContent); if (!levelMatch) return; const newAccessNum = calculateUserAccess(levelMatch[1]); if (status.accessNum !== newAccessNum) { status.accessNum = newAccessNum; GM_config.setValue("accessNumber", newAccessNum); } } catch (error) { console.warn( "福利吧小助手:\n识别用户阅读权限出错,将使用存储中记录的用户阅读权限。", ); } console.debug("福利吧小助手:\n用户阅读权限:", status.accessNum); } // 初始化帖子高亮 function initializePostHighlighting() { // 缓存选择器字符串,避免重复创建字符串 const THREADLIST_SELECTOR = "#threadlisttableid"; // 立即检查目标元素 const threadlist = document.querySelector(THREADLIST_SELECTOR); if (threadlist) { processThreadList(threadlist); return; } // 创建单个共享的 MutationObserver 配置对象 const observerConfig = { childList: true, subtree: true, }; // 使用函数声明提升性能,避免在回调中重复创建函数 function threadlistCallback(mutations, obs) { const threadlist = document.querySelector(THREADLIST_SELECTOR); if (threadlist) { processThreadList(threadlist); obs.disconnect(); } } function bodyCallback(mutations, obs) { if (document.body) { obs.disconnect(); const observer = new MutationObserver(threadlistCallback); observer.observe(document.body, observerConfig); } } // 主逻辑 if (document.body) { const observer = new MutationObserver(threadlistCallback); observer.observe(document.body, observerConfig); } else { const bodyObserver = new MutationObserver(bodyCallback); bodyObserver.observe(document.documentElement, { childList: true, }); } } // 处理帖子列表 function processThreadList(tbody) { const items = tbody.querySelectorAll(".common, .new, .lock"); items.forEach(processPostItem); } // 处理单个帖子 function processPostItem(item) { const titleElement = item.querySelector(".xst"); if (!titleElement) return; const applyVisitedStyle = () => { if (GM_config.get("visitedEnable")) { applySavedVisitedThreadStyle(titleElement); } }; // 如果已经是加粗样式,跳过处理 if (titleElement.style.fontWeight === "700") { applyVisitedStyle(); return; } const readNum = extractNumber(item.querySelector(".xw1")?.textContent); // 处理阅读权限 if (readNum > CONFIG.MAX_ACCESS_NUM) { titleElement.setAttribute("style", getPostStyle("blocked")); applyVisitedStyle(); return; } if (readNum > status.accessNum) { titleElement.setAttribute("style", getPostStyle("restricted")); applyVisitedStyle(); return; } // 处理点赞数 const agreeNum = getMaxAgreeCount(item.querySelectorAll("font")); if (agreeNum >= GM_config.get("agreeThreshold")) { titleElement.setAttribute( "style", getPostStyle("highlighted", GM_config.get("agreeColor")), ); applyVisitedStyle(); return; } // 处理回复数 const replyNum = extractNumber( item.parentNode.querySelector("td.num a")?.textContent, ); if (replyNum >= GM_config.get("replyTHreshold")) { titleElement.setAttribute( "style", getPostStyle("highlighted", GM_config.get("replyColor")), ); } applyVisitedStyle(); } function createSettingsIcon() { const iconSpan = document.createElement("span"); iconSpan.id = "settingsIcon"; iconSpan.title = "打开福利吧小助手设置"; iconSpan.innerHTML = icon.settings; iconSpan.tabIndex = 0; iconSpan.setAttribute("role", "button"); iconSpan.setAttribute("aria-label", iconSpan.title); iconSpan.addEventListener("click", () => { GM_config.open(); }); iconSpan.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); iconSpan.click(); } }); return iconSpan; } function operateCode(inputId, outputId, callback, waitStr = "") { const str = document.getElementById(inputId).value; if (str) { document.getElementById(outputId).value = waitStr; const result = callback(str, outputId); if (result) { document.getElementById(outputId).value = result; } } } function copyCode(copyBtn, resultId) { const copyStr = document.getElementById(resultId).value; if (copyStr) { GM_setClipboard(copyStr, "text"); copyBtn.title = "复制成功"; copyBtn.classList.add("a_copy_completed"); status.timer3 && clearTimeout(status.timer3); status.timer3 = setTimeout(() => { copyBtn.classList.remove("a_copy_completed"); copyBtn.title = "将输出的结果复制到剪贴板中"; }, 2000); } } function createSwitchSpan(keyValue, domArray) { const switchSpan = document.createElement("span"); switchSpan.className = "o"; const switchImg = document.createElement("img"); switchImg.id = keyValue + "_user_img"; switchImg.title = "收起/展开"; switchImg.alt = "收起/展开"; if (status[keyValue]) { domArray.forEach(function (item) { item.classList.add("collapsed_hide"); }); switchImg.src = icon.collapsedYes; } else { switchImg.src = icon.collapsedNo; } switchImg.addEventListener("click", () => { if (status[keyValue]) { domArray.forEach((item) => { item.classList.remove("collapsed_hide"); }); switchImg.src = icon.collapsedNo; status[keyValue] = false; } else { domArray.forEach((item) => { item.classList.add("collapsed_hide"); }); switchImg.src = icon.collapsedYes; status[keyValue] = true; } GM_config.setValue(keyValue, status[keyValue]); }); switchSpan.appendChild(switchImg); return switchSpan; } function createSignatureSwitch(signature, switchStatus = true) { const switchDiv = document.createElement("div"); switchDiv.className = "signature_switch_div"; switchDiv.innerHTML = icon.signature; switchDiv.tabIndex = 0; switchDiv.setAttribute("role", "button"); if (switchStatus) { switchDiv.title = "收起"; } else { switchDiv.title = "展开"; switchDiv.classList.add("signature_switch_close"); signature.classList.add("signature_hide"); } switchDiv.setAttribute("aria-label", switchDiv.title); switchDiv.setAttribute("aria-expanded", String(switchStatus)); switchDiv.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); switchDiv.click(); } }); switchDiv.addEventListener( "click", () => { if (switchStatus) { switchStatus = false; switchDiv.title = "展开"; switchDiv.classList.add("signature_switch_close"); signature.classList.add("signature_hide"); } else { switchStatus = true; switchDiv.title = "收起"; switchDiv.classList.remove("signature_switch_close"); signature.classList.remove("signature_hide"); } switchDiv.setAttribute("aria-label", switchDiv.title); switchDiv.setAttribute("aria-expanded", String(switchStatus)); }, false, ); return switchDiv; } function applyContentBlocking() { if (!GM_config.get("blockUsersEnable")) return; const blockList = GM_config.get("blockUsersList") .split(",") .map((s) => s.trim()); GM_addStyle(style.basic); // 主题列表处理 blockList.forEach((userId) => { // 通过 uid 匹配 (从 space-uid-XXXXX.html 提取) const xpathByUid = `//tbody[contains(@id, 'normalthread')][.//td[contains(@class,'by')]//a[contains(@href, "space-uid-")]]`; const postsByUid = document.evaluate( xpathByUid, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null, ); // 处理 uid 匹配结果 Array.from({ length: postsByUid.snapshotLength }, (_, i) => postsByUid.snapshotItem(i), ).forEach((post) => { const userLinks = post.querySelectorAll('a[href*="space-uid-"]'); for (const link of userLinks) { const href = link.getAttribute("href"); const uidMatch = href.match(/space-uid-(\d+)\.html/); if (uidMatch && uidMatch[1] === userId) { post.innerHTML = `
