Allow you to hide or highlight posts by user id on Posts / Popular Posts page.
La data de
// ==UserScript==
// @name Kemono QoL Improvements
// @namespace Kemono_QoL_Improvements
// @license WTFPL
// @match https://kemono.cr/*
// @match https://coomer.st/*
// @grant none
// @version 3.1
// @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 option_hide_posts_with_no_preview = true;
const hide_group = [
"fanbox-000000000", // [UserName] I recommend you to add comments like this,
"patreon-000000000", // [UserName] so you don't forget why you added them to the blacklist.
];
const highlight_group_1 = [ // Highlight posts from these users (red background)
"fanbox-000000000", // [UserName] I use this to mark creators who have their contents
"patreon-000000000", // [UserName] stored on external services like MEGA / Google Drive.
];
const highlight_group_2 = [ // Highlight posts from these users (green background)
"fanbox-000000000", // [UserName] You can leave this list empty (highlight_group_2 = [];)
"patreon-000000000", // [UserName] if you don't need this feature.
];
const custom_header = new Map([ // Display a custom line below the post title
["fanbox-000000000", "Custom Name"], // You can use this to display the user's changed name,
["patreon-000000000", "Custom Tags"], // or anything you want like tags. Leave empty to disable.
]);
/* Here's a helper bookmarklet that converts your favorite users into the format of the "custom_header" above:
javascript:(function()%7Blet%20output%3D%5B%5D%3BJSON.parse(document.getElementsByTagName('textarea')%5B0%5D.textContent).artists.forEach(artist%3D%3E%7Blet%20key%3D%60%24%7Bartist.service%7D-%24%7Bartist.id%7D%60%3Blet%20line%3D%60%5B%22%24%7Bkey.trim()%7D%22%2C%60%3Boutput.push(%60%20%20%24%7Bline.padEnd(21%2C%20'%20')%7D%20%22%24%7Bartist.name.trim()%7D%22%5D%2C%60)%7D)%3Bnavigator.clipboard.writeText(output.join('%5Cn'))%7D)()%3B
1. Save the entire line as a bookmark and put it on the bookmark bar
2. Go to Export Favorites page (https://kemono.cr/account/favorites/export)
3. Click on "Export Favorites", wait for it to finish (you'll see a wall of text in a textbox)
4. Click on the bookmarklet to run the script, the output will be copied to the clipboard
*/
// ==<User Settings>==
// ==<CSS>==
// Custom classes (__usr_*) are added to the <article> elements with functions below,
// so we won't have long and complicate css selectors as user lists above grows.
const css_posts_page = `img.post-card__image { object-fit: contain; }
article.post-card { height: calc(var(--card-size) / 2 * 3); }
article.__usr_blacklisted { opacity: 0.5; }
article.__usr_no_previews { height: fit-content; }
article.__usr_highlight_group_1 header, article.__usr_highlight_group_1 footer { background: rgb(153 0 0 / 50%) !important; }
article.__usr_highlight_group_2 header, article.__usr_highlight_group_2 footer { background: rgb(24 153 0 / 50%) !important; }
article header > div { text-align: right; }`;
const css_posts_page_blacklisted = `article.__usr_blacklisted { display: none; }`;
const css_posts_page_no_previews = `article.__usr_no_previews { display: none; }`;
// Don't hide or highlight posts on user page
const css_user_page = `img.post-card__image { object-fit: contain; }
article.post-card { height: calc(var(--card-size) / 2 * 3); }`;
// Allow images to tile horizontally on single post page
const css_post_page = `div.post__files { flex-flow: wrap; }`;
// ==</CSS>==
// ==<Script Functions>==
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");
if (styleElement) styleElement.remove();
styleElement = document.createElement("style");
styleElement.id = "__usr__page_style_hide_blacklisted";
styleElement.textContent = css_posts_page_blacklisted;
document.head.append(styleElement);
styleElement = document.getElementById("__usr__page_style_hide_no_previews");
if (styleElement) styleElement.remove();
styleElement = document.createElement("style");
styleElement.id = "__usr__page_style_hide_no_previews";
styleElement.textContent = css_posts_page_no_previews;
document.head.append(styleElement);
} else if (window.location.pathname.includes("/user/")) {
if (window.location.pathname.includes("/post/")) {
styleElement.textContent = css_post_page;
} 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();
let postsHolder = document.getElementsByClassName("card-list__items")[0];
let posts = postsHolder.getElementsByTagName("article");
if (posts.length === 0) return;
let count_posts_blacklisted = 0;
let count_posts_no_previews = 0;
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 => {
let key = `${post.getAttribute("data-service")}-${post.getAttribute("data-user")}`;
if (hide_group.includes(key)) {
post.classList.add("__usr_blacklisted");
count_posts_blacklisted++;
}
if (option_hide_posts_with_no_preview && post.getElementsByClassName("post-card__image-container").length === 0) {
post.classList.add("__usr_no_previews");
count_posts_no_previews++;
}
if (highlight_group_1.includes(key)) {
post.classList.add("__usr_highlight_group_1");
} else if (highlight_group_2.includes(key)) {
post.classList.add("__usr_highlight_group_2");
}
if (custom_header.get(key)) {
let creatorElement = document.createElement("div");
creatorElement.textContent = custom_header.get(key);
post.getElementsByTagName("header")[0].appendChild(creatorElement);
}
postsHolder.appendChild(post);
});
sortMessageElement = document.createElement("small");
sortMessageElement.id = "__usr__sort_message";
hidPostsMessageElement = document.createElement("div");
hidPostsMessageElement.id = "__usr__hid_posts_message";
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);
let hidPostsMessages = [];
if (count_posts_blacklisted > 0) {
let blacklistedUsersMessageElement = document.createElement("small");
blacklistedUsersMessageElement.id = "__usr__hid_posts_message_blacklisted";
blacklistedUsersMessageElement.addEventListener('click', function() { showPostsFromBlacklistedUsers(); });
blacklistedUsersMessageElement.textContent = `${count_posts_blacklisted} ${count_posts_blacklisted > 1 ? "posts from blacklisted users" : "post from a blacklisted user"}`;
hidPostsMessages.push(blacklistedUsersMessageElement);
}
if (count_posts_no_previews > 0) {
let noPreviewImagesMessageElement = document.createElement("small");
noPreviewImagesMessageElement.id = "__usr__hid_posts_message_no_previews";
noPreviewImagesMessageElement.addEventListener('click', function() { showPostsWithNoPreviewImages(); });
noPreviewImagesMessageElement.textContent = `${count_posts_no_previews} ${count_posts_no_previews > 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)
}
}
function showPostsFromBlacklistedUsers() {
document.getElementById("__usr__page_style_hide_blacklisted").remove();
observer.disconnect();
if (document.getElementById("__usr__hid_posts_message_separator")) {
document.getElementById("__usr__hid_posts_message_separator").remove();
document.getElementById("__usr__hid_posts_message_blacklisted").remove()
} else {
document.getElementById("__usr__hid_posts_message").remove();
}
observer.observe(document.body, { childList: true, subtree: true });
}
function showPostsWithNoPreviewImages() {
document.getElementById("__usr__page_style_hide_no_previews").remove();
observer.disconnect();
if (document.getElementById("__usr__hid_posts_message_separator")) {
document.getElementById("__usr__hid_posts_message_separator").remove();
document.getElementById("__usr__hid_posts_message_no_previews").remove()
} else {
document.getElementById("__usr__hid_posts_message").remove();
}
observer.observe(document.body, { childList: true, subtree: true });
}
// ==</Script Functions>==
// ==<Main>==
// Kemono is a SPA (Single Page Application).
// We use MutationObserver to (re)run scripts when page content changes.
let mutationTimeout;
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
clearTimeout(mutationTimeout);
if (window.location.pathname === "/posts" || window.location.pathname === "/posts/popular") {
mutationTimeout = setTimeout(() => {
observer.disconnect();
sortPosts();
observer.observe(document.body, { childList: true, subtree: true });
}, 100);
}
});
observer.observe(document.body, { childList: true, subtree: true });
// ==</Main>==
})();