Steam Key Helper

try to take over the world!

当前为 2017-09-06 提交的版本,查看 最新版本

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