Allow you to hide or highlight posts by user id on Posts / Popular Posts page.
Tính đến
// ==UserScript==
// @name Kemono QoL Improvements
// @namespace Kemono_QoL_Improvements
// @license WTFPL
// @match https://kemono.cr/*
// @match https://coomer.st/*
// @grant none
// @version 2.2
// @author Kaban
// @description Allow you to hide or highlight posts by user id on Posts / Popular Posts page.
// @description Allow you to hide posts with no preview images on Posts / Popular Posts page.
// @description Auto-sorts posts by Post time on Posts / Popular Posts page (within current page).
// @description Makes post thumbnails taller, allow in-post images to be tiled horizontally.
// ==/UserScript==
(() => {
// ==<User Settings>==
const hide_group = [
["fanbox", "00000000"], // [UserName] I recommend you to take note here as comments, so that
["patreon", "000000000"] // [UserName] you don't forget why you added them to the blacklist.
];
const highlight_group_1 = [ // Red Background
["patreon", "00000000"], // [UserName] If you clear this list: const highlight_group_1 = [];
["patreon", "000000000"] // [UserName] Then this group will be skipped.
]
const highlight_group_2 = [ // Green Background
["patreon", "00000000"], // [UserName] You may also add more groups as you like, just modify the code under
["patreon", "000000000"] // [UserName] section <CSS Generation> as well, I tried to make it easy to read.
];
const option_hide_posts_with_no_preview_images = true;
// ==</User Settings>==
// ==<CSS Generation>==
let styles = [];
let selectors = [];
hide_group.forEach(user => {
selectors.push(`article[data-service="${user[0]}"][data-user="${user[1]}"]`);
});
const selector_blacklisted_users = selectors.join(",\n"); // used later for counting how many posts hidden
const css_posts_page_blacklisted_users = `${selector_blacklisted_users} {
display: none;
}`;
styles.push(`${selector_blacklisted_users} {
opacity: 0.5;
}`); // make the hidden posts remain semi-transparent when click on the message to show them
selectors = [];
highlight_group_1.forEach(user => {
selectors.push(`article[data-service="${user[0]}"][data-user="${user[1]}"] header.post-card__header`);
selectors.push(`article[data-service="${user[0]}"][data-user="${user[1]}"] footer.post-card__footer`);
});
styles.push(`${selectors.join(",\n")} {
background: rgb(153 0 0 / 50%);
}`);
selectors = [];
highlight_group_2.forEach(user => {
selectors.push(`article[data-service="${user[0]}"][data-user="${user[1]}"] header.post-card__header`);
selectors.push(`article[data-service="${user[0]}"][data-user="${user[1]}"] footer.post-card__footer`);
});
styles.push(`${selectors.join(",\n")} {
background: rgb(24 153 0 / 50%);
}`);
styles.push(`article.post-card {
height: calc(var(--card-size) / 2 * 3);
}
img.post-card__image {
object-fit: contain;
}`); // make posts taller on Posts / Popular Posts page
const selector_no_preview_images = "article.post-card--preview.post-card:not(:has(div.post-card__image-container))"; // used later for counting how many posts hidden
const css_posts_page_hide_no_preview_images = `${selector_no_preview_images} {
display: none;
}`;
styles.push(`${selector_no_preview_images} {
height: fit-content;
}`); // make posts with no preview images shorter, so the take less space when shown
const css_posts_page = styles.join("\n");
const css_user_page = `article.post-card {
height: calc(var(--card-size) / 2 * 3);
}
img.post-card__image {
object-fit: contain;
}`; // just make the posts taller, don't hide any posts on User page
const css_user_page_in_post = `div.post__files {
flex-flow: wrap;
}`; // allow images to tile horizontally on single Post page
// ==</CSS Generation>==
let old_url = "";
function addStyles() {
if (window.location.href === old_url) return; // run only once per url change
old_url = window.location.href;
let styleElement = document.getElementById("__usr__page_style");
if (styleElement) styleElement.remove();
styleElement = document.createElement("style");
styleElement.id = "__usr__page_style";
if (window.location.pathname === "/posts" || window.location.pathname === "/posts/popular") {
styleElement.textContent = css_posts_page;
document.head.append(styleElement);
styleElement = document.getElementById("__usr__page_style_hide_blacklisted_users");
if (styleElement) styleElement.remove();
styleElement = document.createElement("style");
styleElement.id = "__usr__page_style_hide_blacklisted_users";
styleElement.textContent = css_posts_page_blacklisted_users;
document.head.append(styleElement);
if (!option_hide_posts_with_no_preview_images) return;
styleElement = document.getElementById("__usr__page_style_hide_no_preview_images");
if (styleElement) styleElement.remove();
styleElement = document.createElement("style");
styleElement.id = "__usr__page_style_hide_no_preview_images";
styleElement.textContent = css_posts_page_hide_no_preview_images;
document.head.append(styleElement);
} else if (window.location.pathname.includes("/user/")) {
if (window.location.pathname.includes("/post/")) {
styleElement.textContent = css_user_page_in_post;
} else {
styleElement.textContent = css_user_page;
}
document.head.append(styleElement);
}
}
function sortPosts() {
let sortMessageElement = document.getElementById("__usr__sort_message");
if (sortMessageElement) sortMessageElement.remove();
let hidPostsMessageElement = document.getElementById("__usr__hid_posts_message");
if (hidPostsMessageElement) hidPostsMessageElement.remove();
sortMessageElement = document.createElement("small");
sortMessageElement.id = "__usr__sort_message";
hidPostsMessageElement = document.createElement("div");
hidPostsMessageElement.id = "__usr__hid_posts_message";
let postsHolder = document.getElementsByClassName("card-list__items")[0];
let posts = postsHolder.getElementsByTagName("article");
if (posts.length === 0) return;
if (posts.length > 1) {
// ==<Sort Posts>==
let sortedPosts = Array.from(posts).sort( (a, b) => new Date(b.getElementsByTagName("time")[0].dateTime) - new Date(a.getElementsByTagName("time")[0].dateTime) );
postsHolder.replaceChildren();
sortedPosts.forEach(post => {
postsHolder.appendChild(post);
});
// ==</Sort Posts>==
}
let insertPosition = document.getElementById("paginator-top").getElementsByTagName("small")[0];
if (insertPosition) {
sortMessageElement.textContent = " posts (sorted by Post time).";
insertPosition.after(sortMessageElement);
} else {
sortMessageElement.textContent = `Found ${posts.length} ${posts.length > 1 ? "posts (sorted by Post time)." : "post."}`;
document.getElementById("paginator-top").appendChild(sortMessageElement);
}
sortMessageElement.after(hidPostsMessageElement);
// ==<Count Hidden Posts>==
let hidPostsMessages = [];
let hid_posts = document.querySelectorAll(selector_blacklisted_users);
if (hid_posts.length > 0) {
let blacklistedUsersMessageElement = document.createElement("small");
blacklistedUsersMessageElement.id = "__usr__hid_posts_message_blacklisted_users";
blacklistedUsersMessageElement.addEventListener('click', function() { showPostsFromBlacklistedUsers(); });
blacklistedUsersMessageElement.textContent = `${hid_posts.length} ${hid_posts.length > 1 ? "posts from blacklisted users" : "post from a blacklisted user"}`;
hidPostsMessages.push(blacklistedUsersMessageElement);
}
if (option_hide_posts_with_no_preview_images) {
hid_posts = document.querySelectorAll(selector_no_preview_images);
if (hid_posts.length > 0) {
let noPreviewImagesMessageElement = document.createElement("small");
noPreviewImagesMessageElement.id = "__usr__hid_posts_message_no_preview_images";
noPreviewImagesMessageElement.addEventListener('click', function() { showPostsWithNoPreviewImages(); });
noPreviewImagesMessageElement.textContent = `${hid_posts.length} ${hid_posts.length > 1 ? "posts with no preview images" : "post with no preview image"}`;
hidPostsMessages.push(noPreviewImagesMessageElement);
}
}
if (hidPostsMessages.length > 0) {
let prefix = document.createElement("small");
prefix.textContent = "Hid ";
hidPostsMessageElement.appendChild(prefix)
hidPostsMessageElement.appendChild(hidPostsMessages[0]);
if (hidPostsMessages.length > 1) {
let separator = document.createElement("small");
separator.id = "__usr__hid_posts_message_separator";
separator.textContent = ", ";
hidPostsMessageElement.appendChild(separator);
hidPostsMessageElement.appendChild(hidPostsMessages[1]);
}
let suffix = document.createElement("small");
suffix.textContent = ".";
hidPostsMessageElement.appendChild(suffix)
}
// ==</Count Hidden Posts>==
}
// Kemono is a SPA (Single Page Application)
// It is necessary to use MutationObserver in order to run scripts when switching between pages
let observer = new MutationObserver(mutations => {
addStyles(); // this will fire multiple times as the page loads, but I want the styles to be added as early as possible so keep it here
if (window.location.pathname === "/posts" || window.location.pathname === "/posts/popular") {
let pageLoaded = false;
mutations.forEach(mutation => {
if (mutation.target.classList.contains("ad-container")) pageLoaded = true; // div.ad-container is the last mutations observerd on page update
});
if (pageLoaded) {
observer.disconnect();
sortPosts();
observer.observe(document.body, { childList: true, subtree: true });
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
function showPostsFromBlacklistedUsers() {
document.getElementById("__usr__page_style_hide_blacklisted_users").remove();
if (document.getElementById("__usr__hid_posts_message_separator")) {
document.getElementById("__usr__hid_posts_message_separator").remove();
document.getElementById("__usr__hid_posts_message_blacklisted_users").remove()
} else {
document.getElementById("__usr__hid_posts_message").remove();
}
}
function showPostsWithNoPreviewImages() {
document.getElementById("__usr__page_style_hide_no_preview_images").remove();
if (document.getElementById("__usr__hid_posts_message_separator")) {
document.getElementById("__usr__hid_posts_message_separator").remove();
document.getElementById("__usr__hid_posts_message_no_preview_images").remove()
} else {
document.getElementById("__usr__hid_posts_message").remove();
}
}
})();