// ==UserScript==
// @name Find_Extra_card
// @name:zh-CN Steam寻找多余的卡牌
// @namespace https://blog.chrxw.com
// @version 1.7
// @description 查找徽章满级但是仍然有卡牌的游戏
// @description:zh-CN 查找徽章满级但是仍然有卡牌的游戏
// @author Chr_
// @include /https://steamcommunity\.com/(id|profiles)/[^\/]+/badges/?(\/$|\/\?)?/
// @supportURL https://steamcn.com/t339531-1-1
// @license AGPL-3.0
// @icon https://blog.chrxw.com/favicon.ico
// @grant GM_addStyle
// @grant GM_setClipboard
// ==/UserScript==
(() => {
"use strict";
const WorkTread = 5; // 抓取线程
const SleepTime = 50; // 抓取间隔
const { origin, pathname } = window.location;
const BadgeUrl = `${origin}${pathname}?sort=c&l=schinese&p=`;
const RegPureBadges = RegExp(/<div class="badges_sheet">[\s\S]+<div class="profile_paging">/);
const RegPureCards = RegExp(/<div class="badge_detail_tasks">[\s\S]+<div style="clear: left;">/);
const Line = "==============================\n";
let isWorking = false;
init();
//初始化
function init() {
const genBtn = (text, onclick) => {
const btn = document.createElement("button");
btn.textContent = text;
btn.className = "btn_medium fec_btn";
btn.addEventListener("click", onclick);
return btn;
};
const bar = document.querySelector(".profile_small_header_text");
const btnHelp = genBtn("❔说明", () => {
const { script: { version } } = GM_info;
ShowAlertDialog(``, [
`<h2>【插件版本 ${version}】</h2>`,
`<p>【📇查找本页】:查找当前页面中,徽章已经满级(5级),但是库中仍然有多余卡牌的游戏</p>`,
`<p><strike>【📇查找全部】:暂不可用</strike></p>`,
`<p>【<a href="https://keylol.com/t772471-1-1" target="_blank">发布帖</a>】 【<a href="https://blog.chrxw.com/scripts.html" target="_blank">脚本反馈</a>】 【Developed by <a href="https://steamcommunity.com/id/Chr_" target="_blank">Chr_</a>】</p>`
].join(""));
});
// const btnFindAll = genBtn("📇查找全部", findAllExtraCard);
const btnFindOne = genBtn("📇查找本页", findCurrExtraCard);
bar.appendChild(btnHelp);
// bar.appendChild(btnFindAll);
bar.appendChild(btnFindOne);
}
//读取当前页
async function findCurrExtraCard() {
const [title, text, btnAbort] = showDialog();
isWorking = true;
btnAbort.disabled = false;
title.innerText = "读取本页徽章信息";
text.value += `开始运行 线程数量:${WorkTread}\n${Line}【持有】/【一套】 | 【游戏名】\n` + Line;
const box = document.querySelector(".maincontent>.badges_sheet");
if (box !== null) {
const badges = parseDom2BadgeList(box);
let count = 0;
if (badges.length === 0) {
text.value += "没有找到任何满级徽章\n";
} else {
title.innerText = `运行进度 【 0 / ${badges.length} 】`;
for (let i = 0; i < badges.length && isWorking; i += WorkTread) {
const max = Math.min(i + WorkTread, badges.length);
const tasks = [];
for (let j = i; j < max; j++) {
const [url, title] = badges[j];
tasks.push(getCardInfo(url, title));
}
const values = await Promise.all(tasks);
for (const [succ, name, sum, total] of values) {
if (succ && sum > 0) {
count++;
text.value += `${sum} / ${total} | ${name}\n`;
}
}
title.innerText = `运行进度 【 ${max} / ${badges.length} 】`;
await aiosleep(SleepTime);
}
}
text.value += Line + `共找到 ${count} 个徽章满级但仍有剩余卡牌的游戏\n`;
} else {
text.value += Line + "没有找到任何徽章\n";
}
isWorking = false;
title.innerText = "运行结束";
btnAbort.disabled = true;
}
//读取全部
async function findAllExtraCard() {
// const res = await getCardInfo("233", "https://steamcommunity.com/id/Chr_/gamecards/630060/");
// console.log(res);
const textArea = document.querySelector("textarea");
textArea.className = "fec_text";
const [title, text, btnAbort] = showDialog();
}
//显示提示框
function showDialog() {
const genBtn = (text, onclick) => {
const btn = document.createElement("button");
btn.textContent = text;
btn.className = "btn_medium fec_btn";
if (onclick) { btn.addEventListener("click", onclick); }
return btn;
};
const area = document.createElement("div");
area.className = "fec_area";
const tit = document.createElement("h2");
tit.className = "fec_title";
tit.innerText = "";
const txt = document.createElement("textarea");
txt.className = "fec_text";
const action = document.createElement("div");
action.className = "fec_action";
const btnAbort = genBtn("⛔停止运行", () => {
if (isWorking) {
isWorking = false;
tit.innerText = "已停止";
}
});
btnAbort.disabled = true;
const btnClose = genBtn("❌关闭", null);
const btnCopy = genBtn("📋复制", () => {
GM_setClipboard(txt.value, "text");
btnCopy.innerText = "✅已复制";
setTimeout(() => { btnCopy.innerText = "📋复制"; }, 1000);
});
action.appendChild(btnCopy);
action.appendChild(btnAbort);
action.appendChild(btnClose);
area.appendChild(tit);
area.appendChild(txt);
area.appendChild(action);
const diag = ShowDialog("", area, { bExplicitDismissalOnly: true });
btnClose.addEventListener("click", () => { diag.Dismiss(); });
return [tit, txt, btnAbort];
}
//异步Sleep
function aiosleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
//解析徽章列表的DOM节点
function parseDom2BadgeList(ele) {
const badges = ele.querySelectorAll(".badge_row.is_link");
let maxBadges = [];
for (const badge of badges) {
const url = badge.querySelector("a.badge_row_overlay")?.href;
const level = badge.querySelector(".badge_info_description>div:nth-child(2)")?.innerText.trim() ?? "0 级";
const title = badge.querySelector(".badge_title")?.innerText.trim() ?? "Null";
if (url && level && level.startsWith("5 级")) {
maxBadges.push([url, title]);
}
}
return maxBadges;
}
//读取卡牌页面
function getCardInfo(url, title) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.text())
.then(html => {
const pureHtml = RegPureCards.exec(html)[0];
let box = document.createElement("div");
box.style.display = "none";
box.innerHTML = pureHtml;
const cardCount = box.querySelectorAll(".badge_card_set_text_qty");
const cardTotal = cardCount.length;
if (cardTotal === 0) { resolve([true, title, 0, 0]); }
let sum = 0;
for (let i = 0; i < cardTotal; i++) {
let text = cardCount[i].innerText;
let num = text.substring(1, text.length - 1);
try {
sum += parseInt(num);
} catch (e) {
console.error(e);
}
}
document.body.appendChild(box);
document.body.removeChild(box);
resolve([true, title, sum, cardTotal]);
})
.catch(err => {
console.error("请求失败", err);
resolve([false, null, null, null]);
});
});
}
//读取徽章页面
async function getBadgeList(page) {
await fetch(BadgeUrl + page)
.then(res => res.text())
.then(html => {
const pureHtml = RegPureBadges.exec(html)[0];
let box = document.createElement("div");
box.style.display = "none";
box.innerHTML = pureHtml;
let badges = parseDom2BadgeList(box);
badges.forEach(badge => {
const url = badge.querySelector("a")?.href;
const badgeInfo = badge.querySelector(".badge_info_description>div:nth-child(2)")?.innerText.trim();
if (url === null || badgeInfo === null) { return; }
if (badgeInfo.startsWith("5 级")) {
console.log(`${badgeInfo}`);
}
});
document.body.appendChild(box);
document.body.removeChild(box);
});
}
})();
GM_addStyle(`
.profile_small_header_text > .fec_btn {
float: right;
}
.profile_small_header_text > .fec_btn {
margin-left: 5px;
}
.fec_action > .fec_btn:not(:first-child) {
margin-left: 20px;
}
.fec_btn {
padding: 3px 6px;
}
.fec_action {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 5px;
}
.fec_action > .fec_btn {
flex: 0 0 auto;
}
.fec_text {
height: 300px;
width: 600px;
resize: vertical;
font-size: 15px;
margin: 5px 0;
padding: 5px;
background-color: rgba(0, 0, 0, 0.4);
color: #fff;
border: 1 px solid #000;
border-radius: 3 px;
box-shadow: 1px 1px 0px #45556c;
}
.fec_area {
display: flex;
flex-wrap: wrap;
justify-content: center;
width: 100%;
height: 100%;
}
.fec_area > * {
width: 100%;
}
`);