Hikari_Field_Helper

Hikari_Field入库游戏检测

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name:zh-CN      Hikari_Field入库检测
// @name            Hikari_Field_Helper
// @namespace       https://blog.chrxw.com
// @supportURL      https://blog.chrxw.com/scripts.html
// @contributionURL https://afdian.com/@chr233
// @version         2.19
// @description     Hikari_Field入库游戏检测
// @description:zh-CN  Hikari_Field入库游戏检测
// @author          Chr_
// @include         https://keylol.com/*
// @include         https://store.hikarifield.co.jp/libraries
// @license         AGPL-3.0
// @icon            https://blog.chrxw.com/favicon.ico
// @resource        data https://raw.chrxw.com/GM_Scripts/master/Keylol/Data/Hikari_Field_Helper.json
// @grant           GM_getResourceText
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_addStyle
// ==/UserScript==


(() => {
  "use strict";

  const HFSHOP = "https://store.hikarifield.co.jp/shop/";
  const HFLIBARY = "https://store.hikarifield.co.jp/libraries";

  const { INFO, DESC } = JSON.parse(GM_getResourceText("data"));
  const host = window.location.host;

  if (host === "store.hikarifield.co.jp") {
    //更新库存
    const myGames = document.querySelectorAll(".game-cover>a");
    const ownedGames = [625760]; //魔卡魅恋(免费)
    for (const ele of myGames) {
      const key = ele.href?.replace(HFSHOP, "");
      if (key) {
        let [gameName, appID, _, __] = INFO[key] ?? [null, null, null];
        if (appID !== null) {
          ownedGames.push(appID);
          console.log(`已拥有 ${gameName} ${appID}`);
        }
      } else {
        console.error(`${ele.href} 无效`);
      }
    }
    //储存列表
    GM_setValue("ownedGames", ownedGames);
    GM_setValue("refreshTime", new Date().toISOString());
    swal({
      position: "top-end",
      text: "导入游戏列表成功",
      icon: "success",
      button: false,
      timer: 1200
    });

  } else if (host.endsWith("keylol.com")) {
    //其乐
    if (document.title.search("Keylol") === -1) { return; } //跳过iframe
    const ownedGames = new Set(GM_getValue("ownedGames") ?? []);
    const refreshTime = GM_getValue("refreshTime") ?? null;

    if (ownedGames.size === 0) {
      if (confirm("是否立即导入游戏列表?")) {
        window.location.href = HFLIBARY;
      } else {
        showError("【可以在悬浮窗口中进行同步】");
        GM_setValue("ownedGames", [0]);
      }
    }

    setTimeout(() => {
      const steamLinks = document.querySelectorAll("a[href^='https://store.steampowered.com/'],a[href^='https://steamdb.info/app/']");
      const HFLinks = document.querySelectorAll("a[href^='https://store.hikarifield.co.jp/shop/'],a[href^='https://shop.hikarifield.co.jp/shop/']");
      let flag = HFLinks.length > 0;
      const grubAppid = RegExp(/app\/(\d+)\/?/);
      const grubHFKey = RegExp(/shop\/(\S+)\/?/);
      for (const ele of steamLinks) {
        const href = ele.href;
        if (href) {
          const appID = parseInt(grubAppid.exec(href)?.[1] ?? 0);
          if (appID > 0) {
            if (ownedGames.has(appID)) {
              ele.classList.add("steam-info-link");
              ele.classList.add("steam-info-own");
              flag = true;
            }
          }
        }
      }
      if (!flag) { return; } //未匹配到游戏,结束运行
      for (const ele of HFLinks) {
        const href = ele.href;
        if (href) {
          const key = grubHFKey.exec(href)?.[1];
          if (key) {
            let [_, appID, __, ___] = INFO[key] ?? [null, null, null, null];
            if (appID !== null) {
              if (ownedGames.has(appID)) {
                ele.classList.add("steam-info-link");
                ele.classList.add("steam-info-own");
              }
              ele.setAttribute("data-hf", key);
              ele.addEventListener("mouseenter", showDiag);
              ele.addEventListener("mouseleave", hideDiag);
            }
          } else {
            console.log(ele);
          }
        }
      }
    }, 1000);

    const diagObjs = {}; // 小部件DOM对象
    let isShow = false;  // 悬浮窗是否显示
    let timer = -1;      // 隐藏计时器

    //创建弹窗小部件
    function initDiag() {
      const newDiv = (cls) => { const d = document.createElement("div"); if (cls) { d.className = cls; } return d; };

      const hfBox = newDiv("hf-box");

      let lastRefresh;
      if (refreshTime !== null) {
        try {
          const t = new Date(refreshTime);
          lastRefresh = `账号同步于 ${t.toLocaleString()} 点击刷新`;
        } catch (e) {
          console.error(e);
          lastRefresh = "读取同步时间出错, 点击刷新";
        }
      } else {
        lastRefresh = "账号未同步, 点击刷新";
      }

      hfBox.style.display = "none";

      hfBox.innerHTML = `
      <div class="hf-head">
        <span title="">占位</span>
      </div>
      <div class="hf-body">
        <img src="https://cdn.cloudflare.steamstatic.com/steam/apps/1662840/header.jpg">
      </div>
      <div class="hf-foot">
        <div class="hf-describe">
          <span title="">...</span>
        </div>
        <div class="hf-line"></div>
        <div class="hf-detail">
          <div class="hf-line"></div>
          <div><a href="${HFLIBARY}" target="_blank">${lastRefresh}</a></div>
          <div class="hf-line"></div>
          <p class="hf-hf"><b>HF商店:</b><span class="hf-unknown">占位</span><a href="#" target="_blank" class="hf-link">前往商店</a></p>
          <div class="hf-line"></div>
          <p class="hf-steam"><b>Steam: </b><span class="hf-unknown">占位</span> <a href="#" target="_blank" class="hf-link steam-info-loaded">前往商店</a> (<a href="#" target="_blank">SteamDB</a>)</p></div>
          <div class="hf-line"></div>
        </div>
      </div>`

      document.body.appendChild(hfBox);

      const eleTitle = hfBox.querySelector("div.hf-head>span");
      const eleImg = hfBox.querySelector("div.hf-body>img");
      const eleDesc = hfBox.querySelector("div.hf-describe>span");
      const eleHfState = hfBox.querySelector("p.hf-hf>span");
      const eleHfLink = hfBox.querySelector("p.hf-hf>a");
      const eleSteamState = hfBox.querySelector("p.hf-steam>span");
      const eleSteamLink = hfBox.querySelector("p.hf-steam>a:first-of-type");
      const eleSteamDBLink = hfBox.querySelector("p.hf-steam>a:last-of-type");

      hfBox.addEventListener("mouseenter", diagMoveIn);
      hfBox.addEventListener("mouseleave", hideDiag);

      Object.assign(diagObjs, {
        hfBox, eleTitle, eleImg, eleDesc, eleHfState,
        eleHfLink, eleSteamState, eleSteamLink, eleSteamDBLink
      });
    }

    initDiag();

    const { script: { version } } = GM_info;
    const Tail = ` - 『Hikari Field Helper v${version} by Chr_』`;

    //更新小部件显示
    function showDiag(event) {
      isShow = true;
      clearTmout();

      const ele = event.target;
      const key = ele.getAttribute("data-hf");

      const { hfBox, eleTitle, eleImg, eleDesc, eleHfState, eleHfLink, eleSteamState, eleSteamLink, eleSteamDBLink } = diagObjs;

      const [gameName, appID, steamState, hfState] = INFO[key] ?? [null, null, null, null];
      const describe = DESC[key] ?? "";

      if (!gameName) { return; }

      eleTitle.title = gameName + Tail;
      eleTitle.textContent = gameName;
      eleImg.src = `https://cdn.cloudflare.steamstatic.com/steam/apps/${appID}/header.jpg`;

      eleDesc.textContent = describe.substr(0, 72) + "...";
      eleDesc.title = describe;

      switch (hfState) {
        case -1:
          eleHfState.textContent = "已下架";
          eleHfState.className = "hf-unavailable";
          break;
        case 1:
          eleHfState.textContent = "可购买";
          eleHfState.className = "hf-available";
          break;
        default:
          eleHfState.textContent = "未发售";
          eleHfState.className = "hf-unknown";
          break;
      }
      eleHfLink.href = HFSHOP + key;

      switch (steamState) {
        case -1:
          eleSteamState.textContent = "已下架";
          eleSteamState.className = "hf-unavailable";
          eleSteamLink.classList.add("hf-disabled");
          break;
        case 1:
          eleSteamState.textContent = "可购买";
          eleSteamState.className = "hf-available";
          eleSteamLink.classList.remove("hf-disabled");
          break;
        default:
          eleSteamState.textContent = "未发售";
          eleSteamState.className = "hf-unknown";
          eleSteamLink.classList.remove("hf-disabled");
          break;
      }
      eleSteamLink.href = `https://store.steampowered.com/app/${appID}/`;
      eleSteamDBLink.href = `https://steamdb.info/app/${appID}/`;

      const { top, right } = ele.getBoundingClientRect();

      const boxHeight = 303;
      const boxWidth = 300;

      const boxTop = Math.min(top, document.documentElement.clientHeight - boxHeight) + window.scrollY;
      const boxLeft = Math.min(right, document.documentElement.clientWidth - boxWidth) + window.scrollX;

      hfBox.style.left = `${boxLeft}px`;
      hfBox.style.top = `${boxTop}px`;
      hfBox.style.opacity = 1;
      hfBox.style.display = "";
    }
    //清除计时器
    function clearTmout() {
      if (timer !== -1) {
        clearTimeout(timer);
        timer = -1;
      }
    }
    //对话框鼠标移入
    function diagMoveIn(event) {
      clearTmout();
    }
    //隐藏小部件
    function hideDiag(event) {
      clearTmout();
      const { hfBox } = diagObjs;
      if (isShow) {
        timer = setTimeout(() => {
          isShow = false;
          timer = -1;
          hfBox.style.opacity = 0;
          setTimeout(() => {
            hfBox.style.cssText = "display:none; opacity: 0;";
          }, 200);
        }, 900);
      }
    }
  }

})();

