// ==UserScript==
// @name Humble Choice Get Key
// @namespace http://tampermonkey.net/
// @version 0.04
// @description try to take over the world!
// @author ku mi
// @match https://www.humblebundle.com/subscription/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// ==/UserScript==
const countryMap = {
AD: '安道尔',
AE: '阿拉伯联合酋长国',
AF: '阿富汗',
AG: '安提瓜和巴布达',
AI: '安圭拉',
AL: '阿尔巴尼亚',
AM: '亚美尼亚',
AO: '安哥拉',
AQ: '南极洲',
AR: '阿根廷',
AS: '美属萨摩亚',
AT: '奥地利',
AU: '澳大利亚',
AW: '阿鲁巴',
AX: '奥兰群岛',
AZ: '阿塞拜疆',
BA: '波斯尼亚和黑塞哥维那',
BB: '巴巴多斯',
BD: '孟加拉',
BE: '比利时',
BF: '布基纳法索',
BG: '保加利亚',
BH: '巴林',
BI: '布隆迪',
BJ: '贝宁',
BL: '圣巴托洛缪岛',
BM: '百慕大',
BN: '文莱',
BO: '玻利维亚',
BQ: '博奈尔',
BR: '巴西',
BS: '巴哈马',
BT: '不丹',
BU: '缅甸',
BV: '布韦岛',
BW: '博兹瓦纳',
BY: '白俄罗斯',
BZ: '伯利兹',
CA: '加拿大',
CC: '科科斯(基林)群岛',
CD: '刚果(金)',
CF: '中非共和国',
CG: '刚果(布)',
CH: '瑞士',
CI: '科特迪瓦',
CK: '库克群岛',
CL: '智利',
CM: '喀麦隆',
CN: '中国',
CO: '哥伦比亚',
CR: '哥斯达黎加',
CS: '塞尔维亚和黑山',
CU: '古巴',
CV: '佛得角',
CW: '库拉索',
CX: '圣诞岛',
CY: '塞浦路斯',
CZ: '捷克',
DE: '德国',
DJ: '吉布提',
DK: '丹麦',
DM: '多米尼克',
DO: '多米尼加',
DZ: '阿尔及利亚',
EC: '厄瓜多尔',
EE: '爱沙尼亚',
EG: '埃及',
EH: '西撒哈拉',
ER: '厄立特里亚',
ES: '西班牙',
ET: '埃塞俄比亚',
FI: '芬兰',
FJ: '斐济',
FK: '福克兰群岛',
FM: '密克罗尼西亚',
FO: '法罗群岛',
FR: '法国',
GA: '加蓬',
GB: '英国',
GD: '格林纳达',
GE: '格鲁吉亚',
GF: '法属圭亚那',
GG: '根西',
GH: '加纳',
GI: '直布罗陀',
GL: '格陵兰',
GM: '冈比亚',
GN: '几内亚',
GP: '瓜德鲁普',
GQ: '赤道几内亚',
GR: '希腊',
GS: '南乔治亚岛和南桑威奇群岛',
GT: '危地马拉',
GU: '关岛',
GW: '几内亚比绍',
GY: '圭亚那',
HK: '香港',
HM: '赫德岛和麦克唐纳群岛',
HN: '洪都拉斯',
HR: '克罗地亚',
HT: '海地',
HU: '匈牙利',
ID: '印尼',
IE: '爱尔兰',
IL: '以色列',
IM: '马恩岛',
IN: '印度',
IO: '英属印度洋领地',
IQ: '伊拉克',
IR: '伊朗',
IS: '冰岛',
IT: '意大利',
JE: '泽西岛',
JM: '牙买加',
JO: '约旦',
JP: '日本',
KE: '肯尼亚',
KG: '吉尔吉斯',
KH: '柬埔寨',
KI: '基里巴斯',
KM: '科摩罗',
KN: '圣基茨和尼维斯',
KP: '朝鲜',
KR: '韩国',
KW: '科威特',
KY: '开曼群岛',
KZ: '哈萨克斯坦',
LA: '老挝',
LB: '黎巴嫩',
LC: '圣卢西亚',
LI: '列支敦士登',
LK: '斯里兰卡',
LR: '利比里亚',
LS: '莱索托',
LT: '立陶宛',
LU: '卢森堡',
LV: '拉脱维亚',
LY: '利比亚',
MA: '摩洛哥',
MC: '摩纳哥',
MD: '摩尔多瓦',
ME: '黑山',
MF: '法属圣马丁',
MG: '马达加斯加',
MH: '马绍尔群岛',
MK: '马其顿',
ML: '马里',
MM: '缅甸',
MN: '蒙古',
MO: '澳门',
MP: '北马里亚纳群岛',
MQ: '马提尼克',
MR: '毛里塔尼亚',
MS: '蒙塞拉特',
MT: '马耳他',
MU: '毛里求斯',
MV: '马尔代夫',
MW: '马拉维',
MX: '墨西哥',
MY: '马来西亚',
MZ: '莫桑比克',
NA: '纳米比亚',
NC: '新喀里多尼亚',
NE: '尼日尔',
NF: '诺福克岛',
NG: '尼日利',
NI: '尼加拉瓜',
NL: '荷兰',
NO: '挪威',
NP: '尼泊尔',
NR: '瑙鲁',
NU: '纽埃',
NZ: '新西兰',
OM: '阿曼',
PA: '巴拿马',
PE: '秘鲁',
PF: '法属波利尼西亚a',
PG: '巴布亚新几内亚',
PH: '菲律宾',
PK: '巴基斯坦',
PL: '波兰',
PM: '圣皮埃尔和密克隆',
PN: '皮特凯恩群岛',
PR: '波多黎各',
PS: '巴勒斯坦',
PT: '葡萄牙',
PW: '帕劳',
PY: '巴拉圭',
QA: '卡塔尔',
RE: '留尼旺島',
RO: '罗马尼亚',
RS: '塞尔维亚',
RU: '俄罗斯',
RW: '卢旺达',
SA: '沙特阿拉伯',
SB: '所罗门群岛',
SC: '塞舌尔',
SD: '苏丹',
SE: '瑞典',
SG: '新加坡',
SH: '圣赫勒拿、阿森松与特斯坦达库尼亚',
SI: '斯洛文尼',
SJ: '斯瓦尔巴群岛和扬马延岛',
SK: '斯洛伐克',
SL: '塞拉利昂',
SM: '圣马力诺',
SN: '塞内加尔',
SO: '索马里',
SR: '苏里南',
SS: '南苏丹',
ST: '圣多美和普林西比',
SV: '萨尔瓦多',
SX: '荷属圣马丁',
SY: '叙利亚',
SZ: '斯威士兰',
TC: '特克斯和凯科斯群岛',
TD: '乍得',
TF: '法属南部领土',
TG: '多哥',
TH: '泰国',
TJ: '塔吉克斯坦',
TK: '托克劳',
TL: '东帝汶',
TM: '土库曼斯坦',
TN: '突尼斯',
TO: '汤加',
TR: '土耳其',
TT: '特立尼达和多巴哥',
TV: '图瓦卢',
TW: '台湾',
TZ: '坦桑尼亚',
UA: '乌克兰',
UG: '乌干达',
UM: '美国本土外小岛屿',
US: '美国',
UY: '乌拉圭',
UZ: '乌兹别克斯坦',
VA: '圣座',
VC: '圣文森特和格林纳丁斯',
VE: '委内瑞拉',
VG: '英属维尔京群岛',
VI: '美属维尔京群岛',
VN: '越南',
VU: '瓦努阿图',
WF: '瓦利斯和富图纳群岛',
WS: '萨摩亚',
XK: '科索沃',
YE: '也门',
YT: '马约特',
ZA: '南非',
ZM: '赞比亚',
ZW: '津巴布韦',
};
(function() {
function http (setData) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: setData.method | 'GET',
url: setData.url,
onerror: reject,
ontimeout: reject,
onload: (res) => {
resolve(res.responseText)
}
})
})
}
http({url: window.location}).then(res => {
getInitData(new DOMParser().parseFromString(res, 'text/html'))
}).catch(()=>{})
function getInitData (el) {
const script = el.querySelector('#webpack-monthly-product-data') || el.querySelector('#webpack-subscriber-hub-data')
if(!script) return
const {contentChoiceData, gamekey, contentChoicesMade, downloadPageUrl} = JSON.parse(script.innerText.trim()).contentChoiceOptions
const {content_choices, display_order} = contentChoiceData.initial
const selecedGame = contentChoicesMade.initial.choices_made
const allGame = display_order.map(item => {
let i = content_choices[item]
return {
machine_name: i.tpkds[0].machine_name,
title: i.title,
exclusive: i.tpkds[0].exclusive_countries,
disallowed: i.tpkds[0].disallowed_countries,
appid: i.tpkds[0].steam_app_id,
name: item,
key: i.tpkds[0].redeemed_key_val
}
})
function getLock (game) {
let lockDetil
function getZhName (arr) {
return arr.map(item => {
if (/(\u53f0\u6e7e|\u4e2d\u56fd|\u9999\u6e2f)/.test(countryMap[item])) return '<span style="color: #c93756; font-size: 20px;">' + countryMap[item] + '</span>'
return countryMap[item]
}).join('、')
}
if(game.exclusive.length) {
lockDetil = `<span style="color: #cc6699"><span style="color: #c93756;">只能在</span>以下激活: + ${getZhName(game.exclusive)}</span>`
}
if(game.disallowed.length){
lockDetil = `<span style="color: #B0E2FF"><span style="color: #c93756;">不能在</span>以下地区激活: ${getZhName(game.disallowed)}<span>: `
}
return lockDetil || `<span style="color: #279b61">无限制激活</span>`
}
const gameBox = document.createElement('div')
gameBox.innerHTML = `<button class="_sh_hd_">隐藏锁区信息</button><a class="_down_page_" target="_blank" href=${downloadPageUrl}>Download页面</a/><ul class="_self_view_"></ul>`
const gamelist = gameBox.querySelector('._self_view_')
const sButton = gameBox.querySelector('._sh_hd_')
const optionBox = document.createElement('div')
optionBox.innerHTML = '<ul class="_option_ul_"></ul><ul class="_select_ul_"></ul><textarea class="_key_value_"></textarea>'
const optionUl = optionBox.querySelector('._option_ul_')
const selectUl = optionBox.querySelector('._select_ul_')
const keyValue = optionBox.querySelector('._key_value_')
const textArr = ['刮开游戏(高亮选中)', '选择游戏(不刮开)', '全选高亮', '取消高亮', '清理文本框', '显示数字']
const [getKey, selectKey, allLight, noLight, clearKey, setNumber] = textArr.map((item, index) => {
item = document.createElement('li')
item.innerText = textArr[index]
optionUl.appendChild(item)
return item
})
const liChild = allGame.map((item, index) => {
const li = document.createElement('li')
li.innerText = index + 1
Object.assign(li.dataset, {
title: item.title,
name: item.name,
machine_name: item.machine_name,
key: item.key || ''
})
selectUl.appendChild(li)
return li
})
setNum()
optionUl.onselectstart = () => false
selectUl.onselectstart = () => false
selectUl.addEventListener('click',(e) => {
if (e.target.nodeName === 'LI') e.target.classList.toggle('current')
})
allLight.addEventListener('click', () => {
liChild.forEach(item => item.classList.add('current'))
})
noLight.addEventListener('click', () => {
liChild.forEach(item => item.classList.remove('current'))
})
clearKey.addEventListener('click',() => (keyValue.value = ''))
selectKey.addEventListener('click', () => {
if(selecedGame.length >= 10) return noLight.click()
keyValue.value = ''
let num = 0
let filter = liChild.filter(item => item.classList.contains('current'))
filter.forEach((item, index) => {
if(selecedGame.includes(item.dataset.name)) return num++
let time = setTimeout(() => {
clearTimeout(time)
fetchHttp({url: `https://www.humblebundle.com/humbler/choosecontent?gamekey=${gamekey}&parent_identifier=initial&chosen_identifier=${item.dataset.name}`})
.then(res => res.json())
.then(res => {
keyValue.value += `${item.dataset.title}: ${res.success ? '选择成功' : '选择失败'}\n`
if(res.success){
noSelectList.forEach((it) => {
if(it.title === item.dataset.name){
it.classList.add('current')
it.innerText = '已选择'
}
})
}
})
}, (index - num) * 1500)
})
noLight.click()
})
getKey.addEventListener('click', () => {
keyValue.value = ''
let num = 0
let filter = liChild.filter(item => item.classList.contains('current'))
filter.forEach((item, index) => {
if(item.dataset.key) {
(keyValue.value += `${item.dataset.title}: ${item.dataset.key}\n`)
return num++
}
let time = setTimeout(() => {
clearTimeout(time)
fetchHttp({url: 'https://www.humblebundle.com/humbler/redeemkey', body: `keytype=${item.dataset.machine_name}&key=${gamekey}&keyindex=0`, method: 'POST'})
.then(res => res.json())
.then(res => {
if(res.success){
keyValue.value += `${item.dataset.title}: ${res.key}\n`
noGetList.forEach((it) => {
if(it.dataset.name === item.dataset.name){
it.classList.add('current')
it.innerText = '已刮开'
}
})
}
})
}, index * 1500)
})
noLight.click()
})
setNumber.addEventListener('click', () => {
if(!document.querySelectorAll('._game_num_').length)setNum()
})
function setNum() {
const els = document.querySelectorAll('.js-content-choices .choice-image-container')
els.forEach((item, index) => {
let div = document.createElement('div')
div.setAttribute('class','_game_num_')
div.innerText = index + 1
item.appendChild(div)
})
}
gameBox.insertBefore(optionBox, sButton)
allGame.forEach(item => {
const li = document.createElement('li')
li.innerHTML = `<div style="width: 100%;"><a style="text-decoration: none; color: #169fe3" href="https://store.steampowered.com/app/${item.appid}" target="_blank">${item.title}</a>${item.key ? '<button class="current">已刮开</button>': '<button data-name="'+ item.name + '" data-machine_name="'+ item.machine_name + '" class="no-get" title="'+ item.name + '">未刮开</button>'}${selecedGame.includes(item.name) ? '<button class="current">已选择</button>' : '<button class="no-select" title="'+ item.name + '">未选择</button>'}<p style="margin: 15px 15px 15px 0; ">${getLock(item)}</p></div>`
gamelist.appendChild(li)
})
const view = document.querySelector('.content-choices-view')
const list = document.querySelector(".content-choice-tiles.js-content-choice-tiles")
view.insertBefore(gameBox, list)
sButton.addEventListener('click', () => {
if(sButton.classList.contains('current')){
gamelist.style.display = 'block'
sButton.innerHTML === '隐藏锁区信息'
}else {
gamelist.style.display = 'none'
sButton.innerHTML === '显示锁区信息'
}
sButton.classList.toggle('current')
} )
const noGetList = document.querySelectorAll('.no-get')
const noSelectList = document.querySelectorAll('.no-select')
noSelectList.forEach(item => {
item.addEventListener('click', clickEvent(true, {url: `https://www.humblebundle.com/humbler/choosecontent?gamekey=${gamekey}&parent_identifier=initial&chosen_identifier=${item.title}`}))
})
noGetList.forEach(item => {
item.addEventListener('click', clickEvent(false, {url: 'https://www.humblebundle.com/humbler/redeemkey', body: `keytype=${item.dataset.machine_name}&key=${gamekey}&keyindex=0`, method: 'POST'}))
})
function fetchHttp(data) {
return fetch(data.url, {
method: data.method || 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: 'https://www.humblebundle.com',
Referer: location.href
},
body: data.body,
credentials: 'same-origin'
})
}
function clickEvent (flag, data) {
return function request () {
if((this.innerText === '已选择') || (this.innerText === '已刮开') || (selecedGame.length >= 10)) return
this.innerText = '请求中...'
fetchHttp(data)
.then(res => res.json())
.then(res => {
if(!res.success){
return (this.innerText = flag ? '未选择' : '未刮开')
}
this.removeEventListener('click', request)
this.innerText = flag ? '已选择' : '已刮开'
this.className = 'current'
keyValue.value = ''
if(!flag) keyValue.value += `${this.title}: ${res.key}\n`
else keyValue.value += `${this.title}: 选择成功\n`
})
}
}
GM_addStyle(`
._sh_hd_ {
border: none;
outline: none;
background-color: #c93756;
margin: 20px 0 0 20px;
border-radius: 5px;
line-height: 50px;
padding: 0 20px;
}
._down_page_{
float: right;
padding: 0 20px;
border-radius: 5px;
height: 50px;
margin: 20px 20px 0 0;
line-height: 50px;
background-color: rgb(22, 159, 227);
color: #fff;
text-decoration: none;
}
._key_value_ {
margin: 20px 0 0 20px;
width: 800px;
height: 200px;
resize: none;
font-size: 18px;
color: #fff;
margin-top: 20px;
outline: none;
background-color: #454c5e;
border: none;
}
._option_ul_, ._select_ul_ {
margin: 20px 0 0 20px;
height: 50px;
line-height: 50px;
display: flex;
list-style: none;
padding: 0;
}
._option_ul_ > li, ._select_ul_ > li {
height: 50px;
line-height: 50px;
background-color: rgb(22, 159, 227);
margin-right: 20px;
border-radius: 5px;
text-align: center;
padding: 0 20px;
color: #fff;
font-size: 16px;
cursor: pointer;
}
._select_ul_ > li {
background-color: #454c5e;
}
._select_ul_ > li.current {
background-color: rgb(22, 159, 227);
}
._game_num_ {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 1;
background-color: rgba(0, 0, 0, .3);
text-align: center;
font-size: 100px;
}
._sh_hd_.current {
background-color: #169fe3;
}
._self_view_ {
list-style: none;
margin: 20px 0 0 0;
padding: 0;
}
._self_view_ > li {
font-size: 16px;
padding: 20px 0 0px 20px;
border-bottom: 10px solid #454c5e;
display: flex;
justify-content: space-between;
}
._self_view_ > li button {
border: none;
outline: none;
background-color: #169fe3;
margin: 0 10px;
font-size: 16px;
border-radius: 0.8em;
padding: 5px 15px;
width: 100px;
float: right;
}
._self_view_ > li .current{
background-color: #c93756;
}
._self_view_ > li:last-child {
border: none;
}
`)
}
})();