// ==UserScript== // @name Humble Choice Get Key // @namespace http://tampermonkey.net/ // @version 0.08 // @description get game key or select game // @author ku mi // @match https://www.humblebundle.com/subscription/* // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function () { 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: '津巴布韦', } async function http(data, flag) { try { const res = await fetch(data.url, { method: data.method || 'GET', body: data.body || null, headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Origin: 'https://www.humblebundle.com', Referer: location.href }, credentials: 'same-origin' }) if(flag)return res.text() return res.json() } catch(e) {} } http({ url: location.href, }, true).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 if(!gamekey) return const {content_choices, display_order} = contentChoiceData.initial const selecedGame = contentChoicesMade ? 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 || '' } }) const gameBox = document.createElement('div') const optionBox = document.createElement('div') gameBox.innerHTML = `
锁区信息仅供参考,以激活后的SUB为准Download页面
` optionBox.innerHTML = `
` const gamelist = gameBox.querySelector('._self_view_') const sButton = gameBox.querySelector('._sh_hd_') const sBox = gameBox.querySelector('._sh_box_') const textArr = ['选择游戏(只选不刮)','刮开/提取', '全选高亮', '取消高亮', '显示数字'] const classArr = ['._copy_','._clear_','._option_ul_','._select_ul_', '._key_value_'] const [cButton, clearKey, optionUl, selectUl, keyValue] = classArr.map(item => optionBox.querySelector(item)) const [selectKey, getKey, allLight, noLight, setNumber] = textArr.map((item, index) => { const li = document.createElement('li') li.innerText = item optionUl.appendChild(li) return li }) const liChild = allGame.map((item, index) => { const li = document.createElement('li') li.innerText = index + 1 Object.assign(li.dataset, { name: item.name, title: item.title, 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 = '')) setNumber.addEventListener('click', () => { if(!document.querySelectorAll('._game_num_').length) setNum() }) cButton.addEventListener('click', () => { if(!keyValue.value.length) return keyValue.select() document.execCommand('copy') }) clearKey.addEventListener('click', () => { keyValue.value = '' }) sButton.addEventListener('click', function () { const flag = this.classList.contains('current') gamelist.classList.remove(flag ? '_slide_down_' : '_slide_up_') gamelist.classList.add(flag ? '_slide_up_': '_slide_down_') this.innerText = flag ? '显示锁区信息' : '隐藏锁区信息' this.classList.toggle('current') }) getKey.addEventListener('click', () => { selectGet() }) selectKey.addEventListener('click', () => { selectGet(true) }) gameBox.insertBefore(optionBox, sBox) allGame.forEach(item => { const li = document.createElement('li') li.innerHTML = `
${item.title}${item.key ? ``: ``}${selecedGame.includes(item.name) ? `` : ``}

${getLock(item)}

