您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allow you to hide or highlight posts by user id on Posts / Popular Posts page.
当前为
// ==UserScript== // @name Kemono QoL Improvements // @namespace Kemono_QoL_Improvements // @license WTFPL // @match https://kemono.cr/* // @match https://coomer.st/* // @grant none // @version 3.4 // @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 getThumbSize() { let defaultSize = 180; let thumbSize = 180; // ==<Copied Code from card_list.tsx>== let viewportWidth = window.innerWidth; let offset = 24; let viewportWidthExcludingMargin = viewportWidth - offset; let howManyFit = viewportWidthExcludingMargin / thumbSize; if (howManyFit < 2.0 && 1.5 < howManyFit) { thumbSize = viewportWidthExcludingMargin / 2; } else if (howManyFit > 12) { thumbSize = defaultSize * 1.5; } // ==</Copied Code from card_list.tsx>== return thumbSize; } function sortPosts() { let sortedPostsHolder = document.getElementById("__usr__sorted_posts_holder"); if (sortedPostsHolder) sortedPostsHolder.remove(); 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]; if (!postsHolder) return; postsHolder.style.display = ""; let posts = postsHolder.getElementsByTagName("article"); if (posts.length === 0) return; let count_posts_blacklisted = 0; let count_posts_no_previews = 0; let blacklistedUsers = []; let sortedPosts = []; Array.from(posts).forEach(originalPost => { let post = originalPost.cloneNode(true); let key = `${post.getAttribute("data-service")}-${post.getAttribute("data-user")}`; if (hide_group.includes(key)) { post.classList.add("__usr_blacklisted"); count_posts_blacklisted++; if (!blacklistedUsers.includes(key)) blacklistedUsers.push(key); } 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); } sortedPosts.push(post); }); sortedPosts = sortedPosts.sort( (a, b) => new Date(b.getElementsByTagName("time")[0].dateTime) - new Date(a.getElementsByTagName("time")[0].dateTime) ); sortedPostsHolder = document.createElement("div"); sortedPostsHolder.id = "__usr__sorted_posts_holder"; sortedPostsHolder.classList.add("card-list__items"); sortedPostsHolder.setAttribute("style", `--card-size:${getThumbSize()}px;`); sortedPosts.forEach(post => { sortedPostsHolder.appendChild(post); }); postsHolder.style.display = "none"; postsHolder.after(sortedPostsHolder); 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 " : "post from "}`; if (blacklistedUsers.length > 1) { blacklistedUsersMessageElement.textContent += `${blacklistedUsers.length} blacklisted users`; } else { blacklistedUsersMessageElement.textContent += "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. const debug_mode = true; let mutationTimeout; let observer = new MutationObserver(mutations => { if (debug_mode) console.log(`[${performance.now()}] == mutations begin: ${window.location.href} ==`); 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") { clearTimeout(mutationTimeout); mutations.forEach(mutation => { if (debug_mode) console.log(`[${performance.now()}] mutation target: ${mutation.target} classList ${mutation.target.classList}`); }); mutationTimeout = setTimeout(() => { observer.disconnect(); if (debug_mode) console.log(`[${performance.now()}] == sort begin: ${window.location.href} ==`); sortPosts(); if (debug_mode) console.log(`[${performance.now()}] == sort end: ${window.location.href} ==`); observer.observe(document.body, { childList: true, subtree: true }); }, 100); } if (debug_mode) console.log(`[${performance.now()}] == mutations end: ${window.location.href} ==`); }); observer.observe(document.body, { childList: true, subtree: true }); // ==<Adjust Thumbnail Size on Window Resize>== window.addEventListener("resize", () => { let sortedPostsHolder = document.getElementById("__usr__sorted_posts_holder"); if (!sortedPostsHolder) return; sortedPostsHolder.setAttribute("style", `--card-size:${getThumbSize()}px;`); }); // ==</Adjust Thumbnail Size on Window Resize>== // ==</Main>== })();