Kemono助手

提供更好的Kemono使用体验

Від 26.11.2023. Дивіться остання версія.

  1. // ==UserScript==
  2. // @name Kemono助手
  3. // @version 1.1.5
  4. // @description 提供更好的Kemono使用体验
  5. // @author ZIDOUZI
  6. // @match https://*.kemono.party/*
  7. // @match https://*.kemono.su/*
  8. // @icon https://kemono.su/static/favicon.ico
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // @grant GM_unregisterMenuCommand
  14. // @namespace https://greasyfork.org/users/448292
  15. // ==/UserScript==
  16.  
  17. (async function () {
  18.  
  19. const language = navigator.language || navigator.userLanguage;
  20.  
  21. let vimMode = GM_getValue('vimMode', false);
  22.  
  23. let vimModeId = GM_registerMenuCommand(`[${vimMode ? "✔" : "✖"}]导航键使用vim模式`, vimMode_callback);
  24.  
  25. function vimMode_callback() {
  26. GM_unregisterMenuCommand(vimModeId);
  27. vimMode = !vimMode;
  28. GM_setValue('vimMode', vimMode);
  29. vimModeId = GM_registerMenuCommand(`[${vimMode ? "✔" : "✖"}]导航键使用vim模式`, vimMode_callback);
  30. }
  31.  
  32. const postContent = document.querySelector('.post__content')
  33. if (postContent) {
  34. replaceAsync(postContent.innerHTML, /(?<!a href="|<a [^>]+">)(https?:\/\/[^\s<]+)/g, async function (match) {
  35. let [service, id, post] = await getKemonoUrl(match);
  36. if (service === null) return `<a href="${match}" target="_blank">${match}</a>`;
  37. id = id || window.location.href.match(/\/user\/(\d+)/)[1];
  38. const domain = window.location.href.match(/https:\/\/([^/]+)/)[1];
  39. const url = `${service}/user/${id}${post ? `/post/${post}` : ""}`;
  40. return `<a href="https://${domain}/${url}" target="_self">[已替换]${match}</a>`;
  41. }).then(function (result) {
  42. postContent.innerHTML = result
  43. .replace(/<a href="(https:\/\/[^\s<]+)">\1<\/a>\n?(#[^\s<]+)/g, "<a href=\"$1$2\">$1$2</a>")
  44. .replace(/<a href="(https:\/\/[^\s<]+)">(.*?)<\/a>\n?(#[^\s<]+)/g, "<a href=\"$1$3\">$2</a>")
  45. })
  46. }
  47.  
  48. const prev = document.querySelector(".post__nav-link.prev");
  49. if (prev) {
  50. document.addEventListener("keydown", function (e) {
  51. if (e.key === "Right" || e.key === "ArrowRight" || vimMode && (e.key === "h" || e.key === "H")) {
  52. prev.click();
  53. }
  54. });
  55. }
  56.  
  57. const next = document.querySelector(".post__nav-link.next");
  58. if (next) {
  59. document.addEventListener("keydown", function (e) {
  60. if (e.key === "Left" || e.key === "ArrowLeft" || vimMode && (e.key === "l" || e.key === "L")) {
  61. next.click();
  62. }
  63. });
  64. }
  65.  
  66. if (language === 'zh-CN') {
  67. const dms = document.querySelector('.user-header__dms');
  68.  
  69. if (dms) dms.innerHTML = '私信'
  70.  
  71. const postFlag = document.querySelector('.post__flag');
  72.  
  73. if (postFlag) {
  74. const text = postFlag.querySelector('span:last-child');
  75. if (text) text.textContent = '标记重新导入';
  76. }
  77.  
  78. }
  79.  
  80. })();
  81.  
  82. async function replaceAsync(str, regex, asyncFn) {
  83. const promises = [];
  84. str.replace(regex, (match, ...args) => {
  85. const promise = asyncFn(match, ...args);
  86. promises.push(promise);
  87. });
  88. const data = await Promise.all(promises);
  89. return str.replace(regex, () => data.shift());
  90. }
  91.  
  92. async function getKemonoUrl(url) {
  93.  
  94. function getFanbox(creatorId) {
  95. // 同步执行promise
  96. return new Promise((resolve, reject) => {
  97. GM_xmlhttpRequest({
  98. method: "GET",
  99. url: `https://api.fanbox.cc/creator.get?creatorId=${creatorId}`,
  100. headers: {
  101. "Content-Type": "application/json",
  102. "Accept": "application/json",
  103. "Origin": "https://www.fanbox.cc",
  104. "Referer": "https://www.fanbox.cc/"
  105. },
  106. onload: function (response) {
  107. if (response.status === 200) {
  108. resolve(JSON.parse(response.responseText))
  109. } else {
  110. reject({status: response.status, statusText: response.statusText})
  111. }
  112. },
  113. onerror: function (response) {
  114. reject({status: response.status, statusText: response.statusText})
  115. }
  116. })
  117. })
  118. }
  119.  
  120. const pixiv_user = /https:\/\/www\.pixiv\.net\/users\/(\d+)/i;
  121. const fantia_user = /https:\/\/fantia\.jp\/fanclubs\/(\d+)(\/posts(\S+))?/i;
  122. const fanbox_user1 = /https:\/\/www\.fanbox\.cc\/@([^/]+)(?:\/posts\/(\d+))?/i;
  123. const fanbox_user2 = /https:\/\/(.+)\.fanbox\.cc(?:\/posts\/(\d+))?/i;
  124. const dlsite_user = /https:\/\/www.dlsite.com\/.+?\/profile\/=\/maker_id\/(RG\d+).html/i;
  125. const patreon_user1 = /https:\/\/www.patreon.com\/user\?u=(\d+)/i;
  126. const patreon_user2 = /https:\/\/www.patreon.com\/(\w+)/i;
  127. const patreon_post1 = /https:\/\/www.patreon.com\/posts\/(\d+)/i;
  128. const patreon_post2 = /https:\/\/www.patreon.com\/posts\/video-download-(\d+)/i;
  129.  
  130. let service;
  131. let id;
  132. let post = null;
  133.  
  134. if (pixiv_user.test(url)) {
  135. //pixiv artist
  136. service = "fanbox"
  137. id = url.match(pixiv_user)[1]
  138. } else if (fantia_user.test(url)) {
  139. //fantia
  140. service = "fantia"
  141. id = url.match(fantia_user)[1]
  142. } else if (dlsite_user.test(url)) {
  143. service = "dlsite"
  144. id = url.match(dlsite_user)[1]
  145. } else if (fanbox_user1.test(url) || fanbox_user2.test(url)) {
  146. //fanbox
  147. service = "fanbox"
  148. let matches = fanbox_user1.test(url) ? url.match(fanbox_user1) : url.match(fanbox_user2);
  149. id = (await getFanbox(matches[1])).body.user.userId.toString()
  150. post = matches[2]
  151. } else if (patreon_user1.test(url)) {
  152. // patreon
  153. service = "patreon"
  154. id = url.match(patreon_user1)[1]
  155. } else if (patreon_post1.test(url)) {
  156. // patreon post
  157. service = "patreon"
  158. post = url.match(patreon_post1)[1]
  159. } else if (patreon_post2.test(url)) {
  160. // patreon post
  161. service = "patreon"
  162. post = url.match(patreon_post2)[1]
  163. } else {
  164. return null;
  165. }
  166.  
  167. return [service, id, post]
  168. }