├── License.txt ├── ReadMe.md ├── Steam Guide Section Linker.user.js ├── SteamAwardsClicker.user.js ├── SteamAwardsVote.js ├── SteamNominations.js ├── SteamNominationsSmart.js ├── SteamQueueSpinner.user.js └── SteamRacingFest2022.js /License.txt: -------------------------------------------------------------------------------- 1 | For source code: 2 | 3 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | For non source code: 6 | 7 | This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 8 | https://creativecommons.org/licenses/by-sa/4.0/ -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Steam Scripts 2 | 3 | Most of stuff here is small situational scripts/userscripts for things like summer/winter sales. You should be able to understand what it's about from name/description alone. 4 | 5 | Otherwise feel free to ask me personally. 6 | 7 | [Profile](https://steamcommunity.com/id/ZeroUnderscoreOu/) / [Group](https://steamcommunity.com/groups/0_oWassup) -------------------------------------------------------------------------------- /Steam Guide Section Linker.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Steam Guide Section Linker 3 | // @author ZeroUnderscoreOu 4 | // @version 1.0.1 5 | // @icon 6 | // @description Provides easy way to copy section links from Steam guides. 7 | // @namespace https://github.com/ZeroUnderscoreOu/ 8 | // @match https://steamcommunity.com/sharedfiles/filedetails/* 9 | // @run-at document-idle 10 | // @grant none 11 | // ==/UserScript== 12 | 13 | var CustomStyle = document.createElement("Style"); 14 | 15 | CustomStyle.textContent = ` 16 | A.SGSL {Display: None;} 17 | *.guideSubSectionSelection:Hover A.SGSL {Display: Unset;} 18 | `; 19 | document.head.append(CustomStyle); 20 | 21 | for (let L of document.getElementsByClassName("guideSubSectionSelectionLink")) { 22 | let A = document.createElement("A"); 23 | let ID = L.parentElement.id.split("_")[1]; // section ID 24 | if (ID == "0") { // there's no dedicated ID for overview section; using top navigation instead (will break someday) 25 | ID = "global_header"; 26 | }; 27 | A.href = `#${ID}`; 28 | A.className = "SGSL"; 29 | A.textContent = "\uD83D\uDD17"; // 🔗 link emoji 30 | L.prepend(A); 31 | }; 32 | -------------------------------------------------------------------------------- /SteamAwardsClicker.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Steam Awards Clicker 3 | // @author ZeroUnderscoreOu 4 | // @version 1.0.2 5 | // @icon 6 | // @description Clicker for a random Steam Awards vote 7 | // @namespace https://github.com/ZeroUnderscoreOu/ 8 | // @match *://store.steampowered.com/SteamAwards/ 9 | // @run-at document-idle 10 | // @grant none 11 | // ==/UserScript== 12 | 13 | let Nominations = document.querySelectorAll("Div.vote_nomination Div.btn_vote"); 14 | let Vote = Math.floor(Math.random()*Nominations.length); 15 | Nominations[Vote].click(); -------------------------------------------------------------------------------- /SteamAwardsVote.js: -------------------------------------------------------------------------------- 1 | // 1.3.0-2022Winter 2 | 3 | var LinkVote = "https://store.steampowered.com/salevote"; 4 | var Votes = [ // fallback votes; empty for random 5 | 1245620, // ELDEN RING 6 | 1592190, // BONELAB 7 | 570, // Dota 2 8 | 1144200, // Ready or Not 9 | 698670, // Scorn 10 | 1637320, // Dome Keeper 11 | 1245620, // ELDEN RING 12 | 1462040, // FINAL FANTASY VII REMAKE INTERGRADE 13 | 1703340, // The Stanley Parable: Ultra Deluxe 14 | 1455840, // Dorfromantik 15 | 1794680, // Vampire Survivors 16 | ]; 17 | var Shift = 72; // starting nomination index; continues from previous year 18 | var NominatedApps = []; 19 | var MarkedAsNominated = document.getElementsByClassName("user_nominated_app"); 20 | 21 | if (MarkedAsNominated.length > 0) { 22 | for (let App of MarkedAsNominated) { 23 | App = App.parentElement; 24 | NominatedApps[App.dataset["voteid"]] = App.dataset["voteAppid"]; 25 | console.log(`Previously nominated app ${App.dataset["voteAppid"]} in category ${App.dataset["voteid"]}`); 26 | }; 27 | }; 28 | 29 | function GenerateVotes() { 30 | g_rgAwardSectionScrollDefs.forEach((Section) => { 31 | let Category = Section.voteid; 32 | if (Category in NominatedApps) { 33 | Votes[Category - Shift] = NominatedApps[Category]; 34 | } else { 35 | let VoteRNG = Math.floor(Math.random() * Section.rgApps.length); 36 | VoteRNG = Section.rgApps[VoteRNG].dataset["voteAppid"]; 37 | Votes[Category - Shift] = VoteRNG; 38 | console.log(`Randomly picked app ${VoteRNG} for Section ${Category}`); 39 | }; 40 | }); 41 | PostVotes(); 42 | }; 43 | 44 | function PostVotes(Category = 0) { 45 | var Init = { 46 | method: "Post", 47 | credentials: "include", 48 | body: new FormData() 49 | }; 50 | Init.body.append("sessionid", g_sessionID); 51 | Init.body.append("voteid", Category + Shift); 52 | Init.body.append("appid", Votes[Category]); 53 | Init.body.append("developerid", 0); 54 | fetch(LinkVote, Init).catch((Message) => { 55 | console.error("Voting error:", Message); 56 | }); 57 | if (++Category < Votes.length) { 58 | setTimeout(PostVotes, 500, Category); 59 | } else { 60 | console.log("Voting done"); 61 | }; 62 | }; 63 | 64 | if (Votes.length > 0) { 65 | PostVotes(); 66 | } else { 67 | GenerateVotes(); 68 | }; 69 | -------------------------------------------------------------------------------- /SteamNominations.js: -------------------------------------------------------------------------------- 1 | // 1.0.0-Winter2018 2 | 3 | var Link = "https://store.steampowered.com/salevote"; 4 | var Nominations = [ 5 | 863550, 6 | 617830, 7 | 570, 8 | 32989758, 9 | 750920, 10 | 730, 11 | 289070, 12 | 227300 13 | ]; 14 | var Form = new FormData(); 15 | var Init = { 16 | method: "Post", 17 | credentials: "include", 18 | body: Form 19 | }; 20 | Form.append("sessionid",g_sessionID); 21 | NominationPost(); 22 | 23 | function NominationPost(VoteId=1) { 24 | if (VoteId==4) { //developer nomination 25 | Form.set("appid",0); 26 | Form.set("developerid",Nominations[VoteId-1]); 27 | } else { 28 | Form.set("appid",Nominations[VoteId-1]); 29 | Form.set("developerid",0); 30 | }; 31 | Form.set("voteid",VoteId+25); // first index is 26 32 | fetch(Link,Init).then((Data)=>(Data.text())).then((Data)=>{ 33 | Data = Data.match(/breakafter">\s+(.*?)\s+<\/div>/); 34 | Data = Data ? Data[1] : "No data"; 35 | console.log(Data); 36 | if (VoteId!=Nominations.length) { 37 | setTimeout(NominationPost,1000,++VoteId); 38 | } else { 39 | document.location.href = "https://store.steampowered.com/SteamAwards/2018/"; 40 | }; 41 | }).catch((Message)=>{console.error("SteamNominations error:",Message);}); 42 | }; -------------------------------------------------------------------------------- /SteamNominationsSmart.js: -------------------------------------------------------------------------------- 1 | // 1.1.3-2022Autumn 2 | /* 3 | As far as I understand, source parameter marks the location from which a game was nominated: 4 | 1 - store page, skipped nominations 5 | 2 - awards page, suggested game 6 | 3 - awards page, search result game 7 | Can also use new URL to post nominations 8 | https://store.steampowered.com/steamawards/ajaxnominategame 9 | */ 10 | 11 | var LinkNominate = "https://store.steampowered.com/steamawards/nominategame"; 12 | var LinkCategory = "https://store.steampowered.com/steamawards/category/"; 13 | var Nominations = [ // fallback nominations 14 | 1245620, // ELDEN RING 15 | 1599560, // Wanderer; use 2215130 to skip 16 | 2004320, // Duelyst II 17 | 1385380, // Across the Obelisk 18 | 698670, // Scorn 19 | 1260520, // Patrick's Parabox 20 | 1494260, // Loot River 21 | 1332010, // Stray 22 | 1221250, // NORCO 23 | 1622770, // Doors: Paradox 24 | 1794680 // Vampire Survivors; use 2218020 to skip 25 | ]; 26 | var Shift = 72; // starting nomination index; continues from previous year 27 | var Suggestions = []; // storing used suggestions; can't nominate same game multiple types 28 | var Form = new FormData(); 29 | var Init = { 30 | method: "Post", 31 | credentials: "include", 32 | body: Form 33 | }; 34 | var Session; 35 | 36 | try { 37 | Session = g_sessionID; 38 | } catch { 39 | try { 40 | Session = document.cookie.match(/sessionid=([^;]+)/)[1]; 41 | } catch(Data) { 42 | console.log("Can't get session Id",Data); 43 | }; 44 | }; 45 | 46 | if (Session) { 47 | Form.append("sessionid",Session); 48 | Form.append("source",3); 49 | OutsmartingGabe(); 50 | }; 51 | 52 | function OutsmartingGabe(Nomination=0) { 53 | fetch(`${LinkCategory}${Nomination+Shift}`,{credentials:"include"}) 54 | .then((Data)=>(Data.text())) 55 | .then((Data)=>{ 56 | Data = Data.match(/data-ds-appid="\d+"/g); 57 | if (Data) { 58 | Data = Data.map((Id)=>(parseInt(Id.match(/\d+/)[0]))); // for filter() to work & for consistency 59 | Data = Data.filter((Id)=>(!(Nominations.includes(Id)||Suggestions.includes(Id)))); // removing duplicates, if any 60 | if (Data.length) { // if any suggetstions present 61 | let A = Math.floor(Math.random()*Data.length); 62 | Nominations[Nomination] = Data[A]; 63 | Suggestions.push(Data[A]); 64 | console.log(`#${Nomination} - new nomination ${Data[A]}`); 65 | console.log(Suggestions); 66 | } else { 67 | console.log(`#${Nomination} - no suggestions; using fallback`); 68 | }; 69 | } else { 70 | console.log(`#${Nomination} - no suggestions; using fallback`); 71 | }; 72 | NominationPost(Nomination); 73 | if (++Nomination{console.error("Nominating error:",Data)}); 77 | }; 78 | 79 | function NominationPost(Nomination) { 80 | Form.set("nominatedid",Nominations[Nomination]); 81 | Form.set("categoryid",Nomination+Shift); // nomination Ids increase over the years 82 | fetch(LinkNominate,Init).then((Data)=>(Data.json())).then((Data)=>{ 83 | if (Data&&Data.success==1) { 84 | Data = Data.rgCategories[Nomination].label; 85 | }; 86 | console.log(Data); 87 | if (Nomination==Nominations.length-1) { 88 | console.log("All done, opening the Steam Awards page"); 89 | setTimeout(()=>{ 90 | document.location.href = "https://store.steampowered.com/steamawards/nominations"; 91 | },3000); 92 | }; 93 | }).catch((Message)=>{console.error("Posting error:",Message);}); 94 | }; 95 | -------------------------------------------------------------------------------- /SteamQueueSpinner.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Steam Queue Spinner 3 | // @author ZeroUnderscoreOu 4 | // @version 1.4.0 5 | // @icon 6 | // @description Spinner for your Steam Discovery Queue 7 | // @namespace https://github.com/ZeroUnderscoreOu/ 8 | // @match *://store.steampowered.com/explore* 9 | // @run-at document-idle 10 | // @grant none 11 | // ==/UserScript== 12 | 13 | /* 14 | Почему относительные протокола адреса ("//") нормально работают в консоли, но выдают ошибку в GM? Пришлось жёстко вписать протокол (с учётом введённой поддержки HTTPS Стимом). 15 | Переписать стиль спэна на инлайн? 16 | */ 17 | 18 | "use strict"; 19 | 20 | var IntervalId; 21 | var Queues = document.querySelector("Div.discover_queue_empty Div.subtext"); // amount of queues to clear; attempting to get amount of cards 22 | var Style = document.createElement("Style"); 23 | var Button = document.createElement("Div"); 24 | var Div = document.querySelector("Div.discovery_queue_customize_ctn"); 25 | var RGAD = []; 26 | 27 | // unsafeWindow variables; don't know any better way to read page variables 28 | var GSID; 29 | var GSession; 30 | 31 | try { // only Greasemonkey supports this, I think 32 | GSID = unsafeWindow.GStoreItemData; 33 | GSession = unsafeWindow.g_sessionID; 34 | } catch (Data) { // other managers should be able to access page variables normally, I think 35 | console.error("SQS - unsafeWindow not supported?",Data); 36 | GSID = GStoreItemData; 37 | GSession = g_sessionID; 38 | }; 39 | 40 | try { 41 | Queues = parseInt(Queues.textContent.match(/-?\d/)[0],10); 42 | } catch (Data) { 43 | console.log("SQS - defaulting queues to 1:",Data); 44 | Queues = 1; // defaulting to 1 as it seems to be the standard now 45 | }; 46 | /* 47 | if (Queues<1) { // potential fix for Steam error 48 | Queues = 3; 49 | }; 50 | */ 51 | 52 | console.log("SQS - queues expected:",Queues); 53 | Style.textContent = "#QueueButton {Min-Width: 100px; Text-Align: Center;}"; 54 | Button.id = "QueueButton"; 55 | Button.className = "btnv6_blue_hoverfade btn_medium"; 56 | 57 | try { 58 | RGAD = Object.keys(GSID.rgAppData); 59 | } catch (Data) { 60 | console.error("SQS - couldn't read rgAppData:",Data); 61 | }; 62 | 63 | if (RGAD.length==0) { // if page queue is empty 64 | Button.addEventListener("click",QueueGenerate); 65 | } else { 66 | Button.addEventListener("click",QueueGet); 67 | }; 68 | 69 | Button = Button.appendChild(document.createElement("Span")) 70 | Button.textContent = "Spin"; 71 | document.head.appendChild(Style); 72 | Div.insertBefore(Button.parentElement,Div.firstElementChild); 73 | 74 | function QueueGet(Data,Queue) { // intentionally not providing default value for Queue 75 | // there is a problem with queue when there are unavailable apps in it: 76 | // Store itself manages it fine now, but spinning fails; 77 | // queue should contain full list, but IDK how to get it from page itself, 78 | // so I use rgAppData as a fallback 79 | if (Data&&Data.ctrlKey) { // generating new queue if Ctrl is held during click 80 | console.log("SQS - forcing queue generation"); 81 | QueueGenerate(); 82 | return; 83 | }; 84 | var Ids = Queue || RGAD; 85 | Button.textContent = `Spin (${Queues*12})`; // for visibility with empty queues 86 | console.log("SQS -",Queues,Ids.join(", ")); 87 | Queues--; 88 | IntervalId = setInterval(QueueClear,500,Ids); 89 | }; 90 | 91 | function QueueGenerate() { 92 | var Address = "https://store.steampowered.com/explore/generatenewdiscoveryqueue"; 93 | var Data = new FormData(); 94 | var Init = { 95 | method: "Post", 96 | body: Data, 97 | credentials: "same-origin" 98 | }; 99 | Data.set("sessionid",GSession); 100 | Data.set("queuetype",0); 101 | fetch(Address,Init) 102 | .then((Data)=>(Data.json())) 103 | .then((Data)=>{ 104 | if (Data.rgAppData==undefined) { // IDK why it's happening; need testing 105 | console.log("SQS - bad response:",Data.rgAppData,Data.queue,"\r\n",Data); 106 | Data.rgAppData = {}; 107 | }; 108 | RGAD = Object.keys(Data.rgAppData); // writing for fallback 109 | console.log("SQS - queue generated"); 110 | QueueGet(null,Data.queue); 111 | }) 112 | .catch((Data)=>{ 113 | console.error("SQS - QueueGenerate():",Data); 114 | }); 115 | }; 116 | 117 | function QueueClear(Ids) { 118 | var Id = Ids.shift(); 119 | if (!Id) { 120 | clearInterval(IntervalId); 121 | if (Queues>0) { 122 | QueueGenerate(); 123 | } else { 124 | Button.textContent = "Done"; // for better visibility 125 | Queues = 1; // resetting to 1 in case user wants to spin again 126 | console.log("SQS - queues cleared"); 127 | }; 128 | return; 129 | }; 130 | var Address = "https://store.steampowered.com/app/" + Id; 131 | var Data = new FormData(); 132 | var Init = { 133 | method: "Post", 134 | body: Data, 135 | credentials: "same-origin" 136 | }; 137 | Data.set("sessionid",GSession); 138 | Data.set("appid_to_clear_from_queue",Id); 139 | fetch(Address,Init).catch((Data)=>{ 140 | console.error("SQS - QueueClear():",Data); 141 | }); 142 | Button.textContent = `Spin (${Queues*12+Ids.length})`; 143 | }; 144 | -------------------------------------------------------------------------------- /SteamRacingFest2022.js: -------------------------------------------------------------------------------- 1 | var LinkPost = "https://store.steampowered.com/saleaction/ajaxopendoor"; 2 | var Form = new FormData(); 3 | var Init = { 4 | method: "Post", 5 | credentials: "include", 6 | body: Form, 7 | referrer: "https://store.steampowered.com/category/racing/?tab=15" 8 | }; 9 | var Session; 10 | var Token; 11 | var Check = true; 12 | 13 | try { 14 | Session = g_sessionID; 15 | if (!Session) { 16 | Session = document.cookie.match(/sessionid=([^;]+)/)[1]; 17 | }; 18 | } catch(Data) { 19 | console.error("Error getting session",Data); 20 | Check = false; 21 | }; 22 | 23 | try { 24 | let C = document.getElementById("application_config"); 25 | Token = JSON.parse(C.getAttribute("data-userinfo")).authwgtoken; 26 | } catch(Data) { 27 | console.error("Error getting token",Data); 28 | Check = false; 29 | }; 30 | 31 | Form.append("sessionid",Session); 32 | Form.append("authwgtoken",Token); 33 | 34 | OpenDoor(0); 35 | 36 | 37 | 38 | function OpenDoor(Door) { 39 | Form.set("door_index",Door); 40 | fetch(LinkPost,Init).then((Data)=>(Data.json())).then((Data)=>{ 41 | console.log("Success:",Data.success); 42 | if (Door==4) { 43 | console.log("New tabID",Data.capsuleinsert.tabID); 44 | let tabID = Data.capsuleinsert.tabID; 45 | Init.referrer = `https://store.steampowered.com/category/racing/?tab=${tabID}`; 46 | }; 47 | if (Door==5) { 48 | console.log("All done, opening the badge page"); 49 | setTimeout(()=>{ 50 | document.location.href = "https://steamcommunity.com/my/badges/59"; 51 | },3000); 52 | } else { 53 | setTimeout(OpenDoor,1000,++Door); 54 | }; 55 | }).catch((Message)=>{ 56 | console.error("Posting error:",Message); 57 | }); 58 | }; 59 | --------------------------------------------------------------------------------