Steam 令牌验证器

支持自动填写 Steam 令牌验证码和批量确认交易与市场

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/469857/1218043/Steam%20%E4%BB%A4%E7%89%8C%E9%AA%8C%E8%AF%81%E5%99%A8.js

  1. // ==UserScript==
  2. // @name Steam 令牌验证器
  3. // @namespace https://keylol.com/t652195-1-1
  4. // @version 0.8.3
  5. // @description 支持自动填写 Steam 令牌验证码和批量确认交易与市场
  6. // @author wave
  7. // @match http*://store.steampowered.com/*
  8. // @match http*://help.steampowered.com/*
  9. // @match http*://checkout.steampowered.com/*
  10. // @match http*://steamcommunity.com/*
  11. // @exclude http*://store.steampowered.com/login/transfer
  12. // @exclude http*://help.steampowered.com/login/transfer
  13. // @exclude http*://steamcommunity.com/login/transfer
  14. // @exclude http*://store.steampowered.com/login/logout/
  15. // @exclude http*://help.steampowered.com/login/logout/
  16. // @exclude http*://steamcommunity.com/login/logout/
  17. // @exclude http*://store.steampowered.com/widget/*
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @grant GM_setClipboard
  21. // @grant GM_addStyle
  22. // @grant GM_addValueChangeListener
  23. // @grant GM_xmlhttpRequest
  24. // @grant unsafeWindow
  25. // @connect steampowered.com
  26. // @connect steamcommunity.com
  27. // @require https://bundle.run/buffer@6.0.3
  28. // @require https://cdn.jsdelivr.net/npm/crypto-js@4.0.0/crypto-js.min.js
  29. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAFP0lEQVR4AdXXA5Ar2R7H8bMuPdtmnH/Gnr22bdu2bdu27bvewfO7tu2Mvw+pqene5EbTefj8ytUn9TtK0ip07p9tad5hTb2PK9xMdke7Y93pLypdaXii78KdZfmyirQL5m6r056ZsCO6OLFhoeLNMdNzfq4ihS+PnRH/yo74iZX0e3PbR2QlHsTUybIgQcRKt818RRnrYmKZSw4kyNho8vGTrynj3P91+fMOJITYaH3IsI3gy60Oa3dee/D+Fd+nwsLEScoYszpafHx82cfNjnRd2XFSx8md1jX6INlt9XomOj+rjCo998/K3f7i8pa/Oqez+ydK496vxk9LfuxEv0JNTxiwDTPGW764u0ee+Lzr55IrnnGgf/bDyqXe/2rnhZI4aPgpX3n9VX3/mv7ptjtU6RxPtCGapDy5Eaf82FZXfyTTn7p/qkqj/wJtAStD5wZasabHtNtgZmlNVRr1PxdKEl9w67cqgI3NzZoRdnovU+F78rUyL7QFGnwYxK35Sdor7ZhGp1X4ztmd2g0IcjYNT2oLVLqvwrenvFW3n3MaqCB0X6s9iElFpfiBnt7UoivwaZIKQsde2lFRXLKqcA3uoP0oJ+cdKgijW2tH2TjjUuHq3k0/l9umoL47m2jvgYnMWOXHm8oP3lAab6hwoN54I+wChW/kq5IUqODwRoHSjXtDhevM94+nnkopzofJwf3LufQd7ajjqU+/rv4P8UbW98c26tqlc7fSp3un6TXOf1mFYom5+l7BhMWQmDGRkFNvUcY3g5x774ZS6EQMjp3E69MsKrAh6Q4kInGSeOvU95R/vJt2WQg1iYxiH9lc5Y8cZBJpiM84qLEB/5dyVD0zElJiWchL9PLZQDLiIy4y/K9BrcWhbUBlLuDbLRohXjEzsKnyp+YxIfhU4h4ef2MaTahNYyaSicdLGvo4B6kDlD/VPxaCTRRnAMhlBC5Ek548BeAmSd7jjCswBYB8WiEIVZnLNpZQF0GowzMA1kSuQBQPAZiC4GQBRRTbQhRCXwDySI5UgXYAPCAKYSp6GxCEvwEwKlIFFgGwFiGBHPSKqIgwG4CtkSqwD4DxCB3xNg6hLQAfRKpANgBDEYbibRZCUwAyIlNgMh6zEWrjrTvCAACORKJAYwrxOIkg/AW920QjbAJgqfEFErhGsUKqI9ThCSXyaI2QyksAWhpdwMl+tDKIQqjCEfKAAj6knuapi4jRBbyP3AGiEYR4KlH8tbMCj55GFogihtq48XaW9ppfg6Zk4LEdCb1AXaIQr9RkI1cp4vXusodd7OIqxU4QFU6BPB6yjQGkIHgSzVpCVcg8XEg4BXLwKCCDedQljgw8LrKV1RzmBf49ZwvVECT8Alp5ANyns+YaLsSXx0xnII2JQxAjCmjl8inzKDkfAwC9zymHaGJgAa37rCMNQdhKiSKmIUgkC2hdJg6hKsWu0hgxrsAqLhPIBAThHgA7iUOMLCC4qMxkTpLD6yxHcHEOuIYgRhcoThz98W0QgosnwOeRLCBE8RK4zSROkqs57y6ERgCsimwBYScAPRAS6MR6jjEWz1X8AIAWRheos9uJaFKbIuApdRFdFgKQEcZLeu1eyp96E+2ILgsAyGEayQiCi3qcBOAV1UMuYGJOWeXPHrPNa9A2PPI4RzY38XhJWyTkpN07/57yhzdqbXN4DRvFU/Qywpi9YKFjVxXIX7+e8nfvCgmM5SAXuU0Wm2iJhBEbdXfxhgrsk+9U/MCCEzEwDqw0WcA7Kji8MaROtd3vP4xyiwGJcZe7WmPFshjfs/8Hvxyu95jGcTAAAAAASUVORK5CYII=
  30. // ==/UserScript==
  31.  
  32. (function() {
  33. function bufferizeSecret(secret) {
  34. if (typeof secret === 'string') {
  35. // Check if it's hex
  36. if (secret.match(/[0-9a-f]{40}/i)) {
  37. return buffer.Buffer.from(secret, 'hex');
  38. } else {
  39. // Looks like it's base64
  40. return buffer.Buffer.from(secret, 'base64');
  41. }
  42. }
  43. return secret;
  44. }
  45.  
  46. function generateAuthCode(secret, timeOffset) {
  47. secret = bufferizeSecret(secret);
  48.  
  49. let time = Math.floor(Date.now() / 1000) + (timeOffset || 0);
  50.  
  51. let b = buffer.Buffer.allocUnsafe(8);
  52. b.writeUInt32BE(0, 0); // This will stop working in 2038!
  53. b.writeUInt32BE(Math.floor(time / 30), 4);
  54.  
  55. let hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, CryptoJS.lib.WordArray.create(secret));
  56. hmac = buffer.Buffer.from(hmac.update(CryptoJS.lib.WordArray.create(b)).finalize().toString(CryptoJS.enc.Hex), 'hex');
  57.  
  58. let start = hmac[19] & 0x0F;
  59. hmac = hmac.slice(start, start + 4);
  60.  
  61. let fullcode = hmac.readUInt32BE(0) & 0x7FFFFFFF;
  62.  
  63. const chars = '23456789BCDFGHJKMNPQRTVWXY';
  64.  
  65. let code = '';
  66. for (let i = 0; i < 5; i++) {
  67. code += chars.charAt(fullcode % chars.length);
  68. fullcode /= chars.length;
  69. }
  70.  
  71. return code;
  72. }
  73.  
  74. function generateConfirmationKey(identitySecret, time, tag) {
  75. identitySecret = bufferizeSecret(identitySecret);
  76.  
  77. let dataLen = 8;
  78.  
  79. if (tag) {
  80. if (tag.length > 32) {
  81. dataLen += 32;
  82. } else {
  83. dataLen += tag.length;
  84. }
  85. }
  86.  
  87. let b = buffer.Buffer.allocUnsafe(dataLen);
  88. b.writeUInt32BE(0, 0);
  89. b.writeUInt32BE(time, 4);
  90.  
  91. if (tag) {
  92. b.write(tag, 8);
  93. }
  94.  
  95. let hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, CryptoJS.lib.WordArray.create(identitySecret));
  96. return hmac.update(CryptoJS.lib.WordArray.create(b)).finalize().toString(CryptoJS.enc.Base64);
  97. }
  98.  
  99. function getDeviceID(steamID) {
  100. let salt = '';
  101. return "android:" + CryptoJS.SHA1(steamID.toString() + salt).toString(CryptoJS.enc.Hex).replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12}).*$/, '$1-$2-$3-$4-$5');
  102. }
  103.  
  104. function generateConfirmationQueryParams(account, tag, timeOffset) {
  105. var time = Math.floor(Date.now() / 1000) + (timeOffset || 0);
  106. var key = generateConfirmationKey(account.identitySecret, time, tag);
  107. var deviceID = getDeviceID(account.steamID);
  108. return 'a=' + account.steamID + '&tag=' + tag + '&l=schinese&m=react&t=' + time + '&p=' + encodeURIComponent(deviceID) + '&k=' + encodeURIComponent(key);
  109. }
  110.  
  111. function showAddAccountDialog(strTitle, strOKButton, strCancelButton, rgModalParams) {
  112. if (!strOKButton) {
  113. strOKButton = '确定';
  114. }
  115. if (!strCancelButton) {
  116. strCancelButton = '取消';
  117. }
  118.  
  119. var $Body = $J('<form/>');
  120. var $AccountNameInput = $J('<input/>', {type: 'text', 'class': ''});
  121. var $SharedSecretInput = $J('<input/>', {type: 'text', 'class': ''});
  122. var $SteamIDInput = $J('<input/>', {type: 'text', 'class': ''});
  123. var $IdentitySecretInput = $J('<input/>', {type: 'text', 'class': ''});
  124. if (rgModalParams && rgModalParams.inputMaxSize) {
  125. $AccountNameInput.attr('maxlength', rgModalParams.inputMaxSize);
  126. $SharedSecretInput.attr('maxlength', rgModalParams.inputMaxSize);
  127. $SteamIDInput.attr('maxlength', rgModalParams.inputMaxSize);
  128. $IdentitySecretInput.attr('maxlength', rgModalParams.inputMaxSize);
  129. }
  130. $Body.append($J('<div/>', {'class': 'newmodal_prompt_description'}).append('Steam 帐户名称<span data-tooltip-text="非个人资料名称,用于自动填写 Steam 令牌验证码。"> (?)</span>'));
  131. $Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($AccountNameInput));
  132. $Body.append($J('<div/>', {'class': 'newmodal_prompt_description', 'style': 'margin-top: 8px;'}).append('共享密钥<span data-tooltip-text="即 shared secret,用于生成 Steam 令牌验证码。"> (?)</span>'));
  133. $Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($SharedSecretInput));
  134. $Body.append($J('<div/>', {'class': 'newmodal_prompt_description', 'style': 'margin-top: 8px;'}).append('64 位 Steam ID<span data-tooltip-text="以“7656”开头的 17 位数字,用于确认交易与市场。"> (?)</span>'));
  135. $Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($SteamIDInput));
  136. $Body.append($J('<div/>', {'class': 'newmodal_prompt_description', 'style': 'margin-top: 8px;'}).append('身份密钥<span data-tooltip-text="即 identity secret,用于确认交易与市场。"> (?)</span>'));
  137. $Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($IdentitySecretInput));
  138.  
  139. var deferred = new jQuery.Deferred();
  140. var fnOK = function() {
  141. var name = $AccountNameInput.val().trim();
  142. var secret = $SharedSecretInput.val().trim();
  143. var steamID = $SteamIDInput.val().trim();
  144. var identitySecret = $IdentitySecretInput.val().trim();
  145. if (!name) {
  146. name = '无名氏';
  147. }
  148. if (!secret) {
  149. ShowAlertDialog('错误', '请输入有效的共享密钥。', '确定');
  150. return;
  151. }
  152. if (steamID && steamID.indexOf('7656') != 0 && steamID.length != 17) {
  153. ShowAlertDialog('错误', '请输入有效的 64 位 Steam ID。', '确定');
  154. return;
  155. }
  156. deferred.resolve(name, secret, steamID, identitySecret);
  157. };
  158. var fnCancel = function() {
  159. deferred.reject();
  160. };
  161.  
  162. $Body.submit(function(event) {
  163. event.preventDefault();
  164. fnOK();
  165. });
  166.  
  167. var $OKButton = _BuildDialogButton(strOKButton, true);
  168. $OKButton.click(fnOK);
  169. var $CancelButton = _BuildDialogButton(strCancelButton);
  170. $CancelButton.click(fnCancel);
  171.  
  172. var Modal = _BuildDialog(strTitle, $Body, [$OKButton, $CancelButton], fnCancel);
  173. if(!rgModalParams || !rgModalParams.bNoPromiseDismiss) {
  174. deferred.always(function() {
  175. Modal.Dismiss();
  176. });
  177. }
  178.  
  179. Modal.Show();
  180.  
  181. $AccountNameInput.focus();
  182.  
  183. // attach the deferred's events to the modal
  184. deferred.promise(Modal);
  185.  
  186. return Modal;
  187. }
  188.  
  189. function showImportAccountDialog(strTitle, strDescription, strOKButton, strCancelButton, textAreaMaxLength) {
  190. if (!strOKButton) {
  191. strOKButton = '确定';
  192. }
  193. if (!strCancelButton) {
  194. strCancelButton = '取消';
  195. }
  196.  
  197. var $Body = $J('<form/>');
  198. var $TextArea = $J('<textarea/>', {'class': 'newmodal_prompt_textarea'});
  199. $TextArea.attr('placeholder', strDescription);
  200. if (textAreaMaxLength) {
  201. $TextArea.attr('maxlength', textAreaMaxLength);
  202. $TextArea.bind('keyup change', function() {
  203. var str = $J(this).val();
  204. var mx = parseInt($J(this).attr('maxlength'));
  205. if (str.length > mx) {
  206. $J(this).val(str.substr(0, mx));
  207. return false;
  208. }
  209. });
  210. }
  211. $Body.append($J('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth'}).append($TextArea));
  212.  
  213. var deferred = new jQuery.Deferred();
  214. var fnOK = function() {
  215. deferred.resolve($TextArea.val());
  216. };
  217. var fnCancel = function() {
  218. deferred.reject();
  219. };
  220.  
  221. $Body.submit(function(event) {
  222. event.preventDefault();
  223. fnOK();
  224. });
  225.  
  226. var $OKButton = _BuildDialogButton(strOKButton, true);
  227. $OKButton.click(fnOK);
  228. var $CancelButton = _BuildDialogButton(strCancelButton);
  229. $CancelButton.click(fnCancel);
  230.  
  231. var Modal = _BuildDialog(strTitle, $Body, [$OKButton, $CancelButton], fnCancel);
  232. deferred.always(function() {
  233. Modal.Dismiss();
  234. });
  235. Modal.Show();
  236.  
  237. $TextArea.focus();
  238.  
  239. // attach the deferred's events to the modal
  240. deferred.promise(Modal);
  241.  
  242. return Modal;
  243. }
  244.  
  245. function showConfirmationDialog(account, confList) {
  246. var strOKButton = '全选';
  247. var strCancelButton = '关闭';
  248.  
  249. var $Body = $J('<div/>', {'style': 'position: relative; overflow: hidden; padding-bottom: 66px;'});
  250. var $MobileconfList = $J('<div/>', {'id': 'mobileconf_list'});
  251. $J.each(confList, function(i, v) {
  252. var $ConfirmationEntry = $J('<div/>', {'class': 'mobileconf_list_entry', 'id': 'conf' + v.id, 'data-confid': v.id, 'data-key': v.nonce});
  253. var $ConfirmationEntryContent = $J('<div/>', {'class': 'mobileconf_list_entry_content'});
  254. var $ConfirmationEntryIcon = $J('<div/>', {'class': 'mobileconf_list_entry_icon'}).append('<img src="' + v.icon + '"/>');
  255. var $ConfirmationEntryDescription = $J('<div/>', {'class': 'mobileconf_list_entry_description'}).append('<div>' + v.headline + '</div><div>' + v.summary.join('<br/>') + '</div><div>' + v.type_name + ' - ' + new Date(v.creation_time * 1000).toLocaleString() + '</div>');
  256. var $ConfirmationEntryCheckbox = $J('<div/>', {'class': 'mobileconf_list_checkbox'}).append($J('<input/>', {'id': 'multiconf_' + v.id, 'data-confid': v.id, 'data-key': v.nonce, 'value': '1', 'type': 'checkbox'}));
  257. var $ConfirmationEntrySep = $J('<div/>', {'class': 'mobileconf_list_entry_sep'});
  258. $MobileconfList.append($ConfirmationEntry.append($ConfirmationEntryContent.append($ConfirmationEntryIcon, $ConfirmationEntryDescription, $ConfirmationEntryCheckbox), $ConfirmationEntrySep));
  259. $ConfirmationEntry.on('click', function() {
  260. window.open('https://steamcommunity.com/mobileconf/detailspage/' + v.id + '?' + generateConfirmationQueryParams(account, 'details' + v.id, timeOffset), '_blank', 'height=790,width=600,resize=yes,scrollbars=yes');
  261. });
  262. $ConfirmationEntryCheckbox.on('click', function(e) {
  263. e.stopPropagation();
  264.  
  265. var nChecked = $J('.mobileconf_list_checkbox input:checked').length;
  266. var $elButtons = $J('#mobileconf_buttons');
  267.  
  268. if (nChecked > 0) {
  269. var $btnCancel = $J('#mobileconf_buttons .mobileconf_button_cancel');
  270. var $btnAccept = $J('#mobileconf_buttons .mobileconf_button_accept');
  271. $btnCancel.unbind();
  272. $btnAccept.unbind();
  273.  
  274. $btnCancel.text('取消选择');
  275. $btnAccept.text('确认已选择');
  276.  
  277. $btnCancel.click(function() {
  278. sendMultiMobileConfirmationOp(account, 'cancel', Modal);
  279. });
  280.  
  281. $btnAccept.click(function() {
  282. sendMultiMobileConfirmationOp(account, 'allow', Modal);
  283. });
  284.  
  285. if ($elButtons.is(':hidden')) {
  286. $elButtons.css('bottom', -$elButtons.height() + 'px');
  287. $elButtons.show();
  288. }
  289. $elButtons.css('bottom', '0');
  290. } else {
  291. $elButtons.css('bottom', -$elButtons.height() + 'px');
  292. }
  293. });
  294. });
  295. var $MobileconfButtons = $J('<div/>', {'id': 'mobileconf_buttons', 'style': 'display: none;'});
  296. var $MobileconfButtonCancel = $J('<div/>', {'class': 'mobileconf_button mobileconf_button_cancel'});
  297. var $MobileconfButtonAccept = $J('<div/>', {'class': 'mobileconf_button mobileconf_button_accept'});
  298. $MobileconfButtons.append($J('<div/>').append($MobileconfButtonCancel, $MobileconfButtonAccept));
  299.  
  300. $Body.append($MobileconfList, $MobileconfButtons);
  301.  
  302. var deferred = new jQuery.Deferred();
  303. var fnOK = function() {
  304. $J('.mobileconf_list_checkbox input:not(:checked)').prop('checked', true);
  305. $J('.mobileconf_list_checkbox').eq(0).click();
  306. };
  307. var fnCancel = function() {
  308. deferred.reject();
  309. };
  310.  
  311. var $OKButton = _BuildDialogButton(strOKButton, true);
  312. $OKButton.click(fnOK);
  313. var $CancelButton = _BuildDialogButton(strCancelButton);
  314. $CancelButton.click(fnCancel);
  315.  
  316. var Modal = _BuildDialog('确认交易与市场', $Body, [$OKButton, $CancelButton], fnCancel);
  317. deferred.always(function() {
  318. Modal.Dismiss();
  319. });
  320. Modal.Show();
  321.  
  322. // attach the deferred's events to the modal
  323. deferred.promise(Modal);
  324.  
  325. return Modal;
  326. }
  327.  
  328. function addAccount() {
  329. showAddAccountDialog('添加账户', '确定', '取消').done(function(name, secret, steamID, identitySecret) {
  330. if (steamID && identitySecret) {
  331. accounts.push({
  332. name,
  333. secret,
  334. steamID,
  335. identitySecret
  336. });
  337. GM_setValue('accounts', accounts);
  338. ShowAlertDialog('添加账户', '添加成功,该账户支持确认交易与市场。', '确定');
  339. } else {
  340. accounts.push({
  341. name,
  342. secret
  343. });
  344. GM_setValue('accounts', accounts);
  345. ShowAlertDialog('添加账户', '添加成功,该账户不支持确认交易与市场。', '确定');
  346. }
  347. });
  348. setupTooltips($J('.newmodal'));
  349. }
  350.  
  351. function importAccount() {
  352. showImportAccountDialog('导入账户', '将要导入的数据粘贴于此', '确定', '取消').done(function(data) {
  353. try {
  354. data = JSON.parse(data.replace(/("SteamID":)(\d+)/, '$1"$2"'));
  355. var name = data.account_name || '无名氏';
  356. var secret = data.shared_secret;
  357. var steamID = data.steamid || data.Session && data.Session.SteamID || '';
  358. var identitySecret = data.identity_secret;
  359. if (!secret) {
  360. ShowAlertDialog('错误', '共享密钥不存在,请检查后再试。', '确定').done(function() {
  361. importAccount();
  362. });
  363. return;
  364. }
  365. if (steamID && identitySecret) {
  366. accounts.push({
  367. name,
  368. secret,
  369. steamID,
  370. identitySecret
  371. });
  372. GM_setValue('accounts', accounts);
  373. ShowAlertDialog('导入账户', '导入成功,该账户支持确认交易与市场。', '确定');
  374. } else {
  375. accounts.push({
  376. name,
  377. secret
  378. });
  379. GM_setValue('accounts', accounts);
  380. ShowAlertDialog('导入账户', '导入成功,该账户不支持确认交易与市场。', '确定');
  381. }
  382. } catch (err) {
  383. ShowAlertDialog('错误', '数据格式有误,请检查后再试。', '确定').done(function() {
  384. importAccount();
  385. });
  386. }
  387. });
  388. }
  389.  
  390. function deleteAccount(elem) {
  391. ShowConfirmDialog('删除账户', '确定删除该账户吗?', '确定', '取消').done(function() {
  392. var $Elem = $JFromIDOrElement(elem);
  393. if ($Elem.data('id') >= accounts.length) {
  394. ShowAlertDialog('错误', '无法删除该账户,请稍后再试。', '确定').done(function() {
  395. window.location.reload();
  396. });
  397. } else {
  398. var account = accounts.splice($Elem.data('id'), 1)[0];
  399. GM_setValue('accounts', accounts);
  400. ShowAlertDialog('删除账户', '删除成功。', '确定');
  401. }
  402. });
  403. }
  404.  
  405. function copyAuthCode(elem) {
  406. var $Elem = $JFromIDOrElement(elem);
  407. GM_setClipboard(generateAuthCode(accounts[$Elem.data('id')].secret, timeOffset));
  408. $Elem.css('width', window.getComputedStyle(elem, null).width).text('复制成功').addClass('copy_success');
  409. setTimeout(function() {
  410. $Elem.text($Elem.data('name')).removeClass('copy_success');
  411. }, 1000);
  412. }
  413.  
  414. function refreshAccounts() {
  415. $AuthenticatorPopupMenu.empty();
  416. $J.each(accounts, function(i, v) {
  417. var $AuthenticatorPopupMenuItem = $J('<span/>', {'style': 'display: block; padding: 5px 0 5px 12px; margin-right: 27px; min-width: 50px;', 'data-id': i, 'data-name': v.name, 'data-tooltip-text': '点击复制该账户的验证码'}).append(v.name);
  418. var $AuthenticatorDeleteAccount = $J('<span/>', {'class': 'delete_account', 'data-id': i, 'data-tooltip-text': '删除该账户'});
  419. $AuthenticatorPopupMenu.append($J('<a/>', {'class': 'popup_menu_item', 'style': 'position: relative; padding: 0;'}).append($AuthenticatorPopupMenuItem, $AuthenticatorDeleteAccount));
  420. $AuthenticatorPopupMenuItem.on('click', function() {
  421. copyAuthCode(this);
  422. });
  423. $AuthenticatorDeleteAccount.on('click', function() {
  424. deleteAccount(this);
  425. });
  426. });
  427. setupTooltips($AuthenticatorPopupMenu);
  428.  
  429. var $AuthenticatorAddAccount = $J('<a/>', {'class': 'popup_menu_item'}).append('添加账户');
  430. $AuthenticatorPopupMenu.append($AuthenticatorAddAccount);
  431. $AuthenticatorAddAccount.on('click', function() {
  432. addAccount();
  433. });
  434.  
  435. var $AuthenticatorImportAccount = $J('<a/>', {'class': 'popup_menu_item'}).append('导入账户');
  436. $AuthenticatorPopupMenu.append($AuthenticatorImportAccount);
  437. $AuthenticatorImportAccount.on('click', function() {
  438. importAccount();
  439. });
  440. }
  441.  
  442. function createConfirmationLink(steamID) {
  443. if (!$AuthenticatorPopupMenu.find('.confirmation').length) {
  444. $J.each(accounts, function(i, v) {
  445. if (v.steamID && steamID == v.steamID) {
  446. var $AuthenticatorConfirmation = $J('<a/>', {'class': 'popup_menu_item confirmation'}).append('确认交易与市场');
  447. $AuthenticatorPopupMenu.append($AuthenticatorConfirmation);
  448. $AuthenticatorConfirmation.on('click', async function() {
  449. var waitDialog = ShowBlockingWaitDialog('确认交易与市场', '正在获取确认信息,请稍候…');
  450.  
  451. try {
  452. var res = await new Promise((resolve, reject) => {
  453. GM_xmlhttpRequest({
  454. method: 'GET',
  455. url: 'https://steamcommunity.com/mobileconf/getlist?' + generateConfirmationQueryParams(v, 'conf', timeOffset),
  456. responseType: 'json',
  457. onload: function(response) {
  458. resolve(response.response);
  459. },
  460. onerror: function(error) {
  461. reject(error);
  462. }
  463. });
  464. });
  465. if (res && res.success) {
  466. if (res.conf && res.conf.length) {
  467. showConfirmationDialog(v, res.conf);
  468. } else {
  469. ShowAlertDialog('确认交易与市场', '您当前没有任何确认信息。', '确定');
  470. }
  471. } else {
  472. ShowAlertDialog('错误', res && res.message || '获取确认信息失败,请稍后再试。', '确定');
  473. }
  474. } catch (err) {
  475. ShowAlertDialog('错误', '获取确认信息失败,请稍后再试。', '确定');
  476. }
  477.  
  478. waitDialog.Dismiss();
  479. });
  480. return false;
  481. }
  482. });
  483. }
  484. }
  485.  
  486. async function sendMultiMobileConfirmationOp(account, op, modal) {
  487. var $rgChecked = $J('.mobileconf_list_checkbox input:checked');
  488. if ($rgChecked.length == 0) {
  489. return;
  490. }
  491.  
  492. var waitDialog = ShowBlockingWaitDialog('确认交易与市场', '正在执行此操作,请稍候…');
  493.  
  494. var rgConfirmationId = [];
  495. var rgConfirmationKey = [];
  496.  
  497. $J.each($rgChecked, function(key) {
  498. var $this = $J(this);
  499. var nConfirmationId = $this.data('confid');
  500. var nConfirmationKey = $this.data('key');
  501.  
  502. rgConfirmationId.push(nConfirmationId);
  503. rgConfirmationKey.push(nConfirmationKey);
  504. });
  505.  
  506. var queryString = 'op=' + op + '&' + generateConfirmationQueryParams(account, op, timeOffset);
  507.  
  508. for (var i = 0; i < rgConfirmationId.length; i++) {
  509. queryString += '&cid[]=' + rgConfirmationId[i];
  510. queryString += '&ck[]=' + rgConfirmationKey[i];
  511. }
  512.  
  513. try {
  514. var res = await new Promise((resolve, reject) => {
  515. GM_xmlhttpRequest({
  516. method: 'POST',
  517. url: 'https://steamcommunity.com/mobileconf/multiajaxop',
  518. data: queryString,
  519. headers: {
  520. 'Content-Type': 'application/x-www-form-urlencoded'
  521. },
  522. responseType: 'json',
  523. onload: function(response) {
  524. resolve(response.response);
  525. },
  526. onerror: function(error) {
  527. reject(error);
  528. }
  529. });
  530. });
  531. if (res && res.success) {
  532. for (var j = 0; j < rgConfirmationId.length; j++) {
  533. $J('#conf' + rgConfirmationId[j]).remove();
  534. }
  535.  
  536. var nChecked = $J('.mobileconf_list_checkbox input:checked').length;
  537. var $elButtons = $J('#mobileconf_buttons');
  538.  
  539. if (nChecked == 0) {
  540. $elButtons.css('bottom', -$elButtons.height() + 'px');
  541. }
  542.  
  543. if ($J('.mobileconf_list_entry').length == 0) {
  544. modal && modal.Dismiss();
  545. } else {
  546. modal && modal.AdjustSizing();
  547. }
  548. } else {
  549. ShowAlertDialog('确认错误', res && res.message || '执行此操作时出现问题。请稍后再重试您的请求。', '确定');
  550. }
  551. } catch (err) {
  552. ShowAlertDialog('确认错误', '执行此操作时出现问题。请稍后再重试您的请求。', '确定');
  553. }
  554.  
  555. waitDialog.Dismiss();
  556. }
  557.  
  558. function setupTooltips(selector) {
  559. if (window.location.hostname == 'store.steampowered.com' || window.location.hostname == 'checkout.steampowered.com') {
  560. BindTooltips(selector, {tooltipCSSClass: 'store_tooltip'});
  561. } else if (window.location.hostname == 'help.steampowered.com') {
  562. BindTooltips(selector, {tooltipCSSClass: 'help_tooltip'});
  563. } else if (window.location.hostname == 'steamcommunity.com') {
  564. BindTooltips(selector, {tooltipCSSClass: 'community_tooltip'});
  565. }
  566. }
  567.  
  568. GM_addStyle(`
  569. .delete_account {
  570. position: absolute;
  571. right: 0;
  572. top: 0;
  573. padding: 5px 7.5px;
  574. width: 12px;
  575. height: calc(100% - 10px);
  576. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAQAAAD8fJRsAAAAkElEQVR4AXWQxWEDMRBFJ6AWArqGmW7G12HMDN0ZFmr4dqKF00rDPGPhycnr/vi9nJVPl2qI7Dd0WZpZEyFEygKhy1CkPsX4JCLlB6OP6jo3eRHxhh3xA+OBLULedCtExDOGcRvM6DZzpP/RxgtR4fDKat/ylPUKpZwao1A769VBDbls3H5WO6KfjVu5YOVJDkyDcoTnvnKRAAAAAElFTkSuQmCC);
  577. background-position: center;
  578. background-repeat: no-repeat;
  579. background-origin: content-box;
  580. cursor: pointer;
  581. }
  582.  
  583. .copy_success {
  584. color: #57cbde !important;
  585. }
  586.  
  587. #mobileconf_list {
  588. overflow-y: auto;
  589. max-width: 600px;
  590. max-height: calc(100vh - 270px);
  591. }
  592.  
  593. .mobileconf_list_entry {
  594. cursor: default;
  595. font-size: 15px;
  596. text-shadow: none;
  597. width: 100%;
  598. -webkit-transition: opacity 0.1s, top 0.4s;
  599. transition: opacity 0.1s, top 0.4s;
  600. }
  601.  
  602. .mobileconf_list_entry.copy {
  603. -webkit-transition: opacity 0.4s, top 0.4s;
  604. transition: opacity 0.4s, top 0.4s;
  605. }
  606.  
  607. .mobileconf_list_entry_content {
  608. display: flex;
  609. padding: 10px 20px;
  610. }
  611.  
  612. .mobileconf_list_entry_icon {
  613. height: 3.5em;
  614. margin-right: 10px;
  615. }
  616.  
  617. .mobileconf_list_entry_icon > img {
  618. width: 32px;
  619. }
  620.  
  621. .mobileconf_list_entry_description {
  622. flex: 1;
  623. min-width: 0;
  624. margin-right: 10px;
  625. }
  626.  
  627. .mobileconf_list_entry_description > div {
  628. color: #7a7a7a;
  629. overflow: hidden;
  630. white-space: nowrap;
  631. text-overflow: ellipsis;
  632. }
  633.  
  634. .mobileconf_list_entry_description > div:first-of-type {
  635. color: white;
  636. }
  637.  
  638. .mobileconf_list_checkbox {
  639. overflow: hidden;
  640. vertical-align: middle;
  641. display: inline-block;
  642. color: #ffffff;
  643. width: 3em;
  644. height: 3em;
  645. transform: scale(1.5);
  646. transition-property: transform;
  647. transition-duration: 0.1s;
  648. }
  649.  
  650. .mobileconf_list_checkbox input {
  651. width: 3em;
  652. height: 3em;
  653. }
  654.  
  655. .mobileconf_list_entry_sep {
  656. border-top: 1px solid #303030;
  657. }
  658.  
  659. #mobileconf_buttons {
  660. position: absolute;
  661. display: inline-block;
  662. vertical-align: middle;
  663. height: 3.3em;
  664. line-height: 3.3em;
  665. width: 100%;
  666. font-size: 20px;
  667. background: #324056;
  668. background-image: radial-gradient(
  669. ellipse farthest-corner at 50% 0px,
  670. rgb(74, 107, 152) 0%,
  671. transparent 50%
  672. );
  673. bottom: -3em;
  674. left: 0;
  675. z-index: 10;
  676. -webkit-transition-property: bottom;
  677. transition-property: bottom;
  678. -webkit-transition-duration: 0.4s;
  679. transition-duration: 0.4s;
  680. }
  681.  
  682. .mobileconf_button {
  683. width: 50%;
  684. display: inline-block;
  685. overflow: hidden;
  686. text-align: center;
  687. vertical-align: middle;
  688. line-height: normal;
  689. cursor: pointer;
  690. }
  691. `);
  692.  
  693. var $GlobalActionMenu = $J('#global_action_menu');
  694. var $AuthenticatorLink = $J('<span/>', {'class': 'pulldown global_action_link', 'style': 'display: inline-block; padding-left: 4px; line-height: 25px;', 'onclick': 'ShowMenu(this, "authenticator_dropdown", "right");'}).append('Steam 令牌验证器');
  695. var $AuthenticatorDropdown = $J('<div/>', {'class': 'popup_block_new', 'id': 'authenticator_dropdown', 'style': 'display: none;'});
  696. var $AuthenticatorPopupMenu = $J('<div/>', {'class': 'popup_body popup_menu'});
  697.  
  698. $GlobalActionMenu.prepend($AuthenticatorDropdown.append($AuthenticatorPopupMenu));
  699. $GlobalActionMenu.prepend($AuthenticatorLink);
  700.  
  701. var accounts = GM_getValue('accounts') || [];
  702.  
  703. refreshAccounts();
  704.  
  705. GM_addValueChangeListener('accounts', function(name, old_value, new_value, remote) {
  706. accounts = new_value;
  707. refreshAccounts();
  708.  
  709. if (userSteamID) {
  710. $AuthenticatorPopupMenu.find('.confirmation').remove();
  711. createConfirmationLink(userSteamID);
  712. }
  713.  
  714. AlignMenu($AuthenticatorLink, 'authenticator_dropdown', 'right');
  715. });
  716.  
  717. if (window.location.pathname == '/mobileconf/conf') {
  718. let account;
  719. $J.each(accounts, function(i, v) {
  720. if (v.steamID && g_steamID == v.steamID) {
  721. account = v;
  722. return false;
  723. }
  724. });
  725.  
  726. unsafeWindow.GetValueFromLocalURL = function(url, timeout, success, error, fatal) {
  727. if (url.indexOf('steammobile://steamguard?op=conftag&arg1=allow') !== -1) {
  728. success(generateConfirmationQueryParams(account, 'allow', timeOffset));
  729. } else if (url.indexOf('steammobile://steamguard?op=conftag&arg1=cancel') !== -1) {
  730. success(generateConfirmationQueryParams(account, 'cancel', timeOffset));
  731. } else if (url.indexOf('steammobile://steamguard?op=conftag&arg1=details') !== -1) {
  732. success(generateConfirmationQueryParams(account, 'details', timeOffset));
  733. }
  734. };
  735.  
  736. $J('html').removeClass('force_desktop').addClass('responsive');
  737. V_SetCookie('strResponsiveViewPrefs', null, -1);
  738.  
  739. $J('.mobileconf_list_entry').each(function() {
  740. var $this = $J(this);
  741. if (!$this.has('.mobileconf_list_checkbox').length) {
  742. var $ConfirmationEntryCheckbox = $J('<div/>', {'class': 'mobileconf_list_checkbox'}).append($J('<input/>', {'id': 'multiconf_' + $this.data('confid'), 'data-confid': $this.data('confid'), 'data-key': $this.data('key'), 'value': '1', 'type': 'checkbox'}));
  743. $this.find('.mobileconf_list_entry_icon').after($ConfirmationEntryCheckbox);
  744. $ConfirmationEntryCheckbox.on('click', function(e) {
  745. e.stopPropagation();
  746.  
  747. var nChecked = $J('.mobileconf_list_checkbox input:checked').length;
  748. var $elButtons = $J('#mobileconf_buttons');
  749.  
  750. if (nChecked > 0) {
  751. var $btnCancel = $J('#mobileconf_buttons .mobileconf_button_cancel');
  752. var $btnAccept = $J('#mobileconf_buttons .mobileconf_button_accept');
  753. $btnCancel.unbind();
  754. $btnAccept.unbind();
  755.  
  756. $btnCancel.text('取消选择');
  757. $btnAccept.text('确认已选择');
  758.  
  759. $btnCancel.click(function() {
  760. ActionForAllSelected('cancel');
  761. });
  762.  
  763. $btnAccept.click(function() {
  764. ActionForAllSelected('allow');
  765. });
  766.  
  767. if ($elButtons.is(':hidden')) {
  768. $elButtons.css('bottom', -$elButtons.height() + 'px');
  769. $elButtons.show();
  770. }
  771. $elButtons.css('bottom', '0');
  772. } else {
  773. $elButtons.css('bottom', -$elButtons.height() + 'px');
  774. }
  775. });
  776. }
  777. });
  778.  
  779. var $ResponsiveHeaderContent = $J('.responsive_header_content');
  780. var $ConfirmationCheckAll = $J('<div/>', {'class': 'btn_green_steamui btn_medium'}).append('<span>全选</span>');
  781. var $ConfirmationRefresh = $J('<div/>', {'class': 'btn_blue_steamui btn_medium'}).append('<span>刷新</span>');
  782. $ResponsiveHeaderContent.append($J('<div/>', {'style': 'position: absolute; top: 15px; right: 8px;'}).append($ConfirmationCheckAll, '\n', $ConfirmationRefresh));
  783. $ConfirmationCheckAll.on('click', function() {
  784. if ($J('#mobileconf_list').is(':visible') && $J('#mobileconf_details').is(':hidden')) {
  785. $J('.mobileconf_list_checkbox input:not(:checked)').click();
  786. }
  787. });
  788. $ConfirmationRefresh.on('click', function() {
  789. if (account) {
  790. window.location.replace('https://steamcommunity.com/mobileconf/conf?' + generateConfirmationQueryParams(account, 'conf', timeOffset));
  791. } else {
  792. window.location.reload();
  793. }
  794. });
  795. }
  796.  
  797. var intersectionObserver = new IntersectionObserver(function(entries) {
  798. if (entries[0].intersectionRatio > 0) {
  799. var name = $J('#login_twofactorauth_message_entercode_accountname, [class^="login_SigningInAccountName"], [class^="newlogindialog_AccountName"]').text();
  800. $J.each(accounts, function(i, v) {
  801. if(name == v.name) {
  802. var $AuthCodeInput = $J('#twofactorcode_entry, [class^="login_AuthenticatorInputcontainer"] input.DialogInput, [class^="newlogindialog_SegmentedCharacterInput"] input, [class^="segmentedinputs_SegmentedCharacterInput"] input');
  803. var dt = new DataTransfer();
  804. dt.setData('text', generateAuthCode(v.secret, timeOffset));
  805. $AuthCodeInput[0].dispatchEvent(new ClipboardEvent('paste', {clipboardData: dt, bubbles: true}));
  806. return false;
  807. }
  808. });
  809. }
  810. });
  811.  
  812. var mutationObserver = new MutationObserver(function() {
  813. if ($J('#twofactorcode_entry, [class^="login_AuthenticatorInputcontainer"] input.DialogInput, [class^="newlogindialog_SegmentedCharacterInput"] input, [class^="segmentedinputs_SegmentedCharacterInput"] input').length) {
  814. intersectionObserver.observe($J('#twofactorcode_entry, [class^="login_AuthenticatorInputcontainer"] input.DialogInput, [class^="newlogindialog_SegmentedCharacterInput"] input, [class^="segmentedinputs_SegmentedCharacterInput"] input')[0]);
  815. }
  816.  
  817. if ($J('[class^="newlogindialog_EnterCodeInsteadLink"] [class^="newlogindialog_TextLink"]').length) {
  818. $J('[class^="newlogindialog_EnterCodeInsteadLink"] [class^="newlogindialog_TextLink"]')[0].click();
  819. }
  820. });
  821.  
  822. var userSteamID;
  823.  
  824. if ($J('#account_dropdown .persona').length) {
  825. if (typeof g_steamID != 'undefined' && g_steamID) {
  826. userSteamID = g_steamID;
  827. createConfirmationLink(userSteamID);
  828. } else {
  829. GM_xmlhttpRequest({
  830. method: 'GET',
  831. url: 'https://steamcommunity.com/my/?xml=1',
  832. onload: function(response) {
  833. if (response.responseXML) {
  834. var steamID = $J(response.responseXML).find('steamID64').text();
  835. if (steamID) {
  836. userSteamID = steamID;
  837. createConfirmationLink(userSteamID);
  838. }
  839. }
  840. }
  841. });
  842. }
  843. if (window.location.href.indexOf('checkout.steampowered.com/login/?purchasetype=') !== -1) {
  844. mutationObserver.observe(document.body, {childList: true, subtree: true});
  845. }
  846. } else {
  847. mutationObserver.observe(document.body, {childList: true, subtree: true});
  848. }
  849.  
  850. var timeOffset = 0;
  851.  
  852. GM_xmlhttpRequest({
  853. method: 'POST',
  854. url: 'https://api.steampowered.com/ITwoFactorService/QueryTime/v0001',
  855. responseType: 'json',
  856. onload: function(response) {
  857. if (response.response && response.response.response && response.response.response.server_time) {
  858. timeOffset = response.response.response.server_time - Math.floor(Date.now() / 1000);
  859. }
  860. }
  861. });
  862. })();

QingJ © 2025

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