// ==UserScript== // @name barrage-keywords-stop // @namespace https://github.com/wuxin0011/tampermonkey-script/barrage-keywords-stop // @version 0.0.1 // @author wuxin0011 // @description 抖音、斗鱼、虎牙、bilibili弹幕关键字屏蔽,按下 ctrl+alt+k 即可激活🧨 // @license MIT // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABIFJREFUWEe1l11oHFUUx//nzm6tgQa/GpudrSE0YgI+VAR9EE0LftGgxA9adDNbS7UPsTuJPkSwARVaQaE2mU0jpJbUmSw+iKVIG6lGaqQPGqiCPrSR2pBmZ1ut1opWa7J7j9zZ3Xaz2c1uku3A7jzMvef/u+eec+49hAU8HfbU/RL0CIGaQGgCvN9vAC54b8aIIDm2bPryiXe3Nv5VjmkqNWj7YKJRaNwO4AkQVpUan/5OvzBkTGMx1BMOnJxvzrwApuO+CUCJr/DMEg+D6ZhkTAqfb3K5zz85jctacsbXIJgbmPlOgBsY9CgB1QCSYI5a4eDrxSCKApiOOwLggawwS22/Fa4dLscDnbFf16bkzBsEPJ6dL5OyK/rCHT/nzy8IYNrxOIhuVfsqQJEeI3CoHOH8MRE7YQrCTgb7AZwUTKH8LZkDYDruKQB1YP4Bmmi3QoETixHPnWPabgyEpxWEJpLP7gnVncl+nwVwbSAfnhGpbe+H6v6omDjjYBqCP7WM4KY5AJmAew3ghGTfw33hVROVFLfCeijixC8R6AZm3hUNB3d68aH+vFTz8dfpaKeIZQQ+qLS4p+MkWgX4I09YUGtvKHDUAzBt1wLhJQCHLEN/7nqIZ22aTvwAQJvA2GeFddMDiNjuWSKsJJbresOrv10KwNU4YhxUbs+3paopk/gKoIRlBNZQRyzxEEs+CrBrGcGG6yme44XTAOmSuYUidrybiHagCHG5QKVWXjAtmXdTTo62W4Y+WK5gYYOF3Z5v03TcLQD61aLJdNzv1KlGgh7rDQVUJizoWcjKs4az286M7xXAv5m0WDDAYsSV1rW4wyUFMAXgtoV6YLHiuQDMuLCoLViK+CwA8DEF8DmAB8G8zQoHnVIBsFTxdOGLGyAaANCnsqALhLcADFmGrqph0acS4h6A4+4D0MbgTup0ptZJiM8ATFqG3lhMvVLiGQDvyBestVDX/lMrriyr/hHg21mI5miodmxO3mbP8yUWK6/sx87dR1KOAhizDL05fRY48V0EepWZh6Ph4DNLKTIlY8hx1WnYCokua7Me9QA67USTJFYr9xFxd29bcHc6WDI3mQqsPO36xIsAR0G4mJzx39u/peb81RuRacffBtErYMxYYb260uLb7fP1gpIjAAWYMBBt0zu8Apjrso6h+CfMtEHVaO/6VKGVdw5O3JTS/ANEpHqL48ll/qf6N9b8PQcgcuDsGtK0j72Op1LisfjaVIr6iXAPgAkN/if3GDWnswufcyvOxEMs03ZNE3Goty14uFRwFfqurmAascWMlQCmoaHFel4/nju2YF/wcixRp0nuVsUi7Sb6MCnle3s3B38qB8S0z20gkdrqbScAJoxqLKI9Ru2R/Pnzt2bpkrnD6xOA38EYBXiCmc4wY3z6PxqvqrkxmbryT70mqT6VQlAI2ZwVBjAOyD7LWF30kluyOVXeECkZEUTNDNxdjgcY/A0YX1Ytr4q+s/GWP+ebUxJgVpY47l1MtB7M6wHcPGsvCReZ5UhKaF/sDQUmywFVY/4HmMRRtLE+F8gAAAAASUVORK5CYII= // @source https://github.com/wuxin0011/tampermonkey-script // @supportURL https://github.com/wuxin0011/tampermonkey-script/issues // @match https://www.huya.com/* // @match https://live.douyin.com/* // @match https://live.bilibili.com/* // @match https://www.douyu.com/* // @downloadURL none // ==/UserScript== (function () { if (typeof window === undefined) { return; } /********************************************************************common************************************************************************************************ */ const selectKeywordsLocal = 'selectKeywordsLocal' // 当前网站全部保存的关键词 const isNoShowTipKey = 'tip_isNoShowTipKey' // 是否不需要提示 默认是 const isFisrtInstallKey = 'isFisrtInstallKey' // 是否是第一次安装 默认是第一次 const isFirstAlertKey = 'isFirstAlertKey' // 错误内容第一次给出提示 默认是 const selectOnlyThieRoom = 'selectOnlyThieRoom' // 当前房间号的关键词 const defaultKeywords = ['送出了', '6666', '直播间'] // 默认关键词 const localLink = window.location.href const isDouYinLive = /https?:\/\/live\.douyin.*/.test(localLink) const isHyLive = /https?:\/\/www\.huya\.com.+/.test(localLink) const isDouyuLive = /https?:\/\/.*douyu.*(\/((.*rid=\d+)|(\d+)).*)$/.test(localLink) const isBiliBiliLive = /https?:\/\/live\.bilibili.*/.test(localLink) const isLocalHost = /127\..*/.test(localLink) const noop = () => { } const setItem = (k, v, isParse = false) => window.localStorage.setItem(k, isParse ? JSON.stringify(v) : v) const getItem = (k, isParse = false) => isParse ? JSON.parse(window.localStorage.getItem(k)) : window.localStorage.getItem(k) const isFisrtInstall = () => getItem(isFisrtInstallKey) == null || getItem(isFisrtInstallKey) !== isFisrtInstallKey const isNoShowTip = () => getItem(isNoShowTipKey) == null || getItem(isNoShowTipKey) !== isNoShowTipKey const isFirstAlert = () => getItem(isFirstAlertKey) == null || getItem(isFirstAlertKey) !== isFirstAlertKey const selectKeywords = () => isFisrtInstall() || getItem(selectKeywordsLocal) == null ? defaultKeywords : getItem(selectKeywordsLocal, true) const createRoomId = (id) => id ? `${selectOnlyThieRoom}_${id}` : `${selectOnlyThieRoom}_${localLink}` const getRoomId = () => { try { if (isBiliBiliLive) { return localLink.match(/https:\/\/live\.bilibili\..*\/(\d+).*/) ? localLink.match(/https:\/\/.*\.bilibili\..*\/(\d+).*/)[1] : localLink } else if (isDouYinLive) { return localLink.match(/https:\/\/live\.douyin\..*\/(\d+).*/) ? localLink.match(/https:\/\/.*\.douyin\..*\/(\d+).*/)[1] : localLink } else if (isHyLive) { return localLink.match(/https:\/\/www\.huya\.com\/(.*)/) ? localLink.match(/https:\/\/www\.huya\.com\/(.*)/)[1] : localLink } else if (isDouyuLive) { if (/.*rid=(\d+).*/.test(localLink)) { return localLink.match(/rid=(\d+)/)[1] } if (/https:\/\/www\.douyu\.com\/(\d+).*/.test(url)) { return localLink.match(/https:\/\/www\.douyu\.com\/(\d+)/)[1] } } } catch (error) { } return localLink } const roomId = () => createRoomId(getRoomId()) const selectOnlyThisRoomsKeywords = () => getItem(roomId()) == null ? defaultKeywords : getItem(roomId(), true) let nodeVersion = 0 let beforeTag = null const MARK = "dm-mark-version" const MARK_TAG = (t = 0) => `mark-version-${t}` let keywordsCache = [] let isInit = false let isStart = false let tagInitSuccess = true let isAllRooms = false const isPrintStop = true // 是否禁止控制台输出弹幕 /******************************************************************************************************************************************************************** */ // 弹幕容器 let BARRAGE_CONTAINER = null const BARRAGE_TYPE = { ALL_BARRAGE: 'ALL_BARRAGE', } const removeDom = (dom, r = false) => { if (dom && dom instanceof HTMLElement) { window.requestAnimationFrame(() => { // dom.style.opacity = '0' dom.style.display = 'none' if (r) { dom.remove() } }) } } const contains = (text) => { if (!text) { return false } for (let index = 0; index < keywordsCache.length; index++) { if (keywordsCache[index] && (text.indexOf(keywordsCache[index]) !== -1)) { // if (!isPrintStop) { // console.error('\n\n==============================stop=====================================') // console.error(`禁止`, text, ' keywords: ', keywordsCache[index]) // } return true } } return false } // 比较节点版本信息 const checkVersionIsSame = (node) => node.getAttribute(MARK) == MARK_TAG(nodeVersion) // 弹幕处理 let findBarrages = () => { const findTargetText = (container) => { if (!container) { return; } const nodes = document.querySelectorAll(container) for (let index = 0; index < nodes.length; index++) { const node = nodes[index] if (node) { // mark // check if (!checkVersionIsSame(node)) { if (contains(node?.textContent)) { removeDom(node, true) } else { // update node new version node.setAttribute(MARK, MARK_TAG(nodeVersion)) } } } } } // 遍历节点 for (let i = 0; i < BARRAGE_CONTAINER.length; i++) { findTargetText(BARRAGE_CONTAINER[i]) } window.requestAnimationFrame(findBarrages) } const SUPPORT = { HY: 'HY_LIVE', DOUYIN: 'DOUYIN_LIVE', DOUYU: 'DOUYU_LIVE', BILIBILI: 'BILIBILI_LIVE', LOCALHOST: 'LOCALHOST_LIVE' } const TAG_TYPE = { [SUPPORT.DOUYIN]: { [BARRAGE_TYPE.ALL_BARRAGE]: ['.xgplayer-danmu>div', '.webcast-chatroom___item.webcast-chatroom___enter-done', '.xgplayer-danmu div'] }, [SUPPORT.HY]: { [BARRAGE_TYPE.ALL_BARRAGE]: ['#player-video #danmuwrap #danmudiv .danmu-item', '#player-video #danmuwrap #danmudiv #danmudiv2', '#player-marquee-wrap .player-marquee-noble-item', '#player-marquee-wrap .player-banner-enter', '#chat-room__list>div'] }, [SUPPORT.BILIBILI]: { [BARRAGE_TYPE.ALL_BARRAGE]: ['.web-player-danmaku .danmaku-item-container .bili-dm', '#chat-items .chat-item'] }, [SUPPORT.DOUYU]: { [BARRAGE_TYPE.ALL_BARRAGE]: ['#douyu_room_normal_player_danmuDom .ani-broadcast', '#js-barrage-container #js-barrage-list li'] } } const installBeforeInfo = () => { console.log('欢迎使用弹幕屏蔽插件...') console.log('是否是首次安装', isFisrtInstall() ? "是" : "否") console.log('是否不需要快捷键提示', isNoShowTip() ? "需要" : "不需要") } const addStyle = (str) => { if (isInit) { return; } const head = document.querySelector("head"); const style = document.createElement("style"); style.innerText = str; head.appendChild(style); isInit = true }; const keywordsUpdate = (array) => { if (!Array.isArray(array)) { array = [] } isAllRooms ? setItem(selectKeywordsLocal, array, true) : setItem(roomId(), array, true) // 通知改变 之前被标记标签如果没被处理将失效 notify() } const removeKeywords = (text) => { if (!Array.isArray(keywordsCache)) { return; } const index = keywordsCache.findIndex(t => t == text) if (index >= 0) { keywordsCache.splice(index, 1) keywordsUpdate([...keywordsCache]) } } const createKeywords = (text) => { if (!Array.isArray(keywordsCache)) { keywordsCache = [] } const index = keywordsCache.findIndex(t => t == text) if (index === -1) { keywordsCache = [text, ...keywordsCache] keywordsUpdate(keywordsCache) } } const containerStr = `
确认
房间
清空
× 反馈
` const style = ` .m-dm-container { --dm-container-width: 450px; --dm-container-height: 300px; --dm-container-background-color: 30, 23, 37; --dm-font-color: #fff; --dm-font-color-hover: #000; --dm-background-color: 0, 0, 0; --dm-background-color-hover: #fff; --dm-border-color: #fff; --dm-border-color-hover: #000; } .m-dm-container { width: var(--dm-container-width) !important; height: var(--dm-container-height) !important; background-color: rgba(var(--dm-container-background-color), 1) !important; position: fixed !important; display: flex !important; flex-direction: column !important; box-sizing: border-box !important; box-shadow: 2px 2px 10px rgba(var(--dm-background-color), 0.7) !important; border-radius: 10px !important; position: fixed !important; right: 0 !important; top: 100px !important; border: none !important; transition: transform ease-in-out 0.5s !important; z-index: 999999 !important; box-sizing: border-box !important; padding: 10px !important; } .m-dy-input-add-keywords { width: 150px !important; padding: 6px 12px !important; border: none !important; outline: none !important; margin-left: 10px !important; margin-top: 10px !important; border-radius: 10px !important; } .m-dy-input-add-keywords:focus { border: none !important; outline: none !important; } .m-dm-install-link { color: var(--dm-font-color) !important; } .m-dm-container-header, .m-dm-container-footer { height: 44px !important; position: relative !important; } .m-dm-container-header #m-dm-close-btn { float:right !important; right: 3px !important; color: var(--dm-font-color) !important; font-size: 30px !important; cursor: pointer !important; position: absolute !important; } .m-dm-container-body { flex: 1 !important; overflow: auto !important; } .m-dm-keywords-tag { display: inline-block !important; padding: 5px !important; background-color: var(--dm-background-color) !important; border: none !important; outline: none !important; margin: 5px !important; cursor: pointer !important; color: var(--dm-font-color) !important; font-size: 12px !important; border: 1px solid var(--dm-border-color) !important; border-radius: 10px !important; } .m-dm-keywords-tag:hover { background: rgba(var(--dm-background-color), 0.7) !important; border: 1px solid var(--dm-border-hover-color) !important; } .m-dm-all-keywords-button, .m-dm-delete-keywords-button, .m-dm-add-keywords-button { display: inline-block !important; padding: 4px 8px !important; text-align: center !important; border: none !important; outline: none !important; background-color: var(--dm-background-color-hover) !important; color: var(--dm-font-color-hover) !important; cursor: pointer !important; border: 1px solid var(--dm-border-color) !important; border-radius: 10px !important; } .m-dm-all-keywords-button:hover, .m-dm-delete-keywords-button:hover, .m-dm-add-keywords-button:hover { background-color: rgb(var(--dm-background-color)) !important; color: var(--dm-font-color) !important; border: 1px solid var(--dm-border-color) !important; } .m-dm-container-footer { box-sizing: border-box !important; padding: 10px !important; } .m-dm-container-footer p { color: var(--dm-font-color) !important; cursor: pointer !important; } .m-dm-ani-close { transform: translateX(var(--dm-container-width)) !important; } .m-dm-container-body { overflow: auto !important; -webkit-overflow-scrolling: touch !important; scrollbar-width: thin !important; scrollbar-color: #888888 #f0f0f0 !important; -webkit-overflow-scrolling: touch !important; scrollbar-width: none !important; -ms-overflow-style: none !important; } .m-dm-container-body::-webkit-scrollbar { width: 4px !important; } .m-dm-container-body::-webkit-scrollbar-track { background-color: rgb(22, 24, 35) !important; } .m-dm-container-body::-webkit-scrollbar-thumb { background-color: #333 !important; border-radius: 4px !important; } ` // 初始化之前将本地房间号和全网房间全部关键词收集 const initKeywords = () => { keywordsCache = [] if (Array.isArray(selectOnlyThisRoomsKeywords())) { keywordsCache = [...new Set(selectOnlyThisRoomsKeywords())] } if (Array.isArray(selectKeywords())) { keywordsCache = [...new Set(keywordsCache, selectKeywords())] } console.log('重新启动标签扫描=>', keywordsCache) } // notify ! const notify = () => { window.console.clear() window.cancelAnimationFrame(findBarrages) nodeVersion = nodeVersion + 2 initKeywords() // init keywords if (Array.isArray(keywordsCache) && keywordsCache.length > 0) { findBarrages() // run ! } } const addOperationEvent = () => { const dmContainer = document.querySelector('.m-dm-container') if (!dmContainer) { console.error('获取不到弹幕容器') return; } const dmInput = dmContainer.querySelector('.m-dy-input-add-keywords') const dmBody = dmContainer.querySelector('.m-dm-container-body') const dmAddButton = dmContainer.querySelector('.m-dm-add-keywords-button') const dmChangeButton = dmContainer.querySelector('.m-dm-all-keywords-button') const dmCloseButton = dmContainer.querySelector('#m-dm-close-btn') const dmDeleteButton = dmContainer.querySelector('.m-dm-delete-keywords-button') if (!dmInput || !dmAddButton || !dmBody) { console.log('element has null') return; } const find = (text) => keywordsCache.find((t) => t == text) const add = () => { if (!dmInput.value) { alert('请输入关键字') return; } if (find(dmInput.value)) { if (isFirstAlert()) { setItem(isFirstAlertKey, isFirstAlertKey) alert('关键字已重复') } else { dmInput.value = '' } return; } createTag(dmBody, dmInput.value) createKeywords(dmInput.value) setItem(isFisrtInstallKey, isFisrtInstallKey) dmInput.value = '' } // enter dmInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { add() } }) // click dmAddButton.addEventListener('click', () => { add() }) // click dmCloseButton.addEventListener('click', () => { if (dmContainer.classList.contains('m-dm-ani-close')) { dmContainer.classList.remove('m-dm-ani-close') } else { dmContainer.classList.add('m-dm-ani-close') } }) // click dmChangeButton.addEventListener('click', () => { isAllRooms = !isAllRooms createTags() dmChangeButton.textContent = isAllRooms ? '全房间' : '房间' dmChangeButton.title = isAllRooms ? '当前弹幕在所有直播间生效,点击切换房间' : '当前弹幕仅在该房间生效,点击切换到全房间' }) // c dmDeleteButton.addEventListener('click', () => { if (confirm('确认清空?')) { removeTags() keywordsCache = [] setItem(isAllRooms ? selectKeywordsLocal : roomId(), keywordsCache, true) notify() // window.cancelAnimationFrame(findBarrages) } }) console.log('响应事件监听完毕...') findBarrages() } const addShowEvent = () => { const dmContainer = document.querySelector('.m-dm-container') document.addEventListener('keydown', function (event) { if (event.ctrlKey && event.altKey && event.key === 'k') { if (dmContainer.classList.contains('m-dm-ani-close')) { dmContainer.classList.remove('m-dm-ani-close') setItem(isFisrtInstallKey, isFisrtInstallKey) } else { dmContainer.classList.add('m-dm-ani-close') } } }); console.log('快捷键监听完毕... ctrl + alt + k') } const createTag = (dmBody, text) => { if (!dmBody) { dmBody = document.querySelector('.m-dm-container .m-dm-container-body') } if (!text) { console.log('create tag fail ') return; } const dmTag = document.createElement('span') dmTag.className = 'm-dm-keywords-tag' dmTag.textContent = `${text}` dmTag.title = `点击移除关键字: ${text}` dmTag.addEventListener('click', () => { removeKeywords(text) dmTag.remove() }) !!beforeTag ? dmBody.appendChild(dmTag) : dmBody.insertBefore(dmTag, beforeTag); // update before beforeTag = dmTag } const removeTags = () => { const allTags = document.querySelectorAll('.m-dm-container .m-dm-container-body .m-dm-keywords-tag') if (allTags && allTags.length > 0) { for (let i = 0; i < allTags.length; i++) { removeDom(allTags[i], true) } } } const createTags = () => { // console.log('标签创建中....') const dmBody = document.querySelector('.m-dm-container .m-dm-container-body') removeTags() const keys = isAllRooms ? selectKeywords() : [...selectOnlyThisRoomsKeywords(roomId())] if (!Array.isArray(keys)) { // console.log('标签创建失败....') return; } for (let i = 0; i < keys.length; i++) { createTag(dmBody, keys[i]) } initKeywords() console.log('标签创建完毕....') } const tipClick = () => { const tip = document.querySelector('.m-dm-container-footer p') tip.addEventListener('click', () => { setItem(isNoShowTipKey, isNoShowTipKey) tip.style.display = 'none' }) } const createContainer = () => { installBeforeInfo() const div = document.createElement('div') div.className = `m-dm-container ${isFisrtInstall() ? '' : 'm-dm-ani-close'} ` div.innerHTML = containerStr const tip = div.querySelector('.m-dm-container-footer p') tip.style.display = isNoShowTip() ? 'none' : 'block' document.querySelector('body').append(div) console.log('弹幕容器创建完毕....') } const addOperation = () => { const dmContainer = document.querySelector('.m-dm-container') if (!dmContainer) { console.log('未找到弹幕容器... ') return; } // 添加基本事件监听 addShowEvent() addOperationEvent() createTags() tipClick() } const initDom = () => { // 首次安装默认出来 if (isFisrtInstall()) { setTimeout(() => { createContainer() addOperation() }, 5000) } else { createContainer() addOperation() } } const initTag = (type) => { if (!TAG_TYPE[type]) { tagInitSuccess = false return } BARRAGE_CONTAINER = TAG_TYPE[type][BARRAGE_TYPE.ALL_BARRAGE] tagInitSuccess = !!BARRAGE_CONTAINER && Array.isArray(BARRAGE_CONTAINER) && BARRAGE_CONTAINER.length > 0 } const start = () => { if (isStart) { return; } let isSupport = true console.log('弹幕插件执行中...') if (isDouYinLive) { initTag(SUPPORT.DOUYIN) } else if (isHyLive) { initTag(SUPPORT.HY) } else if (isBiliBiliLive) { initTag(SUPPORT.BILIBILI) } else if (isDouyuLive) { initTag(SUPPORT.DOUYU) } else if (isLocalHost) { isSupport = true } else { isSupport = false } if (!tagInitSuccess) { console.log('标签初始化失败!') return; } if (isSupport) { addStyle(`${style}`) initDom() } else { console.log('对不起不支持当前网址!', localLink) } isStart = true } start() })()