Adds a button at the bottom right of all kemono, coomer & nekohouse supported creator websites that redirects to the corresponding page.
< Părere la script-ul Kemono Browser
I will fix this in a few hours. Thanks for the report.
It seems the extension loads fine, at least for me. Does it still give you errors?
Should have fixed this as of now, in v1.9.16
updated and button working with image
thx a lot for speedy fix
kenomo cr “ Creator not found” error… https://kemono.cr/patreon/user/183864741/post/141117176
Hi, this error has nothing to do with the script, as this creator hasn't yet been uploaded to Kemono. This is indicated by the red color on the button.
I mean there's a ton kemono new post did not show up Latest Cached Updated Artists
Hi, to view those type of posts, you can click on a post on the patreon page like this https://www.patreon.com/posts/keion-zhong-ye-k-141283401 and the button will show green, allowing you to see it. There is nothing I can do about it being red on the main page as the actual creator page doesn't exist.
{
"manifest_version": 3,
"name": "Kemono Patreon Creator Faker",
"description": "Lets you view posts from creators affected by the \"Creator not found\" error. Please note, their profile pages still cannot be viewed.",
"version": "1.0",
"version_name": "v1.0-ManifestV3",
"permissions": [
"webRequest",
"declarativeNetRequestWithHostAccess"
],
"host_permissions": [
"https://kemono.cr/*"
],
"background": {
"service_worker": "service_worker.js"
}
}
async function addRuleThenReloadTab(userId, tabId) {
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [
{
id: ruleId++,
priority: 1,
action: {
type: "redirect",
redirect: {
url: "data:,%7B%7D" // redirect to empty json response "{}"
}
},
condition: {
urlFilter: `|https://kemono.cr/api/v1/patreon/user/${userId}/profile`,
resourceTypes: [ "xmlhttprequest" ]
}
}
]
});
chrome.tabs.reload(tabId);
}
async function removeOldRules() {
let oldRules = await chrome.declarativeNetRequest.getDynamicRules();
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: oldRules.map(rule => rule.id),
});
}
removeOldRules();
let ruleId = 1;
chrome.webRequest.onHeadersReceived.addListener(function(details) {
if (details.statusCode != 404) return;
let url = new URL(details.url);
if (url.pathname.endsWith("/profile")) {
let userId = url.pathname.substring(21).split('/')[0];
addRuleThenReloadTab(userId, details.tabId);
}
},
{ urls: ["https://kemono.cr/api/v1/patreon/user/*"] }
);
{
"manifest_version": 2,
"name": "Kemono Patreon Creator Faker",
"description": "Lets you view posts from creators affected by the \"Creator not found\" error. Please note, their profile pages still cannot be viewed.",
"version": "1.0",
"version_name": "v1.0-ManifestV2",
"permissions": [
"webRequest",
"webRequestBlocking",
"https://kemono.cr/*"
],
"background": {
"scripts": [ "background.js" ],
"persistent": true
}
}
let errorUsers = [];
chrome.webRequest.onBeforeRequest.addListener(function(details) {
let url = new URL(details.url);
if (url.pathname.endsWith("/profile")) {
let userId = url.pathname.substring(21).split('/')[0];
if (errorUsers.includes(userId)) {
return { redirectUrl: "data:,%7B%7D" };
}
}
},
{ urls: ["https://kemono.cr/api/v1/patreon/user/*"] },
[ "blocking" ]
);
chrome.webRequest.onHeadersReceived.addListener(function(details) {
if (details.statusCode != 404) return;
let url = new URL(details.url);
if (url.pathname.endsWith("/profile")) {
let userId = url.pathname.substring(21).split('/')[0];
console.log(`added user ${userId}`);
errorUsers.push(userId);
return { redirectUrl: "data:,%7B%7D" };
}
},
{ urls: ["https://kemono.cr/api/v1/patreon/user/*"] },
[ "blocking" ]
);
How to use:
1. Go to chrome://extensions/
2. Turn on "Developer mode"
3. Click "Load unpacked" and choose a folder:
- The Manifest V2 version works more seamlessly, but does not work on Chrome version 140 and up.
- The Manifest V3 version requires a tab reload (automated) on the first visit.
It is said that this is a repair“ Creator not found” by. JDownloader2
// ==UserScript== // @name Kemono Patcher // @namespace DKKKNND // @license WTFPL // @match https://kemono.cr/* // @match https://coomer.st/* // @run-at document-start // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_setValue // @grant GM_getValue // @version 1.4 // @author Kaban // @description Workaround "Creator not found" error, and more. // @downloadURL https://update.sleazyfork.org/scripts/552123/Kemono%20Patcher.user.js // @updateURL https://update.sleazyfork.org/scripts/552123/Kemono%20Patcher.meta.js // ==/UserScript== (function() { "use strict"; // ==<User Script>== const MISSING_PATREON = JSON.parse(GM_getValue("MISSING_PATREON", "[]")); const RENAME_CREATORS = JSON.parse(GM_getValue("RENAME_CREATORS", "{}")); const PATREON_METADATA_CACHE = JSON.parse(GM_getValue("PATREON_METADATA_CACHE", '{"postIds":[]}')); function onMutation() { updatePageInfo(); observer.disconnect(); switch (pageInfo.pageType) { case "Post Details": updateImportedTime(); break; } observer.observe(document, { childList: true, subtree: true }); } const observer = new MutationObserver(onMutation); observer.observe(document, { childList: true, subtree: true }); let pageInfo = {}; function updatePageInfo() { if (pageInfo.href === window.location.href) return; pageInfo = {}; const pathname = window.location.pathname; const segments = pathname.split('/').filter(segment => segment); switch (segments.length) { case 3: { if (segments[1] === "user") { pageInfo.pageType = "Creator Posts"; const service = segments[0]; const userId = segments[2]; pageInfo.userKey = `${service}-${userId}`; } break; } case 4: { if (segments[1] === "user" && segments[3] === "community") { pageInfo.pageType = "Creator Community"; const service = segments[0]; const userId = segments[2]; pageInfo.userKey = `${service}-${userId}`; } break; } case 5: case 7: { if (segments[1] === "user" && segments[3] === "post" && (segments[5] == undefined || segments[5] === "revision")) { pageInfo.pageType = "Post Details"; const service = segments[0]; const userId = segments[2]; const postId = segments[4]; pageInfo.userKey = `${service}-${userId}`; pageInfo.postKey = `${service}-${userId}-${postId}`; } } } pageInfo.href = window.location.href; updateScriptMenu(); } function updateScriptMenu() { switch (pageInfo.pageType) { case "Creator Posts": case "Post Details": GM_registerMenuCommand("✎ Rename Creator", renameCreator, { id: "renameCreator" }); break; default: GM_unregisterMenuCommand("renameCreator"); } } let postImported = {}; function loadImportedTime(event) { // called from page script if (postImported.restoredKey === event.detail.postKey) return; postImported.postKey = event.detail.postKey; postImported.imported = event.detail.imported; } document.addEventListener("kp-user:load-imported-time", loadImportedTime); function updateImportedTime() { if (postImported.postKey !== pageInfo.postKey) return; if (postImported.imported?.[0] === null) return; // Kemono bug (Pixiv Fanbox) const revisionSelection = document.getElementById("post-revision-selection"); if (revisionSelection) { const revisionOptions = revisionSelection.getElementsByTagName("option"); // switching revision causes text reset, need edit again if (postImported.restoredKey === postImported.postKey && postImported.restoredText === revisionOptions[0].textContent) { return; } for (let i = 0; i < revisionOptions.length; i++) { const date = new Date(postImported.imported[i]); const importedTime = date.toLocaleString("en-CA", { hourCycle: "h23" }); const suffix = revisionOptions[i].textContent.substring(7); revisionOptions[i].textContent = importedTime.replace(',', '') + suffix; } postImported.restoredKey = postImported.postKey; postImported.restoredText = revisionOptions[0].textContent; return; } const revisionSpan = document.querySelector(".post__added span"); if (revisionSpan) { const date = new Date(postImported.imported[0]); const importedTime = date.toLocaleString("en-CA", { hourCycle: "h23" }); revisionSpan.lastChild.textContent = importedTime.replace(',', ''); postImported = { restoredKey: postImported.postKey }; } } let saveTimeout = {}; function debouncedSave(gmKey, object) { clearTimeout(saveTimeout[gmKey]); saveTimeout[gmKey] = setTimeout(() => { // To Do: Make this Multi-Tab Safe GM_setValue(gmKey, JSON.stringify(object)); }, 500); } function renameCreator(event) { if (event.type === "visibilitychange") { if (document.visibilityState !== "visible") return; if (!pageInfo.renameCreatorFlag) return; pageInfo.renameCreatorFlag = null; } if (document.visibilityState === "visible") { const creatorName = document.querySelector(".post__user-name") || document.querySelector(`span[itemprop="name"]`); const userKey = pageInfo.userKey; const name = RENAME_CREATORS[userKey] || creatorName.textContent; const input = prompt(`Enter new name for ${name} (${userKey}):\n(leave empty to reset)`, name); if (input === null || input === name) return; if (input === "") { delete RENAME_CREATORS[userKey]; } else { RENAME_CREATORS[userKey] = input; } debouncedSave("RENAME_CREATORS", RENAME_CREATORS); document.dispatchEvent(new CustomEvent("kp-page:rename-creator", { detail: { userKey: userKey, newName: input } })); creatorName.textContent = input || userKey; } else { if (!pageInfo.renameCreatorFlag) pageInfo.renameCreatorFlag = true; // mobile workaround } } document.addEventListener("visibilitychange", renameCreator); function addMissingPatreon(event) { // called from page script const userId = event.detail.userId; MISSING_PATREON.push(userId); debouncedSave("MISSING_PATREON", MISSING_PATREON); } document.addEventListener("kp-user:add-missing-patreon", addMissingPatreon); function addPatreonCache(event) { // called from page script const postId = event.detail.postId; PATREON_METADATA_CACHE.postIds.push(postId); const userId = event.detail.userId; if (!PATREON_METADATA_CACHE[userId]) PATREON_METADATA_CACHE[userId] = []; const postJson = event.detail.postJson; const postMetadata = { id: postJson.id, user: postJson.user, service: "patreon", title: postJson.title, published: postJson.published, file: { path: postJson.file.path }, attachments: '~'.repeat(postJson.attachments.length) }; PATREON_METADATA_CACHE[userId].push(postMetadata); debouncedSave("PATREON_METADATA_CACHE", PATREON_METADATA_CACHE); } document.addEventListener("kp-user:add-patreon-cache", addPatreonCache); function purgePatreonCache(event) { // called from page script const userId = event.detail.userId; if (PATREON_METADATA_CACHE[userId]) { delete PATREON_METADATA_CACHE[userId]; debouncedSave("PATREON_METADATA_CACHE", PATREON_METADATA_CACHE); } } document.addEventListener("kp-user:purge-patreon-cache", purgePatreonCache); // ==</User Script>== // ==<Main>== const injectScript = document.createElement("script"); injectScript.textContent = `(${patchFetch})();`; document.documentElement.appendChild(injectScript); document.dispatchEvent(new CustomEvent("kp-page:load-data", { detail: { missingPatreon: MISSING_PATREON, renameCreators: RENAME_CREATORS, cachedPatreonPosts: PATREON_METADATA_CACHE } })); injectScript.remove(); // ==</Main>== // ==<Injected Function>== function patchFetch() { let MISSING_PATREON; let RENAME_CREATORS; let PATREON_METADATA_CACHE; let PATREON_METADATA_CACHE_POST_IDS; function loadData(event) { // called from user script MISSING_PATREON = new Set(event.detail.missingPatreon); RENAME_CREATORS = event.detail.renameCreators; PATREON_METADATA_CACHE = event.detail.cachedPatreonPosts; PATREON_METADATA_CACHE_POST_IDS = new Set(PATREON_METADATA_CACHE.postIds); } document.addEventListener("kp-page:load-data", loadData); function renameCreator(event) { // called from user script const userKey = event.detail.userKey; const newName = event.detail.newName; if (newName === "") { delete RENAME_CREATORS[userKey]; } else { RENAME_CREATORS[userKey] = newName; } } document.addEventListener("kp-page:rename-creator", renameCreator); function addMissingPatreon(userId) { if (!MISSING_PATREON.has(userId)) { MISSING_PATREON.add(userId); document.dispatchEvent(new CustomEvent("kp-user:add-missing-patreon", { detail: { userId: userId } })); } } const FAKE_PATREON_PROFILE = function(userId) { const userKey = `patreon-${userId}`; const name = RENAME_CREATORS[userKey] || userKey; const postCount = PATREON_METADATA_CACHE[userId]?.length || 0; const response = { id: userId, name: name, has_chats: true, post_count: postCount, service: "patreon" }; return new Response(JSON.stringify(response)); }; const FAKE_PATREON_POSTS = function(userId, offset) { offset = parseInt(offset) || 0; const cachedPosts = PATREON_METADATA_CACHE[userId] || []; return new Response(JSON.stringify(cachedPosts.slice(offset, offset + 50))); }; const nativeFetch = window.fetch.bind(window); const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const silent429Fetch = async function (input, init) { try { const response = await nativeFetch(input, init); if (response.status === 429) { const MAX_RETRIES = 3; let attempt = 0; let delay = 500; while (attempt < MAX_RETRIES) { attempt++; console.log(`HTTP 429: ${window.location.href}\nRetry (attempt ${attempt}/${MAX_RETRIES}) in ${delay} ms...`); await sleep(delay); delay += 500; // backoff for next retry const response = await nativeFetch(input, init); if (response.ok || response.status !== 429 || attempt === MAX_RETRIES) { return response; } } } return response; } catch (error) { throw error; } }; window.fetch = async function(input, init) { let url; if (input instanceof Request) { url = new URL(input.url); } else if (typeof input === "string") { try { url = new URL(input, location.origin); } catch (error) { return nativeFetch(input, init); } } else { return nativeFetch(input, init); } if (!url.pathname.startsWith("/api/v1/")) { return nativeFetch(input, init); } switch (url.pathname) { case "/api/v1/posts": case "/api/v1/posts/popular": { return silent429Fetch(input, init); } } const segments = url.pathname.split('/').filter(segment => segment); if (segments.length < 6 || segments[3] !== "user") { return nativeFetch(input, init); } const service = segments[2]; const userId = segments[4]; const apiName = segments[5]; switch (apiName) { case "profile": { if (segments.length !== 6) { return nativeFetch(input, init); } if (service === "patreon" && MISSING_PATREON.has(userId)) { return FAKE_PATREON_PROFILE(userId); } try { const response = await nativeFetch(input, init); if (response.ok) { document.dispatchEvent(new CustomEvent("kp-user:purge-patreon-cache", { detail: { userId: userId } })); const newName = RENAME_CREATORS[`${service}-${userId}`]; if (newName) { const responseJSON = await response.json(); responseJSON.name = newName; return new Response(JSON.stringify(responseJSON), { status: response.status, headers: response.headers } ); } } else if (response.status === 404 && service === "patreon") { addMissingPatreon(userId); return FAKE_PATREON_PROFILE(userId); } return response; } catch (error) { return nativeFetch(input, init); } } case "posts": { if (segments.length !== 6) { return nativeFetch(input, init); } const offset = new URLSearchParams(url.search).get("o"); if (service === "patreon" && MISSING_PATREON.has(userId)) { return FAKE_PATREON_POSTS(userId, offset); } try { const response = await silent429Fetch(input, init); if (response.status === 404 && service === "patreon") { return FAKE_PATREON_POSTS(userId, offset); } return response; } catch (error) { return nativeFetch(input, init); } } case "post": { if (!(segments.length === 7 || (segments.length === 9 && segments[7] === "revision"))) { return nativeFetch(input, init); } const postId = segments[6]; try { const response = await nativeFetch(input, init); if (response.ok) { const responseJSON = await response.json(); const imported = []; const revisions = responseJSON.props.revisions; for (const revision of responseJSON.props.revisions) { imported.push(revision[1].added); // second element is post object } // Kemono front end cuts off imported date, send raw data to user script document.dispatchEvent(new CustomEvent("kp-user:load-imported-time", { detail: { postKey: `${service}-${userId}-${postId}`, imported: imported } })); // To Do: make a "white list" for creators do exist so no need for caching if (service === "patreon" && !PATREON_METADATA_CACHE_POST_IDS.has(postId)) { document.dispatchEvent(new CustomEvent("kp-user:add-patreon-cache", { detail: { userId: userId, postId: postId, postJson: responseJSON["post"] } })); PATREON_METADATA_CACHE_POST_IDS.add(postId); } return new Response(JSON.stringify(responseJSON), { status: response.status, headers: response.headers } ); } } catch (error) { return nativeFetch(input, init); } } } return nativeFetch(input, init); }; } // ==</Injected Function>== })();
Hi, I get that this script fixes the seemingly "missing" creators, but I still don't understand what are you asking that I should fix. Is the problem the fact that the button is red? If so, I'm afraid that there's nothing I can do about that for a few technical reasons. I should remind you that you can still click the red button nonetheless and it will work as if it was green, the color is just a "prediction".
Gamefriend 10/17/25 (Fri) 07:43:34 No.61994
>>61991
>>61993
For the time, use this
https://update.sleazyfork.org/scripts/552123/Kemono%20Patcher.user.js
The userscript fixes the Creator not found issue.
Only if you want to download stuff you made have to use an third party program such
as JDownloader2 by. kemono party chan supply
cant install because of dependencies error.
and cant load the .png files when trying open manually.