` gamelist.appendChild(li) }) const noGetList = gamelist.querySelectorAll('.no-get') const noSelectList = gamelist.querySelectorAll('.no-select') const view = document.querySelector('.content-choices-view') const list = document.querySelector(".content-choice-tiles.js-content-choice-tiles") view.insertBefore(gameBox, list) noSelectList.forEach(item => { item.addEventListener('click', clickEvent(true)) }) noGetList.forEach(item => { item.addEventListener('click', clickEvent(false)) }) function clickEvent(flag) { return function request() { let data = { url: `https://www.humblebundle.com/humbler/choosecontent?gamekey=${gamekey}&parent_identifier=initial&chosen_identifiers%5B%5D=${this.dataset.name}`} if ((this.innerText === '已选择') || (this.innerText === '已刮开')) return let getKeyFlag = selecedGame.includes(this.dataset.name) if(!flag && getKeyFlag){ data = {url: 'https://www.humblebundle.com/humbler/redeemkey', body: `keytype=${this.dataset.machine_name}&key=${gamekey}&keyindex=0`, method: 'POST'} } this.innerText = '请求中...' http(data) .then(res => { if(!res.success) { if(flag && !getKeyFlag) keyValue.value += `${this.dataset.title}: 选择失败\n` return (this.innerText = flag ? '未选择' : '未刮开') } if(!flag && !getKeyFlag){ let el = this.nextElementSibling el.innerText = '已选择' el.className = 'current' }else { this.innerText = flag ? '已选择' : '已刮开' this.className = 'current' } if(!flag && getKeyFlag) { keyValue.value += `${this.dataset.title}: ${res.key}\n` liChild[display_order.findIndex(item => item === this.dataset.name)].dataset.key = res.key }else if(flag && !getKeyFlag){ keyValue.value += `${this.dataset.title}: 选择成功\n` } if(!getKeyFlag) selecedGame.push(this.dataset.name) if(!flag && !getKeyFlag) clickEvent(flag).call(this) }).catch(()=>{}) } } function getLock(game) { //获取锁区信息 let lockDetil function getZhName(arr) { return arr.map(item => { if (/(\u53f0\u6e7e|\u4e2d\u56fd|\u9999\u6e2f|\u6fb3\u95e8)/.test(countryMap[item])) return `${countryMap[item]}` return countryMap[item] }).join('、') } if (game.exclusive.length) { lockDetil = `只能在以下激活: ${getZhName(game.exclusive)}` } if (game.disallowed.length) { lockDetil = `不能在以下地区激活: ${getZhName(game.disallowed)}` } return lockDetil || `无限制激活` } function selectGet(flag) { if(flag && selecedGame.length >= 10) return noLight.click() keyValue.value = '' let num = 0 function start(els, item) { els.forEach((it) => { if(it.dataset.name === item.dataset.name){ it.click() } }) } const no = flag ? gamelist.querySelectorAll('.no-select') : gamelist.querySelectorAll('.no-get') liChild.filter(item => item.classList.contains('current')).forEach((item, index) => { if(!flag && item.dataset.key){ num++ return (keyValue.value += `${item.dataset.title}: ${item.dataset.key}\n`) } else if(flag && selecedGame.includes(item.dataset.name)){ num++ return (keyValue.value += `${item.dataset.title}: 重复选择\n`) } let time = setTimeout(() => { clearTimeout(time) start(no, item) }, (index - num) * 500) }) noLight.click() } 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) }) } GM_addStyle(`._sh_hd_{border:none;outline:none;color:#A4D7F5;background-image:linear-gradient( to bottom,rgba(47,137,188,1) 5%,rgba(23,67,92,1) 95%);margin:20px 0 0 20px;border-radius:5px;line-height:50px;padding:0 20px;}._slide_down_{animation:slideDown .3s forwards;}._slide_up_{animation:slideUp .3s forwards;}@keyframes slideUp{0%{max-height:2000px;}100%{max-height:0;}}@keyframes slideDown{0%{max-height:0;}100%{max-height:2000px;}}._down_page_{float:right;padding:0 20px;border-radius:5px;height:50px;margin:20px 20px 0 0;line-height:50px;background:linear-gradient( to bottom,rgba(47,137,188,1) 5%,rgba(23,67,92,1) 95%);color:#A4D7F5;text-decoration:none;}._key_value_{margin:20px 0 0 20px;width:650px;height:200px;resize:none;font-size:18px;color:#fff;outline:none;background-color:#454c5e;border:none;}._option_ul_,._select_ul_{margin:30px 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;color:#A4D7F5;background-image:linear-gradient( to bottom,rgba(47,137,188,1) 5%,rgba(23,67,92,1) 95%);margin-right:20px;border-radius:5px;text-align:center;padding:0 20px;font-size:16px;cursor:pointer;}._select_ul_>li{background:#454c5e;}._select_ul_>li.current{background-image:linear-gradient(to top,#0c3483 0%,#a2b6df 100%,#6b8cce 100%,#a2b6df 100%);}._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-image:linear-gradient(to top,#c71d6f 0%,#d09693 100%);color:#fff;}._self_view_{max-height:0;list-style:none;margin:20px 0 0 0;padding:0;overflow:hidden;}._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-image:linear-gradient( to bottom,rgba(47,137,188,1) 5%,rgba(23,67,92,1) 95%);color:#A4D7F5;margin:0 10px;font-size:16px;border-radius:0.8em;padding:5px 15px;width:100px;float:right;}._copy_,._clear_{background:linear-gradient( to bottom,rgba(47,137,188,1) 5%,rgba(23,67,92,1) 95%);color:#A4D7F5;border-radius:5px;border:none;outline:none;font-size:16px;line-height:50px;text-align:center;margin:170px 0 0 20px;width:70px;}._self_view_>li .current{opacity:0.45;}._self_view_>li:last-child{border:none;}`)} })();