Sleazy Fork is available in English.
skebetterにブラックリスト機能を追加します
// ==UserScript==
// @license MIT
// @name skebetter-blacklist
// @namespace https://skebetter.com/
// @version 1.0.0
// @description skebetterにブラックリスト機能を追加します
// @match https://skebetter.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
// =====================
// オプション設定
// =====================
const HIDE_RETWEET_LIKE = true; // いいね・RTを非表示にする
const HIDE_AUTHOR_PROFILE = true; // 投稿者ページのプロフィールを非表示にする
const HIDE_POST_TEXT = true; // ポストの本文テキストを非表示にする
const KEY_USERS = 'skebetter_users';
const KEY_POSTS = 'skebetter_posts';
function getUsers() {
try { return JSON.parse(GM_getValue(KEY_USERS, '[]')); } catch { return []; }
}
function saveUsers(list) { GM_setValue(KEY_USERS, JSON.stringify(list)); }
function getPosts() {
try { return JSON.parse(GM_getValue(KEY_POSTS, '{}')); } catch { return {}; }
}
function savePosts(map) { GM_setValue(KEY_POSTS, JSON.stringify(map)); }
function hideUser(name, url) {
const list = getUsers();
if (!list.some(u => u.url === url)) {
list.push({ name, url });
saveUsers(list);
}
}
function hidePost(name, authorId, postId) {
const map = getPosts();
const key = `${name} (${authorId})`;
if (!map[key]) map[key] = [];
if (!map[key].includes(postId)) {
map[key].push(postId);
savePosts(map);
}
}
function isUserHidden(url) {
return getUsers().some(u => u.url === url);
}
function isPostHidden(authorId, postId) {
return Object.entries(getPosts()).some(([key, ids]) => {
const m = key.match(/\((\d+)\)$/);
return m && m[1] === authorId && ids.includes(postId);
});
}
function parseAuthorHref(href) {
const m = (href || '').match(/\/author\/(\d+)/);
return m ? m[1] : null;
}
function parsePostHref(href) {
const m = (href || '').match(/\/author\/(\d+)\/[^/]+\/(\d+)/);
return m ? { authorId: m[1], postId: m[2] } : null;
}
function parseTweetId(href) {
const m = (href || '').match(/tweet_id=(\d+)/);
return m ? m[1] : null;
}
function toAbsUrl(href) {
if (!href) return null;
return href.startsWith('http') ? href : 'https://skebetter.com' + href;
}
function findCard(el) {
let node = el.parentElement;
while (node && node !== document.body) {
if (node.classList.contains('rounded-xl') &&
(node.className.includes('bg-white') || node.className.includes('bg-gray-800'))) {
return node;
}
node = node.parentElement;
}
return null;
}
function findOuterWrapper(card) {
let node = card.parentElement;
while (node && node !== document.body) {
const cls = node.className || '';
if (cls.includes('max-w-[') || cls.includes('h-card')) return node;
node = node.parentElement;
}
return null;
}
GM_addStyle(`
.sb-btn-row {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 6px 8px;
}
.sb-hide-btn {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border: 1px solid rgba(128,128,128,0.4);
border-radius: 4px;
background: transparent;
color: #888;
font-size: 11px;
line-height: 1.6;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s, color 0.15s, border-color 0.15s;
user-select: none;
}
.sb-hide-btn:hover {
background: rgba(192,57,43,0.9);
border-color: #c0392b;
color: #fff;
}
/* プロフィール非表示(広すぎる .md:max-w-xl は削除。JS側 hideAuthorProfile() で制御) */
.sb-hide-profile .w-full.md\:max-w-xl.border.rounded-xl.m-2.p-4 {
display: none !important;
}
/* ポスト本文テキスト非表示 */
.sb-hide-post-text [data-sb-card="1"] span.p-2,
.sb-hide-post-text [data-sb-card="1"] span.p-5 {
display: none !important;
}
/* いいね・RT非表示(HIDE_RETWEET_LIKE=true のときCSSで制御) */
.sb-hide-rtlike a[href*="intent/retweet"],
.sb-hide-rtlike a[href*="intent/favorite"],
.sb-hide-rtlike a[href*="intent/retweet"] + span,
.sb-hide-rtlike a[href*="intent/favorite"] + span {
display: none !important;
}
/* カードとouterの固定高さを解除 */
[data-sb-card="1"],
[data-sb-outer="1"] {
height: auto !important;
max-height: none !important;
overflow: visible !important;
}
`);
function processCards() {
document.querySelectorAll('a[href*="/author/"]:not([data-sb-processed])').forEach(link => {
link.setAttribute('data-sb-processed', '1');
const href = link.getAttribute('href');
const authorId = parseAuthorHref(href);
if (!authorId) return;
const card = findCard(link);
if (!card || card.dataset.sbCard) return;
let postId = null;
card.querySelectorAll('a[href*="/author/"]').forEach(a => {
const parsed = parsePostHref(a.getAttribute('href'));
if (parsed && parsed.authorId === authorId) postId = parsed.postId;
});
if (!postId) {
card.querySelectorAll('a[href*="tweet_id="]').forEach(a => {
const tid = parseTweetId(a.getAttribute('href'));
if (tid) postId = 'tweet_' + tid;
});
}
const url = toAbsUrl('/author/' + authorId);
let name = authorId;
card.querySelectorAll(`a[href*="/author/${authorId}"]`).forEach(a => {
const t = a.textContent.trim();
if (t && t.length <= 50 && !a.querySelector('img')) name = t;
});
card.dataset.sbCard = '1';
card.dataset.sbUrl = url;
card.dataset.sbAuthorId = authorId;
if (postId) card.dataset.sbPostId = postId;
const outer = findOuterWrapper(card);
if (outer) outer.dataset.sbOuter = '1';
// ボタンをcard内の2番目の子(テキスト+画像ブロック)の末尾に追加
const contentBlock = card.children[1] || card;
const row = document.createElement('div');
row.className = 'sb-btn-row';
const userBtn = document.createElement('button');
userBtn.className = 'sb-hide-btn';
userBtn.textContent = 'ユーザーを非表示';
userBtn.addEventListener('click', e => {
e.preventDefault(); e.stopPropagation();
hideUser(name, url);
applyHiding();
});
row.appendChild(userBtn);
if (postId) {
const postBtn = document.createElement('button');
postBtn.className = 'sb-hide-btn';
postBtn.textContent = 'このポストを非表示';
postBtn.addEventListener('click', e => {
e.preventDefault(); e.stopPropagation();
hidePost(name, authorId, postId);
applyHiding();
});
row.appendChild(postBtn);
}
contentBlock.appendChild(row);
});
}
function applyHiding() {
// 広告削除
document.querySelectorAll('.grid > *').forEach(el => {
if (el.querySelector('a[href*="al.fanza.co.jp"]')) el.style.display = 'none';
});
document.querySelectorAll('[data-sb-card="1"]').forEach(card => {
const { sbUrl, sbAuthorId, sbPostId } = card.dataset;
if (isUserHidden(sbUrl) || (sbPostId && isPostHidden(sbAuthorId, sbPostId))) {
const outer = findOuterWrapper(card);
(outer || card).style.display = 'none';
}
});
}
let timer = null;
new MutationObserver(() => {
clearTimeout(timer);
timer = setTimeout(() => { hideAuthorProfile(); processCards(); applyHiding(); }, 250);
}).observe(document.body, { childList: true, subtree: true });
function hideAuthorProfile() {
if (!HIDE_AUTHOR_PROFILE) return;
if (!location.pathname.startsWith('/author/')) return;
const main = document.querySelector('main');
if (!main) return;
const children = [...main.children[0]?.children ?? []];
children.forEach(el => {
if ((el.className || '').includes('dark:bg-black')) el.style.display = 'none';
});
}
function init() {
if (HIDE_RETWEET_LIKE) document.documentElement.classList.add('sb-hide-rtlike');
if (HIDE_AUTHOR_PROFILE) document.documentElement.classList.add('sb-hide-profile');
if (HIDE_POST_TEXT) document.documentElement.classList.add('sb-hide-post-text');
hideAuthorProfile();
processCards();
applyHiding();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();