Steam Key Helper

try to take over the world!

当前为 2018-01-19 提交的版本,查看 最新版本

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