Redeem itch.io

自动领取itch.io key链接和免费itch.io游戏

  1. /* eslint-disable max-len,no-plusplus,no-param-reassign,new-cap,func-style */
  2. // ==UserScript==
  3. // @name Redeem itch.io
  4. // @namespace Redeem-itch.io
  5. // @version 1.3.10
  6. // @description 自动领取itch.io key链接和免费itch.io游戏
  7. // @author HCLonely
  8. // @iconURL https://itch.io/favicon.ico
  9. // @include *://*itch.io/*
  10. // @include *://keylol.com/*
  11. // @include *://www.steamgifts.com/discussion/*
  12. // @include *://www.reddit.com/r/*
  13. // @include *://new.isthereanydeal.com/deals/*
  14. // @include *://freegames.codes/game/*
  15. // @include *://itchclaim.tmbpeter.com/*
  16. // @include *://shaigrorb.github.io/freetchio/*
  17. // @supportURL https://blog.hclonely.com/posts/578f9be7/
  18. // @homepage https://blog.hclonely.com/posts/578f9be7/
  19. // @require https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.slim.min.js
  20. // @require https://cdn.jsdelivr.net/npm/sweetalert2@9
  21. // @require https://cdn.jsdelivr.net/npm/promise-polyfill@8.1.3/dist/polyfill.min.js
  22. // @grant GM_xmlhttpRequest
  23. // @grant GM_registerMenuCommand
  24. // @grant GM_openInTab
  25. // @grant GM_addStyle
  26. // @grant unsafeWindow
  27. // @run-at document-end
  28. // @connect itch.io
  29. // @connect *.itch.io
  30. // ==/UserScript==
  31.  
  32. /* global checkItchGame,MutationObserver */
  33. /* eslint-disable camelcase */
  34.  
  35. (function () {
  36. 'use strict';
  37.  
  38. const closeWindow = true; // 领取完成后自动关闭页面,改为'false'则为不自动关闭
  39. const url = window.location.href;
  40.  
  41. /** *************************自动领取itch.io游戏链接***************************/
  42. if (/^https?:\/\/[\w\W]{1,}\.itch\.io\/[\w]{1,}(-[\w]{1,}){0,}\/download\/[\w\W]{0,}/i.test(url)) {
  43. $('button.button').map((i, e) => {
  44. if (/link|claim|链接/gim.test($(e).text())) e.click();
  45. return e;
  46. });
  47. if ((/This page is linked|此页面已链接到帐户/gim.test($('div.inner_column').text()) || $('a.button.download_btn[data-upload_id]').length > 0) && closeWindow === 1) closePage();
  48. }
  49.  
  50. /** *********************领取免费itch.io游戏***************************/
  51. if (/^https?:\/\/.*?itch\.io\/.*?\/purchase(\?.*?)?$/.test(url) && /No thanks, just take me to the downloads|不用了,请带我去下载页面/i.test($('a.direct_download_btn').text())) {
  52. $('a.direct_download_btn')[0].click();
  53. } else if (
  54. $('.purchase_banner_inner').length === 0 &&
  55. (
  56. /0\.00/gim.test($('.button_message').eq(0)
  57. .find('.dollars[itemprop]')
  58. .text()) ||
  59. /0\.00/gim.test($('.money_input').attr('placeholder')) ||
  60. /自己出价|Name your own price/gim.test($('.button_message').eq(0)
  61. .find('.buy_message')
  62. .text())
  63. )
  64. ) {
  65. $('.buy_btn').after(`<a data-itch-href="${$('.buy_btn').attr('href')}" href="javascript:void(0)" onclick="redeemItchGame(this)" target="_self" class="button" one-link-mark="yes" title="仅支持免费游戏">后台领取</a>`);
  66. }
  67.  
  68. /** **********************限时免费游戏包*****************************/
  69. if (/https?:\/\/itch.io\/s\/[\d]{1,}\/[\w\W]{1,}/.test(url)) {
  70. if ($('.promotion_buy_row .buy_game_btn').length > 0) {
  71. $('.promotion_buy_row .buy_game_btn').after('<button id="redeem-itch-io" class="button" style="font-size:18px;letter-spacing:0.025em;line-height:36px;height:40px;padding:0 20px;margin:0 16px">后台领取</button>');
  72. } else {
  73. $('.countdown_row').prepend(`<div style="width: 100%"><button id="redeem-itch-io" class="button" style="font-size:18px;letter-spacing:0.025em;line-height:36px;padding:0 20px;margin: 10px 30%;width: 40%;">后台领取</button></div>`);
  74. }
  75.  
  76. $('#redeem-itch-io').click(async () => {
  77. const gameLink = $('.thumb_link.game_link');
  78. for (const e of gameLink) {
  79. await redeemGame(e);
  80. }
  81. });
  82. }
  83.  
  84. /** **********************后台领取游戏*****************************/
  85. if (['keylol.com', 'www.steamgifts.com', 'www.reddit.com', 'new.isthereanydeal.com', 'freegames.codes', 'itchclaim.tmbpeter.com', 'shaigrorb.github.io'].includes(window.location.hostname)) {
  86. addRedeemBtn();
  87. const observer = new MutationObserver(addRedeemBtn);
  88. observer.observe(document.documentElement, {
  89. attributes: true,
  90. characterData: true,
  91. childList: true,
  92. subtree: true
  93. });
  94. }
  95. function addRedeemBtn() {
  96. for (const e of $('a[href*="itch.io"]:not(".redeem-itch-game")')) {
  97. const positionEle = window.location.hostname === 'shaigrorb.github.io' ? $(e).addClass('redeem-itch-game').parents('.item-card') : $(e).addClass('redeem-itch-game');
  98. positionEle.after(`<a ${
  99. window.location.hostname === 'freegames.codes' ? 'class="details__buy" ' : ''
  100. }
  101. data-itch-href="${$(e).attr('href')}"
  102. href="javascript:void(0);"
  103. onclick="redeemItchGame('${$(e).attr('href')}')"
  104. target="_self"
  105. style="${window.location.hostname === 'shaigrorb.github.io' ? 'position:relative;height:min-content;right:39px;background-color:#16a34a;top:4px;text-decoration-line:none;color:white;font-weight:bold;border-radius:2px;padding:5px;font-size:13px; ' : `margin-${window.location.hostname === 'freegames.codes' ? 'top' : 'left'}:10px !important;`}">领取</a>`);
  106. }
  107. }
  108. GM_registerMenuCommand('提取所有链接', async () => {
  109. log('正在提取链接,请稍候...');
  110. let gamesLink = [];
  111. for (const e of $('a[href*="itch.io"]:not(".itch-io-game-link-owned"):not([href*="itch.io/b/"]):not([href*="itch.io/c/"])')) {
  112. const links = await getUrlLink(e);
  113. gamesLink = [...gamesLink, ...links];
  114. }
  115. gamesLink = [...new Set(gamesLink)];
  116. for (const e of gamesLink) {
  117. await isOwn(e);
  118. }
  119. log('全部领取完成!', 'success');
  120. });
  121. unsafeWindow.redeemItchGame = redeemGame;
  122. function closePage() {
  123. window.close();
  124. }
  125. function log(e, c) {
  126. if (typeof e !== 'string') return console.log(e);
  127. Swal[$('.swal2-container').length > 0 ? 'update' : 'fire']({
  128. title: e,
  129. icon: c || 'info',
  130. customClass: {
  131. title: 'break-all'
  132. }
  133. });
  134. let color = 'color:';
  135. switch (c) {
  136. case 'success':
  137. color += 'green';
  138. break;
  139. case 'warning':
  140. color += 'blue';
  141. break;
  142. case 'info':
  143. color += 'yellow';
  144. break;
  145. case 'error':
  146. color += 'red';
  147. break;
  148. default:
  149. color += 'black';
  150. }
  151. console.log(`%c${e}`, color);
  152. }
  153.  
  154. async function getUrlLink(e) {
  155. let url = '';
  156. if ($(e).attr('data-itch-href')) {
  157. url = $(e).attr('data-itch-href');
  158. } else {
  159. if ($(e).hasClass('itch-io-game-link-owned')) return [];
  160. url = $(e).attr('href');
  161. }
  162. log(`正在处理游戏/优惠包链接: <br/>${url}`);
  163. if (/https?:\/\/itch.io\/s\/[\d]+\/.+/.test(url)) {
  164. log(`正在获取优惠包信息...<br/>${url}`);
  165. const data = await httpRequest({
  166. url,
  167. method: 'get'
  168. });
  169.  
  170. if (data.status === 200) {
  171. if (data.responseText.includes('not_active_notification')) {
  172. log('活动已结束!', 'error');
  173. return [];
  174. }
  175. const gamesLink = [];
  176. const games = $(data.responseText).find('.game_grid_widget.promo_game_grid a.thumb_link.game_link');
  177. for (const e of games) {
  178. gamesLink.push(e.href.replace(/\/$/, ''));
  179. }
  180. return gamesLink;
  181. }
  182. log('请求失败!', 'error');
  183. log(data);
  184. return [];
  185. } else if (/^https?:\/\/.+?\.itch\.io\/[^/]+?(\/purchase)?$/.test(url)) {
  186. return [url.replace('/purchase', '').replace(/\/$/, '')];
  187. }
  188. return [];
  189. }
  190. async function redeemGame(e) {
  191. let url = '';
  192. if (typeof e === 'string') {
  193. url = e;
  194. } else if ($(e).attr('data-itch-href')) {
  195. url = $(e).attr('data-itch-href');
  196. } else {
  197. if ($(e).hasClass('itch-io-game-link-owned')) return;
  198. url = $(e).attr('href');
  199. }
  200. log(`当前游戏/优惠包链接: <br/>${url}`);
  201. if (/https?:\/\/itch.io\/s\/[\d]+\/.+/.test(url)) {
  202. log(`正在获取优惠包信息...<br/>${url}`);
  203. const data = await httpRequest({
  204. url,
  205. method: 'get'
  206. });
  207. if (data.status === 200) {
  208. if (data.responseText.includes('not_active_notification')) {
  209. log('活动已结束!', 'error');
  210. } else {
  211. const games = $(data.responseText).find('.game_grid_widget.promo_game_grid a.thumb_link.game_link');
  212. for (const e of games) {
  213. await isOwn(e.href);
  214. }
  215. }
  216. } else {
  217. log('请求失败!', 'error');
  218. log(data);
  219. }
  220. } else if (/^https?:\/\/.+?\.itch\.io\/[^/]+?(\/purchase)?$/.test(url)) {
  221. await isOwn(url.replace('/purchase', ''));
  222. }
  223. }
  224. async function isOwn(url) {
  225. log(`当前游戏链接: <br/>${url}`);
  226. log(`正在检测游戏是否拥有...<br/>${url}`);
  227. const data = await httpRequest({
  228. url,
  229. method: 'get'
  230. });
  231. if (data.status === 200) {
  232. if (data.responseText.includes('purchase_banner_inner')) {
  233. log('游戏已拥有!', 'success');
  234. } else {
  235. await purchase(url);
  236. }
  237. } else {
  238. log('请求失败!', 'error');
  239. log(data);
  240. }
  241. }
  242. async function purchase(url) {
  243. log(`正在加载购买页面...<br/>${url}`);
  244. const data = await httpRequest({
  245. url: `${url}/purchase`,
  246. method: 'get'
  247. });
  248. if (data.status === 200) {
  249. const html = $(data.responseText);
  250. if (/0\.00/gim.test(html.find('.button_message:first .dollars[itemprop]').text()) || /0\.00/gim.test(html.find('.money_input').attr('placeholder')) || /自己出价|Name your own price/gim.test(html.find('.button_message:first .buy_message').text())) {
  251. const csrf_token = html.find('[name="csrf_token"]').val();
  252. const reward_id = html.find('[name="reward_id"]').val();
  253. await download(url, csrf_token, reward_id);
  254. } else {
  255. log('价格不为 0, 可能活动已结束!', 'error');
  256. }
  257. } else {
  258. log('请求失败!', 'error');
  259. log(data);
  260. }
  261. }
  262. async function download(url, csrf_token, reward_id) {
  263. log(`正在请求下载页面...<br/>${url}`);
  264. const data = await httpRequest({
  265. url: `${url}/download_url`,
  266. method: 'post',
  267. headers: {
  268. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
  269. },
  270. data: `csrf_token=${encodeURIComponent(csrf_token)}${reward_id ? (`&reward_id=${reward_id}`) : ''}`,
  271. responseType: 'json'
  272. });
  273. if (data.status === 200 && data.response && data.response.url) {
  274. await loadDownload(data.response.url, url);
  275. } else {
  276. log('请求失败!', 'error');
  277. log(data);
  278. }
  279. }
  280.  
  281. async function loadDownload(e, referer) {
  282. log('正在加载下载页面...');
  283. const url = new URL(e);
  284. const data = await httpRequest({
  285. url: url.href,
  286. method: 'get',
  287. headers: {
  288. Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
  289. 'Accept-Encoding': 'gzip, deflate, br',
  290. 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  291. DNT: 1,
  292. Host: url.hostname,
  293. Referer: referer,
  294. 'sec-ch-ua': '"\\\\Not;A\\"Brand";v="99", "Google Chrome";v="85", "Chromium";v="85"',
  295. 'sec-ch-ua-mobile': '?0',
  296. 'Sec-Fetch-Dest': 'document',
  297. 'Sec-Fetch-Mode': 'navigate',
  298. 'Sec-Fetch-Site': 'same-origin',
  299. 'Sec-Fetch-User': '?1',
  300. 'Upgrade-Insecure-Requests': 1,
  301. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4164.2 Safari/537.36'
  302. }
  303. });
  304. if (data.status === 200 && data.responseText) {
  305. const html = $(data.responseText);
  306. const claimBtn = html.find('button.button:contains("Link"),button.button:contains("Claim"),button.button:contains("链接")');
  307. const form = html.find('form[action*="claim-key"]');
  308. if (/This page is linked|此页面已链接到帐户/gim.test(html.find('div.inner_column').text()) || html.find('a.button.download_btn[data-upload_id]').length > 0) {
  309. log('领取成功!', 'success');
  310. } else if (form.length > 0) {
  311. const url = form.attr('action');
  312. const csrf_token = form.find('input[name="csrf_token"]').val();
  313. await claimame(url, csrf_token, url.href);
  314. } else if (claimBtn.length > 0 && claimBtn.parents('form').length > 0) {
  315. const form = claimBtn.parents('form');
  316. const url = form.attr('action');
  317. const csrf_token = form.find('input[name="csrf_token"]').val();
  318. await claimame(url, csrf_token, url.href);
  319. } else {
  320. log('领取完成,结果未知!', 'success');
  321. }
  322. } else {
  323. log('请求失败!', 'error');
  324. log(data);
  325. }
  326. if (typeof checkItchGame === 'function') checkItchGame();
  327. }
  328. async function claimame(e, token, referer) {
  329. log('正在领取游戏...');
  330. const url = new URL(e);
  331. const data = await httpRequest({
  332. url: url.href,
  333. method: 'post',
  334. headers: {
  335. Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
  336. 'Accept-Encoding': 'gzip, deflate, br',
  337. 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  338. 'Cache-Control': 'max-age=0',
  339. 'Content-Type': 'application/x-www-form-urlencoded',
  340. DNT: 1,
  341. Host: url.hostname,
  342. Origin: url.origin,
  343. Referer: referer,
  344. 'sec-ch-ua': '"\\\\Not;A\\"Brand";v="99", "Google Chrome";v="85", "Chromium";v="85"',
  345. 'sec-ch-ua-mobile': '?0',
  346. 'Sec-Fetch-Dest': 'document',
  347. 'Sec-Fetch-Mode': 'navigate',
  348. 'Sec-Fetch-Site': 'same-origin',
  349. 'Sec-Fetch-User': '?1',
  350. 'Upgrade-Insecure-Requests': 1,
  351. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4164.2 Safari/537.36'
  352. },
  353. data: `csrf_token=${encodeURIComponent(token)}`
  354. });
  355. if (data.status === 200 && data.responseText) {
  356. const html = $(data.responseText);
  357. if (/This page is linked|此页面已链接到帐户/gim.test(html.find('div.inner_column').text()) || html.find('a.button.download_btn[data-upload_id]').length > 0) {
  358. log('领取成功!', 'success');
  359. } else {
  360. log('领取完成,结果未知!', 'success');
  361. }
  362. } else if (data.finalUrl.includes('/register')) {
  363. log('请先登录!', 'error');
  364. log(data);
  365. } else {
  366. log('请求失败!', 'error');
  367. log(data);
  368. }
  369. }
  370. function httpRequest(option, i = 0) {
  371. return new Promise((resolve, reject) => {
  372. option.onload = (data) => {
  373. resolve(data);
  374. };
  375. option.onerror = reject;
  376. option.ontimeout = reject;
  377. option.onabort = reject;
  378. option.timeout = 30000;
  379. GM_xmlhttpRequest(option);
  380. }).then((data) => data)
  381. .catch(() => {
  382. if (i > 1) {
  383. return {};
  384. }
  385. return httpRequest(option, ++i);
  386. });
  387. }
  388. GM_addStyle('.swal2-title.break-all{word-wrap:break-word; word-break:break-all;}');
  389. }());