Greasy Fork is available in English.

Arkrec Wiki Assistant

Automatically query if this video has been in the arkrec wiki database on video loading

// ==UserScript==
// @name         Arkrec Wiki Assistant
// @name:zh-CN   明日方舟少人WIKI辅助插件
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Automatically query if this video has been in the arkrec wiki database on video loading
// @description:zh-CN  在打开视频时自动判断是否已上传至少人WIKI数据库,并提供上传捷径链接
// @author       philimao
// @match        https://www.bilibili.com/video/BV*
// @icon         https://www.google.com/s2/favicons?domain=arkrec.com
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

const maxRetries = 100;
async function delay(timeout) {
  return new Promise(r => setTimeout(r, timeout));
}
async function waitForDom(selector, retries) {
  // console.log("[Arkrec]",selector);
  let dom, r;
  r = retries || maxRetries;
    console.log("retries", r);
  for (let i = 0; i < r; i++) {
    dom = document.querySelector(selector);
    if (dom) return dom;
    await delay(500);
  }
  console.log(`[Arkrec] Dom element ${selector} not loaded in given time`);
}

async function waitForVar(v, retries, path) {
  let res;
  let r = retries || maxRetries;
  for (let i = 0; i < r; i++) {
    res = v;
    if (path) {
      for (let key of path) {
        res = res[key];
      }
    }
    if (res) return res;
    console.log("[Arkrec]", res);
    await delay(500);
  }
  console.log("[Arkrec] variable not loaded in given time");
}

