Steam Wholesale Sites Extension

try to take over the world!

目前为 2017-12-02 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Steam Wholesale Sites Extension
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.1
  5. // @description try to take over the world!
  6. // @icon http://store.steampowered.com/favicon.ico
  7. // @author Bisumaruko
  8. // @include https://mini.wmtransfer.com/*
  9. // @include http://steamkeys.ovh/*
  10. // @include http://steamfarmkey.ru/*
  11. // @include http://steam1.lequeshop.ru/*
  12. // @include http://steam1.ru/*
  13. // @include http://lastkey.ru/*
  14. // @include http://steamkeyswhosales.com/*
  15. // @include http://alfakeys.ru/*
  16. // @include http://cheap-steam-games.ru/*
  17. // @include http://dmshop.lequeshop.ru/*
  18. // @include http://kartonanet.lequeshop.ru/*
  19. // @include http://keyssell.ru/*
  20. // @include http://keys.farm/*
  21. // @include http://rig4all.lequeshop.ru/*
  22. // @include http://steam-tab.ru/*
  23. // @include http://steamd.lequeshop.ru/*
  24. // @include http://steamkeys-shop.ru/*
  25. // @include http://steamkey.lequeshop.ru/*
  26. // @include http://steamkeystore.ru/*
  27. // @include http://farmacc.ru/*
  28. // @include http://steamrandomkeys.ru/*
  29. // @include http://animekeys.ru/*
  30. // @include http://drunkpatrick.store/*
  31. // @include http://steamfarm.lequestore.ru/*
  32. // @include http://maxfarmshop.ru/*
  33. // @include http://bestkeystore.ru/*
  34. // @include http://bestfarmkey.lequestore.ru/*
  35. // @include http://tkfg.ru/*
  36. // @include http://indiegamekeys.lequestore.ru/*
  37. // @include http://m-b-shop.leque.shop/*
  38. // @include http://200plus.lequeshop.ru/*
  39. // @include http://randomkey.ru/*
  40. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
  41. // @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.0.6/sweetalert2.min.js
  42. // @resource SweetAlert2CSS https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.0.6/sweetalert2.min.css
  43. // @connect store.steampowered.com
  44. // @grant GM_xmlhttpRequest
  45. // @grant GM_setValue
  46. // @grant GM_getValue
  47. // @grant GM_addStyle
  48. // @grant GM_getResourceText
  49. // @run-at document-start
  50. // ==/UserScript==
  51.  
  52. /* global GM_xmlhttpRequest, GM_setValue, GM_getValue, GM_addStyle, GM_getResourceText,
  53. window, document, location, fetch, localStorage, Option, sessionStorage,
  54. swal */
  55.  
  56. // inject swal css
  57. GM_addStyle(GM_getResourceText('SweetAlert2CSS'));
  58.  
  59. // inject styles
  60. GM_addStyle(`
  61. .SWSE_hide { display: none; }
  62. .SWSE_settings, .SWSE_settings input { width: -webkit-fill-available; width: -moz-available; }
  63. .SWSE_settings .name { text-align: right; vertical-align: top; }
  64. .SWSE_settings .value { text-align: left; }
  65. .SWSE_settings .value > * { height: 30px; margin: 0 20px 10px; }
  66. .SWSE_settings .switch { position: relative; display: inline-block; width: 60px; }
  67. .SWSE_settings .switch input { display: none; }
  68. .SWSE_settings .slider {
  69. position: absolute;
  70. cursor: pointer;
  71. top: 0; left: 0; right: 0; bottom: 0;
  72. background-color: #ccc;
  73. transition: 0.4s;
  74. }
  75. .SWSE_settings .slider:before {
  76. position: absolute;
  77. content: "";
  78. height: 26px; width: 26px;
  79. left: 2px; bottom: 2px;
  80. background-color: white;
  81. transition: 0.4s;
  82. }
  83. .SWSE_settings input:checked + .slider { background-color: #2196F3; }
  84. .SWSE_settings input:focus + .slider { box-shadow: 0 0 1px #2196F3; }
  85. .SWSE_settings input:checked + .slider:before { transform: translateX(30px); }
  86. .SWSE_settings > span { display: inline-block; cursor: pointer; color: white; }
  87. .SWSE_owned {
  88. background-image: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 60%, rgba(0, 0, 0, 0.1) 90%) !important;
  89. background-color: #9ccc65 !important;
  90. transition: background 500ms ease 0s;
  91. color: white !important;
  92. }
  93. .SWSE_owned a { color: white !important; }
  94. `);
  95.  
  96. // load up
  97. const regURL = /(https?:\/\/)?([.\w]*steam[-.\w]*){1}\/.*?(apps?|subs?){1}\/(\d+){1}(\/.*\/?)?/m;
  98. const regKey = /((?:([A-Z0-9])(?!\2{4})){5}-){2,5}[A-Z0-9]{5}/g;
  99. const eol = "\r\n";
  100. const has = Object.prototype.hasOwnProperty;
  101.  
  102. const owned = JSON.parse(localStorage.getItem('SWSE_owned') || '{}');
  103. const config = {
  104. data: JSON.parse(GM_getValue('SWSE_config') || '{}'),
  105. set(key, value, callback = null) {
  106. this.data[key] = value;
  107. GM_setValue('SWSE_config', JSON.stringify(this.data));
  108.  
  109. if (typeof callback === 'function') callback();
  110. },
  111. get(key) {
  112. return has.call(this.data, key) ? this.data[key] : null;
  113. },
  114. init() {
  115. if (!has.call(this.data, 'language')) this.data.language = 'english';
  116. if (!has.call(this.data, 'count')) this.data.count = 1;
  117. if (!has.call(this.data, 'email')) this.data.email = '';
  118. }
  119. };
  120.  
  121. // text
  122. const i18n = {
  123. tchinese: {
  124. name: '繁體中文',
  125. settingsTitle: '設定',
  126. settingsCount: '購買數量',
  127. settingsEmail: '購買信箱',
  128. settingsLanguage: '語言',
  129. menuHideOwned: '隱藏已擁有',
  130. menuSyncLibrary: '同步遊戲庫',
  131. keyField: 'Steam 序號',
  132. payButtonText: '付款',
  133. syncSuccessTitle: '同步成功',
  134. syncSuccess: '成功同步Steam 遊戲庫資料'
  135. },
  136. schinese: {
  137. name: '简体中文',
  138. settingsTitle: '设置',
  139. settingsCount: '购买数量',
  140. settingsEmail: '购买邮箱',
  141. settingsLanguage: '语言',
  142. menuHideOwned: '隐藏已拥有',
  143. menuSyncLibrary: '同步游戏库',
  144. keyField: 'Steam 激活码',
  145. payButtonText: '付款',
  146. syncSuccessTitle: '同步成功',
  147. syncSuccess: '成功同步Steam 游戏库资料'
  148. },
  149. english: {
  150. name: 'English',
  151. settingsTitle: 'Settings',
  152. settingsCount: 'Purchase Quantity',
  153. settingsEmail: 'Purchase Email',
  154. settingsLanguage: 'Language',
  155. menuHideOwned: 'Hide Owned',
  156. menuSyncLibrary: 'Sync Library',
  157. keyField: 'Steam Keys',
  158. payButtonText: 'Pay',
  159. syncSuccessTitle: 'Sync Successful',
  160. syncSuccess: 'Successfully sync Steam library data'
  161. }
  162. };
  163. let text = has.call(i18n, config.get('language')) ? i18n[config.get('language')] : i18n.english;
  164.  
  165. const settingsHandler = arg => {
  166. swal.showLoading();
  167.  
  168. arg();
  169.  
  170. setTimeout(swal.hideLoading, 500);
  171. };
  172. const settings = () => {
  173. const panelHTML = `
  174. <table class="SWSE_settings">
  175. <tr>
  176. <td class="name">${text.settingsLanguage}</td>
  177. <td class="value"><select class="language"></select></td>
  178. </tr>
  179. <tr>
  180. <td class="name">${text.settingsCount}</td>
  181. <td class="value"><input type="number" class="count" value="1" min="1"></select></td>
  182. </tr>
  183. <tr>
  184. <td class="name">${text.settingsEmail}</td>
  185. <td class="value"><input type="text" class="email"></td>
  186. </tr>
  187. </table>
  188. `;
  189. const panelHandler = panel => {
  190. // apply settings
  191. const $panel = $(panel).find('.SWSE_settings');
  192.  
  193. // language
  194. const $language = $panel.find('.language');
  195.  
  196. Object.keys(i18n).forEach(language => {
  197. $language.append(new Option(i18n[language].name, language));
  198. });
  199. $panel.find(`option[value=${config.get('language')}]`).prop('selected', true);
  200. $language.change(settingsHandler.bind(null, () => {
  201. const newLanguage = $language.val();
  202. config.set('language', newLanguage);
  203.  
  204. text = has.call(i18n, newLanguage) ? i18n[newLanguage] : i18n.english;
  205. }));
  206.  
  207. // count, email
  208. ['count', 'email'].forEach(field => {
  209. const $field = $panel.find(`.${field}`);
  210. const val = config.get(field);
  211.  
  212. if (val) $field.val(val);
  213.  
  214. $field.change(settingsHandler.bind(null, () => {
  215. config.set(field, $field.val().trim());
  216. }));
  217. });
  218. };
  219.  
  220. swal({
  221. title: text.settingsTitle,
  222. html: panelHTML,
  223. onBeforeOpen: panelHandler
  224. });
  225. };
  226. const matchSteamUrl = (str = '') => {
  227. const input = str.trim();
  228. let output = null;
  229.  
  230. if (input.length > 0) {
  231. const found = input.match(regURL);
  232.  
  233. if (found) {
  234. output = {
  235. type: found[3].slice(0, 3),
  236. id: parseInt(found[4], 10),
  237. index: found.index,
  238. matched: found[0]
  239. };
  240. }
  241. }
  242.  
  243. return output;
  244. };
  245. const check = (d, s, c) => {
  246. let source = d;
  247. let selector = s;
  248. let callback = c;
  249.  
  250. if (typeof d === 'string') {
  251. // dom source omitted
  252. source = document;
  253. selector = d;
  254. callback = s;
  255. }
  256.  
  257. $(source).find(selector.split(',').map(x => `${x}:not(.SWSE_checked)`).join()).each((index, element) => {
  258. const $ele = $(element);
  259. let attr = null;
  260.  
  261. for (let i = 0; i < element.attributes.length; i += 1) {
  262. if (!attr) attr = matchSteamUrl(element.attributes[i].value);
  263. }
  264.  
  265. if (attr && owned[attr.type].includes(attr.id)) callback($ele, 'SWSE_owned');
  266.  
  267. $ele.addClass('SWSE_checked');
  268. });
  269. };
  270. const syncLibrary = (notify = false) => {
  271. GM_xmlhttpRequest({
  272. method: 'GET',
  273. url: `http://store.steampowered.com/dynamicstore/userdata/t=${Math.random()}`,
  274. onload: res => {
  275. if (res.status === 200) {
  276. const data = JSON.parse(res.response);
  277.  
  278. if (data.rgOwnedApps.length > 0) owned.app = data.rgOwnedApps;
  279. if (data.rgOwnedPackages.length > 0) owned.sub = data.rgOwnedPackages;
  280. owned.lastSync = Date.now();
  281.  
  282. localStorage.setItem('SWSE_owned', JSON.stringify(owned));
  283.  
  284. if (notify) {
  285. swal({
  286. title: text.syncSuccessTitle,
  287. text: text.syncSuccess,
  288. type: 'success',
  289. timer: 3000
  290. });
  291. }
  292. }
  293. }
  294. });
  295. };
  296.  
  297. const headerMenu = () => {
  298. // insert header menu
  299. GM_addStyle(`
  300. header.SWSE_header {
  301. display: flex !important;
  302. position: sticky;
  303. top: 0;
  304. padding: 0 7%;
  305. background: #2d3f51;
  306. box-shadow: 0 2px 5px rgba(68, 68, 68, 0.3);
  307. transition: all 0.2s ease;
  308. z-index: 9999;
  309. }
  310. .SWSE_nav ul { margin: 0; padding: 0; list-style: none; }
  311. .SWSE_nav li { float: left; }
  312. .SWSE_nav a {
  313. color: #f5f5f5;
  314. text-decoration: none;
  315. display: block;
  316. padding: 1.5em;
  317. font-size: initial;
  318. font-weight: 600;
  319. text-transform: uppercase;
  320. letter-spacing: 1px;
  321. transition: all 0.2s ease;
  322. }
  323. .SWSE_nav a:hover { color: #16A085; }
  324. `);
  325.  
  326. const $nav = $('<nav class="SWSE_nav"><ul></ul></nav>');
  327.  
  328. $nav.prependTo('body').wrap('<header class="SWSE_header"></header>');
  329. $nav.find('ul').append(
  330. // hide owned button
  331. $(`<li><a>${text.menuHideOwned}</a></li>`).click(() => {
  332. $('.SWSE_owned').toggleClass('SWSE_hide');
  333. }),
  334. // sync library button
  335. $(`<li><a>${text.menuSyncLibrary}</a></li>`).click(syncLibrary.bind(null, true)),
  336. // settings button
  337. $(`<li><a>${text.settingsTitle}</a></li>`).click(settings));
  338. };
  339. const handler = () => {
  340. // order page, auto download keys
  341. if (location.pathname.startsWith('/order/get/')) {
  342. const url = $('table a').attr('href');
  343.  
  344. if (url.length > 0) {
  345. fetch(url, {
  346. method: 'GET',
  347. credentials: 'include'
  348. }).then(res => res.text()).then(t => {
  349. const keys = t.match(regKey);
  350.  
  351. $('table tr:last-child').before(`<tr><td>${text.keyField}</td><td>${keys.join(eol)}</td></tr>`);
  352. });
  353. }
  354. // product page
  355. } else {
  356. // pre-fill inputs
  357. $('input[name=count]').val(config.get('count'));
  358. $('input[name=email]').val(config.get('email'));
  359.  
  360. // insert pay button
  361. const $payButton = $(`<span class="SWSE_payButton">${text.payButtonText}</span>`).click(() => {
  362. const data = {
  363. wm_wmk: true,
  364. purse: $('#purse > span, #copyfund > b').text(),
  365. amount: parseFloat($('#price > span, .payprice').text()),
  366. desc: $('#message > span, #copybill > b').text()
  367. };
  368.  
  369. window.open(`https://mini.wmtransfer.com/SendWebMoney.aspx?${JSON.stringify(data)}`, '', 'height=800,width=1000');
  370. });
  371.  
  372. switch (location.hostname) {
  373. case 'steamkeyswhosales.com':
  374. case 'alfakeys.ru':
  375. $payButton.addClass('btn').css({
  376. 'margin-right': '10px',
  377. cursor: 'pointer',
  378. color: '#FFF',
  379. 'background-color': '#337ab7',
  380. 'border-color': '#2e6da4'
  381. }).insertBefore('#check_pay');
  382. break;
  383. case 'cheap-steam-games.ru':
  384. case 'lastkey.ru':
  385. case 'keys.farm':
  386. case 'steamkeys-shop.ru':
  387. case 'maxfarmshop.ru':
  388. case 'bestkeystore.ru':
  389. case 'bestfarmkey.lequestore.ru':
  390. case 'steamfarmkey.ru':
  391. case 'indiegamekeys.lequestore.ru':
  392. case 'randomkey.ru':
  393. $payButton.addClass('btn-leque btn-leque-primary btn-leque-xs').css({
  394. float: 'right',
  395. 'margin-top': '5px'
  396. }).insertAfter('.btn-leque-xs');
  397. break;
  398. case 'steam1.lequeshop.ru':
  399. case 'steam1.ru':
  400. case 'steam-tab.ru':
  401. case 'steamd.lequeshop.ru':
  402. case 'steamkeystore.ru':
  403. case 'steamrandomkeys.ru':
  404. case 'keyssell.ru':
  405. $payButton.addClass('btn btn-primary').css('margin-top', '10px').insertBefore('.checkpayButton');
  406. break;
  407. case 'dmshop.lequeshop.ru':
  408. case 'kartonanet.lequeshop.ru':
  409. case 'rig4all.lequeshop.ru':
  410. case 'steamkey.lequeshop.ru':
  411. case 'farmacc.ru':
  412. case 'drunkpatrick.store':
  413. case 'steamfarm.lequestore.ru':
  414. case 'animekeys.ru':
  415. case 'tkfg.ru':
  416. case '200plus.lequeshop.ru':
  417. $payButton.addClass('btn btn-primary').insertBefore('.checkpayButton, .checkpaybtn');
  418. break;
  419. default:
  420. }
  421.  
  422. // check owned
  423. switch (location.hostname) {
  424. case 'steamkeys.ovh':
  425. check('a[href*="steampowered"]', ($ele, classes) => {
  426. $ele.closest('tr').addClass(classes);
  427. });
  428. break;
  429. case 'steamkeyswhosales.com':
  430. case 'alfakeys.ru':
  431. case 'ign.akens.ru':
  432. case 'bestkey.akens.ru':
  433. case 'goldkeys.akens.ru':
  434. case 'domenkeys.akens.ru':
  435. case 'cada.akens.ru':
  436. check('div[title*="steam"]', ($ele, classes) => {
  437. $ele.closest('tr').addClass(classes);
  438. });
  439. break;
  440. case 'keymarket.pw':
  441. check('div[style*="steam/apps/"]', ($ele, classes) => {
  442. $ele.nextAll().addClass(classes);
  443. $ele.parent().parent().addClass(classes);
  444. });
  445. break;
  446. case 'steamkey.lequeshop.ru':
  447. check('img[src*="steam/apps/"]', ($ele, classes) => {
  448. $ele.next().addClass(classes);
  449. $ele.parent().addClass(classes);
  450. });
  451. break;
  452. case 'cheap-steam-games.ru':
  453. check('img[src*="steam/apps/"]', ($ele, classes) => {
  454. $ele.closest('.hero-feature').addClass(classes);
  455. $ele.parent().next().addClass(classes);
  456. });
  457. break;
  458. case 'cheapkey.lequeshop.ru':
  459. check('div[style*="steam/apps/"]', ($ele, classes) => {
  460. $ele.parent().addClass(classes);
  461. });
  462. break;
  463. case 'steamfarmkey.ru':
  464. case 'kartonanet.lequeshop.ru':
  465. case 'lastkey.ru':
  466. case 'rig4all.lequeshop.ru':
  467. case 'steamkeys-shop.ru':
  468. case 'farmacc.ru':
  469. case 'keys.lequestore.ru':
  470. case 'steamfarm.lequestore.ru':
  471. case 'proxzy.lequestore.ru':
  472. case 'maxfarmshop.ru':
  473. case 'bestkeystore.ru':
  474. case 'keys.farm':
  475. case 'alonekey.net':
  476. case 'bestfarmkey.lequestore.ru':
  477. case 'm-b-shop.leque.shop':
  478. case 'indiegamekeys.lequestore.ru':
  479. case '200plus.lequeshop.ru':
  480. check('img[src*="steam"]', ($ele, classes) => {
  481. $ele.closest('tr').addClass(classes);
  482. });
  483. break;
  484. case 'keyssell.ru':
  485. case 'steam-tab.ru':
  486. case 'steamd.lequeshop.ru':
  487. case 'steam1.ru':
  488. case 'steamkeystore.ru':
  489. case 'steam1.lequeshop.ru':
  490. case 'steamrandomkeys.ru':
  491. check('a > img[src*="steam/apps/"]', ($ele, classes) => {
  492. $ele.closest('.item-loop').addClass(classes);
  493. $ele.closest('div').prev().addClass(classes);
  494. });
  495. check('.item-poster > img[src*="steam/apps/"]', ($ele, classes) => {
  496. $ele.parent().addClass(classes);
  497. $ele.prev().prev().addClass(classes);
  498. });
  499. break;
  500. case 'reronage.akens.ru':
  501. check('.good-title > div', ($ele, classes) => {
  502. $ele.closest('tr').addClass(classes);
  503. });
  504. break;
  505. case 'animekeys.ru':
  506. check('img[src*="steam"]', ($ele, classes) => {
  507. $ele.closest('div.list-item').addClass(classes);
  508. });
  509. break;
  510. case 'tkfg.ru':
  511. case 'randomkey.ru':
  512. $('#header').css('position', 'initial');
  513. check('a > img[src*="steam"]', ($ele, classes) => {
  514. $ele.parent().prev('.title').find('p').addClass(classes);
  515. $ele.closest('.b-poster').addClass(classes);
  516. });
  517. break;
  518. case 'steamground.com':
  519. GM_xmlhttpRequest({
  520. method: 'GET',
  521. url: 'https://www.steamgifts.com/discussion/iy081/steamground-wholesale-build-a-bundle',
  522. onload: res => {
  523. if (res.status !== 200) return;
  524.  
  525. const games = $('<div/>', {
  526. html: res.response
  527. }).find('.comment__description:first table a[href*="steampowered"]');
  528. const titles = $('.wholesale-card_title');
  529. const hrefs = {};
  530. const process = t => {
  531. let tx = t.trim();
  532.  
  533. if (tx === 'Ball of Light (Journey)') tx = 'Ball of Light';
  534. if (tx === 'Shake Your Money Simulator') tx = 'Shake Your Money Simulator 2016';
  535.  
  536. return tx.toLowerCase().replace(/[\W]/g, '');
  537. };
  538.  
  539. games.each((index, element) => {
  540. hrefs[process(element.textContent)] = element.href;
  541. });
  542. titles.each((index, element) => {
  543. const $ele = $(element);
  544. const href = hrefs[process($ele.text())] || '';
  545.  
  546. if (href.length > 0) $ele.parent().attr('href', href);
  547. });
  548.  
  549. check('.wholesale-card > a[href*="steampowered"]', (element, classes) => {
  550. $(element).parent().addClass(classes);
  551. $(element).children().eq(1).css('color', 'black');
  552. });
  553. }
  554. });
  555. break;
  556. default:
  557. }
  558. }
  559. };
  560. const init = () => {
  561. // on WebMoney payment page
  562. if (location.hostname === 'mini.wmtransfer.com') {
  563. if (location.href.includes('purses-view-history') && sessionStorage.getItem('wm_wmk')) window.close();
  564. if (location.pathnaame === '/SendWebMoney.aspx' && location.search.length > 0) {
  565. try {
  566. const search = location.href.split('?').pop();
  567. const data = JSON.parse(decodeURIComponent(search));
  568.  
  569. if (data.wm_wmk) {
  570. $('#ctl00_cph_tbEmailOrPurseNumber').val(data.purse);
  571. $('#ctl00_cph_tbAmount').val(data.amount);
  572. $('#ctl00_cph_tbDesc').val(data.desc);
  573.  
  574. sessionStorage.setItem('wm_wmk', 1);
  575. }
  576. } catch (err) {
  577. throw err;
  578. }
  579. }
  580. // wholesale sites
  581. } else {
  582. config.init();
  583.  
  584. headerMenu();
  585. $(window).on('load', handler);
  586.  
  587. // sync owned every 10 min
  588. const syncTimer = 10 * 60 * 1000;
  589. if (!owned.lastSync || owned.lastSync < Date.now() - syncTimer) syncLibrary();
  590. }
  591. };
  592.  
  593. $(init);

QingJ © 2025

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