제미니랑 챗지피티를 써서 해당 기능을 구현한 Tampermonkey 스크립트를 만들었습니다. 저만 쓰기 아까워 공유합니다.
모바일 브라우저는 Mises 앱을 쓰면 Tampermonkey를 비롯한 크롬 확장 프로그램들 쓸 수 있습니다.
최신 정보, 알구몬 랭킹, 댓글들 화면에 적용됩니다.
많이 샀어요는 구UI라 그런지 여러번 수정해도 적용이 잘 안돼서 포기했습니다.
// ==UserScript==
// @name 알구몬 키워드 필터
// @namespace local.algumon.keyword.filter
// @version 0.3
// @match https://www.algumon.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
‘use strict’;
const DEBUG = true;
const SETTINGS_URL = '/n/settings/ignored-keywords';
const STORAGE_KEY = 'algumon_ignored_keywords_cache_v03';
function log(...args) {
if (DEBUG) console.log('[AlgumonKeywordFilter]', ...args);
}
function normalize(text) {
return String(text || '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function saveCache(keywords) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(keywords));
} catch (e) {
log('cache save failed', e);
}
}
function loadCache() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
log('cache load failed', e);
return [];
}
}
async function getKeywords() {
try {
const res = await fetch(SETTINGS_URL, {
credentials: 'include',
cache: 'no-store'
});
const html = await res.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const keywords = [];
for (const el of doc.querySelectorAll('input, textarea, div, span, p, li')) {
const text = (el.value || el.getAttribute?.('value') || el.textContent || '').trim();
if (!text) continue;
if (text.length < 2 || text.length > 40) continue;
if (text.includes('\n')) continue;
if (!/[0-9A-Za-z가-힣]/.test(text)) continue;
if ([
'설정',
'추가',
'키워드 추가',
'예외 키워드 관리',
'예외 키워드 등록 팁',
'로그아웃'
].includes(text)) continue;
keywords.push(normalize(text));
}
const unique = [...new Set(keywords)];
if (unique.length) {
saveCache(unique);
log('keywords loaded', unique);
return unique;
}
throw new Error('no keywords found');
} catch (e) {
const cached = loadCache();
log('using cache', cached, e);
return cached;
}
}
function shouldHide(title, keywords) {
const t = normalize(title);
return keywords.some(k => t.includes(k));
}
function hideCard(card) {
card.style.setProperty('display', 'none', 'important');
}
function showCard(card) {
card.style.removeProperty('display');
}
function filterDealPage(keywords) {
const cards = document.querySelectorAll('[id^="deal-"]');
cards.forEach(card => {
const titleEl =
card.querySelector('h3') ||
card.querySelector('a[href*="/deal/"]') ||
card.querySelector('a[href*="/n/deal/"]') ||
card.querySelector('.card-title') ||
card.querySelector('.title') ||
card.querySelector('.subject');
const title = titleEl ? titleEl.textContent.trim() : '';
if (!title) return;
if (shouldHide(title, keywords)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function filterBoughtTodayPage(keywords) {
const cards = document.querySelectorAll(‘[id^=“post-”]’);
cards.forEach(card => {
const candidates = [
card.querySelector('h3'),
card.querySelector('h4'),
card.querySelector('.title'),
card.querySelector('.subject'),
card.querySelector('.product'),
card.querySelector('a[href*="/deal/"]'),
card.querySelector('a[href*="/post/"]'),
card.querySelector('a')
].filter(Boolean);
let title = '';
for (const el of candidates) {
const text = (el.textContent || '').trim();
if (text.length >= 2) {
title = text;
break;
}
}
if (!title) {
const allText = (card.innerText || '')
.split('\n')
.map(v => v.trim())
.filter(Boolean);
title = allText.find(v =>
v.length >= 4 &&
!v.includes('사고싶다') &&
!v.includes('샀어요') &&
!v.includes('댓글') &&
!v.includes('공유') &&
!/^\d+[,.]?\d*원/.test(v) &&
!/배송/.test(v)
) || '';
}
if (!title) return;
if (shouldHide(title, keywords)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function filterCommentPage(keywords) {
const cards = document.querySelectorAll('[id^="comment-"]');
cards.forEach(card => {
const titleEl =
card.querySelector('a[href*="/deal/"]') ||
card.querySelector('a[href*="/n/deal/"]') ||
card.querySelector('h3') ||
card.querySelector('.title') ||
card.querySelector('.subject');
const title = titleEl ? titleEl.textContent.trim() : '';
if (!title) return;
if (shouldHide(title, keywords)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function applyFilter(keywords) {
if (!keywords.length) return;
const path = location.pathname;
if (path.startsWith('/n/comment')) {
filterCommentPage(keywords);
return;
}
if (path.startsWith('/deal/bought-today')) {
filterBoughtTodayPage(keywords);
return;
}
if (path.startsWith('/n/deal')) {
filterDealPage(keywords);
return;
}
}
async function run() {
const keywords = await getKeywords();
const observer = new MutationObserver(() => {
applyFilter(keywords);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
applyFilter(keywords);
}
run();
})();
제 댓글 몇몇도 필터링 돼서 안 보이길래 예외 처리 하고, 글 작성자 쪽에 ‘바이럴’ 메모 해놨다면 필터링 되게 수정했습니다. 메모가 아닌 작성자 이름에는 적용 안 됩니다.
바이럴 말고 다른 문구도 필터링 하려면 [‘바이럴’] 에서 [‘바이럴’, ‘광고’] 이런 식으로 추가하면 됩니다.
// ==UserScript==
// @name 알구몬 키워드 필터
// @namespace local.algumon.keyword.filter
// @version 0.3
// @match https://www.algumon.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
‘use strict’;
const DEBUG = true;
const SETTINGS_URL = '/n/settings/ignored-keywords';
const STORAGE_KEY = 'algumon_ignored_keywords_cache_v03';
// 작성자 메모 필터링
const HARD_FILTER_WORDS = ['바이럴'];
function log(...args) {
if (DEBUG) console.log('[AlgumonKeywordFilter]', ...args);
}
function normalize(text) {
return String(text || '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function hasHardFilter(card) {
const memoEl = card.querySelector('.truncate.min-w-0');
if (!memoEl) return false;
const text = (memoEl.innerText || '').toLowerCase();
return HARD_FILTER_WORDS.some(word => text.includes(word));
}
function saveCache(keywords) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(keywords));
} catch (e) {
log('cache save failed', e);
}
}
function loadCache() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
log('cache load failed', e);
return [];
}
}
async function getKeywords() {
try {
const res = await fetch(SETTINGS_URL, {
credentials: 'include',
cache: 'no-store'
});
const html = await res.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const keywords = [];
for (const el of doc.querySelectorAll('input, textarea, div, span, p, li')) {
const text = (el.value || el.getAttribute?.('value') || el.textContent || '').trim();
if (!text) continue;
if (text.length < 2 || text.length > 40) continue;
if (text.includes('\n')) continue;
if (!/[0-9A-Za-z가-힣]/.test(text)) continue;
if ([
'설정',
'추가',
'키워드 추가',
'예외 키워드 관리',
'예외 키워드 등록 팁',
'로그아웃'
].includes(text)) continue;
keywords.push(normalize(text));
}
const unique = [...new Set(keywords)];
if (unique.length) {
saveCache(unique);
log('keywords loaded', unique);
return unique;
}
throw new Error('no keywords found');
} catch (e) {
const cached = loadCache();
log('using cache', cached, e);
return cached;
}
}
function shouldHide(title, keywords) {
const t = normalize(title);
return keywords.some(k => t.includes(k));
}
function hideCard(card) {
card.style.setProperty('display', 'none', 'important');
}
function showCard(card) {
card.style.removeProperty('display');
}
function filterDealPage(keywords) {
const cards = document.querySelectorAll('[id^="deal-"]');
cards.forEach(card => {
const titleEl =
card.querySelector('h3') ||
card.querySelector('a[href*="/deal/"]') ||
card.querySelector('a[href*="/n/deal/"]') ||
card.querySelector('.card-title') ||
card.querySelector('.title') ||
card.querySelector('.subject');
const title = titleEl ? titleEl.textContent.trim() : '';
if (!title) return;
if (shouldHide(title, keywords) || hasHardFilter(card)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function filterBoughtTodayPage(keywords) {
const cards = document.querySelectorAll(‘[id^=“post-”]’);
cards.forEach(card => {
const candidates = [
card.querySelector('h3'),
card.querySelector('h4'),
card.querySelector('.title'),
card.querySelector('.subject'),
card.querySelector('.product'),
card.querySelector('a[href*="/deal/"]'),
card.querySelector('a[href*="/post/"]'),
card.querySelector('a')
].filter(Boolean);
let title = '';
for (const el of candidates) {
const text = (el.textContent || '').trim();
if (text.length >= 2) {
title = text;
break;
}
}
if (!title) {
const allText = (card.innerText || '')
.split('\n')
.map(v => v.trim())
.filter(Boolean);
title = allText.find(v =>
v.length >= 4 &&
!v.includes('사고싶다') &&
!v.includes('샀어요') &&
!v.includes('댓글') &&
!v.includes('공유') &&
!/^\d+[,.]?\d*원/.test(v) &&
!/배송/.test(v)
) || '';
}
if (!title) return;
if (shouldHide(title, keywords)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function filterCommentPage(keywords) {
const cards = document.querySelectorAll('[id^="comment-"]');
cards.forEach(card => {
const titleEl =
card.querySelector('a[href*="/deal/"]') ||
card.querySelector('a[href*="/n/deal/"]') ||
card.querySelector('h3') ||
card.querySelector('.title') ||
card.querySelector('.subject');
const title = titleEl ? titleEl.textContent.trim() : '';
if (!title) return;
if (shouldHide(title, keywords)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function applyFilter(keywords) {
if (location.href.startsWith('https://www.algumon.com/n/comment?userId=')) {
return;
}
if (!keywords.length) return;
const path = location.pathname;
if (path.startsWith('/n/comment')) {
filterCommentPage(keywords);
return;
}
if (path.startsWith('/deal/bought-today')) {
filterBoughtTodayPage(keywords);
return;
}
if (path.startsWith('/n/deal')) {
filterDealPage(keywords);
return;
}
}
async function run() {
const keywords = await getKeywords();
const observer = new MutationObserver(() => {
applyFilter(keywords);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
applyFilter(keywords);
}
run();
})();
자기 댓글 목록 뿐만 아니라 그냥 모든 댓글 목록은 필터링 안되게 조금 수정했습니다. 관심 분야가 아니더라도 유용할 수도 있는 댓글들이 필터링 되어버려서 계속 끄게 되어가지고.
그리고 사이트 필터링 기능도 추가했습니다. 예시로 ‘psn’, ‘미니맵’ 적어놨는데 적용할 땐 지우시면 됩니다.
// ==UserScript==
// @name 알구몬 키워드 필터
// @namespace local.algumon.keyword.filter
// @version 0.3
// @match https://www.algumon.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
‘use strict’;
const DEBUG = true;
const SETTINGS_URL = '/n/settings/ignored-keywords';
const STORAGE_KEY = 'algumon_ignored_keywords_cache_v03';
// 작성자 메모 필터링
const HARD_FILTER_WORDS = ['바이럴'];
// 사이트 필터링 (영어는 소문자로 입력해야 함)
const HARD_FILTER_BADGES = ['psn', '미니맵'];
function log(...args) {
if (DEBUG) console.log('[AlgumonKeywordFilter]', ...args);
}
function normalize(text) {
return String(text || '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function hasHardFilter(card) {
const memoEl = card.querySelector('.truncate.min-w-0');
if (!memoEl) return false;
const text = (memoEl.innerText || '').toLowerCase();
return HARD_FILTER_WORDS.some(word => text.includes(word));
}
function hasBadgeFilter(card) {
const badges = card.querySelectorAll('.badge, [class*="badge"]');
if (!badges.length) return false;
for (const el of badges) {
const text = (el.textContent || '').toLowerCase();
if (HARD_FILTER_BADGES.some(word => text.includes(word))) {
return true;
}
}
return false;
}
function saveCache(keywords) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(keywords));
} catch (e) {
log('cache save failed', e);
}
}
function loadCache() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
log('cache load failed', e);
return [];
}
}
async function getKeywords() {
try {
const res = await fetch(SETTINGS_URL, {
credentials: 'include',
cache: 'no-store'
});
const html = await res.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const keywords = [];
for (const el of doc.querySelectorAll('input, textarea, div, span, p, li')) {
const text = (el.value || el.getAttribute?.('value') || el.textContent || '').trim();
if (!text) continue;
if (text.length < 2 || text.length > 40) continue;
if (text.includes('\n')) continue;
if (!/[0-9A-Za-z가-힣]/.test(text)) continue;
if ([
'설정',
'추가',
'키워드 추가',
'예외 키워드 관리',
'예외 키워드 등록 팁',
'로그아웃'
].includes(text)) continue;
keywords.push(normalize(text));
}
const unique = [...new Set(keywords)];
if (unique.length) {
saveCache(unique);
log('keywords loaded', unique);
return unique;
}
throw new Error('no keywords found');
} catch (e) {
const cached = loadCache();
log('using cache', cached, e);
return cached;
}
}
function shouldHide(title, keywords) {
const t = normalize(title);
return keywords.some(k => t.includes(k));
}
function hideCard(card) {
card.style.setProperty('display', 'none', 'important');
}
function showCard(card) {
card.style.removeProperty('display');
}
function filterDealPage(keywords) {
const cards = document.querySelectorAll('[id^="deal-"]');
cards.forEach(card => {
const titleEl =
card.querySelector('h3') ||
card.querySelector('a[href*="/deal/"]') ||
card.querySelector('a[href*="/n/deal/"]') ||
card.querySelector('.card-title') ||
card.querySelector('.title') ||
card.querySelector('.subject');
const title = titleEl ? titleEl.textContent.trim() : '';
if (!title) return;
if (shouldHide(title, keywords) || hasHardFilter(card) || hasBadgeFilter(card)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function filterBoughtTodayPage(keywords) {
const cards = document.querySelectorAll(‘[id^=“post-”]’);
cards.forEach(card => {
const candidates = [
card.querySelector('h3'),
card.querySelector('h4'),
card.querySelector('.title'),
card.querySelector('.subject'),
card.querySelector('.product'),
card.querySelector('a[href*="/deal/"]'),
card.querySelector('a[href*="/post/"]'),
card.querySelector('a')
].filter(Boolean);
let title = '';
for (const el of candidates) {
const text = (el.textContent || '').trim();
if (text.length >= 2) {
title = text;
break;
}
}
if (!title) {
const allText = (card.innerText || '')
.split('\n')
.map(v => v.trim())
.filter(Boolean);
title = allText.find(v =>
v.length >= 4 &&
!v.includes('사고싶다') &&
!v.includes('샀어요') &&
!v.includes('댓글') &&
!v.includes('공유') &&
!/^\d+[,.]?\d*원/.test(v) &&
!/배송/.test(v)
) || '';
}
if (!title) return;
if (shouldHide(title, keywords) || hasBadgeFilter(card)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function filterCommentPage(keywords) {
const cards = document.querySelectorAll('[id^="comment-"]');
cards.forEach(card => {
const titleEl =
card.querySelector('a[href*="/deal/"]') ||
card.querySelector('a[href*="/n/deal/"]') ||
card.querySelector('h3') ||
card.querySelector('.title') ||
card.querySelector('.subject');
const title = titleEl ? titleEl.textContent.trim() : '';
if (!title) return;
if (shouldHide(title, keywords) || hasBadgeFilter(card)) {
hideCard(card);
} else {
showCard(card);
}
});
}
function applyFilter(keywords) {
if (location.href.startsWith('https://www.algumon.com/n/comment')) {
return;
}
if (!keywords.length && !HARD_FILTER_BADGES.length) return;
const path = location.pathname;
if (path.startsWith('/n/comment')) {
filterCommentPage(keywords);
return;
}
if (path.startsWith('/deal/bought-today')) {
filterBoughtTodayPage(keywords);
return;
}
if (path.startsWith('/n/deal')) {
filterDealPage(keywords);
return;
}
}
async function run() {
const keywords = await getKeywords();
const observer = new MutationObserver(() => {
applyFilter(keywords);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
applyFilter(keywords);
}
run();
})();