(async function () {
  GM_registerMenuCommand(
    "Global Mode",
    () => GM_setValue("mode", "global"),
    "g"
  );
  GM_registerMenuCommand(
    "Exclusive Mode",
    () => GM_setValue("mode", "exclusive"),
    "e"
  );

  const wikiBtn = document.createElement("button");

  // https://greasyfork.org/en/scripts/398655-%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E8%A7%86%E9%A2%91%E9%A1%B5%E9%9D%A2%E5%B8%B8%E9%A9%BB%E6%98%BE%E7%A4%BAav-bv%E5%8F%B7-%E5%B7%B2%E5%AE%8C%E5%85%A8%E9%87%8D%E6%9E%84-%E6%94%AF%E6%8C%81%E6%98%BE%E7%A4%BA%E5%88%86p%E6%A0%87%E9%A2%98
  async function playerReady() {
    let counter = 0;
    return new Promise((resolve) => {
      function wait() {
        if (
          unsafeWindow.player &&
          unsafeWindow.player.isInitialized &&
          unsafeWindow.player.isInitialized()
        ) {
          return resolve();
        }
        if (counter === 1000) {
          console.log("[Arkrec] Player Timeout, Increase Interval");
        }
        counter++;
        const timeout = counter < 1000 ? 100 : 1000;
        if (counter % 10000 === 0)
          console.log("[Arkrec] Waiting", counter, timeout);
        setTimeout(wait, timeout);
      }
      setTimeout(wait, 2000);
    });
  }

  async function injection() {
    const elem = document.querySelector("[data-inject='arkrec']");
    if (elem) elem.remove();

    const tags = unsafeWindow.__INITIAL_STATE__.tags.map((el) => el.tag_name);
    console.log("[Arkrec]", tags);
    if (
      ["明日方舟", "危机合约", /arknights/i].every(
        (re) =>
          tags.every((tag) => tag.search(re) < 0) ||
          (GM_getValue("mode") === "global" && !tags.includes("手机游戏"))
      )
    )
      return console.log("[Arkrec] No Arknights Tag Detected, Abort Injection");

    const username = await waitForVar(unsafeWindow, 100, ["__INITIAL_STATE__","user","uname"]);
    if (username === "philimao") {
        const playerStyle = document.querySelector(".bpx-player-container") ? 0 : 1;
        let x2 = playerStyle ?
          await waitForDom(".bilibili-player-video-btn-speed-menu-list[data-value='2']") :
          await waitForDom(".bpx-player-ctrl-playbackrate-menu-item[data-value='2']");
        if (x2) x2.click();
        else console.log("[Arkrec] Speed Icon Not Detected");
    }

    let host = "https://arkrec.com";
    // host = "http://127.0.0.1:3001";
    const { bvid, p } = unsafeWindow.__INITIAL_STATE__;
    const url =
      "https://www.bilibili.com/video/" + bvid + (p === 1 ? "" : "?p=" + p);
    try {
      const resRaw = await fetch(host + "/api/query-url", {
        method: "POST",
        mode: "cors",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ url }),
      });
      let toolbar = document.querySelector(".video-toolbar");
      if (toolbar) toolbar.appendChild(wikiBtn);
      else {
        wikiBtn.style.transform = "none";
        toolbar = await waitForDom(".video-toolbar-right");
        toolbar.insertBefore(wikiBtn, toolbar.firstChild);
      }
      if (resRaw.ok) {
        console.log("[Arkrec] Record Exists.");
        const wikiBtn = document.querySelector("[data-inject='arkrec']");
        wikiBtn.style.filter = "invert(0.8)";
        wikiBtn.style.pointerEvents = "none";
        wikiBtn.setAttribute("disabled", "true");
      } else {
        console.log("[Arkrec] Wiki Icon Injecting", bvid, "Page", p);
      }
    } catch (err) {
      console.log(err);
      document.querySelector(".video-toolbar").appendChild(wikiBtn);
      console.log(
        "[Arkrec] Fail to Connect to Arkrec Server. Wiki Icon Injecting",
        bvid,
        "Page",
        p
      );
    }
  }

  // https://greasyfork.org/en/scripts/398542-bilibili-%E6%98%BE%E7%A4%BA-av-%E5%8F%B7
  function registerObserver() {
    console.log("[Arkrec] Register Re-Injection Listener");
    const target = unsafeWindow.__INITIAL_STATE__;
    let videoData = target.videoData;
    const desc = Object.getOwnPropertyDescriptor(target, "videoData");
    const vueHook = desc.set;
    Object.defineProperty(target, "videoData", {
      get: desc.get || (() => videoData),
      set(data) {
        videoData = data;
        injection();
        if (vueHook) vueHook.call(this, data);
      },
      enumerable: true,
      configurable: true,
    });
  }

  const wikiSvg =
    "data:image/svg+xml,%3Csvg%20id%3D%22svg%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22400%22%20height%3D%22176.24521072796932%22%20viewBox%3D%220%2C%200%2C%20400%2C176.24521072796932%22%20version%3D%221.1%22%3E%3Cg%20id%3D%22svgg%22%3E%3Cpath%20id%3D%22path0%22%20d%3D%22M0.000%2081.302%20C%200.000%20178.729%2C-0.539%20176.253%2C20.648%20176.239%20C%2035.296%20176.230%2C36.588%20174.889%2C57.999%20137.469%20C%2068.404%20119.283%2C77.715%20103.341%2C78.689%20102.042%20C%2080.149%20100.093%2C83.915%20105.722%2C100.158%20134.127%20C%20123.163%20174.358%2C124.902%20176.245%2C138.968%20176.245%20C%20148.294%20176.245%2C149.315%20175.895%2C154.176%20171.034%20L%20159.387%20165.824%20159.387%2082.912%20L%20159.387%200.000%20141.762%200.000%20L%20124.138%200.000%20124.138%2052.487%20L%20124.138%20104.974%20107.142%2075.475%20L%2090.146%2045.977%2080.303%2045.977%20L%2070.460%2045.977%2065.395%2054.023%20C%2062.610%2058.448%2C54.860%2071.635%2C48.173%2083.326%20L%2036.015%20104.583%2035.612%2052.292%20L%2035.209%200.000%2017.604%200.000%20L%200.000%200.000%200.000%2081.302%20M163.985%2088.123%20L%20163.985%20176.245%20180.843%20176.245%20L%20197.701%20176.245%20197.701%2088.123%20L%20197.701%200.000%20180.843%200.000%20L%20163.985%200.000%20163.985%2088.123%20M202.299%2088.123%20L%20202.299%20176.245%20213.374%20176.245%20L%20224.450%20176.245%20292.685%20108.065%20L%20360.920%2039.884%20361.424%2019.942%20L%20361.929%200.000%20344.183%200.000%20L%20326.437%200.000%20326.437%2012.654%20L%20326.437%2025.307%20281.992%2069.732%20L%20237.548%20114.156%20237.548%2057.078%20L%20237.548%200.000%20219.923%200.000%20L%20202.299%200.000%20202.299%2088.123%20M364.751%2088.123%20L%20364.751%20176.245%20382.375%20176.245%20L%20400.000%20176.245%20400.000%2088.123%20L%20400.000%200.000%20382.375%200.000%20L%20364.751%200.000%20364.751%2088.123%20M301.143%20102.688%20L%20288.948%20114.956%20307.692%20133.745%20L%20326.437%20152.535%20326.437%20164.390%20L%20326.437%20176.245%20344.061%20176.245%20L%20361.686%20176.245%20361.686%20159.487%20C%20361.686%20150.270%2C360.962%20140.787%2C360.078%20138.414%20C%20358.678%20134.659%2C316.353%2090.421%2C314.160%2090.421%20C%20313.708%2090.421%2C307.850%2095.942%2C301.143%20102.688%20%22%20stroke%3D%22none%22%20fill%3D%22%23000000%22%20fill-rule%3D%22evenodd%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E";
  wikiBtn.setAttribute("data-inject", "arkrec");
  wikiBtn.style.width = "5rem";
  wikiBtn.style.height = "1.8rem";
  wikiBtn.style.marginRight = "2rem";
  wikiBtn.style.float = "right";
  wikiBtn.style.backgroundColor = "initial";
  wikiBtn.style.border = "none";
  wikiBtn.style.backgroundImage = `url(${wikiSvg})`;
  wikiBtn.style.backgroundSize = "contain";
  wikiBtn.style.backgroundRepeat = "no-repeat";
  wikiBtn.style.backgroundPosition = "center";
  wikiBtn.style.filter = "invert(0.4)";
  wikiBtn.style.transform = "translateY(2px)";
  wikiBtn.style.cursor = "pointer";

  const toast = document.querySelector("div");
  toast.id = "arkrec_toast";
  toast.style.width = "80px";
  toast.style.height = "30px";
  toast.style.position = "absolute";
  toast.style.left = 0;
  toast.style.top = 0;
  toast.style.backgroundColor = "black";
  toast.style.color = "white";
  toast.style.fontSize = "13px";
  toast.style.textAlign = "center";
  toast.style.lineHeight = "30px";
  toast.style.opacity = 0;
  toast.style.display = "none";
  toast.style.borderRadius = "4px";
  toast.style.transition = "all 0.3s ease-in-out";
  toast.style.zIndex = 3000;

  async function getScreenshot() {
    return new Promise((resolve, reject) => {
      const video = document.querySelector("video");
      if (!video) {
        alert(
          "检测到播放器不支持截图,请在设置-更多设置-播放策略中选择非HEVC的选项并刷新页面"
        );
        reject();
      }
      const canvas = document.createElement("canvas");
      const context = canvas.getContext("2d");
      const width = video.videoWidth;
      const height = video.videoHeight;
      canvas.width = width;
      canvas.height = height;
      context.drawImage(video, 0, 0, width, height);
      const { ClipboardItem } = window;
      canvas.toBlob((blob) => {
        const clipboardItem = new ClipboardItem({
          "image/png": blob,
        });
        navigator.clipboard
          .write([clipboardItem])
          .then(() => resolve(console.log("复制成功")));
      }, "image/png");
    });
  }

  async function toastInfo(evt, text) {
    toast.innerText = text;
    toast.style.display = "block";
    toast.style.opacity = 1;
    toast.style.transform = `translate(${evt.x - 40}px, ${evt.y - 40}px)`;
    setTimeout(() => {
      toast.style.opacity = 0;
      setTimeout(() => {
        toast.style.display = "none";
      }, 300);
    }, 800);
  }

  wikiBtn.addEventListener("click", (evt) => {
    evt.preventDefault();
    getScreenshot().then(() => toastInfo(evt, "复制成功"));
  });

  wikiBtn.addEventListener("contextmenu", (evt) => {
    evt.preventDefault();
    toastInfo(evt, "截图中...")
      .then(getScreenshot)
      .then(() => {
        const { bvid, p } = unsafeWindow.__INITIAL_STATE__;
        window.open(
          "https://arkrec.com/submit?bvid=" + bvid + (p === 1 ? "" : "&p=" + p)
        );
      });
  });

  wikiBtn.addEventListener("mouseenter", () => {
    wikiBtn.style.filter =
      "invert(1) brightness(0.5) sepia(1) hue-rotate(-194deg) saturate(3)";
  });
  wikiBtn.addEventListener("mouseleave", () => {
    wikiBtn.style.filter = "invert(0.4)";
  });

  await playerReady();
  await injection();
  registerObserver();
  document.querySelector("body").append(toast);
})();