find panda

check panda.chaika.moe for related galleries when searching

  1. // ==UserScript==
  2. // @name find panda
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description check panda.chaika.moe for related galleries when searching
  6. // @description:zh-CN 在搜索时检查panda.chaika.moe里是否有相关的画廊
  7. // @author ayasechan
  8. // @license LGPL
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=e-hentai.org
  10. // @run-at document-start
  11. // @match https://exhentai.org/*
  12. // @match https://e-hentai.org/*
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_addStyle
  15. // @grant GM_getResourceText
  16. // @connect panda.chaika.moe
  17. // @resource iziToast.min.css https://cdn.jsdelivr.net/npm/izitoast@1.4.0/dist/css/iziToast.min.css
  18. // @require https://cdn.jsdelivr.net/npm/izitoast@1.4.0/dist/js/iziToast.min.js
  19. // ==/UserScript==
  20.  
  21. GM_addStyle(GM_getResourceText("iziToast.min.css"))
  22.  
  23.  
  24. "use strict";
  25. (() => {
  26. // src/lib/panda.ts
  27. var get = (url) => {
  28. return new Promise((res, rej) => {
  29. GM_xmlhttpRequest({
  30. url,
  31. onload: (resp) => res(resp.responseText),
  32. onerror: (resp) => rej(resp.error)
  33. });
  34. });
  35. };
  36. var archiveAutocompleteSearch = async (kw) => {
  37. const url = new URL("https://panda.chaika.moe/archive-autocomplete/");
  38. url.searchParams.set("q", kw);
  39. return await get(url.toString());
  40. };
  41. var tagSearch = async (tag) => {
  42. return await get(`https://panda.chaika.moe/tag/${tag}`);
  43. };
  44. var isArchiveAutocompleteFound = (content) => {
  45. return !content.includes(
  46. '<span class="block"><em>No matches found</em></span>'
  47. );
  48. };
  49. var isTagFound = (content) => {
  50. return !content.includes("<strong>No results</strong>");
  51. };
  52.  
  53. // src/lib/parse.ts
  54. var parseFsearch = (url) => {
  55. let fsearch = new URL(url).searchParams.get("f_search");
  56. if (!fsearch) {
  57. return [];
  58. }
  59. fsearch = fsearch.replace(/\$/g, "");
  60. fsearch = fsearch.replace(/\s+(?=(?:(?:[^"]*"){2})*[^"]*"[^"]*$)/g, "_");
  61. fsearch = fsearch.replace(/"/g, "");
  62. const matches = fsearch.match(/(\S+)/g);
  63. const r = matches ? [...new Set(matches)] : [];
  64. return r.map((v) => {
  65. if (isTagWithNamespace(v)) {
  66. return v;
  67. }
  68. return v.replace(/_/g, " ");
  69. });
  70. };
  71. var parseTag = (url) => {
  72. const pathname = new URL(url).pathname;
  73. const matches = pathname.match(/\/tag\/(.+?)\/?$/i);
  74. return matches ? matches[1].replace(/\+/g, "_") : "";
  75. };
  76. var parseKeyword = (url) => {
  77. const pathname = new URL(url).pathname;
  78. if (pathname.startsWith("/tag")) {
  79. return [parseTag(url)];
  80. }
  81. return parseFsearch(url);
  82. };
  83. var isTagWithNamespace = (s) => s.includes(":");
  84.  
  85. // src/index.ts
  86. var main = async () => {
  87. let kws = parseKeyword(location.href);
  88. kws = filteKws(kws);
  89. if (!kws.length) {
  90. return;
  91. }
  92. let kw = "";
  93. for (const v of kws) {
  94. let ok = false;
  95. if (isTagWithNamespace(v)) {
  96. const content = await tagSearch(v);
  97. ok = isTagFound(content);
  98. } else {
  99. const content = await archiveAutocompleteSearch(v);
  100. ok = isArchiveAutocompleteFound(content);
  101. }
  102. if (ok) {
  103. kw = v;
  104. break;
  105. }
  106. }
  107. if (!kw.length) {
  108. return;
  109. }
  110. const openPanda = () => {
  111. const url = new URL("https://panda.chaika.moe/search");
  112. const key = isTagWithNamespace(kw) ? "tags" : "title";
  113. url.searchParams.set(key, kw);
  114. window.open(url.toString());
  115. };
  116. iziToast.show({
  117. message: "Find a panda",
  118. messageColor: "#ffffff",
  119. color: "#363940",
  120. timeout: 1e4,
  121. position: "topRight",
  122. onOpened: (_, el) => {
  123. el.addEventListener("click", openPanda);
  124. },
  125. onClosed: (_, el) => {
  126. el.removeEventListener("click", openPanda);
  127. }
  128. });
  129. };
  130. var filteKws = (kws) => {
  131. const validNS = ["a", "artist", "g", "group", "circle"];
  132. return kws.reduce((pre, cur) => {
  133. if (isTagWithNamespace(cur)) {
  134. const [ns, _] = cur.split(":");
  135. if (validNS.includes(ns)) {
  136. return [cur, ...pre];
  137. }
  138. return pre;
  139. }
  140. return [...pre, cur];
  141. }, []);
  142. };
  143. main();
  144. })();