Guest User

Kemono "Creator not found" bypass.

a guest
Nov 5th, 2025
463
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 14.93 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name        Kemono Patcher
  3. // @namespace   DKKKNND
  4. // @license     WTFPL
  5. // @match       https://kemono.cr/*
  6. // @match       https://coomer.st/*
  7. // @run-at      document-start
  8. // @grant       GM_registerMenuCommand
  9. // @grant       GM_unregisterMenuCommand
  10. // @grant       GM_setValue
  11. // @grant       GM_getValue
  12. // @version     1.4
  13. // @author      Kaban
  14. // @description Workaround "Creator not found" error, and more.
  15. // @downloadURL https://update.sleazyfork.org/scripts/552123/Kemono%20Patcher.user.js
  16. // @updateURL https://update.sleazyfork.org/scripts/552123/Kemono%20Patcher.meta.js
  17. // ==/UserScript==
  18. (function() {
  19. "use strict";
  20.  
  21. // ==<User Script>==
  22. const MISSING_PATREON = JSON.parse(GM_getValue("MISSING_PATREON", "[]"));
  23. const RENAME_CREATORS = JSON.parse(GM_getValue("RENAME_CREATORS", "{}"));
  24. const PATREON_METADATA_CACHE = JSON.parse(GM_getValue("PATREON_METADATA_CACHE", '{"postIds":[]}'));
  25.  
  26. function onMutation() {
  27.   updatePageInfo();
  28.   observer.disconnect();
  29.   switch (pageInfo.pageType) {
  30.     case "Post Details":
  31.       updateImportedTime();
  32.       break;
  33.   }
  34.   observer.observe(document, { childList: true, subtree: true });
  35. }
  36. const observer = new MutationObserver(onMutation);
  37. observer.observe(document, { childList: true, subtree: true });
  38.  
  39. let pageInfo = {};
  40. function updatePageInfo() {
  41.   if (pageInfo.href === window.location.href) return;
  42.   pageInfo = {};
  43.   const pathname = window.location.pathname;
  44.   const segments = pathname.split('/').filter(segment => segment);
  45.   switch (segments.length) {
  46.     case 3: {
  47.       if (segments[1] === "user") {
  48.         pageInfo.pageType = "Creator Posts";
  49.         const service = segments[0];
  50.         const userId = segments[2];
  51.         pageInfo.userKey = `${service}-${userId}`;
  52.       }
  53.       break;
  54.     }
  55.     case 4: {
  56.       if (segments[1] === "user" && segments[3] === "community") {
  57.         pageInfo.pageType = "Creator Community";
  58.         const service = segments[0];
  59.         const userId = segments[2];
  60.         pageInfo.userKey = `${service}-${userId}`;
  61.       }
  62.       break;
  63.     }
  64.     case 5:
  65.     case 7: {
  66.       if (segments[1] === "user" && segments[3] === "post" &&
  67.          (segments[5] == undefined || segments[5] === "revision")) {
  68.         pageInfo.pageType = "Post Details";
  69.         const service = segments[0];
  70.         const userId = segments[2];
  71.         const postId = segments[4];
  72.         pageInfo.userKey = `${service}-${userId}`;
  73.         pageInfo.postKey = `${service}-${userId}-${postId}`;
  74.       }
  75.     }
  76.   }
  77.   pageInfo.href = window.location.href;
  78.   updateScriptMenu();
  79. }
  80.  
  81. function updateScriptMenu() {
  82.   switch (pageInfo.pageType) {
  83.     case "Creator Posts":
  84.     case "Post Details":
  85.       GM_registerMenuCommand("✎ Rename Creator", renameCreator, { id: "renameCreator" });
  86.       break;
  87.     default:
  88.       GM_unregisterMenuCommand("renameCreator");
  89.   }
  90. }
  91.  
  92. let postImported = {};
  93. function loadImportedTime(event) { // called from page script
  94.   if (postImported.restoredKey === event.detail.postKey) return;
  95.   postImported.postKey = event.detail.postKey;
  96.   postImported.imported = event.detail.imported;
  97. }
  98. document.addEventListener("kp-user:load-imported-time", loadImportedTime);
  99.  
  100. function updateImportedTime() {
  101.   if (postImported.postKey !== pageInfo.postKey) return;
  102.   if (postImported.imported?.[0] === null) return; // Kemono bug (Pixiv Fanbox)
  103.  
  104.   const revisionSelection = document.getElementById("post-revision-selection");
  105.   if (revisionSelection) {
  106.     const revisionOptions = revisionSelection.getElementsByTagName("option");
  107.     // switching revision causes text reset, need edit again
  108.     if (postImported.restoredKey === postImported.postKey &&
  109.         postImported.restoredText === revisionOptions[0].textContent) {
  110.       return;
  111.     }
  112.     for (let i = 0; i < revisionOptions.length; i++) {
  113.       const date = new Date(postImported.imported[i]);
  114.       const importedTime = date.toLocaleString("en-CA", { hourCycle: "h23" });
  115.       const suffix = revisionOptions[i].textContent.substring(7);
  116.       revisionOptions[i].textContent = importedTime.replace(',', '') + suffix;
  117.     }
  118.     postImported.restoredKey = postImported.postKey;
  119.     postImported.restoredText = revisionOptions[0].textContent;
  120.     return;
  121.   }
  122.  
  123.   const revisionSpan = document.querySelector(".post__added span");
  124.   if (revisionSpan) {
  125.     const date = new Date(postImported.imported[0]);
  126.     const importedTime = date.toLocaleString("en-CA", { hourCycle: "h23" });
  127.     revisionSpan.lastChild.textContent = importedTime.replace(',', '');
  128.     postImported = { restoredKey: postImported.postKey };
  129.   }
  130. }
  131.  
  132. let saveTimeout = {};
  133. function debouncedSave(gmKey, object) {
  134.   clearTimeout(saveTimeout[gmKey]);
  135.   saveTimeout[gmKey] = setTimeout(() => {
  136.     // To Do: Make this Multi-Tab Safe
  137.     GM_setValue(gmKey, JSON.stringify(object));
  138.   }, 500);
  139. }
  140.  
  141. function renameCreator(event) {
  142.   if (event.type === "visibilitychange") {
  143.     if (document.visibilityState !== "visible") return;
  144.     if (!pageInfo.renameCreatorFlag) return;
  145.     pageInfo.renameCreatorFlag = null;
  146.   }
  147.   if (document.visibilityState === "visible") {
  148.     const creatorName = document.querySelector(".post__user-name") ||
  149.                         document.querySelector(`span[itemprop="name"]`);
  150.     const userKey = pageInfo.userKey;
  151.     const name = RENAME_CREATORS[userKey] || creatorName.textContent;
  152.  
  153.     const input = prompt(`Enter new name for ${name} (${userKey}):\n(leave empty to reset)`, name);
  154.     if (input === null || input === name) return;
  155.     if (input === "") {
  156.       delete RENAME_CREATORS[userKey];
  157.     } else {
  158.       RENAME_CREATORS[userKey] = input;
  159.     }
  160.     debouncedSave("RENAME_CREATORS", RENAME_CREATORS);
  161.     document.dispatchEvent(new CustomEvent("kp-page:rename-creator", {
  162.       detail: { userKey: userKey, newName: input }
  163.     }));
  164.     creatorName.textContent = input || userKey;
  165.   } else {
  166.     if (!pageInfo.renameCreatorFlag) pageInfo.renameCreatorFlag = true; // mobile workaround
  167.   }
  168. }
  169. document.addEventListener("visibilitychange", renameCreator);
  170.  
  171. function addMissingPatreon(event) { // called from page script
  172.   const userId = event.detail.userId;
  173.   MISSING_PATREON.push(userId);
  174.   debouncedSave("MISSING_PATREON", MISSING_PATREON);
  175. }
  176. document.addEventListener("kp-user:add-missing-patreon", addMissingPatreon);
  177.  
  178. function addPatreonCache(event) { // called from page script
  179.   const postId = event.detail.postId;
  180.   PATREON_METADATA_CACHE.postIds.push(postId);
  181.   const userId = event.detail.userId;
  182.   if (!PATREON_METADATA_CACHE[userId]) PATREON_METADATA_CACHE[userId] = [];
  183.   const postJson = event.detail.postJson;
  184.   const postMetadata = {
  185.     id: postJson.id,
  186.     user: postJson.user,
  187.     service: "patreon",
  188.     title: postJson.title,
  189.     published: postJson.published,
  190.     file: { path: postJson.file.path },
  191.     attachments: '~'.repeat(postJson.attachments.length)
  192.   };
  193.   PATREON_METADATA_CACHE[userId].push(postMetadata);
  194.   debouncedSave("PATREON_METADATA_CACHE", PATREON_METADATA_CACHE);
  195. }
  196. document.addEventListener("kp-user:add-patreon-cache", addPatreonCache);
  197.  
  198. function purgePatreonCache(event) { // called from page script
  199.   const userId = event.detail.userId;
  200.   if (PATREON_METADATA_CACHE[userId]) {
  201.     delete PATREON_METADATA_CACHE[userId];
  202.     debouncedSave("PATREON_METADATA_CACHE", PATREON_METADATA_CACHE);
  203.   }
  204. }
  205. document.addEventListener("kp-user:purge-patreon-cache", purgePatreonCache);
  206. // ==</User Script>==
  207.  
  208. // ==<Main>==
  209. const injectScript = document.createElement("script");
  210. injectScript.textContent = `(${patchFetch})();`;
  211. document.documentElement.appendChild(injectScript);
  212. document.dispatchEvent(new CustomEvent("kp-page:load-data", {
  213.   detail: {
  214.     missingPatreon: MISSING_PATREON,
  215.     renameCreators: RENAME_CREATORS,
  216.     cachedPatreonPosts: PATREON_METADATA_CACHE
  217.   }
  218. }));
  219. injectScript.remove();
  220. // ==</Main>==
  221.  
  222. // ==<Injected Function>==
  223. function patchFetch() {
  224.   let MISSING_PATREON;
  225.   let RENAME_CREATORS;
  226.   let PATREON_METADATA_CACHE;
  227.   let PATREON_METADATA_CACHE_POST_IDS;
  228.  
  229.   function loadData(event) { // called from user script
  230.     MISSING_PATREON = new Set(event.detail.missingPatreon);
  231.     RENAME_CREATORS = event.detail.renameCreators;
  232.     PATREON_METADATA_CACHE = event.detail.cachedPatreonPosts;
  233.     PATREON_METADATA_CACHE_POST_IDS = new Set(PATREON_METADATA_CACHE.postIds);
  234.   }
  235.   document.addEventListener("kp-page:load-data", loadData);
  236.  
  237.   function renameCreator(event) { // called from user script
  238.     const userKey = event.detail.userKey;
  239.     const newName = event.detail.newName;
  240.     if (newName === "") {
  241.       delete RENAME_CREATORS[userKey];
  242.     } else {
  243.       RENAME_CREATORS[userKey] = newName;
  244.     }
  245.   }
  246.   document.addEventListener("kp-page:rename-creator", renameCreator);
  247.  
  248.   function addMissingPatreon(userId) {
  249.     if (!MISSING_PATREON.has(userId)) {
  250.       MISSING_PATREON.add(userId);
  251.       document.dispatchEvent(new CustomEvent("kp-user:add-missing-patreon", {
  252.         detail: { userId: userId }
  253.       }));
  254.     }
  255.   }
  256.  
  257.   const FAKE_PATREON_PROFILE = function(userId) {
  258.     const userKey = `patreon-${userId}`;
  259.     const name = RENAME_CREATORS[userKey] || userKey;
  260.     const postCount = PATREON_METADATA_CACHE[userId]?.length || 0;
  261.     const response = {
  262.       id: userId,
  263.       name: name,
  264.       has_chats: true,
  265.       post_count: postCount,
  266.       service: "patreon"
  267.     };
  268.     return new Response(JSON.stringify(response));
  269.   };
  270.  
  271.   const FAKE_PATREON_POSTS = function(userId, offset) {
  272.     offset = parseInt(offset) || 0;
  273.     const cachedPosts = PATREON_METADATA_CACHE[userId] || [];
  274.     return new Response(JSON.stringify(cachedPosts.slice(offset, offset + 50)));
  275.   };
  276.  
  277.   const nativeFetch = window.fetch.bind(window);
  278.  
  279.   const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  280.  
  281.   const silent429Fetch = async function (input, init) {
  282.     try {
  283.       const response = await nativeFetch(input, init);
  284.       if (response.status === 429) {
  285.         const MAX_RETRIES = 3;
  286.         let attempt = 0;
  287.         let delay = 500;
  288.         while (attempt < MAX_RETRIES) {
  289.           attempt++;
  290.           console.log(`HTTP 429: ${window.location.href}\nRetry (attempt ${attempt}/${MAX_RETRIES}) in ${delay} ms...`);
  291.           await sleep(delay);
  292.           delay += 500; // backoff for next retry
  293.           const response = await nativeFetch(input, init);
  294.           if (response.ok || response.status !== 429 || attempt === MAX_RETRIES) {
  295.             return response;
  296.           }
  297.         }
  298.       }
  299.       return response;
  300.     } catch (error) {
  301.       throw error;
  302.     }
  303.   };
  304.  
  305.   window.fetch = async function(input, init) {
  306.     let url;
  307.     if (input instanceof Request) {
  308.       url = new URL(input.url);
  309.     } else if (typeof input === "string") {
  310.       try {
  311.         url = new URL(input, location.origin);
  312.       } catch (error) {
  313.         return nativeFetch(input, init);
  314.       }
  315.     } else {
  316.       return nativeFetch(input, init);
  317.     }
  318.     if (!url.pathname.startsWith("/api/v1/")) {
  319.       return nativeFetch(input, init);
  320.     }
  321.     switch (url.pathname) {
  322.       case "/api/v1/posts":
  323.       case "/api/v1/posts/popular": {
  324.         return silent429Fetch(input, init);
  325.       }
  326.     }
  327.     const segments = url.pathname.split('/').filter(segment => segment);
  328.     if (segments.length < 6 || segments[3] !== "user") {
  329.       return nativeFetch(input, init);
  330.     }
  331.     const service = segments[2];
  332.     const userId  = segments[4];
  333.     const apiName = segments[5];
  334.     switch (apiName) {
  335.       case "profile": {
  336.         if (segments.length !== 6) {
  337.           return nativeFetch(input, init);
  338.         }
  339.  
  340.         if (service === "patreon" && MISSING_PATREON.has(userId)) {
  341.           return FAKE_PATREON_PROFILE(userId);
  342.         }
  343.         try {
  344.           const response = await nativeFetch(input, init);
  345.           if (response.ok) {
  346.             document.dispatchEvent(new CustomEvent("kp-user:purge-patreon-cache", {
  347.               detail: { userId: userId }
  348.             }));
  349.             const newName = RENAME_CREATORS[`${service}-${userId}`];
  350.             if (newName) {
  351.               const responseJSON = await response.json();
  352.               responseJSON.name = newName;
  353.               return new Response(JSON.stringify(responseJSON),
  354.                 { status: response.status, headers: response.headers }
  355.               );
  356.             }
  357.           } else if (response.status === 404 && service === "patreon") {
  358.             addMissingPatreon(userId);
  359.             return FAKE_PATREON_PROFILE(userId);
  360.           }
  361.           return response;
  362.         } catch (error) {
  363.           return nativeFetch(input, init);
  364.         }
  365.       }
  366.       case "posts": {
  367.         if (segments.length !== 6) {
  368.           return nativeFetch(input, init);
  369.         }
  370.  
  371.         const offset = new URLSearchParams(url.search).get("o");
  372.         if (service === "patreon" && MISSING_PATREON.has(userId)) {
  373.           return FAKE_PATREON_POSTS(userId, offset);
  374.         }
  375.         try {
  376.           const response = await silent429Fetch(input, init);
  377.           if (response.status === 404 && service === "patreon") {
  378.             return FAKE_PATREON_POSTS(userId, offset);
  379.           }
  380.           return response;
  381.         } catch (error) {
  382.           return nativeFetch(input, init);
  383.         }
  384.       }
  385.       case "post": {
  386.         if (!(segments.length === 7 ||
  387.              (segments.length === 9 && segments[7] === "revision"))) {
  388.           return nativeFetch(input, init);
  389.         }
  390.  
  391.         const postId = segments[6];
  392.         try {
  393.           const response = await nativeFetch(input, init);
  394.           if (response.ok) {
  395.             const responseJSON = await response.json();
  396.             const imported = [];
  397.             const revisions = responseJSON.props.revisions;
  398.             for (const revision of responseJSON.props.revisions) {
  399.               imported.push(revision[1].added); // second element is post object
  400.             }
  401.             // Kemono front end cuts off imported date, send raw data to user script
  402.             document.dispatchEvent(new CustomEvent("kp-user:load-imported-time", {
  403.               detail: { postKey: `${service}-${userId}-${postId}`, imported: imported }
  404.             }));
  405.  
  406.             // To Do: make a "white list" for creators do exist so no need for caching
  407.             if (service === "patreon" && !PATREON_METADATA_CACHE_POST_IDS.has(postId)) {
  408.               document.dispatchEvent(new CustomEvent("kp-user:add-patreon-cache", {
  409.                 detail: { userId: userId, postId: postId, postJson: responseJSON["post"] }
  410.               }));
  411.               PATREON_METADATA_CACHE_POST_IDS.add(postId);
  412.             }
  413.  
  414.             return new Response(JSON.stringify(responseJSON),
  415.               { status: response.status, headers: response.headers }
  416.             );
  417.           }
  418.         } catch (error) {
  419.           return nativeFetch(input, init);
  420.         }
  421.       }
  422.     }
  423.     return nativeFetch(input, init);
  424.   };
  425. }
  426. // ==</Injected Function>==
  427. })();
Advertisement
Comments
Add Comment
Please, Sign In to add comment