Fast_Add_Cart

Add to cart without redirect to cart page, also provide import/export cart feature.

安裝腳本?
作者推薦腳本

您可能也會喜歡 Hidden_DLC_Helper

安裝腳本
  1. // ==UserScript==
  2. // @name:zh-CN Steam快速添加购物车
  3. // @name Fast_Add_Cart
  4. // @namespace https://blog.chrxw.com
  5. // @supportURL https://blog.chrxw.com/scripts.html
  6. // @contributionURL https://afdian.com/@chr233
  7. // @version 4.6
  8. // @description:zh-CN 超级方便的添加购物车体验, 不用跳转商店页, 附带导入导出购物车功能.
  9. // @description Add to cart without redirect to cart page, also provide import/export cart feature.
  10. // @author Chr_
  11. // @match https://store.steampowered.com/*
  12. // @license AGPL-3.0
  13. // @icon https://blog.chrxw.com/favicon.ico
  14. // @grant GM_addStyle
  15. // @grant GM_setClipboard
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // @grant GM_registerMenuCommand
  19. // ==/UserScript==
  20.  
  21. (async () => {
  22. "use strict";
  23.  
  24. // 多语言
  25. const LANG = {
  26. ZH: {
  27. langName: "中文",
  28. changeLang: "修改插件语言",
  29. facInputBoxPlaceHolder:
  30. "一行一条, 自动忽略【#】后面的内容, 支持的格式如下: (自动保存)",
  31. storeLink: "商店链接",
  32. steamDBLink: "DB链接",
  33. import: "导入",
  34. importDesc: "从文本框批量添加购物车(从上到下导入)",
  35. importDesc2: "当前页面无法导入购物车",
  36. export: "导出",
  37. exportDesc: "将购物车内容导出至文本框",
  38. exportConfirm: "输入框中含有内容, 请选择操作?",
  39. exportConfirmReplace: "覆盖原有内容",
  40. exportConfirmAppend: "添加到最后",
  41. copy: "复制",
  42. copyDesc: "复制文本框中的内容",
  43. copyDone: "复制到剪贴板成功",
  44. reset: "清除",
  45. resetDesc: "清除文本框和已保存的数据",
  46. resetConfirm: "您确定要清除文本框和已保存的数据吗?",
  47. history: "历史",
  48. historyDesc: "查看购物车历史记录",
  49. reload: "刷新",
  50. reloadDesc: "重新读取保存的购物车内容",
  51. reloadConfirm: "您确定要重新读取保存的购物车数据吗?",
  52. goBack: "返回",
  53. goBackDesc: "返回你当前的购物车",
  54. clear: "清空购物车",
  55. clearDesc: "清空购物车",
  56. clearConfirm: "您确定要移除所有您购物车中的物品吗?",
  57. help: "帮助",
  58. helpDesc: "显示帮助",
  59. helpTitle: "插件版本",
  60. formatError: "格式有误",
  61. chooseSub: "请选择SUB",
  62. operation: "操作中……",
  63. operationDone: "操作完成",
  64. addCart: "添加购物车",
  65. addCartTips: "添加到购物车……",
  66. addCartErrorSubNotFount: "未识别到SubID",
  67. noSubDesc: "可能尚未发行或者是免费游戏",
  68. inCart: "在购物车中",
  69. importingTitle: "正在导入购物车……",
  70. add: "添加",
  71. toCart: "到购物车",
  72. tips: "提示",
  73. ok: "是",
  74. no: "否",
  75. fetchingSubs: "读取可用SUB",
  76. noSubFound: "未找到可用SUB",
  77. networkError: "网络错误",
  78. addCartSuccess: "添加购物车成功",
  79. addCartError: "添加购物车失败",
  80. networkRequestError: "网络请求失败",
  81. unknownError: "未知错误",
  82. unrecognizedResult: "返回了未知结果",
  83. batchExtract: "批量提取",
  84. batchExtractDone: "批量提取完成",
  85. batchDesc: "AppID已提取, 可以在购物车页批量导入",
  86. onlyOnsale: " 仅打折",
  87. onlyOnsaleDesc: "勾选后批量导入时仅导入正在打折的游戏.",
  88. onlyOnsaleDesc2: "勾选后批量导出时仅导出正在打折的游戏.",
  89. notOnSale: "尚未打折, 跳过",
  90. },
  91. EN: {
  92. langName: "English",
  93. changeLang: "Change plugin language",
  94. facInputBoxPlaceHolder:
  95. "One line one item, ignore the content after #, support format: (auto save)",
  96. storeLink: "Store link",
  97. steamDBLink: "DB link",
  98. import: "Import",
  99. importDesc: "Batch add cart from textbox (from top to bottom)",
  100. importDesc2: "Current page can't import cart",
  101. export: "Export",
  102. exportDesc: "Export cart content to textbox",
  103. exportConfirm: "Textbox contains content, please choose operation?",
  104. exportConfirmReplace: "Replace original content",
  105. exportConfirmAppend: "Append to the end",
  106. copy: "Copy",
  107. copyDesc: "Copy textbox content",
  108. copyDone: "Copy to clipboard success",
  109. reset: "Reset",
  110. resetDesc: "Clear textbox and saved data",
  111. resetConfirm: "Are you sure to clear textbox and saved cart data?",
  112. history: "History",
  113. historyDesc: "View cart history",
  114. reload: "Reload",
  115. reloadDesc: "Reload saved cart date",
  116. reloadConfirm: "Are you sure to reload saved cart data?",
  117. goBack: "Back",
  118. goBackDesc: "Back to your cart",
  119. clear: "Clear",
  120. clearDesc: "Clear cart",
  121. clearConfirm: "Are you sure to remove all items in your cart?",
  122. help: "Help",
  123. helpDesc: "Show help",
  124. helpTitle: "Plugin Version",
  125. formatError: "Format error",
  126. chooseSub: "Please choose SUB",
  127. operation: "Operation in progress……",
  128. operationDone: "Operation done",
  129. addCart: "Add cart",
  130. addCartTips: "Adding to cart……",
  131. addCartErrorSubNotFount: "Unrecognized SubID",
  132. noSubDesc: "Maybe not released or free game",
  133. inCart: "In cart",
  134. importingTitle: "Importing cart……",
  135. add: "Add",
  136. toCart: "To cart",
  137. tips: "Tips",
  138. ok: "OK",
  139. no: "No",
  140. fetchingSubs: "Fetching available SUB",
  141. noSubFound: "No available SUB",
  142. networkError: "Network error",
  143. addCartSuccess: "Add cart success",
  144. addCartError: "Add cart failed",
  145. networkRequestError: "Network request failed",
  146. unknownError: "Unknown error",
  147. unrecognizedResult: "Returned unrecognized result",
  148. batchExtract: "Extract Items",
  149. batchExtractDone: "Batch Extract Done",
  150. batchDesc: "AppID list now saved, goto cart page to use batch import.",
  151. onlyOnsale: " Only on sale",
  152. onlyOnsaleDesc:
  153. "If checked, script will ignore games that is not on sale when import cart.",
  154. onlyOnsaleDesc2:
  155. "If checked, script will ignore games that is not on sale when export cart.",
  156. notOnSale: "Not on sale, skip",
  157. },
  158. };
  159.  
  160. // 判断语言
  161. let language = GM_getValue("lang", "ZH");
  162. if (!language in LANG) {
  163. language = "ZH";
  164. GM_setValue("lang", language);
  165. }
  166. // 获取翻译文本
  167. const t = (key) => LANG[language][key] || key;
  168.  
  169. {
  170. // 自动弹出提示
  171. const languageTips = GM_getValue("languageTips", true);
  172. if (languageTips && language === "ZH") {
  173. if (!document.querySelector("html").lang.startsWith("zh")) {
  174. ShowConfirmDialog(
  175. "tips",
  176. "Fast add cart now support English, switch?",
  177. "Using English",
  178. "Don't show again"
  179. )
  180. .done(() => {
  181. GM_setValue("lang", "EN");
  182. GM_setValue("languageTips", false);
  183. window.location.reload();
  184. })
  185. .fail((bool) => {
  186. if (bool) {
  187. showAlert(
  188. "",
  189. "You can switch the plugin's language using TamperMonkey's menu."
  190. );
  191. GM_setValue("languageTips", false);
  192. }
  193. });
  194. }
  195. }
  196. //注册(不可用)菜单
  197. GM_registerMenuCommand(`${t("changeLang")} (${t("langName")})`, () => {
  198. switch (language) {
  199. case "EN":
  200. language = "ZH";
  201. break;
  202. case "ZH":
  203. language = "EN";
  204. break;
  205. }
  206. GM_setValue("lang", language);
  207. window.location.reload();
  208. });
  209. }
  210.  
  211. //获取商店语言和区域
  212. const { LANGUAGE: storeLanguage, COUNTRY: userCountry } = JSON.parse(document.querySelector("#application_config")?.getAttribute("data-config") ?? "{}");
  213. const { webapi_token: accessToken } = JSON.parse(document.querySelector("#application_config")?.getAttribute("data-store_user_config") ?? "{}");
  214.  
  215. const G_Objs = {};
  216.  
  217. //初始化
  218. const pathname = window.location.pathname;
  219. if (
  220. pathname === "/search/" ||
  221. pathname === "/" ||
  222. pathname.startsWith("/tags/")
  223. ) {
  224. //搜索页,主页,标签页
  225. return;
  226.  
  227. } else if (
  228. pathname.startsWith("/publisher/") ||
  229. pathname.startsWith("/franchise/") ||
  230. pathname.startsWith("/developer/")
  231. ) {
  232. //发行商主页
  233. return;
  234.  
  235. } else if (
  236. pathname.startsWith("/app/") ||
  237. pathname.startsWith("/sub/") ||
  238. pathname.startsWith("/bundle/")
  239. ) {
  240. //商店详情页
  241. return;
  242.  
  243. } else if (pathname.startsWith("/wishlist/")) {
  244. //愿望单页
  245. return;
  246.  
  247. } else if (pathname.startsWith("/cart")) {
  248. //购物车页
  249.  
  250. function genBr() {
  251. return document.createElement("br");
  252. }
  253. function genBtn(text, title, onclick) {
  254. let btn = document.createElement("button");
  255. btn.textContent = text;
  256. btn.title = title;
  257. btn.className = "btn_medium btnv6_blue_hoverfade fac_cartbtns";
  258. btn.addEventListener("click", onclick);
  259. return btn;
  260. }
  261. function genSpan(text) {
  262. let span = document.createElement("span");
  263. span.textContent = text;
  264. return span;
  265. }
  266. function genTxt(value, placeholder) {
  267. const t = document.createElement("textarea");
  268. t.className = "fac_inputbox";
  269. t.placeholder = placeholder;
  270. t.value = value;
  271. return t;
  272. }
  273. function genChk(name, title, checked = false) {
  274. const l = document.createElement("label");
  275. const i = document.createElement("input");
  276. const s = genSpan(name);
  277. i.textContent = name;
  278. i.title = title;
  279. i.type = "checkbox";
  280. i.className = "fac_checkbox";
  281. i.checked = checked;
  282. l.title = title;
  283. l.appendChild(i);
  284. l.appendChild(s);
  285. return [l, i];
  286. }
  287.  
  288. const savedCart = GM_getValue("fac_cart") ?? "";
  289. const placeHolder = [
  290. t("facInputBoxPlaceHolder"),
  291. `1. ${t("storeLink")}: https://store.steampowered.com/app/xxx`,
  292. `2. ${t("steamDBLink")}: https://steamdb.info/app/xxx`,
  293. "3. appID: xxx a/xxx app/xxx",
  294. "4. subID: s/xxx sub/xxx",
  295. "5. bundleID: b/xxx bundle/xxx",
  296. ].join("\n");
  297.  
  298. const inputBox = genTxt(savedCart, placeHolder);
  299.  
  300. function fitInputBox() {
  301. inputBox.style.height =
  302. Math.min(inputBox.value.split("\n").length * 20 + 20, 900).toString() +
  303. "px";
  304. }
  305.  
  306. inputBox.addEventListener("input", fitInputBox);
  307. G_Objs.inputBox = inputBox;
  308. fitInputBox();
  309.  
  310. const originResetBtn = document.querySelector("div.remove_ctn");
  311. if (originResetBtn != null) {
  312. originResetBtn.style.display = "none";
  313. }
  314.  
  315. const [lblDiscount, chkDiscount] = genChk(
  316. t("onlyOnsale"),
  317. t("onlyOnsaleDesc"),
  318. GM_getValue("fac_discount") ?? false
  319. );
  320. G_Objs.chkDiscount = chkDiscount;
  321.  
  322. const btnImport = genBtn(`🔼${t("import")}`, t("importDesc"), async () => {
  323. inputBox.value = await importCart(
  324. inputBox.value,
  325. chkDiscount.checked
  326. );
  327. });
  328.  
  329. const histryPage = pathname.search("history") !== -1;
  330. if (histryPage) {
  331. btnImport.disabled = true;
  332. btnImport.title = t("importDesc2");
  333. }
  334.  
  335. const [lblDiscount2, chkDiscount2] = genChk(
  336. t("onlyOnsale"),
  337. t("onlyOnsaleDesc2"),
  338. GM_getValue("fac_discount2") ?? false
  339. );
  340. G_Objs.chkDiscount2 = chkDiscount2;
  341.  
  342. const btnExport = genBtn(`🔽${t("export")}`, t("exportDesc"), async () => {
  343. let currentValue = inputBox.value.trim();
  344. const now = new Date().toLocaleString();
  345.  
  346. var data = await exportCart(chkDiscount2.checked);
  347.  
  348. if (currentValue !== "") {
  349. ShowConfirmDialog(
  350. "",
  351. t("exportConfirm"),
  352. t("exportConfirmReplace"),
  353. t("exportConfirmAppend")
  354. )
  355. .done(() => {
  356. inputBox.value = `========【${now}】=========\n` + data;
  357. fitInputBox();
  358. })
  359. .fail((bool) => {
  360. if (bool) {
  361. inputBox.value = currentValue + `\n========【${now}】=========\n` + data;
  362. fitInputBox();
  363. }
  364. });
  365. } else {
  366. inputBox.value =
  367. `========【${now}】=========\n` + exportCart(chkDiscount2.checked);
  368. fitInputBox();
  369. }
  370. });
  371.  
  372. const btnConvertToGift = genBtn("转送礼", "将购物车项目转换为送礼", () => {
  373. editCart(true, false);
  374. });
  375.  
  376. const btnConvertToSelf = genBtn("转自用", "将购物车项目转换为为自己购买", () => {
  377. editCart(false, false);
  378. });
  379.  
  380. const btnCopy = genBtn(`📋${t("copy")}`, t("copyDesc"), () => {
  381. GM_setClipboard(inputBox.value, "text");
  382. showAlert(t("tips"), t("copyDone"), true);
  383. });
  384. const btnClear = genBtn(`🗑️${t("reset")}`, t("resetDesc"), () => {
  385. ShowConfirmDialog("", t("resetConfirm"), t("ok"), t("no")).done(() => {
  386. inputBox.value = "";
  387. GM_setValue("fac_cart", "");
  388. fitInputBox();
  389. });
  390. });
  391. const btnReload = genBtn(`🔃${t("reload")}`, t("reloadDesc"), () => {
  392. ShowConfirmDialog("", t("reloadConfirm"), t("ok"), t("no")).done(() => {
  393. const s = GM_getValue("fac_cart") ?? "";
  394. inputBox.value = s;
  395. fitInputBox();
  396. });
  397. });
  398. const btnHistory = genBtn(`📜${t("history")}`, t("historyDesc"), () => {
  399. window.location.href =
  400. "https://help.steampowered.com/zh-cn/accountdata/ShoppingCartHistory";
  401. });
  402. const btnBack = genBtn(`↩️${t("goBack")}`, t("goBackDesc"), () => {
  403. window.location.href = "https://store.steampowered.com/cart/";
  404. });
  405. const btnForget = genBtn(`⚠️${t("clear")}`, t("clearDesc"), () => {
  406. ShowConfirmDialog("", t("clearConfirm"), t("ok"), t("no")).done(() => {
  407. deleteAccountCart()
  408. .then(() => {
  409. location.reload();
  410. })
  411. .catch((err) => {
  412. console.error(err);
  413. showAlert("出错", err, false);
  414. });
  415. });
  416. });
  417. const btnHelp = genBtn(`🔣${t("help")}`, t("helpDesc"), () => {
  418. const {
  419. script: { version },
  420. } = GM_info;
  421. showAlert(
  422. `${t("helpTitle")} ${version}`,
  423. [
  424. `<p>【🔼${t("import")}】${t("importDesc")}</p>`,
  425. `<p>【✅${t("onlyOnsale")}】${t("onlyOnsaleDesc")}</p>`,
  426. `<p>【🔽${t("export")}】${t("exportDesc")}</p>`,
  427. `<p>【✅${t("onlyOnsale")}】${t("onlyOnsaleDesc2")}</p>`,
  428. `<p>【📋${t("copy")}】${t("copyDesc")}</p>`,
  429. `<p>【🗑️${t("reset")}】${t("resetDesc")}。</p>`,
  430. `<p>【📜${t("history")}】${t("historyDesc")}</p>`,
  431. `<p>【↩️${t("goBack")}】${t("goBackDesc")}</p>`,
  432. `<p>【⚠️${t("clear")}】${t("clearDesc")}</p>`,
  433. `<p>【🔣${t("help")}】${t("helpDesc")}</p>`,
  434. `<p>【<a href="https://keylol.com/t747892-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>`,
  435. ].join("<br>"),
  436. true
  437. );
  438. });
  439.  
  440. const btnArea = document.createElement("div");
  441. btnArea.appendChild(btnImport);
  442. // btnArea.appendChild(btnImport2);
  443. btnArea.appendChild(lblDiscount);
  444. btnArea.appendChild(genSpan(" | "));
  445. btnArea.appendChild(btnExport);
  446. btnArea.appendChild(lblDiscount2);
  447. btnArea.appendChild(genSpan(" | "));
  448. btnArea.appendChild(btnConvertToGift);
  449. btnArea.appendChild(btnConvertToSelf);
  450. btnArea.appendChild(genSpan(" | "));
  451. btnArea.appendChild(btnHelp);
  452.  
  453. const btnArea2 = document.createElement("div");
  454. btnArea2.appendChild(btnCopy);
  455. btnArea2.appendChild(btnClear);
  456. btnArea2.appendChild(btnReload);
  457. btnArea2.appendChild(genSpan(" | "));
  458. btnArea2.appendChild(histryPage ? btnBack : btnHistory);
  459. btnArea2.appendChild(genSpan(" | "));
  460. btnArea2.appendChild(btnForget);
  461.  
  462. window.addEventListener("beforeunload", () => {
  463. GM_setValue("fac_cart", inputBox.value);
  464. GM_setValue("fac_discount", chkDiscount.checked);
  465. GM_setValue("fac_discount2", chkDiscount2.checked);
  466. });
  467.  
  468. //等待购物车加载完毕, 显示额外面板
  469. const timer = setInterval(() => {
  470. const continer = document.querySelector("div[data-featuretarget='react-root']>div>div:last-child>div:last-child>div:first-child>div:last-child");
  471. if (continer) {
  472. clearInterval(timer);
  473. continer.appendChild(btnArea);
  474. continer.appendChild(genBr());
  475. continer.appendChild(inputBox);
  476. continer.appendChild(genBr());
  477. continer.appendChild(btnArea2);
  478. }
  479. }, 500);
  480. }
  481.  
  482. // getStoreItem([730], null, null).then((data) => console.log(data)).catch(err => console.error(err))
  483. // getAccountCart().then((data) => console.log(data)).catch(err => console.error(err))
  484. // addItemsToAccountCart(null, [28627]).then((data) => console.log(data)).catch(err => console.error(err))
  485.  
  486. //始终在右上角显示购物车按钮
  487. const cart_btn = document.getElementById("store_header_cart_btn");
  488. if (cart_btn !== null) {
  489. cart_btn.style.display = "";
  490. }
  491.  
  492. //导入购物车
  493. function importCart(text, onlyOnSale = false) {
  494. const regFull = new RegExp(/(app|a|bundle|b|sub|s)\/(\d+)/);
  495. const regShort = new RegExp(/^([\s]*|)(\d+)/);
  496.  
  497. return new Promise(async (resolve, reject) => {
  498. const dialog = showAlert(
  499. "导入购物车",
  500. `<h2 id="fac_diag" class="fac_diag">${t("operation")}</h2>`,
  501. true
  502. );
  503.  
  504. const timer = setInterval(async () => {
  505. let txt = document.getElementById("fac_diag");
  506. if (txt) {
  507. clearInterval(timer);
  508.  
  509. const txts = text.split("\n");
  510.  
  511. const result = [];
  512.  
  513. const appIds = [];
  514. const subIds = [];
  515. const bundleIds = [];
  516.  
  517. const targetSubIds = [];
  518. const targetBundleIds = [];
  519.  
  520. try {
  521. txt.textContent = "0/4 开始读取输入信息";
  522.  
  523. for (let line of txts) {
  524. if (line.trim() === "") {
  525. continue;
  526. }
  527. const tmp = line.split("#")[0];
  528.  
  529. const match = line.match(regFull) ?? line.match(regShort);
  530. if (!match) {
  531. if (line.search("=====") === -1) {
  532. result.push(`${tmp} #${t("formatError")}`);
  533. } else {
  534. result.push(line);
  535. }
  536. continue;
  537. }
  538.  
  539. let [_, type, subID] = match;
  540. subID = parseInt(subID);
  541. if (subID !== subID) {
  542. result.push(`${tmp} #${t("formatError")}`);
  543. continue;
  544. }
  545.  
  546. switch (type.toLowerCase()) {
  547. case "":
  548. case "a":
  549. case "app":
  550. type = "app";
  551. appIds.push(subID);
  552. break;
  553. case "s":
  554. case "sub":
  555. type = "sub";
  556. subIds.push(subID);
  557. break;
  558. case "b":
  559. case "bundle":
  560. type = "bundle";
  561. bundleIds.push(subID);
  562. break;
  563. default:
  564. result.push(`${tmp} #${t("formatError")}`);
  565. continue;
  566. }
  567. }
  568.  
  569. const count = appIds.length + subIds.length + bundleIds.length;
  570. txt.textContent = `1/4 成功读取 ${count} 个输入内容`;
  571.  
  572. if (count > 0) {
  573. txt.textContent = "1/4 开始读取游戏信息";
  574. const store_items = await getStoreItem(appIds, subIds, bundleIds);
  575.  
  576. console.log(store_items);
  577.  
  578. txt.textContent = "2/4 读取游戏信息成功";
  579.  
  580. for (let { appid, purchase_options } of store_items) {
  581. if (!purchase_options) { continue; }
  582.  
  583. //输入值包含AppId, 解析可购买项
  584. if (appIds.includes(appid)) {
  585. if (purchase_options.length >= 1) {
  586. const { packageid, bundleid, purchase_option_name: name, discount_pct: discount, formatted_final_price: price } = purchase_options[0];
  587. if (discount) {
  588. if (packageid) {
  589. result.push(`sub/${packageid} #app/${appid} #${name} 💳 ${price} 🔖 ${discount}`);
  590. targetSubIds.push(packageid);
  591. } else if (bundleid) {
  592. result.push(`bundle/${bundleid} #app/${appid} #${name} 💳 ${price} 🔖 ${discount}`);
  593. targetBundleIds.push(bundleid);
  594. }
  595. } else {
  596. if (packageid) {
  597. if (!onlyOnSale) {
  598. result.push(`sub/${packageid} #app/${appid} #${name} 💳 ${price}`);
  599. targetSubIds.push(packageid);
  600. } else {
  601. result.push(`sub/${packageid} #app/${appid} #排除 #${name} 💳 ${price}`);
  602. }
  603. } else if (bundleid) {
  604. if (!onlyOnSale) {
  605. result.push(`bundle/${bundleid} #app/${appid} #${name} 💳 ${price}`);
  606. targetBundleIds.push(bundleid);
  607. } else {
  608. result.push(`bundle/${bundleid} #app/${appid} #排除 #${name} 💳 ${price}`);
  609. }
  610. }
  611. }
  612. } else {
  613. result.push(`${tmp} #无可购买项目`);
  614. }
  615. }
  616.  
  617. for (let { packageid, bundleid, purchase_option_name: name, discount_pct: discount, formatted_final_price: price } of purchase_options) {
  618. if (discount) {
  619. if (packageid && subIds.includes(packageid)) {
  620. result.push(`sub/${packageid} #${name} 💳 ${price} 🔖 ${discount}`);
  621. targetSubIds.push(packageid);
  622. } else if (bundleid && bundleIds.includes(bundleid)) {
  623. result.push(`bundle/${bundleid} #${name} 💳 ${price} 🔖 ${discount}`);
  624. targetBundleIds.push(bundleid);
  625. }
  626. } else {
  627. if (packageid && subIds.includes(packageid)) {
  628. if (!onlyOnSale) {
  629. result.push(`sub/${packageid} #${name} 💳 ${price}`);
  630. targetSubIds.push(packageid);
  631. } else {
  632. result.push(`sub/${packageid} #排除 #${name} 💳 ${price}`);
  633.  
  634. }
  635. } else if (bundleid && bundleIds.includes(bundleid)) {
  636. if (!onlyOnSale) {
  637. result.push(`bundle/${bundleid} #${name} 💳 ${price}`);
  638. targetBundleIds.push(bundleid);
  639. } else {
  640. result.push(`bundle/${bundleid} #排除 #${name} 💳 ${price}`);
  641. }
  642. }
  643. }
  644. }
  645. }
  646.  
  647. txt.textContent = "3/4 解析游戏信息成功";
  648.  
  649. const data = await addItemsToAccountCart(targetSubIds, targetBundleIds, false);
  650. console.log(data);
  651.  
  652. txt.textContent = "4/4 导入购物车成功";
  653.  
  654. dialog.Dismiss();
  655.  
  656. resolve(result.join("\n"));
  657.  
  658. setTimeout(() => {
  659. window.location.reload();
  660. }, 1000);
  661.  
  662. } else {
  663. txt.textContent = "4/4 尚未输入有效内容";
  664. resolve(result.join("\n"));
  665. }
  666.  
  667. } catch (err) {
  668. txt.textContent = "导出购物车失败";
  669. console.error(err);
  670. resolve(result.join("\n"));
  671. }
  672. }
  673. }, 200);
  674. });
  675. }
  676. //导出购物车
  677. function exportCart(onlyOnsale = false) {
  678. return new Promise(async (resolve, reject) => {
  679. const dialog = showAlert(
  680. "导出购物车",
  681. `<h2 id="fac_diag" class="fac_diag">${t("operation")}</h2>`,
  682. true
  683. );
  684.  
  685. const timer = setInterval(async () => {
  686. let txt = document.getElementById("fac_diag");
  687. if (txt) {
  688. clearInterval(timer);
  689.  
  690. const result = [];
  691.  
  692. const subIds = [];
  693. const bundleIds = [];
  694. const gameNames = {};
  695.  
  696. try {
  697. txt.textContent = "0/4 开始读取账号购物车";
  698.  
  699. const { line_items } = await getAccountCart();
  700.  
  701. if (line_items) {
  702. for (let { packageid, bundleid } of line_items) {
  703. if (packageid) {
  704. subIds.push(packageid);
  705. }
  706. else if (bundleid) {
  707. bundleIds.push(bundleid);
  708. }
  709. }
  710. }
  711.  
  712. const count = subIds.length + bundleIds.length;
  713. txt.textContent = `1/4 成功读取 ${count} 个购物车内容`;
  714.  
  715. if (count > 0) {
  716. txt.textContent = "1/4 开始读取游戏信息";
  717. const store_items = await getStoreItem(null, subIds, bundleIds);
  718. txt.textContent = "2/4 读取游戏信息成功";
  719.  
  720. for (let { purchase_options } of store_items) {
  721. if (!purchase_options) { continue; }
  722.  
  723. for (let { packageid, bundleid, purchase_option_name, discount_pct } of purchase_options) {
  724. let key;
  725. if (packageid) {
  726. key = `sub/${packageid}`;
  727. } else if (bundleid) {
  728. key = `bundle/${bundleid}`;
  729. }
  730. else {
  731. continue;
  732. }
  733. gameNames[key] = [`${purchase_option_name}`, discount_pct];
  734. }
  735. }
  736.  
  737. txt.textContent = "3/4 解析游戏信息成功";
  738. txt.textContent = "3/4 开始导出购物车信息";
  739. if (line_items) {
  740. for (let { packageid, bundleid, price_when_added: { formatted_amount } } of line_items) {
  741. let key;
  742. if (packageid) {
  743. key = `sub/${packageid}`;
  744. } else if (bundleid) {
  745. key = `bundle/${bundleid}`;
  746. }
  747. const [name, discount] = gameNames[key] ?? "_";
  748. if (discount) {
  749. result.push(`${key} #${name} 💳 ${formatted_amount} 🔖 ${discount}`);
  750. } else if (!onlyOnsale) {
  751. result.push(`${key} #${name} 💳 ${formatted_amount}`);
  752. }
  753. }
  754. }
  755.  
  756. txt.textContent = "3/4 导出购物车信息成功";
  757. dialog.Dismiss();
  758.  
  759. resolve(result.join("\n"));
  760.  
  761. } else {
  762. txt.textContent = "4/4 购物车内容为空";
  763. resolve(result.join("\n"));
  764. }
  765.  
  766. } catch (err) {
  767. txt.textContent = "读取账号购物车失败";
  768. console.error(err);
  769. resolve(result.join("\n"));
  770. }
  771. }
  772. }, 200);
  773. });
  774. }
  775.  
  776. //编辑购物车
  777. async function editCart(setToGift = false, setToPrivate = false) {
  778. const setGiftInfo = await inputGiftee(setToGift);
  779.  
  780. return new Promise(async (resolve, reject) => {
  781. const dialog = showAlert(
  782. "编辑购物车",
  783. `<h2 id="fac_diag" class="fac_diag">${t("operation")}</h2>`,
  784. true
  785. );
  786.  
  787. const timer = setInterval(async () => {
  788. let txt = document.getElementById("fac_diag");
  789. if (txt) {
  790. clearInterval(timer);
  791.  
  792. const lineItemIds = [];
  793.  
  794. try {
  795. txt.textContent = "0/3 开始读取账号购物车";
  796.  
  797. const { line_items } = await getAccountCart();
  798.  
  799. if (line_items) {
  800. for (const { line_item_id, flags: { is_gift, is_private }, gift_info } of line_items) {
  801. const accountid_giftee = gift_info?.accountid_giftee?.toString() ?? "";
  802.  
  803. console.log(line_item_id, is_gift, is_private, accountid_giftee);
  804.  
  805. //跳过无需处理的id
  806. if (setToGift) {
  807. if (is_gift && setGiftInfo?.accountid_giftee == accountid_giftee) {
  808. continue;
  809. }
  810. } else if (setToPrivate) {
  811. if (is_private) {
  812. continue;
  813. }
  814. }
  815. else {
  816. if (!is_gift && !is_private) {
  817. continue;
  818. }
  819. }
  820.  
  821. lineItemIds.push(line_item_id);
  822. }
  823. }
  824.  
  825. console.log(lineItemIds);
  826.  
  827. const count = lineItemIds.length;
  828. txt.textContent = `1/3 共计 ${count} 个待修改购物车内容`;
  829.  
  830. if (count > 0) {
  831. for (let i = 0; i < count; i++) {
  832. const itemId = lineItemIds[i];
  833. await editAccountCart(itemId, setToGift, setToPrivate, setGiftInfo);
  834. txt.textContent = `2/3 当前进度 ${i + 1} / ${count}`;
  835. }
  836.  
  837. txt.textContent = "3/3 批量修改购物车成功";
  838.  
  839. setTimeout(() => dialog.Dismiss(), 1000);
  840. resolve();
  841. } else {
  842. txt.textContent = "3/3 购物车无需修改";
  843.  
  844. setTimeout(() => dialog.Dismiss(), 1000);
  845. resolve();
  846. }
  847.  
  848. } catch (err) {
  849. txt.textContent = "读取账号购物车失败";
  850. console.error(err);
  851. resolve();
  852. }
  853. }
  854. }, 200);
  855. });
  856. }
  857.  
  858. const steamIdConvert = BigInt("0x110000100000000");
  859.  
  860. function inputGiftee(isGift = false) {
  861. const nickname = document.querySelector("#account_pulldown")?.textContent?.trim() ?? "unknown";
  862.  
  863. return new Promise((resolve, reject) => {
  864. if (!isGift) {
  865. resolve(null);
  866. } else {
  867. ShowPromptDialog("提示", "请输入礼物接收人的 SteamID 或者好友代码, 可以留空", "确认", "跳过")
  868. .done(text => {
  869. try {
  870. let steamId = BigInt(text.trim());
  871. if (steamId == 0) {
  872. throw "输入数值有误";
  873. }
  874.  
  875. if (steamId > steamIdConvert) {
  876. steamId -= steamIdConvert;
  877. }
  878. const giftInfo = {
  879. accountid_giftee: steamId.toString(),
  880. gift_message: {
  881. gifteename: steamId.toString(),
  882. message: "Send by Fast_Add_Cart",
  883. sentiment: nickname,
  884. signature: nickname
  885. },
  886. time_scheduled_send: 0
  887. };
  888. resolve(giftInfo);
  889. } catch (err) {
  890. ShowAlertDialog("提示", "输入数值有误")
  891. .then(() => resolve(null));
  892. }
  893. })
  894. .fail(() => {
  895. resolve(null);
  896. });
  897. }
  898. });
  899. }
  900.  
  901. // 获取游戏详情
  902. function getStoreItem(appIds = null, subIds = null, bundleIds = null) {
  903. return new Promise((resolve, reject) => {
  904. const ids = [];
  905. if (appIds) {
  906. for (let x of appIds) {
  907. ids.push({ appid: x });
  908. }
  909. }
  910. if (subIds) {
  911. for (let x of subIds) {
  912. ids.push({ packageid: x });
  913. }
  914. }
  915. if (bundleIds) {
  916. for (let x of bundleIds) {
  917. ids.push({ bundleid: x });
  918. }
  919. }
  920.  
  921. if (ids.length === 0) {
  922. reject([false, "未提供有效ID"]);
  923. }
  924.  
  925. const payload = {
  926. ids,
  927. context: {
  928. language: storeLanguage,
  929. country_code: userCountry,
  930. steam_realm: "1"
  931. },
  932. data_request: {
  933. include_all_purchase_options: true
  934. }
  935. };
  936. const json = encodeURI(JSON.stringify(payload));
  937. fetch(
  938. `https://api.steampowered.com/IStoreBrowseService/GetItems/v1/?input_json=${json}`,
  939. {
  940. method: "GET",
  941. }
  942. )
  943. .then(async (response) => {
  944. if (response.ok) {
  945. const { response: { store_items } } = await response.json();
  946. resolve(store_items);
  947. } else {
  948. reject(t("networkRequestError"));
  949. }
  950. })
  951. .catch((err) => {
  952. reject(err);
  953. });
  954. });
  955. }
  956.  
  957. //读取购物车
  958. function getAccountCart() {
  959. return new Promise((resolve, reject) => {
  960. fetch(
  961. `https://api.steampowered.com/IAccountCartService/GetCart/v1/?access_token=${accessToken}`,
  962. {
  963. method: "GET",
  964. }
  965. )
  966. .then(async (response) => {
  967. if (response.ok) {
  968. const { response: { cart } } = await response.json();
  969. resolve(cart);
  970. } else {
  971. reject(t("networkRequestError"));
  972. }
  973. })
  974. .catch((err) => {
  975. reject(err);
  976. });
  977. });
  978. }
  979.  
  980. //添加购物车
  981. function addItemsToAccountCart(subIds = null, bundleIds = null, isPrivate = false) {
  982. return new Promise((resolve, reject) => {
  983. const items = [];
  984. if (subIds) {
  985. for (let x of subIds) {
  986. items.push({ packageid: x });
  987. }
  988. }
  989. if (bundleIds) {
  990. for (let x of bundleIds) {
  991. items.push({ bundleid: x });
  992. }
  993. }
  994. if (items.length === 0) {
  995. reject([false, "未提供有效ID"]);
  996. }
  997.  
  998. for (let x of items) {
  999. x["gift_info"] = null; //giftInfo;
  1000. x["flags"] = {
  1001. is_gift: false,
  1002. is_private: isPrivate == true
  1003. };
  1004. }
  1005.  
  1006. const payload = {
  1007. user_country: userCountry,
  1008. items,
  1009. navdata: {
  1010. domain: "store.steampowered.com",
  1011. controller: "default",
  1012. method: "default",
  1013. submethod: "",
  1014. feature: "spotlight",
  1015. depth: 1,
  1016. countrycode: userCountry,
  1017. webkey: 0,
  1018. is_client: false,
  1019. curator_data: {
  1020. clanid: null,
  1021. listid: null
  1022. },
  1023. is_likely_bot: false,
  1024. is_utm: false
  1025. }
  1026. };
  1027. const json = JSON.stringify(payload);
  1028.  
  1029. fetch(
  1030. `https://api.steampowered.com/IAccountCartService/AddItemsToCart/v1/?access_token=${accessToken}`,
  1031. {
  1032. method: "POST",
  1033. body: `input_json=${json}`,
  1034. headers: {
  1035. "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  1036. },
  1037. }
  1038. )
  1039. .then(async (response) => {
  1040. if (response.ok) {
  1041. const { response: { cart } } = await response.json();
  1042. resolve(cart);
  1043. } else {
  1044. reject(t("networkRequestError"));
  1045. }
  1046. })
  1047. .catch((err) => {
  1048. reject(err);
  1049. });
  1050. });
  1051. }
  1052.  
  1053. //编辑购物车
  1054. function editAccountCart(itemId, isGift, isPrivate, giftInfo = null) {
  1055. return new Promise((resolve, reject) => {
  1056. const payload = {
  1057. line_item_id: itemId,
  1058. user_country: userCountry,
  1059. gift_info: giftInfo,
  1060. flags: {
  1061. is_gift: isGift,
  1062. is_private: isPrivate,
  1063. }
  1064. };
  1065. const json = JSON.stringify(payload);
  1066.  
  1067. console.log(json);
  1068.  
  1069. fetch(
  1070. `https://api.steampowered.com/IAccountCartService/ModifyLineItem/v1/?access_token=${accessToken}`,
  1071. {
  1072. method: "POST",
  1073. body: `input_json=${json}`,
  1074. headers: {
  1075. "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  1076. },
  1077. }
  1078. )
  1079. .then(async (response) => {
  1080. if (response.ok) {
  1081. const { response: { line_item_ids, cart } } = await response.json();
  1082. resolve([line_item_ids, cart]);
  1083. } else {
  1084. reject(t("networkRequestError"));
  1085. }
  1086. })
  1087. .catch((err) => {
  1088. reject(err);
  1089. });
  1090. });
  1091. }
  1092.  
  1093. //删除购物车
  1094. function deleteAccountCart() {
  1095. return new Promise((resolve, reject) => {
  1096. fetch(
  1097. `https://api.steampowered.com/IAccountCartService/DeleteCart/v1/?access_token=${accessToken}`,
  1098. {
  1099. method: "POST",
  1100. }
  1101. )
  1102. .then(async (response) => {
  1103. if (response.ok) {
  1104. const { response: { line_item_ids, cart } } = await response.json();
  1105. resolve([line_item_ids, cart]);
  1106. } else {
  1107. reject(t("networkRequestError"));
  1108. }
  1109. })
  1110. .catch((err) => {
  1111. reject(err);
  1112. });
  1113. });
  1114. }
  1115.  
  1116. //显示提示
  1117. function showAlert(title, text, succ = true) {
  1118. return ShowAlertDialog(`${succ ? "✅" : "❌"}${title}`, text);
  1119. }
  1120. })();
  1121.  
  1122. GM_addStyle(`
  1123. button.fac_listbtns {
  1124. display: none;
  1125. position: relative;
  1126. z-index: 100;
  1127. padding: 1px;
  1128. }
  1129. a.search_result_row > button.fac_listbtns {
  1130. top: -25px;
  1131. left: 300px;
  1132. }
  1133. a.tab_item > button.fac_listbtns {
  1134. top: -40px;
  1135. left: 330px;
  1136. }
  1137. a.recommendation_link > button.fac_listbtns {
  1138. bottom: 10px;
  1139. right: 10px;
  1140. position: absolute;
  1141. }
  1142. div.wishlist_row > button.fac_listbtns {
  1143. top: 35%;
  1144. right: 30%;
  1145. position: absolute;
  1146. }
  1147. div.game_purchase_action > button.fac_listbtns {
  1148. right: 8px;
  1149. bottom: 8px;
  1150. }
  1151. button.fac_cartbtns {
  1152. padding: 5px 8px;
  1153. }
  1154. button.fac_cartbtns:not(:last-child) {
  1155. margin-right: 4px;
  1156. }
  1157. button.fac_cartbtns:not(:first-child) {
  1158. margin-left: 4px;
  1159. }
  1160. a.tab_item:hover button.fac_listbtns,
  1161. a.search_result_row:hover button.fac_listbtns,
  1162. div.recommendation:hover button.fac_listbtns,
  1163. div.wishlist_row:hover button.fac_listbtns {
  1164. display: block;
  1165. }
  1166. div.game_purchase_action:hover > button.fac_listbtns {
  1167. display: inline;
  1168. }
  1169. button.fac_choose {
  1170. padding: 1px;
  1171. margin: 2px 5px;
  1172. }
  1173. textarea.fac_inputbox {
  1174. resize: vertical;
  1175. font-size: 12px;
  1176. min-height: 130px;
  1177. width: 98%;
  1178. background: #3d4450;
  1179. color: #fff;
  1180. padding: 1%;
  1181. border: gray;
  1182. border-radius: 5px;
  1183. }
  1184. `);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址