GM_addStyle(`.hf-line {
  margin-top: 0.5em;
}
.hf-available {
  color: #6c3;
  padding: 0 0.3em;
}
.hf-unavailable {
  color: #e60;
  padding: 0 0.3em;
}
.hf-unknown {
  color: #ccc;
  padding: 0 0.3em;
}
.hf-available::before {
  content: "☑";
  padding-right: 0.3em;
}
.hf-unavailable::before {
  content: "☒";
  padding-right: 0.3em;
}
.hf-unknown::before {
  content: "☐";
  padding-right: 0.3em;
}
.hf-disabled {
  pointer-events: none;
  cursor: default;
  text-decoration: line-through !important;
}
.hf-detail > div {
  text-align: center;
}
.hf-box {
  width: 300px;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 100;
  background-color: #343a40;
  color: #ccc;
  border-radius: 5px;
  transition: all 0.2s;
}
.hf-box * {
  max-width: 100%;
}
.hf-head {
  overflow: hidden;
  white-space: nowrap;
  margin: 4px 0;
}
.hf-head > span {
  font-size: 12px;
  font-weight: bold;
  padding: 5px 16px;
}
.hf-head > a {
  position: absolute;
  right: 5px;
  cursor: pointer;
}
.hf-body {
  height: 140px;
}
.hf-foot {
  margin: 5px;
}
.hf-foot b {
  color: #8a959c;
  font-weight: normal;
}
.hf-foot a {
  color: #fff;
  text-decoration: none;
}

`);