// ==UserScript==
// @name Steam库移除助手
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 快速移除Steam库中受限、正在了解和被Ban的游戏
// @author lyzlyslyc
// @match http*://steamcommunity.com/*/games/*
// @match http*://help.steampowered.com/*
// @icon https://store.steampowered.com/favicon.ico
// @resource data https://cdn.jsdelivr.net/gh/lyzlyslyc/Scripts/SteamLimitedGames.json
// @grant GM_getResourceText
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @connect help.steampowered.com
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Your code here...
let removeList = GM_getValue("removeList");
if(removeList===undefined)removeList={};
if(document.domain=="help.steampowered.com"){
let applist = [];
let success = 0;
let fail = 0;
let div;
let rows = {};
let currentThread = 0;
let maxThread = 5;
let removeInterval_ms = 1000;
let thread = null;
initializeDiv();
let table = document.getElementById("gameRemoveTable");
for(let appid in removeList){
if(removeList[appid].remove==true){
applist.push(appid);
rows[appid] = addGame(appid,removeList[appid].name);
}
}
if(applist.length>0){
document.getElementById("remove_gamecount").innerText=applist.length;
div.style.display = "";
}
function initializeDiv(){
div = document.createElement("div");
div.id="gameRemoveDiv";
div.innerHTML = `<h2 style="text-align:center; margin-bottom: 5px">检测到需要移除的游戏</h2><div style="overflow: auto;"><span>共<span id="remove_gamecount">0</span>项</span><table style="width: 100%;text-align: center;" id="gameRemoveTable"><thead><tr><th>appid</th><th>游戏名</th><th>状态</th></tr></thead><tbody></tbody></table></div><div id="btnStartRemove" class="btn_green_white_innerfade" style="margin-top: 10px;width: 100%;text-align: center;line-height: 30px;">开始移除</div>`;
div.style.display = "none";
document.body.append(div);
$J('<style type="text/css">#gameRemoveDiv{padding: 10px;position: fixed;top: 20%;right: 0;background: rgb(30, 45, 64);max-height: 40%;display: flex;flex-direction: column;max-width: 30%;z-index: 999;}#gameRemoveTable,#gameRemoveTable tr th,#gameRemoveTable tr td{border:1px solid;padding: 5px;}</style>').appendTo($J("head"));
let btnStart = document.getElementById("btnStartRemove");
btnStart.addEventListener("click",()=>{
if(thread!=null){
btnStart.innerText="开始移除";
clearInterval(thread);
thread=null;
return;
}
btnStart.innerText="停止移除";
thread = setInterval(removeLoop,removeInterval_ms);
})
}
function addGame(appid,name){
let row = table.tBodies[0].insertRow(0);
let appidCell=row.insertCell(0);
let nameCell=row.insertCell(1);
let statusCell = row.insertCell(2);
//let deleteCell = row.insertCell(3);
appidCell.innerText=appid;
nameCell.innerText=name;
statusCell.innerText="未开始";
//deleteCell.innerText="×";
return row;
}
function removeLoop(){
while(currentThread<maxThread){
let appid = applist.pop();
if(appid===undefined&&thread!=null){
clearInterval(thread);
thread=null;
document.getElementById("btnStartRemove").innerText="移除完毕";
console.log("Removing stopped.");
return;
}
removeOne(appid);
}
}
function removeOne(appid){
currentThread++;
rows[appid].style.background="#ff8c00";
rows[appid].cells[2].innerText="请求中";
getAjaxParams(appid).then(params=>doRemovePackage(params)).then(res=>{
if(res.success==true){
rows[appid].style.background="green";
rows[appid].cells[2].innerText="成功";
delete removeList[appid];
GM_setValue("removeList",removeList);
}
else {
rows[appid].style.background="red";
rows[appid].cells[2].innerText="失败";
delete removeList[appid];
GM_setValue("removeList",removeList);
}
}).catch(err=>{
console.log(err);
rows[appid].style.background="red";
rows[appid].cells[2].innerText=err;
}).finally(()=>{currentThread--;})
}
}
else{
let data;
try{
data = JSON.parse(GM_getResourceText("data"));
}
catch(e){
alert("Steam库移除助手:获取受限游戏数据失败,请刷新重试!");
console.log(e);
}
//添加筛选面板
$J(`<style type="text/css">.filtered{display:none !important}.remove_options_label{display:inline;}.remove_option_text{color:#fff}.remove_options input {vertical-align: middle;}</style>`).appendTo("head");
$J("#gameslist_controls").after($J(`<div class="remove_options sort_options">
<div class="remove_options_label">移除筛选</div><span> </span>
<span class="remove_option_text"><input type="checkbox" id="chkShowLimited">显示受限</span><span> </span>
<span class="remove_option_text"><input type="checkbox" id="chkShowLearning">显示了解中</span><span> </span>
<span class="remove_option_text"><input type="checkbox" id="chkShowBanned">显示被Ban</span><span> </span>
<span class="remove_option_text"><input type="checkbox" id="chkRemoveFree">选择免费</span><span> </span>
<span class="remove_option_text"><input type="checkbox" id="chkRemoveCards">选择有卡</span><span> </span>
<span class="remove_option_text"><input type="checkbox" id="chkRemoveAll">选择全部</span><span> </span>
<span style="float:right">
<span class="remove_option_text" style=""><input type="checkbox" id="chkShowListed">仅显示已选择</span><span> </span>
<span class="remove_option_text" style="">已选择<span id="removeCount">2</span>项</span><span style=""> </span>
<a style="" target="_blank" href="https://help.steampowered.com/">前往客服页面移除</a>
</span>
</div>`));
let chkShowLimited = document.querySelector("#chkShowLimited");
let chkShowLearning = document.querySelector("#chkShowLearning");
let chkShowBanned = document.querySelector("#chkShowBanned");
let chkShowListed = document.querySelector("#chkShowListed");
chkShowLimited.addEventListener("click",()=>handleFilterClick());
chkShowLearning.addEventListener("click",()=>handleFilterClick());
chkShowListed.addEventListener("click",()=>handleFilterClick());
chkShowBanned.addEventListener("click",()=>handleFilterClick());
//移除免费
document.querySelector("#chkRemoveFree").addEventListener("click",(e)=>{
document.querySelector("#games_list_row_container").style.display = "none";
if(e.currentTarget.checked){
document.querySelectorAll("#games_list_rows .gameListRow:not(.filtered)").forEach(row=>{
//如果没显示,且是勾选事件,则不显示的不会被加入列表
if(row.style.display=="none")return;
if(data.FOD[row.appid]){
handleRemoveButtonClick(row.btn,row.appid,row.name,true);
}
})
}
else{
document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
if(data.FOD[row.appid]){
if(row.btn.isOnList!=e.currentTarget.checked)handleRemoveButtonClick(row.btn,row.appid,row.name,false);
}
});
}
document.querySelector("#games_list_row_container").style.display = "";
GM_setValue("removeList",removeList);
handleFilterClick();
countSelectedGames();
})
//移除有卡
document.querySelector("#chkRemoveCards").addEventListener("click",(e)=>{
document.querySelector("#games_list_row_container").style.display = "none";
if(e.currentTarget.checked){
document.querySelectorAll("#games_list_rows .gameListRow:not(.filtered)").forEach(row=>{
//如果没显示,且是勾选事件,则不显示的不会被加入列表
if(row.style.display=="none")return;
if(data.cards[row.appid]){
handleRemoveButtonClick(row.btn,row.appid,row.name,true);
}
})
}
else{
document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
if(data.cards[row.appid]){
if(row.btn.isOnList!=e.currentTarget.checked)handleRemoveButtonClick(row.btn,row.appid,row.name,false);
}
});
}
document.querySelector("#games_list_row_container").style.display = "";
GM_setValue("removeList",removeList);
handleFilterClick();
countSelectedGames();
})
//移除全部
document.querySelector("#chkRemoveAll").addEventListener("click",(e)=>{
document.querySelector("#games_list_row_container").style.display = "none";
if(e.currentTarget.checked){
document.querySelectorAll("#games_list_rows .gameListRow:not(.filtered)").forEach(row=>{
if(row.style.display=="none")return;
handleRemoveButtonClick(row.btn,row.appid,row.name,true);
})
}
else{
document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
//如果列表状态和勾选状态不一致,就点击按钮
if(row.btn.isOnList!=e.currentTarget.checked)handleRemoveButtonClick(row.btn,row.appid,row.name,false);
});
}
GM_setValue("removeList",removeList);
document.querySelector("#games_list_row_container").style.display = "";
handleFilterClick();
countSelectedGames();
})
//添加移除按钮
addButtons();
//添加表格变化监视
let observer = new MutationObserver((mutations)=>{
console.log(mutations);
handleFilterClick();
countSelectedGames();
});
observer.observe(document.querySelector("#games_list_rows"),{attributes:true,attributeFilter: ['style'],subtree:true });
countSelectedGames();
function toggleText(btn){
if(btn.isOnList)btn.innerText = "移出移除列表";
else btn.innerText = "加入移除列表";
}
async function addButtons(){
let limitedCount = 0;
let learningCount = 0;
let bannedCount = 0;
let fodCount = 0;
document.querySelector("#games_list_row_container").style.display = "none";
document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
//提取游戏appid和游戏名
row.appid = row.id.match(/\d+/)[0];
row.name = row.querySelector(".gameListRowItemName").innerText;
//创建按钮
let btn = document.createElement("a");
btn.className = "pullup_item remove_item";
btn.href = `javascript:void(0);`;
btn.style = "padding:3px;";
btn.isOnList = (removeList[row.appid]&&removeList[row.appid].remove==true);
toggleText(btn);
btn.addEventListener("click",()=>{handleRemoveButtonClick(btn,row.appid,row.name);GM_setValue("removeList",removeList);countSelectedGames();})
row.querySelector(".bottom_controls").append(btn);
row.btn=btn;
if(data.limited[row.appid])limitedCount++;
if(data.learning[row.appid])learningCount++;
if(data.banned[row.appid])bannedCount++;
let tags = $J("<span></span>");
if(data.FOD[row.appid]){
tags[0].innerText+="免费 ";
$J(row.querySelector(".gameListRowItemName")).after(tags);
if(data.limited[row.appid]||data.learning[row.appid]||data.banned[row.appid])fodCount++;
}
if(data.cards[row.appid]){
tags[0].innerText+="有卡 ";
$J(row.querySelector(".gameListRowItemName")).after(tags);
}
})
document.querySelector("#games_list_row_container").style.display = "";
console.log(`共${limitedCount}个受限游戏,${learningCount}个正在了解游戏,${bannedCount}被Ban游戏,这些游戏中有${fodCount}个免费游戏。`);
}
async function handleRemoveButtonClick(btn,appid,name,isOnList){
if(isOnList)btn.isOnList=isOnList;
else btn.isOnList=!btn.isOnList;
if(removeList[appid])removeList[appid].remove=btn.isOnList;
else removeList[appid] = {name:name,remove:btn.isOnList};
toggleText(btn,btn.isOnList);
}
async function handleFilterClick(){
document.querySelector("#games_list_row_container").style.display = "none";
document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
let appid = row.id.match(/\d+/)[0];
let name = row.querySelector(".gameListRowItemName").innerText;
let filtered = false;
//仅显示已选择
if(chkShowListed.checked)filtered = !row.querySelector(".remove_item").isOnList;
//如果都没有选择,就都不筛选
if((!chkShowLimited.checked)&&(!chkShowLearning.checked)&&(!chkShowBanned.checked))filtered||=false;
//如果选择至少一个,且不游戏在相应名单中,就将游戏筛选
else filtered ||=!((chkShowLimited.checked&&data.limited[appid])||(chkShowLearning.checked&&data.learning[appid])||(chkShowBanned.checked&&data.banned[appid]));
$J(row).toggleClass("filtered",filtered);
});
document.querySelector("#games_list_row_container").style.display = "";
}
function countSelectedGames(){
let count = 0;
document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
if(row.btn.isOnList)count++;
});
document.getElementById("removeCount").innerText=count;
}
}
function getAjaxParams(appid){
return new Promise((resolve,reject)=>{
GM_xmlhttpRequest({
url:`https://help.steampowered.com/zh-cn/wizard/HelpWithGameIssue/?appid=${appid}&issueid=123`,
method:"GET",
timeout:5000,
onload:(res)=>{
let text = res.responseText;
if(text.search("m_steamid")==-1){
reject("Steam客服页面未登录");
return;
}
let match = text.match(/g_sessionID = "([0-9a-zA-Z]+)";/);
if(match==null){
reject("未获取到Steam客服页面SessionID");
return;
}
let parser = new DOMParser();
let doc = parser.parseFromString(text,"text/html");
let packageid = doc.querySelector("#packageid").value;
resolve({ appid:appid, packageid:packageid, sessionid: match[1], wizard_ajax: 1, gamepad: 0 });
},
ontimeout:()=>{reject(`获取参数超时`)},
onerror:(err)=>{reject(`获取参数出错:`+JSON.stringify(err))}
})
})
}
function doRemovePackage(ajaxParams){
return new Promise((resolve,reject)=>{
$J.ajax({
type: 'POST',
url: 'https://help.steampowered.com/zh-cn/wizard/AjaxDoPackageRemove',
data: `packageid=${ajaxParams.packageid}&appid=${ajaxParams.appid}&sessionid=${ajaxParams.sessionid}&wizard_ajax=1&gamepad=0`
}).fail((xhr)=>reject(`移除出错`))
.done((res)=>{
resolve(res);
})
})
}
})();