Steam Key Helper

try to take over the world!

目前为 2017-11-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Steam Key Helper
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.8.6
  5. // @description try to take over the world!
  6. // @icon http://store.steampowered.com/favicon.ico
  7. // @author Bisumaruko
  8. // @include http*://*
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/6.6.6/sweetalert2.min.js
  11. // @resource SweetAlert2CSS https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/6.6.6/sweetalert2.min.css
  12. // @connect store.steampowered.com
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_addStyle
  17. // @grant GM_getResourceText
  18. // @run-at document-start
  19. // @noframes
  20. // ==/UserScript==
  21.  
  22. /* global GM_xmlhttpRequest, GM_setValue, GM_getValue, GM_addStyle, GM_getResourceText,
  23. swal, g_AccountID, g_sessionID, g_oSuggestParams,
  24. window, document, location, MutationObserver, Option */
  25.  
  26. // setup jQuery
  27. const $ = jQuery.noConflict(true);
  28.  
  29. // inject swal css
  30. GM_addStyle(GM_getResourceText('SweetAlert2CSS'));
  31.  
  32. // inject CSS
  33. GM_addStyle(`
  34. .hide { display: none;}
  35. .SKH_link { color: #57bae8; cursor: pointer;}
  36. .SKH_link:hover { text-decoration: underline;}
  37. .SKH_activated { text-decoration: line-through;}
  38. .SKH_panel {
  39. width: 60px;
  40. height: 60px;
  41. position: fixed;
  42. top: 50%;
  43. right: 20px;
  44. transform: translateY(-50%);
  45. background-color: rgb(87, 186, 232);
  46. opacity: 0.5;
  47. text-align: center;
  48. }
  49. .SKH_panel:hover { opacity: 1;}
  50. .SKH_panel .switch {
  51. position: relative;
  52. display: inline-block;
  53. width: 40px;
  54. height: 24px;
  55. margin-top: 5px;
  56. }
  57. .SKH_panel .switch input { display: none;}
  58. .SKH_panel .slider {
  59. position: absolute;
  60. cursor: pointer;
  61. top: 0;
  62. left: 0;
  63. right: 0;
  64. bottom: 0;
  65. background-color: #ccc;
  66. transition: 0.4s;
  67. }
  68. .SKH_panel .slider:before {
  69. position: absolute;
  70. content: "";
  71. height: 20px;
  72. width: 20px;
  73. left: 2px;
  74. bottom: 2px;
  75. background-color: white;
  76. transition: 0.4s;
  77. }
  78. .SKH_panel input:checked + .slider { background-color: #2196F3;}
  79. .SKH_panel input:focus + .slider { box-shadow: 0 0 1px #2196F3;}
  80. .SKH_panel input:checked + .slider:before { transform: translateX(16px);}
  81. .SKH_panel > span { display: inline-block; cursor: pointer; color: white;}
  82. .SKH_panel > button {
  83. width: 20px;
  84. height: 20px;
  85. position: absolute;
  86. bottom: 0;
  87. padding: 2px;
  88. background-color: transparent;
  89. background-size: 18px;
  90. background-repeat: no-repeat;
  91. background-origin: padding-box;
  92. background-position: 50% 50%;
  93. border: none;
  94. outline: none;
  95. box-sizing: border-box;
  96. cursor: pointer;
  97. }
  98. .SKH_panel > .hidePanel {
  99. left: 0;
  100. background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzIgMzIiIGhlaWdodD0iMzJweCIgaWQ9InN2ZzIiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDMyIDMyIiB3aWR0aD0iMzJweCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgaWQ9ImJhY2tncm91bmQiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMzIiIHdpZHRoPSIzMiIvPjwvZz48ZyBpZD0iY2FuY2VsIj48cG9seWdvbiBwb2ludHM9IjIsMjYgNiwzMCAxNiwyMCAyNiwzMCAzMCwyNiAyMCwxNiAzMCw2IDI2LDIgMTYsMTIgNiwyIDIsNiAxMiwxNiAgIi8+PC9nPjwvc3ZnPg==);
  101. filter: opacity(60%);
  102. }
  103. .SKH_panel > .disable {
  104. left: 20px;
  105. background-size: contain;
  106. background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMC8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvVFIvMjAwMS9SRUMtU1ZHLTIwMDEwOTA0L0RURC9zdmcxMC5kdGQnPjxzdmcgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjAiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGc+PHBhdGggZD0iTTEyLDRjNC40LDAsOCwzLjYsOCw4cy0zLjYsOC04LDhzLTgtMy42LTgtOFM3LjYsNCwxMiw0IE0xMiwyQzYuNSwyLDIsNi41LDIsMTJjMCw1LjUsNC41LDEwLDEwLDEwczEwLTQuNSwxMC0xMCAgIEMyMiw2LjUsMTcuNSwyLDEyLDJMMTIsMnoiLz48L2c+PGxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIgeDE9IjE4LjIiIHgyPSI1LjgiIHkxPSIxOC4yIiB5Mj0iNS44Ii8+PC9zdmc+);
  107. filter: opacity(60%);
  108. }
  109. .SKH_panel > .settings {
  110. left: 40px;
  111. background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGhlaWdodD0iMzJweCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzIgMzI7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxnIGlkPSJMYXllcl8xIi8+PGcgaWQ9ImNvZyI+PHBhdGggZD0iTTMyLDE3Ljk2OXYtNGwtNC43ODEtMS45OTJjLTAuMTMzLTAuMzc1LTAuMjczLTAuNzM4LTAuNDQ1LTEuMDk0bDEuOTMtNC44MDVMMjUuODc1LDMuMjUgICBsLTQuNzYyLDEuOTYxYy0wLjM2My0wLjE3Ni0wLjczNC0wLjMyNC0xLjExNy0wLjQ2MUwxNy45NjksMGgtNGwtMS45NzcsNC43MzRjLTAuMzk4LDAuMTQxLTAuNzgxLDAuMjg5LTEuMTYsMC40NjlsLTQuNzU0LTEuOTEgICBMMy4yNSw2LjEyMWwxLjkzOCw0LjcxMUM1LDExLjIxOSw0Ljg0OCwxMS42MTMsNC43MDMsMTIuMDJMMCwxNC4wMzF2NGw0LjcwNywxLjk2MWMwLjE0NSwwLjQwNiwwLjMwMSwwLjgwMSwwLjQ4OCwxLjE4OCAgIGwtMS45MDIsNC43NDJsMi44MjgsMi44MjhsNC43MjMtMS45NDVjMC4zNzksMC4xOCwwLjc2NiwwLjMyNCwxLjE2NCwwLjQ2MUwxNC4wMzEsMzJoNGwxLjk4LTQuNzU4ICAgYzAuMzc5LTAuMTQxLDAuNzU0LTAuMjg5LDEuMTEzLTAuNDYxbDQuNzk3LDEuOTIybDIuODI4LTIuODI4bC0xLjk2OS00Ljc3M2MwLjE2OC0wLjM1OSwwLjMwNS0wLjcyMywwLjQzOC0xLjA5NEwzMiwxNy45Njl6ICAgIE0xNS45NjksMjJjLTMuMzEyLDAtNi0yLjY4OC02LTZzMi42ODgtNiw2LTZzNiwyLjY4OCw2LDZTMTkuMjgxLDIyLDE1Ljk2OSwyMnoiIHN0eWxlPSJmaWxsOiM0RTRFNTA7Ii8+PC9nPjwvc3ZnPg==);
  112. }
  113. `);
  114.  
  115. // inject settings panel css
  116. GM_addStyle(`
  117. .SKH_settings .name { text-align: right; vertical-align: top; white-space: nowrap;}
  118. .SKH_settings .value { text-align: left;}
  119. .SKH_settings .value > * { height: 30px; margin: 0 20px 10px;}
  120. .SKH_settings .switch { position: relative; display: inline-block; width: 60px;}
  121. .SKH_settings .switch input { display: none;}
  122. .SKH_settings .slider {
  123. position: absolute;
  124. cursor: pointer;
  125. top: 0;
  126. left: 0;
  127. right: 0;
  128. bottom: 0;
  129. background-color: #ccc;
  130. transition: 0.4s;
  131. }
  132. .SKH_settings .slider:before {
  133. position: absolute;
  134. content: "";
  135. height: 26px;
  136. width: 26px;
  137. left: 2px;
  138. bottom: 2px;
  139. background-color: white;
  140. transition: 0.4s;
  141. }
  142. .SKH_settings input:checked + .slider { background-color: #2196F3;}
  143. .SKH_settings input:focus + .slider { box-shadow: 0 0 1px #2196F3;}
  144. .SKH_settings input:checked + .slider:before { transform: translateX(30px);}
  145. .SKH_settings input[type=text] { width: 200px;}
  146. .SKH_settings input[type=number] { width: 60px; height: 30px; box-sizing: border-box;}
  147. .SKH_settings textarea { width: 200px; min-width: 200px; height: 60px; min-height: 60px;}
  148. .SKH_settings label + p { display: none; transition: display 1s;}
  149. .SKH_settings label ~ *:last-child { display: block; transition: display 1s;}
  150. .SKH_settings .toggleOn label + p { display: block;}
  151. .SKH_settings .toggleOn label ~ *:last-child { display: none;}
  152. `);
  153.  
  154. // load config
  155. const eol = "\n";
  156. const has = Object.prototype.hasOwnProperty;
  157.  
  158. const regKey = /(?=.*\d)(?:(?:([A-Z0-9])(?!\1{4})){5}-){2,5}[A-Z0-9]{5}/g;
  159. const excludedTag = ['SCRIPT', 'STYLE', 'IFRAME', 'CANVAS', 'TEXTAREA'];
  160. let activating = false;
  161.  
  162. const activated = JSON.parse(GM_getValue('SKH_activated') || '[]');
  163. const currentActivated = [];
  164. const config = {
  165. data: JSON.parse(GM_getValue('SKH_config') || '{}'),
  166. save() {
  167. GM_setValue('SKH_config', JSON.stringify(this.data));
  168. },
  169. set(key, value, callback = null) {
  170. this.data[key] = value;
  171. this.save();
  172.  
  173. if (typeof callback === 'function') callback();
  174. },
  175. get(key) {
  176. return has.call(this.data, key) ? this.data[key] : null;
  177. },
  178. push(key, value) {
  179. if (Array.isArray(this.data[key])) {
  180. this.data[key].push(value);
  181. this.save();
  182. }
  183. },
  184. splice(key, start, count) {
  185. if (Array.isArray(this.data[key])) {
  186. this.data[key].splice(start, count);
  187. this.save();
  188. }
  189. },
  190. init() {
  191. if (!has.call(this.data, 'autoUpdateSessionID')) this.data.autoUpdateSessionID = true;
  192. if (!has.call(this.data, 'autoClosePopup')) this.data.autoClosePopup = false;
  193. if (!has.call(this.data, 'autoClosePopupTimer')) this.data.autoClosePopupTimer = 3;
  194. if (!has.call(this.data, 'autoReplyActivated')) this.data.autoReplyActivated = false;
  195. if (!has.call(this.data, 'enabledForever')) this.data.enabledForever = true;
  196. if (!has.call(this.data, 'enabled')) this.data.enabled = [];
  197. if (!has.call(this.data, 'disabled')) this.data.disabled = [];
  198. if (!has.call(this.data, 'hideForever')) this.data.hideForever = false;
  199. if (!has.call(this.data, 'hide')) this.data.hide = [];
  200. if (!has.call(this.data, 'off')) this.data.off = [];
  201. }
  202. };
  203.  
  204. config.init();
  205.  
  206. // text
  207. const i18n = {
  208. tchinese: {
  209. name: '繁體中文',
  210. updateSuccessTitle: '更新成功!',
  211. updateSuccess: '成功更新Steam sessionID',
  212. errorTitle: '糟糕!',
  213. errorUnexpected: '發生未知錯誤,請稍後再試',
  214. errorInvalidKey: '序號錯誤',
  215. errorUsedKey: '序號已被使用',
  216. errorRateLimited: '啟動受限',
  217. errorCountryRestricted: '地區限制',
  218. errorAlreadyOwned: '產品已擁有',
  219. errorMissingBaseGame: '未擁有主程式',
  220. errorPS3Required: '需要PS3 啟動',
  221. errorGiftWallet: '偵測到禮物卡/錢包序號',
  222. errorFailedRequest: '處理資料發生錯誤,請稍後再試',
  223. errorFailedRequestNeedUpdate: '請求發生錯誤,請稍後再試<br>或者嘗試更新SessionID',
  224. errorActivated: '你已啟動過這序號',
  225. successTitle: '啟動成功!',
  226. processingTitle: '喵~',
  227. processingMsg: '啟動序號中,請稍後',
  228. notLoggedInTitle: '未登入',
  229. notLoggedInMsg: '請登入Steam 以讓腳本紀錄SessionID',
  230. missingTitle: '未發現SessionID',
  231. missingMsg: '請問要更新SessionID 嗎?',
  232. titlehidePanel: '在這網站隱藏懸浮框',
  233. titleDisable: '在這網站禁止腳本',
  234. titleSettings: '開啟設定',
  235. settingsTitle: '設定',
  236. settingsAutoUpdateSessionID: '自動更新SessionID',
  237. settingsSessionID: '我的sessionID',
  238. settingsLanguage: '語言',
  239. settingsAutoClosePopup: '自動關閉彈窗',
  240. settingsTimerDisabled: '不關閉彈窗',
  241. settingsTimerSecond: '秒',
  242. settingsAutoReplyActivated: '自動回覆啟動序號',
  243. settingsEnabledForever: '全域運行腳本',
  244. settingsEnabled: '在這些網站運行腳本',
  245. settingsDisabled: '在這些網站禁止腳本',
  246. settingsHideForever: '全域隱藏懸浮',
  247. settingsHide: '在這些網站隱藏懸浮',
  248. settingsOff: '在這些網站暫停腳本',
  249. placeholderEnabled: '如不全域運行腳本此欄不得為空',
  250. activatedReply: '感謝大佬!已拿%KEYS%'
  251. },
  252. schinese: {
  253. name: '简体中文',
  254. updateSuccessTitle: '更新成功',
  255. updateSuccess: '成功更新Steam sessionID',
  256. errorTitle: '糟糕!',
  257. errorUnexpected: '发生未知错误,请稍后再试',
  258. errorInvalidKey: '激活码错误',
  259. errorUsedKey: '激活码已被使用',
  260. errorRateLimited: '激活受限',
  261. errorCountryRestricted: '地区限制',
  262. errorAlreadyOwned: '产品已拥有',
  263. errorMissingBaseGame: '未拥有游戏本体',
  264. errorPS3Required: '需要PS3 激活',
  265. errorGiftWallet: '侦测到礼物卡/钱包激活码',
  266. errorFailedRequest: '处理资料发生错误,请稍后再试',
  267. errorFailedRequestNeedUpdate: '请求发生错误,请稍后再试<br>或者尝试更新SessionID',
  268. errorActivated: '你已激活过这激活码',
  269. successTitle: '激活成功!',
  270. processingTitle: '喵~',
  271. processingMsg: '激活中,请稍后',
  272. notLoggedInTitle: '未登入',
  273. notLoggedInMsg: '请登入Steam 以让脚本记录SessionID',
  274. missingTitle: '未发现SessionID',
  275. missingMsg: '请问要更新SessionID 吗?',
  276. titlehidePanel: '在这网站隐藏悬浮',
  277. titleDisable: '在这网站禁止脚本',
  278. titleSettings: '打开设置',
  279. settingsTitle: '设置',
  280. settingsAutoUpdateSessionID: '自动更新SessionID',
  281. settingsSessionID: '我的sessionID',
  282. settingsLanguage: '语言',
  283. settingsAutoClosePopup: '自动关闭弹窗',
  284. settingsTimerDisabled: '不关闭弹窗',
  285. settingsTimerSecond: '秒',
  286. settingsAutoReplyActivated: '自动回复激活KEY',
  287. settingsEnabledForever: '全域运行脚本',
  288. settingsEnabled: '在这些网站运行脚本',
  289. settingsDisabled: '在这些网站禁止脚本',
  290. settingsHideForever: '全域隐藏悬浮',
  291. settingsHide: '在这些网站隐藏悬浮',
  292. settingsOff: '在这些网站暂停脚本',
  293. placeholderEnabled: '如不全域运行脚本此栏不得为空',
  294. activatedReply: '感谢大佬!已激活%KEYS%'
  295. },
  296. english: {
  297. name: 'English',
  298. updateSuccessTitle: 'Update Successful!',
  299. updateSuccess: 'Steam sessionID is successfully updated',
  300. errorTitle: 'Opps!',
  301. errorUnexpected: 'An unexpected error has occured, please try again later',
  302. errorInvalidKey: 'Invalid Key',
  303. errorUsedKey: 'Used Key',
  304. errorRateLimited: 'Rate Limited',
  305. errorCountryRestricted: 'Country Restricted',
  306. errorAlreadyOwned: 'Product Already Owned',
  307. errorMissingBaseGame: 'Missing Base Game',
  308. errorPS3Required: 'PS3 Activation Required',
  309. errorGiftWallet: 'Gift Card/Wallet Code Detected',
  310. errorFailedRequest: 'Result parse failed, please try again',
  311. errorFailedRequestNeedUpdate: 'Request failed, please try again<br>or update sessionID',
  312. errorActivated: 'You have activated this key before',
  313. successTitle: 'Activation Successful!',
  314. processingTitle: 'Nyaa~',
  315. processingMsg: 'Activating key, please wait',
  316. notLoggedInTitle: 'Not Logged-In',
  317. notLoggedInMsg: 'Please login to Steam so sessionID can be saved',
  318. missingTitle: 'Missing SessionID',
  319. missingMsg: 'Do you want to update your Steam sessionID?',
  320. titlehidePanel: 'Hide floating panel on this site',
  321. titleDisable: 'Disable script on this site',
  322. titleSettings: 'Open settings panel',
  323. settingsTitle: 'Settings',
  324. settingsAutoUpdateSessionID: 'Auto Update SessionID',
  325. settingsSessionID: 'Your sessionID',
  326. settingsLanguage: 'Language',
  327. settingsAutoClosePopup: 'Close popup in',
  328. settingsTimerDisabled: 'Don\'t close popup',
  329. settingsTimerSecond: 'second',
  330. settingsAutoReplyActivated: 'Auto Reply Activated Keys',
  331. settingsEnabledForever: 'Enable scrip all the time',
  332. settingsEnabled: 'Enable script on',
  333. settingsDisabled: 'Disable script on',
  334. settingsHideForever: 'Hide floating panel all the time',
  335. settingsHide: 'Hide floating panel on',
  336. settingsOff: 'Turn off script on',
  337. placeholderEnabled: 'Must not be empty if script not enabled all the time',
  338. activatedReply: 'Thank you for the keys! Activated %KEYS%'
  339. }
  340. };
  341. let text = has.call(i18n, config.get('language')) ? i18n[config.get('language')] : i18n.english;
  342.  
  343. // functions
  344. const getSessionID = () => {
  345. GM_xmlhttpRequest({
  346. method: 'GET',
  347. url: 'https://store.steampowered.com/',
  348. onload: res => {
  349. if (res.status === 200) {
  350. const accountID = res.response.match(/g_AccountID = (\d+)/).pop();
  351. const sessionID = res.response.match(/g_sessionID = "(\w+)"/).pop();
  352.  
  353. if (accountID > 0) config.set('sessionID', sessionID);else {
  354. swal({
  355. title: text.notLoggedInTitle,
  356. text: text.notLoggedInMsg,
  357. type: 'error',
  358. showCancelButton: true
  359. }).then(() => {
  360. window.open('https://store.steampowered.com/');
  361. });
  362. }
  363. }
  364. }
  365. });
  366. };
  367. const settings = {
  368. construct() {
  369. const panelHTML = `
  370. <div class="SKH_settings">
  371. <table>
  372. <tr>
  373. <td class="name">${text.settingsAutoUpdateSessionID}</td>
  374. <td class="value">
  375. <label class="switch">
  376. <input type="checkbox" class="autoUpdateSessionID">
  377. <span class="slider"></span>
  378. </label>
  379. </td>
  380. </tr>
  381. <tr>
  382. <td class="name">${text.settingsSessionID}</td>
  383. <td class="value">
  384. <input type="text" class="sessionID" value="${config.get('sessionID')}" disabled>
  385. </td>
  386. </tr>
  387. <tr>
  388. <td class="name">${text.settingsLanguage}</td>
  389. <td class="value">
  390. <select class="language"></select>
  391. </td>
  392. </tr>
  393. <tr>
  394. <td class="name">${text.settingsAutoClosePopup}</td>
  395. <td class="value">
  396. <label class="switch">
  397. <input type="checkbox" class="autoClosePopup">
  398. <span class="slider"></span>
  399. </label>
  400. <p><input type="number" class="autoClosePopupTimer" min="0" value="${config.get('autoClosePopupTimer')}"> ${text.settingsTimerSecond}</p>
  401. <p class="timerDisabledText">${text.settingsTimerDisabled}</p>
  402. </td>
  403. </tr>
  404. <tr>
  405. <td class="name">${text.settingsAutoReplyActivated}</td>
  406. <td class="value">
  407. <label class="switch">
  408. <input type="checkbox" class="autoReplyActivated">
  409. <span class="slider"></span>
  410. </label>
  411. </td>
  412. </tr>
  413. <tr>
  414. <td class="name">${text.settingsEnabled}</td>
  415. <td class="value">
  416. <label class="switch">
  417. <input type="checkbox" class="enabledForever">
  418. <span class="slider"></span>
  419. </label>
  420. <p class="enabledForeverText">${text.settingsEnabledForever}</p>
  421. <textarea class="enabledList" placeholder="${text.placeholderEnabled}"></textarea>
  422. </td>
  423. </tr>
  424. <tr>
  425. <td class="name">${text.settingsDisabled}</td>
  426. <td class="value">
  427. <textarea class="disabledList"></textarea>
  428. </td>
  429. </tr>
  430. <tr>
  431. <td class="name">${text.settingsHide}</td>
  432. <td class="value">
  433. <label class="switch">
  434. <input type="checkbox" class="hideForever">
  435. <span class="slider"></span>
  436. </label>
  437. <p class="hideForeverText">${text.settingsHideForever}</p>
  438. <textarea class="hideList"></textarea>
  439. </td>
  440. </tr>
  441. <tr>
  442. <td class="name">${text.settingsOff}</td>
  443. <td class="value">
  444. <textarea class="offList"></textarea>
  445. </td>
  446. </tr>
  447. </table>
  448. </div>
  449. `;
  450.  
  451. return panelHTML;
  452. },
  453. display() {
  454. swal({
  455. title: text.settingsTitle,
  456. html: this.construct()
  457. });
  458.  
  459. // apply settings
  460. const $panel = $(swal.getContent());
  461. const $sessionID = $panel.find('.sessionID');
  462. const $language = $panel.find('.language');
  463.  
  464. // toggles
  465. $panel.find('input[type="checkbox"]').each((index, input) => {
  466. const $input = $(input);
  467.  
  468. $input.change(e => {
  469. swal.showLoading();
  470.  
  471. const setting = e.delegateTarget.className;
  472. const state = e.delegateTarget.checked;
  473.  
  474. config.set(setting, state);
  475. $(e.delegateTarget).closest('td').toggleClass('toggleOn', state);
  476.  
  477. if (setting === 'autoUpdateSessionID') $sessionID.attr('disabled', state);
  478.  
  479. setTimeout(swal.hideLoading, 500);
  480. });
  481. $input.prop('checked', !!config.get(input.className)).trigger('change');
  482. });
  483.  
  484. // sessionID input
  485. $sessionID.prop('disabled', config.get('autoUpdateSessionID'));
  486. $sessionID.change(() => {
  487. swal.showLoading();
  488.  
  489. config.set('sessionID', $sessionID.val().trim());
  490.  
  491. setTimeout(swal.hideLoading, 500);
  492. });
  493.  
  494. // language
  495. Object.keys(i18n).forEach(language => {
  496. $language.append(new Option(i18n[language].name, language));
  497. });
  498. $panel.find(`option[value=${config.get('language')}]`).prop('selected', true);
  499. $language.change(() => {
  500. swal.showLoading();
  501.  
  502. const newLanguage = $language.val();
  503. config.set('language', newLanguage);
  504.  
  505. text = has.call(i18n, newLanguage) ? i18n[newLanguage] : i18n.english;
  506.  
  507. setTimeout(swal.hideLoading, 500);
  508. });
  509.  
  510. // timer
  511. $panel.find('.autoClosePopupTimer').change(e => {
  512. const second = parseInt(e.delegateTarget.value, 10);
  513.  
  514. config.set('autoClosePopupTimer', Number.isInteger(second) ? second : 3);
  515. });
  516.  
  517. // websites list
  518. ['enabled', 'disabled', 'hide', 'off'].forEach(list => {
  519. const $list = $panel.find(`.${list}List`);
  520.  
  521. $list.val(config.get(list).join(eol));
  522. $list.change(() => {
  523. swal.showLoading();
  524.  
  525. const hostList = $list.val().split(eol).map(x => x.trim()).filter(x => x.length);
  526.  
  527. config.set(list, hostList);
  528. if (list === 'enabled') {
  529. const state = !!$panel.find('.enabledForever:checked').length;
  530.  
  531. // script is not enabled all the time and empty enabled website
  532. if (!state && hostList.length === 0) config.set('enabledForever', true);
  533. }
  534.  
  535. setTimeout(swal.hideLoading, 500);
  536. });
  537. });
  538. }
  539. };
  540. const insertPanel = callback => {
  541. const $panel = $('<div class="SKH_panel"></div>');
  542.  
  543. // add toggle switch
  544. $panel.append($(`
  545. <label class="switch">
  546. <input type="checkbox">
  547. <span class="slider"></span>
  548. </label>
  549. `).change(() => {
  550. const host = location.hostname;
  551. const toggle = !!$('.SKH_panel input:checked').length;
  552. const index = config.get('off').indexOf(host);
  553.  
  554. if (toggle && index > -1) config.splice('off', index, 1);else if (!toggle && index === -1) config.push('off', host);
  555. }));
  556.  
  557. // add hide button
  558. $panel.append($(`<button class="hidePanel" title="${text.titlehidePanel}"> </button>`).click(() => {
  559. config.push('hide', location.hostname);
  560. $panel.remove();
  561. }));
  562.  
  563. // add disabled button
  564. $panel.append($(`<button class="disable" title="${text.titleDisable}"> </button>`).click(() => {
  565. config.push('disabled', location.hostname);
  566. $panel.remove();
  567. }));
  568.  
  569. // add settings button
  570. $panel.append($(`<button class="settings" class="${text.titleSettings}"> </button>`).click(() => {
  571. settings.display();
  572. }));
  573.  
  574. $('body').append($panel);
  575.  
  576. callback();
  577.  
  578. // hide panel after 5 seconds
  579. setTimeout(() => {
  580. $panel.hide();
  581. }, 5000);
  582. };
  583. const getResultMsg = result => {
  584. const msg = {
  585. title: text.errorTitle,
  586. html: text.errorUnexpected,
  587. type: 'error'
  588. };
  589. const errors = {
  590. 14: text.errorInvalidKey,
  591. 15: text.errorUsedKey,
  592. 53: text.errorRateLimited,
  593. 13: text.errorCountryRestricted,
  594. 9: text.errorAlreadyOwned,
  595. 24: text.errorMissingBaseGame,
  596. 36: text.errorPS3Required,
  597. 50: text.errorGiftWallet
  598. };
  599.  
  600. if (result.success === 1) {
  601. msg.title = text.successTitle;
  602. msg.html = '';
  603. msg.type = 'success';
  604. } else if (result.success === 2) {
  605. const errorCode = result.purchase_result_details;
  606. if (has.call(errors, errorCode)) msg.html = errors[errorCode];
  607. }
  608.  
  609. const details = [];
  610. result.purchase_receipt_info.line_items.forEach(item => {
  611. const detail = [`<b>${item.line_item_description}</b>`];
  612. if (item.packageid > 0) detail.push(`sub: <a target="_blank" href="https://steamdb.info/sub/${item.packageid}/">${item.packageid}</a>`);
  613. if (item.appid > 0) detail.push(`app: <a target="_blank" href="https://steamdb.info/sub/${item.appid}/">${item.appid}</a>`);
  614.  
  615. details.push(detail.join(', '));
  616. });
  617.  
  618. if (details.length > 0) msg.html += `<br>${details.join('<br>')}`;
  619.  
  620. return msg;
  621. };
  622. const activateKey = key => {
  623. if ($('.SKH_panel input:checked').length > 0 && !activating) {
  624. activating = true;
  625.  
  626. swal(text.processingTitle, text.processingMsg);
  627. swal.showLoading();
  628.  
  629. const timer = config.get('autoClosePopup') ? config.get('autoClosePopupTimer') * 1000 : null;
  630. if (activated.includes(key)) {
  631. swal.close();
  632. if (timer !== 0) {
  633. swal({
  634. title: text.errorTitle,
  635. text: text.errorActivated,
  636. type: 'error',
  637. timer
  638. }).catch(swal.noop);
  639. }
  640.  
  641. activating = false;
  642. } else {
  643. GM_xmlhttpRequest({
  644. method: 'POST',
  645. url: 'https://store.steampowered.com/account/ajaxregisterkey/',
  646. headers: {
  647. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  648. Origin: 'https://store.steampowered.com',
  649. Referer: 'https://store.steampowered.com/account/registerkey'
  650. },
  651. data: `product_key=${key}&sessionid=${config.get('sessionID')}`,
  652. onload: res => {
  653. swal.close();
  654. if (res.status === 200) {
  655. try {
  656. const result = JSON.parse(res.response);
  657. const msg = getResultMsg(result);
  658.  
  659. if (timer !== 0) {
  660. swal({
  661. title: msg.title,
  662. html: msg.html,
  663. type: msg.type,
  664. timer
  665. }).catch(swal.noop);
  666. }
  667.  
  668. // update activated
  669. if (!activated.includes(key)) {
  670. const failCode = result.purchase_result_details;
  671. if (result.success === 1 || [14, 15, 9].includes(failCode)) {
  672. activated.push(key);
  673. GM_setValue('SKH_activated', JSON.stringify(activated));
  674. $(`span:contains(${key}), [value=${key}]`).addClass('SKH_activated');
  675. }
  676. }
  677.  
  678. // insert activated in this session
  679. if (result.success === 1) currentActivated.push(key);
  680. } catch (e) {
  681. swal(text.errorTitle, text.errorFailedRequest, 'error');
  682. }
  683. } else {
  684. const errorMsg = [];
  685.  
  686. errorMsg.push('<pre class="SKH_errorMsg">');
  687. errorMsg.push(`sessionID: ${config.get('sessionID') + eol}`);
  688. errorMsg.push(`autoUpdate: ${config.get('autoUpdateSessionID') + eol}`);
  689. errorMsg.push(`status: ${res.status + eol}`);
  690. errorMsg.push(`response: ${res.response + eol}`);
  691. errorMsg.push('</pre>');
  692.  
  693. swal({
  694. title: text.errorTitle,
  695. html: text.errorFailedRequestNeedUpdate + eol + errorMsg.join(''),
  696. type: 'error'
  697. });
  698. getSessionID();
  699. }
  700.  
  701. activating = false;
  702. }
  703. });
  704. }
  705. }
  706. };
  707. const generateLink = txt => {
  708. const link = $(`<span class="SKH_link">${txt}</span>`).click(() => {
  709. activateKey(txt);
  710. });
  711. if (activated.includes(txt)) link.addClass('SKH_activated');
  712.  
  713. return link[0];
  714. };
  715. const scanText = txt => {
  716. let matched = true;
  717. const matches = [];
  718.  
  719. while (matched) {
  720. matched = regKey.exec(txt.data);
  721. if (matched) matches.push(matched);
  722. }
  723.  
  724. matches.reverse().forEach(match => {
  725. txt.splitText(match.index);
  726. txt.nextSibling.splitText(match[0].length);
  727. txt.parentNode.replaceChild(generateLink(match[0]), txt.nextSibling);
  728. });
  729. };
  730. const scanElement = element => {
  731. if (!excludedTag.includes(element.tagName) && !element.classList.contains('SKH_link')) {
  732. Array.from(element.childNodes).reverse().forEach(child => {
  733. if (child.nodeType === 1) {
  734. // element node
  735. if (child.tagName === 'INPUT') {
  736. if (child.type === 'text' && regKey.test(child.value)) {
  737. const $child = $(child);
  738.  
  739. if (activated.includes(child.value)) $child.addClass('SKH_activated');
  740. $child.prop('disabled', false);
  741. $child.click(() => {
  742. activateKey(child.value);
  743. });
  744. }
  745. } else if (!excludedTag.includes(child.tagName)) scanElement(child);
  746. } else if (child.nodeType === 3) scanText(child); // text node
  747. });
  748. }
  749. };
  750. const init = () => {
  751. // save sessionID
  752. if (location.hostname === 'store.steampowered.com') {
  753. if (g_AccountID > 0) {
  754. const currentID = config.get('sessionID');
  755. const sessionID = g_sessionID || '';
  756. const language = g_oSuggestParams.l || 'english';
  757.  
  758. if (!config.get('language')) config.set('language', language);
  759. if (sessionID.length > 0) {
  760. const update = config.get('autoUpdateSessionID') && currentID !== sessionID;
  761.  
  762. if (!currentID || update) {
  763. config.set('sessionID', sessionID, () => {
  764. swal({
  765. title: text.updateSuccessTitle,
  766. text: text.updateSuccess,
  767. type: 'success',
  768. timer: 3000
  769. });
  770. });
  771. }
  772. }
  773. }
  774. /* else {
  775. swal(text.notLoggedInTitle, text.notLoggedInMsg, 'error');
  776. }
  777. */
  778. } else {
  779. // check sessionID
  780. if (!config.get('sessionID')) getSessionID();
  781.  
  782. const host = location.hostname;
  783. let run = true;
  784.  
  785. if (!config.get('enabledForever') && !config.get('enabled').includes(host)) run = false;
  786. if (config.get('disabled').includes(host)) run = false;
  787.  
  788. if (run) {
  789. // hide floating panel
  790. if (config.get('hideForever') || config.get('hide').includes(host)) {
  791. GM_addStyle('.SKH_panel { display: none;}');
  792. }
  793. // insert floating panel
  794. insertPanel(() => {
  795. // toggle on / off
  796. $('.SKH_panel input').prop('checked', !config.get('off').includes(host));
  797. });
  798.  
  799. // start scanning
  800. scanElement(document.body);
  801.  
  802. // auto reply activated keys on SteamCN
  803. if (config.get('autoReplyActivated')) {
  804. $(window).on('unload', () => {
  805. if (currentActivated.length > 0) {
  806. $('#vmessage').val(text.activatedReply.replace('%KEYS%', currentActivated.join()));
  807. $('#vreplysubmit').click();
  808. }
  809. });
  810. }
  811.  
  812. // monitor
  813. new MutationObserver(mutations => {
  814. mutations.forEach(mutation => {
  815. Array.from(mutation.addedNodes).forEach(addedNode => {
  816. if (addedNode.nodeType === 1) scanElement(addedNode);else if (addedNode.nodeType === 3) scanText(addedNode);
  817. });
  818. });
  819. }).observe(document.body, {
  820. childList: true,
  821. subtree: true
  822. });
  823. }
  824. }
  825. };
  826.  
  827. $(window).on('load', init);