Hikari_Field入库游戏检测
// ==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; } `);