// ==UserScript==
// @name Steam Bundle Sites Extension
// @namespace http://tampermonkey.net/
// @version 1.13.1
// @description A steam bundle sites' tool kits.
// @icon http://store.steampowered.com/favicon.ico
// @author Bisumaruko, Cloud
// @include http*://store.steampowered.com/*
// @include https://www.indiegala.com/gift*
// @include https://www.indiegala.com/profile*
// @include https://www.indiegala.com/game*
// @include http*://*bundlestars.com/*
// @include https://www.fanatical.com/*
// @include https://www.humblebundle.com/downloads*
// @include https://www.humblebundle.com/home/*
// @include http*://*dailyindiegame.com/*
// @include http*://bundle.ccyycn.com/order/*
// @include https://groupees.com/purchases
// @include https://groupees.com/profile/purchases/*
// @include http*://*agiso.com/*
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.9.2/sweetalert2.min.js
// @resource sweetalert2CSS https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.9.2/sweetalert2.min.css
// @connect store.steampowered.com
// @connect www.google.com
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant unsafeWindow
// @run-at document-start
// @noframes
// ==/UserScript==
/* global swal */
// setup jQuery
const $ = jQuery.noConflict(true);
$.fn.pop = [].pop;
$.fn.shift = [].shift;
// inject swal css
GM_addStyle(GM_getResourceText('sweetalert2CSS'));
// inject script css
GM_addStyle(`
pre.SBSE_errorMsg { height: 200px; text-align: left; white-space: pre-wrap; }
`);
// load up
const regKey = /(?:(?:([A-Z0-9])(?!\1{4})){5}-){2,5}[A-Z0-9]{5}/g;
const eol = "\r\n";
const has = Object.prototype.hasOwnProperty;
const unique = a => [...new Set(a)];
const owned = JSON.parse(localStorage.getItem('SBSE_owned') || '{}');
const activated = {
data: JSON.parse(GM_getValue('SBSE_activated') || '[]'),
push(key) {
this.data.push(key);
GM_setValue('SBSE_activated', JSON.stringify(this.data));
},
check(key) {
return this.data.includes(key);
}
};
const config = {
data: JSON.parse(GM_getValue('SBSE_config') || '{}'),
set(key, value, callback = null) {
this.data[key] = value;
GM_setValue('SBSE_config', JSON.stringify(this.data));
if (typeof callback === 'function') callback();
},
get(key) {
return has.call(this.data, key) ? this.data[key] : null;
},
init() {
if (!has.call(this.data, 'autoUpdateSessionID')) this.data.autoUpdateSessionID = true;
if (!has.call(this.data, 'preselectIncludeTitle')) this.data.preselectIncludeTitle = false;
if (!has.call(this.data, 'titleComesLast')) this.data.titleComesLast = false;
if (!has.call(this.data, 'preselectJoinKeys')) this.data.preselectJoinKeys = false;
if (!has.call(this.data, 'joinKeysASFStyle')) this.data.joinKeysASFStyle = true;
if (!has.call(this.data, 'activateAllKeys')) this.data.activateAllKeys = false;
}
};
const keyDetails = {
data: {},
set(key = '', obj) {
if (key.length > 0) {
obj.title = has.call(obj, 'title') ? obj.title.trim() : '';
if (has.call(obj, 'app')) obj.app = parseInt(obj.app, 10);
if (has.call(obj, 'sub')) obj.sub = parseInt(obj.sub, 10);
if (has.call(obj, 'url')) {
const matched = obj.url.match(/steam.+\/(app|sub)\/(\d+)/);
if (matched) obj[matched[1]] = parseInt(matched[2], 10);
}
this.data[key] = obj;
}
},
get(key) {
return has.call(this.data, key) ? this.data[key] : null;
},
isOwned(key) {
const detail = this.data[key];
if (detail && owned.app.includes(detail.app)) return true;
if (detail && owned.sub.includes(detail.sub)) return true;
return false;
}
};
const ISO2 = {
name: {
tchinese: {
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: '不丹',
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: '法屬玻里尼西亞',
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: '辛巴威'
},
schinese: {
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: '不丹',
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: '津巴布韦'
},
english: {
AD: 'Andorra',
AE: 'United Arab Emirates',
AF: 'Afghanistan',
AG: 'Antigua and Barbuda',
AI: 'Anguilla',
AL: 'Albania',
AM: 'Armenia',
AO: 'Angola',
AQ: 'Antarctica',
AR: 'Argentina',
AS: 'American Samoa',
AT: 'Austria',
AU: 'Australia',
AW: 'Aruba',
AX: 'Aland Islands',
AZ: 'Azerbaijan',
BA: 'Bosnia and Herzegovina',
BB: 'Barbados',
BD: 'Bangladesh',
BE: 'Belgium',
BF: 'Burkina Faso',
BG: 'Bulgaria',
BH: 'Bahrain',
BI: 'Burundi',
BJ: 'Benin',
BL: 'Saint Barthélemy',
BM: 'Bermuda',
BN: 'Brunei',
BO: 'Bolivia',
BQ: 'Bonaire',
BR: 'Brazil',
BS: 'Bahamas',
BT: 'Bhutan',
BV: 'Bouvet Island',
BW: 'Botswana',
BY: 'Belarus',
BZ: 'Belize',
CA: 'Canada',
CC: 'Cocos (Keeling) Islands',
CD: 'East Congo',
CF: 'Central African Republic',
CG: 'West Congo',
CH: 'Switzerland',
CI: 'Ivory Coast',
CK: 'Cook Islands',
CL: 'Chile',
CM: 'Cameroon',
CN: 'China',
CO: 'Colombia',
CR: 'Costa Rica',
CS: 'Serbia and Montenegro',
CU: 'Cuba',
CV: 'Cabo Verde',
CW: 'Curaçao',
CX: 'Christmas Island',
CY: 'Cyprus',
CZ: 'Czechia',
DE: 'Germany',
DJ: 'Djibouti',
DK: 'Denmark',
DM: 'Dominica',
DO: 'Dominican Republic',
DZ: 'Algeria',
EC: 'Ecuador',
EE: 'Estonia',
EG: 'Egypt',
EH: 'Western Sahara',
ER: 'Eritrea',
ES: 'Spain',
ET: 'Ethiopia',
FI: 'Finland',
FJ: 'Fiji',
FK: 'Falkland Islands',
FM: 'Micronesia',
FO: 'Faroe Islands',
FR: 'France',
GA: 'Gabon',
GB: 'United Kingdom',
GD: 'Grenada',
GE: 'Georgia',
GF: 'French Guiana',
GG: 'Guernsey',
GH: 'Ghana',
GI: 'Gibraltar',
GL: 'Greenland',
GM: 'Gambia',
GN: 'Guinea',
GP: 'Guadeloupe',
GQ: 'Equatorial Guinea',
GR: 'Greece',
GS: 'South Georgia and the South Sandwich Islands',
GT: 'Guatemala',
GU: 'Guam',
GW: 'Guinea-Bissau',
GY: 'Guyana',
HK: 'Hong Kong',
HM: 'Heard Island and McDonald Islands',
HN: 'Honduras',
HR: 'Croatia',
HT: 'Haiti',
HU: 'Hungary',
ID: 'Indonesia',
IE: 'Ireland',
IL: 'Israel',
IM: 'Isle of Man',
IN: 'India',
IO: 'British Indian Ocean Territory',
IQ: 'Iraq',
IR: 'Iran',
IS: 'Iceland',
IT: 'Italy',
JE: 'Jersey',
JM: 'Jamaica',
JO: 'Jordan',
JP: 'Japan',
KE: 'Kenya',
KG: 'Kyrgyzstan',
KH: 'Cambodia',
KI: 'Kiribati',
KM: 'Comoros',
KN: 'Saint Kitts and Nevis',
KP: 'North Korea',
KR: 'South Korea',
KW: 'Kuwait',
KY: 'Cayman Islands',
KZ: 'Kazakhstan',
LA: 'Lao',
LB: 'Lebanon',
LC: 'Saint Lucia',
LI: 'Liechtenstein',
LK: 'Sri Lanka',
LR: 'Liberia',
LS: 'Lesotho',
LT: 'Lithuania',
LU: 'Luxembourg',
LV: 'Latvia',
LY: 'Libya',
MA: 'Morocco',
MC: 'Monaco',
MD: 'Moldova',
ME: 'Montenegro',
MF: 'Saint Martin (French part)',
MG: 'Madagascar',
MH: 'Marshall Islands',
MK: 'Macedonia',
ML: 'Mali',
MM: 'Myanmar',
MN: 'Mongolia',
MO: 'Macao',
MP: 'Northern Mariana Islands',
MQ: 'Martinique',
MR: 'Mauritania',
MS: 'Montserrat',
MT: 'Malta',
MU: 'Mauritius',
MV: 'Maldives',
MW: 'Malawi',
MX: 'Mexico',
MY: 'Malaysia',
MZ: 'Mozambique',
NA: 'Namibia',
NC: 'New Caledonia',
NE: 'Niger',
NF: 'Norfolk Island',
NG: 'Nigeria',
NI: 'Nicaragua',
NL: 'Netherlands',
NO: 'Norway',
NP: 'Nepal',
NR: 'Nauru',
NU: 'Niue',
NZ: 'New Zealand',
OM: 'Oman',
PA: 'Panama',
PE: 'Peru',
PF: 'French Polynesia',
PG: 'Papua New Guinea',
PH: 'Philippines',
PK: 'Pakistan',
PL: 'Poland',
PM: 'Saint Pierre and Miquelon',
PN: 'Pitcairn',
PR: 'Puerto Rico',
PS: 'Palestine',
PT: 'Portugal',
PW: 'Palau',
PY: 'Paraguay',
QA: 'Qatar',
RE: 'Reunion',
RO: 'Romania',
RS: 'Serbia',
RU: 'Russia',
RW: 'Rwanda',
SA: 'Saudi Arabia',
SB: 'Solomon Islands',
SC: 'Seychelles',
SD: 'Sudan',
SE: 'Sweden',
SG: 'Singapore',
SH: 'Saint Helena, Ascension and Tristan da Cunha',
SI: 'Slovenia',
SJ: 'Svalbard and Jan Mayen',
SK: 'Slovakia',
SL: 'Sierra Leone',
SM: 'San Marino',
SN: 'Senegal',
SO: 'Somalia',
SR: 'Suriname',
SS: 'South Sudan',
ST: 'Sao Tome and Principe',
SV: 'El Salvador',
SX: 'Sint Maarten (Dutch part)',
SY: 'Syria',
SZ: 'Swaziland',
TC: 'Turks and Caicos Islands',
TD: 'Chad',
TF: 'French Southern Territories',
TG: 'Togo',
TH: 'Thailand',
TJ: 'Tajikistan',
TK: 'Tokelau',
TL: 'Timor-Leste',
TM: 'Turkmenistan',
TN: 'Tunisia',
TO: 'Tonga',
TR: 'Turkey',
TT: 'Trinidad and Tobago',
TV: 'Tuvalu',
TW: 'Taiwan',
TZ: 'Tanzania',
UA: 'Ukraine',
UG: 'Uganda',
UM: 'United States Minor Outlying Islands',
US: 'United States',
UY: 'Uruguay',
UZ: 'Uzbekistan',
VA: 'Holy See',
VC: 'Saint Vincent and the Grenadines',
VE: 'Venezuela',
VG: 'Virgin Islands, British',
VI: 'Virgin Islands, U.S.',
VN: 'Viet Nam',
VU: 'Vanuatu',
WF: 'Wallis and Futuna',
WS: 'Samoa',
XK: 'Kosovo',
YE: 'Yemen',
YT: 'Mayotte',
ZA: 'South Africa',
ZM: 'Zambia',
ZW: 'Zimbabwe'
}
},
get(code, language) {
const data = this.name[language || config.get('language') || 'english'];
return has.call(data, code) ? data[code] : code;
}
};
config.init();
// text
const i18n = {
tchinese: {
name: '繁體中文',
updateSuccessTitle: '更新成功!',
updateSuccess: '成功更新Steam sessionID',
successStatus: '成功',
successDetail: '無資料',
skippedStatus: '跳過',
activatedDetail: '已啟動',
failStatus: '失敗',
failTitle: '糟糕!',
failDetailUnexpected: '發生未知錯誤,請稍後再試',
failDetailInvalidKey: '序號錯誤',
failDetailUsedKey: '序號已被使用',
failDetailRateLimited: '啟動受限',
failDetailCountryRestricted: '地區限制',
failDetailAlreadyOwned: '產品已擁有',
failDetailMissingBaseGame: '未擁有主程式',
failDetailPS3Required: '需要PS3 啟動',
failDetailGiftWallet: '偵測到禮物卡/錢包序號',
failDetailParsingFailed: '處理資料發生錯誤,請稍後再試',
failDetailRequestFailedNeedUpdate: '請求發生錯誤,請稍後再試<br/>或者嘗試更新SessionID',
noItemDetails: '無產品詳細資料',
notLoggedInTitle: '未登入',
notLoggedInMsg: '請登入Steam 以讓腳本紀錄SessionID',
missingTitle: '未發現SessionID',
missingMsg: '請問要更新SessionID 嗎?',
emptyInput: '未發現Steam 序號',
settingsTitle: '設定',
settingsAutoUpdateSessionID: '自動更新SessionID',
settingsSessionID: '我的SessionID',
settingsSyncLibrary: '同步遊戲庫資料',
settingsSyncLibraryButton: '同步',
settingsLanguage: '語言',
settingsPreselectIncludeTitle: '預選包括遊戲名',
settingsTitleComesLast: '遊戲名置後',
settingsPreselectJoinKeys: '預選合併序號',
settingsJoinKeysASFStyle: '合併ASF 格式序號',
settingsActivateAllKeys: '不跳過、啟動所有序號',
HBAlreadyOwned: '遊戲已擁有',
HBRedeemAlreadyOwned: '確定刮開 %title% Steam 序號?',
HBActivationRestrictions: '啟動限制',
HBDisallowedCountries: '限制以下地區啟動',
HBExclusiveCountries: '僅限以下地區啟動',
DIGEasyBuyPurchase: '購買',
DIGEasyBuySelectAll: '全選',
DIGEasyBuySelectCancel: '取消',
DIGEasyBuyHideOwned: '隱藏已擁有',
DIGEasyBuyShowOwned: '顯示已擁有',
DIGEasyBuyLoadAllPages: '加載所有頁',
DIGEasyBuyLoading: '加載第%page%頁中',
DIGEasyBuyLoadingComplete: '加載完成',
DIGButtonPurchasing: '購買中',
DIGInsufficientFund: '餘額不足,準備回到帳號頁',
DIGMarketSearchResult: '目前市集上架中',
DIGRateAllPositive: '全部好評',
DIGClickToHideThisRow: '隱藏此上架遊戲',
buttonReveal: '刮開',
buttonRetrieve: '提取',
buttonActivate: '啟動',
buttonCopy: '複製',
buttonReset: '清空',
buttonExport: '匯出',
checkboxIncludeGameTitle: '遊戲名',
checkboxJoinKeys: '合併',
checkboxSkipUsed: '跳過已使用',
checkboxSkipOwned: '跳過已擁有',
checkboxMarketListings: '上架於市集',
selectConnector: '至',
markAllAsUsed: '標記全部已使用',
syncSuccessTitle: '同步成功',
syncSuccess: '成功同步Steam 遊戲庫資料'
},
schinese: {
name: '简体中文',
updateSuccessTitle: '更新成功',
updateSuccess: '成功更新Steam sessionID',
successStatus: '成功',
successDetail: '无信息',
activatedDetail: '已激活',
skippedStatus: '跳过',
failStatus: '失败',
failTitle: '糟糕!',
failDetailUnexpected: '发生未知错误,请稍后再试',
failDetailInvalidKey: '激活码错误',
failDetailUsedKey: '激活码已被使用',
failDetailRateLimited: '激活受限',
failDetailCountryRestricted: '地区限制',
failDetailAlreadyOwned: '产品已拥有',
failDetailMissingBaseGame: '未拥有基础游戏',
failDetailPS3Required: '需要PS3 激活',
failDetailGiftWallet: '侦测到礼物卡/钱包激活码',
failDetailParsingFailed: '处理资料发生错误,请稍后再试',
failDetailRequestFailedNeedUpdate: '请求发生错误,请稍后再试<br/>或者尝试更新SessionID',
noItemDetails: '无产品详细信息',
notLoggedInTitle: '未登入',
notLoggedInMsg: '请登入Steam 以让脚本记录SessionID',
missingTitle: '未发现SessionID',
missingMsg: '请问要更新SessionID 吗?',
emptyInput: '未批配到Steam 激活码',
settingsTitle: '设置',
settingsAutoUpdateSessionID: '自动更新SessionID',
settingsSessionID: '我的SessionID',
settingsSyncLibrary: '同步游戏库资料',
settingsSyncLibraryButton: '同步',
settingsLanguage: '语言',
settingsPreselectIncludeTitle: '预选包括游戏名',
settingsTitleComesLast: '游戏名置后',
settingsPreselectJoinKeys: '预选合并激活码',
settingsJoinKeysASFStyle: '合并ASF 格式激活码',
settingsActivateAllKeys: '不跳过、激活所有激活码',
HBAlreadyOwned: '游戏已拥有',
HBRedeemAlreadyOwned: '确定刮开 %title% Steam 激活码?',
HBActivationRestrictions: '激活限制',
HBDisallowedCountries: '限制以下地区激活',
HBExclusiveCountries: '仅限以下地区激活',
DIGEasyBuyPurchase: '购买',
DIGEasyBuySelectAll: '全选',
DIGEasyBuySelectCancel: '取消',
DIGEasyBuyHideOwned: '隐藏已拥有',
DIGEasyBuyShowOwned: '显示已拥有',
DIGEasyBuyLoadAllPages: '加载所有页',
DIGEasyBuyLoading: '加载第%page%页中',
DIGEasyBuyLoadingComplete: '加载完成',
DIGButtonPurchasing: '购买中',
DIGInsufficientFund: '余额不足,准备回到账号页',
DIGMarketSearchResult: '目前市集上架中',
DIGRateAllPositive: '全部好评',
DIGClickToHideThisRow: '隐藏此上架游戏',
buttonReveal: '刮开',
buttonRetrieve: '提取',
buttonActivate: '激活',
buttonCopy: '复制',
buttonReset: '清空',
buttonExport: '导出',
checkboxIncludeGameTitle: '游戏名',
checkboxJoinKeys: '合并',
checkboxSkipUsed: '跳过已使用',
checkboxSkipOwned: '跳过已拥有',
checkboxMarketListings: '上架于市集',
selectConnector: '至',
markAllAsUsed: '标记全部已使用',
syncSuccessTitle: '同步成功',
syncSuccess: '成功同步Steam 游戏库资料'
},
english: {
name: 'English',
updateSuccessTitle: 'Update Successful!',
updateSuccess: 'Steam sessionID is successfully updated',
successStatus: 'Success',
successDetail: 'No Detail',
activatedDetail: 'Activated',
skippedStatus: 'Skipped',
failStatus: 'Fail',
failTitle: 'Opps!',
failDetailUnexpected: 'Unexpected Error',
failDetailInvalidKey: 'Invalid Key',
failDetailUsedKey: 'Used Key',
failDetailRateLimited: 'Rate Limited',
failDetailCountryRestricted: 'Country Restricted',
failDetailAlreadyOwned: 'Product Already Owned',
failDetailMissingBaseGame: 'Missing Base Game',
failDetailPS3Required: 'PS3 Activation Required',
failDetailGiftWallet: 'Gift Card/Wallet Code Detected',
failDetailParsingFailed: 'Result parse failed',
failDetailRequestFailedNeedUpdate: 'Request failed, please try again<br/>or update sessionID',
noItemDetails: 'No Item Details',
notLoggedInTitle: 'Not Logged-In',
notLoggedInMsg: 'Please login to Steam so sessionID can be saved',
missingTitle: 'Missing SessionID',
missingMsg: 'Do you want to update your Steam sessionID?',
emptyInput: 'Could not find Steam code',
settingsTitle: 'Settings',
settingsAutoUpdateSessionID: 'Auto Update SessionID',
settingsSessionID: 'Your sessionID',
settingsSyncLibrary: 'Sync Library Data',
settingsSyncLibraryButton: 'Sync',
settingsLanguage: 'Language',
settingsPreselectIncludeTitle: 'Pre-select Include Title',
settingsTitleComesLast: 'Title Comes Last',
settingsPreselectJoinKeys: 'Pre-select Join Keys',
settingsJoinKeysASFStyle: 'Join Keys w/ ASF Style',
settingsActivateAllKeys: 'No skip & activate all keys',
HBAlreadyOwned: 'Game Already Owned',
HBRedeemAlreadyOwned: 'Are you sure to redeem %title% Steam Key?',
HBActivationRestrictions: 'Activation Restrictions',
HBDisallowedCountries: 'Cannot be activated in the following regions',
HBExclusiveCountries: 'Can only be activated in the following regions',
DIGEasyBuyPurchase: 'Purchase',
DIGEasyBuySelectAll: 'Select All',
DIGEasyBuySelectCancel: 'Cancel',
DIGEasyBuyHideOwned: 'Hide Owned',
DIGEasyBuyShowOwned: 'Show Owned',
DIGEasyBuyLoadAllPages: 'Load All Pages',
DIGEasyBuyLoading: 'Loading page %page%',
DIGEasyBuyLoadingComplete: 'Loaded',
DIGButtonPurchasing: 'Purchassing',
DIGInsufficientFund: 'Insufficient fund, returning to account page',
DIGMarketSearchResult: 'Currently listing in marketplace',
DIGRateAllPositive: 'Mark All Positive',
DIGClickToHideThisRow: 'Hide this game from listings',
buttonReveal: 'Reveal',
buttonRetrieve: 'Retrieve',
buttonActivate: 'Activate',
buttonCopy: 'Copy',
buttonReset: 'Reset',
buttonExport: 'Export',
checkboxIncludeGameTitle: 'Game Title',
checkboxJoinKeys: 'Join',
checkboxSkipUsed: 'Skip Used',
checkboxSkipOwned: 'Skip Owned',
checkboxMarketListings: 'Market Listings',
selectConnector: 'to',
markAllAsUsed: 'Mark All as Used',
syncSuccessTitle: 'Sync Successful',
syncSuccess: 'Successfully sync Steam library data'
}
};
let text = has.call(i18n, config.get('language')) ? i18n[config.get('language')] : i18n.english;
// inject settings panel css
GM_addStyle(`
.SBSE_settings .name { text-align: right; vertical-align: top; }
.SBSE_settings .value { text-align: left; }
.SBSE_settings .value > * { height: 30px; margin: 0 20px 10px; }
.SBSE_settings .switch { position: relative; display: inline-block; width: 60px; }
.SBSE_settings .switch input { display: none; }
.SBSE_settings .slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
}
.SBSE_settings .slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 2px;
bottom: 2px;
background-color: white;
transition: 0.4s;
}
.SBSE_settings input:checked + .slider { background-color: #2196F3; }
.SBSE_settings input:focus + .slider { box-shadow: 0 0 1px #2196F3; }
.SBSE_settings input:checked + .slider:before { transform: translateX(30px); }
.SBSE_settings > span { display: inline-block; cursor: pointer; color: white; }
`);
// functions
const getSessionID = () => {
GM_xmlhttpRequest({
method: 'GET',
url: 'http://store.steampowered.com/',
onload: res => {
if (res.status === 200) {
const accountID = res.response.match(/g_AccountID = (\d+)/).pop();
const sessionID = res.response.match(/g_sessionID = "(\w+)"/).pop();
if (accountID > 0) config.set('sessionID', sessionID);else {
swal({
title: text.notLoggedInTitle,
text: text.notLoggedInMsg,
type: 'error',
showCancelButton: true
}).then(() => {
window.open('http://store.steampowered.com/');
});
}
}
}
});
};
const syncLibrary = (notify = true) => {
GM_xmlhttpRequest({
method: 'GET',
url: `http://store.steampowered.com/dynamicstore/userdata/t=${Math.random()}`,
onload: res => {
if (res.status === 200) {
const data = JSON.parse(res.response);
if (data.rgOwnedApps.length > 0) owned.app = data.rgOwnedApps;
if (data.rgOwnedPackages.length > 0) owned.sub = data.rgOwnedPackages;
owned.lastSync = Date.now();
localStorage.setItem('SBSE_owned', JSON.stringify(owned));
if (notify) {
swal({
title: text.syncSuccessTitle,
text: text.syncSuccess,
type: 'success',
timer: 3000
});
}
}
}
});
};
const settings = {
construct() {
const panelHTML = `
<div class="SBSE_settings">
<table>
<tr>
<td class="name">${text.settingsAutoUpdateSessionID}</td>
<td class="value">
<label class="switch">
<input type="checkbox" class="autoUpdateSessionID">
<span class="slider"></span>
</label>
</td>
</tr>
<tr>
<td class="name">${text.settingsSessionID}</td>
<td class="value">
<input type="text" class="sessionID" value="${config.get('sessionID')}">
</td>
</tr>
<tr>
<td class="name">${text.settingsSyncLibrary}</td>
<td class="value">
<button class="syncLibrary">${text.settingsSyncLibraryButton}</button>
</td>
</tr>
<tr>
<td class="name">${text.settingsLanguage}</td>
<td class="value">
<select class="language"></select>
</td>
</tr>
<tr>
<td class="name">${text.settingsPreselectIncludeTitle}</td>
<td class="value">
<label class="switch">
<input type="checkbox" class="preselectIncludeTitle">
<span class="slider"></span>
</label>
</td>
</tr>
<tr>
<td class="name">${text.settingsTitleComesLast}</td>
<td class="value">
<label class="switch">
<input type="checkbox" class="titleComesLast">
<span class="slider"></span>
</label>
</td>
</tr>
<tr>
<td class="name">${text.settingsPreselectJoinKeys}</td>
<td class="value">
<label class="switch">
<input type="checkbox" class="preselectJoinKeys">
<span class="slider"></span>
</label>
</td>
</tr>
<tr>
<td class="name">${text.settingsJoinKeysASFStyle}</td>
<td class="value">
<label class="switch">
<input type="checkbox" class="joinKeysASFStyle">
<span class="slider"></span>
</label>
</td>
</tr>
<tr>
<td class="name">${text.settingsActivateAllKeys}</td>
<td class="value">
<label class="switch">
<input type="checkbox" class="activateAllKeys">
<span class="slider"></span>
</label>
</td>
</tr>
</table>
</div>
`;
return panelHTML;
},
display() {
swal({
title: text.settingsTitle,
html: this.construct()
});
// apply settings
const $panel = $(swal.getContent());
const $sessionID = $panel.find('.sessionID');
const $language = $panel.find('.language');
// toggles
$panel.find('input[type=checkbox]').each((index, input) => {
const $input = $(input);
$input.prop('checked', config.get(input.className));
$input.change(e => {
swal.showLoading();
const setting = e.delegateTarget.className;
const state = e.delegateTarget.checked;
config.set(setting, state);
if (setting === 'autoUpdateSessionID') $sessionID.attr('disabled', state);
setTimeout(swal.hideLoading, 500);
});
});
// sessionID input
$sessionID.prop('disabled', config.get('autoUpdateSessionID'));
$sessionID.change(() => {
swal.showLoading();
config.set('sessionID', $sessionID.val().trim());
setTimeout(swal.hideLoading, 500);
});
// sync library
$panel.find('.syncLibrary').click(syncLibrary);
// language
Object.keys(i18n).forEach(language => {
$language.append(new Option(i18n[language].name, language));
});
$panel.find(`option[value=${config.get('language')}]`).prop('selected', true);
$language.change(() => {
swal.showLoading();
const newLanguage = $language.val();
config.set('language', newLanguage);
text = has.call(i18n, newLanguage) ? i18n[newLanguage] : i18n.english;
setTimeout(swal.hideLoading, 500);
});
}
};
const activateHandler = {
keys: [],
results: {},
updateResults(txt = null) {
const $textarea = $('.SBSE_container > textarea');
if (txt) $textarea.val(txt);else {
const results = this.results;
const parsed = [];
Object.values(results).forEach(result => {
parsed.push(result.join(' | '));
});
$textarea.val(parsed.join(eol));
}
},
getResultStatus(result) {
let status = text.failStatus;
let statusMsg = text.failDetailUnexpected;
const errors = {
14: text.failDetailInvalidKey,
15: text.failDetailUsedKey,
53: text.failDetailRateLimited,
13: text.failDetailCountryRestricted,
9: text.failDetailAlreadyOwned,
24: text.failDetailMissingBaseGame,
36: text.failDetailPS3Required,
50: text.failDetailGiftWallet
};
if (result.success === 1) {
status = text.successStatus;
statusMsg = text.successDetail;
} else if (result.success === 2) {
if (has.call(errors, result.purchase_result_details)) {
statusMsg = errors[result.purchase_result_details];
}
}
return `${status}/${statusMsg}`;
},
getResultItems(info) {
const descriptions = [];
if (info && info.line_items) {
info.line_items.forEach(item => {
const description = [];
if (item.packageid > 0) description.push(`sub: ${item.packageid}`);
if (item.appid > 0) description.push(`app: ${item.appid}`);
description.push(item.line_item_description);
descriptions.push(description.join(' '));
});
}
return descriptions.join(', ');
},
activateKey(callback) {
const self = this;
const key = self.keys.shift();
if (key) {
if (activated.check(key)) {
self.results[key].push(`${text.skippedStatus}/${text.activatedDetail}`, text.noItemDetails);
self.updateResults();
// next key
self.activateKey(callback);
} else if (keyDetails.isOwned(key) && !config.get('activateAllKeys')) {
const detail = keyDetails.get(key);
const itemDetail = `${detail.app || detail.sub}, ${detail.title}`;
self.results[key].push(`${text.skippedStatus}/${text.failDetailAlreadyOwned}`, itemDetail);
self.updateResults();
// next key
self.activateKey(callback);
} else {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://store.steampowered.com/account/ajaxregisterkey/',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: 'https://store.steampowered.com',
Referer: 'https://store.steampowered.com/account/registerkey'
},
data: `product_key=${key}&sessionid=${config.get('sessionID')}`,
onload: res => {
if (res.status === 200) {
let status = '';
let items = '';
try {
const result = JSON.parse(res.response);
status = self.getResultStatus(result);
items = self.getResultItems(result.purchase_receipt_info);
// update activated
const failCode = result.purchase_result_details;
if (result.success === 1 || [14, 15, 9].includes(failCode)) {
activated.push(key);
// dispatch activated event
$(document).trigger('activated', [key, result]);
}
} catch (e) {
status = `${text.failStatus}/${text.failDetailParsingFailed}`;
items = text.noItemDetails;
}
self.results[key].push(status, items);
self.updateResults();
// next key
setTimeout(self.activateKey.bind(self, callback), 2000);
} else {
const errorMsg = [];
errorMsg.push('<pre class="SBSE_errorMsg">');
errorMsg.push(`sessionID: ${config.get('sessionID') + eol}`);
errorMsg.push(`autoUpdate: ${config.get('autoUpdateSessionID') + eol}`);
errorMsg.push(`status: ${res.status + eol}`);
errorMsg.push(`response: ${res.response + eol}`);
errorMsg.push('</pre>');
swal({
title: text.failTitle,
html: text.failDetailRequestFailedNeedUpdate + eol + errorMsg.join(''),
type: 'error'
});
getSessionID();
callback();
}
}
});
}
} else callback();
},
activateKeys(input, callback) {
const self = this;
const keys = unique(input.match(regKey));
if (keys.length > 0) {
keys.forEach(key => {
self.results[key] = [key];
});
self.keys = Object.keys(self.results);
self.updateResults();
self.activateKey(callback);
} else {
self.updateResults(text.emptyInput);
callback();
}
}
};
const bundleSitesBoxHandler = {
reveal(handler, $games) {
const $reveal = $('.SBSE_BtnReveal');
$reveal.addClass('working');
handler($games, () => {
$reveal.removeClass('working');
$('.SBSE_BtnRetrieve').click();
});
},
retrieve(data) {
if (data.length > 0) {
const includeTitle = !!$('.SBSE_ChkTitle:checked').length;
const joinKeys = !!$('.SBSE_ChkJoin:checked').length;
const separator = joinKeys ? ',' : eol;
const prefix = joinKeys && config.get('joinKeysASFStyle') ? '!redeem ' : '';
const keys = [];
data.forEach(d => {
if (typeof d === 'string') {
keys.push(d);
} else {
const temp = [d.key];
if (includeTitle) temp.unshift(d.title);
if (config.get('titleComesLast')) temp.reverse();
keys.push(temp.join(', '));
}
});
$('.SBSE_container > textarea').val(prefix + keys.join(separator));
}
},
activate(e) {
const $self = $(e.delegateTarget);
const $textarea = $('.SBSE_container > textarea');
let input = $textarea.val().trim();
if (input.length === 0) {
$('.SBSE_BtnRetrieve').click();
input = $textarea.val();
}
$self.prop('disabled', true).addClass('working');
$textarea.attr('disabled', '');
activateHandler.activateKeys(input, () => {
$self.prop('disabled', false).removeClass('working');
$textarea.removeAttr('disabled');
});
},
copy() {
$('.SBSE_container > textarea').select();
document.execCommand('copy');
},
reset() {
$('.SBSE_container > textarea').val('');
},
export(data, title) {
// data: [{key: ..., title: ...}, ...]
const $export = $('.SBSE_BtnExport');
$export.removeAttr('href').removeAttr('download');
if (Array.isArray(data) && data.length > 0) {
const filename = title.replace(/[\\/:*?"<>|!]/g, '');
const formattedData = data.map(line => `${line.title.replace(/,/g, ' ')},${line.key}`).join(eol);
$export.attr('href', `data:text/csv;charset=utf-8,\ufeff${encodeURIComponent(formattedData)}`).attr('download', `${filename}.csv`);
}
},
settings() {
settings.display();
}
};
const bundleSitesBox = () => {
GM_addStyle(`
.SBSE_container {
width: 100%;
height: 200px;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.SBSE_container > textarea {
width: 100%;
height: 150px;
padding: 5px;
border: none;
box-sizing: border-box;
resize: none;
outline: none;
}
.SBSE_container > div { width: 100%; padding-top: 5px; box-sizing: border-box; }
.SBSE_container > div > button, .SBSE_container > div > a {
width: 120px;
position: relative;
margin-right: 10px;
line-height: 28px;
box-sizing: border-box;
outline: none;
cursor: pointer;
transition: all 0.5s;
}
.SBSE_container > div > a { display: inline-block; text-align: center; }
.SBSE_container label { margin-right: 10px; }
#SBSE_BtnSettings {
width: 20px;
height: 20px;
float: right;
margin-top: 3px;
margin-right: 0;
margin-left: 10px;
background-color: transparent;
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGhlaWdodD0iMzJweCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzIgMzI7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxnIGlkPSJMYXllcl8xIi8+PGcgaWQ9ImNvZyI+PHBhdGggZD0iTTMyLDE3Ljk2OXYtNGwtNC43ODEtMS45OTJjLTAuMTMzLTAuMzc1LTAuMjczLTAuNzM4LTAuNDQ1LTEuMDk0bDEuOTMtNC44MDVMMjUuODc1LDMuMjUgICBsLTQuNzYyLDEuOTYxYy0wLjM2My0wLjE3Ni0wLjczNC0wLjMyNC0xLjExNy0wLjQ2MUwxNy45NjksMGgtNGwtMS45NzcsNC43MzRjLTAuMzk4LDAuMTQxLTAuNzgxLDAuMjg5LTEuMTYsMC40NjlsLTQuNzU0LTEuOTEgICBMMy4yNSw2LjEyMWwxLjkzOCw0LjcxMUM1LDExLjIxOSw0Ljg0OCwxMS42MTMsNC43MDMsMTIuMDJMMCwxNC4wMzF2NGw0LjcwNywxLjk2MWMwLjE0NSwwLjQwNiwwLjMwMSwwLjgwMSwwLjQ4OCwxLjE4OCAgIGwtMS45MDIsNC43NDJsMi44MjgsMi44MjhsNC43MjMtMS45NDVjMC4zNzksMC4xOCwwLjc2NiwwLjMyNCwxLjE2NCwwLjQ2MUwxNC4wMzEsMzJoNGwxLjk4LTQuNzU4ICAgYzAuMzc5LTAuMTQxLDAuNzU0LTAuMjg5LDEuMTEzLTAuNDYxbDQuNzk3LDEuOTIybDIuODI4LTIuODI4bC0xLjk2OS00Ljc3M2MwLjE2OC0wLjM1OSwwLjMwNS0wLjcyMywwLjQzOC0xLjA5NEwzMiwxNy45Njl6ICAgIE0xNS45NjksMjJjLTMuMzEyLDAtNi0yLjY4OC02LTZzMi42ODgtNiw2LTZzNiwyLjY4OCw2LDZTMTkuMjgxLDIyLDE1Ljk2OSwyMnoiIHN0eWxlPSJmaWxsOiM0RTRFNTA7Ii8+PC9nPjwvc3ZnPg==);
background-size: contain;
background-repeat: no-repeat;
background-origin: border-box;
border: none;
vertical-align: top;
}
`);
// spinner button affect
GM_addStyle(`
.SBSE_container > div > button:before {
content: '';
position: absolute;
margin-top: 5px;
right: 10px;
width: 20px;
height: 20px;
border: 3px solid;
border-left-color: transparent;
border-radius: 50%;
box-sizing: border-box;
opacity: 0;
transition: opacity 0.5s;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-name: rotate;
animation-timing-function: linear;
}
.SBSE_container > div > button.narrow.working {
width: 100px;
padding-right: 40px;
transition: all 0.5s;
}
.SBSE_container > div > button.working:before {
transition-delay: 0.5s;
transition-duration: 1s;
opacity: 1;
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`);
const $container = $(`
<div class="SBSE_container">
<textarea></textarea>
<div>
<button class="SBSE_BtnReveal">${text.buttonReveal}</button>
<button class="SBSE_BtnRetrieve">${text.buttonRetrieve}</button>
<button class="SBSE_BtnActivate">${text.buttonActivate}</button>
<button class="SBSE_BtnCopy">${text.buttonCopy}</button>
<button class="SBSE_BtnReset">${text.buttonReset}</button>
<a class="SBSE_BtnExport">${text.buttonExport}</a>
<label><input type="checkbox" class="SBSE_ChkTitle">${text.checkboxIncludeGameTitle}</label>
<label><input type="checkbox" class="SBSE_ChkJoin">${text.checkboxJoinKeys}</label>
<button id="SBSE_BtnSettings"> </button>
</div>
</div>
`);
// bind event
$container.find('.SBSE_BtnCopy').click(bundleSitesBoxHandler.copy);
$container.find('.SBSE_BtnReset').click(bundleSitesBoxHandler.reset);
$container.find('.SBSE_BtnActivate').click(bundleSitesBoxHandler.activate);
$container.find('#SBSE_BtnSettings').click(bundleSitesBoxHandler.settings);
// apply settings
if (config.get('preselectIncludeTitle')) $container.find('.SBSE_ChkTitle').prop('checked', true);
if (config.get('preselectJoinKeys')) $container.find('.SBSE_ChkJoin').prop('checked', true);
return $container;
};
const siteCache = {
fanatical: {
doms: ['.account-content']
},
bundlestars: {
doms: [document]
}
};
const siteHandlers = {
indiegala() {
const $box = bundleSitesBox();
// insert textarea
$('#library-contain').eq(0).before($box);
// support for new password protected gift page
const $node = $('#gift-contents');
if ($node.length > 0) {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
Array.from(mutation.addedNodes).forEach(addedNode => {
if (addedNode.id === 'library-contain') {
$node.prepend($box);
observer.disconnect();
}
});
});
});
observer.observe($node[0], { childList: true });
}
// inject css
GM_addStyle(`
.SBSE_container { margin-top: 10px; }
.SBSE_container > textarea { border: 1px solid #CC001D; border-radius: 3px; }
.SBSE_container > div > button, .SBSE_container > div > a { width: 100px; background-color: #CC001D; color: white; border-radius: 3px; }
.SBSE_container > div > a:hover { color: white; }
`);
// dom source
const source = location.pathname === '/profile' ? 'div[id*="_sale_"].collapse.in' : document;
const extractKeys = () => {
const keys = [];
$(source).find('.game-key-string').each((index, element) => {
const $ele = $(element);
const key = $ele.find('.keys').val();
if (key) {
const $a = $ele.find('.title_game > a');
const title = $a.text().trim();
// append key details
keyDetails.set(key, {
url: $a.attr('href'),
title: $a.text()
});
keys.push({
key,
title
});
}
});
return keys;
};
// button click
$box.find('.SBSE_BtnReveal').click(() => {
const handler = ($games, callback) => {
const game = $games.shift();
if (game) {
const $game = $(game);
const code = $game.attr('id').split('_').pop();
const id = $game.attr('onclick').match(/steampowered\.com\/(app|sub)\/(\d+)/)[2];
$.ajax({
method: 'GET',
url: '/myserials/syncget',
dataType: 'json',
data: {
code,
cache: false,
productId: id
},
beforeSend() {
$(`#permbutton_${code}, #fetchlink_${code}, #info_key_${code}`).hide();
$(`#fetching_${code}`).fadeIn();
$(`#ajax_loader_${code}`).show();
$(`#container_activate_${code}`).html('');
},
success(data) {
$(`#ajax_loader_${code}, #fetching_${code}, #info_key_${code}`).hide();
$(`#serial_${code}`).fadeIn();
$(`#serial_n_${code}`).val(data.serial_number);
$game.parent().prev().find('.btn-convert-to-trade').remove();
handler($games, callback);
},
error() {
swal(text.failTitle, text.failDetailUnexpected, 'error');
}
});
} else callback();
};
bundleSitesBoxHandler.reveal(handler, $(source).find('a[id^=fetchlink_]'));
});
$box.find('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$box.find('.SBSE_BtnExport').click(() => {
const $bundleTitle = location.pathname === '/profile' ? $('[aria-expanded="true"] > div#bundle-title') : $('#bundle-title, #indie_gala_2 > div > span');
const title = `IndieGala ${$bundleTitle.length > 0 ? $bundleTitle.text() : 'Keys'}`;
bundleSitesBoxHandler.export(extractKeys(), title);
});
},
fanatical() {
/*
const cache = siteCache.fanatical;
const selectGames = (selector) => {
let $results = $();
let from = parseInt($('.SBSE_container .selectFrom').val(), 10);
let to = parseInt($('.SBSE_container .selectTo').val(), 10);
if ($.isNumeric(from) && $.isNumeric(to)) {
if (from === 0 && to > 0) from = 1;
if (from > 0 && to === 0) to = cache.doms.length - 1;
for (let i = Math.min(from, to); i <= Math.max(from, to); i += 1) {
$results = $results.add(
$(cache.doms[i]).find(selector),
);
}
}
return $results;
};*/
const extractKeys = () => {
const keys = [];
$('.account-content dl input').each((index, input) => {
const $input = $(input);
keys.push({
key: $input.val(),
title: $input.closest('dd').prev().text().trim()
});
});
return keys;
};
const insertBox = () => {
const $anchor = $('h3:contains(Order Keys)');
if ($('.SBSE_container').length === 0 && $anchor.length > 0) {
// insert textarea
$anchor.eq(0).before(bundleSitesBox());
// inject css
GM_addStyle(`
.SBSE_container { margin-top: 10px; }
.SBSE_container > textarea { background-color: #434343; color: #eee; }
.SBSE_container > div > button, .SBSE_container > div > a { width: 80px; }
.SBSE_container > div > button, .SBSE_container select, .SBSE_container > div > a { border: 1px solid transparent; background-color: #1c1c1c; color: #eee; }
.SBSE_container > div > button:hover, .SBSE_container select:hover, .SBSE_container > div > a:hover { color: #A8A8A8; }
.SBSE_container > div > a { text-decoration: none; }
.SBSE_container label { color: #DEDEDE; }
.SBSE_container select { max-width:120px; height: 30px; }
.SBSE_container select, .SBSE_container span { margin-right: 0; margin-left: 10px; float: right; }
.SBSE_container span { margin-top: 5px; }
`);
// narrow buttons
$('.SBSE_container > div > button').addClass('narrow');
// dodge from master css selector
$('.SBSE_container > div > a').attr('href', '');
/*
// insert bundlestars select
$('.SBSE_container > div').append(`
<select class="selectTo"></select>
<span>${text.selectConnector}</span>
<select class="selectFrom"></select>
`);
*/
// button click
$('.SBSE_BtnReveal').click(() => {
const handler = ($games, callback) => {
const game = $games.shift();
if (game) {
game.click();
setTimeout(handler.bind(null, $games, callback), 300);
} else setTimeout(callback, 500);
};
bundleSitesBoxHandler.reveal(handler, $('.account-content dl button'));
});
$('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$('.SBSE_BtnExport').click(() => {
const $bundleTitle = $('h5');
const title = `Fanatical - ${$bundleTitle.length > 0 ? $bundleTitle.text() : 'Keys'}`;
bundleSitesBoxHandler.export(extractKeys(), title);
});
/*
// setup select
const $selects = $('.SBSE_container select');
$selects.empty();
$selects.append(new Option('All', 0));
// individual games
$anchor.parent().children('dl').each((index, dl) => {
$selects.append(
new Option($(dl).children().eq(1).text(), cache.doms.push(dl) - 1),
);
});
// bundles
$anchor.parent().find('h5').each((index, bundle) => {
const $bundle = $(bundle);
const $tiers = $bundle.parent().find('h6');
const bundleTitle = $bundle.text();
if ($tiers.length > 0) {
$tiers.each((i, tier) => {
const tierTitle = $(tier).text().trim();
$selects.append(
new Option(`${bundleTitle} ${tierTitle}`, cache.doms.push(tier.parentNode) - 1),
);
});
} else {
$selects.append(
new Option(bundleTitle, cache.doms.push(bundle.parentNode) - 1),
);
}
});*/
}
};
new MutationObserver(mutations => {
mutations.forEach(mutation => {
Array.from(mutation.addedNodes).forEach(addedNode => {
if (addedNode.tagName === 'META' && addedNode.name === 'twitter:title') insertBox();
});
});
}).observe($('head')[0], { childList: true });
},
bundlestars(firstCalled) {
const cache = siteCache.bundlestars;
const $anchor = $('h2:contains(Order Keys)');
const BSselect = selector => {
let $results = $();
let from = parseInt($('.SBSE_container .selectFrom').val(), 10);
let to = parseInt($('.SBSE_container .selectTo').val(), 10);
if ($.isNumeric(from) && $.isNumeric(to)) {
if (from === 0 && to > 0) from = 1;
if (from > 0 && to === 0) to = cache.doms.length - 1;
for (let i = Math.min(from, to); i <= Math.max(from, to); i += 1) {
$results = $results.add($(cache.doms[i]).find(selector));
}
}
return $results;
};
const extractKeys = () => {
const keys = [];
BSselect('.key-container input').each((index, input) => {
const $input = $(input);
keys.push({
key: $input.val(),
title: $input.closest('.key-container').prev().text().trim()
});
});
return keys;
};
if ($('.SBSE_container').length === 0 && $anchor.length > 0) {
// insert textarea
$anchor.eq(0).before(bundleSitesBox());
// inject css
GM_addStyle(`
.SBSE_container { border: 1px solid #424242; color: #999999; }
.SBSE_container > textarea { background-color: #303030; color: #DDD; }
.SBSE_container > div > button, .SBSE_container > div > a { width: 80px; }
.SBSE_container > div > button, .SBSE_container select, .SBSE_container > div > a { border: 1px solid transparent; background-color: #262626; color: #DEDEDE; }
.SBSE_container > div > button:hover, .SBSE_container select:hover, .SBSE_container > div > a:hover { color: #A8A8A8; }
.SBSE_container > div > a { text-decoration: none; }
.SBSE_container label { color: #DEDEDE; }
.SBSE_container select { max-width:120px; height: 30px; }
.SBSE_container select, .SBSE_container span { margin-right: 0; margin-left: 10px; float: right; }
.SBSE_container span { margin-top: 5px; }
`);
// narrow buttons
$('.SBSE_container > div > button').addClass('narrow');
// insert bundlestars select
$('.SBSE_container > div').append(`
<select class="selectTo"></select>
<span>${text.selectConnector}</span>
<select class="selectFrom"></select>
`);
// button click
$('.SBSE_BtnReveal').click(() => {
const handler = ($games, callback) => {
const game = $games.shift();
if (game) {
if (!game.closest('.ng-hide')) {
game.click();
setTimeout(handler.bind(null, $games, callback), 300);
} else handler($games, callback);
} else setTimeout(callback, 500);
};
bundleSitesBoxHandler.reveal(handler, BSselect('.key-container a[ng-click^="redeemSerial"]'));
});
$('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$('.SBSE_BtnExport').click(() => {
const $bundleTitle = $('h3.bundle');
const title = `BundleStars - ${$bundleTitle.length > 0 ? $bundleTitle.text() : 'Keys'}`;
bundleSitesBoxHandler.export(extractKeys(), title);
});
}
// setup select
const $selects = $('.SBSE_container select');
$selects.empty();
$selects.append(new Option('All', 0));
cache.doms = [document];
$('hr ~ div > div:not(.ng-hide)').each((index, block) => {
const $block = $(block);
const $bundle = $block.find('h3');
const $tiers = $block.find('h4');
if ($tiers.length > 1) {
// bundles with multiple tiers
$tiers.each((i, tier) => {
const $tier = $(tier);
$selects.append(new Option(`${$bundle.text()} ${$tier.text()}`, cache.doms.push($tier.parent()) - 1));
});
} else if ($bundle.length > 0) {
// bundles with single tier
$selects.append(new Option($bundle.text(), cache.doms.push($bundle.next()) - 1));
} else {
// individual games
$selects.append(new Option($block.find('.title').text(), cache.doms.push($block) - 1));
}
});
if (firstCalled) {
new MutationObserver(mutations => {
mutations.forEach(mutation => {
Array.from(mutation.removedNodes).forEach(removedNode => {
if (removedNode.id === 'loading-bar-spinner') siteHandlers.bundlestars();
});
});
}).observe(document.body, { childList: true });
}
},
humblebundle() {
let atDownload = true;
const $box = bundleSitesBox();
const fetchKey = ($node, machineName, callback) => {
fetch('https://www.humblebundle.com/humbler/redeemkey', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: 'https://www.humblebundle.com',
Referer: location.href
},
body: `gift=false&keytype=${machineName}&key=${unsafeWindow.gamekeys[0]}&keyindex=0`,
credentials: 'same-origin'
}).then(res => {
if (res.ok) return res.json();
throw new Error('Network response was not ok.');
}).then(d => {
if (d.success) {
$node.closest('.sr-unredeemed').replaceWith(`
<div class="sr-redeemed">
<div class="sr-redeemed">
<div class="sr-redeemed-bubble js-sr-redeemed-bubble ">
<div class="keyfield-text">${d.key}</div>
<a class="steam-redeem-button" href="https://store.steampowered.com/account/registerkey?key=${d.key}" target="_blank">
<div class="steam-redeem-text">Redeem</div>
<span class="tooltiptext">Redeem on Steam</span>
</a>
</div>
</div>
</div>
`);
} else swal(text.failTitle, JSON.stringify(d), 'error');
if (typeof callback === 'function') callback();
});
};
const extractKeys = () => {
const skipOwned = !!$('.SBSE_ChkSkipOwned:checked').length;
const keys = [];
const selectors = ['.sr-redeemed-bubble .keyfield-text', // redeem page selector
'tr:has(.hb-steam) .redeemed > .keyfield-value'];
if (skipOwned) selectors[0] = `div:not(.SBSE_owned) > .sr-key ${selectors[0]}`;
$(selectors.join()).each((index, element) => {
const $game = $(element);
const $heading = atDownload ? $game.closest('[class^=sr-key]').prev().children().eq(0) : $game.closest('td').prev('.game-name').find('h4');
keys.push({
key: $game.text().trim(),
title: $heading.text().trim()
});
});
return keys;
};
const $keyManager = $('.js-key-manager-holder');
// insert textarea
// insert at /home/*
if ($keyManager.length > 0) {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
Array.from(mutation.addedNodes).forEach(addedNode => {
if (addedNode.className === 'header') {
atDownload = false;
$(addedNode).after($box);
$box.find('.SBSE_ChkSkipOwned').parent().remove();
observer.disconnect();
}
});
});
});
observer.observe($keyManager[0], { childList: true });
// insert at download page
} else {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
Array.from(mutation.addedNodes).forEach(addedNode => {
const $node = $(addedNode);
if ($node.hasClass('sr-widget') && $node.closest('.keytab').length > 0) {
$node.closest('.whitebox-redux').before($box);
// fetch key data
const $script = $('.steam-keyredeemer-container').next();
if ($script.length > 0) {
let data = $script.text().split('var data = ').pop().split(';').shift();
try {
data = JSON.parse(data);
data.keys.forEach(key => {
keyDetails.set(key.redeemedKeyVal, {
app: key.steamAppId,
title: key.humanName
});
const $heading = $node.find(`.sr-key-heading:has(span[data-machine-name=${key.machineName}])`);
// apply owned effect on game title
const appID = parseInt(key.steamAppId, 10);
if (appID && owned.app.includes(appID)) $heading.parent().parent().addClass('SBSE_owned');
// activation restrictions
let html = '';
const disallowed = key.disallowedCountries.map(c => ISO2.get(c));
const exclusive = key.exclusiveCountries.map(c => ISO2.get(c));
const humanName = $heading.text().trim();
const separator = config.get('language').includes('chinese') ? '、' : ', ';
if (disallowed.length > 0) html += `<p>${text.HBDisallowedCountries}<br>${disallowed.join(separator)}</p>`;
if (exclusive.length > 0) html += `<p>${text.HBExclusiveCountries}<br>${exclusive.join(separator)}</p>`;
if (disallowed.length > 0 || exclusive.length > 0) {
$(`<span class="SBSE_activationRestrictions">${text.HBActivationRestrictions}</span>`).click(() => {
swal({
title: `${humanName}<br>${text.HBActivationRestrictions}`,
html,
type: 'info'
});
}).insertBefore($heading.parent().parent());
}
});
} catch (e) {
// no key details
}
}
observer.disconnect();
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// inject css
GM_addStyle(`
.SBSE_container > div { position: relative; }
.SBSE_container > textarea {
border: 1px solid #CFCFCF;
color: #4a4c45;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
border-radius: 5px;
}
.SBSE_container > div > button, .SBSE_container > div > a {
width: 70px;
border: 1px solid #C9CCD3;
border-radius: 3px;
background-color: #C5C5C5;
background: linear-gradient(to top, #cacaca, #e7e7e7);
color: #4a4c45 !important;
}
.SBSE_container > div > button:hover, .SBSE_container > div > a:hover {
border: 1px solid #b7bac0;
background-color: #fafcff;
color: #555961 !important;
}
.SBSE_container > div > button.narrow.working { width: 76px; padding-right: 36px; }
#SBSE_BtnSettings { position: absolute; right: 0; }
.SBSE_owned .sr-unredeemed-steam-button {
background-color: #F3F3F3;
background: linear-gradient(to top, #E8E8E8, #F6F6F6);
}
.SBSE_owned .sr-unredeemed-steam-button * { opacity: 0.37; }
.SBSE_owned .sr-key-heading > span:last-child::after {
content: '\\f085';
font-family: hb-icons;
color: #17A1E5;
}
.SBSE_activationRestrictions {
float: right;
cursor: pointer;
}
`);
// narrow buttons
$box.find('div > button').addClass('narrow');
// append checkbox for owned game
$box.find('#SBSE_BtnSettings').before($(`<label><input type="checkbox" class="SBSE_ChkSkipOwned" checked>${text.checkboxSkipOwned}</label>`));
// button click
$box.find('.SBSE_BtnReveal').click(() => {
const skipOwned = !!$('.SBSE_ChkSkipOwned:checked').length;
const handler = ($games, callback) => {
const game = $games.shift();
if (game) {
const $game = $(game);
const isOwned = !!$game.closest('.SBSE_owned').length;
const machineName = $game.closest('.sr-key').find('.js-admin-edit').data('machine-name');
if (skipOwned && isOwned) handler($games, callback);else if (atDownload && machineName) {
fetchKey($game, machineName, () => {
handler($games, callback);
});
} else {
game.click();
$('.sr-warning-modal-confirm-button').click();
setTimeout(handler.bind(null, $games, callback), 200);
}
} else callback();
};
const selectors = ['.sr-unredeemed-steam-button', // redeem page selector
'div.keyfield[title="Reveal your Steam key"]'];
if (skipOwned) selectors[0] = `div:not(.SBSE_owned) > .sr-key ${selectors[0]}`;
bundleSitesBoxHandler.reveal(handler, $(selectors.join()));
});
$box.find('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$box.find('.SBSE_BtnExport').click(() => {
const $bundleTitle = $('meta[name=title]');
const title = `Humble Bundle - ${$bundleTitle.length > 0 ? $bundleTitle.attr('content') : 'Keys'}`;
bundleSitesBoxHandler.export(extractKeys(), title);
});
// override default popups
document.addEventListener('click', e => {
const $target = $(e.target).closest('.sr-unredeemed-steam-button');
if ($target.length > 0) {
e.stopPropagation();
const $heading = $target.closest('.sr-key').find('.sr-key-heading');
const machineName = $heading.find('.js-admin-edit').data('machine-name');
const humanName = $heading.text().trim();
const isOwned = !!$target.closest('.SBSE_owned').length;
if (machineName) {
if (isOwned) {
swal({
title: text.HBAlreadyOwned,
text: text.HBRedeemAlreadyOwned.replace('%title%', humanName),
type: 'question',
showCancelButton: true
}).then(result => {
if (result.value) fetchKey($target, machineName);
});
} else fetchKey($target, machineName);
}
}
}, true);
},
dailyindiegame() {
const MPHideList = JSON.parse(GM_getValue('SBSE_DIGMPHideList') || '[]');
const pathname = location.pathname;
if (pathname.includes('/account_page')) {
// inject css
GM_addStyle(`
.SBSE_container { padding: 5px; border: 1px solid #424242; }
.SBSE_container > textarea { border: 1px solid #000; }
.SBSE_container > div > button {
border: none;
background-color: #FD5E0F;
color: rgb(49, 49, 49);
font-family: Ropa Sans;
font-size: 15px;
font-weight: 600;
}
`);
const extractKeys = () => {
const keys = [];
const marketListings = !!$('.SBSE_ChkMarketListings:checked').length;
$('#TableKeys tr').each((index, tr) => {
const $tds = $(tr).children();
if ($tds.eq(4).text().includes('-') && (!$tds.eq(6).text().includes('Cancel trade') || marketListings)) {
keys.push({
key: $tds.eq(4).text().trim(),
title: $tds.eq(2).text().trim()
});
}
});
return keys;
};
// insert textarea
const $box = bundleSitesBox();
$('#TableKeys').eq(0).before($box);
// append checkbox for market keys
$box.find('#SBSE_BtnSettings').before($(`<label><input type="checkbox" class="SBSE_ChkMarketListings">${text.checkboxMarketListings}</label>`));
// button click
$('.SBSE_BtnReveal').click(() => {
const handler = () => {
const $form = $('#form3');
$('.quickaction').val(1);
$.ajax({
method: 'POST',
url: $form.attr('action'),
data: $form.serializeArray(),
success() {
location.reload();
}
});
};
bundleSitesBoxHandler.reveal(handler);
});
$('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$('.SBSE_BtnExport').remove();
// rate all positive
const $awaitRatings = $('a[href^="account_page_0_ratepositive"]');
if ($awaitRatings.length > 0) {
$('#TableKeys td:contains(Rate TRADE)').text(text.DIGRateAllPositive).css('cursor', 'pointer').click(() => {
$awaitRatings.each((index, a) => {
fetch(a.href, {
method: 'GET',
credentials: 'same-origin'
}).then(res => {
if (res.ok) {
$(a).parent('td').html('<span class="DIG3_14_Orange">Positive</span>');
}
});
});
});
}
} else if (pathname === '/account_digstore.html' || pathname === '/account_trades.html' || pathname === '/account_tradesXT.html') {
// DIG EasyBuy
GM_addStyle(`
.DIGEasyBuy_row { height: 30px; }
.DIGEasyBuy button { padding: 4px 8px; outline: none; cursor: pointer; }
.DIGEasyBuy_checked { background-color: #222; }
.DIGEasyBuy_hideOwned tr.SBSE_hide { display: none; }
.DIGEasyBuy_hideOwned tr.SBSE_hide + .SBSE_searchResults { display: none; }
.SBSE_searchResults td { padding: 0 }
.SBSE_searchResults iframe {
width: 100%;
height: 300px;
display: none;
background-color: white;
border: none;
}
`);
// setup row data & event
const easyBuySetup = (i, element) => {
const $game = $(element);
const $row = $game.closest('tr');
$row.attr('data-id', $game.attr('href').replace(/\D/g, ''));
$row.attr('data-price', parseInt($row.find('td:contains(DIG Points)').text(), 10) || 0);
$row.click(() => {
$row.toggleClass('DIGEasyBuy_checked');
});
$row.addClass('DIGEasyBuy_row');
};
$('a[href^="account_buy"]').each(easyBuySetup);
// check if owned / manually hid
const check = (i, a) => {
const $a = $(a);
const $tr = $a.closest('tr');
const data = a.pathname.slice(1).split('/');
const appID = parseInt(data[1], 10);
const id = parseInt($tr.attr('data-id'), 10);
if (owned[data[0]].includes(appID)) $tr.addClass('SBSE_owned SBSE_hide');
if (MPHideList.includes(id)) $tr.addClass('SBSE_hide');
// append manual hide feature
$tr.children().eq(0).attr('title', text.DIGClickToHideThisRow).click(e => {
e.stopPropagation();
if (id > 0) {
MPHideList.push(id);
GM_setValue('SBSE_DIGMPHideList', JSON.stringify(MPHideList));
$tr.addClass('SBSE_hide');
}
});
// no appID found, pre-load Google search result
if (appID === -1 && !MPHideList.includes(id)) {
const $game = $a.find('span');
const gameTitle = encodeURIComponent($game.text().trim()).replace(/%20/g, '+');
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
GM_xmlhttpRequest({
method: 'GET',
url: `https://www.google.com/search?q=steam+${gameTitle}`,
onload: res => {
let html = res.responseText;
// inset style
const index = html.indexOf('</head>');
const style = `
<style>
body { overflow-x: hidden; }
.sfbgx, #sfcnt, #searchform, #top_nav, #appbar, #taw { display: none; }
#center_col { margin-left: 0 !important; }
</style>
`;
html = html.slice(0, index) + style + html.slice(index);
// stripe script tags
html = html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '');
// manipulate urls
html = html.replace(/\/images\//g, 'https://www.google.com/images/').replace(/\/url\?/g, 'https://www.google.com/url?');
$tr.after(`
<tr class="SBSE_searchResults">
<td colspan="11"><iframe sandbox="allow-scripts" srcdoc='${html.replace(/[&<>"']/g, m => map[m])}'></frame></td>
</tr>
`);
}
});
$game.unwrap('a').css({
cursor: 'pointer',
color: 'red'
}).click(e => {
e.stopPropagation();
$tr.next('.SBSE_searchResults').find('iframe').slideToggle('fast');
});
}
};
$('tr a[href*="steampowered"]').each(check);
// append menu buttons
const $target = $('#form3').closest('tr').children().eq(0);
const $DIGEasyBuy = $(`
<div class="DIGEasyBuy">
<button class="DIGButtonPurchase DIG3_Orange_15_Form">${text.DIGEasyBuyPurchase}</button>
<button class="DIGButtonSelectAll DIG3_Orange_15_Form">${text.DIGEasyBuySelectAll}</button>
<button class="DIGButtonHideOwned DIG3_Orange_15_Form">${text.DIGEasyBuyHideOwned}</button>
</div>
`);
if ($target.children().length > 0) {
const $tr = $('<tr/>');
$tr.append($target.clone());
$target.parent().before($tr);
}
$target.empty().append($DIGEasyBuy);
if (pathname === '/account_tradesXT.html') {
$DIGEasyBuy.append(`
<button class="DIGButtonLoadAllPages DIG3_Orange_15_Form">${text.DIGEasyBuyLoadAllPages}</button>
`);
}
// bind button event
$('.DIGButtonPurchase').click(e => {
let bought = 0;
let balance = parseFloat($('a[href^="account_transac"]').parent('div').text().slice(19)) || 0;
const $self = $(e.delegateTarget);
const $checked = $('.DIGEasyBuy_checked');
const handler = callback => {
const item = $checked.shift();
if (item) {
const $item = $(item);
const id = $item.data('id');
const price = parseInt($item.data('price'), 10);
if (id && price > 0) {
if (balance - price > 0) {
let url = `${location.origin}/account_buy.html`;
const requestInit = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `quantity=1&xgameid=${id}&xgameprice1=${price}&send=Purchase`,
mode: 'same-origin',
credentials: 'same-origin',
cache: 'no-store',
referrer: `${location.origin}/account_buy_${id}.html`
};
if (pathname === '/account_trades.html' || pathname === '/account_tradesXT.html') {
url = `${location.origin}/account_buytrade_${id}.html`;
requestInit.body = `gameid=${id}&send=Purchase`;
requestInit.referrer = url;
}
fetch(url, requestInit).then(res => {
if (res.ok) {
$item.click();
bought += 1;
balance -= price;
setTimeout(handler.bind(null, callback), 300);
} else handler(callback);
});
} else {
swal({
title: text.failTitle,
text: text.DIGInsufficientFund,
type: 'error'
}).then(() => {
window.location = `${location.origin}/account_page.html`;
});
}
} else handler(callback);
} else callback();
};
$self.prop('disabled', true).text(text.DIGButtonPurchasing);
handler(() => {
if (bought) window.location = `${location.origin}/account_page.html`;else $self.prop('disabled', false).text(text.DIGButtonPurchase);
});
});
$('.DIGButtonSelectAll').click(e => {
const $self = $(e.delegateTarget);
const state = !$self.data('state');
$('.DIGEasyBuy_row').toggleClass('DIGEasyBuy_checked', state);
$self.data('state', state);
$self.text(state ? text.DIGEasyBuySelectCancel : text.DIGEasyBuySelectAll);
});
$('.DIGButtonHideOwned').click(e => {
const $self = $(e.delegateTarget);
const state = !$self.data('state');
$('#TableKeys').toggleClass('DIGEasyBuy_hideOwned', state);
$self.data('state', state);
$self.text(state ? text.DIGEasyBuyShowOwned : text.DIGEasyBuyHideOwned);
});
$('.DIGButtonLoadAllPages').click(e => {
// auto load all pages at marketplace
const $self = $(e.delegateTarget);
const $tbody = $('#TableKeys > tbody');
const load = (page, retry = 0) => {
$self.text(text.DIGEasyBuyLoading.replace('%page%', page));
fetch(`${location.origin}/account_tradesXT_${page}.html`, {
method: 'GET',
credentials: 'same-origin'
}).then(res => {
if (res.ok) return res.text();
throw new Error('Network response was not ok.');
}).then(html => {
const $result = $(html).find('#TableKeys tr.DIG3_14_Gray');
if ($result.length > 0) {
$result.find('a[href^="account_buy"]').each(easyBuySetup);
$result.find('a[href*="steampowered"]').each(check);
$tbody.append($result);
load(page + 1);
} else $self.text(text.DIGEasyBuyLoadingComplete);
}).catch(() => {
if (retry < 3) load(page, retry + 1);else load(page + 1);
});
};
load(1);
$self.prop('disabled', true);
});
// extension for creating trade at market place
} else if (pathname === '/account_createtrade.html') {
const $form = $('#form_createtrade');
// create trade page
if ($form.length > 0) {
// trim input field
const $gameTitle = $form.find('input[name="typeahead"]');
const $steamKey = $form.find('input[name="STEAMkey"]');
$gameTitle.blur(() => {
unsafeWindow.jQuery('input.typeahead').typeahead('setQuery', $gameTitle.val().trim());
});
$steamKey.blur(e => {
const $self = $(e.delegateTarget);
const key = $self.val().match(regKey);
if (key) $self.val(key[0]);
});
$steamKey.attr({
size: 50,
maxlength: 50
});
// search for current market price when click dropdown menu
const $searchResult = $('<div/>');
$gameTitle.closest('table').after($searchResult);
$searchResult.before(`<h3>${text.DIGMarketSearchResult}</h3>`);
$('.tt-dropdown-menu').click(() => {
$searchResult.empty();
fetch(`${location.origin}/account_tradesXT.html`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `search=${encodeURIComponent($gameTitle.val()).replace(/%20/g, '+')}&button=SEARCH`,
credentials: 'same-origin'
}).then(res => {
if (res.ok) return res.text();
throw new Error('Network response was not ok.');
}).then(html => {
$searchResult.append($(html).find('#TableKeys'));
});
});
// result page
} else {
GM_addStyle(`
.check.icon {
color: #5cb85c;
margin: 12px 0 5px 9px;
width: 42px;
height: 24px;
border-bottom: solid 3px currentColor;
border-left: solid 3px currentColor;
transform: rotate(-45deg);
}
.remove.icon { color: #d9534f; margin-left: 9px; margin-top: 30px; }
.remove.icon:before, .remove.icon:after {
content: '';
position: absolute;
width: 45px;
height: 3px;
background-color: currentColor;
transform: rotate(45deg);
}
.remove.icon:after { transform: rotate(-45deg); }
`);
const $anchor = $('td.DIG3_14_Gray > table:first-child');
const IsSucceed = !!$('td.DIG3_14_Gray:contains("The game key has been added to the DIG MarketPlace.")').length;
if (IsSucceed) $anchor.after('<div class="check icon"></div>');else $anchor.after('<div class="remove icon"></div>');
}
}
},
ccyycn() {
// insert textarea
$('.featurette-divider').eq(0).after(bundleSitesBox());
// inject css
GM_addStyle(`
.SBSE_container {
width: 80%;
margin: 0 auto;
color: #000;
font-size: 16px;
}
.SBSE_container > textarea {
background-color: #EEE;
box-shadow: 0 0 1px 1px rgba(204,204,204,0.5);
border-radius: 5px;
}
.SBSE_container > div { text-align: left; }
.SBSE_container > div > button, .SBSE_container > div > a {
width: 80px;
border: 1px solid #2e6da4;
border-radius: 5px;
background-color: #337ab7;
color: #FFF;
}
.SBSE_container > div > a:hover { text-decoration: none; opacity: 0.9; }
.SBSE_container label { color: #EEE; }
.expanded .showOrderMeta {
display: block !important;
position: absolute;
margin-top: -8px;
right: 265px;
z-index: 1;
}
`);
// narrow buttons
$('.SBSE_container > div > button').addClass('narrow');
const extractKeys = () => {
const keys = [];
$('.deliver-gkey:contains(-)').each((index, element) => {
const $game = $(element);
keys.push({
key: $game.text().trim(),
title: $game.parent().prev().text().trim()
});
});
return keys;
};
// button click
$('.SBSE_BtnReveal').click(() => {
const handler = ($games, callback) => {
const game = $games.shift();
if (game) {
game.click();
setTimeout(handler.bind(null, $games, callback), 300);
} else callback();
};
bundleSitesBoxHandler.reveal(handler, $('.deliver-btn'));
});
$('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$('.SBSE_BtnExport').click(() => {
const bundleTitle = 'CCYYCN Bundle'; // can't find bundle title in html
bundleSitesBoxHandler.export(extractKeys(), bundleTitle);
});
},
groupees() {
if (location.pathname.startsWith('/profile/')) {
// insert textarea
$('.table-products').before(bundleSitesBox());
// inject css
GM_addStyle(`
.SBSE_container > textarea, .SBSE_container > div > button, .SBSE_container > div > a {
background: transparent;
border: 1px solid #8cc53f;
border-radius: 3px;
color: #8cc53f;
transition: all 0.8s ease;
}
.SBSE_container > div > button:hover, .SBSE_container > div > a:hover {
background-color: #8cc53f;
color: white;
text-decoration: none;
}
img.product-cover { display: none; }
`);
// load details
$('img[src*="steam.svg"]').each(async (index, ele) => {
$.ajax({
url: $(ele).closest('tr').find('.item-link').attr('href'),
data: { v: 1 },
dataType: 'script'
});
});
const extractKeys = () => {
const skipUsed = !!$('.SBSE_ChkSkipUsed:checked').length;
const keys = [];
$('.key-block input.code').each((index, element) => {
const $game = $(element);
const used = !!$game.closest('.key-block').find('.key-status:contains(used)').length;
if ($game.val().includes('-') && (!used || used && !skipUsed)) {
keys.push({
key: $game.val(),
title: $game.closest('tr').prev().children('td').eq(2).text().trim()
});
}
});
return keys;
};
// append checkbox for used-key
$('#SBSE_BtnSettings').before($(`<label><input type="checkbox" class="SBSE_ChkSkipUsed" checked>${text.checkboxSkipUsed}</label>`));
// button click
$('.SBSE_BtnReveal').click(() => {
const handler = ($games, callback) => {
const game = $games.shift();
if (game) {
game.click();
setTimeout(handler.bind(null, $games, callback), 300);
} else callback();
};
const $reveals = $('.product:has(img[title*=Steam]) .reveal-product');
const timer = $reveals.length > 0 ? 1500 : 0;
$reveals.click();
setTimeout(() => {
bundleSitesBoxHandler.reveal(handler, $('.btn-reveal-key'));
}, timer);
});
$('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$('.SBSE_BtnExport').click(() => {
const bundleTitle = `Groupees - ${$('h2').text()}`;
bundleSitesBoxHandler.export(extractKeys(), bundleTitle);
});
// bind custom event
$(document).on('activated', (e, key, result) => {
if (result.success === 1) $(`.btn-steam-redeem[href*=${key}]`).next('.key-usage-toggler').click();
});
} else {
// insert textarea
$('.container > div').eq(1).before(bundleSitesBox());
// inject css
GM_addStyle(`
.SBSE_container { margin-bottom: 20px; }
.SBSE_container > textarea { background-color: #EEE; border-radius: 3px; }
.SBSE_container > div > button, .SBSE_container > div > a { outline: none !important; }
#SBSE_BtnSettings { margin-top: 8px; }
`);
// append checkbox for used-key
$('#SBSE_BtnSettings').before($(`<label><input type="checkbox" class="SBSE_ChkSkipUsed" checked>${text.checkboxSkipUsed}</label>`));
// add buttons style via groupees's class
$('.SBSE_container > div > button, .SBSE_container > div > a').addClass('btn btn-default');
// append mark all as used button
new MutationObserver(mutations => {
mutations.forEach(mutation => {
Array.from(mutation.addedNodes).forEach(addedNode => {
const $orderMeta = $(addedNode).find('.order-meta');
if ($orderMeta.length > 0) {
$orderMeta.after($(`<button class="btn btn-default" style="margin-right: 10px;"><b>${text.markAllAsUsed}</b></button>`).click(() => {
$('.expanded .usage').each((i, checkbox) => {
if (!checkbox.checked) checkbox.click();
});
}));
$orderMeta.parent().addClass('showOrderMeta');
}
});
});
}).observe($('#profile_content')[0], { childList: true });
const extractKeys = () => {
const skipUsed = !!$('.SBSE_ChkSkipUsed:checked').length;
const keys = [];
$('.expanded .code').each((index, element) => {
const $game = $(element);
const used = $game.closest('li').find('.usage').prop('checked');
if (!used || used && !skipUsed) {
keys.push({
key: $game.val(),
title: $game.closest('.details').find('h3').text().trim()
});
}
});
return keys;
};
// button click
$('.SBSE_BtnReveal').click(() => {
const handler = ($games, callback) => {
const game = $games.shift();
if (game) {
game.click();
setTimeout(handler.bind(null, $games, callback), 300);
} else callback();
};
const $reveals = $('.product:has(img[title*=Steam]) .reveal-product');
const timer = $reveals.length > 0 ? 1500 : 0;
$reveals.click();
setTimeout(() => {
bundleSitesBoxHandler.reveal(handler, $('.expanded .reveal'));
}, timer);
});
$('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(extractKeys());
});
$('.SBSE_BtnExport').click(() => {
const bundleTitle = `Groupees - ${$('.expanded .caption').text()}`;
bundleSitesBoxHandler.export(extractKeys(), bundleTitle);
});
// bind custom event
$(document).on('activated', (e, key, result) => {
if (result.success === 1) $(`li.key:has(input[value=${key}]) .usage`).click();
});
}
},
agiso() {
const keys = unique($('body').text().match(regKey));
if (keys.length > 0) {
// insert textarea
$('#tabs').eq(0).prepend(bundleSitesBox());
// inject css
GM_addStyle(`
.SBSE_container > textarea { border: 1px solid #AAAAAA; }
.SBSE_container > div > button, .SBSE_container > div > a {
border: 1px solid #d3d3d3;
background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;
color: #555555;
}
.SBSE_container > div > button:hover, .SBSE_container > div > a:hover {
border-color: #999999;
background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;
color: #212121;
}
#SBSE_BtnSettings { width: 32px !important; height: 32px !important; }
`);
// remove event from agiso
$('.SBSE_container > div > button').click(e => {
e.preventDefault();
});
// hide reveal
$('.SBSE_BtnReveal').hide();
// button click
$('.SBSE_BtnRetrieve').click(() => {
bundleSitesBoxHandler.retrieve(keys);
});
$('.SBSE_BtnExport').click(() => {
const bundleTitle = 'agiso Bundle';
bundleSitesBoxHandler.export(keys, bundleTitle);
});
}
}
};
const init = () => {
if (location.hostname === 'store.steampowered.com') {
// save sessionID
if (unsafeWindow.g_AccountID > 0) {
const currentID = config.get('sessionID');
const sessionID = unsafeWindow.g_sessionID || '';
const language = unsafeWindow.g_oSuggestParams.l || 'english';
if (!config.get('language')) config.set('language', language);
if (sessionID.length > 0) {
const update = config.get('autoUpdateSessionID') && currentID !== sessionID;
if (!currentID || update) {
config.set('sessionID', sessionID, () => {
swal({
title: text.updateSuccessTitle,
text: text.updateSuccess,
type: 'success',
timer: 3000
});
});
}
}
}
/* else {
swal(text.notLoggedInTitle, text.notLoggedInMsg, 'error');
} */
} else {
const site = location.hostname.replace(/(www|alds|bundle)\./, '').split('.').shift();
// check sessionID
if (!config.get('sessionID')) getSessionID();
if (has.call(siteHandlers, site)) {
siteHandlers[site](true);
// update owned every 10 min
const updateTimer = 10 * 60 * 1000;
if (!owned.lastSync || owned.lastSync < Date.now() - updateTimer) syncLibrary(false);
}
}
};
$(init);