디시인사이드에 더 나은 단어 차단 기능을 제공합니다
// ==UserScript==
// @name Better Blocks
// @namespace better-blocks
// @description 디시인사이드에 더 나은 단어 차단 기능을 제공합니다
// @version 0.2.0
// @author Sangha Lee
// @copyright 2024, Sangha Lee
// @license MIT
// @match https://www.dcinside.com/
// @match https://gall.dcinside.com/board/*
// @match https://gall.dcinside.com/mgallery/board/*
// @match https://gall.dcinside.com/mini/board/*
// @match https://gall.dcinside.com/person/board/*
// @icon https://nstatic.dcinside.com/dc/m/img/dcinside_icon.png
// @run-at document-start
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
/**
* @typedef {Object} Nodes
* @property {ElementNode?} $parent
* @property {string} parentSelector
* @property {string} childrenSelector
* @property {bool?} observe
*/
/** @type {Nodes[]} */
const nodes = [
{
// www.dcinside.com: 실시간 베스트 게시글 목록
parentSelector: '#dcbest_list_date',
childrenSelector: 'li'
},
{
// gall.dcinside.com: 게시글 목록
parentSelector: '.gall_list',
childrenSelector: 'tr',
observe: true
},
{
// gall.dcinside.com: 댓글 목록
parentSelector: '.comment_wrap',
childrenSelector: 'li',
observe: true
}
]
const url = new URL(location.href)
const gallery = url.searchParams.get('id')
const configs = {
...GM_getValue('configs'),
...JSON.parse(localStorage.getItem('block_parts') ?? '{}')
}
// 전역 설정 가져오기
configs._ = {
...configs._ ?? {},
...JSON.parse(localStorage.getItem('block_all') ?? '{}')
}
// cross-domain 불가능한 localStorage 설정을 유저스크립트 변수와 동기화하기
GM_setValue('configs', configs)
const config = {
on: 1,
word: '',
id: '',
ip: '',
nick: '',
...(configs._.on === 1 ? configs._ : {}),
...(configs[gallery]?.on === 1 ? configs[gallery] : {})
}
function isNotEmpty (v) {
return v !== ''
}
const blockedWords = config.word.split('||').filter(isNotEmpty)
const blockedIDs = config.ip.split('||').filter(isNotEmpty)
const blockedIPs = config.ip.split('||').filter(isNotEmpty)
const blockedNicknames = config.nick.split('||').filter(isNotEmpty)
function isblockedNode ($node) {
// www.dcinside.com: 실시간 베스트 제목
const $bestTitle = $node.querySelector('.besttxt')
if ($bestTitle && blockedWords.some(v => $bestTitle.textContent.includes(v))) {
return true
}
// gall.dcinside.com: 게시글 및 댓글 제목과 내용
const $content = $node.querySelector('.ub-word')
if ($content && blockedWords.some(v => $content.textContent?.includes(v))) {
return true
}
// gall.dcinside.com: 게시글 및 댓글 작성자 정보
const $author = $node.querySelector('.ub-writer')
if ($author) {
const i = $author.dataset
switch (true) {
case i.uid && blockedIDs.some(v => i.uid.includes(v)):
case i.ip && blockedIPs.some(v => i.ip.includes(v)):
case i.nick && blockedNicknames.some(v => i.nick.includes(v)):
return true
}
}
return false
}
function filterNodes ($nodes) {
$nodes
.filter(isblockedNode)
.map(v => v.classList.add('block-disable'))
}
// 기존 차단 메소드 비활성화
Object.defineProperty(window, 'chk_user_block', {
writable: false,
value: undefined
})
document.addEventListener('DOMContentLoaded', () => {
for (const node of nodes) {
node.$parent = document.querySelector(node.parentSelector)
if (node.$parent === null || node.observe !== true) {
continue
}
new MutationObserver(() => filterNodes([...$parent.querySelectorAll(node.childrenSelector)]))
.observe(node.$parent, { childList: true })
}
filterNodes(
nodes
.map(n => [...n.$parent?.querySelectorAll(n.childrenSelector) ?? []])
.filter(v => v && v.length > 0)
.flat()
)
})