// ==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;
}
`);