Steam库移除助手

快速移除Steam库中受限、正在了解和被Ban的游戏

  1. // ==UserScript==
  2. // @name Steam库移除助手
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description 快速移除Steam库中受限、正在了解和被Ban的游戏
  6. // @author lyzlyslyc
  7. // @match http*://steamcommunity.com/*/games/*
  8. // @match http*://help.steampowered.com/*
  9. // @icon https://store.steampowered.com/favicon.ico
  10. // @resource data https://cdn.jsdelivr.net/gh/lyzlyslyc/Scripts/SteamLimitedGames.json
  11. // @grant GM_getResourceText
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @connect help.steampowered.com
  16. // @license MIT
  17. // ==/UserScript==
  18.  
  19. (function() {
  20. 'use strict';
  21.  
  22. // Your code here...
  23. let removeList = GM_getValue("removeList");
  24. if(removeList===undefined)removeList={};
  25. if(document.domain=="help.steampowered.com"){
  26. let applist = [];
  27. let success = 0;
  28. let fail = 0;
  29. let div;
  30. let rows = {};
  31. let currentThread = 0;
  32. let maxThread = 5;
  33. let removeInterval_ms = 1000;
  34. let thread = null;
  35. initializeDiv();
  36. let table = document.getElementById("gameRemoveTable");
  37. for(let appid in removeList){
  38. if(removeList[appid].remove==true){
  39. applist.push(appid);
  40. rows[appid] = addGame(appid,removeList[appid].name);
  41. }
  42. }
  43. if(applist.length>0){
  44. document.getElementById("remove_gamecount").innerText=applist.length;
  45. div.style.display = "";
  46. }
  47.  
  48. function initializeDiv(){
  49. div = document.createElement("div");
  50. div.id="gameRemoveDiv";
  51. 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>`;
  52. div.style.display = "none";
  53. document.body.append(div);
  54. $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"));
  55. let btnStart = document.getElementById("btnStartRemove");
  56. btnStart.addEventListener("click",()=>{
  57. if(thread!=null){
  58. btnStart.innerText="开始移除";
  59. clearInterval(thread);
  60. thread=null;
  61. return;
  62. }
  63. btnStart.innerText="停止移除";
  64. thread = setInterval(removeLoop,removeInterval_ms);
  65. })
  66.  
  67. }
  68.  
  69. function addGame(appid,name){
  70. let row = table.tBodies[0].insertRow(0);
  71. let appidCell=row.insertCell(0);
  72. let nameCell=row.insertCell(1);
  73. let statusCell = row.insertCell(2);
  74. //let deleteCell = row.insertCell(3);
  75. appidCell.innerText=appid;
  76. nameCell.innerText=name;
  77. statusCell.innerText="未开始";
  78. //deleteCell.innerText="×";
  79. return row;
  80. }
  81.  
  82. function removeLoop(){
  83. while(currentThread<maxThread){
  84. let appid = applist.pop();
  85. if(appid===undefined&&thread!=null){
  86. clearInterval(thread);
  87. thread=null;
  88. document.getElementById("btnStartRemove").innerText="移除完毕";
  89. console.log("Removing stopped.");
  90. return;
  91. }
  92. removeOne(appid);
  93. }
  94. }
  95.  
  96. function removeOne(appid){
  97. currentThread++;
  98. rows[appid].style.background="#ff8c00";
  99. rows[appid].cells[2].innerText="请求中";
  100. getAjaxParams(appid).then(params=>doRemovePackage(params)).then(res=>{
  101. if(res.success==true){
  102. rows[appid].style.background="green";
  103. rows[appid].cells[2].innerText="成功";
  104. delete removeList[appid];
  105. GM_setValue("removeList",removeList);
  106. }
  107. else {
  108. rows[appid].style.background="red";
  109. rows[appid].cells[2].innerText="失败";
  110. delete removeList[appid];
  111. GM_setValue("removeList",removeList);
  112. }
  113. }).catch(err=>{
  114. console.log(err);
  115. rows[appid].style.background="red";
  116. rows[appid].cells[2].innerText=err;
  117. }).finally(()=>{currentThread--;})
  118. }
  119. }
  120. else{
  121. let data;
  122. try{
  123. data = JSON.parse(GM_getResourceText("data"));
  124. }
  125. catch(e){
  126. alert("Steam库移除助手:获取受限游戏数据失败,请刷新重试!");
  127. console.log(e);
  128. }
  129.  
  130. //添加筛选面板
  131. $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");
  132. $J("#gameslist_controls").after($J(`<div class="remove_options sort_options">
  133. <div class="remove_options_label">移除筛选</div><span>&nbsp;</span>
  134. <span class="remove_option_text"><input type="checkbox" id="chkShowLimited">显示受限</span><span>&nbsp;</span>
  135. <span class="remove_option_text"><input type="checkbox" id="chkShowLearning">显示了解中</span><span>&nbsp;</span>
  136. <span class="remove_option_text"><input type="checkbox" id="chkShowBanned">显示被Ban</span><span>&nbsp;</span>
  137. <span class="remove_option_text"><input type="checkbox" id="chkRemoveFree">选择免费</span><span>&nbsp;</span>
  138. <span class="remove_option_text"><input type="checkbox" id="chkRemoveCards">选择有卡</span><span>&nbsp;</span>
  139. <span class="remove_option_text"><input type="checkbox" id="chkRemoveAll">选择全部</span><span>&nbsp;</span>
  140. <span style="float:right">
  141. <span class="remove_option_text" style=""><input type="checkbox" id="chkShowListed">仅显示已选择</span><span>&nbsp;</span>
  142. <span class="remove_option_text" style="">已选择<span id="removeCount">2</span>项</span><span style="">&nbsp;</span>
  143. <a style="" target="_blank" href="https://help.steampowered.com/">前往客服页面移除</a>
  144. </span>
  145. </div>`));
  146. let chkShowLimited = document.querySelector("#chkShowLimited");
  147. let chkShowLearning = document.querySelector("#chkShowLearning");
  148. let chkShowBanned = document.querySelector("#chkShowBanned");
  149. let chkShowListed = document.querySelector("#chkShowListed");
  150. chkShowLimited.addEventListener("click",()=>handleFilterClick());
  151. chkShowLearning.addEventListener("click",()=>handleFilterClick());
  152. chkShowListed.addEventListener("click",()=>handleFilterClick());
  153. chkShowBanned.addEventListener("click",()=>handleFilterClick());
  154. //移除免费
  155. document.querySelector("#chkRemoveFree").addEventListener("click",(e)=>{
  156. document.querySelector("#games_list_row_container").style.display = "none";
  157. if(e.currentTarget.checked){
  158. document.querySelectorAll("#games_list_rows .gameListRow:not(.filtered)").forEach(row=>{
  159. //如果没显示,且是勾选事件,则不显示的不会被加入列表
  160. if(row.style.display=="none")return;
  161. if(data.FOD[row.appid]){
  162. handleRemoveButtonClick(row.btn,row.appid,row.name,true);
  163. }
  164. })
  165. }
  166. else{
  167. document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
  168. if(data.FOD[row.appid]){
  169. if(row.btn.isOnList!=e.currentTarget.checked)handleRemoveButtonClick(row.btn,row.appid,row.name,false);
  170. }
  171. });
  172. }
  173. document.querySelector("#games_list_row_container").style.display = "";
  174. GM_setValue("removeList",removeList);
  175. handleFilterClick();
  176. countSelectedGames();
  177. })
  178. //移除有卡
  179. document.querySelector("#chkRemoveCards").addEventListener("click",(e)=>{
  180. document.querySelector("#games_list_row_container").style.display = "none";
  181. if(e.currentTarget.checked){
  182. document.querySelectorAll("#games_list_rows .gameListRow:not(.filtered)").forEach(row=>{
  183. //如果没显示,且是勾选事件,则不显示的不会被加入列表
  184. if(row.style.display=="none")return;
  185. if(data.cards[row.appid]){
  186. handleRemoveButtonClick(row.btn,row.appid,row.name,true);
  187. }
  188. })
  189. }
  190. else{
  191. document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
  192. if(data.cards[row.appid]){
  193. if(row.btn.isOnList!=e.currentTarget.checked)handleRemoveButtonClick(row.btn,row.appid,row.name,false);
  194. }
  195. });
  196. }
  197. document.querySelector("#games_list_row_container").style.display = "";
  198. GM_setValue("removeList",removeList);
  199. handleFilterClick();
  200. countSelectedGames();
  201. })
  202. //移除全部
  203. document.querySelector("#chkRemoveAll").addEventListener("click",(e)=>{
  204. document.querySelector("#games_list_row_container").style.display = "none";
  205. if(e.currentTarget.checked){
  206. document.querySelectorAll("#games_list_rows .gameListRow:not(.filtered)").forEach(row=>{
  207. if(row.style.display=="none")return;
  208. handleRemoveButtonClick(row.btn,row.appid,row.name,true);
  209. })
  210. }
  211. else{
  212. document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
  213. //如果列表状态和勾选状态不一致,就点击按钮
  214. if(row.btn.isOnList!=e.currentTarget.checked)handleRemoveButtonClick(row.btn,row.appid,row.name,false);
  215. });
  216. }
  217. GM_setValue("removeList",removeList);
  218. document.querySelector("#games_list_row_container").style.display = "";
  219. handleFilterClick();
  220. countSelectedGames();
  221. })
  222. //添加移除按钮
  223. addButtons();
  224. //添加表格变化监视
  225. let observer = new MutationObserver((mutations)=>{
  226. console.log(mutations);
  227. handleFilterClick();
  228. countSelectedGames();
  229. });
  230. observer.observe(document.querySelector("#games_list_rows"),{attributes:true,attributeFilter: ['style'],subtree:true });
  231. countSelectedGames();
  232.  
  233. function toggleText(btn){
  234. if(btn.isOnList)btn.innerText = "移出移除列表";
  235. else btn.innerText = "加入移除列表";
  236. }
  237.  
  238. async function addButtons(){
  239. let limitedCount = 0;
  240. let learningCount = 0;
  241. let bannedCount = 0;
  242. let fodCount = 0;
  243. document.querySelector("#games_list_row_container").style.display = "none";
  244. document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
  245. //提取游戏appid和游戏名
  246. row.appid = row.id.match(/\d+/)[0];
  247. row.name = row.querySelector(".gameListRowItemName").innerText;
  248. //创建按钮
  249. let btn = document.createElement("a");
  250. btn.className = "pullup_item remove_item";
  251. btn.href = `javascript:void(0);`;
  252. btn.style = "padding:3px;";
  253. btn.isOnList = (removeList[row.appid]&&removeList[row.appid].remove==true);
  254. toggleText(btn);
  255. btn.addEventListener("click",()=>{handleRemoveButtonClick(btn,row.appid,row.name);GM_setValue("removeList",removeList);countSelectedGames();})
  256. row.querySelector(".bottom_controls").append(btn);
  257. row.btn=btn;
  258.  
  259. if(data.limited[row.appid])limitedCount++;
  260. if(data.learning[row.appid])learningCount++;
  261. if(data.banned[row.appid])bannedCount++;
  262.  
  263. let tags = $J("<span></span>");
  264. if(data.FOD[row.appid]){
  265. tags[0].innerText+="免费 ";
  266. $J(row.querySelector(".gameListRowItemName")).after(tags);
  267. if(data.limited[row.appid]||data.learning[row.appid]||data.banned[row.appid])fodCount++;
  268. }
  269. if(data.cards[row.appid]){
  270. tags[0].innerText+="有卡 ";
  271. $J(row.querySelector(".gameListRowItemName")).after(tags);
  272. }
  273. })
  274. document.querySelector("#games_list_row_container").style.display = "";
  275. console.log(`共${limitedCount}个受限游戏,${learningCount}个正在了解游戏,${bannedCount}被Ban游戏,这些游戏中有${fodCount}个免费游戏。`);
  276. }
  277.  
  278. async function handleRemoveButtonClick(btn,appid,name,isOnList){
  279. if(isOnList)btn.isOnList=isOnList;
  280. else btn.isOnList=!btn.isOnList;
  281. if(removeList[appid])removeList[appid].remove=btn.isOnList;
  282. else removeList[appid] = {name:name,remove:btn.isOnList};
  283. toggleText(btn,btn.isOnList);
  284. }
  285.  
  286. async function handleFilterClick(){
  287. document.querySelector("#games_list_row_container").style.display = "none";
  288. document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
  289. let appid = row.id.match(/\d+/)[0];
  290. let name = row.querySelector(".gameListRowItemName").innerText;
  291. let filtered = false;
  292. //仅显示已选择
  293. if(chkShowListed.checked)filtered = !row.querySelector(".remove_item").isOnList;
  294. //如果都没有选择,就都不筛选
  295. if((!chkShowLimited.checked)&&(!chkShowLearning.checked)&&(!chkShowBanned.checked))filtered||=false;
  296. //如果选择至少一个,且不游戏在相应名单中,就将游戏筛选
  297. else filtered ||=!((chkShowLimited.checked&&data.limited[appid])||(chkShowLearning.checked&&data.learning[appid])||(chkShowBanned.checked&&data.banned[appid]));
  298. $J(row).toggleClass("filtered",filtered);
  299. });
  300. document.querySelector("#games_list_row_container").style.display = "";
  301. }
  302.  
  303. function countSelectedGames(){
  304. let count = 0;
  305. document.querySelectorAll("#games_list_rows .gameListRow").forEach(row=>{
  306. if(row.btn.isOnList)count++;
  307. });
  308. document.getElementById("removeCount").innerText=count;
  309. }
  310. }
  311.  
  312. function getAjaxParams(appid){
  313. return new Promise((resolve,reject)=>{
  314. GM_xmlhttpRequest({
  315. url:`https://help.steampowered.com/zh-cn/wizard/HelpWithGameIssue/?appid=${appid}&issueid=123`,
  316. method:"GET",
  317. timeout:5000,
  318. onload:(res)=>{
  319. let text = res.responseText;
  320. if(text.search("m_steamid")==-1){
  321. reject("Steam客服页面未登录");
  322. return;
  323. }
  324. let match = text.match(/g_sessionID = "([0-9a-zA-Z]+)";/);
  325. if(match==null){
  326. reject("未获取到Steam客服页面SessionID");
  327. return;
  328. }
  329. let parser = new DOMParser();
  330. let doc = parser.parseFromString(text,"text/html");
  331. let packageid = doc.querySelector("#packageid").value;
  332. resolve({ appid:appid, packageid:packageid, sessionid: match[1], wizard_ajax: 1, gamepad: 0 });
  333. },
  334. ontimeout:()=>{reject(`获取参数超时`)},
  335. onerror:(err)=>{reject(`获取参数出错:`+JSON.stringify(err))}
  336. })
  337. })
  338. }
  339.  
  340. function doRemovePackage(ajaxParams){
  341. return new Promise((resolve,reject)=>{
  342. $J.ajax({
  343. type: 'POST',
  344. url: 'https://help.steampowered.com/zh-cn/wizard/AjaxDoPackageRemove',
  345. data: `packageid=${ajaxParams.packageid}&appid=${ajaxParams.appid}&sessionid=${ajaxParams.sessionid}&wizard_ajax=1&gamepad=0`
  346. }).fail((xhr)=>reject(`移除出错`))
  347. .done((res)=>{
  348. resolve(res);
  349. })
  350. })
  351. }
  352.  
  353. })();