// ==UserScript==
// @name Redeem itch.io
// @namespace Redeem-itch.io
// @version 1.3.5
// @description 自动领取itch.io key链接和免费itch.io游戏
// @author HCLonely
// @iconURL https://itch.io/favicon.ico
// @include *://*itch.io/*
// @include *://keylol.com/*
// @include *://www.steamgifts.com/discussion/*
// @include *://www.reddit.com/r/*
// @include *://new.isthereanydeal.com/deals/*
// @supportURL https://blog.hclonely.com/posts/578f9be7/
// @homepage https://blog.hclonely.com/posts/578f9be7/
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js
// @require https://cdn.jsdelivr.net/npm/sweetalert2@9
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/polyfill.min.js
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @grant unsafeWindow
// @run-at document-end
// @connect itch.io
// @connect *.itch.io
// ==/UserScript==
/* global checkItchGame,MutationObserver */
/* eslint-disable camelcase */
(function () {
'use strict'
const closeWindow = true // 领取完成后自动关闭页面,改为'false'则为不自动关闭
const url = window.location.href
/** *************************自动领取itch.io游戏链接***************************/
if (/^https?:\/\/[\w\W]{1,}\.itch\.io\/[\w]{1,}(-[\w]{1,}){0,}\/download\/[\w\W]{0,}/i.test(url)) {
$('button.button').map(function (i, e) {
if (/link|claim|链接/gim.test($(e).text())) e.click()
return e
})
if ((/This page is linked|此页面已链接到帐户/gim.test($('div.inner_column').text()) || $('a.button.download_btn[data-upload_id]').length > 0) && closeWindow === 1) closePage()
}
/** *********************领取免费itch.io游戏***************************/
if (/^https?:\/\/.*?itch\.io\/.*?\/purchase(\?.*?)?$/.test(url) && /No thanks, just take me to the downloads|不用了,请带我去下载页面/i.test($('a.direct_download_btn').text())) {
$('a.direct_download_btn')[0].click()
} else if ($('.purchase_banner_inner').length === 0 && (/0\.00/gim.test($('.button_message').eq(0).find('.dollars[itemprop]').text()) || /0\.00/gim.test($('.money_input').attr('placeholder')) || /自己出价|Name your own price/gim.test($('.button_message').eq(0).find('.buy_message').text()))) {
$('.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>`)
}
/** **********************限时免费游戏包*****************************/
if (/https?:\/\/itch.io\/s\/[\d]{1,}\/[\w\W]{1,}/.test(url)) {
$('.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>')
$('#redeem-itch-io').click(async () => {
const gameLink = $('.thumb_link.game_link')
for (const e of gameLink) {
await redeemGame(e)
}
})
}
/** **********************后台领取游戏*****************************/
if (['keylol.com', 'www.steamgifts.com', 'www.reddit.com', 'new.isthereanydeal.com'].includes(window.location.hostname)) {
addRedeemBtn()
const observer = new MutationObserver(addRedeemBtn)
observer.observe(document.documentElement, {
attributes: true,
characterData: true,
childList: true,
subtree: true
})
}
function addRedeemBtn () {
for (const e of $('a[href*="itch.io"]:not(".redeem-itch-game")')) {
$(e).addClass('redeem-itch-game').after(`<a data-itch-href="${$(e).attr('href')}" href="javascript:void(0)" onclick="redeemItchGame(this)" target="_self" style="margin-left:10px !important;">领取</a>`)
}
}
GM_registerMenuCommand('提取所有链接', async () => {
log('正在提取链接,请稍候...')
let gamesLink = []
for (const e of $('a[href*="itch.io"]:not(".itch-io-game-link-owned"):not([href*="itch.io/b/"]):not([href*="itch.io/c/"])')) {
const links = await getUrlLink(e)
gamesLink = [...gamesLink, ...links]
}
gamesLink = [...new Set(gamesLink)]
for (const e of gamesLink) {
await isOwn(e)
}
log('全部领取完成!', 'success')
})
unsafeWindow.redeemItchGame = redeemGame
function closePage () {
window.close()
}
function log (e, c) {
if (typeof e !== 'string') return console.log(e)
Swal[$('.swal2-container').length > 0 ? 'update' : 'fire']({
title: e,
icon: c || 'info'
})
let color = 'color:'
switch (c) {
case 'success':
color += 'green'
break
case 'warning':
color += 'blue'
break
case 'info':
color += 'yellow'
break
case 'error':
color += 'red'
break
default:
color += 'black'
}
console.log('%c' + e, color)
}
async function getUrlLink (e) {
let url = ''
if ($(e).attr('data-itch-href')) {
url = $(e).attr('data-itch-href')
} else {
if ($(e).hasClass('itch-io-game-link-owned')) return []
url = $(e).attr('href')
}
log('正在处理游戏/优惠包链接: \n' + url)
if (/https?:\/\/itch.io\/s\/[\d]+\/.+/.test(url)) {
log('正在获取优惠包信息...\n' + url)
const data = await httpRequest({
url,
method: 'get'
})
if (data.status === 200) {
if (data.responseText.includes('not_active_notification')) {
log('活动已结束!', 'error')
return []
} else {
const gamesLink = []
const games = $(data.responseText).find('.game_grid_widget.promo_game_grid a.thumb_link.game_link')
for (const e of games) {
gamesLink.push(e.href.replace(/\/$/, ''))
}
return gamesLink
}
} else {
log('请求失败!', 'error')
log(data)
return []
}
} else if (/^https?:\/\/.+?\.itch\.io\/[^/]+?(\/purchase)?$/.test(url)) {
return [url.replace('/purchase', '').replace(/\/$/, '')]
} else {
return []
}
}
async function redeemGame (e) {
let url = ''
if ($(e).attr('data-itch-href')) {
url = $(e).attr('data-itch-href')
} else {
if ($(e).hasClass('itch-io-game-link-owned')) return
url = $(e).attr('href')
}
log('当前游戏/优惠包链接: \n' + url)
if (/https?:\/\/itch.io\/s\/[\d]+\/.+/.test(url)) {
log('正在获取优惠包信息...\n' + url)
const data = await httpRequest({
url,
method: 'get'
})
if (data.status === 200) {
if (data.responseText.includes('not_active_notification')) {
log('活动已结束!', 'error')
} else {
const games = $(data.responseText).find('.game_grid_widget.promo_game_grid a.thumb_link.game_link')
for (const e of games) {
await isOwn(e.href)
}
}
} else {
log('请求失败!', 'error')
log(data)
}
} else if (/^https?:\/\/.+?\.itch\.io\/[^/]+?(\/purchase)?$/.test(url)) {
await isOwn(url.replace('/purchase', ''))
}
}
async function isOwn (url) {
log('当前游戏链接: \n' + url)
log('正在检测游戏是否拥有...\n' + url)
const data = await httpRequest({
url,
method: 'get'
})
if (data.status === 200) {
if (data.responseText.includes('purchase_banner_inner')) {
log('游戏已拥有!', 'success')
} else {
await purchase(url)
}
} else {
log('请求失败!', 'error')
log(data)
}
}
async function purchase (url) {
log('正在加载购买页面...\n' + url)
const data = await httpRequest({
url: url + '/purchase',
method: 'get'
})
if (data.status === 200) {
const html = $(data.responseText)
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())) {
const csrf_token = html.find('[name="csrf_token"]').val()
const reward_id = html.find('[name="reward_id"]').val()
await download(url, csrf_token, reward_id)
} else {
log('价格不为 0, 可能活动已结束!', 'error')
}
} else {
log('请求失败!', 'error')
log(data)
}
}
async function download (url, csrf_token, reward_id) {
log('正在请求下载页面...\n' + url)
const data = await httpRequest({
url: url + '/download_url',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: `csrf_token=${encodeURIComponent(csrf_token)}${reward_id ? ('&reward_id=' + reward_id) : ''}`,
responseType: 'json'
})
if (data.status === 200 && data.response && data.response.url) {
await loadDownload(data.response.url, url)
} else {
log('请求失败!', 'error')
log(data)
}
}
async function loadDownload (e, referer) {
log('正在加载下载页面...')
const url = new URL(e)
const data = await httpRequest({
url: url.href,
method: 'get',
headers: {
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',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
DNT: 1,
Host: url.hostname,
Referer: referer,
'sec-ch-ua': '"\\\\Not;A\\"Brand";v="99", "Google Chrome";v="85", "Chromium";v="85"',
'sec-ch-ua-mobile': '?0',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': 1,
'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'
}
})
if (data.status === 200 && data.responseText) {
const html = $(data.responseText)
const claimBtn = html.find('button.button:contains("Link"),button.button:contains("Claim"),button.button:contains("链接")')
const form = html.find('form[action*="claim-key"]')
if (/This page is linked|此页面已链接到帐户/gim.test(html.find('div.inner_column').text()) || html.find('a.button.download_btn[data-upload_id]').length > 0) {
log('领取成功!', 'success')
} else if (form.length > 0) {
const url = form.attr('action')
const csrf_token = form.find('input[name="csrf_token"]').val()
await claimame(url, csrf_token, url.href)
} else if (claimBtn.length > 0 && claimBtn.parents('form').length > 0) {
const form = claimBtn.parents('form')
const url = form.attr('action')
const csrf_token = form.find('input[name="csrf_token"]').val()
await claimame(url, csrf_token, url.href)
} else {
log('领取完成,结果未知!', 'success')
}
} else {
log('请求失败!', 'error')
log(data)
}
if (typeof checkItchGame === 'function') checkItchGame()
}
async function claimame (e, token, referer) {
log('正在领取游戏...')
const url = new URL(e)
const data = await httpRequest({
url: url.href,
method: 'post',
headers: {
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',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control': 'max-age=0',
'Content-Type': 'application/x-www-form-urlencoded',
DNT: 1,
Host: url.hostname,
Origin: url.origin,
Referer: referer,
'sec-ch-ua': '"\\\\Not;A\\"Brand";v="99", "Google Chrome";v="85", "Chromium";v="85"',
'sec-ch-ua-mobile': '?0',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': 1,
'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'
},
data: `csrf_token=${encodeURIComponent(token)}`
})
if (data.status === 200 && data.responseText) {
const html = $(data.responseText)
if (/This page is linked|此页面已链接到帐户/gim.test(html.find('div.inner_column').text()) || html.find('a.button.download_btn[data-upload_id]').length > 0) {
log('领取成功!', 'success')
} else {
log('领取完成,结果未知!', 'success')
}
} else {
log('请求失败!', 'error')
log(data)
}
}
function httpRequest (option, i = 0) {
return new Promise((resolve, reject) => {
option.onload = data => {
resolve(data)
}
option.onerror = option.ontimeout = option.onabort = () => {
reject() // eslint-disable-line
}
option.timeout = 30000
GM_xmlhttpRequest(option)
}).then(data => {
return data
}).catch(() => {
if (i > 1) {
return {}
} else {
return httpRequest(option, ++i)
}
})
}
})()