// ==UserScript==
// @name Steam 令牌验证器
// @namespace https://keylol.com/t652195-1-1
// @version 0.7.2
// @description 支持自动填写 Steam 令牌验证码和批量确认交易与市场
// @author wave
// @match http*://store.steampowered.com/*
// @match http*://help.steampowered.com/*
// @match http*://steamcommunity.com/*
// @match http*://checkout.steampowered.com/*
// @match *://keylol.com/steamconf*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant GM_addStyle
// @grant GM_addValueChangeListener
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @connect steampowered.com
// @connect steamcommunity.com
// @require https://bundle.run/[email protected]
// @require https://unpkg.com/crypto-js/crypto-js.js
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGJhc2VQcm9maWxlPSJ0aW55IiB2aWV3Qm94PSIwIDAgMzIgMzIiICB4bWxuczp2PSJodHRwczovL3ZlY3RhLmlvL25hbm8iPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyLjUiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTI4LjggMzAuOEgzLjNhMS45NyAxLjk3IDAgMCAxLTEuOS0xLjlWMTAuN2ExLjk3IDEuOTcgMCAwIDEgMS45LTEuOWgyNS41YTEuOTcgMS45NyAwIDAgMSAxLjkgMS45VjI5YzAgMS0uOSAxLjgtMS45IDEuOHoiLz48cGF0aCBkPSJNMjIgOC44VjUuN2MwLTIuNC0yLjEtNC40LTQuNi00LjRoLTMuMWMtMi41IDAtNC42IDItNC42IDQuNHYzLjFIMjJ6Ii8+PC9nPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNy42IDE1LjZzMCAuMSAwIDBsLTMuOSA1LjdhNi4yOCA2LjI4IDAgMCAwLTEuOS4zYy0uMy4xLS41LjItLjcuNGwtOC42LTMuNXMtLjkgMy40IDAgNS44bDYuNyAyLjNhNC40NiA0LjQ2IDAgMCAwIDIuNiAzLjFjMi4zLjkgNC45LS4xIDUuOC0yLjMuMi0uNi40LTEuMi4zLTEuOGw1LjYtMy45aC4xYzMuNCAwIDYuMS0yLjcgNi4xLTZzLTIuNy02LTYuMS02Yy0zLjMtLjEtNiAyLjYtNiA1Ljl6bS0xIDExLjNjLS43IDEuNy0yLjcgMi41LTQuNSAxLjgtLjgtLjMtMS40LS45LTEuOC0xLjdsMS41LjdjMS4zLjUgMi44LS4xIDMuMy0xLjMuNS0xLjMtLjEtMi43LTEuNC0zLjJsLTEuNi0uN2MuOC0uMyAxLjctLjMgMi41IDBhMy4xOSAzLjE5IDAgMCAxIDEuOCAxLjhjLjYuOS42IDEuOC4yIDIuNm03LjEtNy4zYy0yLjIgMC00LjEtMS44LTQuMS00czEuOC00IDQuMS00YzIuMiAwIDQuMSAxLjggNC4xIDQtLjEgMi4yLTEuOSA0LTQuMSA0bS0yLjctNGEyLjY5IDIuNjkgMCAxIDEgNS40IDAgMi42OSAyLjY5IDAgMCAxLTIuNyAyLjdjLTEuNS0uMS0yLjctMS4yLTIuNy0yLjd6Ii8+PC9zdmc+
// ==/UserScript==
(function() {
var util = (function () {
function util() {}
util.xhr = function (xhrData) {
return new Promise(function(resolve, reject) {
if (!xhrData.xhr) {
GM_xmlhttpRequest({
method: xhrData.method || "get",
url: xhrData.url,
data: xhrData.data,
headers: xhrData.headers || {},
responseType: xhrData.type || "",
timeout: 3e5,
onload: function onload(res) {
return resolve({ response: res, body: res.response });
},
onerror: reject,
ontimeout: reject
});
} else {
var xhr = new XMLHttpRequest();
xhr.open(xhrData.method || "get", xhrData.url, true);
if (xhrData.method === "post") {xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=utf-8");}
if (xhrData.cookie) {xhr.withCredentials = true;}
xhr.responseType = xhrData.type || "";
xhr.timeout = 3e5;
if (xhrData.headers) {for (var k in xhrData.headers) {xhr.setRequestHeader(k, xhrData.headers[k]);}}
xhr.onload = function(ev) {
var evt = ev.target;
resolve({ response: evt, body: evt.response });
};
xhr.onerror = reject;
xhr.ontimeout = reject;
xhr.send(xhrData.data);
}
});
};
util.createElement = function (data) {
var node;
if (data.node) {
node = document.createElement(data.node);
if (data.content) {this.setElement({node: node, content: data.content});}
if (data.html) {node.innerHTML = data.html;}
}
return node;
};
util.setElement = function (data) {
if (data.node) {
for (let name in data.content) {data.node.setAttribute(name, data.content[name]);}
if (data.html!=undefined) {data.node.innerHTML = data.html;}
}
};
return util;
})();
function bufferizeSecret(secret) {
if (typeof secret === 'string') {
// Check if it's hex
if (secret.match(/[0-9a-f]{40}/i)) {
return buffer.Buffer.from(secret, 'hex');
} else {
// Looks like it's base64
return buffer.Buffer.from(secret, 'base64');
}
}
return secret;
}
function generateAuthCode(secret, timeOffset) {
secret = bufferizeSecret(secret);
let time = Math.floor(Date.now() / 1000) + (timeOffset || 0);
let b = buffer.Buffer.allocUnsafe(8);
b.writeUInt32BE(0, 0); // This will stop working in 2038!
b.writeUInt32BE(Math.floor(time / 30), 4);
let hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, CryptoJS.lib.WordArray.create(secret));
hmac = buffer.Buffer.from(hmac.update(CryptoJS.lib.WordArray.create(b)).finalize().toString(CryptoJS.enc.Hex), 'hex');
let start = hmac[19] & 0x0F;
hmac = hmac.slice(start, start + 4);
let fullcode = hmac.readUInt32BE(0) & 0x7FFFFFFF;
const chars = '23456789BCDFGHJKMNPQRTVWXY';
let code = '';
for (let i = 0; i < 5; i++) {
code += chars.charAt(fullcode % chars.length);
fullcode /= chars.length;
}
return code;
};
function generateConfirmationKey(identitySecret, time, tag) {
identitySecret = bufferizeSecret(identitySecret);
let dataLen = 8;
if (tag) {
if (tag.length > 32) {
dataLen += 32;
} else {
dataLen += tag.length;
}
}
let b = buffer.Buffer.allocUnsafe(dataLen);
b.writeUInt32BE(0, 0);
b.writeUInt32BE(time, 4);
if (tag) {
b.write(tag, 8);
}
let hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, CryptoJS.lib.WordArray.create(identitySecret));
return hmac.update(CryptoJS.lib.WordArray.create(b)).finalize().toString(CryptoJS.enc.Base64);
};
function getDeviceID(steamID) {
let salt = '';
return "android:" + CryptoJS.SHA1(steamID.toString() + salt).toString(CryptoJS.enc.Hex).replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12}).*$/, '$1-$2-$3-$4-$5');
};
function generateConfirmationQueryParams(account, tag, timeOffset) {
var time = Math.floor(Date.now() / 1000) + (timeOffset || 0);
var key = generateConfirmationKey(account.identitySecret, time, tag);
var deviceID = getDeviceID(account.steamID);
return 'a=' + account.steamID + '&tag=' + tag + '&m=react&t=' + time + '&p=' + encodeURIComponent(deviceID) + '&k=' + encodeURIComponent(key);
}
function showAddAccountDialog(strTitle, strOKButton, strCancelButton, rgModalParams) {
if (!strOKButton) {
strOKButton = '确定';
}
if (!strCancelButton) {
strCancelButton = '取消';
}
var $Body = $J('<form/>');
var $AccountNameInput = $J('<input/>', {type: 'text', 'class': ''});
var $SharedSecretInput = $J('<input/>', {type: 'text', 'class': ''});
var $SteamIDInput = $J('<input/>', {type: 'text', 'class': ''});
var $IdentitySecretInput = $J('<input/>', {type: 'text', 'class': ''});
if (rgModalParams && rgModalParams.inputMaxSize) {
$AccountNameInput.attr('maxlength', rgModalParams.inputMaxSize);
$SharedSecretInput.attr('maxlength', rgModalParams.inputMaxSize);
$SteamIDInput.attr('maxlength', rgModalParams.inputMaxSize);
$IdentitySecretInput.attr('maxlength', rgModalParams.inputMaxSize);
}
$Body.append($J('<div/>', {'class': 'newmodal_prompt_description'}).append('Steam 帐户名称<span data-tooltip-text="非个人资料名称,用于自动填写 Steam 令牌验证码。"> (?)</span>'));
$Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($AccountNameInput));
$Body.append($J('<div/>', {'class': 'newmodal_prompt_description', 'style': 'margin-top: 8px;'}).append('共享密钥<span data-tooltip-text="即 shared secret,用于生成 Steam 令牌验证码。"> (?)</span>'));
$Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($SharedSecretInput));
$Body.append($J('<div/>', {'class': 'newmodal_prompt_description', 'style': 'margin-top: 8px;'}).append('64 位 Steam ID<span data-tooltip-text="以“7656”开头的 17 位数字,用于确认交易与市场。"> (?)</span>'));
$Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($SteamIDInput));
$Body.append($J('<div/>', {'class': 'newmodal_prompt_description', 'style': 'margin-top: 8px;'}).append('身份密钥<span data-tooltip-text="即 identity secret,用于确认交易与市场。"> (?)</span>'));
$Body.append($J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth'}).append($IdentitySecretInput));
var deferred = new jQuery.Deferred();
var fnOK = function() {
var name = $AccountNameInput.val().trim();
var secret = $SharedSecretInput.val().trim();
var steamID = $SteamIDInput.val().trim();
var identitySecret = $IdentitySecretInput.val().trim();
if (!name) {
name = '无名氏';
}
if (!secret) {
ShowAlertDialog('错误', '请输入有效的共享密钥。', '确定');
return;
}
if (steamID && steamID.indexOf('7656') != 0 && steamID.length != 17) {
ShowAlertDialog('错误', '请输入有效的 64 位 Steam ID。', '确定');
return;
}
deferred.resolve(name, secret, steamID, identitySecret);
};
var fnCancel = function() {
deferred.reject();
};
$Body.submit(function(event) {
event.preventDefault();
fnOK();
});
var $OKButton = _BuildDialogButton(strOKButton, true);
$OKButton.click(fnOK);
var $CancelButton = _BuildDialogButton(strCancelButton);
$CancelButton.click(fnCancel);
var Modal = _BuildDialog(strTitle, $Body, [$OKButton, $CancelButton], fnCancel);
if(!rgModalParams || !rgModalParams.bNoPromiseDismiss) {
deferred.always(function() {
Modal.Dismiss();
});
}
Modal.Show();
$AccountNameInput.focus();
// attach the deferred's events to the modal
deferred.promise(Modal);
return Modal;
}
function showImportAccountDialog(strTitle, strDescription, strOKButton, strCancelButton, textAreaMaxLength) {
if (!strOKButton) {
strOKButton = '确定';
}
if (!strCancelButton) {
strCancelButton = '取消';
}
var $Body = $J('<form/>');
var $TextArea = $J('<textarea/>', {'class': 'newmodal_prompt_textarea'});
$TextArea.attr('placeholder', strDescription);
if (textAreaMaxLength) {
$TextArea.attr('maxlength', textAreaMaxLength);
$TextArea.bind('keyup change', function() {
var str = $J(this).val();
var mx = parseInt($J(this).attr('maxlength'));
if (str.length > mx) {
$J(this).val(str.substr(0, mx));
return false;
}
});
}
$Body.append($J('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth'}).append($TextArea));
var deferred = new jQuery.Deferred();
var fnOK = function() {
deferred.resolve($TextArea.val());
};
var fnCancel = function() {
deferred.reject();
};
$Body.submit(function(event) {
event.preventDefault();
fnOK();
});
var $OKButton = _BuildDialogButton(strOKButton, true);
$OKButton.click(fnOK);
var $CancelButton = _BuildDialogButton(strCancelButton);
$CancelButton.click(fnCancel);
var Modal = _BuildDialog(strTitle, $Body, [$OKButton, $CancelButton], fnCancel);
deferred.always(function() {
Modal.Dismiss();
});
Modal.Show();
$TextArea.focus();
// attach the deferred's events to the modal
deferred.promise(Modal);
return Modal;
}
function addAccount() {
showAddAccountDialog('添加账户', '确定', '取消').done(function(name, secret, steamID, identitySecret) {
if (steamID && identitySecret) {
accounts.push({
name,
secret,
steamID,
identitySecret
});
GM_setValue('accounts', accounts);
ShowAlertDialog('添加账户', '添加成功,该账户支持确认交易与市场。', '确定');
} else {
accounts.push({
name,
secret
});
GM_setValue('accounts', accounts);
ShowAlertDialog('添加账户', '添加成功,该账户不支持确认交易与市场。', '确定');
}
});
setupTooltips($J('.newmodal'));
}
function importAccount() {
showImportAccountDialog('导入账户', '将要导入的数据粘贴于此', '确定', '取消').done(function(data) {
try {
data = JSON.parse(data.replace(/("SteamID":)(\d+)/, '$1"$2"'));
var name = data.account_name || '无名氏';
var secret = data.shared_secret;
var steamID = data.steamid || data.Session && data.Session.SteamID || '';
var identitySecret = data.identity_secret;
if (!secret) {
ShowAlertDialog('错误', '共享密钥不存在,请检查后再试。', '确定').done(function() {
importAccount();
});
return;
}
if (steamID && identitySecret) {
accounts.push({
name,
secret,
steamID,
identitySecret
});
GM_setValue('accounts', accounts);
ShowAlertDialog('导入账户', '导入成功,该账户支持确认交易与市场。', '确定');
} else {
accounts.push({
name,
secret
});
GM_setValue('accounts', accounts);
ShowAlertDialog('导入账户', '导入成功,该账户不支持确认交易与市场。', '确定');
}
} catch(err) {
ShowAlertDialog('错误', '数据格式有误,请检查后再试。', '确定').done(function() {
importAccount();
});
}
});
}
function deleteAccount(elem) {
ShowConfirmDialog('删除账户', '确定删除该账户吗?', '确定', '取消').done(function() {
var $Elem = $JFromIDOrElement(elem);
if ($Elem.data('id') >= accounts.length) {
ShowAlertDialog('错误', '无法删除该账户,请稍后再试。', '确定').done(function() {
window.location.reload();
});
} else {
var account = accounts.splice($Elem.data('id'), 1)[0];
GM_setValue('accounts', accounts);
ShowAlertDialog('删除账户', '删除成功。', '确定');
}
});
}
function copyAuthCode(elem) {
var $Elem = $JFromIDOrElement(elem);
GM_setClipboard(generateAuthCode(accounts[$Elem.data('id')].secret, timeOffset));
$Elem.css('width', window.getComputedStyle(elem, null).width).text('复制成功').addClass('copy_success');
setTimeout(function() {
$Elem.text($Elem.data('name')).removeClass('copy_success');
}, 1000);
}
function refreshAccounts() {
$AuthenticatorPopupMenu.empty();
$J.each(accounts, function(i, v) {
var $AuthenticatorPopupMenuItem = $J('<span/>', {'style': 'display: block; padding: 5px 0 5px 12px; margin-right: 27px; min-width: 50px;', 'data-id': i, 'data-name': v.name, 'data-tooltip-text': '点击复制该账户的验证码'}).append(v.name);
var $AuthenticatorDeleteAccount = $J('<span/>', {'class': 'delete_account', 'data-id': i, 'data-tooltip-text': '删除该账户'});
$AuthenticatorPopupMenu.append($J('<a/>', {'class': 'popup_menu_item', 'style': 'position: relative; padding: 0;'}).append($AuthenticatorPopupMenuItem, $AuthenticatorDeleteAccount));
$AuthenticatorPopupMenuItem.on('click', function() {
copyAuthCode(this);
});
$AuthenticatorDeleteAccount.on('click', function() {
deleteAccount(this);
});
});
setupTooltips($AuthenticatorPopupMenu);
var $AuthenticatorAddAccount = $J('<a/>', {'class': 'popup_menu_item'}).append('添加账户');
$AuthenticatorPopupMenu.append($AuthenticatorAddAccount);
$AuthenticatorAddAccount.on('click', function() {
addAccount();
});
var $AuthenticatorImportAccount = $J('<a/>', {'class': 'popup_menu_item'}).append('导入账户');
$AuthenticatorPopupMenu.append($AuthenticatorImportAccount);
$AuthenticatorImportAccount.on('click', function() {
importAccount();
});
}
function createConfirmationLink(steamID) {
if (!$AuthenticatorPopupMenu.find('.confirmation').length) {
$J.each(accounts, function(i, v) {
if (v.steamID && steamID == v.steamID) {
var $AuthenticatorConfirmation = $J('<a/>', {'class': 'popup_menu_item confirmation'}).append('确认交易与市场');
$AuthenticatorPopupMenu.append($AuthenticatorConfirmation);
$AuthenticatorConfirmation.on('click', function() {
window.open('https://keylol.com/steamconf/', '确认交易与市场', 'height=790,width=600,resize=yes,scrollbars=yes');
});
return false;
}
});
}
}
function setupTooltips(selector) {
if (window.location.hostname == 'store.steampowered.com') {
BindTooltips(selector, {tooltipCSSClass: 'store_tooltip'});
} else if (window.location.hostname == 'help.steampowered.com') {
BindTooltips(selector, {tooltipCSSClass: 'help_tooltip'});
} else if (window.location.hostname == 'steamcommunity.com') {
BindTooltips(selector, {tooltipCSSClass: 'community_tooltip'});
}
}
GM_addStyle(`
.delete_account {
position: absolute;
right: 0;
top: 0;
padding: 0 7.5px;
width: 12px;
height: 100%;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAQAAAD8fJRsAAAAkElEQVR4AXWQxWEDMRBFJ6AWArqGmW7G12HMDN0ZFmr4dqKF00rDPGPhycnr/vi9nJVPl2qI7Dd0WZpZEyFEygKhy1CkPsX4JCLlB6OP6jo3eRHxhh3xA+OBLULedCtExDOGcRvM6DZzpP/RxgtR4fDKat/ylPUKpZwao1A769VBDbls3H5WO6KfjVu5YOVJDkyDcoTnvnKRAAAAAElFTkSuQmCC);
background-position: center;
background-repeat: no-repeat;
background-origin: content-box;
cursor: pointer;
}
.copy_success {
color: #57cbde !important;
}
`);
var $GlobalActionMenu = $J('#global_action_menu');
var $AuthenticatorLink = $J('<span/>', {'class': 'pulldown global_action_link', 'style': 'display: inline-block; padding-left: 4px; line-height: 25px;', 'onclick': 'ShowMenu(this, "authenticator_dropdown", "right");'}).append('Steam 令牌验证器');
var $AuthenticatorDropdown = $J('<div/>', {'class': 'popup_block_new', 'id': 'authenticator_dropdown', 'style': 'display: none;'});
var $AuthenticatorPopupMenu = $J('<div/>', {'class': 'popup_body popup_menu'});
$GlobalActionMenu.prepend($AuthenticatorDropdown.append($AuthenticatorPopupMenu));
$GlobalActionMenu.prepend($AuthenticatorLink);
var accounts = GM_getValue('accounts') || [];
refreshAccounts();
GM_addValueChangeListener('accounts', function(name, old_value, new_value, remote) {
accounts = new_value;
refreshAccounts();
if (userSteamID) {
$AuthenticatorPopupMenu.find('.confirmation').remove();
createConfirmationLink(userSteamID);
}
AlignMenu($AuthenticatorLink, 'authenticator_dropdown', 'right');
});
if (window.location.href.indexOf('https://keylol.com/steamconf')>-1) {
var init = async function () {
document.querySelector('#load-text').innerHTML = '加载中……';
var xmlstr = await util.xhr({url: 'https://steamcommunity.com/my?xml=1'});
var steamid = (new DOMParser()).parseFromString(xmlstr.body, 'application/xml').getElementsByTagName('steamID64')[0].innerHTML;
var account = {};
accounts.forEach(function (v) {
if (v.steamID && steamid == v.steamID) {
account = v;
return false;
}
});
var data = await util.xhr({
url: 'https://steamcommunity.com/mobileconf/getlist?' + generateConfirmationQueryParams(account, 'list', timeOffset),
type: 'json'
}).then(res => res.body);
var submitbtn = document.querySelector('#submit');
var cancelbtn = document.querySelector('#cancel');
submitbtn.onclick = async function () {
var allowData = await util.xhr({
url: 'https://steamcommunity.com/mobileconf/multiajaxop?' + generateConfirmationQueryParams(account, 'accept', timeOffset) + '&op=allow',
method: 'POST',
data: unsafeWindow.formdata,
headers: {
'content-type': `multipart/form-data; boundary=${unsafeWindow.boundary}`
},
type: 'json'
}).then(res => res.body);
if (allowData.success) {
alert('确认完成!');
window.location.reload();
} else {
alert('确认失败!');
}
};
cancelbtn.onclick = async function () {
var cancelData = await util.xhr({
url: 'https://steamcommunity.com/mobileconf/multiajaxop?' + generateConfirmationQueryParams(account, 'reject', timeOffset) + '&op=cancel',
method: 'POST',
data: unsafeWindow.formdata,
headers: {
'content-type': `multipart/form-data; boundary=${unsafeWindow.boundary}`
},
type: 'json'
}).then(res => res.body);
if (cancelData.success) {
alert('取消完成!');
window.location.reload();
} else {
alert('确认失败!');
}
};
unsafeWindow.proc_data(data);
};
init();
}
var intersectionObserver = new IntersectionObserver(function(entries) {
if (entries[0].intersectionRatio > 0) {
var name = $J('#login_twofactorauth_message_entercode_accountname, [class^="login_SigningInAccountName"], [class^="newlogindialog_AccountName').text();
$J.each(accounts, function(i, v) {
if(name == v.name) {
var $AuthCodeInput = $J('#twofactorcode_entry, [class^="login_AuthenticatorInputcontainer"] input.DialogInput, [class^="newlogindialog_SegmentedCharacterInput"] input, [class^="segmentedinputs_SegmentedCharacterInput"] input');
var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
nativeInputValueSetter.call($AuthCodeInput[0], generateAuthCode(v.secret, timeOffset));
$AuthCodeInput[0].dispatchEvent(new Event('input', {bubbles: true}));
return false;
}
});
}
});
var mutationObserver = new MutationObserver(function() {
if ($J('#twofactorcode_entry, [class^="login_AuthenticatorInputcontainer"] input.DialogInput, [class^="newlogindialog_SegmentedCharacterInput"] input, [class^="segmentedinputs_SegmentedCharacterInput"] input').length) {
intersectionObserver.observe($J('#twofactorcode_entry, [class^="login_AuthenticatorInputcontainer"] input.DialogInput, [class^="newlogindialog_SegmentedCharacterInput"] input, [class^="segmentedinputs_SegmentedCharacterInput"] input')[0]);
}
if ($J('[class^="newlogindialog_EnterCodeInsteadLink"] [class^="newlogindialog_TextLink"]').length) {
$J('[class^="newlogindialog_EnterCodeInsteadLink"] [class^="newlogindialog_TextLink"]')[0].click();
}
});
var userSteamID;
if ($J('#account_dropdown .persona').length) {
if (typeof g_steamID != 'undefined' && g_steamID) {
userSteamID = g_steamID;
createConfirmationLink(userSteamID);
} else {
GM_xmlhttpRequest({
method: 'GET',
url: 'https://steamcommunity.com/my/?xml=1',
onload: function(response) {
if (response.responseXML) {
var steamID = $J(response.responseXML).find('steamID64').text();
if (steamID) {
userSteamID = steamID;
createConfirmationLink(userSteamID);
}
}
}
});
}
if (window.location.href.indexOf('store.steampowered.com/login/?purchasetype=') !== -1) {
mutationObserver.observe(document.body, {childList: true, subtree: true});
}
} else {
mutationObserver.observe(document.body, {childList: true, subtree: true});
}
var timeOffset = 0;
GM_xmlhttpRequest({
method: 'POST',
url: 'https://api.steampowered.com/ITwoFactorService/QueryTime/v0001',
responseType: 'json',
onload: function(response) {
if (response.response && response.response.response && response.response.response.server_time) {
timeOffset = response.response.response.server_time - Math.floor(Date.now() / 1000);
}
}
});
})();