find panda

check panda.chaika.moe for related galleries when searching

As of 2022-12-02. See the latest version.

// ==UserScript==
// @name find panda
// @namespace http://tampermonkey.net/
// @version 0.2
// @description check panda.chaika.moe for related galleries when searching
// @description:zh 在搜索时检查panda.chaika.moe里是否有相关的画廊
// @author ayasechan
// @license LGPL
// @icon https://www.google.com/s2/favicons?sz=64&domain=e-hentai.org
// @run-at document-start
// @match https://exhentai.org/*
// @match https://e-hentai.org/*
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @connect panda.chaika.moe
// @resource iziToast.min.css https://cdn.jsdelivr.net/npm/[email protected]/dist/css/iziToast.min.css
// @resource iziToast.min.js https://cdn.jsdelivr.net/npm/[email protected]/dist/js/iziToast.min.js
// ==/UserScript==

/**
 * Tips:
 * 
 * 目前的匹配模式很简单
 * 优先匹配搜索时的第一个 group 或 artist 标签
 * 匹配成功后会忽略其他搜索内容和标签
 * 使用标签搜索时会且只会匹配上述两个标签
 * 
 * 没有出现这两个标签时 则将所有的搜索的内容都视为关键词
 * 
 * 其他e站支持的语法 比如排除 精确搜索什么的都不支持
 * 因此要使脚本成功运行 最好只使用简单的关键词搜索
 */


/*
  inject resource
*/
((...names) => {
  if (typeof GM_getResourceText === void 0) {
    const text = "need grant GM_getResourceText permission";
    alert(text);
    throw text;
  }
  for (const name of names) {
    const data = GM_getResourceText(name);
    if (!data.length) {
      console.warn(`resource ${name} have no content`);
    }
    if (name.endsWith(".js")) {
      const el = document.createElement("script");
      el.textContent = data;
      document.body.appendChild(el);
    } else if (name.endsWith(".css")) {
      const el = document.createElement("style");
      el.textContent = data;
      document.body.appendChild(el);
    }
  }
})('iziToast.min.css', 'iziToast.min.js');




(function() {
  "use strict";
  const request = (url, { json = null, form = null } = {}) => {
    return new Promise((resolve, reject) => {
      const r = GM_xmlhttpRequest != null ? GM_xmlhttpRequest : XMLHttpRequest;
      let method = "POST";
      let ct, data;
      if (json) {
        ct = "application/json";
        data = JSON.stringify(json);
      } else if (form) {
        ct = "application/x-www-form-urlencoded";
        data = new FormData();
        for (const [k, v] of Object.entries(form)) {
          data.append(k, v);
        }
      } else {
        method = "GET";
      }
      r({
        url,
        data,
        method,
        headers: {
          "Content-Type": ct
        },
        onload: (resp) => {
          resolve(resp.responseText);
        },
        onerror: (err) => {
          reject(err);
        }
      });
    });
  };
  const main = async () => {
    const kw = parseKeyword();
    if (!kw) {
      return;
    }
    let content;
    try {
      content = await request(
        `https://panda.chaika.moe/archive-autocomplete/?q=${kw}`
      );
    } catch (error) {
      alert("can not connect to panda.chaika.moe. see more info from dev tool");
      throw error;
    }
    if (content === noMatchesFound) {
      console.log(`nothing found in panda for ${kw}`);
      return;
    }
    const openPanda = () => {
      window.open(`https://panda.chaika.moe/search/?title=${kw}`);
    };
    iziToast.show({
      message: "Find a panda",
      messageColor: "#ffffff",
      color: "#363940",
      timeout: 1e4,
      position: "topRight",
      onOpened: (_, el) => {
        el.addEventListener("click", openPanda);
      },
      onClosed: (_, el) => {
        el.removeEventListener("click", openPanda);
      }
    });
  };
  const parseKeyword = () => {
    var _a, _b, _c;
    const u = new URL(location.href);
    const f_search = u.searchParams.get("f_search");
    if (f_search) {
      const result2 = f_search.match(fSearchRegex);
      if (result2) {
        const kw = (_c = (_a = result2.groups) == null ? void 0 : _a.kw1) != null ? _c : (_b = result2.groups) == null ? void 0 : _b.kw2;
        if (kw) {
          return kw;
        }
      }
      return f_search;
    }
    const result = u.pathname.match(tagRegex);
    if (result) {
      return result[1].replaceAll("+", " ");
    }
  };
  const tagRegex = /^\/tag\/(?:artist|group):([\w\+]+)$/i;
  const fSearchRegex = /(?:artist|group|circle|a|g):(?:(?<kw1>\w+)\$|"(?<kw2>[\w ]+)\$")/i;
  const noMatchesFound = '<span class="block"><em>No matches found</em></span>';
  main();
})();