您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
已經厭倦看到一堆不感興趣的大樓? 這就是你需要的
当前为
// ==UserScript== // @name 場外大樓過濾器 // @description 已經厭倦看到一堆不感興趣的大樓? 這就是你需要的 // @namespace nathan60107 // @author nathan60107(貝果) // @version 1.1.0 // @homepage https://home.gamer.com.tw/creationCategory.php?owner=nathan60107&c=425332 // @match https://forum.gamer.com.tw/B.php?*bsn=60076* // @icon https://www.google.com/s2/favicons?sz=64&domain=gamer.com.tw // @require https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js // @require https://code.jquery.com/jquery-3.6.3.min.js // @grant GM_setValue // @grant GM_getValue // @run-at document-start // @noframes // ==/UserScript== let $ = jQuery let dd = (...d) => { if (window.BAHAID && window.BAHAID !== 'nathan60107') return d.forEach((it) => { console.log(it) }) } /** * @param {boolean} bool */ function isCheck(bool) { return bool ? 'checked' : '' } /** * @param {string} condi * @param {string} curr */ function isSelected(condi, curr) { return condi === curr ? 'selected' : '' } /** * @param {string|undefined} a * @param {string} b */ function versionCompare(a, b) { if (!a) return -1 return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }) } /** * @param {number} reply * @param {number} like */ function userCondition(reply, like) { if (!setting.filterReply && !setting.filterLike) { return false } else if (!setting.filterLike) { return reply > setting.replyLimit } else if (!setting.filterReply) { return like > setting.likeLimit } else { return [ reply > setting.replyLimit, like > setting.likeLimit, ].reduce( setting.likeCondition === 'or' ? (a, b) => a || b : (a, b) => a && b ) } } /** @param {{title: string, snA: string}[]} whitelist */ function whitelistToString(whitelist) { return whitelist.map(wl => `<li> - <a href="https://forum.gamer.com.tw/C.php?bsn=60076&snA=${wl.snA}">${wl.title}</a> <button data-snA="${wl.snA}">刪除</button> </li>` ).join('') } /** * @type {defaultSetting} */ let setting = {} const defaultSetting = { dataVersion: GM_info.script.version, enableLoadMore: true, enableFilter: true, filterReply: true, replyLimit: 1000, filterLike: false, likeCondition: 'and', likeLimit: 1000, /** @type {{title: string, snA: string}[]} */ whitelist: [] } const settingKey = Object.keys(defaultSetting) let postSet = new Set() let removeCount = 0 let resetCoundown = -1 let url = new URLSearchParams(window.location.href) let pageIndex = +(url.get('page') ?? 1) function initSettingPanel() { for (const key of settingKey) { setting[key] = GM_getValue(key) if (setting[key] === undefined) { setting[key] = defaultSetting[key] GM_setValue(key, defaultSetting[key]) } } if (versionCompare(setting.dataVersion, '1.1.0') < 0) { resetSetting(false) toastr.info('由於版本更新,設定已重置,若持續出現此通知請回報錯誤。') } if (setting.enableLoadMore) $('.b-popular, .b-pager').hide() $('.BH-qabox1').after(` <style> #no-building, #no-building .setting-building, #no-building ul { display: flex; flex-direction: column; gap: 10px; } #no-building { padding: 18px 12px; } #no-building .setting-building { padding-left: 10px; } #no-building label { display: block; } #no-building input[type="number"] { width: 4em; } #no-building button, #no-building select { width: max-content; } #no-building .setting-building select { margin-left: 45px; } #no-building input::-webkit-outer-spin-button, #no-building input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } #no-building input[type=number] { -moz-appearance: textfield; } </style> <h5>大樓過濾設定</h5> <div id="no-building" class="BH-rbox"></div> `) updateSettingPanel() } function updateSettingPanel() { $('#no-building').html(` <div class="version">腳本版本:${GM_info.script.version}</div> <div class="page">目前頁數:${pageIndex}</div> <div class="info">已過濾大樓: ${removeCount} 棟</div> <label> <input ${isCheck(setting.enableLoadMore)} data-key="enableLoadMore" type="checkbox" > 啟用載入更多內容 </label> <label> <input ${isCheck(setting.enableFilter)} data-key="enableFilter" type="checkbox" > 啟用大樓過濾 </label> 大樓過濾條件: <div class="setting-building"> <label> <input ${isCheck(setting.filterReply)} data-key="filterReply" type="checkbox"> 回覆超過<input value="${setting.replyLimit}" data-key="replyLimit" type="number">樓 </label> <select data-key="likeCondition"> <option value="and" ${isSelected(setting.likeCondition, 'and')}>且</option> <option value="or" ${isSelected(setting.likeCondition, 'or')}>或</option> </select> <label> <input ${isCheck(setting.filterLike)} data-key="filterLike" type="checkbox"> 推文超過<input value="${setting.likeLimit}" data-key="likeLimit" type="number">次 </label> 白名單文章: <ul> ${whitelistToString(setting.whitelist)} </ul> <input class="setting-whitelist-input" placeholder="在此處貼上文章網址" type="text"> <button class="setting-add-whitelist" type="button">新增白名單</button> </div> <button class="setting-reset" type="button">${resetCoundown === -1 ? '重置設定' : `確定? ${resetCoundown}`}</button> `) $('#no-building .setting-reset').on('click', confirmReset) $('#no-building .setting-add-whitelist').on('click', () => modifyWhitelist('add')) for (const { snA } of setting.whitelist) { $(`[data-snA="${snA}"]`).on('click', () => modifyWhitelist(snA)) } for (const [key, val] of Object.entries(defaultSetting)) { if (Array.isArray(val)) continue let targetKey = typeof val === 'boolean' ? 'checked' : 'value' $(`#no-building [data-key="${key}"]`).on('change', function (e) { setting[key] = e.target[targetKey] if (typeof val === 'number') setting[key] = parseInt(setting[key]) saveSetting() }) } } function saveSetting() { for (const key of settingKey) { GM_setValue(key, setting[key]) } toastr.info('大樓過濾設定已更新') } function confirmReset() { // First click of reset setting, countdown if (resetCoundown === -1) { resetCoundown = 10 updateSettingPanel() let itv = setInterval(() => { resetCoundown-- updateSettingPanel() if (resetCoundown === -1) clearInterval(itv) }, 1000) // Second click, reset setting and reload } else { resetSetting() } } function resetSetting(reload = true) { for (const key of settingKey) { GM_setValue(key, defaultSetting[key]) } if (reload) location.reload() } /** * If action is add, add url in input to whitelist. * Or, delete whiltelist item that snA == action * @param {'add'|string} action */ function modifyWhitelist(action) { if (action === 'add') { let tempList = _.cloneDeep(setting.whitelist) let input = $('.setting-whitelist-input').val().match(/snA=(\d+)/) if (!input) return let snA = input[1] $.ajax({ url: `https://forum.gamer.com.tw/C.php?bsn=60076&snA=${snA}` }).done((resHTML) => { let title = $(resHTML).filter('title').text() .replace(' @場外休憩區 哈啦板 - 巴哈姆特', '') .replace(/^【.*】/, '') tempList.push({ title, snA }) if (_.isEqual(setting.whitelist, tempList)) { toastr.warning('白名單已存在') } else { setting.whitelist = _.uniqBy(tempList, 'snA') updateSettingPanel() saveSetting() } }) } else { _.pullAllBy(setting.whitelist, [{ snA: action }], 'snA') updateSettingPanel() saveSetting() } } /** * @param {HTMLElement} html * @param {'append'|'remove'} mode */ function processHtml(html, mode) { let snA = $(html).find('.b-list__time__edittime > a').attr('href').match(/snA=([\d]+)/)[1] let title = $(html).find('.b-list__main__title').text() let top = $(html).hasClass('b-list__row--sticky') let like = $(html).find('.b-list__summary__gp').text() let replyRaw = $(html).find('.b-list__time__edittime > a').attr('href').match(/tnum=([0-9]+)&/) let reply = replyRaw ? parseInt(replyRaw[1]) - 1 : 0 let inWhitelist = _.filter(setting.whitelist, ['snA', snA]).length !== 0 // Duplicated, skip it if (postSet.has(snA)) return // Should be filtered if (setting.enableFilter && !top && !inWhitelist && userCondition(reply, like)) { if (mode === 'remove') $(html).remove() removeCount++ // Should be kept } else { if (mode === 'append') $('.b-list > tbody').append(html) } postSet.add(snA) } function htmlToPost() { $('.b-list__row:not(:has(.b-list_ad))').map( function f() { processHtml(this, 'remove') } ) updateSettingPanel() } /** * @type {IntersectionObserverCallback} */ function loadMore(entries) { if (!setting.enableLoadMore || entries.every((val) => !val.isIntersecting)) return pageIndex++ url.set('page', pageIndex) $.ajax({ url: decodeURIComponent(url.toString()) }).done((resHTML) => { $(resHTML).find('.b-list__row:not(:has(.b-list_ad))').map( function f() { processHtml(this, 'append') } ) updateSettingPanel() // Register lazyload img and draw non-image thumbnail Forum.B.lazyThumbnail() Forum.Common.drawNoImageCanvas() }) } function initIntersectionObserver() { let observer = new IntersectionObserver(loadMore) observer.observe($('#BH-footer')[0]) } (function () { document.addEventListener("DOMContentLoaded", function () { initIntersectionObserver() initSettingPanel() htmlToPost() }) })(); /** * Reference: * [JSDoc](https://ricostacruz.com/til/typescript-jsdoc) */