// ==UserScript==
// @name auto-task
// @namespace auto-task
// @version 5.0.4
// @description 自动完成 Freeanywhere,Giveawaysu,GiveeClub,Givekey,Gleam,Indiedb,keyhub,OpiumPulses,Opquests,SweepWidget 等网站的任务。
// @description:en Automatically complete the tasks of FreeAnyWhere, GiveawaySu, GiveeClub, Givekey, Gleam, Indiedb, keyhub, OpiumPulses, Opquests, SweepWidget websites.
// @author HCLonely
// @license MIT
// @run-at document-start
// @homepage https://auto-task-doc.js.org/
// @supportURL https://github.com/HCLonely/auto-task/issues
// @icon https://auto-task.hclonely.com/favicon.ico
// @tag games
// @include *://freeanywhere.net/*
// @include *://giveaway.su/giveaway/view/*
// @include *://givee.club/*/event/*
// @include *://givekey.ru/giveaway/*
// @include *://www.indiedb.com/giveaways*
// @include *://key-hub.eu/giveaway/*
// @include *://keylol.com/*
// @include *://www.opiumpulses.com/giveaways
// @include *://prys.revadike.com/giveaway/?id=*
// @include *://opquests.com/quests/*
// @include *://gleam.io/*
// @include *://sweepwidget.com/view/*
// @include *://giveawayhopper.com/c/*
// @include *://discord.com/*
// @include *://www.twitch.tv/*
// @include *://www.youtube.com/*
// @include *://m.youtube.com/*
// @include *://*.reddit.com/*
// @include *://twitter.com/settings/account?k*
// @include *://x.com/settings/account*
// @include *://steamcommunity.com/*
// @include *://store.steampowered.com/*
// @include *://give.gamesforfarm.local/*
// @include *://gamesforfarm-testing.ru/*
// @include *://mee6.xyz/*
// @include *://gamesforfarm.com/*
// @include https://auto-task.hclonely.com/setting.html
// @include https://auto-task.hclonely.com/history.html
// @include https://auto-task-doc.js.org/setting.html
// @include https://auto-task-doc.js.org/history.html
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_info
// @grant GM_openInTab
// @grant GM_setClipboard
// @grant GM_getResourceText
// @grant GM_cookie
// @grant GM_addValueChangeListener
// @grant GM_removeValueChangeListener
// @grant unsafeWindow
// @grant window.close
// @grant window.localStorage
// @grant window.sessionStorage
// @grant window.focus
// @connect auto-task.hclonely.com
// @connect auto-task-doc.js.org
// @connect cdn.jsdelivr.net
// @connect store.steampowered.com
// @connect steamcommunity.com
// @connect login.steampowered.com
// @connect twitter.com
// @connect x.com
// @connect abs.twimg.com
// @connect api.twitter.com
// @connect youtube.com
// @connect www.youtube.com
// @connect facebook.com
// @connect instagram.com
// @connect vk.com
// @connect twitch.tv
// @connect www.twitch.tv
// @connect gql.twitch.tv
// @connect github.com
// @connect discordapp.com
// @connect discord.gg
// @connect discord.com
// @connect www.reddit.com
// @connect oauth.reddit.com
// @connect raw.githubusercontent.com
// @connect t.me
// @connect bit.ly
// @connect giveaway.su
// @connect google.com
// @connect www.vloot.io
// @connect givee.club
// @connect gleam.io
// @connect www.indiedb.com
// @connect key-hub.eu
// @connect opquests.com
// @connect itch.io
// @connect auto-task.hclonely.com
// @connect giveawayhopper.com
// @connect freeanywhere.net
// @connect *
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js.cookie.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/src/sha1.min.js
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.js
// @resource style https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.min.css
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/keyboard.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js
// @require https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@3e60eeb368f25f237a512e7553fd6d70f36dc74c/targets/wasm_exec.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/inspect.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.min.js
// @noframes
// ==/UserScript==
console.log('%c%s', 'color:blue', 'Auto-Task[Load]: 脚本开始加载');
/*
* @Author : HCLonely
* @Date : 2025-06-15 14:59:17
* @LastEditTime : 2025-08-18 19:05:01
* @LastEditors : HCLonely
* @FilePath : /auto-task/src/scripts/checkDependence.js
* @Description :
*/
const neededDependencies = ['jQuery', 'Cookies', 'sha1', 'Swal', 'keyboardJS', 'dayjs', 'Go', 'util', 'browser'];
const missingDependencies = neededDependencies.filter(dependency => typeof window[dependency] === 'undefined');
if (missingDependencies.length > 0) {
console.log('%c%s', 'color:red', `[Auto-Task] 脚本加载失败,缺少的依赖:${missingDependencies.join(', ')}`);
if (confirm(`[Auto-Task] 脚本依赖加载失败,请刷新重试或安装全依赖版本,是否前往安装全依赖版本?\n缺少的依赖:${missingDependencies.join(', ')}`)) {
GM_openInTab('https://github.com/HCLonely/auto-task/raw/main/dist/auto-task.all.user.js', { active: true });
}
}
(function(Swal, Cookies, browser, util, dayjs, keyboardJS) {
'use strict';
const tokenKeyPattern = /token|auth|session|jwt|key|secret|api[-_]?key|bearer|authorization|access[-_]?token|refresh[-_]?token|sid/i;
const tokenStringPatterns = [ /([A-Za-z0-9-_]{10,})\.([A-Za-z0-9-_]{10,})\.([A-Za-z0-9-_]{10,})/g, /(Bearer|Basic)\s+([A-Za-z0-9\-._~+/]+=*)/gi, /\b([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\b/gi, /\b(eyJ[A-Za-z0-9\-_]+)\b/g ];
const maskToken = str => {
if (typeof str !== 'string' || str.length < 8) {
return str;
}
return str.replace(/^([A-Za-z0-9\-_+/=]{4})[A-Za-z0-9\-_+/=]+([A-Za-z0-9\-_+/=]{4})$/, '$1***$2');
};
const maskObject = obj => {
if (Array.isArray(obj)) {
return obj.map(maskObject);
} else if (obj && typeof obj === 'object') {
const newObj = {};
for (const key in obj) {
if (tokenKeyPattern.test(key) && typeof obj[key] === 'string') {
newObj[key] = maskToken(obj[key]);
} else {
newObj[key] = maskObject(obj[key]);
}
}
return newObj;
}
if (typeof obj === 'string' && obj.length > 8) {
return maskString(obj);
}
return obj;
};
const maskString = str => {
let masked = str;
for (const pattern of tokenStringPatterns) {
masked = masked.replace(pattern, ((match, ...groups) => {
if (groups.length >= 3 && match.includes('.')) {
return groups.map((seg => seg.length > 8 ? `${seg.slice(0, 4)}***${seg.slice(-4)}` : seg)).join('.');
}
if (match.length > 8) {
return `${match.slice(0, 4)}***${match.slice(-4)}`;
}
return match;
}));
}
return masked;
};
const maskArgs = args => args.map((arg => {
if (typeof arg === 'string') {
return maskString(arg);
} else if (typeof arg === 'object' && arg !== null) {
return maskObject(arg);
}
return arg;
}));
const consoleLogHook = () => {
const originalLog = console.log;
window.__allLogs = window.__allLogs || [];
console.log = function(...args) {
const maskedArgs = maskArgs(args);
window.__allLogs.push(maskedArgs);
originalLog.apply(console, maskedArgs);
};
};
const defaultGlobalOptions = {
doTask: {
discord: {
servers: true
},
twitch: {
channels: true
},
twitter: {
users: true,
retweets: true
},
vk: {
names: true
},
youtube: {
channels: true,
likes: true
},
reddit: {
reddits: true
},
steam: {
groups: true,
officialGroups: true,
wishlists: true,
follows: true,
forums: true,
workshops: true,
curators: true,
workshopVotes: true,
announcements: true,
licenses: true,
playtests: true,
playTime: true
}
},
undoTask: {
discord: {
servers: true
},
twitch: {
channels: true
},
twitter: {
users: true,
retweets: true
},
vk: {
names: true
},
youtube: {
channels: true,
likes: true
},
reddit: {
reddits: true
},
steam: {
groups: true,
officialGroups: true,
wishlists: true,
follows: true,
forums: true,
workshops: true,
curators: true,
playTime: true
}
},
ASF: {
AsfEnabled: false,
AsfIpcUrl: '',
AsfIpcPassword: '',
AsfBotname: 'asf',
steamWeb: false,
preferASF: false,
steamWebApiKey: ''
},
position: {
buttonSideX: 'right',
buttonSideY: 'top',
buttonDistance: '15,30',
showButtonSideX: 'right',
showButtonSideY: 'top',
showButtonDistance: '15,30',
logSideX: 'right',
logSideY: 'bottom',
logDistance: '10,10'
},
hotKey: {
doTaskKey: 'alt + d',
undoTaskKey: 'alt + u',
toggleLogKey: 'alt + l'
},
other: {
twitterVerifyId: '783214',
youtubeVerifyChannel: 'UCrXUsMBcfTVqwAS7DKg9C0Q',
autoUpdateSource: 'jsdelivr',
language: 'zh',
checkLogin: true,
checkLeftKey: true,
defaultShowButton: true,
defaultShowLog: true,
debug: false,
receivePreview: true
}
};
const userDefinedGlobalOptions = GM_getValue('globalOptions') || {};
const deepMerge = (target, source) => {
try {
const result = {
...target
};
for (const [key, value] of Object.entries(source)) {
const targetValue = target[key];
if (isObject(value) && isObject(targetValue)) {
result[key] = deepMerge(targetValue, value);
} else if (value !== undefined) {
result[key] = value;
}
}
return result;
} catch (error) {
console.log('%c%s', 'color:white;background:red', `Auto-Task[Error]: deepMerge\n${error.stack}`);
return target;
}
};
const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value);
const globalOptions = deepMerge(defaultGlobalOptions, userDefinedGlobalOptions);
var style = '.colorful-button,#auto-task-buttons a.auto-task-website-btn,.show-button-div a.auto-task-website-btn,body.auto-task-options .auto-task-form table button{position:relative !important;padding:14px 28px !important;text-align:center !important;color:#fff !important;text-decoration:none !important;background:#2196f3 !important;border-radius:30px !important;text-transform:capitalize !important;font-weight:600 !important;letter-spacing:.5px !important;border:none !important;transition:all .2s ease !important;display:inline-block !important;line-height:1.5 !important;margin:8px !important;margin-bottom:12px !important;box-sizing:border-box !important;min-height:50px !important;min-width:140px !important;outline:none !important;vertical-align:middle !important;white-space:nowrap !important;font-size:18px !important}.colorful-button:hover,#auto-task-buttons a.auto-task-website-btn:hover,.show-button-div a.auto-task-website-btn:hover,body.auto-task-options .auto-task-form table button:hover{background:#1976d2 !important;box-shadow:0 4px 8px rgba(0,0,0,.1) !important;cursor:pointer !important;color:#fff !important;text-decoration:none !important}.colorful-button:active,#auto-task-buttons a.auto-task-website-btn:active,.show-button-div a.auto-task-website-btn:active,body.auto-task-options .auto-task-form table button:active{transform:translateY(1px) !important;color:#fff !important;text-decoration:none !important}.colorful-button:focus,#auto-task-buttons a.auto-task-website-btn:focus,.show-button-div a.auto-task-website-btn:focus,body.auto-task-options .auto-task-form table button:focus{color:#fff !important;text-decoration:none !important;outline:none !important}#auto-task-info{position:fixed;bottom:10px;right:10px;width:60%;max-width:500px;max-height:60%;overflow-y:auto;color:#000;background-color:#fff;padding-left:5px;z-index:999999999 !important;border:solid 2px #add8e6;border-radius:10px;font-size:14px !important}#auto-task-info li{text-align:left;display:block !important;align-items:baseline !important}#auto-task-info li .before-icon{display:inline-block !important;width:14px !important;height:14px !important;position:relative !important;top:2px !important;margin-right:5px !important;background-size:14px !important;background-repeat:no-repeat !important;flex-shrink:0 !important}#auto-task-info li font.before{color:#57bae8;margin-right:5px}#auto-task-info li a.high-light{color:#00aeff;font-weight:bold}#auto-task-info .success{color:green}#auto-task-info .error{color:red}#auto-task-info .warning{color:blue}#auto-task-info .info{color:#ff0}#auto-task-info .update-text{color:green;border:solid 2px #8dcb69;margin:5px 10px 5px 20px;border-radius:10px;padding:5px 20px}.auto-task-keylol{display:inline-block;text-transform:capitalize;margin-left:10px;text-decoration:none !important;border:solid 1px;border-radius:5px;padding:0 2px}.auto-task-keylol[selected=selected]{background-color:blue !important;color:#fff !important}.auto-task-form table{font-family:verdana,arial,sans-serif;font-size:11px;color:#333;border-width:1px;border-color:#999;border-collapse:collapse;width:100%}.auto-task-form table thead td{border-width:1px;padding:8px;border-style:solid;border-color:#a9c6c9;font-weight:bold;background-color:#fff}.auto-task-form table tbody tr{background-color:#d4e3e5}.auto-task-form table tbody tr:hover{background-color:#ff6 !important}.auto-task-form table tbody tr th{background-color:#c3dde0;border-width:1px;padding:8px;border-style:solid;border-color:#a9c6c9;text-transform:capitalize}.auto-task-form table tbody tr td{border-width:1px;padding:8px;border-style:solid;border-color:#a9c6c9}.swal2-modal{width:70% !important;max-width:1000px !important}.swal2-modal #swal2-title{text-align:center !important}body.auto-task-options{padding-top:10px;text-align:center}body.auto-task-options .auto-task-form{width:80%;max-width:1000px;margin:0 auto;padding-bottom:20px}body.auto-task-options .auto-task-form table input.editOption{width:80%}body.auto-task-options .auto-task-form table #getTwitterUserId,body.auto-task-options .auto-task-form table #getYoutubeChannelId{margin-top:5px}body.auto-task-options .auto-task-form table button{z-index:1;position:relative !important;padding:5px 20px !important;text-align:center !important;color:#fff !important;text-decoration:none !important;background:#2196f3 !important;border-radius:30px !important;text-transform:capitalize !important;font-weight:600 !important;letter-spacing:.5px !important;border:none !important;transition:all .2s ease !important;display:inline-block !important;line-height:1 !important;margin:8px !important;box-sizing:border-box !important;min-height:30px !important;min-width:140px !important;outline:none !important;vertical-align:middle !important;white-space:nowrap !important;font-size:15px !important}body.auto-task-options .auto-task-form table input[type=text]{outline-style:none;border:1px solid #ccc;border-radius:3px;padding:5px 10px;font-size:14px}body.auto-task-options .auto-task-form table input[type=text]:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}body.auto-task-options .auto-task-form table label{position:relative;width:160px;height:80px;cursor:pointer;transform:scale(0.25);margin:-25% 0;top:-30px;display:inline-block}body.auto-task-options .auto-task-form table label input{position:relative;z-index:1;appearance:none}body.auto-task-options .auto-task-form table label input:checked~span{background:#05be05;box-shadow:0 15px 25px rgba(5,190,5,.4)}body.auto-task-options .auto-task-form table label input:checked~span i{left:84px}body.auto-task-options .auto-task-form table label input:checked~span i::before{background:#05be05;box-shadow:35px 0 0 #05be05}body.auto-task-options .auto-task-form table label input:checked~span i::after{bottom:12px;height:15px;border-bottom-left-radius:15px;border-bottom-right-radius:15px;background:#05be05}body.auto-task-options .auto-task-form table label span{position:absolute;top:0;left:0;width:100%;height:100%;background:#fe0000;border-radius:80px;transition:.5s;box-shadow:0 15px 25px rgba(254,0,0,.4)}body.auto-task-options .auto-task-form table label span i{position:absolute;top:4px;left:4px;width:72px;height:72px;background:#fff;border-radius:50%}body.auto-task-options .auto-task-form table label span i::before{content:"";position:absolute;top:22px;left:12px;width:12px;height:12px;border-radius:50%;background:#fe0000;box-shadow:35px 0 0 #fe0000;transition:.5s}body.auto-task-options .auto-task-form table label span i::after{content:"";position:absolute;bottom:15px;left:calc(50% - 15px);width:30px;height:6px;border-radius:6px;background:#fe0000;transition:.5s}body.auto-task-history{font-size:15px;font-weight:400;line-height:1.5}body.auto-task-history .container a{color:#007bff;text-decoration:none;background-color:rgba(0,0,0,0)}body.auto-task-history .container .card{width:80%;max-width:800px;border-radius:10px;background:rgba(118,118,118,.1019607843);border-top:1px solid hsla(0,0%,100%,.5019607843);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);box-shadow:0 15px 25px rgba(0,0,0,.1019607843);margin:20px auto;position:relative;display:flex;flex-direction:column;word-wrap:break-word;-webkit-background-clip:border-box;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}body.auto-task-history .container .card .title{text-align:center;font-size:30px;font-weight:bold;margin:5px 0}body.auto-task-history .container .card .title a:hover{text-decoration:none;background:#93e1ff;border-radius:10px;padding:3px}body.auto-task-history .container .card ul{margin-bottom:25px}body.auto-task-history .container .card ul li{margin-bottom:5px;line-height:20px}body.auto-task-history .container .card ul a:hover{text-decoration:underline}body.auto-task-history .container .card .delete-task{right:10px;width:38px;height:35px;position:absolute;font-size:24px;cursor:pointer;border-radius:10px}body.auto-task-history .container .card .delete-task:hover{background:#fff}body.auto-task-history .container .card .time{right:5px;position:absolute;bottom:0;color:#e83e8c;font-family:\'SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace\';font-size:15px}#auto-task-buttons,.show-button-div{position:fixed !important;top:30px;right:15px;width:150px !important;z-index:999999999 !important;padding:8px !important;border-radius:12px !important}#auto-task-buttons p,.show-button-div p{line-height:normal !important;height:auto !important;text-align:center !important;margin:8px 0 !important;padding:0 !important;font-size:16px !important;color:#333 !important}#auto-task-buttons a.auto-task-website-btn,.show-button-div a.auto-task-website-btn{width:140px !important;min-height:30px !important;line-height:1.5 !important;font-size:16px !important;display:block !important;margin:0 auto !important;padding:8px 16px !important}.show-button-div{width:40px !important;cursor:pointer !important;padding:4px !important}.show-button-div a.auto-task-website-btn{right:-15px !important}.show-button-div a.auto-task-website-btn::after{content:"✓" !important;position:absolute !important;left:12px !important;top:50% !important;transform:translateY(-50%) !important;font-size:18px !important;font-weight:bold !important;color:#fff !important}.auto-task-capitalize{text-transform:capitalize !important}.swal2-file:focus,.swal2-input:focus,.swal2-textarea:focus{box-shadow:inset 0px 0px 4px 1px rgba(100,150,200,.5) !important}.swal2-checkbox-custom{align-items:center;justify-content:center;background:#fff;color:inherit;margin:1em auto}.swal2-checkbox-custom input{flex-shrink:0;margin:0 .4em}.giveaway-actions #getKey{display:none !important}.auto-task-giveaway-status{color:#fff;border-radius:10px;padding:0 5px;margin-left:5px}.auto-task-giveaway-status.active{background-color:#5cb85c}.auto-task-giveaway-status.not-active{background-color:#d9534f}';
const data$1 = {
website: '网站',
type: '类型',
edit: '编辑',
whiteList: '白名单',
skipTask: '跳过撤销任务',
whiteListOptions: '白名单设置',
changeWhiteListOption: '设置白名单(%0)',
whiteListNotFound: '找不到此项白名单: %0',
changeWhiteListSuccess: '白名单修改成功,刷新生效!',
changeWebsiteOptions: '网站设置',
changeGlobalOptions: '全局设置',
ok: '是',
save: '保存',
close: '关闭',
return: '返回',
option: '选项',
value: '值',
websiteOptions: '当前网站设置',
changeWebsiteOptionsSuccess: '更改当前网站设置成功,刷新生效!',
changeGlobalOptionsSuccess: '更改全局设置成功,刷新生效!',
needLogin: '请先登录!',
getTasksInfo: '正在获取并处理任务信息...',
gettingKey: '正在获取Key...',
verifyingTask: '正在验证任务',
notice: '自动任务脚本提醒',
noKeysLeft: '此页面已经没有剩余key了,是否关闭?',
giveawayEnded: '此活动已结束,是否关闭?',
giveawayNotWork: '此活动因某些原因(已结束/暂停/未开始...)不可用(如果是脚本误判请及时反馈),是否关闭?',
confirm: '确定',
cancel: '取消',
unKnown: '未知',
unKnownTaskType: '未识别的任务',
doing: '正在做任务',
allTasksComplete: '所有任务已完成!',
getTaskIdFailed: '获取任务Id失败!',
initSuccess: '%0 初始化成功!',
initFailed: '%0 初始化失败!',
errorLink: '链接错误: %0',
needInit: '请先初始化',
verifyingAuth: '正在验证%0凭证...',
updatingAuth: '正在更新%0凭证...',
refreshingToken: '正在刷新%0凭证...',
settingToken: '正在设置%0凭证...',
steamStoreTab: 'Steam商店(弹窗)',
steamCommunityTab: 'Steam社区(弹窗)',
initing: '正在初始化...',
getFailed: '获取%0失败!',
checkLoginFailed: '检测登录状态失败!',
checkLeftKeyFailed: '检测剩余Key失败!',
userId: '用户Id',
joiningGiveaway: '正在加入赠Key',
needJoinGiveaway: '需要先加入赠Key',
cannotUndo: '此网站不支持取消任务',
verifyAuth: '正在验证 %0 凭证...',
closePageNotice: '如果此页面没有自动关闭,请自行关闭本页面。',
errorReport: '检测到脚本报错,是否前往反馈BUG?',
visitingLink: '正在访问链接...',
doTask: '做任务',
undoTask: '撤销任务',
verifyTask: '验证任务',
getKey: '获取Key',
selectAll: '全选',
selectNone: '全不选',
invertSelect: '反选',
doFreeTask: '加入免费赠品',
doPointTask: '加入点数赠品',
skipTaskOption: '设置中已配置跳过任务',
other: '其他',
globalOptions: '全局设置',
checkLogin: '登录检测</br>需要登录的网站自动登录,部分网站支持',
checkLeftKey: '剩余Key检测</br>赠Key活动结束提示是否关闭,部分网站支持',
twitterVerifyId: '通过尝试关注该账号验证Twitter凭证</br>默认为Twitter官方帐号 783214</br>不想关注官方账号可以改为自己的帐号',
youtubeVerifyChannel: '通过尝试订阅该频道验证YouTube凭证</br>默认为YouTube官方频道 UCrXUsMBcfTVqwAS7DKg9C0Q</br>不想关注官方频道可以改为自己的频道',
autoUpdateSource: '更新源</br>github: 需代理,实时更新</br>jsdelivr: 可不用代理,更新有延迟</br>standby: 备用</br>auto: 依次使用github, jsdelivr, standby源进行尝试更新',
saveGlobalOptions: '保存设置',
settingPage: '设置页面',
name: '名称',
version: '版本',
scriptManager: '脚本管理器',
script: '脚本',
environment: '环境',
os: '系统',
browser: '浏览器',
getId: '获取 %0 id',
getTwitterUserId: '获取Twitter用户id(获取id功能仅在设置页面可用)',
getYoutubeChannelId: '获取Youtube频道id(获取id功能仅在设置页面可用)',
showButton: '显示按钮',
hideButton: '隐藏按钮',
showLog: '显示日志',
hideLog: '隐藏日志',
defaultShowButton: '默认显示按钮',
defaultShowLog: '默认显示日志',
debug: '输出调试日志,不要开启此选项!',
receivePreview: '接收预览版更新',
position: '组件位置',
buttonSideX: '按钮区域水平方向定位(实时预览功能仅在设置页面可用)</br>left: 靠左 | right: 靠右',
buttonSideY: '按钮区域垂直方向定位(实时预览功能仅在设置页面可用)</br>top: 靠上 | bottom: 靠下',
buttonDistance: '按钮区域距边缘的距离(实时预览功能仅在设置页面可用)</br>格式: X距离,Y距离',
showButtonSideX: '显示按钮水平方向定位(实时预览功能仅在设置页面可用)</br>left: 靠左 | right: 靠右',
showButtonSideY: '显示按钮垂直方向定位(实时预览功能仅在设置页面可用)</br>top: 靠上 | bottom: 靠下',
showButtonDistance: '显示按钮距边缘的距离(实时预览功能仅在设置页面可用)</br>格式: X距离,Y距离',
logSideX: '日志区域水平方向定位(实时预览功能仅在设置页面可用)</br>left: 靠左 | right: 靠右',
logSideY: '日志区域垂直方向定位(实时预览功能仅在设置页面可用)</br>top: 靠上 | bottom: 靠下',
logDistance: '日志区域距边缘的距离(实时预览功能仅在设置页面可用)</br>格式: X距离,Y距离',
hotKey: '快捷键',
doTaskKey: '做任务快捷键</br>(实时预览功能仅在设置页面可用)',
undoTaskKey: '撤销任务快捷键</br>(实时预览功能仅在设置页面可用)',
toggleLogKey: '显示/隐藏日志快捷键</br>(实时预览功能仅在设置页面可用)',
tasksHistory: '任务历史',
clearHistory: '清空历史',
clearHistoryFinished: '已清空任务历史!',
deleteTask: '删除任务',
lastChangeTime: '最后一次修改时间',
clearTaskFinished: '删除以下任务完成!',
clearTaskFailed: '删除任务失败,没有找到任务名!',
syncData: '数据同步',
settingData: '正在上传数据...',
gettingData: '正在获取数据...',
help: '帮助',
fileName: '文件名',
upload2gist: '同步到Gist',
downloadFromGist: '从Gist同步',
saveAndTest: '保存配置并测试',
testSuccess: '测试成功!',
testFailed: '测试失败!',
saveAndTestNotice: '请先保存配置并测试!',
processingData: '正在处理数据...',
updatingData: '正在上传数据...',
syncDataSuccess: '同步数据成功!',
syncDataFailed: '同步数据失败,请在控制台查看错误信息!',
downloadingData: '正在下载数据...',
checkedNoData: '没有检测到远程数据,请确认配置是否正确!',
savingData: '正在保存数据...',
syncHistory: '同步任务历史',
checkUpdateFailed: '检测更新失败',
newVersionNotice: '检测到新版本V%0, <a class="high-light" href="%1" target="_blank">点此更新</a>',
language: '语言</br>目前仅支持zh: 中文, en: 英文',
gistOptions: 'Gist 设置',
swalNotice: '检测到您第一次安装V4版本脚本,请前往阅读用前必读内容!',
echoNotice: '检测到您第一次安装V4版本脚本,请<a class="high-light" href="%0" target="_blank">点此前往</a>阅读用前必读内容!',
noticeLink: 'https://auto-task-doc.js.org/guide/#用前必读',
toGithub: '前往Github反馈',
toKeylol: '前往其乐论坛反馈',
copySuccess: '错误信息已复制到剪切板,是否前往其乐论坛反馈?',
copyFailed: '请复制下方错误信息后前往Keylol论坛反馈!',
updateText: '%0 版本更新内容:',
Active: '进行中',
Ended: '已结束',
Banned: '已封禁',
Paused: '已暂停',
notStart: '未开始',
noRemoteData: '检测到远程无数据',
errorRemoteDataFormat: '远程数据格式错误',
updateHistory: '历史更新记录<a class="high-light" href="https://auto-task-doc.js.org/logs/" target="_blank">点此查看</a>',
AsfEnabled: '使用ASF做Steam相关任务(需<a href="https://github.com/chr233/ASFEnhance" target="_blank">ASFEnhance</a>插件)',
steamWeb: '同时使用Steam Web API做任务',
preferASF: '优先使用ASF',
AsfIpcUrl: 'ASF IPC 地址',
AsfIpcPassword: 'ASF IPC 密码',
versionNotMatched: '脚本管理器版本过低,需TamperMonkey >= 5.2.0或TamperMonkey Beta >= 5.2.6196',
curatorLimitNotice: '失败:可能是鉴赏家关注数量限制,请<a class="high-light" href="https://store.steampowered.com/curators/home/" target="_blank">取关部分鉴赏家</a>后再试',
unknownScriptHandler: '未知脚本管理器,适用性未知',
debugModeNotice: '检测到 DEBUG 模式已开启,非必要请关闭!',
steamWebApiKey: 'Steam Web API 密钥<br>用于检测游戏挂机状态,<a href="https://steamcommunity.com/dev/apikey" target="_blank">点此申请</a>',
groups: '组',
officialGroups: '官方组',
wishlists: '愿望单',
follows: '游戏关注',
forums: '论坛',
workshops: '创意工坊收藏',
curators: '鉴赏家',
workshopVotes: '创意工坊点赞',
announcements: '社区通知',
steamCommunity: 'Steam社区',
steamStore: 'Steam商店',
licenses: '入库免费游戏',
playtests: '请求访问权限',
playTime: '挂时长',
needLoginSteamStore: '请先<a href="https://store.steampowered.com/login/" target="_blank">登录Steam商店</a>',
needLoginSteamCommunity: '请先<a href="https://steamcommunity.com/login/home/" target="_blank">登录Steam社区</a>',
joiningSteamGroup: '正在加入Steam组',
leavingSteamGroup: '正在退出Steam组',
gettingSteamGroupId: '正在获取Steam组Id',
joiningSteamOfficialGroup: '正在加入Steam官方组',
leavingSteamOfficialGroup: '正在退出Steam官方组',
gettingSteamOfficialGroupId: '正在获取Steam官方组Id',
subscribingForum: '正在订阅Steam论坛',
unsubscribingForum: '正在取消订阅Steam论坛',
gettingForumId: '正在获取Steam论坛Id',
followingCurator: '正在关注Steam鉴赏家',
unfollowingCurator: '正在取关Steam鉴赏家',
gettingCuratorId: '正在获取Steam鉴赏家Id',
addingToWishlist: '正在添加游戏到Steam愿望单',
removingFromWishlist: '正在从Steam愿望单移除游戏',
followingGame: '正在关注Steam游戏',
unfollowingGame: '正在取关Steam游戏',
favoritingWorkshop: '正在收藏Steam创意工坊物品',
unfavoritingWorkshop: '正在取消收藏Steam创意工坊物品',
gettingWorkshopAppId: '正在获取Steam创意工坊物品Id',
votingUpWorkshop: '正在点赞Steam创意工坊物品',
gettingAnnouncementParams: '正在获取Steam通知信息',
likingAnnouncement: '正在点赞Steam通知',
changingArea: '正在更换Steam地区: %0...',
notNeededChangeArea: '当前地区不需要更换',
noAnotherArea: '请检测是否开启正确开启代理',
gettingAreaInfo: '正在获取Steam地区信息...',
changeAreaNotice: '疑似锁区游戏,尝试换区执行',
steamFinishNotice: 'Steam任务完成,尝试将购物车地区换回',
gettingSubid: '正在获取游戏subid',
addingFreeLicense: '正在入库',
missParams: '缺少参数',
gettingLicenses: '正在获取Licenses...',
requestingPlayTestAccess: '正在请求访问权限',
gettingDemoAppid: '正在获取Steam游戏的Demo Appid',
tryChangeAreaNotice: '此功能无法检测游戏是否限区,因此会尝试换区后再入库,换区失败也不影响后续入库',
gettingUserInfo: '正在获取Steam用户社区凭证...',
retry: '重试',
owned: '已拥有',
redirect: '重定向',
noSubid: '无法获取,跳过',
noASFInstance: '未启用ASF,跳过挂时长任务',
initingASF: '正在初始化ASF...',
playingGames: '正在挂游戏时长[%0]...',
stoppingPlayGames: '正在停止挂游戏时长...',
stopPlayTimeTitle: 'Steam游戏挂机时长满足,是否结束挂机?',
stopPlayTimeText: '挂机已超时:%0 分钟',
ASFNotSupportted: '当前功能(%0)ASF无法实现,跳过',
checkingPlayGamesStatus: '正在检查挂游戏时长状态...',
gettingSteamId: '正在获取Steam ID...',
checkingPlayStatus: '正在检查挂机状态...',
noPlayStatus: '游戏未运行',
servers: '服务器',
joiningDiscordServer: '正在加入Discord服务器',
leavingDiscordServer: '正在退出Discord服务器',
gettingDiscordGuild: '正在获取Discord服务器Id',
getDiscordAuthFailed: '获取Discord凭证失败,请检测Discord帐号是否已登录',
discordImportantNotice: '重要提醒!!!',
discordImportantNoticeText: '由于Discord网站后台更新,目前使用此脚本加组后可能会导致Discord帐号被强制退出,且需要两步验证才能正常登录,请谨慎使用!!!',
continueDiscordTask: '本次执行Discord任务',
skipDiscordTask: '本次跳过Discord任务',
continueAndDontRemindAgain: '总是执行Discord任务且不再提醒',
gettingDiscordXContextProperties: '正在获取Discord加群参数',
captchaNeeded: '检测到人机验证,请手动完成!',
users: '用户',
loginIns: '请先<a href="https://www.instagram.com/accounts/login/" target="_blank">登录Instagram</a>',
insBanned: '您的Instagram账户已被封禁',
verifyingInsAuth: '正在验证Instagram凭证...',
gettingInsUserId: '正在获取Instagram用户Id',
followingIns: '正在关注Instagram用户',
unfollowingIns: '正在取关Instagram用户',
reddits: '社区/用户',
loginReddit: '请先<a href="https://www.reddit.com/login/" target="_blank">登录Reddit</a>',
changingRedditVersion: '正在切换Reddit为新版页面...',
joiningReddit: '正在加入Reddit社区',
leavingReddit: '正在退出Reddit社区',
followingRedditUser: '正在关注Reddit用户',
unfollowingRedditUser: '正在取关Reddit用户',
channels: '频道',
followingTwitchChannel: '正在关注Twitch频道',
unfollowingTwitchChannel: '正在取关Twitch频道',
gettingTwitchChannelId: '正在获取Twitch频道Id',
checkingTwitchIntegrity: '正在检查Twitch完整性...',
twitterUser: '推特用户',
retweets: '转推',
gettingTwitterUserId: '正在获取推特用户Id',
followingTwitterUser: '正在关注推特用户',
unfollowingTwitterUser: '正在取关推特用户',
retweetting: '正在转推',
unretweetting: '正在撤销转推',
names: '组/社区/动态',
loginVk: '请先<a href="https://vk.com/login/" target="_blank">登录Vk</a>',
gettingVkId: '正在获取Vk任务Id',
joiningVkGroup: '正在加入Vk组',
leavingVkGroup: '正在退出Vk组',
joiningVkPublic: '正在加入Vk社区',
leavingVkPublic: '正在退出Vk社区',
sendingVkWall: '正在转发Vk动态',
deletingVkWall: '正在撤销转发Vk动态',
youtubeChannel: 'YouTube频道',
likes: '点赞',
loginYtb: '请先<a href="https://accounts.google.com/ServiceLogin?service=youtube" target="_blank">登录YouTube</a>',
tryUpdateYtbAuth: '请尝试<a href="https://www.youtube.com/#auth" target="_blank">更新YouTube凭证</a>',
gettingYtbToken: '正在获取YouTube Token...',
followingYtbChannel: '正在订阅YouTube频道',
unfollowingYtbChannel: '正在退订YouTube频道',
likingYtbVideo: '正在点赞YouTube视频',
unlikingYtbVideo: '正在取消点赞YouTube视频',
giveKeyNoticeBefore: '每次验证间隔15s',
giveKeyNoticeAfter: '如果没有key, 请在<a href="https://givekey.ru/profile" target="_blank">https://givekey.ru/profile</a>查看',
noPoints: '点数不够,跳过抽奖',
getNeedPointsFailed: '获取所需点数失败,跳过抽奖',
joiningLottery: '正在加入抽奖',
doingGleamTask: '正在做Gleam任务...',
gettingGleamLink: '正在获取Gleam任务链接...',
gleamTaskNotice: '如果此页面长时间未关闭,请完成任一任务后自行关闭!',
verifiedGleamTasks: '已尝试验证所有任务,验证失败的任务请尝试手动验证或完成!',
campaign: '检测到人机验证,请手动完成!3秒后重新检测...',
gsNotice: '为避免得到"0000-0000-0000"key, 已自动屏蔽"Grab Key"按钮,获取key时请关闭脚本!',
giveeClubVerifyNotice: '正在验证任务...',
giveeClubVerifyFinished: '请等待验证完成后自行加入赠Key',
doingKeyhubTask: '正在做Keyhub任务...',
SweepWidgetNotice: '正在处理并验证任务,每次验证任务有1~3s间隔防止触发验证过快警告...',
tasksNotCompleted: '任务未完成',
notConnect: '社交平台未连接,跳过任务: %0',
tgTaskNotice: '检测到Telegram任务,需要手动完成',
updatingUserData: '正在更新用户数据...',
gettingUserGames: '正在获取用户游戏...',
confirmingTask: '正在跳过警告页面...',
unSupporttedTaskType: '不支持的任务类型: %0',
taskNotFinished: '有任务未完成,不获取密钥',
logCopied: '完整日志已复制到剪切板,请前往反馈!'
};
const data = {
website: 'Website',
type: 'Type',
edit: 'Edit',
whiteList: 'Whitelist',
skipTask: 'Skip undo task',
whiteListOptions: 'Whitelist options',
changeWhiteListOption: 'Whitelist option(%0)',
whiteListNotFound: 'Cannot find this whitelist: %0',
changeWhiteListSuccess: 'The whitelist is successfully modified, and the page refresh will take effect!',
changeWebsiteOptions: 'Website options',
changeGlobalOptions: 'Global options',
ok: 'OK',
save: 'Save',
close: 'Close',
return: 'Return',
option: 'Option',
value: 'Value',
websiteOptions: 'Current website settings',
changeWebsiteOptionsSuccess: 'The current website setting is changed successfully, and the page refresh will take effect!',
changeGlobalOptionsSuccess: 'The global setting is changed successfully, and the refresh will take effect!',
needLogin: 'Please log in first!',
getTasksInfo: 'Obtaining and processing task information...',
gettingKey: 'Getting Key...',
verifyingTask: 'Verifying task',
notice: 'Automatic task script notice',
noKeysLeft: 'There are no more keys left on this page. Do you want to close it?',
giveawayEnded: 'This event has ended, do you want to close it?',
giveawayNotWork: 'This activity is unavailable for some reasons (banned/ended/paused/not started...)' + ' (if it is a script misjudgment, please give us feedback in time), is it closed?',
confirm: 'Confirm',
cancel: 'Cancel',
unKnown: 'Unknown',
unKnownTaskType: 'Unrecognized task',
doing: 'Doing a task',
allTasksComplete: 'All tasks have been completed!',
getTaskIdFailed: 'Failed to obtain task Id!',
initSuccess: '%0 was initialized successfully!',
initFailed: '%0 initialization failed!',
errorLink: 'Link error: %0',
needInit: 'Please initialize first',
verifyingAuth: 'Verifying %0 token...',
updatingAuth: 'Update %0 token...',
refreshingToken: 'Refreshing %0 token...',
settingToken: 'Setting %0 token...',
steamStoreTab: 'Steam store (new tab)',
steamCommunityTab: 'Steam community(new tab)',
initing: 'Initializing...',
getFailed: 'Failed to get %0!',
checkLoginFailed: 'Failed to detect login status!',
checkLeftKeyFailed: 'Failed to detect the remaining keys!',
userId: 'User Id',
joiningGiveaway: 'Joining giveaway',
needJoinGiveaway: 'Need to join the giveaway first',
cannotUndo: 'This website does not support canceling tasks',
verifyAuth: 'Verifying %0 token...',
closePageNotice: 'f this page does not close automatically, please close this page yourself.',
errorReport: 'A script error is detected, do you want to report the BUG?',
visitingLink: 'Visiting link ...',
doTask: 'DoTask',
undoTask: 'UndoTask',
verifyTask: 'Verify',
getKey: 'GetKey',
selectAll: 'SelectAll',
selectNone: 'SelectNone',
invertSelect: 'InvertSelect',
doFreeTask: 'FreeTask',
doPointTask: 'PointTask',
skipTaskOption: 'Skip task has been configured in the settings',
other: 'Other',
globalOptions: 'Global Options',
checkLogin: 'Login detection</br>Need to log in to the website automatically log in, part of this website supports.',
checkLeftKey: 'Key remaining detection</br>The end of the giveaway event prompts whether to close or not, part of this website supports.',
twitterVerifyId: 'Verify Twitter token by trying to follow the account.</br>The default is the official Twitter account 783214.</br>' + 'If you don\'t want to follow the official account, you can change it to your own account.',
youtubeVerifyChannel: 'Verify YouTube token by trying to subscribe to the channel.</br>' + 'The default is the official YouTube channel UCrXUsMBcfTVqwAS7DKg9C0Q.</br>' + 'If you don\'t want to follow the official channel, you can change it to your own channel.',
autoUpdateSource: 'The source to update</br>github: Fast update.</br>jsdelivr: Update is delayed.</br>' + 'standby: Standby source.</br>auto: Try to update using github, jsdelivr, standby sources in turn.',
saveGlobalOptions: 'SaveSettings',
settingPage: 'Setting Page',
name: 'Name',
version: 'Version',
scriptManager: 'Script Manager',
script: 'Script',
environment: 'Environment',
os: 'OS',
browser: 'Browser',
getId: 'Get %0 id',
getTwitterUserId: 'Get Twitter user id (Get id function is only available on the settings page).',
getYoutubeChannelId: 'Get Youtube channel id (Get id function is only available on the settings page).',
showButton: 'ShowButton',
hideButton: 'HideButton',
showLog: 'ShowLog',
hideLog: 'HideLog',
defaultShowButton: 'Default display button',
defaultShowLog: 'Display log by default',
debug: 'Output debug log, do not enable this option!',
receivePreview: 'Receive preview updates',
position: 'Component position',
buttonSideX: 'Horizontal positioning of the button area (real-time preview function is only available on the setting page).' + '</br>left: left | right: right',
buttonSideY: 'The button area is positioned in the vertical direction (real-time preview function is only available on the settings page).' + '</br>top: top | bottom: bottom',
buttonDistance: 'The distance between the button area and the edge (the real-time preview function is only available on the setting page).' + '</br> Format: X distance, Y distance',
showButtonSideX: 'ShowButton horizontal positioning (real-time preview function is only available on the setting page).' + '</br>left: left | right: right',
showButtonSideY: 'ShowButton vertical positioning (real-time preview function is only available on the setting page).' + '</br>top: top | bottom: bottom',
showButtonDistance: 'The distance between the ShowButton and the edge (real-time preview function is only available on the setting page).' + '</br> Format: X distance, Y distance',
logSideX: 'Horizontal positioning of the log area (real-time preview function is only available on the setting page).' + '</br>left: left | right: right',
logSideY: 'Vertical positioning of the log area (real-time preview function is only available on the setting page).' + '</br>top: top | bottom: bottom',
logDistance: 'The distance between the log area and the edge (the real-time preview function is only available on the setting page).' + '</br> Format: X distance, Y distance',
hotKey: 'Shortcut key',
doTaskKey: 'DoTask shortcut keys</br> (real-time preview function is only available on the settings page).',
undoTaskKey: 'UndoTask shortcut keys</br> (real-time preview function is only available on the settings page).',
toggleLogKey: 'ShowLog/HideLog shortcut keys</br> (real-time preview function is only available on the settings page).',
tasksHistory: 'TasksHistory',
clearHistory: 'Clear history',
clearHistoryFinished: 'The mission history has been cleared!',
deleteTask: 'Delete task',
lastChangeTime: 'Last Change Time',
clearTaskFinished: 'Delete the following tasks completed!',
clearTaskFailed: 'Failed to delete the task, the task name was not found!',
syncData: 'DataSync',
settingData: 'Uploading data...',
gettingData: 'Getting data...',
help: 'Help',
fileName: 'Filename',
upload2gist: 'Sync to Gist',
downloadFromGist: 'Sync from Gist',
saveAndTest: 'Save configuration and test',
testSuccess: 'Test success!',
testFailed: 'Test failed!',
saveAndTestNotice: 'Please save the configuration and test first!',
processingData: 'Processing data...',
updatingData: 'Uploading data...',
syncDataSuccess: 'Synchronized data successfully!',
syncDataFailed: 'Failed to synchronize data, please check the error message on the console!',
downloadingData: 'Downloading data...',
checkedNoData: 'No remote data is detected, please confirm whether the configuration is correct!',
savingData: 'Saving data...',
syncHistory: 'Synchronize tasks history',
checkUpdateFailed: 'Check update failed',
newVersionNotice: 'Checked a new version V%0, <a class="high-light" href="%1" target="_blank">click to update</a>',
language: 'Language</br> Currently only supports zh: Chinese, en: English',
gistOptions: 'Gist Settings',
swalNotice: 'It is detected that you are installing the V4 version script for the first time' + ', please go to read the READ ME FIRST content before use!',
echoNotice: 'It is detected that you are installing the V4 version script for the first time' + ', please <a class="high-light" href="%0" target="_blank">click here</a> to read the READ ME FIRST content before use!',
noticeLink: 'https://auto-task-doc.js.org/en/guide/#read-me-first',
toGithub: 'Feedback(Github)',
toKeylol: 'Feedback(Keylol)',
copySuccess: 'The error message has been copied to the clipboard. Do you want to go to the Keylol forum to give feedback?',
copyFailed: 'Please copy the error information below and report back to the Keylol forum!',
updateText: 'Updates in version %0:',
Active: 'Active',
Ended: 'Ended',
Banned: 'Banned',
Paused: 'Paused',
notStart: 'notStart',
noRemoteData: 'No data remotely',
errorRemoteDataFormat: 'Remote data has wrong format',
updateHistory: '<a class="high-light" href="https://auto-task-doc.js.org/logs/" target="_blank">Click here</a>' + ' to view the historical update record.',
AsfEnabled: 'Use ASF to do Steam related tasks (requires <a href="https://github.com/chr233/ASFEnhance" target="_blank">ASFEnhance</a> plugin)',
steamWeb: 'Use Steam Web API to do Steam related tasks simultaneously',
preferASF: 'Prefer ASF to do Steam related tasks',
AsfIpcUrl: 'ASF IPC URL',
AsfIpcPassword: 'ASF IPC Password',
curatorLimitNotice: 'Failed: Maybe the curator follow limit is reached, please <a class="high-light" href="https://store.steampowered.com/curators/home/" target="_blank">unfollow some curators</a> and try again',
unknownScriptHandler: 'Unknown script handler, compatibility unknown',
debugModeNotice: 'Detected DEBUG mode enabled, please close it if it is not needed!',
steamWebApiKey: 'Steam Web API Key<br>Used to detect game idle status, <a href="https://steamcommunity.com/dev/apikey" target="_blank">click to apply</a>',
groups: 'Group',
officialGroups: 'Official Group',
wishlists: 'Wishlist',
follows: 'Follow Game',
forums: 'Forum',
workshops: 'Favorite Workshop',
curators: 'Curator',
workshopVotes: 'Voteup Workshop',
announcements: 'Announcement',
steamCommunity: 'Steam Community',
steamStore: 'Steam Store',
licenses: 'Add License',
playtests: 'Playtest Access',
needLoginSteamStore: 'Please <a href="https://store.steampowered.com/login/" target="_blank">log in to the Steam Store</a>',
needLoginSteamCommunity: 'Please <a href="https://steamcommunity.com/login/home/" target="_blank">log in to the Steam Community</a>',
joiningSteamGroup: 'Joining Steam Group',
leavingSteamGroup: 'Leaving Steam Group',
gettingSteamGroupId: 'Getting Steam Group Id',
joiningSteamOfficialGroup: 'Joining Steam Official Group',
leavingSteamOfficialGroup: 'Leaving Steam Official Group',
gettingSteamOfficialGroupId: 'Getting Steam Official Group Id',
subscribingForum: 'Subscribing the Steam Forum',
unsubscribingForum: 'Unsubscribing the Steam Forum',
gettingForumId: 'Getting Steam Forum Id',
followingCurator: 'Following Steam Curator',
unfollowingCurator: 'Unfollowing Steam Curator',
gettingCuratorId: 'Getting Steam Curator Id',
addingToWishlist: 'Adding the game to the Steam wishlist',
removingFromWishlist: 'Removing the game from the Steam wishlist',
followingGame: 'Following Steam games',
unfollowingGame: 'Unfollowing Steam games',
favoritingWorkshop: 'Favouring Steam Workshop Items',
unfavoritingWorkshop: 'Unfavoriting Steam Workshop Items',
gettingWorkshopAppId: 'Getting Steam Workshop Item Id',
votingUpWorkshop: 'Liking Steam workshop items',
gettingAnnouncementParams: 'Getting Steam announcement information',
likingAnnouncement: 'Liking Steam announcement',
changingArea: 'Changing Steam area: %0...',
notNeededChangeArea: 'The current area does not need to be changed',
noAnotherArea: 'Please check whether the proxy is turned on correctly',
gettingAreaInfo: 'Getting Steam area information...',
changeAreaNotice: 'Suspected of a locked zone game, try to change the zone to execute',
steamFinishNotice: 'Steam task completed, try to change the shopping cart area back to ',
gettingSubid: 'Getting subid',
addingFreeLicense: 'Adding free license',
missParams: 'Missing parameters',
gettingLicenses: 'Getting licenses...',
requestingPlayTestAccess: 'Requesting play test access',
gettingDemoAppid: 'Getting demo appid for steam game',
tryChangeAreaNotice: 'This function cannot detect whether the game is limited, so it will try to change the area before entering the library' + '. Failure to change the area will not affect the subsequent storage.',
versionNotMatched: 'The script manager version is too low, requiring TamperMonkey >= 5.2.0 or TamperMonkey Beta >= 5.2.6196',
gettingUserInfo: 'Getting steam user community credentials...',
retry: 'Retry',
owned: 'Owned',
redirect: 'Redirect',
noSubid: 'skip due to unrecognized',
noASFInstance: 'ASF is not enabled, skip idle time task',
initingASF: 'Initing ASF...',
playingGames: 'Playing games [%0]...',
stoppingPlayGames: 'Stopping play games...',
stopPlayTimeTitle: 'The Steam game idle time has finished. Do you want to end it?',
stopPlayTimeText: 'Time out: %0 minutes',
ASFNotSupportted: 'The current function (%0) cannot be implemented by ASF, skipping',
checkingPlayGamesStatus: 'Checking play games status...',
gettingSteamId: 'Getting Steam ID...',
checkingPlayStatus: 'Checking play status...',
noPlayStatus: 'Game not running',
servers: 'Server',
joiningDiscordServer: 'Joining Discord Server',
leavingDiscordServer: 'Leaving Discord Server',
gettingDiscordGuild: 'Getting Discord server Id',
getDiscordAuthFailed: 'Failed to get Discord token, please check whether the Discord account is logged in',
discordImportantNotice: 'Important Reminder! ! !',
discordImportantNoticeText: 'Due to the background update of the Discord website, currently using this script to join a group may cause the Discord account to be forcibly logged out, and two-step verification is required to log in normally, please use it with caution! ! !',
continueDiscordTask: 'Do Discord tasks this time.',
skipDiscordTask: 'Skip Discord tasks this time.',
continueAndDontRemindAgain: 'Always do Discord tasks and do not remind again.',
gettingDiscordXContextProperties: 'Getting Discord X context properties...',
captchaNeeded: 'Captcha detected, please complete it manually!',
users: 'User',
loginIns: 'Please <a href="https://www.instagram.com/accounts/login/" target="_blank">log in to Instagram</a>',
insBanned: 'Your Instagram account has been banned',
verifyingInsAuth: 'Verifying Instagram token...',
gettingInsUserId: 'Getting Instagram user Id',
followingIns: 'Following Instagram user',
unfollowingIns: 'Unfollowing Instagram user',
reddits: 'Reddit/User',
loginReddit: 'Please <a href="https://www.reddit.com/login/" target="_blank">log in to Reddit</a>',
changingRedditVersion: 'Switching Reddit to a new version page...',
joiningReddit: 'Joining the Reddit',
leavingReddit: 'Leaving the Reddit',
followingRedditUser: 'Following Reddit User',
unfollowingRedditUser: 'Unfollowing Reddit User',
channels: 'Channel',
followingTwitchChannel: 'Following Twitch Channel',
unfollowingTwitchChannel: 'Unfollowing Twitch Channel',
gettingTwitchChannelId: 'Getting Twitch Channel Id',
checkingTwitchIntegrity: 'Checking Twitch integrity...',
twitterUser: 'Twitter User',
retweets: 'Retweet',
gettingTwitterUserId: 'Getting Twitter User Id',
followingTwitterUser: 'Following Twitter User',
unfollowingTwitterUser: 'Unfollowing Twitter User',
retweetting: 'Retweetting',
unretweetting: 'Unretweetting',
names: 'Group/Public/Wall',
loginVk: 'Please <a href="https://vk.com/login/" target="_blank">log in to Vk</a>',
gettingVkId: 'Getting Vk task Id',
joiningVkGroup: 'Joining Vk Group',
leavingVkGroup: 'Leaving Vk Group',
joiningVkPublic: 'Joining Vk Public',
leavingVkPublic: 'Leaving Vk Public',
sendingVkWall: 'Sending Vk Wall',
deletingVkWall: 'Deleting Vk Wall',
youtubeChannel: 'YouTube Channel',
likes: 'Like',
loginYtb: 'Please <a href="https://accounts.google.com/ServiceLogin?service=youtube" target="_blank">log in to YouTube</a>',
tryUpdateYtbAuth: 'Please try to <a href="https://www.youtube.com/#auth" target="_blank">update YouTube token</a>',
gettingYtbToken: 'Getting YouTube Token...',
followingYtbChannel: 'Subscribing to YouTube channel',
unfollowingYtbChannel: 'Unsubscribing to YouTube channel',
likingYtbVideo: 'Liking YouTube video',
unlikingYtbVideo: 'Unliking YouTube video',
giveKeyNoticeBefore: 'Each verification interval is 15s',
giveKeyNoticeAfter: 'If there is no key, please check at <a href="https://givekey.ru/profile" target="_blank">https://givekey.ru/profile</a>',
noPoints: 'Not enough points, skip the lottery',
getNeedPointsFailed: 'ailed to obtain the required points, skip the lottery',
joiningLottery: 'Joining the lottery',
doingGleamTask: 'Doing Gleam Task...',
gettingGleamLink: 'Getting Gleam task link...',
gleamTaskNotice: 'If this page has not been closed for a long time, please close it yourself after completing any task!',
verifiedGleamTasks: 'Attempted to verify all tasks. If the verification fails, please try to verify manually or complete it!',
campaign: 'ReCAPTCHA detected, please complete it manually! 3 seconds later, re-verify...',
gsNotice: 'In order to avoid getting the "0000-0000-0000" key, the "Grab Key" button has been hidden,' + ' please close the script when obtaining the key!',
giveeClubVerifyNotice: 'Verifying task...',
giveeClubVerifyFinished: 'Wait for the verification to complete and join it by yourself',
doingKeyhubTask: 'Doing Keyhub Task...',
SweepWidgetNotice: 'The task is being processed and verified. ' + 'There is an interval of 1~3s for each verification task to prevent the triggering of too fast verification warning...',
tasksNotCompleted: 'Tasks Not Completed',
notConnect: 'Social platform is not connectted, skip task: %0',
tgTaskNotice: 'The telegram task is checked, need to do it yourself!',
updatingUserData: 'Updating user data...',
gettingUserGames: 'Getting user games...',
confirmingTask: 'Confirming task...',
unSupporttedTaskType: 'Unsupportted task type: %0',
taskNotFinished: 'There are tasks not completed, do not get the key',
logCopied: 'Full log has been copied to the clipboard, please go to feedback!'
};
const languages = {
zh: data$1,
en: data
};
const SUPPORTED_LANGUAGES = [ 'zh', 'en' ];
const getCurrentLanguage = () => {
const userLanguage = globalOptions.other.language;
return SUPPORTED_LANGUAGES.includes(userLanguage) ? userLanguage : 'en';
};
const replacePlaceholders = (text, args) => text.replace(/%([\d]+)/g, ((_, index) => args[parseInt(index, 10)] || ''));
const I18n = (key, ...args) => {
const currentLanguage = getCurrentLanguage();
const translation = languages[currentLanguage]?.[key];
if (!translation) {
console.warn(`Missing translation for key: ${key} in language: ${currentLanguage}`);
return key;
}
return replacePlaceholders(translation, args);
};
var DebugLevel;
(function(DebugLevel) {
DebugLevel['ERROR'] = 'error';
DebugLevel['WARN'] = 'warn';
DebugLevel['INFO'] = 'info';
DebugLevel['DEBUG'] = 'debug';
DebugLevel['TRACE'] = 'trace';
})(DebugLevel || (DebugLevel = {}));
const defaultConfig = {
enabled: false,
level: DebugLevel.INFO,
prefix: 'Auto-Task',
styles: {
error: 'color:#ff0000;font-weight:bold',
warn: 'color:#ffa500',
info: 'color:#a7a7a7',
debug: 'color:#808080',
trace: 'color:#87ceeb'
},
showTimestamp: true
};
class Debugger {
config;
levelPriority;
constructor(config = {}) {
this.config = {
...defaultConfig,
...config
};
this.levelPriority = {
error: 0,
warn: 1,
info: 2,
debug: 3,
trace: 4
};
}
getTimestamp() {
return (new Date).toLocaleString();
}
shouldLog(level) {
return this.config.enabled && this.levelPriority[level] <= this.levelPriority[this.config.level];
}
formatMessage(level, message) {
const parts = [ this.config.prefix ];
if (this.config.showTimestamp) {
parts.push(`[${this.getTimestamp()}]`);
}
parts.push(`[${level.toUpperCase()}]:`);
parts.push(message);
return parts.join(' ');
}
log(level, message, ...args) {
if (!this.shouldLog(level)) {
return;
}
const formattedMessage = this.formatMessage(level, message);
const style = this.config.styles[level];
if (args.length > 0) {
console.groupCollapsed('%c%s', style, formattedMessage);
args.forEach((arg => {
console.log(util.inspect(arg, {
showHidden: true,
depth: null,
colors: false
}));
}));
console.groupEnd();
} else {
console.log('%c%s', style, formattedMessage);
}
}
error(message, ...args) {
this.log(DebugLevel.ERROR, message, ...args);
}
warn(message, ...args) {
this.log(DebugLevel.WARN, message, ...args);
}
info(message, ...args) {
this.log(DebugLevel.INFO, message, ...args);
}
debug(message, ...args) {
this.log(DebugLevel.DEBUG, message, ...args);
}
trace(message, ...args) {
this.log(DebugLevel.TRACE, message, ...args);
if (this.shouldLog(DebugLevel.TRACE)) {
console.trace();
}
}
updateConfig(config) {
this.config = {
...this.config,
...config
};
}
enable() {
this.config.enabled = true;
}
disable() {
this.config.enabled = false;
}
setLevel(level) {
this.config.level = level;
}
}
let debugInstance;
const initDebug = () => {
if (!debugInstance) {
debugInstance = new Debugger({
enabled: window.DEBUG || false
});
if (window.DEBUG) {
debugInstance.setLevel(DebugLevel.DEBUG);
}
}
return debugInstance;
};
const debug = (...args) => {
const instance = initDebug();
return instance.debug(...args);
};
const getRunLogs = () => {
debug('开始获取运行日志');
const logElements = $('#auto-task-info>li');
const logs = logElements.length > 0 ? $.makeArray(logElements).map((element => element.innerText)).join('\n') : '';
debug('运行日志获取完成', {
logsLength: logs.length
});
return logs;
};
const getEnvironmentInfo = async () => {
debug('开始获取环境信息');
const envInfo = {
website: window.location.href,
browser: JSON.stringify(await browser.getInfo(), null, 2),
manager: `${GM_info.scriptHandler} ${GM_info.version}`,
userScript: GM_info.script.version,
logs: '',
runLogs: getRunLogs()
};
debug('环境信息获取完成', envInfo);
return envInfo;
};
const buildGithubIssueParams = async (name, errorStack, envInfo) => {
debug('开始构建GitHub Issue参数', {
name: name,
errorStackLength: errorStack.length
});
const params = {
title: `[BUG] 脚本报错: ${name}`,
labels: 'bug',
template: 'bug_report.yml',
website: envInfo.website,
browser: envInfo.browser,
manager: envInfo.manager,
'user-script': envInfo.userScript,
logs: errorStack || '',
'run-logs': ''
};
const runLogs = window.__allLogs.join('\n');
await GM_setClipboard(runLogs);
debug('GitHub Issue参数构建完成', params);
return params;
};
const generateGithubLink = async (name, errorStack, envInfo) => {
debug('开始生成GitHub Issue链接');
const params = new URLSearchParams(await buildGithubIssueParams(name, errorStack, envInfo));
const link = `https://github.com/HCLonely/auto-task/issues/new?${params.toString()}`;
debug('GitHub Issue链接生成完成', {
link: link
});
return link;
};
const logError = (name, errorStack) => {
debug('记录错误日志', {
name: name
});
console.log('%c%s', 'color:white;background:red', `Auto-Task[Error]: ${name}\n${errorStack}`);
};
const handleErrorReport = async (platform, name, errorStack, envInfo) => {
debug('开始处理错误报告', {
platform: platform,
name: name
});
{
const githubLink = await generateGithubLink(name, errorStack, envInfo);
debug('打开GitHub Issue链接', {
githubLink: githubLink
});
GM_openInTab(githubLink, {
active: true
});
}
};
async function throwError(error, name) {
debug('开始处理错误', {
name: name,
error: error
});
if (window.TRACE) {
debug('启用跟踪模式');
console.trace('%cAuto-Task[Trace]:', 'color:blue');
}
const errorStack = error.stack || '';
logError(name, errorStack);
debug('获取环境信息');
const envInfo = await getEnvironmentInfo();
envInfo.logs = errorStack;
debug('显示错误报告对话框');
const {isConfirmed: isConfirmed} = await Swal.fire({
title: I18n('errorReport'),
icon: 'error',
showCancelButton: true,
confirmButtonText: I18n('toGithub'),
cancelButtonText: I18n('close')
});
if (isConfirmed) {
debug('用户确认提交错误报告');
await handleErrorReport('github', name, errorStack, envInfo);
Swal.fire({
title: I18n('logCopied'),
icon: 'success',
showConfirmButton: false,
showCancelButton: true,
cancelButtonText: I18n('close')
});
} else {
debug('用户取消提交错误报告');
}
}
const parseHeaders = headerString => {
debug('开始解析HTTP头', {
headerString: headerString
});
const headers = {};
if (!headerString) {
debug('HTTP头为空,返回空对象');
return headers;
}
headerString.split('\n').forEach((header => {
const [name, ...values] = header.trim().split(':');
const value = values.join(':').trim();
if (!name || !value) {
return;
}
if (headers[name]) {
headers[name] = Array.isArray(headers[name]) ? [ ...headers[name], value ] : [ headers[name], value ];
} else {
headers[name] = value;
}
}));
if (headers['set-cookie'] && !Array.isArray(headers['set-cookie'])) {
headers['set-cookie'] = [ headers['set-cookie'] ];
}
debug('HTTP头解析完成', {
headers: headers
});
return headers;
};
const processResponse = (data, options) => {
debug('开始处理响应数据', {
responseType: options.responseType
});
const headers = parseHeaders(data.responseHeaders);
data.responseHeadersText = data.responseHeaders;
data.responseHeaders = headers;
data.finalUrl = headers.location || data.finalUrl;
debug('响应头处理完成', {
finalUrl: data.finalUrl
});
if (options.responseType === 'json' && data?.response && typeof data.response !== 'object') {
debug('尝试解析JSON响应');
try {
data.response = JSON.parse(data.responseText);
debug('JSON解析成功');
} catch {
debug('JSON解析失败,保持原始响应');
}
}
};
const httpRequest = async (options, times = 0) => {
debug('开始HTTP请求', {
url: options.url,
method: options.method,
retryTimes: times
});
if (window.TRACE) {
console.trace('%cAuto-Task[Trace]:', 'color:blue');
}
try {
const result = await new Promise((resolve => {
const requestObj = {
fetch: true,
timeout: 3e4,
ontimeout: data => {
debug('请求超时', {
url: options.url
});
resolve({
result: 'Error',
statusText: 'Timeout',
status: 601,
data: data,
options: options
});
},
onabort: () => {
debug('请求被中止', {
url: options.url
});
resolve({
result: 'Error',
statusText: 'Aborted',
status: 602,
data: undefined,
options: options
});
},
onerror: data => {
debug('请求发生错误', {
url: options.url,
error: data
});
resolve({
result: 'Error',
statusText: 'Error',
status: 603,
data: data,
options: options
});
},
onload: data => {
debug('请求加载完成', {
url: options.url,
status: data.status
});
processResponse(data, options);
resolve({
result: 'Success',
statusText: 'Load',
status: 600,
data: data,
options: options
});
},
...options,
responseType: options.dataType || options.responseType
};
debug('发送请求', {
requestObj: requestObj
});
GM_xmlhttpRequest(requestObj);
}));
if (window.DEBUG) {
console.log('%cAuto-Task[httpRequest]:', 'color:blue', result);
}
if (result.status !== 600 && times < 2) {
debug('请求失败,准备重试', {
status: result.status,
retryTimes: times + 1
});
return await httpRequest(options, times + 1);
}
debug('请求完成', {
status: result.status,
result: result.result
});
return result;
} catch (error) {
debug('请求发生JavaScript错误', {
error: error
});
console.log('%cAuto-Task[httpRequest]:', 'color:red', JSON.stringify({
errorMsg: error,
options: options
}));
throwError(error, 'httpRequest');
return {
result: 'JsError',
statusText: 'Error',
status: 604,
error: error,
options: options
};
}
};
var ASF = '<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" version="1.1">\n<g id="surface1">\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.372549%,17.254902%,26.274511%);fill-opacity:1;" d="M 7.652344 0.175781 C 8.046875 0.351562 8.210938 0.59375 8.441406 0.953125 C 9.007812 1.746094 9.644531 2.539062 10.640625 2.761719 C 12.011719 2.949219 13.429688 2.882812 14.808594 2.796875 C 14.90625 2.792969 15.003906 2.785156 15.101562 2.78125 C 15.1875 2.773438 15.273438 2.769531 15.363281 2.761719 C 15.429688 2.769531 15.496094 2.777344 15.566406 2.78125 C 15.761719 3.082031 15.757812 3.191406 15.75 3.539062 C 15.75 3.625 15.746094 3.710938 15.746094 3.800781 C 15.742188 3.867188 15.742188 3.933594 15.738281 4 C 15.824219 4.027344 15.910156 4.058594 16 4.085938 C 15.988281 4.34375 15.976562 4.597656 15.960938 4.851562 C 15.957031 4.925781 15.957031 5 15.953125 5.074219 C 15.945312 5.175781 15.945312 5.175781 15.941406 5.285156 C 15.9375 5.378906 15.9375 5.378906 15.929688 5.480469 C 15.914062 5.652344 15.914062 5.652344 15.824219 5.914062 C 15.816406 6.058594 15.8125 6.203125 15.8125 6.34375 C 15.8125 6.433594 15.8125 6.519531 15.808594 6.613281 C 15.808594 6.707031 15.808594 6.800781 15.808594 6.898438 C 15.804688 7.097656 15.804688 7.300781 15.800781 7.5 C 15.796875 7.816406 15.792969 8.132812 15.792969 8.449219 C 15.789062 8.753906 15.785156 9.058594 15.78125 9.363281 C 15.78125 9.457031 15.78125 9.550781 15.78125 9.648438 C 15.777344 9.738281 15.777344 9.824219 15.773438 9.914062 C 15.773438 10.03125 15.773438 10.03125 15.773438 10.148438 C 15.738281 10.347656 15.738281 10.347656 15.625 10.503906 C 15.386719 10.671875 15.152344 10.625 14.871094 10.609375 C 14.867188 10.675781 14.859375 10.742188 14.855469 10.808594 C 14.816406 11.367188 14.746094 11.890625 14.609375 12.433594 C 14.3125 12.535156 14.105469 12.507812 13.804688 12.441406 C 13.6875 12.414062 13.6875 12.414062 13.570312 12.390625 C 13.480469 12.367188 13.480469 12.367188 13.390625 12.347656 C 13.359375 12.515625 13.359375 12.515625 13.324219 12.683594 C 13.226562 13.144531 13.054688 13.566406 12.871094 14 C 12.75 13.988281 12.628906 13.980469 12.511719 13.96875 C 12.410156 13.957031 12.410156 13.957031 12.308594 13.949219 C 12.050781 13.90625 11.8125 13.828125 11.566406 13.738281 C 11.492188 13.867188 11.492188 13.867188 11.417969 14 C 11.355469 14.113281 11.292969 14.222656 11.226562 14.335938 C 11.195312 14.394531 11.164062 14.449219 11.132812 14.507812 C 11 14.738281 10.882812 14.941406 10.695312 15.128906 C 10.257812 15.097656 9.957031 14.863281 9.601562 14.625 C 9.402344 14.503906 9.402344 14.503906 9.222656 14.542969 C 9.015625 14.617188 8.925781 14.703125 8.777344 14.863281 C 8.730469 14.914062 8.683594 14.964844 8.632812 15.019531 C 8.535156 15.125 8.433594 15.234375 8.335938 15.339844 C 7.972656 15.726562 7.972656 15.726562 7.675781 15.792969 C 7.4375 15.726562 7.367188 15.660156 7.21875 15.460938 C 7 15.1875 6.773438 14.941406 6.523438 14.695312 C 6.375 14.550781 6.230469 14.40625 6.085938 14.261719 C 6.023438 14.195312 5.957031 14.128906 5.890625 14.0625 C 2.175781 10.347656 2.175781 10.347656 1.945312 10.117188 C 1.738281 9.910156 1.527344 9.707031 1.316406 9.503906 C 1.160156 9.351562 1 9.199219 0.84375 9.042969 C 0.722656 8.925781 0.722656 8.925781 0.597656 8.808594 C 0.519531 8.734375 0.445312 8.660156 0.363281 8.582031 C 0.296875 8.515625 0.226562 8.445312 0.152344 8.375 C -0.03125 8.132812 -0.0351562 8.039062 0 7.738281 C 0.117188 7.570312 0.117188 7.570312 0.28125 7.402344 C 0.34375 7.339844 0.40625 7.277344 0.472656 7.210938 C 0.542969 7.144531 0.613281 7.074219 0.683594 7.003906 C 0.757812 6.933594 0.828125 6.859375 0.902344 6.785156 C 1.101562 6.585938 1.304688 6.386719 1.503906 6.1875 C 1.714844 5.980469 1.921875 5.773438 2.132812 5.5625 C 2.480469 5.214844 2.832031 4.863281 3.183594 4.515625 C 3.683594 4.023438 4.175781 3.53125 4.671875 3.039062 C 5.015625 2.695312 5.359375 2.355469 5.699219 2.015625 C 5.785156 1.929688 5.867188 1.847656 5.949219 1.765625 C 6.222656 1.492188 6.496094 1.222656 6.773438 0.949219 C 6.839844 0.882812 6.910156 0.8125 6.980469 0.742188 C 7.078125 0.648438 7.078125 0.648438 7.171875 0.550781 C 7.226562 0.496094 7.285156 0.441406 7.339844 0.386719 C 7.476562 0.261719 7.476562 0.261719 7.652344 0.175781 Z M 7.652344 0.175781 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(5.490196%,7.843138%,14.901961%);fill-opacity:1;" d="M 10.433594 2.78125 C 11.101562 2.777344 11.765625 2.773438 12.433594 2.769531 C 12.742188 2.769531 13.050781 2.765625 13.359375 2.761719 C 13.714844 2.757812 14.070312 2.757812 14.425781 2.757812 C 14.539062 2.753906 14.648438 2.753906 14.761719 2.753906 C 14.867188 2.753906 14.96875 2.753906 15.074219 2.753906 C 15.210938 2.753906 15.210938 2.753906 15.347656 2.75 C 15.421875 2.761719 15.492188 2.773438 15.566406 2.78125 C 15.761719 3.082031 15.757812 3.191406 15.75 3.539062 C 15.75 3.625 15.746094 3.710938 15.746094 3.800781 C 15.742188 3.867188 15.742188 3.933594 15.738281 4 C 15.824219 4.027344 15.910156 4.058594 16 4.085938 C 15.988281 4.332031 15.980469 4.578125 15.96875 4.824219 C 15.964844 4.929688 15.964844 4.929688 15.960938 5.035156 C 15.9375 5.492188 15.863281 5.90625 15.738281 6.347656 C 15.675781 6.3125 15.613281 6.273438 15.546875 6.234375 C 15.011719 5.921875 14.472656 5.609375 13.9375 5.296875 C 13.761719 5.195312 13.582031 5.089844 13.40625 4.988281 C 12.996094 4.746094 12.585938 4.507812 12.167969 4.273438 C 10.847656 3.527344 10.847656 3.527344 10.433594 2.957031 C 10.433594 2.898438 10.433594 2.839844 10.433594 2.78125 Z M 10.433594 2.78125 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(95.686275%,96.078432%,96.470588%);fill-opacity:1;" d="M 7.566406 5.304688 C 7.878906 5.410156 7.90625 5.480469 8.058594 5.761719 C 8.117188 5.859375 8.117188 5.859375 8.175781 5.964844 C 8.269531 6.195312 8.277344 6.363281 8.261719 6.609375 C 8.355469 6.550781 8.449219 6.492188 8.550781 6.433594 C 8.777344 6.296875 8.855469 6.261719 9.128906 6.261719 C 9.136719 6.425781 9.140625 6.589844 9.148438 6.753906 C 9.152344 6.894531 9.152344 6.894531 9.15625 7.035156 C 9.125 7.34375 9.058594 7.492188 8.871094 7.738281 C 8.703125 7.871094 8.703125 7.871094 8.527344 7.972656 C 8.46875 8.007812 8.410156 8.042969 8.351562 8.078125 C 8.292969 8.109375 8.234375 8.140625 8.175781 8.175781 C 8.113281 8.207031 8.054688 8.238281 7.996094 8.273438 C 7.710938 8.398438 7.511719 8.335938 7.21875 8.261719 C 6.832031 8.070312 6.476562 7.859375 6.261719 7.476562 C 6.167969 7.203125 6.164062 7.007812 6.167969 6.722656 C 6.167969 6.636719 6.171875 6.550781 6.171875 6.460938 C 6.171875 6.394531 6.171875 6.328125 6.175781 6.261719 C 6.492188 6.332031 6.753906 6.457031 7.042969 6.609375 C 7.050781 6.480469 7.050781 6.480469 7.058594 6.347656 C 7.113281 5.9375 7.332031 5.636719 7.566406 5.304688 Z M 7.566406 5.304688 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(5.882353%,8.235294%,15.294118%);fill-opacity:1;" d="M 15.652344 6.433594 C 15.679688 6.433594 15.710938 6.433594 15.738281 6.433594 C 15.746094 6.988281 15.75 7.539062 15.753906 8.09375 C 15.753906 8.28125 15.757812 8.46875 15.757812 8.65625 C 15.761719 8.925781 15.761719 9.195312 15.765625 9.464844 C 15.765625 9.550781 15.765625 9.636719 15.769531 9.722656 C 15.769531 9.800781 15.769531 9.878906 15.769531 9.960938 C 15.769531 10.027344 15.769531 10.097656 15.769531 10.167969 C 15.730469 10.398438 15.664062 10.472656 15.476562 10.609375 C 15.15625 10.625 15.15625 10.625 14.871094 10.609375 C 14.867188 10.675781 14.859375 10.742188 14.855469 10.808594 C 14.816406 11.367188 14.746094 11.890625 14.609375 12.433594 C 14.3125 12.535156 14.105469 12.507812 13.804688 12.441406 C 13.726562 12.421875 13.648438 12.40625 13.570312 12.390625 C 13.480469 12.367188 13.480469 12.367188 13.390625 12.347656 C 13.449219 12.027344 13.535156 11.730469 13.644531 11.425781 C 13.679688 11.332031 13.710938 11.238281 13.746094 11.144531 C 13.78125 11.046875 13.816406 10.953125 13.851562 10.851562 C 13.902344 10.710938 13.902344 10.710938 13.957031 10.566406 C 14.054688 10.289062 14.160156 10.015625 14.261719 9.738281 C 14.292969 9.65625 14.320312 9.574219 14.351562 9.488281 C 14.613281 8.792969 14.613281 8.792969 14.929688 8.523438 C 15.359375 8.140625 15.347656 7.753906 15.40625 7.203125 C 15.441406 6.886719 15.472656 6.707031 15.652344 6.433594 Z M 15.652344 6.433594 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(13.725491%,18.431373%,37.254903%);fill-opacity:1;" d="M 13.566406 9.824219 C 13.609375 9.914062 13.609375 9.914062 13.652344 10 C 13.539062 10.21875 13.417969 10.425781 13.289062 10.636719 C 13.207031 10.769531 13.125 10.90625 13.046875 11.039062 C 13.003906 11.109375 12.960938 11.183594 12.914062 11.253906 C 12.691406 11.632812 12.46875 12.015625 12.25 12.398438 C 12.207031 12.472656 12.160156 12.550781 12.117188 12.628906 C 11.8125 13.160156 11.53125 13.703125 11.269531 14.253906 C 11.113281 14.582031 10.933594 14.855469 10.695312 15.128906 C 10.3125 15.078125 10.042969 14.933594 9.710938 14.734375 C 9.574219 14.648438 9.574219 14.648438 9.429688 14.566406 C 9.359375 14.523438 9.289062 14.480469 9.21875 14.433594 C 9.253906 14.046875 9.523438 13.835938 9.789062 13.582031 C 9.832031 13.539062 9.875 13.496094 9.921875 13.449219 C 10.15625 13.21875 10.402344 12.996094 10.65625 12.78125 C 11.15625 12.34375 11.621094 11.871094 12.089844 11.398438 C 12.25 11.238281 12.414062 11.078125 12.574219 10.914062 C 12.679688 10.8125 12.78125 10.707031 12.886719 10.605469 C 12.933594 10.554688 12.980469 10.507812 13.03125 10.457031 C 13.234375 10.253906 13.40625 10.066406 13.566406 9.824219 Z M 13.566406 9.824219 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(95.294118%,95.686275%,96.078432%);fill-opacity:1;" d="M 6.175781 10.347656 C 6.503906 10.464844 6.769531 10.617188 7.054688 10.820312 C 7.128906 10.871094 7.203125 10.925781 7.277344 10.976562 C 7.445312 11.105469 7.59375 11.234375 7.738281 11.390625 C 7.769531 11.316406 7.769531 11.316406 7.800781 11.238281 C 7.941406 10.992188 8.105469 10.882812 8.335938 10.722656 C 8.410156 10.671875 8.488281 10.617188 8.566406 10.5625 C 8.761719 10.449219 8.90625 10.386719 9.128906 10.347656 C 9.21875 11.453125 9.21875 11.453125 8.914062 11.832031 C 8.617188 12.113281 8.285156 12.273438 7.914062 12.433594 C 7.882812 12.921875 7.855469 13.410156 7.824219 13.914062 C 7.683594 13.914062 7.539062 13.914062 7.390625 13.914062 C 7.390625 13.398438 7.390625 12.878906 7.390625 12.347656 C 7.246094 12.320312 7.105469 12.289062 6.957031 12.261719 C 6.5625 12.078125 6.359375 11.875 6.175781 11.476562 C 6.144531 11.097656 6.152344 10.726562 6.175781 10.347656 Z M 6.175781 10.347656 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(8.235294%,11.764706%,23.137255%);fill-opacity:1;" d="M 14.433594 9.042969 C 14.523438 9.21875 14.523438 9.21875 14.484375 9.378906 C 14.460938 9.445312 14.4375 9.507812 14.414062 9.574219 C 14.386719 9.648438 14.359375 9.71875 14.335938 9.792969 C 14.308594 9.871094 14.277344 9.949219 14.25 10.027344 C 14.222656 10.105469 14.195312 10.183594 14.164062 10.265625 C 14.007812 10.703125 13.84375 11.132812 13.675781 11.566406 C 13.503906 11.996094 13.386719 12.429688 13.285156 12.878906 C 13.179688 13.269531 13.03125 13.632812 12.871094 14 C 12.378906 13.96875 12 13.910156 11.566406 13.652344 C 11.703125 13.144531 11.960938 12.71875 12.222656 12.265625 C 12.269531 12.1875 12.316406 12.105469 12.363281 12.023438 C 12.460938 11.859375 12.554688 11.695312 12.648438 11.53125 C 12.765625 11.332031 12.882812 11.128906 12.996094 10.925781 C 13.402344 10.222656 13.859375 9.621094 14.433594 9.042969 Z M 14.433594 9.042969 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.433594 2.78125 C 11.101562 2.777344 11.765625 2.773438 12.433594 2.769531 C 12.742188 2.769531 13.050781 2.765625 13.359375 2.761719 C 13.714844 2.757812 14.070312 2.757812 14.425781 2.757812 C 14.539062 2.753906 14.648438 2.753906 14.761719 2.753906 C 14.867188 2.753906 14.96875 2.753906 15.074219 2.753906 C 15.210938 2.753906 15.210938 2.753906 15.347656 2.75 C 15.421875 2.761719 15.492188 2.773438 15.566406 2.78125 C 15.761719 3.082031 15.757812 3.191406 15.75 3.539062 C 15.75 3.625 15.746094 3.710938 15.746094 3.800781 C 15.742188 3.867188 15.742188 3.933594 15.738281 4 C 14.917969 3.957031 14.128906 3.839844 13.324219 3.6875 C 13.066406 3.640625 12.808594 3.59375 12.546875 3.550781 C 12.363281 3.515625 12.175781 3.484375 11.992188 3.449219 C 11.78125 3.414062 11.566406 3.378906 11.355469 3.34375 C 11.226562 3.324219 11.226562 3.324219 11.097656 3.304688 C 11.027344 3.292969 10.953125 3.28125 10.878906 3.273438 C 10.664062 3.207031 10.570312 3.132812 10.433594 2.957031 C 10.433594 2.898438 10.433594 2.839844 10.433594 2.78125 Z M 10.433594 2.78125 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(95.686275%,96.078432%,96.470588%);fill-opacity:1;" d="M 9.128906 8.261719 C 9.183594 9.257812 9.183594 9.257812 9.011719 9.695312 C 8.605469 10.082031 8.152344 10.3125 7.597656 10.398438 C 7.09375 10.3125 6.6875 10.128906 6.347656 9.738281 C 6.074219 9.320312 6.136719 8.828125 6.175781 8.347656 C 6.539062 8.394531 6.765625 8.519531 7.066406 8.734375 C 7.140625 8.789062 7.214844 8.839844 7.292969 8.894531 C 7.476562 9.042969 7.476562 9.042969 7.566406 9.21875 C 7.621094 9.21875 7.679688 9.21875 7.738281 9.21875 C 7.835938 9.117188 7.933594 9.015625 8.03125 8.914062 C 8.738281 8.261719 8.738281 8.261719 9.128906 8.261719 Z M 9.128906 8.261719 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(7.843138%,11.372549%,22.745098%);fill-opacity:1;" d="M 11.042969 3.476562 C 11.648438 3.675781 12.246094 3.886719 12.84375 4.101562 C 12.984375 4.152344 12.984375 4.152344 13.125 4.207031 C 14.054688 4.542969 14.984375 4.878906 15.914062 5.21875 C 15.867188 5.597656 15.816406 5.972656 15.738281 6.347656 C 15.675781 6.3125 15.613281 6.273438 15.546875 6.234375 C 15.011719 5.921875 14.472656 5.609375 13.9375 5.296875 C 13.667969 5.140625 13.402344 4.984375 13.132812 4.828125 C 12.804688 4.636719 12.476562 4.445312 12.148438 4.253906 C 12.011719 4.179688 11.878906 4.101562 11.746094 4.023438 C 11.683594 3.988281 11.621094 3.949219 11.558594 3.914062 C 11.140625 3.671875 11.140625 3.671875 11.042969 3.476562 Z M 11.042969 3.476562 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0.392157%);fill-opacity:1;" d="M 15.652344 6.433594 C 15.679688 6.433594 15.710938 6.433594 15.738281 6.433594 C 15.746094 6.988281 15.75 7.539062 15.753906 8.09375 C 15.753906 8.28125 15.757812 8.46875 15.757812 8.65625 C 15.761719 8.925781 15.761719 9.195312 15.765625 9.464844 C 15.765625 9.550781 15.765625 9.636719 15.769531 9.722656 C 15.769531 9.839844 15.769531 9.839844 15.769531 9.960938 C 15.769531 10.027344 15.769531 10.097656 15.769531 10.167969 C 15.730469 10.394531 15.664062 10.472656 15.476562 10.609375 C 15.199219 10.625 15.199219 10.625 14.957031 10.609375 C 14.875 10.285156 14.871094 10.046875 14.933594 9.71875 C 14.949219 9.636719 14.964844 9.550781 14.980469 9.464844 C 15 9.378906 15.015625 9.289062 15.03125 9.199219 C 15.050781 9.113281 15.066406 9.027344 15.082031 8.933594 C 15.203125 8.289062 15.203125 8.289062 15.304688 8.085938 C 15.34375 7.792969 15.375 7.5 15.40625 7.203125 C 15.441406 6.886719 15.472656 6.707031 15.652344 6.433594 Z M 15.652344 6.433594 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(86.274511%,87.450981%,89.019608%);fill-opacity:1;" d="M 8.609375 3.390625 C 8.78125 3.390625 8.953125 3.390625 9.128906 3.390625 C 9.128906 4.109375 9.128906 4.824219 9.128906 5.566406 C 8.988281 5.59375 8.84375 5.621094 8.695312 5.652344 C 8.609375 5.566406 8.609375 5.566406 8.597656 5.355469 C 8.597656 5.265625 8.601562 5.175781 8.601562 5.082031 C 8.601562 4.984375 8.601562 4.886719 8.601562 4.785156 C 8.601562 4.683594 8.601562 4.578125 8.601562 4.472656 C 8.605469 4.371094 8.605469 4.265625 8.605469 4.160156 C 8.605469 3.902344 8.605469 3.648438 8.609375 3.390625 Z M 8.609375 3.390625 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(93.725491%,94.117647%,94.509804%);fill-opacity:1;" d="M 6.175781 3.390625 C 6.316406 3.390625 6.460938 3.390625 6.609375 3.390625 C 6.609375 4.136719 6.609375 4.882812 6.609375 5.652344 C 6.464844 5.625 6.320312 5.59375 6.175781 5.566406 C 6.175781 4.847656 6.175781 4.128906 6.175781 3.390625 Z M 6.175781 3.390625 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(93.333334%,93.725491%,94.117647%);fill-opacity:1;" d="M 7.476562 2.433594 C 7.59375 2.433594 7.707031 2.433594 7.824219 2.433594 C 7.824219 3.179688 7.824219 3.925781 7.824219 4.695312 C 7.683594 4.695312 7.539062 4.695312 7.390625 4.695312 C 7.390625 4.335938 7.386719 3.976562 7.386719 3.613281 C 7.386719 3.511719 7.382812 3.410156 7.382812 3.300781 C 7.382812 3.203125 7.382812 3.105469 7.382812 3.003906 C 7.382812 2.914062 7.382812 2.824219 7.382812 2.730469 C 7.390625 2.523438 7.390625 2.523438 7.476562 2.433594 Z M 7.476562 2.433594 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(5.490196%,7.843138%,14.901961%);fill-opacity:1;" d="M 15.390625 6.871094 C 15.511719 7.203125 15.449219 7.484375 15.390625 7.824219 C 15.304688 7.769531 15.21875 7.710938 15.128906 7.652344 C 15.171875 7.328125 15.207031 7.148438 15.390625 6.871094 Z M 15.390625 6.871094 "/>\n</g>\n</svg>\n';
var Web = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="16" height="16">\n\t<title>favicon</title>\n\t<defs>\n\t\t<image width="256" height="256" id="img1" href="data:image/png;base64,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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>\n';
var Discord$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="16" height="16">\n\t<title>favicon</title>\n\t<defs>\n\t\t<image width="256" height="256" id="img1" href="data:image/png;base64,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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>\n';
var Twitch$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16">\n\t<title>favicon-32-e29e246c157142c94346</title>\n\t<defs>\n\t\t<image width="28" height="32" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgBAMAAADzmwVxAAAAAXNSR0IB2cksfwAAACdQTFRFAAAAk0b/kUb/////wZf/yKP/j0b/j0X/j0b/kUb/j0D/kUb/j0j/8nvBLQAAAA10Uk5TAH//////gJCgzxDfIMV/78EAAACZSURBVHicY2RgYFRkgANGBgYhAWQuUFIQxnvPCJJE4oJ0InFBOpG4Sgxg7ntBMKI9F81ebFzGdyhc+SvIXKa3dshc+as3kbhMb+MawFygANNbBga2GwwwrvwFBoaKNBhXGCjJXp4A4xoAJWe4McC4QMA51QGZO8sFEuwMDEoQKQS3KoUBiQsyFIkb18CAxFW/yYDMTVgA4wIAtrc0x9ZZthYAAAAASUVORK5CYII="/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="2" y="0"/>\n</svg>\n';
var Instagram = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" width="16" height="16">\n\t<title>lam-fZmwmvn</title>\n\t<defs>\n\t\t<image width="76" height="76" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEwAAABMCAMAAADwSaEZAAAAAXNSR0IB2cksfwAAAvdQTFRFR3BM1TeUehf6exj7gBn8bxf9bxX9eA32hAnukgXlngTjqATisAPguQLewAThxAHdygLd0AHazwDU3gDA3gDB7ROe1gDY3wDI2gDHYBlFeBf9cxn8gRf9ihf9kQvx3ADW6AC/kRf9oQzxrw3v4wDX5gDO7AC0uAnomRf86wDV8ADG8QPR8wC69AqfoBf8qBf74BTc6xTW8wCr9xOVvzT24WHu3oP39qzy9sr5+N78/ej6/t31+Xrj+lbX9xvS9wnJ//7+//////n69QCesRn7/gLD/gG+/gGy/8vq/ivO/gG5//X7/QKq9wea6k3l+zrS+yq8/gCj+ACPuhr6/gq3/gCc7xbO/A/BxRr4/RO4/gCW/VDD/2nM/gCQ0hzz/7Pe/gGJ/gmw/UG4+h2r/gGC3Bru6Bvd/QJ79BrD/hOt/pDO5Rro/hKk9xu2/gJ08Brf+x6g/hKY/S+j/QJt/SCW/hOQ/SKQ/m+3/haH/SKI/lul/+vw/QBn/EOT/h2B/hV8/iF6/iR0/idu/uHk/i1//QFi/its/oar/T94/ixm/Spd/mON/lZw/snV/i5V/omZ/hNw/kNZ//r2/jNO/jtT/jNi/gBf/l1c/jpJ/nh//tfS/jlc/jFH/jND/khR/jk//T9D/hFn/ipM/kM6/js1/kZB/sS1/R1k/lA9/Us1/kEq/oN1/qmz/g5c/nNI/lEv/hZb/lg2/kgf/h5Z/lgq/lQX/l8m/mI4/iJS/mMY/oxO/mch/mcv/FEP/FwG/pdo/m0b/WUB/rd//mwC/nQY/nQn/m4s/nMC/qOJ/ndk/vHc/noV/noC/oQd/p0//oIS/nsi/unI/tuq/oIC/rA9/ooB/owT/vbn/pAE/pUP/pYB/pwI/p0A/qkf/tBr/GMY/XQD+3QI/qQC/qoC/td8+4QW/q8A/rsW/rQA/rgA/rsA/r4A/YUD/SGH/sEA/JUF/sUA/JIG/skA/DJm/JgH/asD/s0A/EFI+68J/bcE+1E0+0w6/L0Fi9deiwAAAP10Uk5TAA5AcL3X////////////////5a9+QP//zwL/nP////////////////7/////xP//////df/////////////////////////////////l/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+XBcP///z/////////V1f+M/6v/oavV/9VwwMFw5VUJIaQAAAukSURBVHicbZhbbBxXGYD/f2Zn9uZL7NiNL4nTFLnUqZpKlaqKRCBIgApIWhKE1PahPKAiRFNaCdSqL4U+0odKlUorQXkAoSCQQLRqSwlKpIKaFAJNlFtDTdzarr221/a63uvc+S9nZ9eB8Xr27Myc7/zXc/4zCOmBcph2wg35kZ7wxg9Kl9WkQ0gbltVp/y8q/Qik3XZYhHAt2QIbhWU7RQ3B2va1/y8QYEpUko4aZUqjAKVRReBYOSUBrA3D6nD5JmqVsSMJWLAFBJCHJuZa3IrWktHSaAm7WEOCoz4oJFjtkCzoVk1AdLQURe3qetIeilkpiS9lIshwawlTUofDnZ2A/tQUm/lmHty19ZES/x61ykpKx7cjRi2PLI8vqs9Svdq81EtAVDq7sO4wbHR5pBtFmCz4plViEDJhC4cFo3MozYawACplcgBmhg1pC4qaJbrKpDZIEe0jJILERKOnxrDsZRLV3mGlQZIhlAtYS1G7u+JHjpaVNhPU+Gr08PA9gP9CtEduQEENBiQXcAumjY0gcRDXYGhVaAz3XRYtuIzWmLGkaMgsx7Jy3CvR0w2sBKwYIWllNGHWYqH10GngItpjDApZKkENb9pZxhOqm4byW12olwK0XSiNLQKMc5QOAGxgZpfcMihwMjkjqbELSHc0jS2XchixQtGCoa3gbfKEq0oMbzg5MELxx8rfYLNmDGmEJej3IcyNQ3kMFkkwKOC+jj2GYcPJ8qMJeLEzKNb3b6BxeCZBxtEe/rAfcdws7t6k3/14pz7DsQb9C2L5wuqOOUAOgiEjcjsK0qOwNNRg+bI9VY5BBzYHiYpfTB/oh/kCs7wggFpv644rjBpc7zJgt1ebkcteCosO0zYH1/vp8jfaDzjBLBksQW9ojh/2KV/dwQpIzGjmqKGMHzJOOUtm9SeqDj/iAE0g+ABnqibKTJHGLq5Q4mLr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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>';
var Twitter$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="16" height="16">\n\t<title>icon-ios</title>\n\t<defs>\n\t\t<image width="1024" height="1024" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAMAAABIw9uxAAAAAXNSR0IB2cksfwAAAwBQTFRFAAAABAQEFxcXGBgYDAwMERERCgoKfn5+wMDAv7+/k5OTFhYWKioqp6enq6urJycnPDw83Nzc////+/v7f39/BwcHKSkpu7u78vLyc3NzXl5e7+/v6+vrU1NTExMTra2t9/f3e3t7BgYGCQkJiIiI+fn50tLSLy8vDw8Pjo6O/f39mJiYrq6ur6+vGRkZCAgIh4eH/v7+Gxsb1NTUhISEAQEBZGRk8PDwvLy8ICAgVVVV6enp7OzsVlZWAwMDWFhY4+Pj0NDQODg4AgIC1tbWNTU1QkJC4eHh2NjYPz8/pKSktLS0Li4uxcXFy8vL+vr6wsLC7u7ubW1tBQUFSEhI5eXl8/PzfHx89vb2Nzc3EhISkpKS/Pz8nZ2dEBAQDQ0NuLi4hYWFIyMjwcHBi4uLYmJi7e3tvr6+Q0ND3d3dXFxczMzMZ2dn8fHx29vbOzs72traQEBAj4+Pvb29Hh4eMjIyx8fHHR0dt7e3JiYmaGhoNjY2oaGh39/fmZmZpqamFRUVrKysl5eXZmZmw8PDJCQkMDAwz8/Pa2tr6urqycnJMzMzUFBQ4uLifX19+Pj4xMTE5+fnFBQUo6OjnJycISEhb29vR0dH5OTkRUVFmpqalJSUcHBw9fX1xsbGJSUleHh4Dg4ObGxsKCgodHR0QUFB5ubmPj4+1dXV4ODgGhoaubm5eXl5hoaGOTk56Ojom5ubkZGRXV1dd3d3qKioUVFRysrKRkZGqqqqtra29PT0aWlpg4ODHBwcCwsLlpaWenp6sbGxcXFxT09Pzs7O19fXSkpK3t7e2dnZIiIijIyMgICAYGBgbm5u0dHRUlJScnJylZWVsLCwurq6Tk5OVFRU09PTNDQ0Ojo6KysrPT09Y2NjZWVlW1tbjY2NtbW1yMjIYWFhn5+fWlpadnZ2TU1Nzc3NpaWlLS0tgYGBV1dXoqKiWVlZS0tLdXV1s7OzHx8fgoKCoKCgkJCQREREX19fTExMSUlJqampMTExLCwsiYmJsrKyampqioqKnp6enA0qUQAAUvtJREFUeJzt3X1gjmX/P/DrZFt5aNTQ4reNzTC5Lasrj9tks7Kk1iwNbR5KMVHa8rDGPM00NzeNodCEydMippIVlqhMSTGR4ksSIk+F7HfXfd9luGzXcb2P6zyv83i//vne9/fb8TnOb+y96zrO4/gcmoWIlKXp/QBEpB8GAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwB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ABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHC/j9GYmsPmw8LugAAAABJRU5ErkJggg=="/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>\n';
var Reddit$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" width="16" height="16">\n\t<title>192x192</title>\n\t<defs>\n\t\t<image width="192" height="192" id="img1" href="data:image/png;base64,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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>';
var Youtube$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="16" height="16">\n\t<title>favicon_48x48</title>\n\t<defs>\n\t\t<image width="48" height="48" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAAXNSR0IB2cksfwAAARRQTFRF////AAAA/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/xBA/wAz/56x/5uv/xZF/wAz/wAz/6Cz/////+/y/26L/wQ2/wAz/wAz/9Pc/0Bm/6m6/x1K/wAz//X3/3uV/wg5/wAz/93k/01x/7fF/yVR/yZR/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAzgb8Y2wAAAFx0Uk5TAAAJNFp0i52ptMDQ3ODl8Pj/99uoWS+z+zD6uxL+DEhAcm2Vkav/v////7zU///////T6P/////0////8///////8ufSlEEUDVe1/bkyOF13jKC2wd3mqp92XDdJ2BKpAAABRElEQVR4nO2VwUtCQRDG53vV0/DwEhNDXoREdCjC0CK6Rlj9vYHdg04SiSAUEUFE8gwJi0roNb0tHwS7Gzu3iOawh5nvt+zOzs6AhIZ/wAXA1/rdxl7ERMz+K3PukXlmwJwCRWDksHeGvd4nMDv14nic6ShOAK/kqk+Itz5QfnbWE/kRQjwJgIyPhaFATxSg8iAC8lgciIAClu41Z7FvByawHGnOEnBnA8pYvdWcoaqPazNQQe3KACQJx6URiBF4RoAoiwsD8I6tcwtAOaCrxVaQ13cJ008SoKMFfwTUO7WFgHcmAYxHEl56XZ7WnVMjYH84YWnU5MUnLu/dlj1qsIL8iwqbwAYaXVGbqcobGeYgbJW015Y14yTvdZw4yLeBw3SgzFdVNXTW0Dw42myNvWqANJrM+zyJURY4vlHS3zgU/wDwAXpXWYPo2LOhAAAAAElFTkSuQmCC"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>';
var Vk$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="16" height="16">\n\t<title>fav_logo</title>\n\t<defs>\n\t\t<image width="256" height="256" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAAAXNSR0IB2cksfwAAAKhQTFRFAAAAAHf/AHX/AHf/AHb/AHj/AHf/AHX/AHf/AHf/AHf/AHf/AHf/AHf/AHf/AHf/AHD/AHb/AHX/AHb/AHb/AHn/AHb/AHb/AHv/AHf/////QJn/IIj/gLv/T6L/YKr/7/f/v93/3+7/n8z/z+b/X6r/MJD/r9T/oMz/kMP/f7v/MJH/EH//cLL/cLP/UKH/j8P/sNT/b7P/7/b/z+X/j8T/r9X/AHf/9wxrvQAAADh0Uk5TACAwQFBgb4CQn6+/z9/v/xBfYM9wX3+gT7D//////////////////////////////////////4AQV6hAAAAMp0lEQVR4nO2da1fjuBnHJZtciZkwMztT2GHbnn3R/Qb7/V/uN+hpe/bs2d5oSxkgAZLgy1p2nDiJ/VwkJSYW/2E4QIKRftZz0WNZlsKGpJCpRP6R/c8/5S9K4DdhJUnxVVx8Vj+K1ef0k0GLV9JvnJCeJz2pPgwOYqQkiUUcJ+qf9jF02i59z/elp/0396E4VRTH/F/kARg/nPi+19QJJyiKwyhi/Qa9M+NJJz3v3BY1oTB6oUMg9mj81DmOzhcKu3c0v0DpVdr7E7PmNCIaAxyA3z/G3udaLFBbwAC8l6GlxjSj+OQr/AYYQLd/VIZfKQQB1EF/+Lpiva5ABPUA5PB4bX9bi1mtO6wF0IbRv1bSval5paaX45f2nP5cdYOgGoB32g7rLyueVhKoBHAetWn4F0p6/6v4aVVPP8333ZaG1Pvv7s8qALS2/5UEdgG0uP9VBHYAtLr/FQS2AZxrFFWOSt0tT7gFQAZt9P9lJdPNU7zZXzlqX/zfVuzflb/dBPB5dtC2NKPwsfzdBoDu4LBNaUid8rygDKD9DiBXMiklxeUuO2EASmUjKAF4zyuoH7Om666WAATtjwCF4snqyzUAhwZA2Q+uATg0AFI/+FB8tQLg1AAQwivqpCsATg0AIaLp8osCgD9qqikNqQgEBQBncoBCRS6wBCDPmmtKQ5L5nGgJwDEXqLSMhEsAp227DIBraQM5AActoLCBHICDFlDYQA7AQQsobCAH8K7RljSkxFM2kAFwLgvKleVCGYB+r+GmNKO5Sv4yAE66gKUTyAA46QKWTkABcNQFCDGJcwAfX5puSUNSmYACMOw03ZKGtHjOAXw47rWQ+lJVEQXAUR+YVwZTAF7QdEMa00OiAJycNt2OxjS4VgB6/abb0ZjSMCAdLAeulYYB6WwirJSGAencFYGy4okC4GwUTHWv7vl0sR5Y6CGR7k6FlKaRdDkNSCfCt9LduaBS50a6nAepqph0OQ9SmZB0OQ8SYvhvxwFEU+lyIqhSQSnO3FgeWq3kQTqdCae5sHQ6E05z4TcATk8FhJi4DmAqnZ4LCTGQTq6OWct/A+D0ZDCdD7sOYMYBALpLOYVeXSkA7kz1H+pfs32MQjPJWB8EAji5Jx3DRuPHQA2L2IyV5pJRDzlfAC927oAX17IB4D1wf7c3qX+tSgsOgA/QWw8HADpE//+kQ6x1jAAgCxjUbRZTp4VkFIS+eQJetHD6iIeAmnFasUkGqCEHAGR7hwPwGQo3XBcgQg6A30FHJw4+cwAXwLuGVfvEgGIBgP70wQBABxDv/kVqREksAJdQjO3dko5hDOBbyNme/4PUiJJYAM6geRNx9BkDgEIROwgKEUlGVRyKP9Q/bgoAbAM3D04VWwMgHqEXVzIEcPUE5SLsGMAEMAL3KKX9dUMAoAcguqENsQCADpjogMwABCfQdISdBQkmgCuQMC0XNgMABmINF8gEAE8HP/xKOYQRAHgI0hqwJR4AcDZEy4SMAIB/X8cFcgGAuTAtETABAHpA/kQwEw+ABRM0AAAbgN4ASAFwro7/HoT8/u+EQxgAgA1AbwCIRHKujv8BHOWksqg+ANgA+gutAZC2mgPAghfWBgDD18mCc7EAwIkAaRDqAgi6oAFo5QCZWABgMySFAU0A8BxALwnMxQMAxkGSF9QE8Ee4g5oeUIkHACyJkLygHgDYAYpuqOkBBRcAPCGmeEEtAEj/9T2g4AKAw8Dnn82OUNeP76/hY+p7QMEFAE+HLv6GH0ADwCek1GJiAGwA4KUBSkmADwA7/3qzwJWYAGAvSGgKGwBm/yYRQIkJAPaChJoUFwAS/1IHMKRMQerFBAB7wYs52hgegKtHbKdzMwcg2ABgL0hYnsAC8OUZvXStnwIuxQUAlgQI9sgBgLo/swwgFxcAXBLAbYAOIOjgG/3zrwXuiAsAKcug2TAVwFX8BBpbpsu/om9BxQWAOIEAG7VEAN/f4t03DgCZ2ABgJ4DaAAUA6ewbFIE2xAYAZwJoLoQDGJ09U7pvqf98AHBVCM2FQADvkt59ROq9tf7zASDTAcwNQgC6xL4r2eq/BgA4EGKpABJFqLq8ttR/DQBYF+DCmB0AvVML/j8XHwBmA3ByZgWAhfxnJQ0A4FIhLBLaAGCe/5akAQDrA9g+cwDdF9rCfKI0AGA2AA4BYwD23F8uHQBIHBDf/qX+NVMA73xr7i+XDoCrKRKwgSvVZgD6c6vDX0kHAHKBCEwHjQBYP/1CEwC8Xi7VN7/UvWIAYA+nX2gCwNwgMFE1ANBbvB4AmBus94NGJhA82o0ASnoAUDdYawSGUWAwt41ADwCWDabJwH+qW2oaBi+e/2l2gG1pAsCHQE0kMM8ErU2Ec2kCQK6RKVW7ARtzgbHNQaALgNCRyuqYldmgzUGgCwApjubHrghblgoi9gaBNgB45Xqmi9nu5XJLAMRgYCkp1AaAB4LKUGALQINF0UJ4IEhbOdgeA9YAiIvwFxuH0QdAGQK7VmAPgCVHYAAAWb1cvGkzFtgEYIWACQB0Uphps5VWAZguj1EyASD+RLpPaqOIZReACDqmwcAIACEUKvX99TIOCECvH9xCd+hX6XJmSMAIgPiOuERxPYlDL46OPp/ccHY5NSVgBgCtjKxUICCtDxidzekMDrtMbltEI0jVlxkC6gqRUTCjHtmMgCEA7EaODQ1mU84aId8nDgOjaGgKgDAvLqkvh4DX2LmkNCYiMCFgDOBqxnXctaq4pnbWISGor0KjMgbAcAOYqi4qBiPKldC6AhxB5gCICSFB1VdVSXagv2DMAgByNoCp5rJycElIONH1eXWyAYA0LySo9ro65fi6dw2wbp2tFaE+RlD9woJRDzcD/g4ySgnr5ul6kTNCSMDKCuS+SSU9N8C7e7xeVoKhwZ2jStCyhFrZAmCFALy2BidA3NRyQ9YA2CCArC9DCejcPmcPgLgaanmhkrANFFAC+95ICRNrWlAhrP0oAY2E0CoA03wAPYFfviJv4G+lZBeA+G5iMi/ARzCac7L9oGUAFF9dL4IJY1bGHgKxtPzIWVLiXiPjzdQEfwiw9hOkSd8RUJw45ga4Q2APAPTNgBTFML7MIbAPANqDgBbGkQtyzCGwHwCag4AGAKu/8IZAKPf04HFqPbMsYiKHzL15Q2DI2VqbpUCy7YC6NzNShGQNAdbe4kzR6pklUVN5xMWwbqjZJwA2AioAZG0KYReDtRacByxoKJC0wn4m8mQOGQKc8uB8/88YGXvUchkZADIEOG6Q9YwRXQWjBWkY0KfzyJSAtLNhrpk8zENnRwGBAR0AkgswCiOzwz1oaXT2ghTNGO2Gq9AMGzjsk6aCD/MQgMAAgNyxQbcB/+APW7v66l3UrARiAEDcID0ODBp63N6Xk9tu7IebveDUNG3ZwFR6Af2v2leQiI93YrlqJGbksF9AWGPyCJi+PXLTcQDy7bG7bw9edv7R25br4sel8FGKPdXEjkMKwD4rIq9ei2cp9lsReeWazaU4zHz4lapzI4XTDx4eXEvh9MPHp5EUTufCD0maBrqcC9+LFIDDqWA8UQAcTgXDRwXA4Uxo8awAOJwIzOYKwKHLoq9Ig2sFwOE4OIkVAIdLIvciA+BsGIimOQBnw0AaBDIAzoaBzk0OoNlrIw1qGuUAxrGjXlCtMsi6vpe1gq9foXqAUQbA0apYtklBBsDRmohyATkAR51AttAo77mTTiBzAUsATmYC3WzzixzA2NYd8Mekh6zTS+N30AZyCygAOGgDuQUUABy0gdwCCgDu2YCaCSoVAJzLhabLK4KrDMixqkhc3GW8AtAZNtSUZtQtdsBa58BOVQZXA6AEwKlIuBoAJQAuXSNcD4AyAIcCwXoAlAG4kwsUOYBSGYAM3PCDyaSU92502RE/2CnfbrJ5zp0wgrIBbAMYR+2PBPF0Y+K3ZfXeqO1uYMMBiB0A7c+In7b83M4J//HPh2pKI/rhp60f7I74VhPY6X8FgDYT2O1/FYD2EqjofyUA0Rm0MRYkz1V5XnVP5ah9+UD8WLmrd82plp/atmwm7NxV/rx2rH9ctMkMkm7d7eb1vWzTIAifaq97QKe502+HJ4ifgJti4HHeBgRJD3wEAmbox44gGiBPgMA9nf/xeH1BOEPvCKO4ennSPcZCSfgyrI58GyLGumNjkESk3gsyACW/4x8FhCSKXgJa7wULgJLvn4ev+Cpq0nmKwjNy55V00j3P871UGr+5P8XdpyiONJZ5GOS70lP/POHJppLmJOk+J0mcxLH+Ahc7bR/fe2o7Ei/blETkQFYjxIBPsupYtkmp+jb9n32oTyZNLvQbtgoRXeLovigAAAAASUVORK5CYII="/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>';
var AutoTask = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16">\n\t<title>favicon</title>\n\t<defs>\n\t\t<image width="32" height="32" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IB2cksfwAAAgdQTFRFPHjYPnnYUIXbR3/ZUofbaJbgZpTfSIDaPXjYUYfbapfhbJnhZ5XgRn7ZP3rYU4jca5jhbZriaZfgR3/aVIncapjhZZTgRn/aVIjcbpvicp3iZ5bgQXvZVYncjrHorsfutMvvvtLyj7HoVorcdqDj0+H26/H7/v7+////1eH2apfgYpLfm7nq8fX87fP71uL2vNDxnLrq7fL7/v7/7PL7faXkWIvdRH3ZfqXk7vP7/f3+1+P2k7TpkbPpnLrrY5PfRn/ZPXnYXo/eu9DweqPkeKHjtMvwdaDjRX7ZQXvYWYzd1OL2dJ/jaJfgeKHk1eL2bpriWozdkLLoaZfhbZrhdZ/i7PH7WozeeKLk8/b8nbvrr8fvQHrYb5vitszwuM7wapjgfKTkz971/P3+9/n9mrnqtczwuM3wZpXgcp7jwdTyl7fqeaLkvtLxxtfzc53i1OH2+vz+t83wkrPoxNbzdJ7jZpTgjbDo6fD62uX39Pf8rsfvkbPoT4TaYZLfr8fuc57jcJviQnzYja/n+vv+rcbucZziYJHfXpDeqsTtkrPpQnzZd6Dj0+D21uP2UYbcYZHfdqHj8vb8q8Xussnv7/T7YpPfSoLaQHvYX5Dfnbrr0+D1h6vmYJHegajln7zrSYHZw9XzTIPaUIbbT4XbXY7ea5nhUofcW43dV4rdXI7dXI7eX5DeWOgbkgAAAmNJREFUeJx10l1IFFEUB/DzT/c6uTpb9CCIbYFBZVIhpuXLYD6oYCTSt9LXkgj7lL1kPVVbSSGbD5ZhUqD0AUKxrNqTK5KhRoo+WFgbZUHuw6Ls+jEz7jbdGbVYZ3deZuacH/ece+4FxTwAousisX9JXCiJgcDTfJFwIiDqeb1MMD4Q0hDR36oVgXgAGTJ4EdJkdRN+mYFokyPpeg1NS5pNmTGD7JANKz1wgUkz2AMss9UVdInhdeCgHvTLerM7QlY5Dej/D4oHpOHdbCM+ruJ8zIczx/LeroFyvEeRP2Ur+pixzdQc2xdtJz5EAisAlT4UT/+xjpYNzemiBD1sc/5X66gxD5BY7iUmCrtGZHXbjMLUI+P70SsUfM7vUVUhTKiZmgJVYKTQw9sr9Sgn9Ga76PiAtOilk08VFPA8nUJfyXOq5qmOs2ERniBd7K7AE1KdTZAmaA3U4g0qX1fhIY84uwygXkVhaRNbK3FGeUdVFjRzYBOkzAeEOQl089V3RitNWq782A7cW2YkaXqTqUHJxxsQG1wsd2GD3Td/q7Ha3kiaZZGlC0LN3fkok41B3Y+4mF0pyxr0phmDslDZgbbagcFr9drqqOG+fgcvTzf/NiZJatHRsbyhQ7gs/zusVtS7W4UCz5IekiYahiYd/Z0xp5l1o97dNrtXsX2jnMAnONNdftOVEyqWfy4YF8YaXVi6fd4E+NK59ox9NB6Y3tLdXiubQUcdn1g2+b2U7K4xXzn+2Qmn8XH42AU5HqDycw59ny1Jl0IUFxBe1IFakh/5KAEgZAXxzBGihIDEx5b23pjIX32q0uJJAW9GAAAAAElFTkSuQmCC"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>';
const ICONS = {
'[ASF]': ASF,
'[Web]': Web,
'[Discord]': Discord$1,
'[Twitch]': Twitch$1,
'[Instagram]': Instagram,
'[Twitter]': Twitter$1,
'[Reddit]': Reddit$1,
'[Youtube]': Youtube$1,
'[Vk]': Vk$1,
'[AutoTask]': AutoTask
};
const generateLink = (url, text) => `<a href="${url}" target="_blank">${text}</a>`;
const createBaseElement = content => $(`<li>${content}<font class="log-status"></font></li>`).addClass('card-text');
const createPlatformElement = (type, text, id) => {
const urlGenerators = {
group: text => `https://steamcommunity.com/groups/${text}`,
officialGroup: text => `https://steamcommunity.com/games/${text}`,
forum: text => `https://steamcommunity.com/app/${text}/discussions/`,
curator: text => `https://store.steampowered.com/${text?.includes('/') ? text : `curator/${text}`}`,
app: text => `https://store.steampowered.com/app/${text}`,
sub: text => `https://steamdb.info/sub/${text}/`,
workshop: text => `https://steamcommunity.com/sharedfiles/filedetails/?id=${text}`,
announcement: (text, id) => `https://store.steampowered.com/news/app/${text}/view/${id}`,
discord: {
invite: text => `https://discord.com/invite/${text}`,
server: text => `https://discord.com/channels/@me/${text}`
},
twitch: text => `https://www.twitch.tv/${text}`,
instagram: text => `https://www.instagram.com/${text}/`,
twitter: text => `https://x.com/${text}`,
reddit: {
subreddit: text => `https://www.reddit.com/r/${text}/`,
user: text => `https://www.reddit.com/user/${text?.replace('u_', '')}`
},
youtube: {
channel: text => `https://www.youtube.com/channel/${text}`,
video: text => `https://www.youtube.com/watch?v=${text}`
},
vk: text => `https://vk.com/${text}/`
};
const typeMap = {
joiningSteamGroup: [ 'group' ],
leavingSteamGroup: [ 'group' ],
gettingSteamGroupId: [ 'group' ],
joiningSteamOfficialGroup: [ 'officialGroup' ],
leavingSteamOfficialGroup: [ 'officialGroup' ],
gettingSteamOfficialGroupId: [ 'officialGroup' ],
subscribingForum: [ 'forum' ],
unsubscribingForum: [ 'forum' ],
gettingForumId: [ 'forum' ],
followingCurator: [ 'curator' ],
unfollowingCurator: [ 'curator' ],
gettingCuratorId: [ 'curator' ],
addingToWishlist: [ 'app' ],
removingFromWishlist: [ 'app' ],
followingGame: [ 'app' ],
unfollowingGame: [ 'app' ],
gettingSubid: [ 'app' ],
addingFreeLicense: [ 'app', 'sub' ],
requestingPlayTestAccess: [ 'app' ],
gettingDemoAppid: [ 'app' ],
favoritingWorkshop: [ 'workshop' ],
unfavoritingWorkshop: [ 'workshop' ],
gettingWorkshopAppId: [ 'workshop' ],
votingUpWorkshop: [ 'workshop' ],
gettingAnnouncementParams: [ 'announcement' ],
likingAnnouncement: [ 'announcement' ],
joiningDiscordServer: [ 'discord', 'invite' ],
gettingDiscordGuild: [ 'discord', 'invite' ],
gettingDiscordXContextProperties: [ 'discord', 'invite' ],
leavingDiscordServer: [ 'discord', 'server' ],
followingTwitchChannel: [ 'twitch' ],
unfollowingTwitchChannel: [ 'twitch' ],
gettingTwitchChannelId: [ 'twitch' ],
gettingInsUserId: [ 'instagram' ],
followingIns: [ 'instagram' ],
unfollowingIns: [ 'instagram' ],
gettingTwitterUserId: [ 'twitter' ],
followingTwitterUser: [ 'twitter' ],
unfollowingTwitterUser: [ 'twitter' ],
joiningReddit: [ 'reddit', 'subreddit' ],
leavingReddit: [ 'reddit', 'subreddit' ],
followingRedditUser: [ 'reddit', 'user' ],
unfollowingRedditUser: [ 'reddit', 'user' ],
followingYtbChannel: [ 'youtube', 'channel' ],
unfollowingYtbChannel: [ 'youtube', 'channel' ],
likingYtbVideo: [ 'youtube', 'video' ],
unlikingYtbVideo: [ 'youtube', 'video' ],
gettingVkId: [ 'vk' ],
joiningVkGroup: [ 'vk' ],
leavingVkGroup: [ 'vk' ],
joiningVkPublic: [ 'vk' ],
leavingVkPublic: [ 'vk' ],
sendingVkWall: [ 'vk' ],
deletingVkWall: [ 'vk' ]
};
const urlConfig = typeMap[type];
if (!urlConfig || !text) {
return null;
}
const [platform, subType] = urlConfig;
const urlGenerator = urlGenerators[platform];
if (typeof urlGenerator === 'function') {
const url = urlGenerator(text, id);
const displayText = platform === 'announcement' ? id || '' : text;
return createBaseElement(`${I18n(type)}[${generateLink(url, displayText)}]...`);
}
if (subType && typeof urlGenerator === 'object') {
const subGenerator = urlGenerator[subType];
if (typeof subGenerator === 'function') {
const displayText = type.includes('RedditUser') ? text.replace('u_', '') : text;
return createBaseElement(`${I18n(type)}[${generateLink(subGenerator(text), displayText)}]...`);
}
}
return null;
};
const createSpecialElement = (type, text, html, id) => {
switch (type) {
case 'retweetting':
case 'unretweetting':
return createBaseElement(`${I18n(type)}${text}...`);
case 'visitingLink':
return createBaseElement(`${I18n('visitingLink')}[${generateLink(text || '', text || '')}]...`);
case 'verifyingInsAuth':
case 'text':
return createBaseElement(I18n(text || ''));
case 'html':
return $(text || html || '');
case 'whiteList':
return $(`<li><font class="warning">${I18n('skipTask')}[${text}(${id})](${I18n('whiteList')})</font></li>`);
case 'globalOptionsSkip':
return $(`<li>${I18n('skipTaskOption')}<font class="warning">${text}</font></li>`);
default:
return createBaseElement(`${I18n('unKnown')}:${type}(${text})...`);
}
};
const echoLog = ({type: type, text: text, html: html, id: id, before: before}) => {
const emptyStatus = {
success: () => emptyStatus,
error: () => emptyStatus,
warning: () => emptyStatus,
info: () => emptyStatus,
view: () => emptyStatus,
remove: () => emptyStatus
};
try {
let ele;
if (!type && !text && !html) {
ele = createBaseElement('');
} else if (text && !type) {
ele = createBaseElement(text);
} else if (html && !type) {
ele = $(html);
} else if (type) {
const platformElement = createPlatformElement(type, text, id);
ele = platformElement || createSpecialElement(type, text, html, id);
} else {
ele = createBaseElement('');
}
if (before) {
if (before in ICONS) {
const iconKey = before;
const svgContent = ICONS[iconKey];
const base64Svg = btoa(svgContent);
ele.prepend(`<font class="before-icon" style="background-image: url('data:image/svg+xml;base64,${base64Svg}')"></font>`);
} else {
ele.prepend(`<font class="before">${before}</font>`);
}
} else {
const base64Svg = btoa(ICONS['[AutoTask]']);
ele.prepend(`<font class="before-icon" style="background-image: url('data:image/svg+xml;base64,${base64Svg}')"></font>`);
}
ele.addClass('card-text');
$('#auto-task-info').append(ele);
ele[0]?.scrollIntoView();
const font = ele.find('font.log-status');
const status = {
font: font,
success(text = 'Success', html = false) {
this.font?.attr('class', '').addClass('success');
html ? this.font?.html(text) : this.font?.text(text);
return this;
},
error(text = 'Error', html = false) {
this.font?.attr('class', '').addClass('error');
html ? this.font?.html(text) : this.font?.text(text);
return this;
},
warning(text = 'Warning', html = false) {
this.font?.attr('class', '').addClass('warning');
html ? this.font?.html(text) : this.font?.text(text);
return this;
},
info(text = 'Info', html = false) {
this.font?.attr('class', '').addClass('info');
html ? this.font?.html(text) : this.font?.text(text);
return this;
},
view() {
this.font?.[0].scrollIntoView();
return this;
},
remove() {
this.font?.parent().remove();
return this;
}
};
return status;
} catch (error) {
throwError(error, 'echoLog');
return emptyStatus;
}
};
const unique = array => {
try {
return Array.from(new Set(array));
} catch (error) {
throwError(error, 'unique');
return [];
}
};
const delay = (time = 1e3) => new Promise((resolve => setTimeout((() => resolve(true)), time)));
const getRedirectLink = async (link, redirectOnce = false) => {
try {
if (!link) {
return null;
}
const redirectLinksCache = GM_getValue('redirectLinks') || {};
const cachedLink = redirectLinksCache[link];
if (cachedLink) {
debug('使用缓存的重定向链接', {
original: link,
cached: cachedLink
});
return cachedLink;
}
const {data: data} = await httpRequest({
url: link,
method: 'GET',
redirect: redirectOnce ? 'manual' : 'follow'
});
if (data?.finalUrl) {
redirectLinksCache[link] = data.finalUrl;
GM_setValue('redirectLinks', redirectLinksCache);
debug('获取新的重定向链接', {
original: link,
final: data.finalUrl
});
return data.finalUrl;
}
debug('未找到重定向链接', {
link: link
});
return null;
} catch (error) {
throwError(error, 'getRedirectLink');
return null;
}
};
const visitLink = async (link, options) => {
try {
debug('开始访问链接', {
link: link,
options: options
});
const logStatus = echoLog({
type: 'visitLink',
text: link
});
const {result: result, statusText: statusText, status: status} = await httpRequest({
url: link,
method: 'GET',
...options
});
if (result === 'Success') {
debug('链接访问成功', {
link: link
});
logStatus.success();
return true;
}
debug('链接访问失败', {
link: link,
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
} catch (error) {
throwError(error, 'visitLink');
return false;
}
};
const getUrlQuery = url => {
try {
debug('开始解析URL查询参数', {
url: url || window.location.href
});
const searchParams = url ? new URL(url, window.location.origin).searchParams : new URLSearchParams(window.location.search);
const query = {};
for (const [key, value] of searchParams.entries()) {
query[key] = value;
}
debug('URL查询参数解析结果', query);
return query;
} catch (error) {
throwError(error, 'getUrlQuery');
return {};
}
};
const stringToColour = str => {
try {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
const rgb = Array.from({
length: 3
}, ((_, i) => {
const value = hash >> i * 8 & 255;
return value.toString(16).padStart(2, '0');
}));
const color = `#${rgb.join('')}`;
return color;
} catch (error) {
throwError(error, 'stringToColour');
return '#ffffff';
}
};
const getAllLocalStorageAsObjects = localStorage => {
try {
debug('开始将所有LocalStorage转换为对象');
const result = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (!key) {
continue;
}
const value = localStorage.getItem(key);
try {
result[key] = JSON.parse(value);
} catch (error) {
result[key] = value;
console.error(error);
}
}
debug('所有LocalStorage转换为对象完成', result);
return result;
} catch (error) {
debug('将所有LocalStorage转换为对象失败', {
error: error
});
throwError(error, 'getAllLocalStorageAsObjects');
return {};
}
};
class Social {
tasks;
getRealParams(name, links, doTask, link2param) {
try {
debug('开始获取实际参数', {
name: name,
linksCount: links.length,
doTask: doTask
});
let realParams = [];
if (links.length > 0) {
debug('处理链接参数');
const convertedLinks = links.map((link => link2param(link))).filter((link => link !== undefined));
debug('链接参数处理结果', {
convertedLinksCount: convertedLinks.length
});
realParams = [ ...realParams, ...convertedLinks ];
}
if (!doTask && this.tasks[name]?.length) {
debug('处理任务参数', {
taskCount: this.tasks[name].length
});
realParams = [ ...realParams, ...this.tasks[name] ];
}
const uniqueParams = unique(realParams);
debug('参数处理完成', {
originalCount: realParams.length,
uniqueCount: uniqueParams.length
});
return uniqueParams;
} catch (error) {
debug('获取实际参数时发生错误', {
error: error
});
throwError(error, 'Social.getRealParams');
return [];
}
}
}
class Discord extends Social {
tasks;
whiteList;
#auth=GM_getValue('discordAuth') || {};
#cache=GM_getValue('discordCache') || {};
#xContextPropertiesCache=GM_getValue('discordXContextPropertiesCache') || {};
#initialized=false;
constructor() {
super();
const defaultTasksTemplate = {
servers: []
};
this.tasks = defaultTasksTemplate;
this.whiteList = {
...defaultTasksTemplate,
...GM_getValue('whiteList')?.discord || {}
};
}
async init(action) {
try {
debug('开始初始化Discord模块', {
action: action
});
if (!GM_getValue('dontRemindDiscordAgain')) {
debug('显示Discord重要提示对话框');
const result = await Swal.fire({
title: I18n('discordImportantNotice'),
text: I18n('discordImportantNoticeText'),
showCancelButton: true,
showDenyButton: true,
confirmButtonText: I18n('continueDiscordTask'),
cancelButtonText: I18n('skipDiscordTask'),
denyButtonText: I18n('continueAndDontRemindAgain')
}).then((({isConfirmed: isConfirmed, isDenied: isDenied}) => {
if (isConfirmed) {
return true;
}
if (isDenied) {
GM_setValue('dontRemindDiscordAgain', true);
return true;
}
return false;
}));
if (!result) {
this.#initialized = false;
return 'skip';
}
}
if (action === 'do' && !globalOptions.doTask.discord.servers || action === 'undo' && !globalOptions.undoTask.discord.servers) {
this.#initialized = false;
debug('检测到用户已禁用Discord任务,跳过初始化');
return 'skip';
}
if (this.#initialized) {
debug('Discord模块已初始化');
return true;
}
if (!this.#auth.auth || !this.#auth.xSuperProperties) {
debug('未找到Discord授权信息,尝试更新授权');
if (await this.#updateAuth()) {
this.#initialized = true;
return true;
}
return false;
}
const isVerified = await this.#verifyAuth();
if (isVerified) {
debug('Discord授权验证成功');
echoLog({
before: '[Discord]'
}).success(I18n('initSuccess', 'Discord'));
this.#initialized = true;
return true;
}
GM_setValue('discordAuth', {
auth: null
});
debug('Discord授权验证失败,尝试重新获取授权');
if (await this.#updateAuth()) {
echoLog({
before: '[Discord]'
}).success(I18n('initSuccess', 'Discord'));
this.#initialized = true;
return true;
}
echoLog({
before: '[Discord]'
}).error(I18n('initFailed', 'Discord'));
return false;
} catch (error) {
throwError(error, 'Discord.init');
return false;
}
}
async #verifyAuth() {
try {
debug('开始验证Discord授权');
const logStatus = echoLog({
text: I18n('verifyingAuth', 'Discord'),
before: '[Discord]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://discord.com/api/v6/users/@me',
method: 'HEAD',
headers: {
authorization: this.#auth.auth
}
});
if (result !== 'Success') {
debug('Discord授权验证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('Discord授权验证状态码错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('Discord授权验证成功');
logStatus.success();
return true;
} catch (error) {
throwError(error, 'Discord.verifyAuth');
return false;
}
}
async #updateAuth() {
try {
debug('开始更新Discord授权');
const logStatus = echoLog({
text: I18n('updatingAuth', 'Discord'),
before: '[Discord]'
});
return await new Promise((resolve => {
const newTab = GM_openInTab('https://discord.com/channels/@me', {
active: true,
insert: true,
setParent: true
});
newTab.name = 'ATv4_discordAuth';
newTab.onclose = async () => {
const {auth: auth, xSuperProperties: xSuperProperties} = GM_getValue('discordAuth');
if (auth && xSuperProperties) {
debug('成功获取新的Discord授权');
this.#auth = {
auth: auth,
xSuperProperties: xSuperProperties
};
logStatus.success();
resolve(await this.#verifyAuth());
} else {
debug('获取Discord授权失败');
logStatus.error('Error: Update discord auth failed!');
resolve(false);
}
};
}));
} catch (error) {
throwError(error, 'Discord.updateAuth');
return false;
}
}
async #joinServer(inviteId) {
try {
debug('开始加入Discord服务器', {
inviteId: inviteId
});
const logStatus = echoLog({
type: 'joiningDiscordServer',
text: inviteId,
before: '[Discord]'
});
const xContextProperties = await this.#getXContextProperties(inviteId);
if (!xContextProperties) {
debug('获取加群参数失败,无法加入服务器', {
inviteId: inviteId
});
logStatus.error('Error: Failed to get xContextProperties');
return false;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://discord.com/api/v9/invites/${inviteId}`,
method: 'POST',
dataType: 'json',
headers: {
'content-type': 'application/json',
authorization: this.#auth.auth,
origin: 'https://discord.com',
referer: `https://discord.com/invite/${inviteId}`,
'x-super-properties': this.#auth.xSuperProperties,
'x-context-properties': xContextProperties
},
data: '{"session_id":null}'
});
if (result !== 'Success' || data?.status !== 200) {
debug('加入Discord服务器失败', {
result: result,
statusText: statusText,
status: status
});
if (status === 400) {
debug('加入Discord服务器失败,状态码为400,需完成人机验证');
logStatus.error(I18n('captchaNeeded'));
return false;
}
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
const guild = data.response?.guild?.id;
if (!guild) {
debug('获取服务器ID失败');
logStatus.error('Error: Failed to get guild ID');
return false;
}
debug('成功加入Discord服务器', {
guild: guild
});
logStatus.success();
this.#setCache(inviteId, guild);
this.tasks.servers = unique([ ...this.tasks.servers, inviteId ]);
return true;
} catch (error) {
throwError(error, 'Discord.joinServer');
return false;
}
}
async #leaveServer(inviteId) {
try {
debug('开始退出Discord服务器', {
inviteId: inviteId
});
if (this.whiteList.servers.includes(inviteId)) {
debug('服务器在白名单中,跳过退出操作', {
inviteId: inviteId
});
echoLog({
type: 'whiteList',
text: 'Discord.leaveServer',
id: inviteId,
before: '[Discord]'
});
return true;
}
const guild = await this.#getGuild(inviteId);
if (!guild) {
debug('获取服务器ID失败,无法退出服务器', {
inviteId: inviteId
});
return false;
}
const logStatus = echoLog({
type: 'leavingDiscordServer',
text: guild,
before: '[Discord]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://discord.com/api/v9/users/@me/guilds/${guild}`,
method: 'DELETE',
headers: {
authorization: this.#auth.auth,
'x-super-properties': this.#auth.xSuperProperties
}
});
if (result !== 'Success' || data?.status !== 204) {
debug('退出Discord服务器失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
debug('成功退出Discord服务器', {
guild: guild
});
logStatus.success();
return true;
} catch (error) {
throwError(error, 'Discord.leaveServer');
return false;
}
}
async #getXContextProperties(inviteId) {
try {
debug('开始获取Discord加群参数', {
inviteId: inviteId
});
const logStatus = echoLog({
type: 'gettingDiscordXContextProperties',
text: inviteId,
before: '[Discord]'
});
const cachedXContextProperties = this.#xContextPropertiesCache[inviteId];
if (cachedXContextProperties) {
debug('从缓存中获取到加群参数', {
inviteId: inviteId,
cachedXContextProperties: cachedXContextProperties
});
logStatus.success();
return cachedXContextProperties;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://discord.com/api/v9/invites/${inviteId}?with_counts=true&with_expiration=true&with_permissions=true`,
responseType: 'json',
method: 'GET',
headers: {
authorization: this.#auth.auth,
'x-super-properties': this.#auth.xSuperProperties
}
});
if (result !== 'Success' || data?.status !== 200) {
debug('获取加群参数失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
const guild = data.response?.guild?.id;
if (!guild) {
debug('加群参数中未找到ID', {
inviteId: inviteId
});
logStatus.error('Error: Failed to get guild ID');
return false;
}
const xContextProperties = {
location: 'Accept Invite Page',
location_guild_id: data.response?.guild?.id,
location_channel_id: data.response?.channel?.id,
location_channel_type: data.response?.channel?.type
};
debug('成功获取加群参数', xContextProperties);
logStatus.success();
this.#setXContextPropertiesCache(inviteId, window.btoa(JSON.stringify(xContextProperties)));
this.#setCache(inviteId, guild);
return window.btoa(JSON.stringify(xContextProperties));
} catch (error) {
throwError(error, 'Discord.getXContextProperties');
return false;
}
}
async #getGuild(inviteId) {
try {
debug('开始获取Discord服务器ID', {
inviteId: inviteId
});
const logStatus = echoLog({
type: 'gettingDiscordGuild',
text: inviteId,
before: '[Discord]'
});
const cachedGuild = this.#cache[inviteId];
if (cachedGuild) {
debug('从缓存中获取到服务器ID', {
inviteId: inviteId,
cachedGuild: cachedGuild
});
logStatus.success();
return cachedGuild;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://discord.com/api/v9/invites/${inviteId}`,
responseType: 'json',
method: 'GET',
headers: {
authorization: this.#auth.auth,
'x-super-properties': this.#auth.xSuperProperties
}
});
if (result !== 'Success' || data?.status !== 200) {
debug('获取服务器信息失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
const guild = data.response?.guild?.id;
if (!guild) {
debug('服务器信息中未找到ID', {
inviteId: inviteId
});
logStatus.error('Error: Failed to get guild ID');
return false;
}
debug('成功获取服务器ID', {
inviteId: inviteId,
guild: guild
});
logStatus.success();
this.#setCache(inviteId, guild);
return guild;
} catch (error) {
throwError(error, 'Discord.getGuild');
return false;
}
}
async toggle({doTask: doTask = true, serverLinks: serverLinks = []}) {
try {
debug('开始处理Discord服务器任务', {
doTask: doTask,
serverLinksCount: serverLinks.length
});
if (!this.#initialized) {
debug('Discord模块未初始化');
echoLog({
text: I18n('needInit'),
before: '[Discord]'
});
return false;
}
if (doTask && !globalOptions.doTask.discord.servers || !doTask && !globalOptions.undoTask.discord.servers) {
debug('根据全局选项跳过Discord服务器任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'discord.servers',
before: '[Discord]'
});
return true;
}
const realServers = this.getRealParams('servers', serverLinks, doTask, (link => link.match(/invite\/(.+)/)?.[1]));
debug('处理后的服务器列表', {
count: realServers.length,
servers: realServers
});
if (realServers.length === 0) {
debug('没有需要处理的服务器');
return true;
}
const results = [];
for (const server of realServers) {
results.push(doTask ? this.#joinServer(server) : this.#leaveServer(server));
await delay(1e3);
}
return await Promise.allSettled(results).then((() => true));
} catch (error) {
throwError(error, 'Discord.toggleServers');
return false;
}
}
#setCache(inviteId, guild) {
try {
debug('设置Discord服务器缓存', {
inviteId: inviteId,
guild: guild
});
this.#cache[inviteId] = guild;
GM_setValue('discordCache', this.#cache);
debug('Discord服务器缓存设置成功');
} catch (error) {
debug('设置Discord服务器缓存失败', {
error: error
});
throwError(error, 'Discord.setCache');
}
}
#setXContextPropertiesCache(inviteId, xContextProperties) {
try {
debug('设置Discord加群参数缓存', {
inviteId: inviteId,
xContextProperties: xContextProperties
});
this.#xContextPropertiesCache[inviteId] = xContextProperties;
GM_setValue('discordXContextPropertiesCache', this.#xContextPropertiesCache);
} catch (error) {
debug('设置Discord加群参数缓存失败', {
error: error
});
throwError(error, 'Discord.setXContextPropertiesCache');
}
}
}
class Reddit extends Social {
tasks;
whiteList;
#auth;
#initialized=false;
constructor() {
super();
const defaultTasksTemplate = {
reddits: []
};
debug('初始化Reddit实例');
this.tasks = defaultTasksTemplate;
this.whiteList = {
...defaultTasksTemplate,
...GM_getValue('whiteList')?.reddit || {}
};
}
async init() {
try {
debug('开始初始化Reddit模块');
if (this.#initialized) {
debug('Reddit模块已初始化');
return true;
}
const isVerified = await this.#updateAuth();
if (isVerified) {
debug('Reddit授权验证成功');
echoLog({
before: '[Reddit]'
}).success(I18n('initSuccess', 'Reddit'));
this.#initialized = true;
return true;
}
debug('Reddit初始化失败');
echoLog({
before: '[Reddit]'
}).error(I18n('initFailed', 'Reddit'));
return false;
} catch (error) {
debug('Reddit初始化发生错误', {
error: error
});
throwError(error, 'Reddit.init');
return false;
}
}
async #useBeta() {
try {
debug('开始切换Reddit为新版');
const logStatus = echoLog({
text: I18n('changingRedditVersion'),
before: '[Reddit]'
});
return await new Promise((resolve => {
const newTab = GM_openInTab('https://www.reddit.com/', {
active: true,
insert: true,
setParent: true
});
newTab.name = 'ATv4_redditAuth';
newTab.onclose = async () => {
debug('新版Reddit标签页已关闭');
logStatus.success();
resolve(await this.#updateAuth(true));
};
}));
} catch (error) {
debug('切换Reddit版本时发生错误', {
error: error
});
throwError(error, 'Reddit.useBeta');
return false;
}
}
async #updateAuth(beta = false) {
try {
debug('开始更新Reddit授权', {
beta: beta
});
const logStatus = echoLog({
text: I18n('updatingAuth', 'Reddit'),
before: '[Reddit]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://www.reddit.com/',
method: 'GET',
nochche: true,
headers: {
'Cache-Control': 'no-cache'
}
});
if (result !== 'Success') {
debug('获取Reddit页面失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.responseText.includes('www.reddit.com/login/')) {
debug('需要登录Reddit');
logStatus.error(`Error:${I18n('loginReddit')}`, true);
return false;
}
if (data?.status !== 200) {
debug('Reddit页面状态码错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
if (data.responseText.includes('redesign-beta-optin-btn') && !beta) {
debug('检测到旧版Reddit,需要切换到新版');
return await this.#useBeta();
}
const accessToken = data.responseText.match(/"accessToken":"(.*?)","expires":"(.*?)"/)?.[1];
if (!accessToken) {
debug('未找到Reddit访问令牌');
logStatus.error('Error: Parameter "accessToken" not found!');
return false;
}
debug('成功获取Reddit访问令牌');
this.#auth = {
token: accessToken
};
logStatus.success();
return true;
} catch (error) {
debug('更新Reddit授权时发生错误', {
error: error
});
throwError(error, 'Reddit.updateAuth');
return false;
}
}
async #toggleTask({name: name, doTask: doTask = true}) {
try {
debug('开始处理Reddit任务', {
name: name,
doTask: doTask
});
if (!doTask && this.whiteList.reddits.includes(name)) {
debug('Reddit在白名单中,跳过取消订阅', {
name: name
});
echoLog({
type: 'whiteList',
text: 'Reddit.undoTask',
id: name,
before: '[Reddit]'
});
return true;
}
let type = doTask ? 'joiningReddit' : 'leavingReddit';
if (/^u_/.test(name)) {
type = doTask ? 'followingRedditUser' : 'unfollowingRedditUser';
}
debug('任务类型', {
type: type,
name: name
});
const logStatus = echoLog({
type: type,
text: name,
before: '[Reddit]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://oauth.reddit.com/api/subscribe?redditWebClient=desktop2x&app=desktop2x-client-production&raw_json=1&gilding_detail=1',
method: 'POST',
headers: {
authorization: `Bearer ${this.#auth.token}`,
'content-type': 'application/x-www-form-urlencoded'
},
data: $.param({
action: doTask ? 'sub' : 'unsub',
sr_name: name,
api_type: 'json'
})
});
if (result !== 'Success') {
debug('Reddit任务请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('Reddit任务状态码错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('Reddit任务处理成功', {
name: name,
doTask: doTask
});
logStatus.success();
if (doTask) {
this.tasks.reddits = unique([ ...this.tasks.reddits, name ]);
}
return true;
} catch (error) {
debug('处理Reddit任务时发生错误', {
error: error
});
throwError(error, 'Reddit.toggleTask');
return false;
}
}
async toggle({doTask: doTask = true, redditLinks: redditLinks = []}) {
try {
debug('开始处理Reddit链接任务', {
doTask: doTask,
redditLinksCount: redditLinks.length
});
if (!this.#initialized) {
debug('Reddit模块未初始化');
echoLog({
text: I18n('needInit'),
before: '[Reddit]'
});
return false;
}
if (doTask && !globalOptions.doTask.reddit.reddits || !doTask && !globalOptions.undoTask.reddit.reddits) {
debug('根据全局选项跳过Reddit任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'reddit.reddits',
before: '[Reddit]'
});
return true;
}
const realReddits = this.getRealParams('reddits', redditLinks, doTask, (link => {
const name = link.match(/https?:\/\/www\.reddit\.com\/r\/([^/]*)/)?.[1];
const userName = link.match(/https?:\/\/www\.reddit\.com\/user\/([^/]*)/)?.[1];
if (userName) {
return name || userName;
}
return name;
}));
debug('处理后的Reddit列表', {
count: realReddits.length,
reddits: realReddits
});
if (realReddits.length === 0) {
debug('没有需要处理的Reddit链接');
return true;
}
const prom = [];
for (const name of realReddits) {
prom.push(this.#toggleTask({
name: name,
doTask: doTask
}));
await delay(1e3);
}
return await Promise.all(prom).then((() => true));
} catch (error) {
debug('处理Reddit链接任务时发生错误', {
error: error
});
throwError(error, 'Reddit.toggle');
return false;
}
}
}
class Twitch extends Social {
tasks;
whiteList;
#auth=GM_getValue('twitchAuth') || {};
#cache=GM_getValue('twitchCache') || {};
#initialized=false;
#integrityToken;
constructor() {
super();
const defaultTasksTemplate = {
channels: []
};
debug('初始化Twitch实例');
this.tasks = defaultTasksTemplate;
this.whiteList = {
...defaultTasksTemplate,
...GM_getValue('whiteList')?.twitch || {}
};
}
async init() {
try {
debug('开始初始化Twitch模块');
if (this.#initialized) {
debug('Twitch模块已初始化');
return true;
}
if (!this.#auth.authToken || !this.#auth.clientId || !this.#auth.clientVersion || !this.#auth.deviceId || !this.#auth.clientSessionId) {
if (await this.#updateAuth()) {
this.#initialized = true;
return true;
}
return false;
}
const isVerified = await this.#verifyAuth(true);
if (isVerified) {
debug('Twitch授权验证成功');
echoLog({
before: '[Twitch]'
}).success(I18n('initSuccess', 'Twitch'));
this.#initialized = true;
return true;
}
GM_setValue('twitchAuth', null);
if (await this.#updateAuth()) {
debug('Twitch重新授权成功');
echoLog({
before: '[Twitch]'
}).success(I18n('initSuccess', 'Twitch'));
this.#initialized = true;
return true;
}
debug('Twitch初始化失败');
echoLog({
before: '[Twitch]'
}).error(I18n('initFailed', 'Twitch'));
return false;
} catch (error) {
debug('Twitch初始化发生错误', {
error: error
});
throwError(error, 'Twitch.init');
return false;
}
}
async #verifyAuth(isFirst) {
try {
debug('开始验证Twitch授权');
const logStatus = echoLog({
text: I18n('verifyingAuth', 'Twitch'),
before: '[Twitch]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://gql.twitch.tv/gql',
method: 'POST',
dataType: 'json',
headers: {
Authorization: `OAuth ${this.#auth.authToken}`,
'Client-Id': this.#auth.clientId
},
data: '[{"operationName":"FrontPageNew_User","variables":{"limit":1},"extensions":{"persistedQuery":{"version":1,' + '"sha256Hash":"64bd07a2cbaca80699d62636d966cf6395a5d14a1f0a14282067dcb28b13eb11"}}}]'
});
if (result !== 'Success') {
debug('Twitch授权验证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || !data.response?.[0]?.data?.currentUser) {
debug('Twitch授权验证状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
await this.#integrity(isFirst);
debug('Twitch授权验证成功');
logStatus.success();
return true;
} catch (error) {
debug('Twitch授权验证发生错误', {
error: error
});
throwError(error, 'Twitch.verifyAuth');
return false;
}
}
async #integrity(isFirst = true, ct = '') {
try {
debug('开始检查Twitch完整性', {
isFirst: isFirst,
ct: ct
});
const logStatus = echoLog({
text: I18n('checkingTwitchIntegrity'),
before: '[Twitch]'
});
if (isFirst && (!this.#auth.authToken || !this.#auth.clientId || !this.#auth.clientVersion || !this.#auth.deviceId || !this.#auth.clientSessionId)) {
return await this.#updateAuth(false);
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://gql.twitch.tv/integrity',
method: 'POST',
dataType: 'json',
anonymous: true,
headers: {
Origin: 'https://www.twitch.tv',
Referer: 'https://www.twitch.tv/',
Authorization: `OAuth ${this.#auth.authToken}`,
'Client-Id': this.#auth.clientId,
'Client-Version': this.#auth.clientVersion,
'X-Device-Id': this.#auth.deviceId,
'Client-Session-Id': this.#auth.clientSessionId,
'x-kpsdk-ct': ct
}
});
if (result !== 'Success') {
debug('Twitch完整性检查请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (!ct && data?.responseHeaders?.['x-kpsdk-ct']) {
debug('需要重新检查Twitch完整性');
return await this.#integrity(isFirst, data.responseHeaders['x-kpsdk-ct']);
}
if (data?.status !== 200 || !data.response?.token) {
debug('Twitch完整性检查状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
this.#integrityToken = data.response.token;
debug('Twitch完整性检查成功');
logStatus.success();
return true;
} catch (error) {
debug('Twitch完整性检查发生错误', {
error: error
});
throwError(error, 'Twitch.integrity');
return false;
}
}
async #updateAuth(isFirst = true) {
try {
debug('开始更新Twitch授权', {
isFirst: isFirst
});
const logStatus = echoLog({
text: I18n('updatingAuth', 'Twitch'),
before: '[Twitch]'
});
return await new Promise((resolve => {
const newTab = GM_openInTab('https://www.twitch.tv/', {
active: true,
insert: true,
setParent: true
});
newTab.name = 'ATv4_twitchAuth';
newTab.onclose = async () => {
const auth = GM_getValue('twitchAuth');
if (auth) {
debug('成功获取新的Twitch授权');
this.#auth = auth;
logStatus.success();
resolve(await this.#verifyAuth(isFirst));
} else {
debug('获取Twitch授权失败');
logStatus.error('Error: Update twitch auth failed!');
resolve(false);
}
};
}));
} catch (error) {
debug('更新Twitch授权时发生错误', {
error: error
});
throwError(error, 'Twitch.updateAuth');
return false;
}
}
async #toggleChannel({name: name, doTask: doTask = true}) {
try {
debug('开始处理Twitch频道任务', {
name: name,
doTask: doTask
});
if (!doTask && this.whiteList.channels.includes(name)) {
debug('Twitch频道在白名单中,跳过取消关注', {
name: name
});
echoLog({
type: 'whiteList',
text: 'Twitch.unfollowChannel',
id: name,
before: '[Twitch]'
});
return true;
}
const channelId = await this.#getChannelId(name);
if (!channelId) {
return false;
}
const logStatus = echoLog({
type: `${doTask ? '' : 'un'}followingTwitchChannel`,
text: name,
before: '[Twitch]'
});
const followData = `[{"operationName":"FollowButton_FollowUser","variables":{"input":{"disableNotifications":false,"targetID":"${channelId}` + '"}},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"800e7346bdf7e5278a3c1d3f21b2b56e2639928f86815677a7126b093b2fdd08"}}}]';
const unfollowData = `[{"operationName":"FollowButton_UnfollowUser","variables":{"input":{"targetID":"${channelId}"}},` + '"extensions":{"persistedQuery":{"version":1,"sha256Hash":"f7dae976ebf41c755ae2d758546bfd176b4eeb856656098bb40e0a672ca0d880"}}}]';
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://gql.twitch.tv/gql',
method: 'POST',
dataType: 'json',
anonymous: true,
headers: {
Origin: 'https://www.twitch.tv',
Referer: 'https://www.twitch.tv/',
Authorization: `OAuth ${this.#auth.authToken}`,
'Client-Id': this.#auth.clientId,
'Client-Version': this.#auth.clientVersion,
'X-Device-Id': this.#auth.deviceId,
'Client-Session-Id': this.#auth.clientSessionId,
'Client-Integrity': this.#integrityToken
},
data: doTask ? followData : unfollowData
});
if (result !== 'Success') {
debug('Twitch频道操作请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || data.response?.[0] && data.response[0].errors) {
debug('Twitch频道操作状态错误', {
status: data?.status,
statusText: data?.statusText,
errors: data?.response?.[0].errors
});
logStatus.error(`Error:${data?.response?.[0].errors?.[0]?.message || `${data?.statusText}(${data?.status})`}`);
return false;
}
debug('Twitch频道操作成功', {
name: name,
doTask: doTask
});
logStatus.success();
if (doTask) {
this.tasks.channels = unique([ ...this.tasks.channels, name ]);
}
return true;
} catch (error) {
debug('处理Twitch频道任务时发生错误', {
error: error
});
throwError(error, 'Twitch.toggleChannel');
return false;
}
}
async #getChannelId(name) {
try {
debug('开始获取Twitch频道ID', {
name: name
});
const logStatus = echoLog({
type: 'gettingTwitchChannelId',
text: name,
before: '[Twitch]'
});
const cachedChannelId = this.#cache[name];
if (cachedChannelId) {
debug('从缓存获取到Twitch频道ID', {
name: name,
id: cachedChannelId
});
logStatus.success();
return cachedChannelId;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://gql.twitch.tv/gql',
method: 'POST',
headers: {
Authorization: `OAuth ${this.#auth.authToken}`,
'Client-Id': this.#auth.clientId
},
responseType: 'json',
data: `[{"operationName":"ActiveWatchParty","variables":{"channelLogin":"${name}"},` + '"extensions":{"persistedQuery":{"version":1,"sha256Hash":"4a8156c97b19e3a36e081cf6d6ddb5dbf9f9b02ae60e4d2ff26ed70aebc80a30"}}}]'
});
if (result !== 'Success') {
debug('获取Twitch频道ID请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取Twitch频道ID状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const newChannelId = data.response?.[0]?.data?.user?.id;
if (!newChannelId) {
debug('未找到Twitch频道ID', {
name: name
});
logStatus.error(`Error:${data?.statusText || 'Unknown'}(${data?.status || 'Unknown'})`);
return false;
}
debug('成功获取Twitch频道ID', {
name: name,
id: newChannelId
});
this.#setCache(name, newChannelId);
logStatus.success();
return newChannelId;
} catch (error) {
debug('获取Twitch频道ID时发生错误', {
error: error
});
throwError(error, 'Twitch.getChannelId');
return false;
}
}
async toggle({doTask: doTask = true, channelLinks: channelLinks = []}) {
try {
debug('开始处理Twitch链接任务', {
doTask: doTask,
channelLinksCount: channelLinks.length
});
if (!this.#initialized) {
debug('Twitch模块未初始化');
echoLog({
text: I18n('needInit'),
before: '[Twitch]'
});
return false;
}
const prom = [];
if (doTask && !globalOptions.doTask.twitch.channels || !doTask && !globalOptions.undoTask.twitch.channels) {
debug('根据全局选项跳过Twitch任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'twitch.channels',
before: '[Twitch]'
});
} else {
const realChannels = this.getRealParams('channels', channelLinks, doTask, (link => link.match(/https:\/\/(www\.)?twitch\.tv\/(.+)/)?.[2]));
debug('处理后的Twitch频道列表', {
count: realChannels.length,
channels: realChannels
});
if (realChannels.length > 0) {
for (const channel of realChannels) {
prom.push(this.#toggleChannel({
name: channel,
doTask: doTask
}));
await delay(1e3);
}
}
}
return Promise.all(prom).then((() => true));
} catch (error) {
debug('处理Twitch链接任务时发生错误', {
error: error
});
throwError(error, 'Twitch.toggle');
return false;
}
}
#setCache(name, id) {
try {
debug('设置Twitch频道ID缓存', {
name: name,
id: id
});
this.#cache[name] = id;
GM_setValue('twitchCache', this.#cache);
} catch (error) {
debug('设置Twitch频道ID缓存时发生错误', {
error: error
});
throwError(error, 'Twitch.setCache');
}
}
}
const encodeSha256 = async data => {
const encoder = new TextEncoder;
const dataBuffer = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
return Array.from(new Uint8Array(hashBuffer));
};
const encodeBase64 = data => {
let binary = '';
const bytes = new Uint8Array(data);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/=/g, '');
};
const decodeBase64 = data => {
const binaryString = atob(data);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return Array.from(bytes);
};
const generateTransactionId = async (method, path, key, animationKey) => {
const DEFAULT_KEYWORD = 'obfiowerehiring';
const ADDITIONAL_RANDOM_NUMBER = 3;
const timeNow = Math.floor((Date.now() - 1682924400 * 1e3) / 1e3);
const timeNowBytes = [ timeNow & 255, timeNow >> 8 & 255, timeNow >> 16 & 255, timeNow >> 24 & 255 ];
const data = `${method}!${path}!${timeNow}${DEFAULT_KEYWORD}${animationKey}`;
const hashBytes = await encodeSha256(data);
const keyBytes = decodeBase64(key);
const randomNum = Math.floor(Math.random() * 256);
const bytesArr = [ ...keyBytes, ...timeNowBytes, ...hashBytes.slice(0, 16), ADDITIONAL_RANDOM_NUMBER ];
const out = new Uint8Array(bytesArr.length + 1);
out[0] = randomNum;
bytesArr.forEach(((item, index) => {
out[index + 1] = item ^ randomNum;
}));
return encodeBase64(out);
};
const url = 'https://raw.githubusercontent.com/fa0311/x-client-transaction-id-pair-dict/refs/heads/main/pair.json';
const getTID = async () => {
const res = await fetch(url);
const json = await res.json();
return async (method, path) => {
const randomPair = json[Math.floor(Math.random() * json.length)];
const {animationKey: animationKey, verification: verification} = randomPair;
const tid = await generateTransactionId(method, path, verification, animationKey);
return tid;
};
};
const parseResponseHeaders = headerStr => {
const headers = {};
if (!headerStr) {
return headers;
}
headerStr.split('\r\n').forEach((line => {
if (line) {
const parts = line.split(':');
const key = parts.shift()?.trim();
const value = parts.join(':').trim();
if (key) {
if (key.toLowerCase() === 'set-cookie') {
if (headers[key]) {
if (Array.isArray(headers[key])) {
headers[key].push(value);
} else {
headers[key] = [ headers[key], value ];
}
} else {
headers[key] = value;
}
} else {
headers[key] = value;
}
}
}
}));
return headers;
};
const axiosGM = function(config) {
const finalConfig = {
...axiosGM.defaults,
...config
};
const retries = finalConfig.retry ?? 0;
const retryDelay = finalConfig.retryDelay ?? 0;
const requestAttempt = attempt => new Promise(((resolve, reject) => {
GM_xmlhttpRequest({
method: finalConfig.method ? finalConfig.method.toUpperCase() : 'GET',
url: finalConfig.url,
headers: finalConfig.headers,
data: finalConfig.data,
responseType: finalConfig.responseType || 'json',
timeout: finalConfig.timeout,
fetch: finalConfig.fetch ?? true,
onload(response) {
const axiosResponse = {
data: response.response || response.responseText,
status: response.status,
statusText: response.statusText,
headers: parseResponseHeaders(response.responseHeaders),
config: finalConfig,
request: response
};
resolve(axiosResponse);
},
onerror(error) {
if (attempt < retries) {
setTimeout((() => {
requestAttempt(attempt + 1).then(resolve).catch(reject);
}), retryDelay);
} else {
reject(error);
}
},
ontimeout() {
if (attempt < retries) {
setTimeout((() => {
requestAttempt(attempt + 1).then(resolve).catch(reject);
}), retryDelay);
} else {
reject('Error: timeout');
}
}
});
}));
return requestAttempt(0);
};
axiosGM.defaults = {};
axiosGM.get = function(url, config = {}) {
return axiosGM({
...config,
url: url,
method: 'GET'
});
};
axiosGM.post = function(url, data, config = {}) {
return axiosGM({
...config,
url: url,
data: data,
method: 'POST'
});
};
axiosGM.head = function(url, config = {}) {
return axiosGM({
...config,
url: url,
method: 'HEAD'
});
};
axiosGM.create = function(instanceDefaults = {}) {
const instance = config => {
const mergedConfig = {
...axiosGM.defaults,
...instanceDefaults,
...config
};
return axiosGM(mergedConfig);
};
instance.defaults = {
...axiosGM.defaults,
...instanceDefaults
};
instance.get = function(url, config = {}) {
return instance({
...config,
url: url,
method: 'GET'
});
};
instance.post = function(url, data, config = {}) {
return instance({
...config,
url: url,
data: data,
method: 'POST'
});
};
instance.head = function(url, config = {}) {
return instance({
...config,
url: url,
method: 'HEAD'
});
};
instance.create = axiosGM.create;
return instance;
};
const getFwdForSdkUrl = async () => {
const rawHtml = await axiosGM({
url: 'https://x.com',
method: 'GET'
});
return [ ...rawHtml.data.matchAll(/"(loader\.FwdForSdk)":"([^"]+?)"/g) ];
};
const fwdForSdkExpoter = async url => {
const {data: data} = await axiosGM.get(url);
const regex = /Uint8Array\(\w\)\.set\(\[(.*?)\]\)/;
if (!regex.test(data)) {
return false;
}
const json = `[${data.match(regex)?.[1]}]`;
const obj = JSON.parse(json);
return new Uint8Array(obj);
};
const getWasmData = async () => {
const fwdForSdkUrl = await getFwdForSdkUrl();
for (const url of fwdForSdkUrl) {
const sdkData = await fwdForSdkExpoter(`https://abs.twimg.com/responsive-web/client-web/${url[1]}.${url[2]}a.js`);
if (sdkData) {
return sdkData;
}
}
return false;
};
const getFwdForSdk = async () => {
debug('开始获取 XFwdForSdk');
const wasmData = await getWasmData();
debug('获取 wasmData 成功', {
wasmData: wasmData
});
const go = new Go;
const wasmModule = await WebAssembly.instantiate(wasmData, {
...go.importObject,
env: {
...go.importObject.env,
memory: new WebAssembly.Memory({
initial: 10
}),
table: new WebAssembly.Table({
initial: 0,
element: 'anyfunc'
})
}
});
debug('初始化 wasmModule 成功');
go.run(wasmModule.instance);
debug('运行 wasmModule 成功');
const {str: str, expiryTimeMillis: expiryTimeMillis} = await globalThis.getForwardedForStr();
debug('获取 XFwdForSdk 成功', {
str: str,
expiryTimeMillis: expiryTimeMillis
});
return {
str: str,
expiryTimeMillis: parseInt(expiryTimeMillis, 10)
};
};
const generateSecCHUA = () => {
if (navigator.userAgentData && navigator.userAgentData.brands) {
return navigator.userAgentData.brands.map((brand => `"${brand.brand}";v="${brand.version}"`)).join(', ');
}
return '"Google Chrome";v="125", "Chromium";v="125", "Not-A.Brand";v="99"';
};
class Twitter extends Social {
tasks;
whiteList;
#verifyId=globalOptions.other.twitterVerifyId;
#auth=GM_getValue('twitterAuth') || {};
#cache=GM_getValue('twitterCache') || {};
#initialized=false;
#getTID;
#FwdForSdk;
#headers={};
constructor() {
super();
const defaultTasksTemplate = {
users: [],
retweets: [],
likes: []
};
debug('初始化Twitter实例');
this.tasks = defaultTasksTemplate;
this.whiteList = {
...defaultTasksTemplate,
...GM_getValue('whiteList')?.twitter || {}
};
this.#headers = {
authorization: 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
'X-Twitter-Auth-Type': 'OAuth2Session',
'X-Twitter-Active-User': 'yes',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua': generateSecCHUA()
};
}
async init() {
try {
debug('开始初始化Twitter模块');
if (this.#initialized) {
debug('Twitter模块已初始化');
return true;
}
debug('获取Twitter授权信息');
if (!await this.#updateAuth()) {
return false;
}
debug('创建Twitter会话和SDK');
this.#getTID = await getTID();
this.#FwdForSdk = await getFwdForSdk();
const isVerified = await this.#verifyAuth();
if (isVerified) {
debug('Twitter授权验证成功');
echoLog({
before: '[Twitter]'
}).success(I18n('initSuccess', 'Twitter'));
this.#initialized = true;
return true;
}
debug('Twitter授权失效,尝试重新获取');
GM_setValue('twitterAuth', null);
if (await this.#updateAuth()) {
debug('Twitter重新授权成功');
echoLog({
before: '[Twitter]'
}).success(I18n('initSuccess', 'Twitter'));
this.#initialized = true;
return true;
}
debug('Twitter初始化失败');
echoLog({
before: '[Twitter]'
}).error(I18n('initFailed', 'Twitter'));
return false;
} catch (error) {
debug('Twitter初始化发生错误', {
error: error
});
throwError(error, 'Twitter.init');
return false;
}
}
async #verifyAuth() {
try {
debug('开始验证Twitter授权');
return await this.#toggleUser({
name: 'verify',
doTask: true,
verify: true
});
} catch (error) {
debug('Twitter授权验证发生错误', {
error: error
});
throwError(error, 'Twitter.verifyAuth');
return false;
}
}
async #updateAuth() {
try {
debug('开始更新Twitter授权');
const logStatus = echoLog({
text: I18n('updatingAuth', 'Twitter'),
before: '[Twitter]'
});
return await new Promise((resolve => {
GM_cookie.list({
url: 'https://x.com/settings/account'
}, (async (cookies, error) => {
if (!error) {
const ct0 = cookies.find((cookie => cookie.name === 'ct0'))?.value;
const isLogin = cookies.find((cookie => cookie.name === 'twid'))?.value;
if (isLogin && ct0) {
debug('成功获取Twitter授权信息');
GM_setValue('twitterAuth', {
ct0: ct0
});
this.#auth = {
ct0: ct0
};
this.#headers['x-csrf-token'] = ct0;
this.#headers['x-twitter-client-language'] = cookies.find((cookie => cookie.name === 'lang'))?.value || 'en';
logStatus.success();
resolve(true);
} else {
debug('获取Twitter授权失败:未登录');
logStatus.error(I18n('needLogin'));
resolve(false);
}
} else {
debug('获取Twitter授权失败', {
error: error
});
logStatus.error('Error: Update twitter auth failed!');
resolve(false);
}
}));
}));
} catch (error) {
debug('更新Twitter授权时发生错误', {
error: error
});
throwError(error, 'Twitter.updateToken');
return false;
}
}
async #toggleUser({name: name, doTask: doTask = true, verify: verify = false, retry: retry = false}) {
try {
debug('开始处理Twitter用户任务', {
name: name,
doTask: doTask,
verify: verify,
retry: retry
});
if (!doTask && !verify && this.whiteList.users.includes(name)) {
debug('Twitter用户在白名单中,跳过取消关注', {
name: name
});
echoLog({
type: 'whiteList',
text: 'Twitter.unfollowUser',
id: name,
before: '[Twitter]'
});
return true;
}
const userId = verify ? this.#verifyId : await this.userName2id(name);
if (!userId) {
return false;
}
const logStatus = verify ? echoLog({
text: I18n('verifyingAuth', 'Twitter'),
before: '[Twitter]'
}) : echoLog({
type: `${doTask ? '' : 'un'}followingTwitterUser`,
text: name,
before: '[Twitter]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://x.com/i/api/1.1/friendships/${doTask ? 'create' : 'destroy'}.json`,
method: 'POST',
headers: {
...this.#headers,
'Content-Type': 'application/x-www-form-urlencoded',
'x-client-transaction-id': await this.#getTID('POST', `/i/api/1.1/friendships/${doTask ? 'create' : 'destroy'}.json`),
'x-xp-forwarded-for': this.#FwdForSdk.str
},
responseType: 'json',
data: $.param({
include_profile_interstitial_type: 1,
include_blocking: 1,
include_blocked_by: 1,
include_followed_by: 1,
include_want_retweets: 1,
include_mute_edge: 1,
include_can_dm: 1,
include_can_media_tag: 1,
skip_status: 1,
id: userId
})
});
if (result !== 'Success') {
debug('Twitter用户操作请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200) {
debug('Twitter用户操作成功', {
name: name,
doTask: doTask
});
logStatus.success();
if (doTask && !verify) {
this.tasks.users = unique([ ...this.tasks.users, name ]);
}
return true;
}
if (verify && data?.status === 403) {
if (data.response?.errors?.[0]?.code === 158) {
debug('Twitter授权验证成功(已关注)');
logStatus.success();
return true;
}
if (data.response?.errors?.[0]?.code === 353 && !retry && data.responseHeaders?.['set-cookie']) {
const newCt0 = data.responseHeaders['set-cookie']?.find((cookie => cookie.includes('ct0=')))?.split(';')?.at(0)?.split('=')?.at(-1);
if (newCt0) {
debug('获取到新的Twitter授权Token,重试操作');
this.#auth.ct0 = newCt0;
GM_setValue('twitterAuth', this.#auth);
logStatus.warning(I18n('retry'));
return this.#toggleUser({
name: name,
doTask: doTask,
verify: verify,
retry: true
});
}
}
}
debug('Twitter用户操作失败', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('处理Twitter用户任务时发生错误', {
error: error
});
throwError(error, 'Twitter.toggleUser');
return false;
}
}
async userName2id(name) {
try {
debug('开始获取Twitter用户ID', {
name: name
});
const logStatus = echoLog({
type: 'gettingTwitterUserId',
text: name,
before: '[Twitter]'
});
const cachedUserId = this.#cache[name];
if (cachedUserId) {
debug('从缓存获取到Twitter用户ID', {
name: name,
id: cachedUserId
});
logStatus.success();
return cachedUserId;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://x.com/i/api/graphql/jUKA--0QkqGIFhmfRZdWrQ/UserByScreenName' + `?variables=%7B%22screen_name%22%3A%22${name}%22%7D&features=%7B%22responsive_web_grok_bio_auto_translation_is_enabled%22%3Afalse%2C%22hidden_profile_subscriptions_enabled%22%3Atrue%2C%22payments_enabled%22%3Afalse%2C%22profile_label_improvements_pcf_label_in_post_enabled%22%3Atrue%2C%22rweb_tipjar_consumption_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_is_identity_verified_enabled%22%3Atrue%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22responsive_web_twitter_article_notes_tab_enabled%22%3Atrue%2C%22subscriptions_feature_can_gift_premium%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D&fieldToggles=%7B%22withAuxiliaryUserLabels%22%3Atrue%7D`,
method: 'GET',
headers: {
...this.#headers,
'content-type': 'application/json',
referer: `https://x.com/${name}`,
'x-client-transaction-id': await this.#getTID('GET', '/i/api/graphql/jUKA--0QkqGIFhmfRZdWrQ/UserByScreenName' + `?variables=%7B%22screen_name%22%3A%22${name}%22%7D&features=%7B%22responsive_web_grok_bio_auto_translation_is_enabled%22%3Afalse%2C%22hidden_profile_subscriptions_enabled%22%3Atrue%2C%22payments_enabled%22%3Afalse%2C%22profile_label_improvements_pcf_label_in_post_enabled%22%3Atrue%2C%22rweb_tipjar_consumption_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_is_identity_verified_enabled%22%3Atrue%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22responsive_web_twitter_article_notes_tab_enabled%22%3Atrue%2C%22subscriptions_feature_can_gift_premium%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D&fieldToggles=%7B%22withAuxiliaryUserLabels%22%3Atrue%7D`),
'x-xp-forwarded-for': this.#FwdForSdk.str
},
responseType: 'json'
});
if (result !== 'Success') {
debug('获取Twitter用户ID请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取Twitter用户ID状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
let response = data.response || (typeof data.responseText === 'object' ? data.responseText : null);
if (!response) {
try {
response = JSON.parse(data.responseText);
} catch (error) {
response = null;
}
}
const fetchedUserId = response?.data?.user?.result?.rest_id;
if (!fetchedUserId) {
debug('未找到Twitter用户ID', {
name: name
});
logStatus.error(`Error:${data.statusText}(${data.status})`);
return false;
}
debug('成功获取Twitter用户ID', {
name: name,
id: fetchedUserId
});
this.#setCache(name, fetchedUserId);
logStatus.success();
return fetchedUserId;
} catch (error) {
debug('获取Twitter用户ID时发生错误', {
error: error
});
throwError(error, 'Twitter.getUserId');
return false;
}
}
async #toggleRetweet({retweetId: retweetId, doTask: doTask = true, retry: retry = false}) {
try {
debug('开始处理Twitter转推任务', {
retweetId: retweetId,
doTask: doTask,
retry: retry
});
if (!doTask && this.whiteList.retweets.includes(retweetId)) {
debug('Twitter转推在白名单中,跳过取消', {
retweetId: retweetId
});
echoLog({
type: 'whiteList',
text: 'Twitter.unretweet',
id: retweetId,
before: '[Twitter]'
});
return true;
}
const logStatus = echoLog({
type: `${doTask ? '' : 'un'}retweetting`,
text: retweetId,
before: '[Twitter]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://x.com/i/api/graphql/${doTask ? 'ojPdsZsimiJrUGLR1sjUtA/CreateRetweet' : 'iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet'}`,
method: 'POST',
headers: {
...this.#headers,
'Content-Type': 'application/json',
origin: 'https://x.com',
referer: 'https://x.com/home',
'x-client-transaction-id': await this.#getTID('POST', `/i/api/graphql/${doTask ? 'ojPdsZsimiJrUGLR1sjUtA/CreateRetweet' : 'iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet'}`),
'x-xp-forwarded-for': this.#FwdForSdk.str
},
data: `{"variables":{"${doTask ? '' : 'source_'}tweet_id":"${retweetId}","dark_request":false},"queryId":"${doTask ? 'ojPdsZsimiJrUGLR1sjUtA' : 'iQtK4dl5hBmXewYZuEOKVw'}"}`,
responseType: 'json'
});
if (result !== 'Success') {
debug('Twitter转推操作请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 403 && data.response?.errors?.[0]?.code === 353 && !retry && data.responseHeaders?.['set-cookie']) {
const newCt0 = data.responseHeaders['set-cookie']?.find((cookie => cookie.includes('ct0=')))?.split(';')?.at(0)?.split('=')?.at(-1);
if (newCt0) {
debug('获取到新的Twitter授权Token,重试操作');
this.#auth.ct0 = newCt0;
GM_setValue('twitterAuth', this.#auth);
logStatus.warning(I18n('retry'));
return this.#toggleRetweet({
retweetId: retweetId,
doTask: doTask,
retry: true
});
}
}
if (data?.status !== 200 && !(data?.status === 403 && data.response?.errors?.[0]?.code === 327)) {
debug('Twitter转推操作状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
if (data.response?.errors && data.response?.errors?.[0]?.code !== 327) {
debug('Twitter转推操作出错', {
error: data.response?.errors?.[0]?.message
});
logStatus.error(`Error:${data.response?.errors?.[0]?.message}`);
return false;
}
debug('Twitter转推操作成功', {
retweetId: retweetId,
doTask: doTask
});
logStatus.success();
if (doTask) {
this.tasks.retweets = unique([ ...this.tasks.retweets, retweetId ]);
}
return true;
} catch (error) {
debug('处理Twitter转推任务时发生错误', {
error: error
});
throwError(error, 'Twitter.toggleRetweet');
return false;
}
}
async toggle({doTask: doTask = true, userLinks: userLinks = [], retweetLinks: retweetLinks = []}) {
try {
debug('开始处理Twitter链接任务', {
doTask: doTask,
userLinksCount: userLinks.length,
retweetLinksCount: retweetLinks.length
});
if (!this.#initialized) {
debug('Twitter模块未初始化');
echoLog({
text: I18n('needInit'),
before: '[Twitter]'
});
return false;
}
if (doTask && !globalOptions.doTask.twitter.users || !doTask && !globalOptions.undoTask.twitter.users) {
debug('根据全局选项跳过Twitter用户任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'twitter.users',
before: '[Twitter]'
});
} else {
const realUsers = this.getRealParams('users', userLinks, doTask, (link => link.match(/https:\/\/x\.com\/([^/]+)/)?.[1] || link.match(/https:\/\/twitter\.com\/([^/]+)/)?.[1]));
debug('处理后的Twitter用户列表', {
count: realUsers.length,
users: realUsers
});
if (realUsers.length > 0) {
for (const user of realUsers) {
if (Date.now() > this.#FwdForSdk.expiryTimeMillis) {
debug('Twitter SDK过期,重新获取', {
expiryTimeMillis: this.#FwdForSdk.expiryTimeMillis
});
this.#FwdForSdk = await getFwdForSdk();
}
await this.#toggleUser({
name: user,
doTask: doTask
});
await delay(1e3);
}
}
}
if (doTask && !globalOptions.doTask.twitter.retweets || !doTask && !globalOptions.undoTask.twitter.retweets) {
debug('根据全局选项跳过Twitter转推任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'twitter.retweets',
before: '[Twitter]'
});
} else {
const realRetweets = this.getRealParams('retweets', retweetLinks, doTask, (link => link.match(/https:\/\/x\.com\/.*?\/status\/([\d]+)/)?.[1] || link.match(/https:\/\/twitter\.com\/.*?\/status\/([\d]+)/)?.[1]));
debug('处理后的Twitter转推列表', {
count: realRetweets.length,
retweets: realRetweets
});
if (realRetweets.length > 0) {
for (const retweet of realRetweets) {
if (Date.now() > this.#FwdForSdk.expiryTimeMillis) {
debug('Twitter SDK过期,重新获取');
this.#FwdForSdk = await getFwdForSdk();
}
await this.#toggleRetweet({
retweetId: retweet,
doTask: doTask
});
await delay(1e3);
}
}
}
return true;
} catch (error) {
debug('处理Twitter链接任务时发生错误', {
error: error
});
throwError(error, 'Twitter.toggle');
return false;
}
}
#setCache(name, id) {
try {
debug('设置Twitter用户ID缓存', {
name: name,
id: id
});
this.#cache[name] = id;
GM_setValue('twitterCache', this.#cache);
} catch (error) {
debug('设置Twitter用户ID缓存时发生错误', {
error: error
});
throwError(error, 'Twitter.setCache');
}
}
}
class Vk extends Social {
tasks;
whiteList;
#username='';
#cache=GM_getValue('vkCache') || {};
#initialized=false;
constructor() {
super();
const defaultTasksTemplate = {
names: []
};
debug('初始化Vk实例');
this.tasks = defaultTasksTemplate;
this.whiteList = {
...defaultTasksTemplate,
...GM_getValue('whiteList')?.vk || {}
};
}
async init() {
try {
debug('开始初始化Vk模块');
if (this.#initialized) {
debug('Vk模块已初始化');
return true;
}
const isVerified = await this.#verifyAuth();
if (isVerified) {
debug('Vk授权验证成功');
echoLog({
before: '[Vk]'
}).success(I18n('initSuccess', 'Vk'));
this.#initialized = true;
return true;
}
debug('Vk初始化失败');
echoLog({
before: '[Vk]'
}).error(I18n('initFailed', 'Vk'));
return false;
} catch (error) {
debug('Vk初始化发生错误', {
error: error
});
throwError(error, 'Vk.init');
return false;
}
}
async #verifyAuth() {
try {
debug('开始验证Vk授权');
const logStatus = echoLog({
text: I18n('verifyAuth', 'Vk'),
before: '[Vk]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://vk.com/im',
method: 'GET'
});
if (result !== 'Success') {
debug('Vk授权验证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.finalUrl.includes('vk.com/login')) {
debug('Vk授权验证失败:需要登录');
logStatus.error(`Error:${I18n('loginVk')}`, true);
return false;
}
if (data?.status !== 200) {
debug('Vk授权验证状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
this.#username = data.responseText.match(/TopNavBtn__profileLink" href="\/(.*?)"/)?.[1] || '';
debug('Vk授权验证成功');
logStatus.success();
return true;
} catch (error) {
debug('Vk授权验证发生错误', {
error: error
});
throwError(error, 'Vk.verifyAuth');
return false;
}
}
async #toggleGroup(name, dataParam, doTask = true) {
try {
debug('开始处理Vk群组任务', {
name: name,
doTask: doTask
});
const logStatus = echoLog({
type: doTask ? 'joiningVkGroup' : 'leavingVkGroup',
text: name,
before: '[Vk]'
});
if (dataParam.groupAct === 'enter' && !doTask || dataParam.groupAct === 'leave' && doTask) {
debug('Vk群组操作已完成,跳过', {
name: name,
doTask: doTask
});
logStatus.success();
return true;
}
const reqData = {
act: doTask ? 'enter' : 'leave',
al: 1,
gid: dataParam.groupId,
hash: dataParam.groupHash
};
if (doTask) {
reqData.context = '_';
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://vk.com/al_groups.php',
method: 'POST',
headers: {
origin: 'https://vk.com',
referer: `https://vk.com/${name}`,
'content-type': 'application/x-www-form-urlencoded'
},
data: $.param(reqData)
});
if (result !== 'Success') {
debug('Vk群组操作请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('Vk群组操作状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('Vk群组操作成功', {
name: name,
doTask: doTask
});
logStatus.success();
if (doTask) {
this.tasks.names = unique([ ...this.tasks.names, name ]);
}
return true;
} catch (error) {
debug('处理Vk群组任务时发生错误', {
error: error
});
throwError(error, 'Vk.toggleGroup');
return false;
}
}
async #togglePublic(name, dataParam, doTask = true) {
try {
debug('开始处理Vk公共页面任务', {
name: name,
doTask: doTask
});
const logStatus = echoLog({
type: doTask ? 'joiningVkPublic' : 'leavingVkPublic',
text: name,
before: '[Vk]'
});
if (dataParam.publicJoined && doTask || !dataParam.publicJoined && !doTask) {
debug('Vk公共页面操作已完成,跳过', {
name: name,
doTask: doTask
});
logStatus.success();
return true;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://vk.com/al_public.php',
method: 'POST',
headers: {
origin: 'https://vk.com',
referer: `https://vk.com/${name}`,
'content-type': 'application/x-www-form-urlencoded'
},
data: $.param({
act: doTask ? 'a_enter' : 'a_leave',
al: 1,
pid: dataParam.publicPid,
hash: dataParam.publicHash
})
});
if (result !== 'Success') {
debug('Vk公共页面操作请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('Vk公共页面操作状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('Vk公共页面操作成功', {
name: name,
doTask: doTask
});
logStatus.success();
if (doTask) {
this.tasks.names = unique([ ...this.tasks.names, name ]);
}
return true;
} catch (error) {
debug('处理Vk公共页面任务时发生错误', {
error: error
});
throwError(error, 'Vk.togglePublic');
return false;
}
}
async #toggleLikeWall(name, dataParam, doTask = true) {
try {
debug('开始处理Vk点赞任务', {
name: name,
doTask: doTask
});
const logStatus = echoLog({
type: doTask ? 'likingVkPublic' : 'unlikingVkPublic',
text: name,
before: '[Vk]'
});
const postData = {
act: 'a_set_reaction',
al: 1,
event_subtype: 'post_modal',
from: 'wall_page',
hash: dataParam.hash,
object: dataParam.object,
track_code: dataParam.trackCode,
wall: 2
};
if (doTask) {
postData.reaction_id = 0;
}
const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({
url: 'https://vk.com/like.php?act=a_set_reaction',
method: 'POST',
headers: {
origin: 'https://vk.com',
referer: `https://vk.com/${name}`,
'content-type': 'application/x-www-form-urlencoded'
},
data: $.param(postData)
});
if (resultR !== 'Success') {
debug('Vk点赞操作请求失败', {
result: resultR,
statusText: statusTextR,
status: statusR
});
logStatus.error(`${resultR}:${statusTextR}(${statusR})`);
return false;
}
if (dataR?.status !== 200) {
debug('Vk点赞操作状态错误', {
status: dataR?.status,
statusText: dataR?.statusText
});
logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`);
return false;
}
if (dataR.response?.payload?.[1]?.[1]?.like_my !== true) {
debug('Vk点赞操作验证失败');
logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`);
return false;
}
debug('Vk点赞操作成功', {
name: name,
doTask: doTask
});
logStatus.success();
return true;
} catch (error) {
debug('处理Vk点赞任务时发生错误', {
error: error
});
throwError(error, 'Vk.sendWall');
return false;
}
}
async #sendWall(name) {
try {
debug('开始处理Vk转发任务', {
name: name
});
const logStatus = echoLog({
type: 'sendingVkWall',
text: name,
before: '[Vk]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://vk.com/like.php',
method: 'POST',
headers: {
origin: 'https://vk.com',
referer: `https://vk.com/${name}`,
'content-type': 'application/x-www-form-urlencoded'
},
data: $.param({
act: 'publish_box',
al: 1,
object: name
})
});
if (result !== 'Success') {
debug('Vk转发操作请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('Vk转发操作状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const hash = data.responseText.match(/shHash:[\s]*'(.*?)'/)?.[1];
if (!hash) {
debug('获取Vk转发hash失败');
logStatus.error('Error: Get "hash" failed');
return false;
}
const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({
url: 'https://vk.com/like.php',
method: 'POST',
headers: {
origin: 'https://vk.com',
referer: `https://vk.com/${name}`,
'content-type': 'application/x-www-form-urlencoded'
},
data: $.param({
Message: '',
act: 'a_do_publish',
al: 1,
close_comments: 0,
friends_only: 0,
from: 'box',
hash: hash,
list: '',
mark_as_ads: 0,
mute_notifications: 0,
object: name,
ret_data: 1,
to: 0
})
});
if (resultR !== 'Success') {
debug('Vk转发确认请求失败', {
result: resultR,
statusText: statusTextR,
status: statusR
});
logStatus.error(`${resultR}:${statusTextR}(${statusR})`);
return false;
}
if (dataR?.status !== 200) {
debug('Vk转发确认状态错误', {
status: dataR?.status,
statusText: dataR?.statusText
});
logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`);
return false;
}
const jsonData = JSON.parse(dataR.responseText?.replace('\x3c!--', '') || '{}');
if (jsonData?.payload?.[1]?.[1]?.share_my !== true) {
debug('Vk转发确认验证失败');
logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`);
return false;
}
debug('Vk转发操作成功', {
name: name
});
logStatus.success();
const postId = jsonData?.payload?.[1]?.[1]?.post_id;
const ownerId = jsonData?.payload?.[1]?.[1]?.owner_id;
if (postId && ownerId) {
this.#setCache(name, `${ownerId}_${postId}`);
}
this.tasks.names = unique([ ...this.tasks.names, name ]);
return true;
} catch (error) {
debug('处理Vk转发任务时发生错误', {
error: error
});
throwError(error, 'Vk.sendWall');
return false;
}
}
async #deleteWall(name, dataParams) {
try {
debug('开始处理Vk删除墙任务', {
name: name
});
const logStatus = echoLog({
type: 'deletingVkWall',
text: name,
before: '[Vk]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://vk.com/al_wall.php?act=delete',
method: 'POST',
headers: {
origin: 'https://vk.com',
referer: `https://vk.com/${this.#username}?w=wall${this.#cache[name]}%2Fall`,
'content-type': 'application/x-www-form-urlencoded'
},
data: $.param({
act: 'delete',
al: 1,
confirm: 0,
from: 'wkview',
hash: dataParams.wallHash,
post: this.#cache[name]
})
});
if (result !== 'Success') {
debug('Vk删除墙请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('Vk删除墙状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const jsonData = JSON.parse(data.responseText?.replace('\x3c!--', '') || '{}');
if (!jsonData?.payload?.[1]?.[1]) {
debug('Vk删除墙验证失败');
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('Vk删除墙操作成功', {
name: name
});
logStatus.success();
return true;
} catch (error) {
debug('处理Vk删除墙任务时发生错误', {
error: error
});
throwError(error, 'Vk.deleteWall');
return false;
}
}
async #getId(name, doTask) {
try {
debug('开始获取Vk ID', {
name: name,
doTask: doTask
});
let url = `https://vk.com/${name}`;
if (/^wall-/.test(name)) {
if (doTask) {
return {
type: 'sendWall'
};
}
if (!this.#cache[name]) {
return {
type: 'unSupport'
};
}
url = `https://vk.com/${this.#username}?w=wall${this.#cache[name]}`;
}
const logStatus = echoLog({
type: 'gettingVkId',
text: name,
before: '[Vk]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: url,
method: 'GET'
});
if (result !== 'Success') {
debug('获取Vk ID请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取Vk ID状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const [, groupAct, groupId, , groupHash] = data.responseText.match(/Groups.(enter|leave)\(.*?,.*?([\d]+?), ('|')(.*?)('|')/) || [];
const publicHash = data.responseText.match(/"enterHash":"(.*?)"/)?.[1];
const publicPid = data.responseText.match(/"public_id":([\d]+?),/)?.[1];
const publicJoined = !data.responseText.includes('Public.subscribe');
if (groupAct && groupId && groupHash) {
debug('获取到Vk群组ID', {
groupAct: groupAct,
groupId: groupId,
groupHash: groupHash
});
logStatus.success();
return {
groupAct: groupAct,
groupId: groupId,
groupHash: groupHash,
type: 'group'
};
}
if (publicHash && publicPid) {
debug('获取到Vk公共页面ID', {
publicHash: publicHash,
publicPid: publicPid,
publicJoined: publicJoined
});
logStatus.success();
return {
publicHash: publicHash,
publicPid: publicPid,
publicJoined: publicJoined,
type: 'public'
};
}
if (name.includes('action=like')) {
const hash = data.responseText.match(/data-reaction-hash="(.*?)"/)?.[1];
const trackCode = data.responseText.match(/data-post-track-code="(.*?)"/)?.[1];
const object = name.match(/wall-[\w_]+/)?.[0];
if (hash && trackCode && object) {
debug('获取到Vk点赞ID', {
hash: hash,
trackCode: trackCode,
object: object
});
logStatus.success();
return {
type: 'likeWall',
hash: hash,
trackCode: trackCode,
object: object
};
}
}
if (data.responseText.includes('wall.deletePost') && !doTask) {
const wallHash = data.responseText.match(/wall\.deletePost\(this, '.*?', '(.*?)'\)/)?.[1];
if (wallHash) {
debug('获取到Vk删除墙ID', {
wallHash: wallHash
});
logStatus.success();
return {
type: 'deleteWall',
wallHash: wallHash
};
}
}
if (name.includes('wall') && doTask) {
debug('获取到Vk墙ID');
logStatus.success();
return {
type: 'sendWall'
};
}
debug('未找到Vk ID参数');
logStatus.error('Error: Parameters not found!');
return false;
} catch (error) {
debug('获取Vk ID时发生错误', {
error: error
});
throwError(error, 'Vk.getId');
return false;
}
}
async #toggleVk({name: name, doTask: doTask = true}) {
try {
debug('开始处理Vk任务', {
name: name,
doTask: doTask
});
if (!doTask && this.whiteList.names.includes(name)) {
debug('Vk任务在白名单中,跳过', {
name: name
});
echoLog({
type: 'whiteList',
text: 'Vk.undoTask',
id: name,
before: '[Vk]'
});
return true;
}
const formatName = name.replace(/\/$/, '');
const data = await this.#getId(formatName, doTask);
if (!data) {
return false;
}
switch (data.type) {
case 'group':
return await this.#toggleGroup(formatName, data, doTask);
case 'public':
return await this.#togglePublic(formatName, data, doTask);
case 'likeWall':
return await this.#toggleLikeWall(formatName, data, doTask);
case 'sendWall':
return doTask ? await this.#sendWall(formatName) : true;
case 'deleteWall':
return doTask ? true : await this.#deleteWall(formatName, data);
default:
debug('未知的Vk任务类型', {
type: data.type
});
return false;
}
} catch (error) {
debug('处理Vk任务时发生错误', {
error: error
});
throwError(error, 'Vk.toggleVk');
return false;
}
}
async toggle({doTask: doTask = true, nameLinks: nameLinks = []}) {
try {
debug('开始处理Vk链接任务', {
doTask: doTask,
nameLinksCount: nameLinks.length
});
if (!this.#initialized) {
debug('Vk模块未初始化');
echoLog({
text: I18n('needInit'),
before: '[Vk]'
});
return false;
}
const prom = [];
if (doTask && !globalOptions.doTask.vk.names || !doTask && !globalOptions.undoTask.vk.names) {
debug('根据全局选项跳过Vk任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'vk.names',
before: '[Vk]'
});
} else {
const realNames = this.getRealParams('names', nameLinks, doTask, (link => link.match(/https:\/\/vk\.com\/([^/]+)/)?.[1]));
debug('处理后的Vk链接列表', {
count: realNames.length,
names: realNames
});
if (realNames.length > 0) {
for (const name of realNames) {
prom.push(this.#toggleVk({
name: name,
doTask: doTask
}));
await delay(1e3);
}
}
}
return Promise.all(prom).then((() => true));
} catch (error) {
debug('处理Vk链接任务时发生错误', {
error: error
});
throwError(error, 'Vk.toggle');
return false;
}
}
#setCache(name, postId) {
try {
debug('设置Vk缓存', {
name: name,
postId: postId
});
this.#cache[name] = postId;
GM_setValue('vkCache', this.#cache);
} catch (error) {
debug('设置Vk缓存时发生错误', {
error: error
});
throwError(error, 'Vk.setCache');
}
}
}
const getInfo = async function(link, type) {
try {
debug('开始获取YouTube信息', {
link: link,
type: type
});
const logStatus = echoLog({
text: I18n('gettingYtbToken'),
before: '[Youtube]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success') {
debug('获取YouTube信息请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return {};
}
if (data?.status !== 200) {
debug('获取YouTube信息状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return {};
}
if (data.responseText.includes('accounts.google.com/ServiceLogin?service=youtube')) {
debug('获取YouTube信息失败:需要登录');
logStatus.error(`Error:${I18n('loginYtb')}`, true);
return {
needLogin: true
};
}
const apiKey = data.responseText.match(/"INNERTUBE_API_KEY":"(.*?)"/)?.[1];
const context = (data.responseText.match(/\(\{"INNERTUBE_CONTEXT":([\w\W]*?)\}\)/) || data.responseText.match(/"INNERTUBE_CONTEXT":([\w\W]*?\}),"INNERTUBE/))?.[1] || '{}';
const {client: client, request: request} = JSON.parse(context);
if (!apiKey || !client || !request) {
debug('获取YouTube信息失败:缺少必要参数');
logStatus.error('Error: Parameter "apiKey" not found!');
return {};
}
client.hl = 'en';
if (type === 'channel') {
const channelId = data.responseText.match(/"channelId":"(.+?)"/)?.[1];
if (!channelId) {
debug('获取YouTube频道ID失败');
logStatus.error('Error: Get "channelId" failed!');
return {};
}
debug('成功获取YouTube频道信息', {
channelId: channelId
});
logStatus.success();
return {
params: {
apiKey: apiKey,
client: client,
request: request,
channelId: channelId
}
};
}
if (type === 'likeVideo') {
const videoId = data.responseText.match(/<link rel="shortlinkUrl" href="https:\/\/youtu\.be\/(.*?)">/)?.[1];
const likeParams = data.responseText.match(/"likeParams":"(.*?)"/)?.[1];
if (!videoId) {
debug('获取YouTube视频ID失败');
logStatus.error('Error: Get "videoId" failed!');
return {};
}
debug('成功获取YouTube视频信息', {
videoId: videoId
});
logStatus.success();
return {
params: {
apiKey: apiKey,
client: client,
request: request,
videoId: videoId,
likeParams: likeParams
}
};
}
debug('未知的YouTube信息类型', {
type: type
});
logStatus.error('Error: Unknown type');
return {};
} catch (error) {
debug('获取YouTube信息时发生错误', {
error: error
});
throwError(error, 'Youtube.getInfo');
return {};
}
};
class Youtube extends Social {
tasks;
whiteList;
#auth=GM_getValue('youtubeAuth') || {};
#initialized=false;
#verifyChannel=`https://www.youtube.com/channel/${globalOptions.other.youtubeVerifyChannel}`;
constructor() {
super();
const defaultTasksTemplate = {
channels: [],
likes: []
};
debug('初始化YouTube实例');
this.tasks = defaultTasksTemplate;
this.whiteList = {
...defaultTasksTemplate,
...GM_getValue('whiteList')?.youtube || {}
};
}
async init() {
try {
debug('开始初始化YouTube模块');
if (this.#initialized) {
debug('YouTube模块已初始化');
return true;
}
if (!this.#auth.PAPISID) {
debug('YouTube授权信息不完整,需要更新授权');
if (await this.#updateAuth()) {
this.#initialized = true;
return true;
}
return false;
}
const isVerified = await this.#verifyAuth();
if (isVerified) {
debug('YouTube授权验证成功');
echoLog({
before: '[Youtube]'
}).success(I18n('initSuccess', 'Youtube'));
this.#initialized = true;
return true;
}
debug('YouTube授权失效,尝试重新获取');
GM_setValue('youtubeAuth', null);
if (await this.#updateAuth()) {
debug('YouTube重新授权成功');
echoLog({
before: '[Youtube]'
}).success(I18n('initSuccess', 'Youtube'));
this.#initialized = true;
return true;
}
debug('YouTube初始化失败');
echoLog({
before: '[Youtube]'
}).error(I18n('initFailed', 'Youtube'));
return false;
} catch (error) {
debug('YouTube初始化发生错误', {
error: error
});
throwError(error, 'Youtube.init');
return false;
}
}
async #verifyAuth() {
try {
debug('开始验证YouTube授权');
return await this.#toggleChannel({
link: this.#verifyChannel,
doTask: true,
verify: true
});
} catch (error) {
debug('YouTube授权验证发生错误', {
error: error
});
throwError(error, 'Youtube.verifyAuth');
return false;
}
}
async #updateAuth() {
try {
debug('开始更新YouTube授权');
const logStatus = echoLog({
text: I18n('updatingAuth', 'Youtube'),
before: '[Youtube]'
});
return await new Promise((resolve => {
GM_cookie.list({
url: 'https://www.youtube.com/@YouTube'
}, (async (cookies, error) => {
if (!error) {
const PAPISID = cookies.find((cookie => cookie.name === '__Secure-3PAPISID'))?.value;
if (PAPISID) {
debug('成功获取YouTube新授权信息');
GM_setValue('youtubeAuth', {
PAPISID: PAPISID
});
this.#auth = {
PAPISID: PAPISID
};
logStatus.success();
resolve(await this.#verifyAuth());
} else {
debug('获取YouTube授权失败:未登录');
logStatus.error(I18n('needLogin'));
resolve(false);
}
} else {
debug('获取YouTube授权失败', {
error: error
});
logStatus.error('Error: Update youtube auth failed!');
resolve(false);
}
}));
}));
} catch (error) {
debug('更新YouTube授权时发生错误', {
error: error
});
throwError(error, 'Youtube.updateAuth');
return false;
}
}
#getInfo(link, type) {
debug('调用获取YouTube信息方法', {
link: link,
type: type
});
return getInfo(link, type);
}
async #toggleChannel({link: link, doTask: doTask = true, verify: verify = false}) {
try {
debug('开始处理YouTube频道任务', {
link: link,
doTask: doTask,
verify: verify
});
const {params: params, needLogin: needLogin} = await this.#getInfo(link, 'channel');
const {apiKey: apiKey, client: client, request: request, channelId: channelId} = params || {};
if (needLogin) {
debug('YouTube频道操作失败:需要登录');
echoLog({
html: I18n('loginYtb'),
before: '[Youtube]'
});
return false;
}
if (!(apiKey && client && request && channelId)) {
debug('YouTube频道操作失败:获取参数失败');
echoLog({
text: '"getYtbToken" failed',
before: '[Youtube]'
});
return false;
}
if (!doTask && !verify && this.whiteList.channels.includes(channelId)) {
debug('YouTube频道在白名单中,跳过取消订阅', {
channelId: channelId
});
echoLog({
type: 'whiteList',
text: 'Youtube.unfollowChannel',
id: channelId,
before: '[Youtube]'
});
return true;
}
const logStatus = verify ? echoLog({
text: I18n('verifyingAuth', 'Youtube'),
before: '[Youtube]'
}) : echoLog({
type: doTask ? 'followingYtbChannel' : 'unfollowingYtbChannel',
text: channelId,
before: '[Youtube]'
});
const nowTime = parseInt(String((new Date).getTime() / 1e3), 10);
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://www.youtube.com/youtubei/v1/subscription/${doTask ? '' : 'un'}subscribe?key=${apiKey}&prettyPrint=false`,
method: 'POST',
headers: {
origin: 'https://www.youtube.com',
referer: `https://www.youtube.com/channel/${channelId}`,
'content-type': 'application/json',
'x-goog-authuser': '0',
'x-goog-visitor-id': client?.visitorData,
'x-origin': 'https://www.youtube.com',
authorization: `SAPISIDHASH ${nowTime}_${sha1(`${nowTime} ${this.#auth.PAPISID} https://www.youtube.com`)}`
},
data: JSON.stringify({
context: {
client: client,
request: {
sessionId: request?.sessionId,
internalExperimentFlags: [],
consistencyTokenJars: []
},
user: {}
},
channelIds: [ channelId ],
params: doTask ? 'EgIIAhgA' : 'CgIIAhgA'
})
});
if (result !== 'Success') {
debug('YouTube频道操作请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('YouTube频道操作状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const isSubscribed = doTask && (/"subscribed":true/.test(data.responseText) || data.responseText.includes('The subscription already exists'));
const isUnsubscribed = !doTask && /"subscribed":false/.test(data.responseText);
const isVerified = verify && data.responseText.includes('You may not subscribe to yourself');
if (isSubscribed || isUnsubscribed || isVerified) {
debug('YouTube频道操作成功', {
doTask: doTask,
verify: verify
});
logStatus.success();
if (doTask && !verify) {
this.tasks.channels = unique([ ...this.tasks.channels, link ]);
}
return true;
}
debug('YouTube频道操作失败,需要更新授权');
logStatus.error(I18n('tryUpdateYtbAuth'), true);
return false;
} catch (error) {
debug('处理YouTube频道任务时发生错误', {
error: error
});
throwError(error, 'Youtube.toggleChannel');
return false;
}
}
async #toggleLikeVideo({link: link, doTask: doTask = true}) {
try {
debug('开始处理YouTube视频点赞任务', {
link: link,
doTask: doTask
});
const {params: params, needLogin: needLogin} = await this.#getInfo(link, 'likeVideo');
const {apiKey: apiKey, client: client, request: request, videoId: videoId, likeParams: likeParams} = params || {};
if (needLogin) {
debug('YouTube视频点赞失败:需要登录');
echoLog({
html: `${I18n('loginYtb')}`,
before: '[Youtube]'
});
return false;
}
if (!(apiKey && client && request && videoId && likeParams)) {
debug('YouTube视频点赞失败:获取参数失败');
echoLog({
text: '"getYtbToken" failed',
before: '[Youtube]'
});
return false;
}
if (!doTask && this.whiteList.likes.includes(videoId)) {
debug('YouTube视频在白名单中,跳过取消点赞', {
videoId: videoId
});
echoLog({
type: 'whiteList',
text: 'Youtube.unlikeVideo',
id: videoId,
before: '[Youtube]'
});
return true;
}
const logStatus = echoLog({
type: doTask ? 'likingYtbVideo' : 'unlikingYtbVideo',
text: videoId,
before: '[Youtube]'
});
const nowTime = parseInt(String((new Date).getTime() / 1e3), 10);
const likeVideoData = {
context: {
client: client,
request: {
sessionId: request.sessionId,
internalExperimentFlags: [],
consistencyTokenJars: []
},
user: {}
},
target: {
videoId: videoId
}
};
if (doTask && !likeParams) {
debug('YouTube视频点赞失败:缺少likeParams参数');
logStatus.error('Empty likeParams');
return false;
}
if (doTask) {
likeVideoData.params = likeParams;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://www.youtube.com/youtubei/v1/like/${doTask ? '' : 'remove'}like?key=${apiKey}`,
method: 'POST',
headers: {
origin: 'https://www.youtube.com',
referer: `https://www.youtube.com/watch?v=${videoId}`,
'content-type': 'application/json',
'x-goog-authuser': '0',
'x-goog-visitor-id': client.visitorData,
'x-origin': 'https://www.youtube.com',
authorization: `SAPISIDHASH ${nowTime}_${sha1(`${nowTime} ${this.#auth.PAPISID} https://www.youtube.com`)}`
},
data: JSON.stringify(likeVideoData)
});
if (result !== 'Success') {
debug('YouTube视频点赞请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('YouTube视频点赞状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const isLiked = doTask && data.responseText.includes('Added to Liked videos');
const isUnliked = !doTask && (data.responseText.includes('Removed from Liked videos') || data.responseText.includes('Dislike removed'));
if (isLiked || isUnliked) {
debug('YouTube视频点赞操作成功', {
doTask: doTask
});
logStatus.success();
if (doTask) {
this.tasks.likes = unique([ ...this.tasks.likes, link ]);
}
return true;
}
debug('YouTube视频点赞失败,需要更新授权');
logStatus.error(I18n('tryUpdateYtbAuth'), true);
return false;
} catch (error) {
debug('处理YouTube视频点赞任务时发生错误', {
error: error
});
throwError(error, 'Youtube.toggleLikeVideo');
return false;
}
}
async toggle({doTask: doTask = true, channelLinks: channelLinks = [], videoLinks: videoLinks = []}) {
try {
debug('开始处理YouTube链接任务', {
doTask: doTask,
channelLinksCount: channelLinks.length,
videoLinksCount: videoLinks.length
});
if (!this.#initialized) {
debug('YouTube模块未初始化');
echoLog({
text: I18n('needInit'),
before: '[Youtube]'
});
return false;
}
const prom = [];
const shouldProcessChannels = doTask && globalOptions.doTask.youtube.channels || !doTask && globalOptions.undoTask.youtube.channels;
const shouldProcessVideos = doTask && globalOptions.doTask.youtube.likes || !doTask && globalOptions.undoTask.youtube.likes;
if (!shouldProcessChannels) {
debug('根据全局选项跳过YouTube频道任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'youtube.channels',
before: '[Youtube]'
});
} else {
const realChannels = this.getRealParams('channels', channelLinks, doTask, (link => {
if (/^https:\/\/(www\.)?google\.com.*?\/url\?.*?url=https:\/\/www\.youtube\.com\/.*/.test(link)) {
return link.match(/url=(https:\/\/www\.youtube\.com\/.*)/)?.[1];
}
return link;
}));
debug('处理后的YouTube频道链接列表', {
count: realChannels.length,
channels: realChannels
});
if (realChannels.length > 0) {
for (const channel of realChannels) {
prom.push(this.#toggleChannel({
link: channel,
doTask: doTask
}));
await delay(1e3);
}
}
}
if (!shouldProcessVideos) {
debug('根据全局选项跳过YouTube视频任务', {
doTask: doTask
});
echoLog({
type: 'globalOptionsSkip',
text: 'youtube.likes',
before: '[Youtube]'
});
} else {
const realLikes = this.getRealParams('likes', videoLinks, doTask, (link => {
if (/^https:\/\/(www\.)?google\.com.*?\/url\?.*?url=https:\/\/www\.youtube\.com\/.*/.test(link)) {
return link.match(/url=(https:\/\/www\.youtube\.com\/.*)/)?.[1];
}
return link;
}));
debug('处理后的YouTube视频链接列表', {
count: realLikes.length,
videos: realLikes
});
if (realLikes.length > 0) {
for (const video of realLikes) {
prom.push(this.#toggleLikeVideo({
link: video,
doTask: doTask
}));
await delay(1e3);
}
}
}
return Promise.all(prom).then((() => true));
} catch (error) {
debug('处理YouTube链接任务时发生错误', {
error: error
});
throwError(error, 'Youtube.toggle');
return false;
}
}
}
class SteamASF {
#asfOptions;
#botName='asf';
#groupInfo;
#steamWebApiKey;
#steamId;
constructor({AsfIpcUrl: AsfIpcUrl, AsfIpcPassword: AsfIpcPassword, AsfBotname: AsfBotname, steamWebApiKey: steamWebApiKey}) {
debug('初始化SteamASF实例', {
AsfIpcUrl: AsfIpcUrl,
AsfBotname: AsfBotname
});
const asfCommandsUrl = new URL('/Api/Command/', AsfIpcUrl);
this.#asfOptions = {
url: asfCommandsUrl.href,
method: 'POST',
responseType: 'json',
headers: {
accept: 'application/json',
'Content-Type': 'application/json',
Host: asfCommandsUrl.host,
Origin: asfCommandsUrl.origin,
Referer: asfCommandsUrl.href,
Authentication: AsfIpcPassword
}
};
if (AsfBotname) {
this.#botName = AsfBotname;
}
if (steamWebApiKey) {
this.#steamWebApiKey = steamWebApiKey;
}
debug('SteamASF实例初始化完成', {
botName: this.#botName
});
}
async init() {
try {
debug('开始初始化ASF');
const logStatus = echoLog({
text: I18n('initingASF'),
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: '{"Command":"!stats"}'
});
if (result !== 'Success') {
debug('ASF初始化请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.response?.Success === true && data.response.Message === 'OK' && data.response.Result) {
debug('ASF初始化成功');
logStatus.success();
return true;
}
if (data?.response?.Result || data?.response?.Message) {
debug('ASF初始化失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(data?.response?.Result || data.response.Message);
return false;
}
debug('ASF初始化失败', {
statusText: data?.statusText,
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('ASF初始化发生错误', {
error: error
});
throwError(error, 'SteamASF.init');
return false;
}
}
async joinGroup(groupName) {
try {
debug('开始加入Steam组', {
groupName: groupName
});
const logStatus = echoLog({
type: 'joiningSteamGroup',
text: groupName,
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!JOINGROUP ${this.#botName} ${groupName}`
})
});
if (result !== 'Success') {
debug('加入Steam组请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '已加入', '已申请', 'Joined', 'Applied', 'Присоединился', 'costs' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功加入Steam组', {
groupName: groupName
});
logStatus.success();
return true;
}
debug('加入Steam组失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('加入Steam组时发生错误', {
error: error,
groupName: groupName
});
throwError(error, 'SteamASF.joinGroup');
return false;
}
}
joinOfficialGroup=this.joinGroup;
leaveOfficialGroup=this.leaveGroup;
async leaveGroup(groupName) {
try {
debug('开始退出Steam组', {
groupName: groupName
});
if (!this.#groupInfo) {
debug('未找到组信息,尝试获取组ID');
if (!await this.#getGroupId()) {
return false;
}
}
const groupId = await this.#groupInfo[groupName];
if (!groupId) {
debug('未找到组ID', {
groupName: groupName
});
return false;
}
const logStatus = echoLog({
type: 'leavingSteamGroup',
text: groupName,
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!LEAVEGROUP ${this.#botName} ${groupId}`
})
});
if (result !== 'Success') {
debug('退出Steam组请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功退出Steam组', {
groupName: groupName
});
logStatus.success();
return true;
}
debug('退出Steam组失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('退出Steam组时发生错误', {
error: error,
groupName: groupName
});
throwError(error, 'SteamASF.leaveGroup');
return false;
}
}
async #getGroupId() {
try {
debug('开始获取Steam组ID列表');
const logStatus = echoLog({
type: 'gettingSteamGroupId',
text: 'All',
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!GROUPLIST ${this.#botName}`
})
});
if (result !== 'Success') {
debug('获取Steam组ID列表请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && data.response?.Result?.includes('|')) {
this.#groupInfo = Object.fromEntries(data.response.Result.split('\n').map((line => {
const [, name, id] = line.trim().split('|');
if (name && id) {
return [ name, id ];
}
return null;
})).filter((ele => ele)));
debug('成功获取Steam组ID列表', {
groupCount: Object.keys(this.#groupInfo).length
});
logStatus.success();
return true;
}
debug('获取Steam组ID列表失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('获取Steam组ID列表时发生错误', {
error: error
});
throwError(error, 'SteamASF.getGroupID');
return false;
}
}
async addToWishlist(gameId) {
try {
debug('开始添加游戏到愿望单', {
gameId: gameId
});
const logStatus = echoLog({
type: 'addingToWishlist',
text: gameId,
before: '[ASF]'
});
const gameStatus = await this.#checkGame(gameId);
if (gameStatus.wishlist === true) {
debug('游戏已在愿望单中', {
gameId: gameId
});
logStatus.success();
return true;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!ADDWISHLIST ${this.#botName} ${gameId}`
})
});
if (result !== 'Success') {
debug('添加游戏到愿望单请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功添加游戏到愿望单', {
gameId: gameId
});
logStatus.success();
return true;
}
debug('添加游戏到愿望单失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('添加游戏到愿望单时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'SteamASF.addToWishlist');
return false;
}
}
async removeFromWishlist(gameId) {
try {
debug('开始从愿望单移除游戏', {
gameId: gameId
});
const logStatus = echoLog({
type: 'removingFromWishlist',
text: gameId,
before: '[ASF]'
});
const gameStatus = await this.#checkGame(gameId);
if (gameStatus.wishlist === false) {
debug('游戏已不在愿望单中', {
gameId: gameId
});
logStatus.success();
return true;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!REMOVEWISHLIST ${this.#botName} ${gameId}`
})
});
if (result !== 'Success') {
debug('从愿望单移除游戏请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功从愿望单移除游戏', {
gameId: gameId
});
logStatus.success();
return true;
}
debug('从愿望单移除游戏失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('从愿望单移除游戏时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'SteamASF.removeFromWishlist');
return false;
}
}
async toggleFollowGame(gameId, doTask) {
try {
debug('开始处理游戏关注状态', {
gameId: gameId,
doTask: doTask
});
const logStatus = echoLog({
type: `${doTask ? '' : 'un'}followingGame`,
text: gameId,
before: '[ASF]'
});
const gameStatus = await this.#checkGame(gameId);
if (doTask && gameStatus.followed === true || !doTask && gameStatus.followed === false) {
debug('游戏关注状态已符合要求', {
gameId: gameId,
doTask: doTask,
currentStatus: gameStatus.followed
});
logStatus.success();
return true;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!${doTask ? '' : 'UN'}FOLLOWGAME ${this.#botName} ${gameId}`
})
});
if (result !== 'Success') {
debug('处理游戏关注状态请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功处理游戏关注状态', {
gameId: gameId,
doTask: doTask
});
logStatus.success();
return true;
}
debug('处理游戏关注状态失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('处理游戏关注状态时发生错误', {
error: error,
gameId: gameId,
doTask: doTask
});
throwError(error, 'SteamASF.toggleFollowGame');
return false;
}
}
async #checkGame(gameId) {
try {
debug('开始检查游戏状态', {
gameId: gameId
});
const {result: result, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!CHECK ${this.#botName} ${gameId}`
})
});
if (result !== 'Success') {
debug('检查游戏状态请求失败', {
result: result
});
return {};
}
if (data?.status !== 200 || !data.response?.Result?.includes(gameId)) {
debug('检查游戏状态响应无效', {
status: data?.status
});
return {};
}
const matchedResult = data.response.Result.split('\n').find((result => result.includes(gameId)))?.split('|');
if (!matchedResult || matchedResult.length <= 3) {
debug('未找到游戏状态信息', {
gameId: gameId
});
return {};
}
const status = {
wishlist: matchedResult.at(-3).trim() === '√' || matchedResult.at(-2).trim() === '√',
followed: matchedResult.at(-1).trim() === '√'
};
debug('成功获取游戏状态', {
gameId: gameId,
status: status
});
return status;
} catch (error) {
debug('检查游戏状态时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'SteamASF.checkGame');
return {};
}
}
async toggleCurator(curatorId, doTask = true) {
try {
debug('开始处理鉴赏家关注状态', {
curatorId: curatorId,
doTask: doTask
});
const logStatus = echoLog({
type: doTask ? 'followingCurator' : 'unfollowingCurator',
text: curatorId,
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!${doTask ? '' : 'UN'}FOLLOWCURATOR ${this.#botName} ${curatorId}`
})
});
if (result !== 'Success') {
debug('处理鉴赏家关注状态请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功处理鉴赏家关注状态', {
curatorId: curatorId,
doTask: doTask
});
logStatus.success();
return true;
}
if (data?.status === 200) {
debug('处理鉴赏家关注状态失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(I18n('curatorLimitNotice'));
return false;
}
debug('处理鉴赏家关注状态失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('处理鉴赏家关注状态时发生错误', {
error: error,
curatorId: curatorId,
doTask: doTask
});
throwError(error, 'Steam.toggleCurator');
return false;
}
}
async addLicense(id) {
try {
debug('开始添加许可证', {
id: id
});
const [type, ids] = id.split('-');
const idsArr = ids.split(',');
if (type === 'appid') {
const logStatus = echoLog({
type: 'addingFreeLicense',
text: ids,
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!addlicense ${this.#botName} ${idsArr.map((id => `app/${id}`)).join(',')}`
})
});
if (result !== 'Success') {
debug('添加应用许可证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ 'AlreadyPurchased', 'OK' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功添加应用许可证', {
ids: ids
});
logStatus.success();
return true;
}
debug('添加应用许可证失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
}
if (type === 'subid') {
const logStatus = echoLog({
type: 'addingFreeLicenseSubid',
text: ids,
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!addlicense ${this.#botName} ${idsArr.map((id => `sub/${id}`)).join(',')}`
})
});
if (result !== 'Success') {
debug('添加订阅许可证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && data.response?.Result) {
const resultLines = data.response.Result.split('\n');
debug('处理订阅许可证结果', {
resultLines: resultLines
});
idsArr.forEach((subid => {
const targetLine = resultLines.find((text => text.includes(subid)));
if (targetLine && [ '成功', 'Success', 'Успех' ].find((text => targetLine.includes(text)))) {
debug('成功添加订阅许可证', {
subid: subid
});
echoLog({
before: '[ASF]'
}).success(targetLine);
} else {
debug('添加订阅许可证失败', {
subid: subid,
targetLine: targetLine
});
echoLog({
before: '[ASF]'
}).error(targetLine);
}
}));
return true;
}
debug('添加订阅许可证失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
}
debug('无效的许可证类型', {
type: type
});
return false;
} catch (error) {
debug('添加许可证时发生错误', {
error: error,
id: id
});
throwError(error, 'SteamASF.addLicense');
return false;
}
}
async requestPlayTestAccess(id) {
try {
debug('开始请求游戏试玩权限', {
id: id
});
const logStatus = echoLog({
type: 'requestingPlayTestAccess',
text: id,
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!REQUESTACCESS ${this.#botName} ${id}`
})
});
if (result !== 'Success') {
debug('请求游戏试玩权限请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功请求游戏试玩权限', {
id: id
});
logStatus.success();
return true;
}
debug('请求游戏试玩权限失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('请求游戏试玩权限时发生错误', {
error: error,
id: id
});
throwError(error, 'SteamASF.requestPlayTestAccess');
return false;
}
}
async playGames(ids) {
try {
debug('开始挂游戏时长', {
ids: ids
});
const logStatus = echoLog({
text: I18n('playingGames', ids),
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!play ${this.#botName} ${ids}`
})
});
if (result !== 'Success') {
debug('挂游戏时长请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '正在运行', '正在掛', 'Playing', 'Играет' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功开始挂游戏时长', {
ids: ids
});
logStatus.success();
return true;
}
debug('开始挂游戏时长失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('挂游戏时长时发生错误', {
error: error,
ids: ids
});
throwError(error, 'SteamASF.playGames');
return false;
}
}
async getSteamIdASF() {
try {
debug('开始获取Steam ID');
const logStatus = echoLog({
text: I18n('gettingSteamId'),
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!steamid ${this.#botName}`
})
});
if (result !== 'Success' || data?.status !== 200) {
debug('获取Steam ID请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return '';
}
if (data.response?.Result) {
const steamId = data.response.Result.trim()?.split(/\s+/)?.at(-1);
if (steamId) {
debug('成功获取Steam ID', steamId);
logStatus.success();
return steamId;
}
}
debug('获取Steam ID失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return '';
} catch (error) {
debug('获取Steam ID时发生错误', {
error: error
});
throwError(error, 'SteamASF.getSteamIdASF');
return '';
}
}
async getSteamIdWeb() {
try {
debug('开始获取Steam ID');
const logStatus = echoLog({
text: I18n('gettingSteamId'),
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://store.steampowered.com',
method: 'GET',
headers: {
host: 'store.steampowered.com'
}
});
if (result !== 'Success' || data?.status !== 200) {
debug('获取Steam ID请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return '';
}
const steamId = data.responseText.match(/steamid":"(\d+)/)?.[1];
if (steamId) {
debug('成功获取Steam ID', steamId);
logStatus.success();
return steamId;
}
debug('获取Steam ID失败', {
data: data
});
logStatus.error(`${result}:${statusText}(${status})`);
return '';
} catch (error) {
debug('获取Steam ID时发生错误', {
error: error
});
throwError(error, 'SteamASF.getSteamIdWeb');
return '';
}
}
async getSteamId() {
const steamId = await this.getSteamIdWeb();
if (steamId) {
return steamId;
}
return this.getSteamIdASF();
}
async checkPlayStatus(ids) {
try {
debug('开始检查挂游戏时长状态');
if (!this.#steamWebApiKey) {
debug('未设置Steam Web API Key');
return 'skip';
}
if (!this.#steamId) {
const steamId = await this.getSteamId();
if (!steamId) {
debug('未获取到Steam ID');
return 'skip';
}
this.#steamId = steamId;
}
const logStatus = echoLog({
text: I18n('checkingPlayStatus'),
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${this.#steamWebApiKey}&steamids=${this.#steamId}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (result !== 'Success') {
debug('检查挂游戏时长状态请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200) {
debug('挂游戏时长状态正常', {
data: data
});
const playedIds = new Set(data.responseText?.match(/\d+/g));
const neededIds = new Set(ids.match(/\d+/g));
if (neededIds.intersection(playedIds).size > 0) {
logStatus.success();
return true;
}
logStatus.warning(I18n('noPlayStatus'));
return false;
}
debug('挂游戏时长状态异常', {
data: data
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('检查挂游戏时长状态时发生错误', {
error: error
});
throwError(error, 'SteamASF.checkPlayStatus');
return false;
}
}
async stopPlayGames() {
try {
debug('开始停止挂游戏时长');
const logStatus = echoLog({
text: I18n('stoppingPlayGames'),
before: '[ASF]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
...this.#asfOptions,
data: JSON.stringify({
Command: `!resume ${this.#botName}`
})
});
if (result !== 'Success') {
debug('停止挂游戏时长请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status === 200 && [ '已经恢复', '已恢复', '已經繼續', '已繼續', 'resumed', 'возобновлён' ].find((text => data.response?.Result?.includes(text)))) {
debug('成功停止挂游戏时长');
logStatus.success();
return true;
}
debug('停止挂游戏时长失败', {
result: data?.response?.Result,
message: data?.response?.Message
});
logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('停止挂游戏时长时发生错误', {
error: error
});
throwError(error, 'SteamASF.stopPlayGames');
return false;
}
}
async #unsupportted(name) {
try {
debug('尝试使用不支持的功能', {
name: name
});
echoLog({
before: '[ASF]'
}).warning(I18n('ASFNotSupportted', name));
return false;
} catch (error) {
debug('处理不支持的功能时发生错误', {
error: error,
name: name
});
throwError(error, 'SteamASF.unsupportted');
return false;
}
}
async toggleForum() {
return this.#unsupportted('toggleForum');
}
async toggleFavoriteWorkshop() {
return this.#unsupportted('toggleFavoriteWorkshop');
}
async voteUpWorkshop() {
return this.#unsupportted('voteUpWorkshop');
}
async likeAnnouncement() {
return this.#unsupportted('likeAnnouncement');
}
}
class SteamWeb {
#cache={
...{
group: {},
officialGroup: {},
forum: {},
workshop: {},
curator: {}
},
...GM_getValue('steamCache')
};
#auth={};
#storeInitialized=false;
#communityInitialized=false;
#area='CN';
#oldArea;
#areaStatus='end';
constructor() {
debug('初始化SteamWeb实例');
}
async init(type = 'all') {
try {
debug('开始初始化SteamWeb', {
type: type
});
const initStoreResult = await this.initStore();
debug('Steam商店初始化完成', {
initStoreResult: initStoreResult
});
if (type === 'store') {
return initStoreResult;
}
const initCommunityResult = await this.initCommunity(initStoreResult);
debug('Steam社区初始化完成', {
initCommunityResult: initCommunityResult
});
return initCommunityResult;
} catch (error) {
debug('SteamWeb初始化发生错误', {
error: error,
type: type
});
throwError(error, 'SteamWeb.init');
return false;
}
}
async initStore() {
try {
debug('开始初始化Steam商店');
if (this.#storeInitialized) {
return true;
}
let storeInitialized = await this.#updateStoreAuth();
if (!storeInitialized) {
storeInitialized = await this.#updateStoreAuthTab();
}
this.#storeInitialized = storeInitialized;
if (!this.#storeInitialized) {
echoLog({
before: '[Web]'
}).error(I18n('initFailed', 'Steam'));
return false;
}
echoLog({
before: '[Web]'
}).success(I18n('initSuccess', 'SteamStore'));
debug('Steam商店初始化完成');
return true;
} catch (error) {
debug('Steam商店初始化发生错误', {
error: error
});
throwError(error, 'SteamWeb.initStore');
return false;
}
}
async initCommunity(initStoreResult) {
try {
debug('开始初始化Steam社区');
if (this.#communityInitialized) {
return true;
}
let communityInitialized = await this.#updateCommunityAuth(initStoreResult);
if (!communityInitialized) {
communityInitialized = await this.#updateCommunityAuthTab();
GM_setValue('steamCommunityAuth', null);
}
this.#communityInitialized = communityInitialized;
if (!this.#communityInitialized) {
echoLog({
before: '[Web]'
}).error(I18n('initFailed', 'Steam'));
return false;
}
echoLog({
before: '[Web]'
}).success(I18n('initSuccess', 'SteamCommunity'));
debug('Steam社区初始化完成');
return true;
} catch (error) {
debug('Steam社区初始化发生错误', {
error: error
});
throwError(error, 'SteamWeb.initCommunity');
return false;
}
}
async #refreshToken(type = 'steamStore') {
try {
debug('开始刷新令牌', {
type: type
});
const host = {
steamStore: 'store.steampowered.com',
steamCommunity: 'steamcommunity.com'
};
const logStatus = echoLog({
text: I18n('refreshingToken', I18n(type)),
before: '[Web]'
});
debug('准备刷新令牌请求数据');
const formData = new FormData;
formData.append('redir', `https://${host[type]}/`);
debug('发送刷新令牌请求');
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://login.steampowered.com/jwt/ajaxrefresh',
method: 'POST',
responseType: 'json',
headers: {
Host: 'login.steampowered.com',
Origin: `https://${host[type]}`,
Referer: `https://${host[type]}/`
},
data: formData
});
debug('收到刷新令牌响应', {
result: result,
status: status,
statusText: statusText
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (!data?.response?.success) {
debug('响应不成功', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('开始设置新令牌');
if (!await this.#setToken(data.response, type)) {
debug('设置新令牌失败');
logStatus.error('Error');
return false;
}
debug('成功刷新令牌');
logStatus.success();
return true;
} catch (error) {
debug('刷新令牌时发生错误', {
error: error
});
throwError(error, 'SteamWeb.refreshToken');
return false;
}
}
async #setToken(param, type) {
try {
const host = {
steamStore: 'store.steampowered.com',
steamCommunity: 'steamcommunity.com'
};
debug('开始设置Steam令牌', {
type: type
});
const logStatus = echoLog({
text: I18n('settingToken', I18n(type)),
before: '[Web]'
});
debug('准备表单数据');
const formData = new FormData;
formData.append('steamID', param.steamID);
formData.append('nonce', param.nonce);
formData.append('redir', param.redir);
formData.append('auth', param.auth);
debug('表单数据准备完成', {
steamID: param.steamID,
nonce: param.nonce,
redir: param.redir
});
debug('发送设置令牌请求');
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://${host[type]}/login/settoken`,
method: 'POST',
headers: {
Accept: 'application/json, text/plain, */*',
Host: host[type],
Origin: `https://${host[type]}`
},
data: formData
});
debug('收到设置令牌响应', {
result: result,
status: status,
statusText: statusText
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('成功设置令牌');
logStatus.success();
return true;
} catch (error) {
debug('设置令牌时发生错误', {
error: error,
type: type
});
throwError(error, 'SteamWeb.setToken');
return false;
}
}
async #updateStoreAuth(retry = false) {
try {
debug('开始更新Steam商店身份验证');
const logStatus = echoLog({
text: I18n('updatingAuth', I18n('steamStore')),
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://store.steampowered.com/',
method: 'GET',
headers: {
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Upgrade-Insecure-Requests': '1'
},
redirect: 'manual'
});
debug('收到Steam商店身份验证响应', {
result: result,
statusText: statusText,
status: status
});
if (data?.status !== 200) {
if (![ 301, 302 ].includes(data?.status)) {
debug('Steam商店身份验证状态错误', {
status: data?.status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (!await this.#refreshToken('steamStore')) {
debug('Steam商店身份验证刷新失败');
logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true);
return false;
}
if (retry) {
debug('Steam商店身份验证重试失败');
logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true);
return false;
}
debug('Steam商店身份验证重试中');
logStatus.warning(I18n('retry'));
return this.#updateStoreAuth(true);
}
if (!data.responseText.includes('data-miniprofile=')) {
if (await this.#refreshToken('steamStore')) {
debug('Steam商店身份验证需要重试');
logStatus.warning(I18n('retry'));
if (retry) {
debug('Steam商店身份验证重试次数超限');
logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true);
return false;
}
return this.#updateStoreAuth(true);
}
debug('Steam商店身份验证失败:需要登录');
logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true);
return false;
}
const storeSessionID = data.responseText.match(/g_sessionID = "(.+?)";/)?.[1];
if (!storeSessionID) {
debug('Steam商店身份验证失败:获取sessionID失败');
logStatus.error('Error: Get "sessionID" failed');
return false;
}
this.#auth.storeSessionID = storeSessionID;
debug('Steam商店身份验证更新成功', {
storeSessionID: storeSessionID
});
logStatus.success();
return true;
} catch (error) {
debug('更新Steam商店身份验证时发生错误', {
error: error
});
throwError(error, 'SteamWeb.updateStoreAuth');
return false;
}
}
async #updateStoreAuthTab() {
try {
debug('开始通过新标签页更新Steam商店身份验证');
const logStatus = echoLog({
text: I18n('updatingAuth', I18n('steamStoreTab')),
before: '[Web]'
});
return await new Promise((resolve => {
GM_deleteValue('steamStoreAuth');
GM_setValue('ATv4_updateStoreAuth', true);
const newTab = GM_openInTab('https://store.steampowered.com/', {
active: true,
setParent: true
});
debug('打开Steam商店新标签页');
newTab.name = 'ATv4_updateStoreAuth';
const listenerId = GM_addValueChangeListener('steamStoreAuth', ((key, oldValue, newValue) => {
debug('监听到Steam商店身份验证值变化', {
oldValue: oldValue,
newValue: newValue
});
GM_removeValueChangeListener(listenerId);
GM_deleteValue('ATv4_updateStoreAuth');
newTab?.close();
window.focus();
if (newValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
this.#auth.storeSessionID = newValue.storeSessionID;
debug('Steam商店身份验证更新成功', {
storeSessionID: newValue.storeSessionID
});
logStatus.success();
resolve(true);
return;
}
debug('Steam商店身份验证更新失败');
logStatus.error('Failed');
resolve(false);
}));
newTab.onclose = () => {
debug('Steam商店新标签页已关闭');
GM_deleteValue('ATv4_updateStoreAuth');
};
}));
} catch (error) {
debug('通过新标签页更新Steam商店身份验证时发生错误', {
error: error
});
throwError(error, 'SteamWeb.updateStoreAuthTab');
return false;
}
}
async #updateCommunityAuthTab() {
try {
debug('开始通过新标签页更新Steam社区身份验证');
const logStatus = echoLog({
text: I18n('updatingAuth', I18n('steamCommunityTab')),
before: '[Web]'
});
return await new Promise((resolve => {
GM_deleteValue('steamCommunityAuth');
GM_setValue('ATv4_updateCommunityAuth', true);
const newTab = GM_openInTab('https://steamcommunity.com/my', {
active: true,
setParent: true
});
debug('打开Steam社区新标签页');
newTab.name = 'ATv4_updateCommunityAuth';
const listenerId = GM_addValueChangeListener('steamCommunityAuth', ((key, oldValue, newValue) => {
debug('监听到Steam社区身份验证值变化', {
oldValue: oldValue,
newValue: newValue
});
GM_removeValueChangeListener(listenerId);
GM_deleteValue('ATv4_updateCommunityAuth');
newTab?.close();
window.focus();
if (newValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
this.#auth.steam64Id = newValue.steam64Id;
this.#auth.communitySessionID = newValue.communitySessionID;
debug('Steam社区身份验证更新成功', {
steam64Id: newValue.steam64Id,
communitySessionID: newValue.communitySessionID
});
logStatus.success();
resolve(true);
return;
}
debug('Steam社区身份验证更新失败');
logStatus.error('Failed');
resolve(false);
}));
newTab.onclose = () => {
debug('Steam社区新标签页已关闭');
GM_deleteValue('ATv4_updateCommunityAuth');
};
}));
} catch (error) {
debug('通过新标签页更新Steam社区身份验证时发生错误', {
error: error
});
throwError(error, 'SteamWeb.updateCommunityAuthTab');
return false;
}
}
async #updateCommunityAuth(initStoreResult, retry = false) {
try {
debug('开始更新Steam社区身份验证');
const logStatus = echoLog({
text: I18n('gettingUserInfo'),
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://steamcommunity.com/my',
method: 'GET',
headers: {
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
Host: 'steamcommunity.com',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate'
},
redirect: 'follow'
});
debug('收到Steam社区身份验证响应', {
result: result,
statusText: statusText,
status: status
});
if (data?.status !== 200) {
debug('Steam社区身份验证状态错误', {
status: data?.status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data.finalUrl.includes('https://steamcommunity.com/login/home')) {
if (initStoreResult) {
if (await this.#refreshToken('steamCommunity')) {
debug('Steam社区身份验证需要重试');
logStatus.warning(I18n('retry'));
if (retry) {
debug('Steam社区身份验证重试次数超限');
logStatus.error(`Error:${I18n('needLoginSteamCommunity')}`, true);
return false;
}
return this.#updateCommunityAuth(initStoreResult, retry);
}
}
debug('Steam社区身份验证失败:需要登录');
logStatus.error(`Error:${I18n('needLoginSteamCommunity')}`, true);
return false;
}
const steam64Id = data.responseText.match(/g_steamID = "(.+?)";/)?.[1];
const communitySessionID = data.responseText.match(/g_sessionID = "(.+?)";/)?.[1];
if (!steam64Id || !communitySessionID) {
debug('Steam社区身份验证失败:获取身份信息失败');
logStatus.error('Error: Get "sessionID" failed');
return false;
}
this.#auth.steam64Id = steam64Id;
this.#auth.communitySessionID = communitySessionID;
debug('Steam社区身份验证更新成功', {
steam64Id: steam64Id,
communitySessionID: communitySessionID
});
logStatus.success();
return true;
} catch (error) {
debug('更新Steam社区身份验证时发生错误', {
error: error
});
throwError(error, 'SteamWeb.updateCommunityAuth');
return false;
}
}
async #getAreaInfo() {
try {
debug('开始获取Steam地区信息');
const logStatus = echoLog({
text: I18n('gettingAreaInfo'),
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://store.steampowered.com/cart/',
method: 'GET'
});
debug('获取地区信息请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success' || data?.status !== 200) {
debug('获取地区信息失败', {
result: result,
status: data?.status,
statusText: data?.statusText
});
logStatus.error(result === 'Success' ? `Error:${data?.statusText}(${data?.status})` : `${result}:${statusText}(${status})`);
return {};
}
const cartConfigRaw = data.responseText.match(/data-cart_config="(.*?)"/)?.[1];
debug('cartConfigRaw提取结果', {
cartConfigRaw: cartConfigRaw
});
const temp = document.createElement('div');
temp.innerHTML = cartConfigRaw || '{}';
const cartConfigStr = temp.textContent || temp.innerText;
let cartConfig;
try {
cartConfig = JSON.parse(cartConfigStr);
debug('cartConfig解析成功', {
cartConfig: cartConfig
});
} catch (error) {
debug('cartConfig解析失败', {
error: error
});
logStatus.error('Error: get country info filed');
console.error(error);
return {};
}
if (!cartConfig.rgUserCountryOptions) {
debug('未找到可更换地区');
logStatus.warning('Warning: Area cannot be changed');
return {};
}
const userInfoRaw = data.responseText.match(/data-userinfo="(.*?)"/)?.[1];
debug('userInfoRaw提取结果', {
userInfoRaw: userInfoRaw
});
const temp1 = document.createElement('div');
temp1.innerHTML = userInfoRaw || '{}';
const userInfoStr = temp1.textContent || temp1.innerText;
let userInfo;
try {
userInfo = JSON.parse(userInfoStr);
debug('userInfo解析成功', {
userInfo: userInfo
});
} catch (error) {
debug('userInfo解析失败', {
error: error
});
logStatus.error('Error: get country info filed');
console.error(error);
return {};
}
const currentArea = userInfo.country_code;
const areas = Object.keys(cartConfig.rgUserCountryOptions).filter((area => area !== 'help'));
debug('地区信息提取', {
currentArea: currentArea,
areas: areas
});
if (!currentArea || areas.length === 0) {
debug('未获取到当前地区或可更换地区为空', {
currentArea: currentArea,
areas: areas
});
logStatus.error('Error: get country info filed');
return {};
}
this.#area = currentArea;
debug('获取地区信息成功', {
currentArea: currentArea,
areas: areas
});
logStatus.success();
return {
currentArea: currentArea,
areas: areas
};
} catch (error) {
debug('获取地区信息时发生异常', {
error: error
});
throwError(error, 'SteamWeb.getAreaInfo');
return {};
}
}
async #changeArea(area) {
try {
debug('开始更换Steam地区', {
area: area
});
if (this.#areaStatus === 'waiting') {
debug('当前地区状态为waiting,等待状态改变');
await new Promise((resolve => {
const checker = setInterval((() => {
if (this.#areaStatus !== 'waiting') {
clearInterval(checker);
resolve(true);
}
}));
}));
}
if (this.#area === area || !area && this.#area !== 'CN') {
debug('无需更换地区', {
currentArea: this.#area,
targetArea: area
});
return true;
}
this.#areaStatus = 'waiting';
let aimedArea = area;
if (!aimedArea) {
debug('未指定目标地区,自动获取可用地区');
const {currentArea: currentArea, areas: areas} = await this.#getAreaInfo();
debug('获取到地区信息', {
currentArea: currentArea,
areas: areas
});
if (!currentArea || !areas) {
debug('获取地区信息失败', {
currentArea: currentArea,
areas: areas
});
this.#areaStatus = 'error';
return false;
}
if (currentArea !== 'CN') {
debug('当前地区不是CN,无需更换', {
currentArea: currentArea
});
this.#areaStatus = 'skip';
echoLog({
text: I18n('notNeededChangeArea'),
before: '[Web]'
});
return 'skip';
}
const anotherArea = areas.filter((area => area && area !== 'CN'));
debug('可更换的其他地区', {
anotherArea: anotherArea
});
if (!anotherArea || anotherArea.length === 0) {
debug('没有可用的其他地区');
this.#areaStatus = 'noAnotherArea';
echoLog({
text: I18n('noAnotherArea'),
before: '[Web]'
});
return false;
}
[aimedArea] = anotherArea;
debug('选定目标地区', {
aimedArea: aimedArea
});
}
const logStatus = echoLog({
text: I18n('changingArea', aimedArea),
before: '[Web]'
});
debug('发送更换地区请求', {
aimedArea: aimedArea
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://store.steampowered.com/country/setcountry',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
cc: aimedArea,
sessionid: this.#auth.storeSessionID
})
});
debug('更换地区请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success' || data?.status !== 200 || data.responseText !== 'true') {
debug('更换地区失败', {
result: result,
status: data?.status,
statusText: data?.statusText,
responseText: data?.responseText
});
this.#areaStatus = 'error';
logStatus.error(result === 'Success' ? `Error:${data?.statusText}(${data?.status})` : `${result}:${statusText}(${status})`);
return 'CN';
}
const {currentArea: currentArea} = await this.#getAreaInfo();
debug('更换后获取到的当前地区', {
currentArea: currentArea
});
if (currentArea) {
this.#area = currentArea;
if (!this.#oldArea) {
this.#oldArea = currentArea;
}
}
if (currentArea !== aimedArea) {
debug('更换后当前地区与目标地区不符', {
currentArea: currentArea,
aimedArea: aimedArea
});
this.#areaStatus = 'error';
logStatus.error('Error: change country filed');
return 'CN';
}
this.#areaStatus = 'success';
debug('更换地区成功', {
currentArea: currentArea
});
logStatus.success();
return currentArea;
} catch (error) {
debug('更换地区时发生异常', {
error: error
});
this.#areaStatus = 'error';
throwError(error, 'SteamWeb.changeArea');
return false;
}
}
async joinGroup(groupName) {
try {
debug('开始加入Steam组', {
groupName: groupName
});
const logStatus = echoLog({
type: 'joiningSteamGroup',
text: groupName,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/groups/${groupName}`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
action: 'join',
sessionID: this.#auth.communitySessionID
})
});
if (result !== 'Success') {
debug('加入Steam组请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || data.responseText.includes('grouppage_join_area')) {
debug('加入Steam组失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('成功加入Steam组', {
groupName: groupName
});
logStatus.success();
return true;
} catch (error) {
debug('加入Steam组时发生错误', {
error: error,
groupName: groupName
});
throwError(error, 'SteamWeb.joinGroup');
return false;
}
}
async leaveGroup(groupName) {
try {
debug('开始退出Steam组', {
groupName: groupName
});
const groupId = await this.#getGroupId(groupName);
if (!groupId) {
return false;
}
const logStatus = echoLog({
type: 'leavingSteamGroup',
text: groupName,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/profiles/${this.#auth.steam64Id}/home_process`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
sessionID: this.#auth.communitySessionID,
action: 'leaveGroup',
groupId: groupId
})
});
if (result !== 'Success') {
debug('退出Steam组请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || !data.finalUrl.includes('groups')) {
debug('退出Steam组失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const hasGroupLink = $(data.responseText.replace(/<img.*?>/g, '').toLowerCase()).find(`a[href='https://steamcommunity.com/groups/${groupName.toLowerCase()}']`).length > 0;
if (hasGroupLink) {
debug('Error: Group link still exists');
return false;
}
debug('成功退出Steam组', {
groupName: groupName
});
logStatus.success();
return true;
} catch (error) {
debug('退出Steam组时发生错误', {
error: error,
groupName: groupName
});
throwError(error, 'SteamWeb.leaveGroup');
return false;
}
}
async #getGroupId(groupName) {
try {
debug('开始获取Steam组ID', {
groupName: groupName
});
const logStatus = echoLog({
type: 'gettingSteamGroupId',
text: groupName,
before: '[Web]'
});
const cachedGroupId = this.#cache.group[groupName];
if (cachedGroupId) {
debug('从缓存中获取到组ID', {
groupName: groupName,
cachedGroupId: cachedGroupId
});
logStatus.success();
return cachedGroupId;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/groups/${groupName}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
debug('获取组ID请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('获取组ID请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取组ID响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const matchedGroupId = data.responseText.match(/OpenGroupChat\( '([0-9]+)'/)?.[1];
debug('正则提取组ID结果', {
matchedGroupId: matchedGroupId
});
if (!matchedGroupId) {
debug('未能提取到组ID', {
groupName: groupName
});
logStatus.error(`Error:${data.statusText}(${data.status})`);
return false;
}
this.#setCache('group', groupName, matchedGroupId);
debug('组ID已缓存', {
groupName: groupName,
matchedGroupId: matchedGroupId
});
logStatus.success();
return matchedGroupId;
} catch (error) {
debug('获取组ID时发生异常', {
error: error,
groupName: groupName
});
throwError(error, 'SteamWeb.getGroupID');
return false;
}
}
async joinOfficialGroup(gameId) {
try {
debug('开始加入Steam官方组', {
gameId: gameId
});
const logStatus = echoLog({
type: 'joiningSteamOfficialGroup',
text: gameId,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/games/${gameId}?action=join&sessionID=${this.#auth.communitySessionID}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
if (result !== 'Success') {
debug('加入Steam官方组请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || data.responseText.includes('id="publicGroupJoin"')) {
debug('加入Steam官方组失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const groupId = data.responseText.match(/steam:\/\/friends\/joinchat\/([0-9]+)/)?.[1];
if (groupId) {
this.#setCache('officialGroup', gameId, groupId);
}
debug('成功加入Steam官方组', {
gameId: gameId
});
logStatus.success();
return true;
} catch (error) {
debug('加入Steam官方组时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'SteamWeb.joinOfficialGroup');
return false;
}
}
async leaveOfficialGroup(gameId) {
try {
debug('开始退出Steam官方组', {
gameId: gameId
});
const groupId = await this.#getOfficialGroupId(gameId);
if (!groupId) {
return false;
}
const logStatus = echoLog({
type: 'leavingSteamOfficialGroup',
text: gameId,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/profiles/${this.#auth.steam64Id}/home_process`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
sessionID: this.#auth.communitySessionID,
action: 'leaveGroup',
groupId: groupId
})
});
if (result !== 'Success') {
debug('退出Steam官方组请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('退出Steam官方组失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({
url: `https://steamcommunity.com/games/${gameId}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
if (resultR !== 'Success') {
debug('退出Steam官方组时发生错误', {
error: resultR,
status: statusR,
statusText: statusTextR
});
logStatus.error(`${resultR}:${statusTextR}(${statusR})`);
return false;
}
if (dataR?.status !== 200 || !dataR.responseText.includes('id="publicGroupJoin"')) {
debug('退出Steam官方组失败', {
status: dataR?.status
});
logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`);
return false;
}
debug('成功退出Steam官方组', {
gameId: gameId
});
logStatus.success();
return true;
} catch (error) {
debug('退出Steam官方组时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'SteamWeb.leaveOfficialGroup');
return false;
}
}
async #getOfficialGroupId(gameId) {
try {
debug('开始获取Steam官方群组ID', {
gameId: gameId
});
const logStatus = echoLog({
type: 'gettingSteamOfficialGroupId',
text: gameId,
before: '[Web]'
});
const cachedGroupId = this.#cache.officialGroup[gameId];
if (cachedGroupId) {
debug('从缓存中获取到官方群组ID', {
gameId: gameId,
cachedGroupId: cachedGroupId
});
logStatus.success();
return cachedGroupId;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/games/${gameId}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
debug('获取官方群组ID请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('获取官方群组ID请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取官方群组ID响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const matchedGroupId = data.responseText.match(/steam:\/\/friends\/joinchat\/([0-9]+)/)?.[1];
debug('正则提取官方群组ID结果', {
matchedGroupId: matchedGroupId
});
if (!matchedGroupId) {
debug('未能提取到官方群组ID', {
gameId: gameId
});
logStatus.error(`Error:${data.statusText}(${data.status})`);
return false;
}
this.#setCache('officialGroup', gameId, matchedGroupId);
debug('官方群组ID已缓存', {
gameId: gameId,
matchedGroupId: matchedGroupId
});
logStatus.success();
return matchedGroupId;
} catch (error) {
debug('获取官方群组ID时发生异常', {
error: error,
gameId: gameId
});
throwError(error, 'SteamWeb.getGroupID');
return false;
}
}
async addToWishlist(gameId) {
try {
debug('开始添加游戏到愿望单', {
gameId: gameId
});
const logStatus = echoLog({
type: 'addingToWishlist',
text: gameId,
before: '[Web]'
});
const {result: result, data: data} = await httpRequest({
url: 'https://store.steampowered.com/api/addtowishlist',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
sessionid: this.#auth.storeSessionID,
appid: gameId
}),
dataType: 'json'
});
if (result === 'Success' && data?.status === 200 && data.response?.success === true) {
debug('成功添加游戏到愿望单', {
gameId: gameId
});
logStatus.success();
return true;
}
const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({
url: `https://store.steampowered.com/app/${gameId}`,
method: 'GET'
});
if (resultR !== 'Success') {
debug('添加游戏到愿望单请求失败', {
result: resultR,
status: statusR,
statusText: statusTextR
});
logStatus.error(`${resultR}:${statusTextR}(${statusR})`);
return false;
}
if (dataR?.status !== 200) {
debug('添加游戏到愿望单失败', {
status: dataR?.status
});
logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`);
return false;
}
if (this.#area === 'CN' && dataR.responseText.includes('id="error_box"')) {
debug('changeAreaNotice');
if (!await this.#changeArea()) {
return false;
}
return await this.addToWishlist(gameId);
}
if (dataR.responseText.includes('class="queue_actions_ctn"') && dataR.responseText.includes('class="already_in_library"')) {
debug('成功添加游戏到愿望单', {
gameId: gameId
});
logStatus.success();
return true;
}
if (dataR.responseText.includes('class="queue_actions_ctn"') && dataR.responseText.includes('id="add_to_wishlist_area_success" style="display: none;') || !dataR.responseText.includes('class="queue_actions_ctn"')) {
debug('添加游戏到愿望单失败', {
status: dataR.statusText
});
logStatus.error(`Error:${dataR.statusText}(${dataR.status})`);
return false;
}
debug('成功添加游戏到愿望单', {
gameId: gameId
});
logStatus.success();
return true;
} catch (error) {
debug('添加游戏到愿望单时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'SteamWeb.addToWishlist');
return false;
}
}
async removeFromWishlist(gameId) {
try {
debug('开始从愿望单移除游戏', {
gameId: gameId
});
const logStatus = echoLog({
type: 'removingFromWishlist',
text: gameId,
before: '[Web]'
});
const {result: result, data: data} = await httpRequest({
url: 'https://store.steampowered.com/api/removefromwishlist',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
sessionid: this.#auth.storeSessionID,
appid: gameId
}),
dataType: 'json'
});
if (result === 'Success' && data?.status === 200 && data.response?.success === true) {
debug('成功从愿望单移除游戏', {
gameId: gameId
});
logStatus.success();
return true;
}
const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({
url: `https://store.steampowered.com/app/${gameId}`,
method: 'GET'
});
if (resultR !== 'Success') {
debug('从愿望单移除游戏请求失败', {
result: resultR,
status: statusR,
statusText: statusTextR
});
logStatus.error(`${resultR}:${statusTextR}(${statusR})`);
return false;
}
if (dataR?.status !== 200) {
debug('从愿望单移除游戏失败', {
status: dataR?.status
});
logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`);
return false;
}
if (this.#area === 'CN' && dataR.responseText.includes('id="error_box"')) {
debug('changeAreaNotice');
const result = await this.#changeArea();
if (!result || result === 'CN' || result === 'skip') {
return false;
}
return await this.removeFromWishlist(gameId);
}
if (dataR.responseText.includes('class="queue_actions_ctn"') && (dataR.responseText.includes('ds_owned_flag ds_flag') || dataR.responseText.includes('add_to_wishlist_area'))) {
debug('成功从愿望单移除游戏', {
gameId: gameId
});
logStatus.success();
return true;
}
debug('从愿望单移除游戏请求失败', {
result: resultR,
status: statusR,
statusText: statusTextR
});
logStatus.error(`Error:${dataR.statusText}(${dataR.status})`);
return false;
} catch (error) {
debug('从愿望单移除游戏时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'SteamWeb.removeFromWishlist');
return false;
}
}
async toggleFollowGame(gameId, doTask) {
try {
debug('开始处理游戏关注状态', {
gameId: gameId,
doTask: doTask
});
const logStatus = echoLog({
type: `${doTask ? '' : 'un'}followingGame`,
text: gameId,
before: '[Web]'
});
const requestData = {
sessionid: this.#auth.storeSessionID,
appid: gameId
};
if (!doTask) {
requestData.unfollow = '1';
}
const {result: result, data: data} = await httpRequest({
url: 'https://store.steampowered.com/explore/followgame/',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param(requestData)
});
if (result === 'Success' && data?.status === 200 && data.responseText === 'true') {
debug('成功处理游戏关注状态', {
gameId: gameId,
doTask: doTask
});
logStatus.success();
return true;
}
const followed = await this.#isFollowedGame(gameId);
if (this.#area === 'CN' && followed === 'areaLocked') {
debug('changeAreaNotice');
if (!await this.#changeArea()) {
return false;
}
return await this.toggleFollowGame(gameId, doTask);
}
if (doTask === followed) {
debug('成功处理游戏关注状态', {
gameId: gameId,
doTask: doTask
});
logStatus.success();
return true;
}
debug('处理游戏关注状态请求失败', {
result: result
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
} catch (error) {
debug('处理游戏关注状态时发生错误', {
error: error,
gameId: gameId,
doTask: doTask
});
throwError(error, 'SteamWeb.toggleFollowGame');
return false;
}
}
async #isFollowedGame(gameId) {
try {
debug('开始判断Steam游戏是否已关注', {
gameId: gameId
});
const {result: result, data: data} = await httpRequest({
url: `https://store.steampowered.com/app/${gameId}`,
method: 'GET'
});
debug('获取游戏页面请求结果', {
result: result,
status: data?.status
});
if (result !== 'Success') {
debug('请求失败', {
result: result
});
return false;
}
if (data?.status !== 200) {
debug('响应状态错误', {
status: data?.status
});
return false;
}
if (this.#area === 'CN' && data.responseText.includes('id="error_box"')) {
debug('地区锁定,返回areaLocked', {
area: this.#area
});
return 'areaLocked';
}
const isFollowed = $(data.responseText.replace(/<img.*?>/g, '')).find('.queue_control_button.queue_btn_follow>.btnv6_blue_hoverfade.btn_medium.queue_btn_active').css('display') !== 'none';
debug('关注状态判断结果', {
isFollowed: isFollowed
});
return isFollowed;
} catch (error) {
debug('判断游戏关注状态时发生异常', {
error: error,
gameId: gameId
});
throwError(error, 'SteamWeb.isFollowedGame');
return false;
}
}
async toggleForum(gameId, doTask = true) {
try {
debug('开始处理论坛订阅状态', {
gameId: gameId,
doTask: doTask
});
const forumId = await this.#getForumId(gameId);
if (!forumId) {
return false;
}
const logStatus = echoLog({
type: `${doTask ? '' : 'un'}subscribingForum`,
text: gameId,
before: '[Web]'
});
const [id, feature] = forumId.split('_');
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/forum/${id}/General/${doTask ? '' : 'un'}subscribe/${feature || '0'}/`,
method: 'POST',
responseType: 'json',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
sessionid: this.#auth.communitySessionID
})
});
if (result !== 'Success') {
debug('处理论坛订阅状态请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return true;
}
if (data?.status !== 200 || data.response?.success !== 1 && data.response?.success !== 29) {
debug('处理论坛订阅状态失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return true;
}
debug('成功处理论坛订阅状态', {
gameId: gameId,
doTask: doTask
});
logStatus.success();
return true;
} catch (error) {
debug('处理论坛订阅状态时发生错误', {
error: error,
gameId: gameId,
doTask: doTask
});
throwError(error, 'SteamWeb.toggleForum');
return false;
}
}
async #getForumId(gameId) {
try {
debug('开始获取Steam论坛ID', {
gameId: gameId
});
const logStatus = echoLog({
type: 'gettingForumId',
text: gameId,
before: '[Web]'
});
const cachedForumId = this.#cache.forum[gameId];
if (cachedForumId) {
debug('从缓存中获取到论坛ID', {
gameId: gameId,
cachedForumId: cachedForumId
});
logStatus.success();
return cachedForumId;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/app/${gameId}/discussions/`,
method: 'GET'
});
debug('获取论坛ID请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('获取论坛ID请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取论坛ID响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const matchedForumId = data.responseText?.match(/General_([\d]+(_[\d]+)?)/)?.[1];
debug('正则提取论坛ID结果', {
matchedForumId: matchedForumId
});
if (!matchedForumId) {
debug('未能提取到论坛ID', {
gameId: gameId
});
logStatus.error(`Error:${data.statusText}(${data.status})`);
return false;
}
this.#setCache('forum', gameId, matchedForumId);
debug('论坛ID已缓存', {
gameId: gameId,
matchedForumId: matchedForumId
});
logStatus.success();
return matchedForumId;
} catch (error) {
debug('获取论坛ID时发生异常', {
error: error,
gameId: gameId
});
throwError(error, 'SteamWeb.getForumId');
return false;
}
}
async toggleFavoriteWorkshop(id, doTask = true) {
try {
debug('开始处理创意工坊收藏状态', {
id: id,
doTask: doTask
});
const appid = await this.#getWorkshopAppId(id);
if (!appid) {
return false;
}
const logStatus = echoLog({
type: doTask ? 'favoritingWorkshop' : 'unfavoritingWorkshop',
text: id,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/sharedfiles/${doTask ? '' : 'un'}favorite`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
id: id,
appid: appid,
sessionid: this.#auth.communitySessionID
})
});
if (result !== 'Success') {
debug('处理创意工坊收藏状态请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || data.responseText) {
debug('处理创意工坊收藏状态失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('成功处理创意工坊收藏状态', {
id: id,
doTask: doTask
});
logStatus.success();
return true;
} catch (error) {
debug('处理创意工坊收藏状态时发生错误', {
error: error,
id: id,
doTask: doTask
});
throwError(error, 'SteamWeb.toggleFavoriteWorkshop');
return false;
}
}
async #getWorkshopAppId(id) {
try {
debug('开始获取Steam创意工坊AppId', {
id: id
});
const logStatus = echoLog({
type: 'gettingWorkshopAppId',
text: id,
before: '[Web]'
});
const cachedAppId = this.#cache.workshop[id];
if (cachedAppId) {
debug('从缓存中获取到AppId', {
id: id,
cachedAppId: cachedAppId
});
logStatus.success();
return cachedAppId;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://steamcommunity.com/sharedfiles/filedetails/?id=${id}`,
method: 'GET'
});
debug('获取创意工坊AppId请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('获取创意工坊AppId请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取创意工坊AppId响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const matchedAppId = data.responseText.match(/<input type="hidden" name="appid" value="([\d]+?)" \/>/)?.[1];
debug('正则提取AppId结果', {
matchedAppId: matchedAppId
});
if (!matchedAppId) {
debug('未能提取到AppId', {
id: id
});
logStatus.error('Error: getWorkshopAppId failed');
return false;
}
debug('AppId已缓存', {
id: id,
matchedAppId: matchedAppId
});
return matchedAppId;
} catch (error) {
debug('获取创意工坊AppId时发生异常', {
error: error,
id: id
});
throwError(error, 'SteamWeb.getWorkshopAppId');
return false;
}
}
async voteUpWorkshop(id) {
try {
debug('开始点赞创意工坊物品', {
id: id
});
const logStatus = echoLog({
type: 'votingUpWorkshop',
text: id,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://steamcommunity.com/sharedfiles/voteup',
method: 'POST',
responseType: 'json',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
id: id,
sessionid: this.#auth.communitySessionID
})
});
if (result !== 'Success') {
debug('点赞创意工坊物品请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return true;
}
if (data?.status !== 200 || data.response?.success !== 1) {
debug('点赞创意工坊物品失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return true;
}
debug('成功点赞创意工坊物品', {
id: id
});
logStatus.success();
return true;
} catch (error) {
debug('点赞创意工坊物品时发生错误', {
error: error,
id: id
});
throwError(error, 'SteamWeb.voteUpWorkshop');
return false;
}
}
async toggleCurator(curatorId, doTask = true) {
try {
debug('开始处理鉴赏家关注状态', {
curatorId: curatorId,
doTask: doTask
});
const logStatus = echoLog({
type: doTask ? 'followingCurator' : 'unfollowingCurator',
text: curatorId,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://store.steampowered.com/curators/ajaxfollow',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $.param({
clanid: curatorId,
sessionid: this.#auth.storeSessionID,
follow: doTask
}),
dataType: 'json'
});
if (result !== 'Success') {
debug('处理鉴赏家关注状态请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.response?.success?.success === 25) {
debug('处理鉴赏家关注状态失败', {
status: data?.status,
success: data?.response?.success,
message: data?.response?.msg
});
logStatus.error(I18n('curatorLimitNotice'));
return false;
}
if (data?.status !== 200 || data.response?.success?.success !== 1) {
debug('处理鉴赏家关注状态失败', {
status: data?.status,
success: data?.response?.success
});
logStatus.error(`Error:${data?.statusText}(${data?.response?.success}` || `${data?.status})`);
return false;
}
debug('成功处理鉴赏家关注状态', {
curatorId: curatorId,
doTask: doTask
});
logStatus.success();
return true;
} catch (error) {
debug('处理鉴赏家关注状态时发生错误', {
error: error,
curatorId: curatorId,
doTask: doTask
});
throwError(error, 'SteamWeb.toggleCurator');
return false;
}
}
async #getAnnouncementParams(appId, viewId) {
try {
debug('开始获取Steam公告参数', {
appId: appId,
viewId: viewId
});
const logStatus = echoLog({
type: 'gettingAnnouncementParams',
text: appId,
id: viewId,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/events/ajaxgetpartnerevent?appid=${appId}&announcement_gid=${viewId}&lang_list=6_0&last_modified_time=0&origin=https:%2F%2Fstore.steampowered.com&for_edit=false`,
method: 'GET',
responseType: 'json',
headers: {
Host: 'store.steampowered.com',
Referer: `https://store.steampowered.com/news/app/${appId}/view/${viewId}`
}
});
debug('获取公告参数请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('获取公告参数请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return {};
}
if (data?.status !== 200 || data?.response?.success !== 1) {
debug('获取公告参数响应状态错误', {
status: data?.status,
statusText: data?.statusText,
response: data?.response
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return {};
}
const {clanid: clanid, gid: gid} = data.response.event?.announcement_body || {};
debug('公告参数提取', {
clanid: clanid,
gid: gid
});
if (!clanid) {
debug('未能提取到clanid', {
appId: appId,
viewId: viewId
});
logStatus.error(`Error:${data.statusText}(${data.status})`);
return {};
}
logStatus.success();
debug('获取公告参数成功', {
clanId: clanid,
gid: gid
});
return {
clanId: clanid,
gid: gid
};
} catch (error) {
debug('获取公告参数时发生异常', {
error: error,
appId: appId,
viewId: viewId
});
throwError(error, 'SteamWeb.likeAnnouncement');
return {};
}
}
async likeAnnouncement(id) {
try {
debug('开始点赞公告', {
id: id
});
const [appId, viewId] = id.split('/');
if (!(appId && viewId)) {
echoLog({
before: '[Web]'
}).error(`${I18n('missParams')}(id)`);
return false;
}
const {clanId: clanId, gid: gid} = await this.#getAnnouncementParams(appId, viewId);
if (!clanId) {
return false;
}
const logStatus = echoLog({
type: 'likingAnnouncement',
text: appId,
id: viewId,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/updated/ajaxrateupdate/${gid || viewId}`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Host: 'store.steampowered.com',
Origin: 'https://store.steampowered.com',
Referer: `https://store.steampowered.com/news/app/${appId}/view/${viewId}`
},
data: $.param({
sessionid: this.#auth.storeSessionID,
voteup: 1,
clanid: clanId,
ajax: 1
}),
dataType: 'json'
});
if (result !== 'Success') {
debug('点赞公告请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || data.response.success !== 1) {
debug('点赞公告失败', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('成功点赞公告', {
id: id
});
logStatus.success();
return true;
} catch (error) {
debug('点赞公告时发生错误', {
error: error,
id: id
});
throwError(error, 'SteamWeb.likeAnnouncement');
return false;
}
}
async #appid2subid(id) {
try {
debug('开始将AppId转换为SubId', {
id: id
});
const logStatus = echoLog({
type: 'gettingSubid',
text: id,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/app/${id}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
debug('获取App页面请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('获取App页面请求失败', {
result: result
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取App页面响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
if (data.responseText.includes('ds_owned_flag ds_flag') || data.responseText.includes('class="already_in_library"')) {
debug('App已拥有', {
id: id
});
logStatus.success(I18n('owned'));
return true;
}
if (this.#area === 'CN' && data.responseText.includes('id="error_box"')) {
debug('地区锁定,尝试更换地区', {
area: this.#area
});
logStatus.warning(I18n('changeAreaNotice'));
const result = await this.#changeArea();
if (!result || result === 'CN' || result === 'skip') {
debug('更换地区失败或未更换', {
result: result
});
return false;
}
return await this.#appid2subid(id);
}
let subid = data.responseText.match(/name="subid" value="([\d]+?)"/)?.[1];
debug('正则提取subid结果1', {
subid: subid
});
if (subid) {
logStatus.success();
return subid;
}
subid = data.responseText.match(/AddFreeLicense\(\s*(\d+)/)?.[1];
debug('正则提取subid结果2', {
subid: subid
});
if (subid) {
logStatus.success();
return subid;
}
debug('未能提取到subid', {
id: id
});
logStatus.error(`Error:${I18n('noSubid')}`);
return false;
} catch (error) {
debug('AppId转SubId时发生异常', {
error: error,
id: id
});
throwError(error, 'SteamWeb.appid2subid');
return false;
}
}
async #getLicenses() {
try {
debug('开始获取Steam用户许可证信息');
const logStatus = echoLog({
text: I18n('gettingLicenses'),
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/dynamicstore/userdata/?t=${(new Date).getTime()}`,
method: 'GET',
responseType: 'json'
});
debug('获取许可证请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('获取许可证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取许可证响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('获取到的许可证列表', {
licenses: data.response?.rgOwnedPackages
});
logStatus.remove();
return data.response?.rgOwnedPackages;
} catch (error) {
debug('获取许可证时发生异常', {
error: error
});
throwError(error, 'SteamWeb.getLicenses');
return false;
}
}
async addLicense(id) {
try {
debug('开始添加许可证', {
id: id
});
const [type, ids] = id.split('-');
debug('解析许可证ID', {
type: type,
ids: ids
});
if (type !== 'appid' && type !== 'subid') {
debug('无效的许可证类型', {
type: type
});
return false;
}
if (type === 'appid') {
debug('处理appid类型许可证', {
ids: ids
});
const subid = await this.#appid2subid(ids);
debug('appid转换为subid结果', {
appid: ids,
subid: subid
});
if (!subid) {
debug('appid转换失败', {
appid: ids
});
return false;
}
if (subid === true) {
debug('appid已拥有', {
appid: ids
});
return true;
}
const logStatus = echoLog({
type: 'addingFreeLicense',
text: ids,
before: '[Web]'
});
debug('开始添加免费许可证', {
subid: subid
});
if (!await this.#addFreeLicense(subid, logStatus)) {
debug('添加免费许可证失败', {
subid: subid
});
return false;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/app/${ids}`,
method: 'GET'
});
debug('验证许可证添加状态', {
result: result,
status: status,
statusText: statusText
});
if (result !== 'Success') {
debug('验证请求失败', {
result: result,
status: status,
statusText: statusText
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('验证响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
if (!data.responseText.includes('ds_owned_flag ds_flag') && !data.responseText.includes('class="already_in_library"')) {
debug('未找到游戏拥有标记', {
status: data.status,
statusText: data.statusText
});
logStatus.error(`Error:${data.statusText}(${data.status})`);
return false;
}
debug('appid许可证添加成功', {
appid: ids
});
logStatus.success();
return true;
}
if (this.#area === 'CN') {
debug('当前区域为CN,尝试更改区域', {
currentArea: this.#area
});
echoLog({
before: '[Web]'
}).success(I18n('tryChangeAreaNotice'));
await this.#changeArea();
}
const logStatusArr = {};
const idsArr = ids.split(',');
debug('处理subid类型许可证', {
idsArr: idsArr
});
for (const subid of idsArr) {
debug('开始处理单个subid', {
subid: subid
});
const logStatus = echoLog({
type: 'addingFreeLicense',
text: subid,
before: '[Web]'
});
if (!await this.#addFreeLicense(subid, logStatus)) {
debug('添加subid许可证失败', {
subid: subid
});
return false;
}
logStatusArr[subid] = logStatus;
}
const licenses = await this.#getLicenses();
debug('获取许可证列表', {
licenses: licenses
});
if (!licenses) {
debug('获取许可证列表失败');
return false;
}
for (const subid of idsArr) {
const hasLicense = licenses.includes(parseInt(subid, 10));
debug('验证许可证添加状态', {
subid: subid,
hasLicense: hasLicense
});
if (hasLicense) {
logStatusArr[subid].success();
} else {
logStatusArr[subid].error();
}
}
debug('所有subid许可证处理完成', {
idsArr: idsArr
});
return true;
} catch (error) {
debug('添加许可证过程发生错误', {
error: error,
id: id
});
throwError(error, 'SteamWeb.addLicense');
return false;
}
}
async #addFreeLicense(id, logStatusPre) {
try {
debug('开始添加免费Steam游戏许可证', {
id: id
});
const logStatus = logStatusPre || echoLog({
type: 'addingFreeLicenseSubid',
text: id,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/freelicense/addfreelicense/${id}`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Host: 'store.steampowered.com',
Origin: 'https://store.steampowered.com',
Referer: 'https://store.steampowered.com/account/licenses/'
},
data: $.param({
ajax: true,
sessionid: this.#auth.storeSessionID
}),
dataType: 'json'
});
debug('添加免费许可证请求结果', {
result: result,
statusText: statusText,
status: status
});
if (result !== 'Success') {
debug('添加免费许可证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('添加免费许可证响应状态错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
if (this.#area === 'CN' && data.responseText.includes('id="error_box"')) {
debug('地区锁定,尝试更换地区', {
area: this.#area
});
logStatus.warning(I18n('changeAreaNotice'));
const result = await this.#changeArea();
if (!result || [ 'CN', 'skip' ].includes(result)) {
debug('更换地区失败或未更换', {
result: result
});
return false;
}
return await this.#addFreeLicense(id);
}
debug('成功添加免费许可证', {
id: id
});
logStatus.success();
return true;
} catch (error) {
debug('添加免费许可证时发生异常', {
error: error,
id: id
});
throwError(error, 'SteamWeb.addFreeLicense');
return false;
}
}
async requestPlayTestAccess(id) {
debug('开始请求游戏试玩权限', {
id: id
});
try {
debug('开始请求游戏试玩权限', {
id: id
});
const logStatus = echoLog({
type: 'requestingPlayTestAccess',
text: id,
before: '[Web]'
});
debug('准备发送试玩权限请求', {
id: id
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/ajaxrequestplaytestaccess/${id}`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Host: 'store.steampowered.com',
Origin: 'https://store.steampowered.com',
Referer: `https://store.steampowered.com/app/${id}`
},
data: $.param({
sessionid: this.#auth.storeSessionID
}),
dataType: 'json'
});
debug('收到试玩权限请求响应', {
result: result,
status: status,
statusText: statusText,
responseData: data
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
status: status,
statusText: statusText
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || data?.response?.success !== 1) {
debug('响应状态错误', {
status: data?.status,
statusText: data?.statusText,
success: data?.response?.success
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('成功请求游戏试玩权限', {
id: id
});
logStatus.success();
return true;
} catch (error) {
debug('请求游戏试玩权限时发生错误', {
error: error,
id: id
});
throwError(error, 'SteamWeb.requestPlayTestAccess');
return false;
}
}
async resetArea() {
try {
debug('检查区域设置状态', {
currentArea: this.#area,
oldArea: this.#oldArea,
needReset: Boolean(this.#oldArea && this.#area !== this.#oldArea)
});
if (this.#oldArea && this.#area !== this.#oldArea) {
debug('需要重置区域', {
fromArea: this.#area,
toArea: this.#oldArea
});
echoLog({
before: '[Web]'
}).warning(I18n('steamFinishNotice') + this.#oldArea);
const changeResult = await this.#changeArea(this.#oldArea);
debug('区域重置结果', {
success: changeResult,
targetArea: this.#oldArea
});
} else {
debug('无需重置区域', {
currentArea: this.#area,
oldArea: this.#oldArea
});
}
debug('区域重置流程完成');
return true;
} catch (error) {
debug('重置区域时发生错误', {
error: error
});
throwError(error, 'SteamWeb.resetArea');
return false;
}
}
#setCache(type, name, id) {
try {
debug('开始设置缓存', {
type: type,
name: name,
id: id
});
this.#cache[type][name] = id;
GM_setValue('steamCache', this.#cache);
debug('设置缓存成功', {
type: type,
name: name,
id: id
});
} catch (error) {
debug('设置缓存时发生异常', {
error: error,
type: type,
name: name,
id: id
});
throwError(error, 'SteamWeb.setCache');
}
}
}
class Steam extends Social {
tasks;
whiteList;
#cache={
...{
group: {},
officialGroup: {},
forum: {},
workshop: {},
curator: {}
},
...GM_getValue('steamCache')
};
#TaskExecutor=[];
constructor() {
super();
debug('初始化Steam实例');
const defaultTasksTemplate = {
groups: [],
officialGroups: [],
wishlists: [],
follows: [],
forums: [],
workshops: [],
workshopVotes: [],
curators: [],
curatorLikes: [],
announcements: [],
licenses: [],
playtests: [],
playTime: []
};
this.tasks = defaultTasksTemplate;
this.whiteList = {
...defaultTasksTemplate,
...GM_getValue('whiteList')?.steam || {}
};
this.#TaskExecutor = this.#getTaskExecutionOrder(globalOptions.ASF.AsfEnabled, globalOptions.ASF.steamWeb, globalOptions.ASF.preferASF);
debug('Steam实例初始化完成', {
taskExecutorCount: this.#TaskExecutor.length
});
}
async init(type = 'all') {
try {
debug('开始初始化Steam模块', {
type: type
});
for (let i = 0; i < this.#TaskExecutor.length; i++) {
debug(`初始化执行器 ${i + 1}/${this.#TaskExecutor.length}`);
if (!await this.#TaskExecutor[i].init(type)) {
debug(`执行器 ${i + 1} 初始化失败,移除该执行器`);
this.#TaskExecutor.splice(i, 1);
}
}
debug('Steam模块初始化完成', {
remainingExecutors: this.#TaskExecutor.length
});
return this.#TaskExecutor.length > 0;
} catch (error) {
debug('Steam初始化发生错误', {
error: error
});
throwError(error, 'Steam.init');
return false;
}
}
async #joinGroup(groupName) {
try {
debug('开始加入Steam组', {
groupName: groupName
});
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.joinGroup(groupName)) {
debug('成功加入Steam组', {
groupName: groupName
});
this.tasks.groups = unique([ ...this.tasks.groups, groupName ]);
return true;
}
}
debug('加入Steam组失败', {
groupName: groupName
});
return false;
} catch (error) {
debug('加入Steam组时发生错误', {
error: error,
groupName: groupName
});
throwError(error, 'Steam.joinGroup');
return false;
}
}
async #leaveGroup(groupName) {
try {
debug('开始退出Steam组', {
groupName: groupName
});
if (this.whiteList.groups.includes(groupName)) {
debug('Steam组在白名单中,跳过退出', {
groupName: groupName
});
echoLog({
type: 'whiteList',
text: 'Steam.leaveGroup',
id: groupName
});
return true;
}
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.leaveGroup(groupName)) {
debug('成功退出Steam组', {
groupName: groupName
});
return true;
}
}
debug('退出Steam组失败', {
groupName: groupName
});
return false;
} catch (error) {
debug('退出Steam组时发生错误', {
error: error,
groupName: groupName
});
throwError(error, 'Steam.leaveGroup');
return false;
}
}
async #joinOfficialGroup(gameId) {
try {
debug('开始加入Steam官方组', {
gameId: gameId
});
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.joinOfficialGroup(gameId)) {
debug('成功加入Steam官方组', {
gameId: gameId
});
return true;
}
}
debug('加入Steam官方组失败', {
gameId: gameId
});
return false;
} catch (error) {
debug('加入Steam官方组时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'Steam.joinOfficialGroup');
return false;
}
}
async #leaveOfficialGroup(gameId) {
try {
debug('开始退出Steam官方组', {
gameId: gameId
});
if (this.whiteList.officialGroups.includes(gameId)) {
debug('Steam官方组在白名单中,跳过退出', {
gameId: gameId
});
echoLog({
type: 'whiteList',
text: 'Steam.leaveOfficialGroup',
id: gameId
});
return true;
}
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.leaveOfficialGroup(gameId)) {
debug('成功退出Steam官方组', {
gameId: gameId
});
this.tasks.officialGroups = unique([ ...this.tasks.officialGroups, gameId ]);
return true;
}
}
debug('退出Steam官方组失败', {
gameId: gameId
});
return false;
} catch (error) {
debug('退出Steam官方组时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'Steam.leaveOfficialGroup');
return false;
}
}
async #addToWishlist(gameId) {
try {
debug('开始添加游戏到愿望单', {
gameId: gameId
});
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.addToWishlist(gameId)) {
debug('成功添加游戏到愿望单', {
gameId: gameId
});
this.tasks.wishlists = unique([ ...this.tasks.wishlists, gameId ]);
return true;
}
}
debug('添加游戏到愿望单失败', {
gameId: gameId
});
return false;
} catch (error) {
debug('添加游戏到愿望单时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'Steam.addToWishlist');
return false;
}
}
async #removeFromWishlist(gameId) {
try {
debug('开始从愿望单移除游戏', {
gameId: gameId
});
if (this.whiteList.wishlists.includes(gameId)) {
debug('游戏在愿望单白名单中,跳过移除', {
gameId: gameId
});
echoLog({
type: 'whiteList',
text: 'Steam.removeFromWishlist',
id: gameId
});
return true;
}
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.removeFromWishlist(gameId)) {
debug('成功从愿望单移除游戏', {
gameId: gameId
});
return true;
}
}
debug('从愿望单移除游戏失败', {
gameId: gameId
});
return false;
} catch (error) {
debug('从愿望单移除游戏时发生错误', {
error: error,
gameId: gameId
});
throwError(error, 'Steam.removeFromWishlist');
return false;
}
}
async #toggleFollowGame(gameId, doTask) {
try {
debug('开始处理游戏关注状态', {
gameId: gameId,
doTask: doTask
});
if (!doTask && this.whiteList.follows.includes(gameId)) {
debug('游戏在关注白名单中,跳过取关', {
gameId: gameId
});
echoLog({
type: 'whiteList',
text: 'Steam.unfollowGame',
id: gameId
});
return true;
}
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.toggleFollowGame(gameId, doTask)) {
if (doTask) {
debug('成功关注游戏', {
gameId: gameId
});
this.tasks.follows = unique([ ...this.tasks.follows, gameId ]);
} else {
debug('成功取关游戏', {
gameId: gameId
});
}
return true;
}
}
debug('处理游戏关注状态失败', {
gameId: gameId,
doTask: doTask
});
return false;
} catch (error) {
debug('处理游戏关注状态时发生错误', {
error: error,
gameId: gameId,
doTask: doTask
});
throwError(error, 'Steam.toggleFollowGame');
return false;
}
}
async #toggleForum(gameId, doTask = true) {
try {
debug('开始处理论坛订阅状态', {
gameId: gameId,
doTask: doTask
});
if (!doTask && this.whiteList.forums.includes(gameId)) {
debug('论坛在白名单中,跳过取消订阅', {
gameId: gameId
});
echoLog({
type: 'whiteList',
text: 'Steam.unsubscribeForum',
id: gameId
});
return true;
}
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.toggleForum(gameId, doTask)) {
if (doTask) {
debug('成功订阅论坛', {
gameId: gameId
});
this.tasks.forums = unique([ ...this.tasks.forums, gameId ]);
} else {
debug('成功取消订阅论坛', {
gameId: gameId
});
}
return true;
}
}
debug('处理论坛订阅状态失败', {
gameId: gameId,
doTask: doTask
});
return false;
} catch (error) {
debug('处理论坛订阅状态时发生错误', {
error: error,
gameId: gameId,
doTask: doTask
});
throwError(error, 'Steam.toggleForum');
return true;
}
}
async #toggleFavoriteWorkshop(id, doTask = true) {
try {
debug('开始处理创意工坊收藏状态', {
id: id,
doTask: doTask
});
if (!doTask && this.whiteList.workshops.includes(id)) {
debug('创意工坊物品在白名单中,跳过取消收藏', {
id: id
});
echoLog({
type: 'whiteList',
text: 'Steam.unfavoriteWorkshop',
id: id
});
return true;
}
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.toggleFavoriteWorkshop(id)) {
if (doTask) {
debug('成功收藏创意工坊物品', {
id: id
});
this.tasks.workshops = unique([ ...this.tasks.workshops, id ]);
} else {
debug('成功取消收藏创意工坊物品', {
id: id
});
}
return true;
}
}
debug('处理创意工坊收藏状态失败', {
id: id,
doTask: doTask
});
return false;
} catch (error) {
debug('处理创意工坊收藏状态时发生错误', {
error: error,
id: id,
doTask: doTask
});
throwError(error, 'Steam.toggleFavoriteWorkshop');
return false;
}
}
async #voteUpWorkshop(id) {
try {
debug('开始点赞创意工坊物品', {
id: id
});
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.voteUpWorkshop(id)) {
debug('成功点赞创意工坊物品', {
id: id
});
return true;
}
}
debug('点赞创意工坊物品失败', {
id: id
});
return false;
} catch (error) {
debug('点赞创意工坊物品时发生错误', {
error: error,
id: id
});
throwError(error, 'Steam.voteupWorkshop');
return true;
}
}
async #toggleCurator(curatorId, doTask = true) {
try {
debug('开始处理鉴赏家关注状态', {
curatorId: curatorId,
doTask: doTask
});
if (!doTask && this.whiteList.curators.includes(curatorId)) {
debug('鉴赏家在白名单中,跳过取关', {
curatorId: curatorId
});
echoLog({
type: 'whiteList',
text: 'Steam.unfollowCurator',
id: curatorId
});
return true;
}
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.toggleCurator(curatorId, doTask)) {
if (doTask) {
debug('成功关注鉴赏家', {
curatorId: curatorId
});
this.tasks.curators = unique([ ...this.tasks.curators, curatorId ]);
} else {
debug('成功取关鉴赏家', {
curatorId: curatorId
});
}
return true;
}
}
debug('处理鉴赏家关注状态失败', {
curatorId: curatorId,
doTask: doTask
});
return false;
} catch (error) {
debug('处理鉴赏家关注状态时发生错误', {
error: error,
curatorId: curatorId,
doTask: doTask
});
throwError(error, 'Steam.toggleCurator');
return false;
}
}
async getCuratorId(path, name) {
try {
debug('开始获取鉴赏家ID', {
path: path,
name: name
});
const logStatus = echoLog({
type: 'gettingCuratorId',
text: `${path}/${name}`,
before: '[Web]'
});
const curatorId = this.#cache.curator[`${path}/${name}`];
if (curatorId) {
debug('从缓存获取到鉴赏家ID', {
path: path,
name: name,
curatorId: curatorId
});
logStatus.success();
return curatorId;
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/${path}/${name}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
if (result === 'Success') {
if (data?.status === 200) {
const curatorId = data.responseText.match(/g_pagingData.*?"clanid":([\d]+)/)?.[1];
if (curatorId) {
debug('成功获取鉴赏家ID', {
path: path,
name: name,
curatorId: curatorId
});
this.#setCache('curator', `${path}/${name}`, curatorId);
logStatus.success();
return curatorId;
}
debug('未找到鉴赏家ID', {
path: path,
name: name,
status: data.status
});
logStatus.error(`Error:${data.statusText}(${data.status})`);
return false;
}
debug('获取鉴赏家页面失败', {
path: path,
name: name,
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('请求鉴赏家页面失败', {
path: path,
name: name,
result: result,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
} catch (error) {
debug('获取鉴赏家ID时发生错误', {
error: error,
path: path,
name: name
});
throwError(error, 'SteamWeb.getCuratorID');
return false;
}
}
async #toggleCuratorLike(link, doTask = true) {
try {
debug('开始处理鉴赏家点赞状态', {
link: link,
doTask: doTask
});
const [path, name] = link.split('/');
if (!(path && name)) {
debug('无效的鉴赏家链接', {
link: link
});
echoLog({
text: I18n('errorLink', link),
before: '[Web]'
});
return false;
}
const curatorId = await this.getCuratorId(path, name);
if (curatorId) {
debug('获取到鉴赏家ID,开始处理点赞', {
curatorId: curatorId,
doTask: doTask
});
return await this.#toggleCurator(curatorId, doTask);
}
debug('未获取到鉴赏家ID', {
link: link
});
return false;
} catch (error) {
debug('处理鉴赏家点赞状态时发生错误', {
error: error,
link: link,
doTask: doTask
});
throwError(error, 'Steam.toggleCuratorLike');
return false;
}
}
async #likeAnnouncement(id) {
try {
debug('开始点赞公告', {
id: id
});
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.likeAnnouncement(id)) {
debug('成功点赞公告', {
id: id
});
return true;
}
}
debug('点赞公告失败', {
id: id
});
return false;
} catch (error) {
debug('点赞公告时发生错误', {
error: error,
id: id
});
throwError(error, 'Steam.likeAnnouncement');
return false;
}
}
async #addLicense(id) {
try {
debug('开始添加许可证', {
id: id
});
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.addLicense(id)) {
debug('成功添加许可证', {
id: id
});
return true;
}
}
debug('添加许可证失败', {
id: id
});
return false;
} catch (error) {
debug('添加许可证时发生错误', {
error: error,
id: id
});
throwError(error, 'Steam.addLicense');
return false;
}
}
async #requestPlayTestAccess(id) {
try {
debug('开始请求游戏试玩权限', {
id: id
});
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.requestPlayTestAccess(id)) {
debug('成功请求游戏试玩权限', {
id: id
});
return true;
}
}
debug('请求游戏试玩权限失败', {
id: id
});
return false;
} catch (error) {
debug('请求游戏试玩权限时发生错误', {
error: error,
id: id
});
throwError(error, 'Steam.requestPlayTestAccess');
return false;
}
}
async #getDemoAppid(id) {
try {
debug('开始获取游戏试玩ID', {
id: id
});
const logStatus = echoLog({
type: 'gettingDemoAppid',
text: id,
before: '[Web]'
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://store.steampowered.com/app/${id}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Host: 'store.steampowered.com',
Origin: 'https://store.steampowered.com',
Referer: `https://store.steampowered.com/app/${id}`
}
});
if (result === 'Success') {
if (data?.status === 200) {
const demoAppid = data.responseText.match(/steam:\/\/(install|run)\/(\d+)/)?.[2];
debug('成功获取游戏试玩ID', {
id: id,
demoAppid: demoAppid
});
logStatus.success();
return demoAppid || false;
}
debug('获取游戏页面失败', {
id: id,
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('请求游戏页面失败', {
id: id,
result: result,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
} catch (error) {
debug('获取游戏试玩ID时发生错误', {
error: error,
id: id
});
throwError(error, 'Steam.getDemoAppid');
return false;
}
}
async #playGames(ids, playTime, doTask = true) {
try {
debug('开始处理游戏挂时长', {
ids: ids,
playTime: playTime,
doTask: doTask
});
if (playTime <= 0) {
debug('游戏时长小于等于0,跳过挂时长');
return true;
}
const asf = this.#TaskExecutor.find((e => e instanceof SteamASF));
if (!asf) {
debug('未找到ASF实例');
echoLog({}).warning(I18n('noASFInstance'));
return false;
}
if (!doTask) {
debug('停止挂时长');
return await asf.stopPlayGames();
}
const idsArr = await Promise.all(ids.split(',').map((async id => {
try {
const demoAppid = await this.#getDemoAppid(id);
return demoAppid ? `${id},${demoAppid}` : id;
} catch (error) {
debug('获取游戏试玩ID失败', {
error: error,
id: id
});
return id;
}
})));
const uniqueIds = unique(idsArr.join(',').split(','));
debug('处理后的游戏ID列表', {
uniqueIds: uniqueIds
});
debug('开始尝试入库游戏', {
uniqueIds: uniqueIds
});
await Promise.all(uniqueIds.map((async id => {
for (const taskExecutor of this.#TaskExecutor) {
if (await taskExecutor.addLicense(`appid-${id}`)) {
debug('成功入库游戏', {
id: id
});
return true;
}
}
return false;
})));
await asf.playGames(uniqueIds.join(','));
const status = await asf.checkPlayStatus(uniqueIds.join(','));
if (status !== true) {
await delay(3e3);
await asf.playGames(uniqueIds.join(','));
const status = await asf.checkPlayStatus(uniqueIds.join(','));
if (!status) {
debug('启动游戏失败');
return false;
}
}
const stopPlayTime = Date.now() + (playTime + 10) * 60 * 1e3;
const stopPlayTimeOld = GM_getValue('stopPlayTime', 0) || 0;
GM_setValue('stopPlayTime', Math.max(stopPlayTime, stopPlayTimeOld));
const playedGames = GM_getValue('playedGames', []) || [];
GM_setValue('playedGames', unique([ ...playedGames, ...uniqueIds ]));
const taskLink = GM_getValue('taskLink', []) || [];
GM_setValue('taskLink', unique([ ...taskLink, window.location.href ]));
debug('游戏挂时长状态更新完成');
return true;
} catch (error) {
debug('处理游戏挂时长时发生错误', {
error: error,
ids: ids,
playTime: playTime
});
throwError(error, 'Steam.playGames');
return false;
}
}
async toggle({doTask: doTask = true, groupLinks: groupLinks = [], officialGroupLinks: officialGroupLinks = [], wishlistLinks: wishlistLinks = [], followLinks: followLinks = [], forumLinks: forumLinks = [], workshopLinks: workshopLinks = [], workshopVoteLinks: workshopVoteLinks = [], curatorLinks: curatorLinks = [], curatorLikeLinks: curatorLikeLinks = [], announcementLinks: announcementLinks = [], licenseLinks: licenseLinks = [], playtestLinks: playtestLinks = [], playTimeLinks: playTimeLinks = []}) {
try {
debug('开始处理Steam任务', {
doTask: doTask,
linksCount: {
groups: groupLinks.length,
officialGroups: officialGroupLinks.length,
wishlists: wishlistLinks.length,
follows: followLinks.length,
forums: forumLinks.length,
workshops: workshopLinks.length,
workshopVotes: workshopVoteLinks.length,
curators: curatorLinks.length,
curatorLikes: curatorLikeLinks.length,
announcements: announcementLinks.length,
licenses: licenseLinks.length,
playtests: playtestLinks.length,
playTime: playTimeLinks.length
}
});
const allLinks = [ ...groupLinks, ...officialGroupLinks, ...forumLinks, ...workshopLinks, ...workshopVoteLinks, ...wishlistLinks, ...followLinks, ...curatorLinks, ...curatorLikeLinks, ...announcementLinks, ...licenseLinks, ...playtestLinks, ...playTimeLinks ];
if (allLinks.length > 0 && this.#TaskExecutor.length === 0) {
debug('Steam模块未初始化');
echoLog({
text: I18n('needInit')
});
return false;
}
const tasks = [];
if (this.shouldProcessTask('groups', doTask)) {
debug('开始处理群组任务');
const realGroups = this.getRealParams('groups', groupLinks, doTask, (link => link.match(/groups\/(.+)\/?/)?.[1]?.split('/')?.[0]));
debug('处理后的群组列表', {
count: realGroups.length,
groups: realGroups
});
for (const group of realGroups) {
tasks.push(doTask ? this.#joinGroup(group) : this.#leaveGroup(group));
await delay(1e3);
}
}
if (this.shouldProcessTask('officialGroups', doTask)) {
const realOfficialGroups = this.getRealParams('officialGroups', officialGroupLinks, doTask, (link => link.match(/games\/(.+)\/?/)?.[1]));
for (const officialGroup of realOfficialGroups) {
tasks.push(doTask ? this.#joinOfficialGroup(officialGroup) : this.#leaveOfficialGroup(officialGroup));
await delay(1e3);
}
}
if (this.shouldProcessTask('wishlists', doTask)) {
const realWishlists = this.getRealParams('wishlists', wishlistLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1]));
for (const game of realWishlists) {
tasks.push(doTask ? this.#addToWishlist(game) : this.#removeFromWishlist(game));
await delay(1e3);
}
}
if (this.shouldProcessTask('follows', doTask)) {
const realFollows = this.getRealParams('follows', followLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1]));
for (const game of realFollows) {
tasks.push(this.#toggleFollowGame(game, doTask));
await delay(1e3);
}
}
if (this.shouldProcessTask('playTime', doTask)) {
const realGames = this.getRealParams('playTime', playTimeLinks, doTask, (link => `${link.split('-')[0]}-${link.match(/app\/([\d]+)/)?.[1] || ''}`));
if (realGames.length > 0) {
const maxTime = Math.max(...realGames.map((info => parseInt(info.split('-')[0], 10) || 0)));
const games = realGames.filter((info => {
const [time, game] = info.split('-');
return (parseInt(time, 10) || 0) > 0 && game;
})).map((info => info.split('-')[1]));
tasks.push(this.#playGames(games.join(','), maxTime, doTask));
await delay(1e3);
}
}
if (this.shouldProcessTask('forums', doTask)) {
const realForums = this.getRealParams('forums', forumLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1]));
for (const forum of realForums) {
tasks.push(this.#toggleForum(forum, doTask));
await delay(1e3);
}
}
if (this.shouldProcessTask('workshops', doTask)) {
const realWorkshops = this.getRealParams('workshops', workshopLinks, doTask, (link => link.match(/\?id=([\d]+)/)?.[1]));
for (const workshop of realWorkshops) {
tasks.push(this.#toggleFavoriteWorkshop(workshop, doTask));
await delay(1e3);
}
}
if (doTask && globalOptions.doTask.steam.workshopVotes) {
const realworkshopVotes = this.getRealParams('workshopVotes', workshopVoteLinks, doTask, (link => link.match(/\?id=([\d]+)/)?.[1]));
for (const workshop of realworkshopVotes) {
tasks.push(this.#voteUpWorkshop(workshop));
await delay(1e3);
}
}
if (this.shouldProcessTask('curators', doTask)) {
const realCurators = this.getRealParams('curators', curatorLinks, doTask, (link => link.match(/curator\/([\d]+)/)?.[1]));
const realCuratorLikes = this.getRealParams('curatorLikes', curatorLikeLinks, doTask, (link => link.match(/https?:\/\/store\.steampowered\.com\/(.*?)\/([^/?]+)/)?.slice(1, 3).join('/')));
for (const curator of realCurators) {
tasks.push(this.#toggleCurator(curator, doTask));
await delay(1e3);
}
for (const curatorLike of realCuratorLikes) {
tasks.push(this.#toggleCuratorLike(curatorLike, doTask));
await delay(1e3);
}
}
if (doTask && globalOptions.doTask.steam.announcements) {
const realAnnouncements = this.getRealParams('announcements', announcementLinks, doTask, (link => {
if (link.includes('store.steampowered.com')) {
return link.match(/store\.steampowered\.com\/news\/app\/([\d]+)\/view\/([\d]+)/)?.slice(1, 3).join('/');
}
return link.match(/steamcommunity\.com\/games\/([\d]+)\/announcements\/detail\/([\d]+)/)?.slice(1, 3).join('/');
}));
for (const id of realAnnouncements) {
tasks.push(this.#likeAnnouncement(id));
await delay(1e3);
}
}
if (doTask && globalOptions.doTask.steam.licenses && licenseLinks.length > 0) {
for (const ids of licenseLinks) {
const [type, idsStr] = ids.split('-');
const idsArr = idsStr.split(',');
for (const id of idsArr) {
tasks.push(this.#addLicense(`${type}-${id}`));
await delay(1e3);
}
}
}
if (doTask && globalOptions.doTask.steam.playtests) {
const realPlaytests = this.getRealParams('playtests', playtestLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1]));
for (const id of realPlaytests) {
tasks.push(this.#requestPlayTestAccess(id));
await delay(1e3);
}
}
debug('开始执行所有任务');
const results = await Promise.all(tasks);
this.#TaskExecutor.find((e => e instanceof SteamWeb))?.resetArea();
debug('所有任务执行完成', {
success: results.every((result => result))
});
return results.every((result => result));
} catch (error) {
debug('处理Steam任务时发生错误', {
error: error
});
throwError(error, 'Steam.toggle');
return false;
}
}
shouldProcessTask(taskType, doTask) {
debug('检查是否处理任务', {
taskType: taskType,
doTask: doTask
});
if (doTask) {
const result = globalOptions.doTask.steam[taskType];
debug('检查doTask配置', {
taskType: taskType,
result: result
});
return globalOptions.doTask.steam[taskType];
}
const undoTaskType = taskType;
return undoTaskType in globalOptions.undoTask.steam && globalOptions.undoTask.steam[undoTaskType];
}
#setCache(type, name, id) {
try {
this.#cache[type][name] = id;
GM_setValue('steamCache', this.#cache);
} catch (error) {
throwError(error, 'SteamWeb.setCache');
}
}
#getTaskExecutionOrder(asfEnabled, steamWebEnabled, preferASF) {
if (!asfEnabled) {
return [ new SteamWeb ];
}
if (!steamWebEnabled) {
return [ new SteamASF(globalOptions.ASF) ];
}
return preferASF ? [ new SteamASF(globalOptions.ASF), new SteamWeb ] : [ new SteamWeb, new SteamASF(globalOptions.ASF) ];
}
}
class Website {
undoneTasks;
socialTasks;
giveawayId;
socialInitialized={
discord: false,
instagram: false,
reddit: false,
twitch: false,
twitter: false,
vk: false,
youtube: false,
steamStore: false,
steamCommunity: false
};
initialized=false;
steamTaskType={
steamStore: false,
steamCommunity: false
};
social={};
async #bind(name, init) {
try {
debug('开始绑定社交媒体', {
name: name
});
const result = await init;
debug('绑定结果', {
name: name,
result: result
});
return {
name: name,
result: result
};
} catch (error) {
debug('绑定失败', {
name: name,
error: error
});
throwError(error, 'Website.bind');
return {
name: name,
result: false
};
}
}
async initSocial(action) {
try {
debug('开始初始化社交媒体', {
action: action
});
const pro = [];
const tasks = action === 'do' ? this.undoneTasks : this.socialTasks;
if (tasks.discord) {
const hasDiscord = Object.values(tasks.discord).reduce(((total, arr) => [ ...total, ...arr ])).length > 0;
debug('检查 Discord 任务', {
hasDiscord: hasDiscord
});
if (hasDiscord && (!this.socialInitialized.discord || !this.social.discord)) {
debug('初始化 Discord');
this.social.discord = new Discord;
pro.push(this.#bind('discord', this.social.discord.init(action)));
}
}
if (tasks.reddit) {
const hasReddit = Object.values(tasks.reddit).reduce(((total, arr) => [ ...total, ...arr ])).length > 0;
debug('检查 Reddit 任务', {
hasReddit: hasReddit
});
if (hasReddit && (!this.socialInitialized.reddit || !this.social.reddit)) {
debug('初始化 Reddit');
this.social.reddit = new Reddit;
pro.push(this.#bind('reddit', this.social.reddit.init()));
}
}
if (tasks.twitch) {
const hasTwitch = Object.values(tasks.twitch).reduce(((total, arr) => [ ...total, ...arr ])).length > 0;
debug('检查 Twitch 任务', {
hasTwitch: hasTwitch
});
if (hasTwitch && (!this.socialInitialized.twitch || !this.social.twitch)) {
debug('初始化 Twitch');
this.social.twitch = new Twitch;
pro.push(this.#bind('twitch', this.social.twitch.init()));
}
}
if (tasks.twitter) {
const hasTwitter = Object.values(tasks.twitter).reduce(((total, arr) => [ ...total, ...arr ])).length > 0;
debug('检查 Twitter 任务', {
hasTwitter: hasTwitter
});
if (hasTwitter && (!this.socialInitialized.twitter || !this.social.twitter)) {
debug('初始化 Twitter');
this.social.twitter = new Twitter;
pro.push(this.#bind('twitter', this.social.twitter.init()));
}
}
if (tasks.vk) {
const hasVk = Object.values(tasks.vk).reduce(((total, arr) => [ ...total, ...arr ])).length > 0;
debug('检查 VK 任务', {
hasVk: hasVk
});
if (hasVk && (!this.socialInitialized.vk || !this.social.vk)) {
debug('初始化 VK');
this.social.vk = new Vk;
pro.push(this.#bind('vk', this.social.vk.init()));
}
}
if (tasks.youtube) {
const hasYoutube = Object.values(tasks.youtube).reduce(((total, arr) => [ ...total, ...arr ])).length > 0;
debug('检查 YouTube 任务', {
hasYoutube: hasYoutube
});
if (hasYoutube && (!this.socialInitialized.youtube || !this.social.youtube)) {
debug('初始化 YouTube');
this.social.youtube = new Youtube;
pro.push(this.#bind('youtube', this.social.youtube.init()));
}
}
if (tasks.steam) {
const steamLength = Object.values(tasks.steam).reduce(((total, arr) => [ ...total, ...arr ])).length;
debug('检查 Steam 任务', {
steamLength: steamLength
});
if (steamLength > 0) {
if (!this.social.steam) {
debug('创建 Steam 实例');
this.social.steam = new Steam;
}
const steamCommunityLength = Object.keys(tasks.steam).map((type => [ 'groupLinks', 'officialGroupLinks', 'forumLinks', 'workshopLinks', 'workshopVoteLinks' ].includes(type) ? tasks.steam?.[type]?.length || 0 : 0)).reduce(((total, number) => total + number), 0);
debug('Steam 社区任务数量', {
steamCommunityLength: steamCommunityLength
});
if (steamLength - steamCommunityLength > 0) {
this.steamTaskType.steamStore = true;
if (!this.socialInitialized.steamStore) {
debug('初始化 Steam 商店');
pro.push(this.#bind('steamStore', this.social.steam.init('store')));
}
}
if (steamCommunityLength > 0) {
if (!this.socialInitialized.steamCommunity) {
this.steamTaskType.steamCommunity = true;
debug('初始化 Steam 社区');
pro.push(this.#bind('steamCommunity', this.social.steam.init('community')));
}
}
}
}
if (tasks.links && tasks.links.length > 0) {
debug('初始化链接访问', {
linksCount: tasks.links.length
});
this.social.visitLink = visitLink;
}
debug('等待所有社交媒体初始化完成');
return await Promise.all(pro).then((result => {
let checked = true;
for (const data of result) {
if (data.result) {
debug('社交媒体初始化成功', {
name: data.name
});
this.socialInitialized[data.name] = data.result;
} else {
debug('社交媒体初始化失败', {
name: data.name
});
checked = false;
}
}
debug('社交媒体初始化完成', {
allSuccess: checked
});
return checked;
}));
} catch (error) {
debug('初始化社交媒体失败', {
error: error
});
throwError(error, 'Website.initSocial');
return false;
}
}
uniqueTasks(allTasks) {
try {
debug('开始去重任务');
const result = {};
for (const [social, types] of Object.entries(allTasks)) {
debug('处理社交媒体任务', {
social: social
});
result[social] = {};
for (const [type, tasks] of Object.entries(types)) {
debug('处理任务类型', {
social: social,
type: type
});
result[social][type] = unique(tasks);
}
}
debug('任务去重完成');
return result;
} catch (error) {
debug('任务去重失败', {
error: error
});
throwError(error, 'Website.uniqueTasks');
return allTasks;
}
}
async toggleTask(action) {
try {
debug('开始切换任务状态', {
action: action
});
if (!this.initialized && !this.init()) {
debug('初始化失败');
return false;
}
if (!await this.classifyTask(action)) {
debug('任务分类失败');
return false;
}
debug('初始化社交媒体');
await this.initSocial(action);
const pro = [];
const doTask = action === 'do';
const tasks = doTask ? this.undoneTasks : this.socialTasks;
if (this.socialInitialized.discord === true && this.social.discord) {
debug('处理 Discord 任务');
pro.push(this.social.discord.toggle({
doTask: doTask,
...tasks.discord
}));
}
if (this.socialInitialized.reddit === true && this.social.reddit) {
debug('处理 Reddit 任务');
pro.push(this.social.reddit.toggle({
doTask: doTask,
...tasks.reddit
}));
}
if (this.socialInitialized.twitch === true && this.social.twitch) {
debug('处理 Twitch 任务');
pro.push(this.social.twitch.toggle({
doTask: doTask,
...tasks.twitch
}));
}
if (this.socialInitialized.twitter === true && this.social.twitter) {
debug('处理 Twitter 任务');
pro.push(this.social.twitter.toggle({
doTask: doTask,
...tasks.twitter
}));
}
if (this.socialInitialized.vk === true && this.social.vk) {
debug('处理 VK 任务');
pro.push(this.social.vk.toggle({
doTask: doTask,
...tasks.vk
}));
}
if (this.socialInitialized.youtube === true && this.social.youtube) {
debug('处理 YouTube 任务');
pro.push(this.social.youtube.toggle({
doTask: doTask,
...tasks.youtube
}));
}
if ((this.steamTaskType.steamCommunity ? this.socialInitialized.steamCommunity === true : true) && (this.steamTaskType.steamStore ? this.socialInitialized.steamStore === true : true) && this.social.steam) {
debug('处理 Steam 任务');
pro.push(this.social.steam.toggle({
doTask: doTask,
...tasks.steam
}));
}
if (this.social.visitLink && tasks.links && doTask) {
debug('处理链接任务', {
linksCount: tasks.links.length
});
for (const link of tasks.links) {
pro.push(this.social.visitLink(link));
}
}
if (doTask && tasks.extra && this.extraDoTask) {
const hasExtra = Object.values(tasks.extra).reduce(((total, arr) => [ ...total, ...arr ])).length > 0;
if (hasExtra) {
debug('处理额外任务');
pro.push(this.extraDoTask(tasks.extra));
}
}
debug('等待所有任务完成');
await Promise.all(pro);
debug('所有任务完成');
echoLog({}).success(I18n('allTasksComplete'));
return true;
} catch (error) {
debug('切换任务失败', {
error: error
});
throwError(error, 'Website.toggleTask');
return false;
}
}
async doTask() {
try {
debug('开始执行任务');
const result = await this.toggleTask('do');
debug('任务执行完成', {
success: result
});
return result;
} catch (error) {
debug('执行任务失败', {
error: error
});
throwError(error, 'Website.doTask');
return false;
}
}
async undoTask() {
try {
debug('开始撤销任务');
const result = await this.toggleTask('undo');
debug('任务撤销完成', {
success: result
});
return result;
} catch (error) {
debug('撤销任务失败', {
error: error
});
throwError(error, 'Website.undoTask');
return false;
}
}
}
const defaultTasksTemplate$6 = {
steam: {
groupLinks: [],
wishlistLinks: [],
curatorLinks: [],
followLinks: [],
playTimeLinks: []
},
discord: {
serverLinks: []
},
vk: {
nameLinks: []
},
youtube: {
channelLinks: []
},
extra: {
website: []
}
};
const defaultTasks$8 = JSON.stringify(defaultTasksTemplate$6);
class FreeAnyWhere extends Website {
name='FreeAnyWhere';
tasks=[];
socialTasks=JSON.parse(defaultTasks$8);
undoneTasks=JSON.parse(defaultTasks$8);
games;
buttons=[ 'doTask', 'undoTask', 'verifyTask', 'getKey' ];
static test() {
const isMatch = window.location.host === 'freeanywhere.net';
debug('检查网站匹配', {
host: window.location.host,
isMatch: isMatch
});
return isMatch;
}
async init() {
try {
debug('初始化 FreeAnyWhere', {
url: window.location.href
});
const logStatus = echoLog({
text: I18n('initing')
});
debug('检测登录状态');
if ($('div.header__login a[href*=logout]').length === 0) {
debug('未登录,准备跳转到登录页面');
window.open('https://freeanywhere.net/game.php?steam_login', '_self');
logStatus.warning(I18n('needLogin'));
return false;
}
debug('检测是否为登录页面');
if (window.location.href.includes('/login')) {
logStatus.warning(I18n('needLogin'));
return false;
}
if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
const giveawayIdSuccess = this.#getGiveawayId();
debug('获取抽奖ID结果', {
success: giveawayIdSuccess,
id: this.giveawayId
});
this.initialized = true;
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'Freeanywhere.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('获取已保存的任务信息');
this.socialTasks = GM_getValue(`fawTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$8);
}
const tasks = $('div.game__content-tasks__task').map(((index, element) => ({
id: $(element).attr('data-id'),
social: $(element).find('div.task-img img').attr('alt'),
link: $(element).find('div.task-link a').attr('href'),
title: $(element).find('div.task-link').text().trim(),
type: $(element).attr('data-type'),
data: $(element).attr('data-data'),
isSuccess: $(element).hasClass('done')
}))).toArray();
debug('获取到的任务列表', {
tasksCount: tasks.length,
tasks: tasks
});
if (tasks.length === 0) {
logStatus.success();
return false;
}
if (action === 'verify') {
this.tasks = [];
}
for (const task of tasks) {
await this.#processTask(task, action);
}
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.uniqueTasks(this.socialTasks);
debug('任务分类结果', {
undoneTasks: this.undoneTasks,
socialTasks: this.socialTasks
});
GM_setValue(`fawTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Freeanywhere.classifyTask');
return false;
}
}
async #processTask(task, action) {
try {
debug('处理任务', {
task: task,
action: action
});
const {id: id, social: social, title: title, type: type, link: link, data: data, isSuccess: isSuccess} = task;
const taskInfo = {
id: id,
title: title,
social: social,
type: type,
data: data
};
if (action === 'verify' && !isSuccess) {
debug('添加到验证任务列表', taskInfo);
this.tasks.push(taskInfo);
return;
}
debug('处理特定类型任务', {
type: type,
action: action,
isSuccess: isSuccess
});
switch (type) {
case 'steam_account_verify':
case 'site_email_verify':
debug('跳过任务', {
type: type
});
break;
case 'steam_game_sub':
if (action === 'undo' && link) {
this.socialTasks.steam.followLinks.push(link);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.steam.followLinks.push(link);
}
break;
case 'steam_game_wishlist':
if (action === 'undo' && link) {
this.socialTasks.steam.wishlistLinks.push(link);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.steam.wishlistLinks.push(link);
}
break;
case 'steam_group_sub':
if (action === 'undo' && link) {
this.socialTasks.steam.groupLinks.push(link);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.steam.groupLinks.push(link);
}
break;
case 'site_visit':
if (action === 'do' && !isSuccess) {
this.undoneTasks.extra.website.push(`id=${id}&type=${type}&task=true`);
}
break;
case 'vk_community_sub':
if (action === 'undo' && link) {
this.socialTasks.vk.nameLinks.push(link);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.vk.nameLinks.push(link);
}
break;
case 'vk_post_like':
if (action === 'undo' && link) {
this.socialTasks.vk.nameLinks.push(`${link}&action=like`);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.vk.nameLinks.push(`${link}&action=like`);
}
break;
case 'discord_server_sub':
if (action === 'undo' && link) {
this.socialTasks.discord.serverLinks.push(link);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.discord.serverLinks.push(link);
}
break;
case 'youtube_channel_sub':
if (action === 'undo' && link) {
this.socialTasks.youtube.channelLinks.push(link);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.youtube.channelLinks.push(link);
}
break;
case 'steam_game_playtime':
if (action === 'undo' && link) {
this.socialTasks.steam.playTimeLinks.push(`${title.match(/(\d+)\s*min/)?.[1] || '0'}-${link}`);
}
if (action === 'do' && !isSuccess && link) {
this.undoneTasks.steam.playTimeLinks.push(`${title.match(/(\d+)\s*min/)?.[1] || '0'}-${link}`);
}
break;
case 'telegram_channel_sub':
debug('跳过 Telegram 任务');
echoLog({}).warning(`${I18n('tgTaskNotice')}`);
break;
case 'none':
debug('跳过未连接的任务', {
type: type
});
echoLog({}).warning(`${I18n('notConnect', type)}`);
break;
default:
debug('未知任务类型', {
type: type
});
echoLog({}).warning(`${I18n('unKnownTaskType', type)}`);
break;
}
} catch (error) {
debug('处理任务失败', {
error: error
});
throwError(error, 'FreeAnyWhere.processTask');
}
}
async verifyTask() {
try {
debug('开始验证任务');
if (!this.initialized && !await this.init()) {
debug('未初始化');
return false;
}
if (this.tasks.length === 0 && !await this.classifyTask('verify')) {
debug('任务列表为空', this.tasks);
return false;
}
debug('开始验证任务列表', {
tasks: this.tasks
});
const pro = [];
for (const task of this.tasks) {
pro.push(this.#verify(task));
await delay(1e3);
}
const result = await Promise.allSettled(pro);
debug('任务验证结果', {
result: result
});
echoLog({}).success(I18n('allTasksComplete'));
if (result.every((item => item.status === 'fulfilled' && item.value === true))) {
return !!await this.getKey(true);
}
return false;
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Freeanywhere.verifyTask');
return false;
}
}
async extraDoTask({website: website}) {
try {
debug('执行额外任务', {
website: website
});
const promises = website.map((link => this.#doVisitWebsite(link)));
const results = await Promise.allSettled(promises);
debug('额外任务执行结果', {
results: results
});
return true;
} catch (error) {
debug('执行额外任务失败', {
error: error
});
throwError(error, 'FreeAnyWhere.extraDoTask');
return false;
}
}
async #doVisitWebsite(link) {
try {
debug('访问网站', {
link: link
});
const logStatus = echoLog({
text: I18n('visitingLink')
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://freeanywhere.net/php/task_site_visit_done.php',
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: link
});
if (result !== 'Success') {
debug('访问失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.responseText.indexOf('bad') !== -1 || data?.responseText.length > 50) {
debug('访问响应异常', {
responseText: data?.responseText
});
logStatus.error(data?.responseText);
return false;
}
debug('访问成功');
logStatus.success();
return true;
} catch (error) {
debug('访问网站失败', {
error: error
});
throwError(error, 'FreeAnyWhere.doVisitWebsite');
return false;
}
}
async getKey(initialized) {
try {
debug('开始获取密钥', {
initialized: initialized
});
if (!initialized && !this.initialized && !await this.init()) {
debug('未初始化');
return false;
}
const logStatus = echoLog({
text: I18n('gettingKey')
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://freeanywhere.net/php/user_get_key.php',
method: 'POST'
});
if (result !== 'Success') {
debug('获取密钥失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.responseText.indexOf('bad') !== -1 || data?.responseText.length > 50) {
debug('密钥响应异常', {
responseText: data?.responseText
});
logStatus.error(data?.responseText);
return false;
}
debug('获取密钥成功', {
key: data.responseText
});
logStatus.success();
echoLog({}).success(data.responseText);
return data.responseText;
} catch (error) {
debug('获取密钥失败', {
error: error
});
throwError(error, 'FreeAnyWhere.getKey');
return false;
}
}
async #verify(task) {
try {
if ($('.task-check-extension').length > 0) {
return this.#verifyWithExtension(task);
}
return this.#verifyWithoutExtension(task);
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Freeanywhere.verify');
return false;
}
}
async #verifyWithExtension(task) {
try {
await this.#updateUserData();
debug('验证任务', {
task: task
});
const logStatus = echoLog({
text: `${I18n('verifyingTask')}${task.title.trim()}...`
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://freeanywhere.net/php/extension/user_task_update.php',
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: `id=${task.id}&type=${task.type}${task.data && task.data !== 'none' ? `&data=${task.data}` : ''}`
});
if (result !== 'Success' || !data?.responseText) {
debug('验证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
const response = data.responseText.trim();
if (response !== 'good') {
debug('验证响应异常', {
response: response,
statusText: data?.statusText,
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('验证成功');
logStatus.success();
return true;
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Freeanywhere.verifyWithExtension');
return false;
}
}
async #verifyWithoutExtension(task) {
try {
debug('验证任务', {
task: task
});
const logStatus = echoLog({
text: `${I18n('verifyingTask')}${task.title.trim()}...`
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://freeanywhere.net/php/user_task_update.php',
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: `id=${task.id}&type=${task.type}${task.data && task.data !== 'none' ? `&data=${task.data}` : ''}`
});
if (result !== 'Success' || !data?.responseText) {
debug('验证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
const response = data.responseText.trim();
if (response !== 'good') {
debug('验证响应异常', {
response: response,
statusText: data?.statusText,
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('验证成功');
logStatus.success();
return true;
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Freeanywhere.verifyWithoutExtension');
return false;
}
}
async #updateUserData() {
try {
let postData = '';
const userData = GM_getValue('FAW_STORAGE') || {};
if (Object.keys(userData).length === 0 || !userData.tasks || !userData.user || !userData.games || !userData.settings) {
if (!this.games) {
await this.#userGamesGet();
}
if (!this.games) {
debug('获取用户游戏失败');
return false;
}
postData = `extension=${encodeURIComponent(JSON.stringify({
games: this.games,
settings: {
game_update: Math.floor(Date.now() / 1e3)
},
tasks: {},
user: {
avatar: $('header.games_for_farm_site').attr('data-avatar'),
lang: $('header.games_for_farm_site').attr('data-lang'),
name: $('header.games_for_farm_site').attr('data-name'),
steam: $('header.games_for_farm_site').attr('data-steam')
}
}))}`;
} else {
postData = `extension=${encodeURIComponent(JSON.stringify(userData))}`;
}
debug('更新用户数据');
const logStatus = echoLog({
text: `${I18n('updatingUserData')}`
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://freeanywhere.net/php/extension/user_data_update.php',
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: postData
});
if (data?.status !== 200) {
debug('验证请求失败', {
result: result,
statusText: statusText,
status: status,
data: data
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
debug('验证成功');
logStatus.success();
return true;
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Freeanywhere.updateUserData');
return false;
}
}
async #userGamesGet() {
try {
debug('获取用户游戏');
const logStatus = echoLog({
text: `${I18n('gettingUserGames')}`
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://freeanywhere.net/php/extension/user_games_get.php',
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: `steam=${$('header.games_for_farm_site').attr('data-steam')}`,
dataType: 'json'
});
if (result !== 'Success' || data?.status !== 200 || !data?.responseText) {
debug('验证请求失败', {
result: result,
statusText: statusText,
status: status,
data: data
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
debug('验证成功');
this.games = data.response;
logStatus.success();
return true;
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Freeanywhere.userGamesGet');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const giveawayStatus = $('div.card-info__lable-info').text()?.includes('Giveaway ended');
debug('Giveaway状态', {
giveawayStatus: giveawayStatus
});
if (!giveawayStatus) {
return true;
}
debug('没有剩余密钥,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('giveawayEnded'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'FreeAnyWhere.checkLeftKey');
return false;
}
}
#getGiveawayId() {
try {
debug('开始获取抽奖ID');
const giveawayId = $('link[rel="canonical"]').attr('href')?.match(/n=([\d]+)/)?.[1];
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({}).error(I18n('getFailed', 'GiveawayId'));
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'FreeAnyWhere.getGiveawayId');
return false;
}
}
}
const defaultTasks$7 = {
steam: {
groupLinks: [],
wishlistLinks: [],
curatorLinks: [],
curatorLikeLinks: [],
followLinks: [],
forumLinks: [],
announcementLinks: [],
workshopVoteLinks: [],
playtestLinks: [],
playTimeLinks: []
},
discord: {
serverLinks: []
},
vk: {
nameLinks: []
},
twitch: {
channelLinks: []
},
reddit: {
redditLinks: []
},
youtube: {
channelLinks: [],
likeLinks: []
},
twitter: {
userLinks: [],
retweetLinks: []
}
};
class GiveawaySu extends Website {
name='GiveawaySu';
socialTasks=defaultTasks$7;
undoneTasks=defaultTasks$7;
buttons=[ 'doTask', 'undoTask' ];
static test() {
const url = window.location.href;
const isMatch = /^https?:\/\/giveaway\.su\/giveaway\/view\/[\d]+/.test(url);
debug('检查网站匹配', {
url: url,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('登录检查失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
debug('显示网站通知');
echoLog({}).warning(I18n('gsNotice'));
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'Giveawaysu.after');
}
}
init() {
try {
debug('初始化 GiveawaySu');
const logStatus = echoLog({
text: I18n('initing')
});
if ($('a.steam-login').length > 0) {
debug('发现未登录状态,重定向到 Steam 登录');
window.open('/steam/redirect', '_self');
logStatus.warning(I18n('needLogin'));
return false;
}
const giveawayIdResult = this.#getGiveawayId();
if (!giveawayIdResult) {
debug('获取抽奖ID失败');
return false;
}
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'Giveawaysu.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('恢复已保存的任务信息');
this.socialTasks = GM_getValue(`gasTasks-${this.giveawayId}`)?.tasks || defaultTasks$7;
return true;
}
const tasks = $('#actions tr');
if (!tasks.length) {
debug('未找到任务');
logStatus.warning(I18n('noTasks'));
return true;
}
debug('检查并处理 Discord 和 Twitch 绑定');
if ($('div.bind-discord').is(':visible')) {
debug('点击 Discord 绑定按钮');
$('div.bind-discord a')[0]?.click();
}
if ($('div.bind-twitch').is(':visible')) {
debug('点击 Twitch 绑定按钮');
$('div.bind-twitch a')[0]?.click();
}
const processTask = async task => {
const td = $(task).find('td:not(".hidden")');
const colorfulTask = td.eq(1).find('a:not([data-trigger="link"])');
const colorlessTask = td.eq(2).find('a:not([data-trigger="link"])');
const taskDes = colorfulTask.length > 0 ? colorfulTask : colorlessTask;
if (!taskDes.length) {
debug('跳过无效任务');
return true;
}
const taskIcon = td.eq(0).find('i').attr('class') || '';
const taskName = taskDes.text().trim();
const taskHref = taskDes.attr('href');
debug('处理任务', {
taskIcon: taskIcon,
taskName: taskName,
taskHref: taskHref
});
if (taskIcon.includes('ban') || /disable adblock/gi.test(taskName)) {
debug('跳过禁用任务');
return true;
}
if (!taskHref) {
debug('任务链接为空');
return false;
}
try {
debug('获取重定向链接');
const taskLink = await getRedirectLink(taskHref);
if (!taskLink) {
debug('获取重定向链接失败');
return false;
}
debug('分类任务', {
taskLink: taskLink,
taskIcon: taskIcon,
taskName: taskName
});
this.#classifyTaskByType(taskLink, taskIcon, taskName);
return true;
} catch (error) {
debug('获取重定向链接失败', {
error: error
});
throwError(error, 'Giveawaysu.classifyTask->getRedirectLink');
return false;
}
};
debug('开始处理所有任务');
const results = await Promise.all(Array.from(tasks).map(processTask));
const success = results.some((result => result));
if (!success) {
debug('所有任务处理失败');
logStatus.error(I18n('allTasksFailed'));
return false;
}
debug('任务处理完成');
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.undoneTasks;
debug('保存任务信息');
GM_setValue(`gasTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Giveawaysu.classifyTask');
return false;
}
}
static TASK_PATTERNS={
wishlist: /wishlist.*game|add.*wishlist/gim,
follow: /follow.*button/gim,
twitter: /(on twitter)|(Follow.*on.*Facebook)/gim,
vkGroup: /join.*vk.*group/gim,
youtubeVideo: /(watch|like).*video/gim,
youtubeChannel: /subscribe.*youtube.*channel/gim,
watchArt: /watch.*art/gim,
reddit: /subscribe.*subreddit|follow.*reddit/gim,
twitchChannel: /follow.*twitch.*channel/gim,
instagram: /follow.*instagram/gim,
discord: /join.*discord/gim,
playtest: /request.*playtest/gim,
steamForum: /subscribe.*steam.*forum/gim,
curator: /(follow|subscribe).*curator/gim,
curatorLink: /^https?:\/\/store\.steampowered\.com\/curator\//,
announcement: /like.*announcement/gim,
steamGroup: /join/gi
};
#classifyTaskByType(taskLink, taskIcon, taskName) {
try {
debug('开始分类任务', {
taskLink: taskLink,
taskIcon: taskIcon,
taskName: taskName
});
const {TASK_PATTERNS: TASK_PATTERNS} = GiveawaySu;
if (taskIcon.includes('steam') && TASK_PATTERNS.steamGroup.test(taskName)) {
debug('添加 Steam 组任务');
this.undoneTasks.steam.groupLinks.push(taskLink);
return;
}
if (TASK_PATTERNS.announcement.test(taskName)) {
debug('添加 Steam 公告任务');
this.undoneTasks.steam.announcementLinks.push(taskLink);
return;
}
if (TASK_PATTERNS.curator.test(taskName) && TASK_PATTERNS.curatorLink.test(taskLink)) {
debug('添加 Steam 鉴赏家关注任务');
this.undoneTasks.steam.curatorLinks.push(taskLink);
return;
}
if (taskIcon.includes('steam') && /follow|subscribe/gim.test(taskName)) {
debug('添加 Steam 鉴赏家点赞任务');
this.undoneTasks.steam.curatorLikeLinks.push(taskLink);
return;
}
if (TASK_PATTERNS.steamForum.test(taskName)) {
debug('添加 Steam 论坛任务');
this.undoneTasks.steam.forumLinks.push(taskLink);
return;
}
if (taskIcon.includes('thumbs-up') && /^https?:\/\/steamcommunity\.com\/sharedfiles\/filedetails\/\?id=[\d]+/.test(taskLink)) {
debug('添加 Steam 创意工坊投票任务');
this.undoneTasks.steam.workshopVoteLinks.push(taskLink);
return;
}
if (taskIcon.includes('plus') && TASK_PATTERNS.playtest.test(taskName)) {
debug('添加 Steam 游戏测试任务');
this.undoneTasks.steam.playtestLinks.push(taskLink);
return;
}
if (taskIcon.includes('discord') || TASK_PATTERNS.discord.test(taskName)) {
debug('添加 Discord 服务器任务');
this.undoneTasks.discord.serverLinks.push(taskLink);
return;
}
if (taskIcon.includes('instagram') || TASK_PATTERNS.instagram.test(taskName)) {
debug('跳过 Instagram 任务');
return;
}
if (taskIcon.includes('twitch') || TASK_PATTERNS.twitchChannel.test(taskName)) {
debug('添加 Twitch 频道任务');
this.undoneTasks.twitch.channelLinks.push(taskLink);
return;
}
if (taskIcon.includes('reddit') || TASK_PATTERNS.reddit.test(taskName)) {
debug('添加 Reddit 任务');
this.undoneTasks.reddit.redditLinks.push(taskLink);
return;
}
if (TASK_PATTERNS.watchArt.test(taskName)) {
debug('添加创意工坊物品任务');
this.undoneTasks.steam.workshopVoteLinks.push(taskLink);
return;
}
if (TASK_PATTERNS.youtubeChannel.test(taskName)) {
debug('添加 YouTube 频道任务');
this.undoneTasks.youtube.channelLinks.push(taskLink);
return;
}
if (TASK_PATTERNS.youtubeVideo.test(taskName) || (taskIcon.includes('youtube') || taskIcon.includes('thumbs-up')) && TASK_PATTERNS.youtubeVideo.test(taskName)) {
debug('添加 YouTube 视频任务');
this.undoneTasks.youtube.likeLinks.push(taskLink);
return;
}
if (taskIcon.includes('vk') || TASK_PATTERNS.vkGroup.test(taskName)) {
debug('添加 VK 任务');
this.undoneTasks.vk.nameLinks.push(taskLink);
return;
}
if (TASK_PATTERNS.twitter.test(taskName)) {
debug('跳过 Twitter 任务');
return;
}
if (TASK_PATTERNS.wishlist.test(taskName)) {
debug('添加 Steam 愿望单任务');
this.undoneTasks.steam.wishlistLinks.push(taskLink);
}
if (TASK_PATTERNS.follow.test(taskName)) {
debug('添加 Steam 关注任务');
this.undoneTasks.steam.followLinks.push(taskLink);
return;
}
debug('未识别的任务类型', {
taskLink: taskLink,
taskIcon: taskIcon,
taskName: taskName
});
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Giveawaysu.classifyTaskByType');
}
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
const needLogin = $('a.steam-login').length > 0;
if (needLogin) {
debug('未登录,重定向到 Steam 登录');
window.open('/steam/redirect', '_self');
}
debug('登录检查完成', {
needLogin: needLogin
});
return true;
} catch (error) {
debug('登录检查失败', {
error: error
});
throwError(error, 'Giveawaysu.checkLogin');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const isEnded = $('.giveaway-ended').length > 0;
const hasNoKeys = $('.giveaway-key').length === 0;
debug('检查抽奖状态', {
isEnded: isEnded,
hasNoKeys: hasNoKeys
});
if (!(isEnded && hasNoKeys)) {
return true;
}
debug('没有剩余密钥,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('noKeysLeft'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'Giveawaysu.checkLeftKey');
return false;
}
}
#getGiveawayId() {
try {
debug('从URL获取抽奖ID');
const giveawayId = window.location.href.match(/\/view\/([\d]+)/)?.[1];
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({
text: I18n('getFailed', 'GiveawayId')
});
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'Giveawaysu.getGiveawayId');
return false;
}
}
}
class Indiedb {
name='Indiedb';
buttons=[ 'doTask' ];
static test() {
const {host: host} = window.location;
const isMatch = host === 'www.indiedb.com';
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('检查登录失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'Indiedb.after');
}
}
async doTask() {
try {
debug('开始执行任务');
if (!await this.#join()) {
debug('加入抽奖失败');
return false;
}
return await this.#do();
} catch (error) {
debug('执行任务失败', {
error: error
});
throwError(error, 'Indiedb.doTask');
return false;
}
}
async #join() {
try {
debug('开始加入抽奖');
if ($('a.buttonenter:contains(Register to join)').length > 0) {
debug('需要登录');
echoLog({}).error(I18n('needLogin'));
return false;
}
const currentoption = $('a.buttonenter.buttongiveaway');
const buttonText = currentoption.text();
debug('检查按钮状态', {
buttonText: buttonText
});
if (/success/gim.test($('a.buttonenter.buttongiveaway').text())) {
debug('已成功加入抽奖');
return true;
}
if (!/join giveaway/gim.test(buttonText)) {
debug('需要加入抽奖');
echoLog({}).warning(I18n('needJoinGiveaway'));
return false;
}
const logStatus = echoLog({
text: `${I18n('joiningGiveaway')}...`
});
debug('发送加入请求');
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: currentoption.attr('href'),
method: 'POST',
data: 'ajax=t',
dataType: 'json',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Accept: 'application/json, text/javascript, */*; q=0.01',
Origin: window.location.origin,
referer: window.location.href
}
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('尝试备用加入方法');
if (await this.#join2()) {
debug('备用加入方法成功');
logStatus.success('Success');
return true;
}
debug('加入失败', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
if (!data.response?.success) {
debug('响应失败', {
text: data.response?.text
});
logStatus.error(`Error${data.response?.text ? `:${data.response?.text}` : ''}`);
return false;
}
debug('加入成功');
currentoption.addClass('buttonentered').text('Success - Giveaway joined');
$('#giveawaysjoined').slideDown();
$('#giveawaysrecommend').slideDown();
logStatus.success(`Success${data.response?.text ? `:${data.response?.text}` : ''}`);
return true;
} catch (error) {
debug('加入抽奖失败', {
error: error
});
throwError(error, 'Indiedb.join');
return false;
}
}
async #join2() {
try {
debug('开始备用加入方法');
return await new Promise((resolve => {
const targetNode = document.getElementById('giveawaysjoined');
const config = {
attributes: true
};
const observer = new MutationObserver((() => {
if ($('#giveawaysjoined').is(':visible')) {
debug('检测到加入成功');
resolve(true);
observer.disconnect();
}
}));
observer.observe(targetNode, config);
debug('点击加入按钮');
$('a.buttonenter.buttongiveaway')[0]?.click();
setTimeout((() => {
debug('加入超时');
resolve(false);
observer.disconnect();
}), 3e4);
}));
} catch (error) {
debug('备用加入方法失败', {
error: error
});
throwError(error, 'Indiedb.join2');
return false;
}
}
async #do() {
try {
debug('开始执行任务');
const id = $('script').map(((index, script) => {
if (!/\$\(document\)/gim.test(script.innerHTML)) {
return null;
}
return [ script.innerHTML.match(/"\/[\d]+"/gim)?.[0]?.match(/[\d]+/)?.[0], script.innerHTML.match(/"\/newsletter\/ajax\/subscribeprofile\/optin\/[\d]+"/gim)?.[0]?.match(/[\d]+/)?.[0] ];
}));
if (id.length < 2) {
debug('获取任务ID失败');
echoLog({}).error(I18n('getFailed', 'TaskId'));
return false;
}
const pro = [];
const tasks = $('#giveawaysjoined a[class*=promo]');
debug('找到任务', {
count: tasks.length
});
for (const task of tasks) {
const promo = $(task);
if (promo.hasClass('buttonentered')) {
debug('跳过已完成任务');
continue;
}
const taskText = promo.parents('p').text();
debug('处理任务', {
taskText: taskText
});
const status = echoLog({
text: `${I18n('doing')}:${taskText}...`
});
if (/the-challenge-of-adblock/gim.test(promo.attr('href'))) {
debug('跳过未知任务类型');
status.error(`Error:${I18n('unKnownTaskType')}`);
continue;
}
if (/facebookpromo|twitterpromo|visitpromo/gim.test(task.className)) {
let text = '';
if (promo.hasClass('facebookpromo')) {
text = 'facebookpromo';
} else if (promo.hasClass('twitterpromo')) {
text = 'twitterpromo';
} else {
text = 'visitpromo';
}
debug('处理社交媒体任务', {
type: text
});
pro.push(this.#handleSocialPromo(text, id[0], status, promo));
} else if (promo.hasClass('emailoptinpromo')) {
debug('处理邮件订阅任务');
pro.push(this.#handleEmailPromo(id[1], status, promo));
} else if (promo.hasClass('watchingpromo')) {
debug('处理关注任务');
pro.push(this.#handleWatchingPromo(promo, status));
} else {
debug('处理默认任务');
pro.push(this.#handleDefaultPromo(promo, status));
}
}
await Promise.all(pro);
debug('所有任务完成');
echoLog({}).success(I18n('allTasksComplete'));
return true;
} catch (error) {
debug('执行任务失败', {
error: error
});
throwError(error, 'Indiedb.do');
return false;
}
}
async #handleSocialPromo(text, id, status, promo) {
try {
debug('处理社交媒体任务', {
text: text,
id: id
});
return await new Promise((resolve => {
$.ajax({
type: 'POST',
url: urlPath(`/giveaways/ajax/${text}/${id}`),
timeout: 6e4,
dataType: 'json',
data: {
ajax: 't'
},
error(response, error, exception) {
debug('请求失败', {
response: response,
error: error,
exception: exception
});
if (window.DEBUG) {
console.log('%cAuto-Task[Debug]:', 'color:red', {
response: response,
error: error,
exception: exception
});
}
status.error('Error:An error has occurred performing the action requested. Please try again shortly.');
resolve(true);
},
success(response) {
if (response.success) {
debug('任务完成', {
response: response
});
status.success(`Success:${response.text}`);
promo.addClass('buttonentered').closest('p').html(promo.closest('p').find('span').html());
} else {
debug('任务失败', {
response: response
});
status.error(`Error:${response.text}`);
}
resolve(true);
}
});
}));
} catch (error) {
debug('处理社交媒体任务失败', {
error: error
});
throwError(error, 'Indiedb.handleSocialPromo');
return false;
}
}
async #handleEmailPromo(id, status, promo) {
try {
debug('处理邮件订阅任务', {
id: id
});
return await new Promise((resolve => {
$.ajax({
type: 'POST',
url: urlPath(`/newsletter/ajax/subscribeprofile/optin/${id}`),
timeout: 6e4,
dataType: 'json',
data: {
ajax: 't',
emailsystoggle: 4
},
error(response, error, exception) {
debug('请求失败', {
response: response,
error: error,
exception: exception
});
if (window.DEBUG) {
console.log('%cAuto-Task[Debug]:', 'color:red', {
response: response,
error: error,
exception: exception
});
}
status.error('Error:An error has occurred performing the action requested. Please try again shortly.');
resolve(true);
},
success(response) {
if (response.success) {
debug('任务完成', {
response: response
});
status.success(`Success:${response.text}`);
promo.toggleClass('buttonentered').closest('p').html(promo.closest('p').find('span').html());
} else {
debug('任务失败', {
response: response
});
status.error(`Error:${response.text}`);
}
resolve(true);
}
});
}));
} catch (error) {
debug('处理邮件订阅任务失败', {
error: error
});
throwError(error, 'Indiedb.handleEmailPromo');
return false;
}
}
async #handleWatchingPromo(promo, status) {
try {
debug('处理关注任务');
return await new Promise((resolve => {
const href = promo.attr('href');
if (!href) {
debug('无效的链接');
status.error('Error: Invalid href');
resolve(true);
return;
}
const data = getUrlQuery(href);
data.ajax = 't';
const [baseUrl] = href.split(/[?#]/);
if (!baseUrl) {
debug('无效的URL');
status.error('Error: Invalid URL');
resolve(true);
return;
}
debug('发送请求', {
url: baseUrl,
data: data
});
$.ajax({
type: 'POST',
url: urlPath(baseUrl),
timeout: 6e4,
dataType: 'json',
data: data,
error(response, error, exception) {
debug('请求失败', {
response: response,
error: error,
exception: exception
});
if (window.DEBUG) {
console.log('%cAuto-Task[Debug]:', 'color:red', {
response: response,
error: error,
exception: exception
});
}
status.error('Error:An error has occurred performing the action requested. Please try again shortly.');
resolve(true);
},
success(response) {
if (response.success) {
debug('任务完成', {
response: response
});
status.success(`Success:${response.text}`);
promo.toggleClass('buttonentered').closest('p').html(promo.closest('p').find('span').html());
} else {
debug('任务失败', {
response: response
});
status.error(`Error:${response.text}`);
}
resolve(true);
}
});
}));
} catch (error) {
debug('处理关注任务失败', {
error: error
});
throwError(error, 'Indiedb.handleWatchingPromo');
return false;
}
}
async #handleDefaultPromo(promo, status) {
try {
debug('处理默认任务');
return await new Promise((resolve => {
const href = promo.attr('href');
if (!href) {
debug('无效的链接');
status.error('Error: Invalid href');
resolve(true);
return;
}
debug('发送请求', {
url: href
});
$.ajax({
type: 'POST',
url: urlPath(href),
timeout: 6e4,
dataType: 'json',
data: {
ajax: 't'
},
error(response, error, exception) {
debug('请求失败', {
response: response,
error: error,
exception: exception
});
if (window.DEBUG) {
console.log('%cAuto-Task[Debug]:', 'color:red', {
response: response,
error: error,
exception: exception
});
}
status.error('Error:An error has occurred performing the action requested. Please try again shortly.');
resolve(true);
},
success(response) {
if (response.success) {
debug('任务完成', {
response: response
});
status.success(`Success:${response.text}`);
promo.toggleClass('buttonentered').closest('p').html(promo.closest('p').find('span').html());
} else {
debug('任务失败', {
response: response
});
status.error(`Error:${response.text}`);
}
resolve(true);
}
});
}));
} catch (error) {
debug('处理默认任务失败', {
error: error
});
throwError(error, 'Indiedb.handleDefaultPromo');
return false;
}
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
if ($('a.buttonenter:contains(Register to join)').length > 0) {
debug('未登录,重定向到登录页面');
window.open('/members/login', '_self');
}
debug('登录检查完成');
return true;
} catch (error) {
debug('检查登录失败', {
error: error
});
throwError(error, 'Indiedb.checkLogin');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const hasEndedButton = $('a.buttonenter:contains("next time"), a.buttonenter:contains("Giveaway is closed")').length > 0;
debug('检查抽奖状态', {
hasEndedButton: hasEndedButton
});
if (!hasEndedButton) {
return true;
}
debug('抽奖已结束,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('giveawayEnded'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'Indiedb.checkLeftKey');
return false;
}
}
}
const defaultTasksTemplate$5 = {
steam: {
groupLinks: [],
officialGroupLinks: [],
wishlistLinks: [],
curatorLinks: []
},
discord: {
serverLinks: []
},
extra: {
videoTasks: []
},
links: []
};
const defaultTasks$6 = JSON.stringify(defaultTasksTemplate$5);
class Keyhub extends Website {
name='Keyhub';
socialTasks=JSON.parse(defaultTasks$6);
undoneTasks=JSON.parse(defaultTasks$6);
buttons=[ 'doTask', 'undoTask' ];
static test() {
const {host: host} = window.location;
const isMatch = host === 'key-hub.eu';
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('检查登录失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
debug('隐藏 NSFW 内容');
$('.NSFW').hide();
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'Keyhub.after');
}
}
init() {
try {
debug('开始初始化');
const logStatus = echoLog({
text: I18n('initing')
});
if ($('a[href*="/connect/steam"]').length > 0) {
debug('需要登录 Steam');
window.open('/connect/steam', '_self');
logStatus.warning(I18n('needLogin'));
return false;
}
if (!this.#getGiveawayId()) {
debug('获取抽奖ID失败');
return false;
}
debug('隐藏 VPN 覆盖层');
$('#VPNoverlay').hide();
$('#mainArticleSection').show();
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'Keyhub.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('恢复已保存的任务信息');
this.socialTasks = GM_getValue(`khTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$6);
}
const tasks = $('.task:not(".googleads")').filter(((index, element) => action === 'do' ? $(element).find('i.fa-check-circle:visible').length === 0 : true)).find('a');
debug('找到任务', {
count: tasks.length
});
for (const task of tasks) {
let link = $(task).attr('href');
const taskDes = $(task).text().trim();
debug('处理任务', {
taskDes: taskDes,
link: link
});
if (!link) {
debug('跳过无链接任务');
continue;
}
if (/\/away\?data=/.test(link) || /steamcommunity\.com\/gid\//.test(link)) {
debug('获取重定向链接');
link = await getRedirectLink(link) || link;
}
if (/https?:\/\/key-hub\.eu\/connect\/discord/.test(link)) {
debug('处理 Discord 连接任务');
GM_openInTab(link, {
active: true
});
continue;
}
if (/steamcommunity\.com\/groups\//.test(link)) {
debug('处理 Steam 组任务');
if (action === 'undo') {
this.socialTasks.steam.groupLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.steam.groupLinks.push(link);
}
continue;
}
if (/steamcommunity\.com\/games\/[\d]+/.test(link)) {
debug('处理 Steam 官方组任务');
if (action === 'undo') {
this.socialTasks.steam.officialGroupLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.steam.officialGroupLinks.push(link);
}
continue;
}
if (/store\.steampowered\.com\/app\//.test(link) && /wishlist/gim.test(taskDes)) {
debug('处理 Steam 愿望单任务');
if (action === 'undo') {
this.socialTasks.steam.wishlistLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.steam.wishlistLinks.push(link);
}
continue;
}
if (/store\.steampowered\.com\/curator\//.test(link)) {
debug('处理 Steam 鉴赏家任务');
if (action === 'undo') {
this.socialTasks.steam.curatorLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.steam.curatorLinks.push(link);
}
continue;
}
if (/^https?:\/\/discord\.com\/invite\//.test(link)) {
debug('处理 Discord 服务器任务');
if (action === 'undo') {
this.socialTasks.discord.serverLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.discord.serverLinks.push(link);
}
continue;
}
if (/^javascript:videoTask.+/.test(link)) {
debug('处理视频任务');
if (action === 'do') {
const taskData = link.match(/javascript:videoTask\('.+?','(.+?)'/)?.[1];
if (taskData) {
debug('添加视频任务', {
taskData: taskData
});
this.undoneTasks.extra.videoTasks.push(taskData);
}
}
continue;
}
if (this.#isSkippableLink(link)) {
debug('跳过可忽略的链接', {
link: link
});
continue;
}
debug('未知任务类型', {
taskDes: taskDes,
link: link
});
echoLog({}).warning(`${I18n('unKnownTaskType')}: ${taskDes}(${link})`);
}
debug('任务分类完成');
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.uniqueTasks(this.socialTasks);
if (window.DEBUG) {
console.log('%cAuto-Task[Debug]:', 'color:blue', JSON.stringify(this));
}
debug('保存任务信息');
GM_setValue(`khTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Keyhub.classifyTask');
return false;
}
}
#isSkippableLink(link) {
return /^https?:\/\/www\.instagram\.com\/.*/.test(link) || /^https?:\/\/twitter\.com\/.*/.test(link) || /^https?:\/\/www\.twitch\.tv\/.*/.test(link) || /^https?:\/\/www\.facebook\.com\/.*/.test(link) || /^https?:\/\/www\.youtube\.com\/.*/.test(link) || /^https?:\/\/store\.steampowered\.com\/developer\//.test(link) || /^https?:\/\/.*?\.itch\.io\/.*/.test(link) || /^https?:\/\/key-hub\.eu.*/.test(link) || /^https?:\/\/store\.steampowered\.com\/app\/.*/.test(link) || /^https?:\/\/qr\.streamelements\.com\/.*/.test(link) || /^https?:\/\/store\.steampowered\.com\/news\/app\/.*/.test(link);
}
async #doScriptTask(data) {
try {
debug('执行脚本任务', {
data: data
});
const logStatus = echoLog({
text: I18n('doingKeyhubTask')
});
const {result: result, statusText: statusText, status: status, data: response} = await httpRequest({
url: `/away?data=${data}`,
method: 'GET',
headers: {
origin: 'https://key-hub.eu',
referer: 'https://key-hub.eu/'
}
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (response?.status !== 200) {
debug('响应错误', {
status: response?.status,
statusText: response?.statusText
});
logStatus.error(`Error:${response?.statusText}(${response?.status})`);
return false;
}
debug('任务完成');
logStatus.success();
return true;
} catch (error) {
debug('执行脚本任务失败', {
error: error
});
throwError(error, 'Keyhub.doScriptTask');
return false;
}
}
async extraDoTask({videoTasks: videoTasks}) {
try {
debug('开始执行额外任务', {
count: videoTasks.length
});
const pro = [];
for (const data of videoTasks) {
pro.push(this.#doScriptTask(data));
}
return Promise.all(pro).then((() => {
debug('所有额外任务完成');
return true;
}));
} catch (error) {
debug('执行额外任务失败', {
error: error
});
throwError(error, 'Keyhub.extraDoTask');
return false;
}
}
#getGiveawayId() {
try {
debug('获取抽奖ID');
const giveawayId = window.location.href.match(/giveaway\/([\d]+)/)?.[1];
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({}).error(I18n('getFailed', 'GiveawayId'));
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'Keyhub.getGiveawayId');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const leftKey = $('#keysleft').text().trim();
debug('检查剩余密钥数量', {
leftKey: leftKey
});
if (leftKey !== '0') {
return true;
}
debug('没有剩余密钥,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('noKeysLeft'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'Keyhub.checkLeftKey');
return false;
}
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
if ($('a[href*="/connect/steam"]').length > 0) {
debug('未登录,重定向到 Steam 登录页面');
window.open('/connect/steam', '_self');
}
debug('登录检查完成');
return true;
} catch (error) {
debug('检查登录失败', {
error: error
});
throwError(error, 'Keyhub.checkLogin');
return false;
}
}
}
const defaultTasksTemplate$4 = {
steam: {
groupLinks: [],
wishlistLinks: [],
curatorLinks: [],
curatorLikeLinks: []
},
twitter: {
userLinks: []
},
vk: {
nameLinks: []
},
discord: {
serverLinks: []
}
};
const defaultTasks$5 = JSON.stringify(defaultTasksTemplate$4);
class Givekey extends Website {
name='Givekey';
tasks=[];
socialTasks=JSON.parse(defaultTasks$5);
undoneTasks=JSON.parse(defaultTasks$5);
userId;
buttons=[ 'doTask', 'undoTask', 'verifyTask' ];
static test() {
const url = window.location.host;
const isMatch = url === 'givekey.ru';
debug('检查网站匹配', {
url: url,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
await new Promise((resolve => {
const checker = setInterval((() => {
if ($('#navbarDropdown').length > 0) {
debug('导航栏元素已加载');
clearInterval(checker);
resolve(true);
}
}), 500);
}));
if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'Givekey.after');
}
}
init() {
try {
debug('初始化 Givekey');
const logStatus = echoLog({
text: I18n('initing')
});
if ($('a[href*="/auth/steam"]').length > 0) {
debug('未登录,重定向到 Steam 登录页面');
window.open('/auth/steam', '_self');
logStatus.warning(I18n('needLogin'));
return false;
}
if (!this.#getGiveawayId()) {
debug('获取抽奖ID失败');
return false;
}
const userId = $('meta[name="user-id"]').attr('content');
if (!userId) {
debug('获取用户ID失败');
logStatus.error(I18n('getFailed', I18n('userId')));
return false;
}
this.userId = userId;
this.initialized = true;
debug('初始化完成', {
userId: userId
});
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'Givekey.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('恢复已保存的任务信息');
this.socialTasks = GM_getValue(`gkTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$5);
}
const tasks = $('.card-body:has("button") .row');
debug('找到任务元素', {
count: tasks.length
});
for (const task of tasks) {
const taskEle = $(task);
const button = taskEle.find('button');
const isSuccess = /Complete/i.test(button.text().trim());
debug('处理任务', {
isSuccess: isSuccess
});
if (isSuccess && action !== 'undo') {
debug('跳过已完成的任务');
continue;
}
const checkButton = taskEle.find('#task_check');
const taskId = checkButton.attr('data-id');
if (taskId) {
debug('添加任务ID', {
taskId: taskId
});
this.tasks.push(taskId);
}
if (action === 'verify') {
continue;
}
const taskLink = taskEle.find('a');
let href = taskLink.attr('href');
if (!href) {
debug('任务链接为空');
continue;
}
const text = taskLink.text().trim();
if (!text) {
debug('任务描述为空');
continue;
}
if (/^https?:\/\/givekey\.ru\/giveaway\/[\d]+\/execution_task/.test(href)) {
debug('获取重定向链接', {
href: href
});
href = await getRedirectLink(href);
}
if (!href) {
debug('获取重定向链接失败');
continue;
}
const icon = taskEle.find('i');
await this.#classifyTaskByType(href, text, icon, isSuccess, action);
}
debug('任务分类完成');
logStatus.success();
this.tasks = unique(this.tasks);
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.uniqueTasks(this.socialTasks);
debug('保存任务信息');
GM_setValue(`gkTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Givekey.classifyTask');
return false;
}
}
async verifyTask() {
try {
debug('开始验证任务');
if (!this.initialized && !this.init()) {
debug('初始化失败');
return false;
}
if (this.tasks.length === 0 && !await this.classifyTask('verify')) {
debug('任务分类失败');
return false;
}
echoLog({}).warning(I18n('giveKeyNoticeBefore'));
const taskLength = this.tasks.length;
debug('开始验证任务', {
taskCount: taskLength
});
for (let i = 0; i < taskLength; i++) {
await this.#verify(this.tasks[i]);
if (i < taskLength - 1) {
debug('等待15秒');
await delay(15e3);
}
}
debug('所有任务验证完成');
echoLog({}).success(I18n('allTasksComplete'));
echoLog({
html: `<li><font class="warning">${I18n('giveKeyNoticeAfter')}</font></li>`
});
return true;
} catch (error) {
debug('任务验证失败', {
error: error
});
throwError(error, 'Givekey.verifyTask');
return false;
}
}
async #verify(task) {
try {
debug('验证任务', {
taskId: task
});
const logStatus = echoLog({
html: `<li>${I18n('verifyingTask')}${task}...<font></font></li>`
});
const csrfToken = $('meta[name="csrf-token"]').attr('content');
if (!csrfToken) {
debug('CSRF token 未找到');
logStatus.error('CSRF token not found');
return false;
}
debug('发送验证请求');
const response = await $.ajax({
url: 'https://givekey.ru/giveaway/task',
method: 'POST',
data: `id=${task}&user_id=${this.userId}`,
dataType: 'json',
headers: {
'X-CSRF-TOKEN': csrfToken
}
});
if (!response) {
debug('未收到响应');
logStatus.error('No response received');
return false;
}
debug('处理响应', {
response: response
});
if (response.btn) {
$(`button[data-id=${this.userId}]`).html(response.btn);
}
if (response.status === 'ok') {
$(`.task_check_${response.id}`).html(`<button class="btn btn-success mb-2 btn-block" disabled>${response.btn}</button>`);
debug('任务验证成功');
logStatus.success();
return true;
}
if (response.status === 'end') {
debug('获得密钥');
logStatus.success();
echoLog({}).success(response.key);
return true;
}
debug('验证失败', {
error: response.msg
});
logStatus.error(`Error:${response.msg}`);
return false;
} catch (error) {
debug('验证过程出错', {
error: error
});
throwError(error, 'Givekey.verify');
return false;
}
}
#getGiveawayId() {
try {
debug('从URL获取抽奖ID');
const giveawayId = window.location.href.match(/giveaway\/([\d]+)/)?.[1];
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({
text: I18n('getFailed', 'GiveawayId')
});
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'Givekey.getGiveawayId');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const keysCount = $('#keys_count').text();
debug('检查密钥数量', {
keysCount: keysCount
});
if (keysCount) {
return true;
}
debug('没有剩余密钥,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('noKeysLeft'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'Givekey.checkLeftKey');
return false;
}
}
async #classifyTaskByType(href, text, icon, isSuccess, action) {
try {
debug('开始分类任务类型', {
href: href,
text: text,
isSuccess: isSuccess,
action: action
});
if (/^https?:\/\/vk\.com\//.test(href)) {
debug('添加 VK 任务');
this.socialTasks.vk.nameLinks.push(href);
if (action === 'do' && !isSuccess) {
this.undoneTasks.vk.nameLinks.push(href);
}
return;
}
if (/^https?:\/\/steamcommunity\.com\/groups/.test(href)) {
debug('添加 Steam 组任务');
this.socialTasks.steam.groupLinks.push(href);
if (action === 'do' && !isSuccess) {
this.undoneTasks.steam.groupLinks.push(href);
}
return;
}
if (/^https?:\/\/store\.steampowered\.com\/app\//.test(href)) {
debug('添加 Steam 愿望单任务');
this.socialTasks.steam.wishlistLinks.push(href);
if (action === 'do' && !isSuccess) {
this.undoneTasks.steam.wishlistLinks.push(href);
}
return;
}
if (/Subscribe/gi.test(text) && icon.hasClass('fa-steam-square')) {
if (/^https?:\/\/store\.steampowered\.com\/curator\//.test(href)) {
debug('添加 Steam 鉴赏家关注任务');
this.socialTasks.steam.curatorLinks.push(href);
if (action === 'do' && !isSuccess) {
this.undoneTasks.steam.curatorLinks.push(href);
}
} else {
debug('添加 Steam 鉴赏家点赞任务');
this.socialTasks.steam.curatorLikeLinks.push(href);
if (action === 'do' && !isSuccess) {
this.undoneTasks.steam.curatorLikeLinks.push(href);
}
}
return;
}
if (/^https?:\/\/twitter\.com\//.test(href) && /Subscribe/gi.test(text)) {
debug('添加 Twitter 关注任务');
this.socialTasks.twitter.userLinks.push(href);
if (action === 'do' && !isSuccess) {
this.undoneTasks.twitter.userLinks.push(href);
}
return;
}
if (icon.hasClass('fa-discord') || /^https?:\/\/discord\.com\/invite\//.test(href)) {
debug('添加 Discord 服务器任务');
this.socialTasks.discord.serverLinks.push(href);
if (action === 'do' && !isSuccess) {
this.undoneTasks.discord.serverLinks.push(href);
}
return;
}
debug('未识别的任务类型', {
href: href,
text: text
});
echoLog({}).warning(`${I18n('unKnownTaskType')}: ${text}(${href})`);
} catch (error) {
debug('任务类型分类失败', {
error: error
});
throwError(error, 'Givekey.classifyTaskByType');
}
}
}
class GiveeClub extends GiveawaySu {
name='GiveeClub';
buttons=[ 'doTask', 'undoTask', 'verifyTask' ];
static test() {
const url = window.location.href;
const isMatch = /^https?:\/\/givee\.club\/.*?\/event\/[\d]+/.test(url);
debug('检查网站匹配', {
url: url,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('登录检查失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'GiveeClub.after');
}
}
init() {
try {
debug('初始化 GiveeClub');
const logStatus = echoLog({
text: I18n('initing')
});
if (!this.#checkLogin()) {
debug('登录检查失败');
logStatus.warning(I18n('needLogin'));
return false;
}
const giveawayIdResult = this.#getGiveawayId();
if (!giveawayIdResult) {
debug('获取抽奖ID失败');
return false;
}
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'GiveeClub.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('恢复已保存的任务信息');
this.socialTasks = GM_getValue(`gcTasks-${this.giveawayId}`)?.tasks || defaultTasks$7;
return true;
}
debug('初始化未完成任务列表');
this.undoneTasks = defaultTasks$7;
const tasks = $('.event-actions tr');
const processTask = async task => {
const taskDes = $(task).find('.event-action-label a');
const taskIcon = $(task).find('.event-action-icon i').attr('class') || '';
const taskName = taskDes.text().trim();
const taskType = $(task).find('button[data-type]')?.attr('data-type') || '';
const taskFinished = $(task).find('.event-action-buttons .btn-success')?.length;
const appId = taskDes.attr('data-steam-wishlist-appid');
debug('处理任务', {
taskName: taskName,
taskType: taskType,
taskIcon: taskIcon,
taskFinished: taskFinished,
appId: appId
});
if (taskIcon.includes('ban') || /AdBlock/i.test(taskName) || taskIcon.includes('envelope') || taskFinished) {
debug('跳过无效或已完成任务');
return true;
}
const taskHref = taskDes.attr('href');
if (!taskHref) {
debug('任务链接为空');
return false;
}
try {
debug('获取重定向链接', {
taskHref: taskHref
});
const taskLink = await getRedirectLink(taskHref, taskType.includes('steam'));
if (!taskLink) {
debug('获取重定向链接失败');
return false;
}
if (taskType === 'steam.game.wishlist' && appId) {
debug('添加 Steam 愿望单任务', {
appId: appId
});
this.undoneTasks.steam.wishlistLinks.push(`https://store.steampowered.com/app/${appId}`);
return true;
}
debug('分类任务', {
taskLink: taskLink,
taskType: taskType
});
this.#classifyTaskByType(taskLink, taskType, taskIcon, taskName, taskDes);
return true;
} catch (error) {
debug('获取重定向链接失败', {
error: error
});
throwError(error, 'GiveeClub.classifyTask->getRedirectLink');
return false;
}
};
debug('开始处理所有任务');
await Promise.all(Array.from(tasks).map(processTask));
debug('任务处理完成');
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.undoneTasks;
debug('保存任务信息');
GM_setValue(`gcTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'GiveeClub.classifyTask');
return false;
}
}
#classifyTaskByType(taskLink, taskType, taskIcon, taskName, taskDes) {
try {
debug('开始分类任务', {
taskLink: taskLink,
taskType: taskType,
taskIcon: taskIcon,
taskName: taskName
});
if (taskType === 'steam.group.join' && /^https?:\/\/steamcommunity\.com\/groups/.test(taskLink)) {
debug('添加 Steam 组任务');
this.undoneTasks.steam.groupLinks.push(taskLink);
return;
}
if (/like.*announcement/gi.test(taskName)) {
debug('添加 Steam 公告任务');
this.undoneTasks.steam.announcementLinks.push(taskLink);
return;
}
if (taskType === 'steam.game.wishlist' && /^https?:\/\/store\.steampowered\.com\/app\//.test(taskLink)) {
debug('添加 Steam 愿望单任务');
this.undoneTasks.steam.wishlistLinks.push(taskLink);
return;
}
if (taskType === 'steam.game.wishlist' && taskDes.attr('data-steam-wishlist-appid')) {
debug('添加 Steam 愿望单任务(通过 appId)');
this.undoneTasks.steam.wishlistLinks.push(`https://store.steampowered.com/app/${taskDes.attr('data-steam-wishlist-appid')}`);
return;
}
if (taskType === 'steam.game.follow' && /^https?:\/\/store\.steampowered\.com\/app\//.test(taskLink)) {
debug('添加 Steam 游戏关注任务');
this.undoneTasks.steam.followLinks.push(taskLink);
return;
}
if (/^https?:\/\/store\.steampowered\.com\/curator\//.test(taskLink)) {
debug('添加 Steam 鉴赏家关注任务');
this.undoneTasks.steam.curatorLinks.push(taskLink);
return;
}
if (taskIcon.includes('steam') && /follow|subscribe/gim.test(taskName)) {
debug('添加 Steam 鉴赏家点赞任务');
this.undoneTasks.steam.curatorLikeLinks.push(taskLink);
return;
}
if (/subscribe.*steam.*forum/gim.test(taskName)) {
debug('添加 Steam 论坛任务');
this.undoneTasks.steam.forumLinks.push(taskLink);
return;
}
if (taskType === 'steam.game.playtime' && /^https?:\/\/store\.steampowered\.com\/app\//.test(taskLink)) {
const time = taskDes.text().match(/(\d+)(?:\.\d+)?/gim)?.[0] || '0';
debug('添加 Steam 游戏时长任务', {
time: time
});
this.undoneTasks.steam.playTimeLinks.push(`${time}-${taskLink}`);
return;
}
if (taskIcon.includes('discord')) {
debug('添加 Discord 服务器任务');
this.undoneTasks.discord.serverLinks.push(taskLink);
return;
}
if (taskIcon.includes('instagram')) {
debug('跳过 Instagram 任务');
return;
}
if (taskIcon.includes('twitch')) {
debug('添加 Twitch 频道任务');
this.undoneTasks.twitch.channelLinks.push(taskLink);
return;
}
if (taskIcon.includes('reddit')) {
debug('添加 Reddit 任务');
this.undoneTasks.reddit.redditLinks.push(taskLink);
return;
}
if (/watch.*art/gim.test(taskName)) {
debug('添加创意工坊物品任务');
this.undoneTasks.steam.workshopVoteLinks.push(taskLink);
return;
}
if (/subscribe.*youtube.*channel/gim.test(taskName)) {
debug('添加 YouTube 频道任务');
this.undoneTasks.youtube.channelLinks.push(taskLink);
return;
}
if (/(watch|like).*youtube.*video/gim.test(taskName) || (taskIcon.includes('youtube') || taskIcon.includes('thumbs-up')) && /(watch|like).*video/gim.test(taskName)) {
debug('添加 YouTube 视频任务');
this.undoneTasks.youtube.likeLinks.push(taskLink);
return;
}
if (taskIcon.includes('vk') || /join.*vk.*group/gim.test(taskName)) {
debug('添加 VK 任务');
this.undoneTasks.vk.nameLinks.push(taskLink);
return;
}
if (taskIcon.includes('twitter')) {
if (/https?:\/\/(twitter|x)\.com\/[^/]+\/?$/gim.test(taskLink)) {
debug('添加 Twitter 用户关注任务');
this.undoneTasks.twitter.userLinks.push(taskLink);
return;
}
if (/https?:\/\/(twitter|x)\.com\/[^/]+?\/status\/[\d]+/gim.test(taskLink)) {
debug('添加 Twitter 转发任务');
this.undoneTasks.twitter.retweetLinks.push(taskLink);
return;
}
}
if (/(on twitter)|(Follow.*on.*Facebook)/gim.test(taskName)) {
debug('跳过 Twitter/Facebook 任务');
return;
}
if (/follow.*button/gim.test(taskName)) {
debug('添加 Steam 关注任务');
this.undoneTasks.steam.followLinks.push(taskLink);
return;
}
debug('未识别的任务类型', {
taskLink: taskLink,
taskType: taskType,
taskIcon: taskIcon,
taskName: taskName
});
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'GiveeClub.classifyTaskByType');
return;
}
}
async verifyTask() {
try {
debug('开始验证任务');
const logStatus = echoLog({
text: I18n('giveeClubVerifyNotice')
});
const taskButtons = $('.event-actions tr button').has('i.glyphicon-refresh').not('[data-type="user.adblock"]');
debug('找到需要验证的任务按钮', {
count: taskButtons.length
});
for (const button of taskButtons) {
debug('点击验证按钮', {
type: $(button).attr('data-type')
});
button.click();
if ($(button).attr('data-type') !== 'steam.game.wishlist') {
debug('等待1秒');
await delay(1e3);
}
}
debug('任务验证完成');
logStatus.warning(I18n('giveeClubVerifyFinished'));
return true;
} catch (error) {
debug('任务验证失败', {
error: error
});
throwError(error, 'Givekey.verifyTask');
return false;
}
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
const needLogin = $('a[href*="/account/auth"]').length > 0;
if (needLogin) {
debug('未登录,重定向到登录页面');
window.open($('a[href*="/account/auth"]').attr('href'), '_self');
}
debug('登录检查完成', {
needLogin: needLogin
});
return true;
} catch (error) {
debug('登录检查失败', {
error: error
});
throwError(error, 'GiveeClub.checkLogin');
return false;
}
}
#getGiveawayId() {
try {
debug('从URL获取抽奖ID');
const giveawayId = window.location.href.match(/\/event\/([\d]+)/)?.[1];
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({
text: I18n('getFailed', 'GiveawayId')
});
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'GiveeClub.getGiveawayId');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const isEnded = $('.event-ended').length > 0;
const hasNoWinner = $('.event-winner').length === 0;
debug('检查抽奖状态', {
isEnded: isEnded,
hasNoWinner: hasNoWinner
});
if (!(isEnded && hasNoWinner)) {
return true;
}
debug('没有剩余密钥,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('giveawayEnded'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'GiveeClub.checkLeftKey');
return false;
}
}
}
const defaultOptions$1 = {
maxPoint: '99999999'
};
class OpiumPulses {
name='OpiumPulses';
options={
...defaultOptions$1,
...GM_getValue('OpiumPulsesOptions')
};
maxPoints=99999999;
myPoints=0;
buttons=[ 'doFreeTask', 'doPointTask' ];
static test() {
const {host: host} = window.location;
const isMatch = host === 'www.opiumpulses.com';
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('检查登录失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
debug('解析最大积分', {
maxPoint: this.options.maxPoint
});
this.maxPoints = parseInt(this.options.maxPoint, 10);
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'OpiumPulses.after');
}
}
async doFreeTask() {
try {
debug('开始执行免费任务');
this.#toggleTask('FREE');
} catch (error) {
debug('执行免费任务失败', {
error: error
});
throwError(error, 'OpiumPulses.doFreeTask');
}
}
async doPointTask() {
try {
debug('开始执行积分任务');
const pointsText = $('.page-header__nav-func-user-nav-items.points-items').text();
const pointsMatch = pointsText.match(/[\d]+/gim)?.[0] || '0';
this.myPoints = parseInt(pointsMatch, 10);
debug('获取当前积分', {
pointsText: pointsText,
pointsMatch: pointsMatch,
myPoints: this.myPoints
});
this.#toggleTask('points');
} catch (error) {
debug('执行积分任务失败', {
error: error
});
throwError(error, 'OpiumPulses.doPointTask');
}
}
async #toggleTask(type) {
try {
debug('开始切换任务', {
type: type
});
const items = $(`.giveaways-page-item:contains('${type}'):not(:contains('ENTERED'))`);
debug('找到未参与的抽奖项目', {
count: items.length
});
for (const item of items) {
const pointsText = $(item).find('.giveaways-page-item-header-points').text();
const needPoints = parseInt(pointsText.match(/[\d]+/gim)?.[0] || '999999', 10);
const name = $(item).find('.giveaways-page-item-footer-name').text().trim();
debug('处理抽奖项目', {
name: name,
needPoints: needPoints
});
if (type === 'points') {
if (needPoints > this.myPoints) {
debug('积分不足', {
needPoints: needPoints,
myPoints: this.myPoints
});
echoLog({}).warning(`${I18n('noPoints')}: ${name}`);
continue;
}
if (!needPoints) {
debug('获取所需积分失败');
echoLog({}).warning(`${I18n('getNeedPointsFailed')}: ${name}`);
continue;
}
if (needPoints > this.maxPoints) {
debug('超过最大积分限制', {
needPoints: needPoints,
maxPoints: this.maxPoints
});
continue;
}
}
const logStatus = echoLog({
text: `${I18n('joiningLottery')}<a href="${$(item).find('a.giveaways-page-item-img-btn-more').attr('href')}" target="_blank">${name}</a>...`
});
const aElement = $(item).find('a.giveaways-page-item-img-btn-enter:contains(\'enter\')');
if (aElement?.attr('onclick')?.includes('checkUser')) {
const giveawayId = aElement.attr('onclick')?.match(/[\d]+/)?.[0];
if (giveawayId) {
debug('执行用户检查', {
giveawayId: giveawayId
});
checkUser(giveawayId);
}
}
if (!aElement.attr('href')) {
debug('无效的链接');
logStatus.error('Error: No "href".');
continue;
}
debug('发送加入请求', {
url: aElement.attr('href')
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: aElement.attr('href'),
method: 'GET'
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
continue;
}
debug('发送最终请求', {
url: data?.finalUrl
});
const {result: result0, statusText: statusText0, status: status0, data: data0} = await httpRequest({
url: data?.finalUrl,
method: 'GET'
});
if (!data0?.responseText) {
debug('响应无效', {
result: result0,
statusText: statusText0,
status: status0
});
logStatus.error(`${result0}:${statusText0}(${status0})`);
continue;
}
if (/You're not eligible to enter/gim.test(data0.responseText)) {
debug('用户不符合参与条件');
logStatus.error('You\'re not eligible to enter');
continue;
}
if (!/You've entered this giveaway/gim.test(data0.responseText)) {
debug('加入抽奖失败', {
result: result0,
statusText: statusText0,
status: status0
});
logStatus.error(`${result0}:${statusText0}(${status0})`);
continue;
}
debug('加入抽奖成功');
logStatus.success();
if (type === 'points') {
const points = data0.responseText.match(/Points:[\s]*?([\d]+)/)?.[1];
if (points) {
debug('更新用户积分', {
points: points
});
this.myPoints = parseInt(points, 10);
}
}
}
debug('任务处理完成');
echoLog({
text: '-----END-----'
});
} catch (error) {
debug('切换任务失败', {
error: error
});
throwError(error, 'OpiumPulses.toggleTask');
}
}
init() {
debug('初始化完成');
return true;
}
classifyTask() {
debug('任务分类完成');
return true;
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
if ($('a[href*="/site/login"]').length > 1) {
debug('未登录,重定向到登录页面');
window.open('/site/login', '_self');
}
debug('登录检查完成');
return true;
} catch (error) {
debug('检查登录失败', {
error: error
});
throwError(error, 'OpiumPulses.checkLogin');
return false;
}
}
}
const leftKeyChecker = {
async classify(link) {
try {
debug('开始分类链接', {
link: link
});
let result = false;
if (/^https?:\/\/giveaway\.su\/giveaway\/view\/[\d]+/.test(link)) {
debug('匹配到 giveaway.su 链接');
result = await this.giveawaySu(link);
} else if (/^https?:\/\/givee\.club\/[\w]+?\/event\/[\d]+/.test(link)) {
debug('匹配到 givee.club 链接');
result = await this.giveeClub(link);
} else if (/^https?:\/\/gleam\.io\/.+?\/.+/.test(link)) {
debug('匹配到 gleam.io 链接');
result = await this.gleam(link);
} else if (/^https?:\/\/www\.indiedb\.com\/giveaways\/.+/.test(link)) {
debug('匹配到 indiedb.com 链接');
result = await this.indieDb(link);
} else if (/^https?:\/\/key-hub\.eu\/giveaway\/[\d]+/.test(link)) {
debug('匹配到 key-hub.eu 链接');
result = await this.keyhub(link);
} else if (/^https?:\/\/opquests\.com\/quests\/[\d]+/.test(link)) {
debug('匹配到 opquests.com 链接');
result = await this.opquests(link);
} else if (/^https?:\/\/itch\.io\/s\/[\d]+?\/.*/.test(link)) {
debug('匹配到 itch.io 链接');
result = await this.itch(link);
} else if (/^https?:\/\/freeanywhere\.net\/game\?n=[\d]+/.test(link)) {
debug('匹配到 freeanywhere.net 链接');
result = await this.freeanywhere(link);
} else {
debug('未匹配到支持的链接格式');
}
debug('链接分类完成', {
result: result
});
return result;
} catch (error) {
debug('链接分类出错', {
error: error
});
throwError(error, 'leftKeyChecker.classify');
return false;
}
},
async giveawaySu(link) {
try {
debug('开始检查 giveaway.su 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
if (data.responseText.includes('class="steam-login"')) {
debug('检测到未登录状态');
return false;
}
if (data.responseText.includes('class="giveaway-ended"')) {
debug('检测到抽奖已结束');
return 'Ended';
}
debug('检测到抽奖进行中');
return 'Active';
} catch (error) {
debug('检查 giveaway.su 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.giveawaySu');
return false;
}
},
async giveeClub(link) {
try {
debug('开始检查 givee.club 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
if (data.responseText.includes('class="event-winner"')) {
debug('检测到已中奖');
return 'Won';
}
if (data.responseText.includes('class="event-ended"')) {
debug('检测到活动已结束');
return 'Ended';
}
debug('检测到活动进行中');
return 'Active';
} catch (error) {
debug('检查 givee.club 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.giveeClub');
return false;
}
},
async gleam(link) {
try {
debug('开始检查 gleam.io 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
if (/incentives":{"[\d]+?":\[".+?"\]/.test(data.responseText)) {
debug('检测到已中奖');
return 'Won';
}
const campaignDiv = data.responseText.match(/<div class='popup-blocks-container'[\w\W]+?'>/)?.[0];
if (!campaignDiv) {
debug('未找到活动信息');
return false;
}
const campaignString = $(campaignDiv).attr('ng-init')?.match(/initCampaign\(([\w\W]+?)\)$/)?.[1];
if (!campaignString) {
debug('未找到活动初始化数据');
return false;
}
const {campaign: campaign} = JSON.parse(campaignString);
debug('解析活动数据', {
campaign: campaign
});
if (campaign.banned) {
debug('检测到活动已被禁止');
return 'Banned';
}
if (campaign.finished) {
debug('检测到活动已结束');
return 'Ended';
}
if (campaign.paused) {
debug('检测到活动已暂停');
return 'Paused';
}
if ((new Date).getTime() < campaign.starts_at * 1e3) {
debug('检测到活动未开始');
return 'NotStart';
}
debug('检测到活动进行中');
return 'Active';
} catch (error) {
debug('检查 gleam.io 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.gleam');
return false;
}
},
async indieDb(link) {
try {
debug('开始检查 indiedb.com 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
if (data.responseText.includes('Congrats you WON')) {
debug('检测到已中奖');
return 'Won';
}
if (data.responseText.includes('Giveaway is closed') || data.responseText.includes('next time')) {
debug('检测到抽奖已结束');
return 'Ended';
}
debug('检测到抽奖进行中');
return 'Active';
} catch (error) {
debug('检查 indiedb.com 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.indieDb');
return false;
}
},
async keyhub(link) {
try {
debug('开始检查 key-hub.eu 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
const keysleft = data.responseText.match(/<span id="keysleft">([\d]+?)<\/span>/)?.[1];
if (!keysleft) {
debug('未找到剩余密钥信息');
return false;
}
debug('检测到剩余密钥数量', {
keysleft: keysleft
});
if (keysleft === '0') {
debug('检测到密钥已用完');
return 'Ended';
}
debug('检测到活动进行中');
return `Active(${keysleft})`;
} catch (error) {
debug('检查 key-hub.eu 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.keyhub');
return false;
}
},
async opquests(link) {
try {
debug('开始检查 opquests.com 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (data?.status === 404) {
debug('检测到活动不存在');
return 'Ended';
}
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
const keysleft = data.responseText.match(/<div class="">[\s]*?([\d]+?)[\s]*?of/)?.[1];
if (!keysleft) {
debug('未找到剩余密钥信息');
return false;
}
debug('检测到剩余密钥数量', {
keysleft: keysleft
});
if (keysleft === '0') {
debug('检测到密钥已用完');
return 'Ended';
}
debug('检测到活动进行中');
return `Active(${keysleft})`;
} catch (error) {
debug('检查 opquests.com 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.opquests');
return false;
}
},
async itch(link) {
try {
debug('开始检查 itch.io 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
const endDate = data.responseText.match(/{"start_date":"[0-9A-Z-:]+?".*?"end_date":"([0-9A-Z-:]+?)".*?}/)?.[1];
if (!endDate) {
debug('未找到结束日期信息');
return false;
}
debug('检测到活动结束日期', {
endDate: endDate
});
if ((new Date).getTime() > new Date(endDate).getTime()) {
debug('检测到活动已结束');
return 'Ended';
}
const formattedEndDate = dayjs(endDate).format('YYYY-MM-DD HH:mm:ss');
debug('检测到活动进行中', {
formattedEndDate: formattedEndDate
});
return `Active(${formattedEndDate})`;
} catch (error) {
debug('检查 itch.io 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.itch');
return false;
}
},
async freeanywhere(link) {
try {
debug('开始检查 freeanywhere.net 链接', {
link: link
});
const {result: result, data: data} = await httpRequest({
url: link,
method: 'GET'
});
if (result !== 'Success' || data?.status !== 200) {
debug('请求失败', {
result: result,
status: data?.status
});
return false;
}
const giveawayStatus = data.responseText.includes('Giveaway ended');
if (giveawayStatus) {
debug('检测到活动已结束');
return 'Ended';
}
debug('检测到活动进行中');
return 'Active';
} catch (error) {
debug('检查 freeanywhere.net 链接出错', {
error: error
});
throwError(error, 'leftKeyChecker.freeanywhere');
return false;
}
}
};
const defaultTasksTemplate$3 = {
steam: {
groupLinks: [],
wishlistLinks: [],
curatorLinks: [],
curatorLikeLinks: [],
followLinks: [],
forumLinks: [],
announcementLinks: [],
workshopVoteLinks: [],
licenseLinks: []
},
discord: {
serverLinks: []
},
vk: {
nameLinks: []
},
twitch: {
channelLinks: []
},
reddit: {
redditLinks: []
},
twitter: {
userLinks: [],
retweetLinks: []
},
youtube: {
channelLinks: [],
likeLinks: []
}
};
const defaultTasks$4 = JSON.stringify(defaultTasksTemplate$3);
class Keylol extends Website {
name='Keylol';
socialTasks=JSON.parse(defaultTasks$4);
undoneTasks=JSON.parse(defaultTasks$4);
buttons=[ 'doTask', 'undoTask', 'selectAll', 'selectNone', 'invertSelect' ];
static CONFIG={
LINK_PATTERNS: {
DISCORD: /^https?:\/\/discord\.com\/invite\/.+/,
REDDIT: /^https?:\/\/www\.reddit\.com\/(r|user)\/.+/,
INSTAGRAM: /^https:\/\/www\.instagram\.com\/.+/,
TWITTER: /^https:\/\/(twitter|x)\.com\/.+/,
TWITTER_RETWEET: /https:\/\/(twitter|x)\.com\/.*?\/status\/[\d]+/,
TWITCH: /^https:\/\/(www\.)?twitch\.tv\/.+/,
VK: /^https:\/\/vk\.com\/.+/,
STEAM_CURATOR: /curator\/[\d]+/,
STEAM_PUBLISHER: /(publisher|developer|franchise)\/.+/,
STEAM_NEWS: /news(hub)?\/app\/[\d]+\/view\/[\d]+/,
STEAM_APP: /app\/[\d]+/,
STEAM_GROUP: /groups\/.+/,
STEAM_ANNOUNCEMENT: /announcements\/detail\/[\d]+/,
YOUTUBE: /youtube\.com/
},
SELECTORS: {
MAIN_POST: {
KEYLOL: '#postlist>div[id^="post_"]:first',
DEFAULT: 'div.container'
}
}
};
static test() {
const {host: host} = window.location;
const link = $('.subforum_left_title_left_up a').eq(3).attr('href');
const isMatch = host === 'keylol.com' && (!!link?.includes('319') || !!link?.includes('234'));
debug('检查网站匹配', {
host: host,
link: link,
isMatch: isMatch
});
return isMatch;
}
init() {
debug('初始化 Keylol');
return true;
}
after() {
try {
debug('开始处理页面链接');
const mainPost = $(this.name === 'Keylol' ? Keylol.CONFIG.SELECTORS.MAIN_POST.KEYLOL : Keylol.CONFIG.SELECTORS.MAIN_POST.DEFAULT);
const allLinks = mainPost.find('a');
debug('找到所有链接', {
count: allLinks.length
});
allLinks.each(((_, link) => {
const $link = $(link);
const href = $link.attr('href');
if (!href) {
return;
}
this.#classifyAndProcessLink($link, href);
}));
debug('开始处理抽奖链接');
this.#processGiveawayLinks(mainPost);
if (this.name === 'Keylol') {
debug('开始处理 Keylol 特定链接');
this.#processKeylolSpecificLinks(mainPost);
}
debug('设置 MutationObserver');
this.#setupMutationObserver();
} catch (error) {
debug('处理页面链接失败', {
error: error
});
throwError(error, 'keylol.after');
}
}
#classifyAndProcessLink($link, href) {
debug('分类处理链接', {
href: href
});
const {LINK_PATTERNS: LINK_PATTERNS} = Keylol.CONFIG;
switch (true) {
case LINK_PATTERNS.DISCORD.test(href):
debug('发现 Discord 链接');
this.#addBtn($link[0], 'discord', 'serverLinks', href);
break;
case LINK_PATTERNS.REDDIT.test(href):
debug('发现 Reddit 链接');
this.#addBtn($link[0], 'reddit', 'redditLinks', href);
break;
case LINK_PATTERNS.TWITTER.test(href):
if (LINK_PATTERNS.TWITTER_RETWEET.test(href)) {
debug('发现 Twitter 转发链接');
this.#addBtn($link[0], 'twitter', 'retweetLinks', href);
} else {
debug('发现 Twitter 用户链接');
this.#addBtn($link[0], 'twitter', 'userLinks', href);
}
break;
case LINK_PATTERNS.TWITCH.test(href):
debug('发现 Twitch 链接');
this.#addBtn($link[0], 'twitch', 'channelLinks', href);
break;
case LINK_PATTERNS.VK.test(href):
debug('发现 VK 链接');
this.#addBtn($link[0], 'vk', 'nameLinks', href);
break;
case href.includes('store.steampowered.com'):
debug('发现 Steam 商店链接');
this.#processSteamStoreLink($link[0], href);
break;
case href.includes('steamcommunity.com'):
debug('发现 Steam 社区链接');
this.#processSteamCommunityLink($link[0], href);
break;
case LINK_PATTERNS.YOUTUBE.test(href):
debug('发现 YouTube 链接');
this.#addBtn($link[0], 'youtube', 'channelLinks', href);
this.#addBtn($link[0], 'youtube', 'likeLinks', href);
break;
}
}
#processSteamStoreLink(element, href) {
debug('处理 Steam 商店链接', {
href: href
});
const {LINK_PATTERNS: LINK_PATTERNS} = Keylol.CONFIG;
if (LINK_PATTERNS.STEAM_CURATOR.test(href)) {
debug('发现 Steam 鉴赏家链接');
this.#addBtn(element, 'steam', 'curatorLinks', href);
} else if (LINK_PATTERNS.STEAM_PUBLISHER.test(href)) {
debug('发现 Steam 发行商链接');
this.#addBtn(element, 'steam', 'curatorLikeLinks', href);
} else if (LINK_PATTERNS.STEAM_NEWS.test(href)) {
debug('发现 Steam 新闻链接');
this.#addBtn(element, 'steam', 'announcementLinks', href);
} else if (LINK_PATTERNS.STEAM_APP.test(href)) {
debug('发现 Steam 应用链接');
this.#addBtn(element, 'steam', 'followLinks', href);
this.#addBtn(element, 'steam', 'wishlistLinks', href);
}
}
#processSteamCommunityLink(element, href) {
debug('处理 Steam 社区链接', {
href: href
});
const {LINK_PATTERNS: LINK_PATTERNS} = Keylol.CONFIG;
if (LINK_PATTERNS.STEAM_GROUP.test(href)) {
debug('发现 Steam 组链接');
this.#addBtn(element, 'steam', 'groupLinks', href);
} else if (LINK_PATTERNS.STEAM_ANNOUNCEMENT.test(href)) {
debug('发现 Steam 公告链接');
this.#addBtn(element, 'steam', 'announcementLinks', href);
}
}
#processGiveawayLinks(mainPost) {
debug('开始处理抽奖链接');
const giveawayLinks = mainPost.find('a[href*="giveaway.su/giveaway/view/"],' + 'a[href*="givee.club/"],' + 'a[href*="gleam.io/"],' + 'a[href*="www.indiedb.com/giveaways/"],' + 'a[href*="key-hub.eu/giveaway/"],' + 'a[href*="opquests.com/quests/"],' + 'a[href*="freeanywhere.net/game?n="],' + 'a[href*="itch.io/s/"]:visible');
debug('找到抽奖链接', {
count: giveawayLinks.length
});
giveawayLinks.each(((_, link) => {
const href = $(link).attr('href');
if (!href) {
return;
}
debug('检查抽奖链接状态', {
href: href
});
leftKeyChecker.classify(href).then((status => {
if (!status) {
return;
}
const statusClass = /^Active/.test(status) ? 'active' : 'not-active';
const statusTitle = /^Active/.test(status) ? I18n('Active') : I18n(status);
debug('更新抽奖链接状态', {
href: href,
status: status,
statusClass: statusClass
});
$(`a[href="${href}"]`).after(`<font class="auto-task-giveaway-status ${statusClass}" title="${statusTitle}">${status}</font>`);
})).catch((error => {
debug('检查抽奖链接状态失败', {
href: href,
error: error
});
throwError(error, 'keylol.after -> leftKeyChecker');
}));
}));
}
#processKeylolSpecificLinks(mainPost) {
debug('开始处理 Keylol 特定链接');
const asfLinks = mainPost.find('a[href^="#asf"]:visible');
debug('找到 ASF 链接', {
count: asfLinks.length
});
asfLinks.each(((_, link) => {
const href = $(link).attr('href');
if (!href) {
return;
}
debug('处理 ASF 链接', {
href: href
});
const $link = $(`a[href="${href}"]`);
$link.after('<span style="color: #ccc; margin: 0 -5px 0 5px"> | </span>');
this.#addBtn($link.next()[0], 'steam', 'licenseLinks', `appid-${href.replace('#asf', '')}`);
}));
const steamDbLinks = mainPost.find('a[href*="steamdb.info/sub/"]:visible');
debug('找到 SteamDB 链接', {
count: steamDbLinks.length
});
steamDbLinks.each(((_, link) => {
const href = $(link).attr('href');
if (!href) {
return;
}
const subid = href.match(/^https:\/\/steamdb\.info\/sub\/([\d]+)/)?.[1];
if (!subid) {
return;
}
debug('处理 SteamDB 链接', {
href: href,
subid: subid
});
this.#addBtn(link, 'steam', 'licenseLinks', `subid-${subid}`);
}));
const asfBlocks = mainPost.find('.blockcode:contains("addlicense"):visible');
debug('找到 ASF 代码块', {
count: asfBlocks.length
});
asfBlocks.each(((_, block) => {
const appid = [ ...block.innerText.matchAll(/a(pp)?\/([\d]+)/g) ].map((matched => matched?.[2])).filter((id => id));
if (appid.length > 0) {
debug('处理 ASF 代码块 appid', {
appid: appid
});
this.#addBtn($(block).children('em')[0], 'steam', 'licenseLinks', `appid-${appid.join(',')}`);
}
const subid = block.innerText.match(/[\d]+/g)?.filter((matched => !appid.includes(matched)));
if (subid?.length) {
debug('处理 ASF 代码块 subid', {
subid: subid
});
this.#addBtn($(block).children('em')[0], 'steam', 'licenseLinks', `subid-${subid.join(',')}`);
}
}));
}
#setupMutationObserver() {
debug('设置 MutationObserver');
if ($('#threadindex').length > 0) {
const [elementTargetNode] = $('#postlist').children('div[id^="post_"]');
const elementObserver = new MutationObserver((() => {
debug('检测到 DOM 变化,重新处理页面');
elementObserver.disconnect();
this.after();
}));
elementObserver.observe(elementTargetNode, {
childList: true
});
debug('MutationObserver 设置完成');
}
}
classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
this.socialTasks = JSON.parse(defaultTasks$4);
this.undoneTasks = JSON.parse(defaultTasks$4);
const selectedBtns = $('.auto-task-keylol[selected="selected"]:visible').get();
debug('找到选中的按钮', {
count: selectedBtns.length
});
for (const btn of selectedBtns) {
const social = btn.getAttribute('data-social');
const type = btn.getAttribute('data-type');
const link = btn.getAttribute('data-link');
debug('处理任务按钮', {
social: social,
type: type,
link: link
});
if (!(social && type && link)) {
debug('跳过无效任务按钮');
continue;
}
if (!(social in this.undoneTasks)) {
debug('跳过未知社交平台', {
social: social
});
continue;
}
if (action === 'do' && type in this.undoneTasks[social]) {
debug('添加到未完成任务', {
social: social,
type: type,
link: link
});
this.undoneTasks[social][type].push(link);
}
if (action === 'undo' && type in this.socialTasks[social]) {
debug('添加到社交任务', {
social: social,
type: type,
link: link
});
this.socialTasks[social][type].push(link);
}
}
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.uniqueTasks(this.socialTasks);
debug('任务分类完成', {
undoneTasks: this.undoneTasks,
socialTasks: this.socialTasks
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Keylol.classifyTask');
return false;
}
}
selectAll() {
try {
debug('选择所有可见任务');
const tasks = $('.auto-task-keylol:visible');
tasks.attr('selected', 'selected');
debug('选择完成', {
count: tasks.length
});
} catch (error) {
debug('选择所有任务失败', {
error: error
});
throwError(error, 'Keylol.selectAll');
}
}
selectNone() {
try {
debug('取消选择所有可见任务');
const tasks = $('.auto-task-keylol:visible');
tasks.removeAttr('selected');
debug('取消选择完成', {
count: tasks.length
});
} catch (error) {
debug('取消选择所有任务失败', {
error: error
});
throwError(error, 'Keylol.selectNone');
}
}
invertSelect() {
try {
debug('反转选择所有可见任务');
const tasks = $('.auto-task-keylol:visible');
tasks.each(((_, element) => {
const $element = $(element);
if ($element.attr('selected')) {
$element.removeAttr('selected');
} else {
$element.attr('selected', 'selected');
}
}));
debug('反转选择完成', {
count: tasks.length
});
} catch (error) {
debug('反转选择任务失败', {
error: error
});
throwError(error, 'Keylol.invertSelect');
}
}
#addBtn(before, social, linkType, link) {
try {
debug('添加任务按钮', {
social: social,
linkType: linkType,
link: link
});
if (!before || !social || !linkType || !link) {
debug('跳过无效按钮参数');
return;
}
const button = $('<a>', {
href: 'javascript:void(0);',
class: 'auto-task-keylol',
target: '_self',
'data-social': social,
'data-type': linkType,
'data-link': link,
text: linkType.replace('Links', ''),
onclick: 'this.getAttribute("selected") ? this.removeAttribute("selected") : this.setAttribute("selected", "selected")'
});
$(before).after(button);
debug('按钮添加成功');
} catch (error) {
debug('添加按钮失败', {
error: error,
social: social,
linkType: linkType
});
throwError(error, `keylol.addBtn: ${social}/${linkType}`);
}
}
}
const defaultTasks$3 = {
steam: {
groupLinks: [],
wishlistLinks: [],
followLinks: [],
curatorLikeLinks: [],
playTimeLinks: []
},
twitter: {
userLinks: [],
retweetLinks: []
},
discord: {
serverLinks: []
}
};
class Opquests extends Website {
name='Opquests';
undoneTasks={
...defaultTasks$3
};
buttons=[ 'doTask', 'verifyTask', 'getKey' ];
static test() {
const {host: host} = window.location;
const isMatch = host === 'opquests.com';
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('检查登录失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
const opquestsVerifyTasks = GM_getValue('opquestsVerifyTasks') || [];
debug('获取待验证任务', {
count: opquestsVerifyTasks.length
});
if (opquestsVerifyTasks.length > 0) {
const taskId = opquestsVerifyTasks.pop();
debug('处理任务', {
taskId: taskId
});
GM_setValue('opquestsVerifyTasks', opquestsVerifyTasks);
const [verifyBtn] = $(`#task_id[value="${taskId}"]`).parent().find('button[type="button"],button[type="submit"]').has('i.fa-check');
if (verifyBtn) {
debug('点击验证按钮');
verifyBtn.click();
return;
}
debug('未找到验证按钮,继续处理下一个任务');
this.after();
return;
}
if (GM_getValue('opquestsVerifyTasks')) {
debug('清除验证任务缓存');
GM_deleteValue('opquestsVerifyTasks');
}
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'Opquests.after');
}
}
init() {
try {
debug('开始初始化');
const logStatus = echoLog({
text: I18n('initing')
});
if ($('a[href*="/auth/redirect"]').length > 0) {
debug('需要登录');
window.open('/auth/redirect', '_self');
logStatus.warning(I18n('needLogin'));
return false;
}
if (!this.#getGiveawayId()) {
debug('获取抽奖ID失败');
return false;
}
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'Opquests.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
if (action === 'undo') {
debug('不支持撤销操作');
echoLog({
text: I18n('cannotUndo')
});
return false;
}
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
const tasks = $('.w-full:contains("Validate") .items-center');
debug('找到任务', {
count: tasks.length
});
for (const task of tasks) {
const link = $(task).find('a:contains("Open")').attr('href');
if (!link) {
debug('跳过无链接任务');
continue;
}
const taskDes = $(task).find('div').eq(1).text().trim();
debug('处理任务', {
taskDes: taskDes,
link: link
});
if (/steamcommunity\.com\/groups\//.test(link)) {
debug('添加 Steam 组任务');
this.undoneTasks.steam.groupLinks.push(link);
continue;
}
if (/store\.steampowered\.com\/app\//.test(link)) {
if (/wishlist/gim.test(taskDes)) {
debug('添加 Steam 愿望单任务');
this.undoneTasks.steam.wishlistLinks.push(link);
} else if (/follow/gim.test(taskDes)) {
debug('添加 Steam 关注任务');
this.undoneTasks.steam.followLinks.push(link);
} else if (/play/gim.test(taskDes)) {
const time = parseInt(taskDes.replace(/\s/gim, '').match(/(\d+)hours/im)?.[1] || '0', 10) * 60;
debug('添加 Steam 游戏时长任务', {
time: time
});
this.undoneTasks.steam.playTimeLinks.push(`${time}-${link}`);
}
continue;
}
if (/store\.steampowered\.com\/(publisher|developer|curator)\//.test(link) && /follow/gim.test(taskDes)) {
debug('添加 Steam 鉴赏家关注任务');
this.undoneTasks.steam.curatorLikeLinks.push(link);
continue;
}
if (link.includes('//x.com/')) {
if (/follow/gim.test(taskDes)) {
debug('添加 Twitter 关注任务');
this.undoneTasks.twitter.userLinks.push(link);
continue;
}
if (link.includes('status') && /Repost/gim.test(taskDes)) {
debug('添加 Twitter 转发任务');
this.undoneTasks.twitter.retweetLinks.push(link);
continue;
}
}
if (link.includes('//discord.com/')) {
if (/join/gim.test(taskDes)) {
debug('添加 Discord 加入任务');
this.undoneTasks.discord.serverLinks.push(link);
continue;
}
}
if (/clash\.gg/.test(link)) {
debug('跳过不支持的 Clash.gg 任务');
echoLog({}).warning(`${I18n('unSupporttedTaskType')}: ${taskDes}(${link})`);
continue;
}
debug('未知任务类型');
echoLog({}).warning(`${I18n('unKnownTaskType')}: ${taskDes}(${link})`);
}
debug('任务分类完成');
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
if (window.DEBUG) {
console.log('%cAuto-Task[Debug]:', 'color:blue', JSON.stringify(this));
}
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Opquests.classifyTask');
return false;
}
}
async verifyTask() {
try {
debug('开始验证任务');
if (!this.initialized) {
debug('未初始化,执行初始化');
this.init();
}
const tasks = $.makeArray($('.items-center').has('input[name="task_id"]')).map((ele => $(ele).find('input[name="task_id"]').val()));
debug('获取待验证任务', {
count: tasks.length
});
GM_setValue('opquestsVerifyTasks', tasks);
await this.#confirm();
debug('执行后续操作');
this.after();
return true;
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Opquests.verifyTask');
return false;
}
}
async #confirm() {
try {
debug('开始确认任务');
const logStatus = echoLog({
html: `<li>${I18n('confirmingTask')}...<font></font></li>`
});
debug('发送确认请求');
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://opquests.com/quests/${this.giveawayId}?confirm=1`,
method: 'GET',
nochche: true,
headers: {
origin: 'https://opquests.com',
referer: `https://opquests.com/warning?id=${this.giveawayId}`
}
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('响应错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('确认成功');
logStatus.success();
return true;
} catch (error) {
debug('确认任务失败', {
error: error
});
throwError(error, 'Opquests.confirm');
return false;
}
}
async getKey(isButton) {
try {
if ($('[name="task_id"]').length > 0) {
debug('有任务未完成,不获取密钥');
echoLog({}).warning(I18n('taskNotFinished'));
return false;
}
debug('开始获取密钥', {
isButton: isButton
});
const logStatus = echoLog({
text: I18n('gettingKey')
});
debug('发送获取密钥请求');
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://opquests.com/keys',
method: 'GET'
});
if (result !== 'Success') {
debug('请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (!data?.responseText) {
debug('响应无效', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const questTitle = $('h1.font-bold').text().trim().replace(' Quest', '');
const key = $(data.responseText).find(`div.items-center:contains("${questTitle}")`).find('div.font-bold').next().text();
debug('查找密钥', {
questTitle: questTitle,
hasKey: !!key
});
if (!key) {
debug('未找到密钥');
logStatus.error('Error: Key was not found');
if (isButton) {
debug('重定向到密钥页面');
window.open('https://opquests.com/keys', '_self');
}
return false;
}
debug('获取密钥成功');
logStatus.success();
echoLog({}).success(key);
return true;
} catch (error) {
debug('获取密钥失败', {
error: error
});
throwError(error, 'Opquests.getKey');
return false;
}
}
#getGiveawayId() {
try {
debug('开始获取抽奖ID');
const giveawayId = window.location.href.match(/quests\/([\d]+)/)?.[1];
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({}).error(I18n('getFailed', 'GiveawayId'));
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'Opquests.getGiveawayId');
return false;
}
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
if ($('a[href*="/auth/redirect"]').length > 0) {
debug('未登录,重定向到登录页面');
window.open('/auth/redirect', '_self');
}
debug('登录检查完成');
return true;
} catch (error) {
debug('检查登录失败', {
error: error
});
throwError(error, 'Opquests.checkLogin');
return false;
}
}
}
const defaultTasksTemplate$2 = {
steam: {
groupLinks: [],
wishlistLinks: [],
followLinks: [],
curatorLinks: [],
curatorLikeLinks: [],
playTimeLinks: []
},
twitter: {
userLinks: [],
retweetLinks: []
},
twitch: {
channelLinks: []
},
discord: {
serverLinks: []
},
youtube: {
channelLinks: []
},
extra: {
gleam: []
}
};
const defaultTasks$2 = JSON.stringify(defaultTasksTemplate$2);
class Gleam extends Website {
name='Gleam';
undoneTasks=JSON.parse(defaultTasks$2);
socialTasks=JSON.parse(defaultTasks$2);
buttons=[ 'doTask', 'undoTask', 'verifyTask' ];
static test() {
const {host: host} = window.location;
const isMatch = host === 'gleam.io';
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return isMatch;
}
before() {
try {
debug('重写全局对话框函数');
unsafeWindow.confirm = () => {};
unsafeWindow.alert = () => {};
unsafeWindow.prompt = () => {};
} catch (error) {
debug('重写全局对话框函数失败', {
error: error
});
throwError(error, 'Gleam.before');
}
}
async after() {
try {
debug('开始执行后续操作');
if (window.location.search.includes('8b07d23f4bfa65f9')) {
debug('检测到特殊查询参数,开始处理任务');
const checkComplete = setInterval((() => {
if ($('.entry-content .entry-method i.fa-check').length > 0) {
debug('任务已完成,关闭窗口');
clearInterval(checkComplete);
window.close();
}
}));
await this.verifyTask();
echoLog({}).warning(I18n('gleamTaskNotice'));
} else if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'Gleam.after');
}
}
init() {
try {
debug('初始化 Gleam');
const logStatus = echoLog({
text: I18n('initing')
});
if (!this.#getGiveawayId()) {
debug('获取抽奖ID失败');
return false;
}
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'Gleam.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('恢复已保存的任务信息');
this.socialTasks = GM_getValue(`gleamTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$2);
}
const tasks = $('.entry-content .entry-method');
debug('找到任务元素', {
count: tasks.length
});
for (const task of tasks) {
const $task = $(task);
if (action === 'do' && $task.find('i.fa-question').length === 0) {
debug('跳过已完成的任务');
continue;
}
const socialIcon = $task.find('.icon-wrapper i');
const taskInfo = $task.find('.user-links');
const taskText = taskInfo.text().trim();
const expandInfo = $task.find('.expandable');
const aElements = expandInfo.find('a.btn');
debug('处理任务', {
taskText: taskText
});
if (aElements.length > 0) {
debug('处理可点击元素', {
count: aElements.length
});
for (const element of aElements) {
const $element = $(element);
const href = $element.attr('href');
$element.removeAttr('href')[0].click();
$element.attr('href', href);
}
}
if (socialIcon.hasClass('fa-twitter') || socialIcon.hasClass('fa-x-twitter')) {
const link = $task.find('a[href^="https://twitter.com/"],a[href^="https://x.com/"]').attr('href');
if (!link) {
continue;
}
if (/follow/gi.test(taskText)) {
if (action === 'undo') {
this.socialTasks.twitter.userLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.twitter.userLinks.push(link);
}
continue;
}
if (/retweet/gim.test(taskText)) {
if (action === 'undo') {
this.socialTasks.twitter.retweetLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.twitter.retweetLinks.push(link);
}
continue;
}
}
if (socialIcon.hasClass('fa-twitch') && /follow/gim.test(taskText)) {
const link = $task.find('a[href^="https://twitch.tv/"]').attr('href');
if (!link) {
continue;
}
if (action === 'undo') {
this.socialTasks.twitch.channelLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.twitch.channelLinks.push(link);
}
continue;
}
if (socialIcon.hasClass('fa-discord') && /join/gim.test(taskText)) {
let link = $task.find('a[href^="https://discord.com/invite/"]').attr('href');
if (!link) {
const ggLink = $task.find('a[href^="https://discord.gg/"]').attr('href')?.match(/discord\.gg\/([^/]+)/)?.[1];
if (!ggLink) {
continue;
}
link = `https://discord.com/invite/${ggLink}`;
}
if (action === 'undo') {
this.socialTasks.discord.serverLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.discord.serverLinks.push(link);
}
continue;
}
if (socialIcon.hasClass('fa-external-link-square-alt')) {
continue;
}
if (socialIcon.hasClass('fa-youtube') && /subscribe/gim.test(taskText)) {
const link = $task.find('a[href^="https://www.youtube.com/channel/"]').attr('href');
if (!link) {
continue;
}
if (action === 'undo') {
this.socialTasks.youtube.channelLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.youtube.channelLinks.push(link);
}
continue;
}
if (socialIcon.attr('class')?.includes('steam')) {
if (/join.*group/gi.test(taskText)) {
const link = $task.find('a[href^="https://steamcommunity.com/groups/"]').attr('href');
if (!link) {
continue;
}
if (action === 'undo') {
this.socialTasks.steam.groupLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.steam.groupLinks.push(link);
}
continue;
}
if (/follow.*curator/gi.test(taskText)) {
const link = $task.find('a[href^="https://store.steampowered.com/curator/"]').attr('href');
if (!link) {
continue;
}
if (action === 'undo') {
this.socialTasks.steam.curatorLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.steam.curatorLinks.push(link);
}
continue;
}
if (/play.*hours/gi.test(taskText)) {
const link = $task.find('a[href^="https://steamcommunity.com/app/"],a[href^="https://store.steampowered.com/app/"]').attr('href');
if (!link) {
continue;
}
if (action === 'undo') {
this.socialTasks.steam.playTimeLinks.push(link);
}
if (action === 'do') {
this.undoneTasks.steam.playTimeLinks.push(link);
}
continue;
}
if (/Sign up/gi.test(taskText)) {
continue;
}
}
if (socialIcon.hasClass('fa-bullhorn') && /Complete|Increase/gi.test(taskText)) {
if (action !== 'do') {
continue;
}
const gleamLink = await this.#getGleamLink(taskText);
if (!gleamLink) {
continue;
}
this.undoneTasks.extra.gleam.push(gleamLink);
continue;
}
if (socialIcon.hasClass('fa-question') || socialIcon.hasClass('fa-reddit') || socialIcon.hasClass('fa-instagram') || socialIcon.hasClass('fa-facebook-f') || socialIcon.hasClass('fa-telegram-plane') || socialIcon.hasClass('fa-telegram') || socialIcon.hasClass('fa-vk') || socialIcon.hasClass('fa-envelope') || socialIcon.hasClass('fa-gift') || socialIcon.hasClass('fa-square-up-right') || socialIcon.hasClass('fa-gamepad-modern') || socialIcon.hasClass('fa-dollar-sign') || socialIcon.hasClass('fa-tiktok') || socialIcon.hasClass('fa-gamepad-alt') || socialIcon.hasClass('fa-bag-shopping') || socialIcon.hasClass('fa-swords') || socialIcon.hasClass('fa-shield') && taskText.includes('one of our giveaways') || socialIcon.hasClass('fa-shield') && taskText.includes('Check out') || socialIcon.hasClass('fa-shield') && taskText.includes('vloot.io')) {
continue;
}
echoLog({}).warning(`${I18n('unKnownTaskType')}: ${taskText}`);
}
debug('任务分类完成');
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.uniqueTasks(this.socialTasks);
debug('保存任务信息');
GM_setValue(`gleamTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Gleam.classifyTask');
return false;
}
}
async extraDoTask({gleam: gleam}) {
try {
debug('开始执行额外任务', {
count: gleam.length
});
const pro = [];
for (const link of gleam) {
pro.push(this.#doGleamTask(link));
}
return Promise.all(pro).then((() => true));
} catch (error) {
debug('执行额外任务失败', {
error: error
});
throwError(error, 'Gleam.extraDoTask');
return false;
}
}
async #checkCampaign() {
try {
debug('检测人机验证');
let logStatus;
if ($('[campaign-key="campaign.key"]').length > 0) {
logStatus = echoLog({
text: I18n('campaign')
});
debug('检测到人机验证');
await delay(3e3);
logStatus.warning(I18n('retry'));
await this.#checkCampaign();
return true;
}
logStatus?.success();
return false;
} catch (error) {
debug('检测人机验证失败', {
error: error
});
throwError(error, 'Gleam.checkCampaign');
return false;
}
}
async verifyTask() {
try {
debug('开始验证任务');
echoLog({
text: `${I18n('verifyingTask')}...`
});
const tasks = $('.entry-content .entry-method');
unsafeWindow._OxA = '_OxA';
for (const task of tasks) {
const campaign = await this.#checkCampaign();
if (campaign) {
return this.verifyTask();
}
const $task = $(task);
if ($task.find('i.fa-check').length > 0) {
debug('跳过已完成的任务');
continue;
}
debug('处理任务验证');
const taskInfo = $task.find('.user-links');
taskInfo[0].click();
const aElements = $task.find('.expandable').find('a.btn');
if (aElements.length > 0) {
debug('处理可点击元素', {
count: aElements.length
});
for (const element of aElements) {
const $element = $(element);
const href = $element.attr('href');
$element.removeAttr('href')[0].click();
$element.attr('href', href);
}
}
debug('处理计时器');
unsafeWindow.$hookTimer?.setSpeed(1e3);
const visitBtn = $task.find('.expandable').find('span:contains(more seconds),button:contains(more seconds)').filter(':visible');
if (visitBtn.length > 0 && unsafeWindow.$hookTimer) {
debug('处理访问按钮');
const newTab = GM_openInTab('', {
active: true
});
await delay(1e3);
newTab?.close();
window.focus();
}
await delay(3e3);
unsafeWindow.$hookTimer?.setSpeed(1);
const expandInfo = $task.find('.expandable');
const [input] = expandInfo.find('input');
if (input) {
debug('处理输入框');
const evt = new Event('input', {
bubbles: true,
cancelable: true,
composed: true
});
const valuelimit = [ ...expandInfo.text().matchAll(/"(.+?)"/g) ].at(-1)?.[1];
input.value = valuelimit || 'vloot';
input.dispatchEvent(evt);
await delay(1e3);
}
await this.#checkSync();
const continueBtn = $task.find('.expandable').find('span:contains(Continue),button:contains(Continue),a:contains(Continue)');
for (const button of continueBtn) {
debug('点击继续按钮');
button.click();
await delay(500);
await this.#checkSync();
}
}
debug('任务验证完成');
echoLog({
text: I18n('verifiedGleamTasks')
});
return true;
} catch (error) {
debug('任务验证失败', {
error: error
});
throwError(error, 'Gleam.verifyTask');
return false;
}
}
async #checkSync() {
try {
debug('开始检查同步状态');
return await new Promise((resolve => {
const checker = setInterval((() => {
if ($('.entry-content .entry-method i.fa-sync').length === 0) {
debug('同步完成');
clearInterval(checker);
resolve(true);
}
}), 500);
}));
} catch (error) {
debug('检查同步状态失败', {
error: error
});
throwError(error, 'Gleam.checkSync');
return false;
}
}
async #doGleamTask(link) {
try {
debug('执行 Gleam 任务', {
link: link
});
const logStatus = echoLog({
text: I18n('doingGleamTask')
});
return await new Promise((resolve => {
GM_openInTab(`${link}?8b07d23f4bfa65f9`, {
active: true,
insert: true,
setParent: true
}).onclose = () => {
debug('任务完成');
logStatus.success();
resolve(true);
};
}));
} catch (error) {
debug('执行 Gleam 任务失败', {
error: error
});
throwError(error, 'Gleam.doGleamTask');
return false;
}
}
#getGiveawayId() {
try {
debug('获取抽奖ID');
const giveawayId = window.location.pathname;
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({
text: I18n('getFailed', 'GiveawayId')
});
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'Gleam.getGiveawayId');
return false;
}
}
async #getGleamLink(title) {
try {
debug('获取 Gleam 链接', {
title: title
});
const logStatus = echoLog({
text: I18n('gettingGleamLink')
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: 'https://www.vloot.io/api/v1/giveaways',
method: 'GET',
responseType: 'json'
});
if (result === 'Success') {
if (data?.status === 200 && data?.response?.Success === true && data?.response?.Data) {
const {link: link} = data.response.Data.find((giveaway => title.replace(/[\s]/g, '').toLowerCase().includes(giveaway.title.replace(/[\s]/g, '').toLowerCase()))) || {};
if (link) {
debug('获取链接成功', {
link: link
});
logStatus.success();
return link;
}
debug('获取链接失败');
logStatus.error(`Error:${I18n('getLinkFailed')}`);
return false;
}
debug('API响应错误', {
status: data?.status,
statusText: data?.statusText
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('请求失败', {
result: result,
status: status,
statusText: statusText
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
} catch (error) {
debug('获取 Gleam 链接失败', {
error: error
});
throwError(error, 'Gleam.getGleamLink');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const campaignString = $('div.popup-blocks-container').attr('ng-init')?.match(/initCampaign\(([\w\W]+?)\)$/)?.[1];
if (!campaignString) {
debug('未找到活动配置信息');
return false;
}
const {campaign: campaign, incentive: incentive} = JSON.parse(campaignString);
const controllerString = $('div.campaign.reward').attr('ng-init')?.match(/initContestant\(([\w\W]+?)\);/)?.[1];
let ownedKey = false;
if (controllerString) {
if (JSON.parse(controllerString).contestant?.claims?.incentives?.[incentive.id]?.length) {
debug('用户已拥有密钥');
ownedKey = true;
}
}
const isGiveawayInvalid = campaign.banned || campaign.finished && !ownedKey || campaign.paused || (new Date).getTime() < campaign.starts_at * 1e3;
debug('检查抽奖状态', {
banned: campaign.banned,
finished: campaign.finished,
ownedKey: ownedKey,
paused: campaign.paused,
notStarted: (new Date).getTime() < campaign.starts_at * 1e3
});
if (!isGiveawayInvalid) {
return true;
}
debug('抽奖无效,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('giveawayNotWork'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'Gleam.checkLeftKey');
return false;
}
}
}
const defaultOptions = {
username: '',
email: ''
};
class SweepWidget extends Website {
name='SweepWidget';
options={
...defaultOptions,
...GM_getValue('SweepWidgetOptions')
};
buttons=[ 'doTask' ];
static test() {
const {host: host} = window.location;
const isMatch = /^sweepwidget\.com$/.test(host);
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return /^https?:\/\/sweepwidget\.com\/view\/[\d]+/.test(window.location.href);
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('检查登录失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'SweepWidget.after');
}
}
init() {
try {
debug('开始初始化');
const logStatus = echoLog({
text: I18n('initing')
});
if (!this.#checkLogin()) {
debug('需要登录');
logStatus.warning(I18n('needLogin'));
return false;
}
if (!this.#getGiveawayId()) {
debug('获取抽奖ID失败');
return false;
}
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'SweepWidget.init');
return false;
}
}
classifyTask() {
debug('任务分类完成');
return true;
}
async doTask() {
try {
debug('开始执行任务');
if ($('#unlock_rewards_main_wrapper').length === 0) {
debug('未显示奖励界面,尝试登录');
if ($('input[name="sw__login_name"]:visible').length > 0) {
debug('填写用户名', {
username: this.options.username
});
$('input[name="sw__login_name"]').val(this.options.username);
}
if ($('input[name="sw__login_email"]:visible').length > 0) {
debug('填写邮箱', {
email: this.options.email
});
$('input[name="sw__login_email"]').val(this.options.email);
}
if ($('#sw_login_button:visible').length > 0) {
debug('点击登录按钮');
$('#sw_login_button')[0].click();
}
const isEntered = await this.#checkEnter();
if (!isEntered) {
debug('进入抽奖失败');
return false;
}
}
const logStatus = echoLog({
text: I18n('SweepWidgetNotice')
});
const tasks = $('#sw_inner_entry_methods_l2_wrapper>div.sw_entry');
debug('找到任务列表', {
count: tasks.length
});
for (const task of tasks) {
const $task = $(task);
if ($task.find('i.fa-check:visible').length > 0) {
debug('跳过已完成的任务');
continue;
}
const title = $task.find('.sw_text_inner');
const aElement = $task.find('a.sw_link');
const link = aElement.attr('href');
if (!link) {
debug('跳过无效链接的任务');
continue;
}
debug('处理任务', {
title: title.text(),
link: link
});
title[0].click();
aElement.attr('href', '#a').attr('target', '_self');
aElement[0]?.click();
await delay(300);
aElement.attr('href', link).attr('target', '_blank');
debug('填写测试文本');
$task.find('input[type="text"]').val('test');
const verifyBtn = $task.find('input.sw_verify');
if (verifyBtn.prop('disabled') === true) {
debug('验证按钮被禁用,尝试重新激活');
title[0].click();
await delay(300);
title[0].click();
await delay(300);
}
debug('点击验证按钮');
$task.find('input.sw_verify').removeAttr('disabled')[0]?.click();
await this.#checkFinish($task);
const randomDelay = parseInt(`${Math.random() * (3e3 - 1e3 + 1) + 1e3}`, 10);
debug('等待随机延迟', {
delay: randomDelay
});
await delay(randomDelay);
}
debug('所有任务执行完成');
logStatus.success();
return true;
} catch (error) {
debug('执行任务失败', {
error: error
});
throwError(error, 'SweepWidget.doTask');
return false;
}
}
#checkLogin() {
try {
debug('检查登录状态');
if ($('#twitter_login_button').length > 0) {
debug('点击 Twitter 登录按钮');
$('#twitter_login_button')[0].click();
}
debug('登录检查完成');
return true;
} catch (error) {
debug('检查登录失败', {
error: error
});
throwError(error, 'SweepWidget.checkLogin');
return false;
}
}
#getGiveawayId() {
try {
debug('开始获取抽奖ID');
const giveawayId = window.location.href.match(/\/view\/([\d]+)/)?.[1];
if (!giveawayId) {
debug('获取抽奖ID失败');
echoLog({
text: I18n('getFailed', 'GiveawayId')
});
return false;
}
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'SweepWidget.getGiveawayId');
return false;
}
}
async #checkEnter() {
try {
debug('开始检查是否进入抽奖');
return new Promise((resolve => {
const checker = setInterval((() => {
if ($('#unlock_rewards_main_wrapper').length === 0) {
debug('等待进入抽奖...');
return;
}
debug('成功进入抽奖');
clearInterval(checker);
resolve(true);
}), 500);
}));
} catch (error) {
debug('检查进入抽奖失败', {
error: error
});
throwError(error, 'SweepWidget.checkEnter');
return false;
}
}
async #checkFinish($task) {
try {
debug('开始检查任务完成状态');
return new Promise((resolve => {
const checker = setInterval((() => {
const isCompleted = $task.find('i.fa-check:visible').length > 0 || $task.find('.sw_entry_input:visible').length === 0;
if (!isCompleted) {
debug('等待任务完成...');
return;
}
debug('任务完成');
clearInterval(checker);
resolve(true);
}), 500);
}));
} catch (error) {
debug('检查任务完成状态失败', {
error: error
});
throwError(error, 'SweepWidget.checkFinish');
return false;
}
}
}
const processFormData = formData => {
debug('开始处理表单数据', {
formDataLength: formData.length
});
const data = {};
formData.forEach((value => {
data[value.name] = value.value;
}));
debug('表单数据处理完成', {
processedData: data
});
return data;
};
const updateGlobalOption = (element, data) => {
const name = $(element).attr('name');
if (!name) {
debug('元素缺少name属性', {
element: element
});
return;
}
debug('开始更新全局选项', {
name: name
});
const keys = name.split('.');
const value = data[name];
const processedValue = value ? value === 'on' ? true : value : value ?? false;
debug('处理选项值', {
keys: keys,
originalValue: value,
processedValue: processedValue
});
if (keys.length === 3) {
globalOptions[keys[0]][keys[1]][keys[2]] = processedValue;
debug('更新三级选项', {
path: keys.join('.'),
value: processedValue
});
} else if (keys.length === 2) {
globalOptions[keys[0]][keys[1]] = processedValue;
debug('更新二级选项', {
path: keys.join('.'),
value: processedValue
});
}
};
const generateFormRow = (type, option, data, isFirstOption, totalOptions) => {
debug('开始生成表单行', {
type: type,
option: option,
isFirstOption: isFirstOption,
totalOptions: totalOptions
});
const backgroundColor = `${stringToColour(type)}44`;
const headerBackgroundColor = `${stringToColour(type)}66`;
if ([ 'other', 'position', 'hotKey', 'ASF' ].includes(type)) {
const header = isFirstOption ? `<th rowspan="${totalOptions}" style="background-color: ${headerBackgroundColor}">${I18n(type)}</th>` : '';
if (typeof data === 'boolean') {
debug('生成布尔类型选项行', {
type: type,
option: option,
value: data
});
return `\n <tr style="background-color: ${backgroundColor}">\n ${header}\n <td>${I18n(option)}</td>\n <td>\n <label>\n <input type="checkbox" name="${type}.${option}"${data ? ' checked="checked"' : ''}/>\n <span><i></i></span>\n </label>\n </td>\n </tr>`;
}
debug('生成文本类型选项行', {
type: type,
option: option,
value: data
});
return `\n <tr style="background-color: ${backgroundColor}">\n ${header}\n <td>${I18n(option)}</td>\n <td>\n <input class="editOption" type="text" name="${type}.${option}" value="${data}"/>\n </td>\n </tr>`;
}
debug('生成社交媒体选项行', {
type: type,
option: option,
dataKeys: Object.keys(data)
});
return Object.entries(data).map((([socialType, value]) => `\n <tr style="background-color: ${stringToColour(option)}66">\n ${isFirstOption ? `<th rowspan="${totalOptions}" style="background-color: ${headerBackgroundColor}">${I18n(type)}</th>` : ''}\n <td>${option}.${I18n(socialType)}</td>\n <td>\n <label>\n <input type="checkbox" name="${type}.${option}.${socialType}"${value ? ' checked="checked"' : ''}/>\n <span><i></i></span>\n </label>\n </td>\n </tr>`)).join('');
};
const generateGlobalOptionsForm = () => {
debug('开始生成全局选项表单');
const formRows = Object.entries(globalOptions).map((([type, data1]) => {
debug('处理选项类型', {
type: type,
optionsCount: Object.keys(data1).length
});
return Object.entries(data1).map((([option, data2], index) => {
const totalOptions = [ 'other', 'position', 'hotKey', 'ASF' ].includes(type) ? Object.keys(data1).length : Object.values(data1).reduce(((acc, cur) => acc + Object.keys(cur).length), 0);
return generateFormRow(type, option, data2, index === 0, totalOptions);
})).join('');
})).join('');
debug('表单生成完成');
return `\n <form id="globalOptionsForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('type')}</td>\n <td>${I18n('option')}</td>\n <td>${I18n('value')}</td>\n </tr>\n </thead>\n <tbody>\n ${formRows}\n </tbody>\n </table>\n </form>`;
};
const saveData = () => {
try {
debug('开始保存全局选项数据');
const formData = $('#globalOptionsForm').serializeArray();
debug('获取表单数据', {
formDataLength: formData.length
});
const data = processFormData(formData);
debug('开始更新全局选项');
$.makeArray($('#globalOptionsForm input')).forEach((element => {
updateGlobalOption(element, data);
}));
GM_setValue('globalOptions', globalOptions);
debug('全局选项保存完成');
Swal.fire({
title: I18n('changeGlobalOptionsSuccess'),
icon: 'success'
});
} catch (error) {
debug('保存全局选项时发生错误', {
error: error
});
throwError(error, 'saveData');
}
};
const changeGlobalOptions = showType => {
try {
debug('开始显示全局选项配置界面', {
showType: showType
});
const formHtml = generateGlobalOptionsForm();
if (showType === 'swal') {
debug('使用弹窗显示选项');
Swal.fire({
title: I18n('globalOptions'),
html: formHtml,
showConfirmButton: true,
confirmButtonText: I18n('save'),
showCancelButton: true,
cancelButtonText: I18n('close')
}).then((({isConfirmed: isConfirmed}) => {
if (isConfirmed) {
debug('用户确认保存选项');
saveData();
} else {
debug('用户取消保存选项');
}
}));
} else {
debug('使用页面内显示选项');
$('body').append(`<h2>${I18n('globalOptions')}</h2>${formHtml}`);
}
} catch (error) {
debug('显示全局选项配置界面时发生错误', {
error: error
});
throwError(error, 'changeGlobalOptions');
}
};
const defaultWhiteList = {
discord: {
servers: []
},
instagram: {
users: []
},
twitch: {
channels: []
},
twitter: {
users: [],
retweets: [],
likes: []
},
vk: {
names: []
},
youtube: {
channels: [],
likes: []
},
reddit: {
reddits: []
},
steam: {
groups: [],
officialGroups: [],
wishlists: [],
follows: [],
forums: [],
workshops: [],
curators: [],
workshopVotes: [],
curatorLikes: [],
announcements: [],
licenses: [],
playtests: [],
playTime: []
}
};
const REGEX_PATTERNS = {
DISCORD_INVITE: /invite\/(.+)/,
INSTAGRAM_USER: /https:\/\/www\.instagram\.com\/(.+)?\//,
TWITCH_CHANNEL: /https:\/\/(www\.)?twitch\.tv\/(.+)/,
TWITTER_USER: /https:\/\/twitter\.com\/(.+)/,
TWITTER_STATUS: /https:\/\/twitter\.com\/.*?\/status\/([\d]+)/,
VK_NAME: /https:\/\/vk\.com\/([^/]+)/,
REDDIT_USER: /https?:\/\/www\.reddit\.com\/user\/([^/]*)/,
REDDIT_SUBREDDIT: /https?:\/\/www\.reddit\.com\/r\/([^/]*)/,
STEAM_GROUP: /groups\/(.+)\/?/,
STEAM_APP: /app\/([\d]+)/,
STEAM_WORKSHOP: /\?id=([\d]+)/,
STEAM_CURATOR: /curator\/([\d]+)/,
STEAM_STORE: /https?:\/\/store\.steampowered\.com\/(.*?)\/([^/?]+)/
};
const link2id = async function(type) {
try {
debug('开始从链接获取ID', {
type: type
});
const link = $('#socialLink').val();
let id = '';
switch (type) {
case 'discord.servers':
id = REGEX_PATTERNS.DISCORD_INVITE.exec(link)?.[1] || '';
break;
case 'instagram.users':
id = REGEX_PATTERNS.INSTAGRAM_USER.exec(link)?.[1] || '';
break;
case 'twitch.channels':
id = REGEX_PATTERNS.TWITCH_CHANNEL.exec(link)?.[2] || '';
break;
case 'twitter.users':
id = REGEX_PATTERNS.TWITTER_USER.exec(link)?.[1] || '';
break;
case 'twitter.retweets':
id = REGEX_PATTERNS.TWITTER_STATUS.exec(link)?.[1] || '';
break;
case 'vk.names':
id = REGEX_PATTERNS.VK_NAME.exec(link)?.[1] || '';
break;
case 'youtube.channels':
id = (await getInfo(link, 'channel'))?.params?.channelId || '';
break;
case 'youtube.likes':
id = (await getInfo(link, 'likeVideo'))?.params?.videoId || '';
break;
case 'reddit.reddits':
{
const userMatch = REGEX_PATTERNS.REDDIT_USER.exec(link);
const subredditMatch = REGEX_PATTERNS.REDDIT_SUBREDDIT.exec(link);
id = userMatch?.[1] || subredditMatch?.[1] || '';
break;
}
case 'steam.groups':
id = REGEX_PATTERNS.STEAM_GROUP.exec(link)?.[1] || '';
break;
case 'steam.wishlists':
case 'steam.follows':
case 'steam.forums':
case 'steam.playtests':
case 'steam.playTime':
id = REGEX_PATTERNS.STEAM_APP.exec(link)?.[1] || '';
break;
case 'steam.workshops':
id = REGEX_PATTERNS.STEAM_WORKSHOP.exec(link)?.[1] || '';
break;
case 'steam.curators':
{
if (link.includes('curator')) {
id = REGEX_PATTERNS.STEAM_CURATOR.exec(link)?.[1] || '';
} else {
const storeMatch = REGEX_PATTERNS.STEAM_STORE.exec(link);
if (!storeMatch) {
break;
}
const [, param1, param2] = storeMatch;
const steam = new Steam;
if (await steam.init()) {
id = await steam.getCuratorId(param1, param2) || '';
}
}
break;
}
}
debug('从链接获取ID结果', {
type: type,
id: id
});
return id;
} catch (error) {
debug('从链接获取ID时发生错误', {
error: error
});
throwError(error, 'link2id');
return I18n('getFailed', 'id');
}
};
const disabledType = {
steam: [ 'workshopVotes', 'curatorLikes', 'announcements' ],
twitter: [ 'likes' ]
};
const assignWhiteList = whiteList => {
try {
debug('开始合并白名单');
const newWhiteList = {};
for (const [key, value] of Object.entries(defaultWhiteList)) {
newWhiteList[key] = {
...value,
...whiteList[key]
};
}
debug('白名单合并完成');
return newWhiteList;
} catch (error) {
debug('合并白名单时发生错误', {
error: error
});
throwError(error, 'assignWhiteList');
return defaultWhiteList;
}
};
const whiteListOptions = function(showType) {
try {
debug('开始显示白名单选项', {
showType: showType
});
const whiteList = assignWhiteList(GM_getValue('whiteList') || {});
let whiteListOptionsForm = `<form id="whiteListForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('website')}</td>\n <td>${I18n('type')}</td>\n <td>${I18n('edit')}</td>\n </tr>\n </thead>\n <tbody>`;
for (const [social, types] of Object.entries(whiteList)) {
const validTypes = Object.keys(types).filter((type => !disabledType[social]?.includes(type)));
whiteListOptionsForm += validTypes.map(((type, index) => {
const bgColor = `${stringToColour(social)}66`;
return `\n <tr style="background-color: ${bgColor}">\n ${index === 0 ? `<th rowspan="${validTypes.length}" style="background-color: ${bgColor}">${social}</th>` : ''}\n <td>${I18n(type)}</td>\n <td><button type="button" class="editWhiteList" data-value="${social}.${type}">${I18n('edit')}</button></td>\n </tr>`;
})).join('');
}
whiteListOptionsForm += '</tbody></table></form>';
if (showType === 'swal') {} else {
debug('使用页面显示白名单选项');
$('body').append(`<h2>${I18n('whiteList')}</h2>${whiteListOptionsForm}`);
}
$('.editWhiteList').on('click', (function() {
const value = $(this).attr('data-value');
if (!value) {
return;
}
const [social, type] = value.split('.');
const currentList = whiteList[social]?.[type];
if (!currentList) {
debug('未找到白名单配置', {
social: social,
type: type
});
echoLog({}).warning(I18n('whiteListNotFound', value));
return;
}
debug('编辑白名单', {
social: social,
type: type
});
Swal.fire({
title: I18n('changeWhiteListOption', value),
input: 'textarea',
html: `\n <input id="socialLink" class="swal2-input" placeholder="在此处输入链接获取id">\n <button id="link2id" data-type="${value}" class="swal2-confirm swal2-styled">获取id</button>\n <p style="margin-bottom:0 !important;">在下方填写白名单,每行一个</p>\n `,
inputValue: currentList.join('\n'),
showConfirmButton: true,
confirmButtonText: I18n('save'),
showCancelButton: true,
cancelButtonText: I18n('close'),
showDenyButton: true,
denyButtonText: I18n('return')
}).then((({isDenied: isDenied, isConfirmed: isConfirmed, value: value}) => {
if (isDenied) {
debug('返回白名单选项');
if (showType === 'swal') {}
return;
}
if (isConfirmed && value) {
debug('保存白名单更改', {
social: social,
type: type,
value: value
});
whiteList[social][type] = value.split('\n').filter(Boolean);
GM_setValue('whiteList', whiteList);
Swal.fire({
title: I18n('changeWhiteListSuccess'),
icon: 'success'
});
}
}));
$('#link2id').on('click', (async function() {
const type = $(this).attr('data-type');
if (!type) {
return;
}
debug('从链接获取ID按钮点击', {
type: type
});
const id = await link2id(type);
$('#socialLink').val(id);
}));
}));
} catch (error) {
debug('显示白名单选项时发生错误', {
error: error
});
throwError(error, 'whiteListOptions');
}
};
const setGistData = async (token, gistId, fileName, content) => {
try {
debug('开始设置Gist数据', {
gistId: gistId,
fileName: fileName
});
const logStatus = echoLog({
text: I18n('settingData')
});
const contentData = JSON.stringify({
files: {
[fileName]: {
content: JSON.stringify(content)
}
}
});
debug('准备发送的数据', {
contentData: contentData
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://api.github.com/gists/${gistId}`,
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `token ${token}`
},
data: contentData,
responseType: 'json',
method: 'POST',
timeout: 3e4
});
if (result !== 'Success') {
debug('设置Gist数据失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
const expectedContent = JSON.stringify(content);
if (data?.status !== 200 || data?.response?.files?.[fileName]?.content !== expectedContent) {
debug('设置Gist数据验证失败', {
status: data?.status,
content: data?.response?.files?.[fileName]?.content
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('设置Gist数据成功');
logStatus.success();
return true;
} catch (error) {
debug('设置Gist数据发生错误', {
error: error
});
throwError(error, 'setGistData');
return false;
}
};
const getGistData = async (token, gistId, fileName, test = false) => {
try {
debug('开始获取Gist数据', {
gistId: gistId,
fileName: fileName,
test: test
});
const logStatus = echoLog({
text: I18n('gettingData')
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://api.github.com/gists/${gistId}`,
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `token ${token}`
},
responseType: 'json',
method: 'GET',
timeout: 3e4
});
if (result !== 'Success') {
debug('获取Gist数据失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200) {
debug('获取Gist数据状态码错误', {
status: data?.status
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
const content = data.response?.files?.[fileName]?.content;
if (!content) {
debug('获取的Gist数据为空');
logStatus.error(`Error:${I18n('noRemoteData')}`);
return false;
}
if (test) {
debug('Gist数据测试成功');
logStatus.success();
return true;
}
try {
const formatedContent = JSON.parse(content);
debug('Gist数据解析成功', {
contentLength: Object.keys(formatedContent).length
});
logStatus.success();
return formatedContent;
} catch (error) {
debug('Gist数据解析失败', {
error: error
});
logStatus.error(`Error:${I18n('errorRemoteDataFormat')}`);
console.log('%c%s', 'color:white;background:red', `Auto-Task[Error]: getGistData\n${error.stack}`);
return false;
}
} catch (error) {
debug('获取Gist数据发生错误', {
error: error
});
throwError(error, 'getGistData');
return false;
}
};
const syncOptions = async () => {
try {
debug('开始同步选项配置');
const defaultOptions = {
TOKEN: '',
GIST_ID: '',
FILE_NAME: '',
SYNC_HISTORY: true
};
let syncOptions = GM_getValue('gistOptions') || defaultOptions;
debug('获取已保存的同步选项', syncOptions);
const createForm = options => `\n <div class="gist-options-form">\n <p>\n <label for="github-token">Github Token</label>\n <input\n id="github-token"\n class="swal2-input"\n placeholder="Github Token"\n value="${options.TOKEN}"\n autocomplete="off"\n spellcheck="false"\n />\n </p>\n <p>\n <label for="gist-id">Gist ID</label>\n <input\n id="gist-id"\n class="swal2-input"\n placeholder="Gist ID"\n value="${options.GIST_ID}"\n autocomplete="off"\n spellcheck="false"\n />\n </p>\n <p>\n <label for="file-name">${I18n('fileName')}</label>\n <input\n id="file-name"\n class="swal2-input"\n placeholder="${I18n('fileName')}"\n value="${options.FILE_NAME}"\n autocomplete="off"\n spellcheck="false"\n />\n </p>\n <p class="sync-history-wrapper">\n <label for="sync-history" class="swal2-checkbox-custom">\n <input\n id="sync-history"\n type="checkbox"\n ${options.SYNC_HISTORY ? 'checked="checked"' : ''}\n />\n <span class="swal2-label">${I18n('syncHistory')}</span>\n </label>\n </p>\n <div class="button-group">\n <button id="upload-data" type="button" class="swal2-confirm swal2-styled" onclick="handleUpload()">\n ${I18n('upload2gist')}\n </button>\n <button id="download-data" type="button" class="swal2-confirm swal2-styled" onclick="handleDownload()">\n ${I18n('downloadFromGist')}\n </button>\n </div>\n </div>\n `;
const showConfigDialog = async () => {
debug('显示配置对话框');
const result = await Swal.fire({
title: I18n('gistOptions'),
html: createForm(syncOptions),
focusConfirm: false,
showLoaderOnConfirm: true,
footer: `<a href="https://auto-task-doc.js.org/guide/#%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5" target="_blank">${I18n('help')}</a>`,
preConfirm: async () => {
const options = {
TOKEN: $('#github-token').val().trim(),
GIST_ID: $('#gist-id').val().trim(),
FILE_NAME: $('#file-name').val().trim(),
SYNC_HISTORY: $('#sync-history').prop('checked')
};
debug('保存新的同步选项', options);
GM_setValue('gistOptions', options);
syncOptions = options;
return await getGistData(options.TOKEN, options.GIST_ID, options.FILE_NAME, true);
},
allowOutsideClick: () => !Swal.isLoading(),
confirmButtonText: I18n('saveAndTest'),
showCancelButton: true,
cancelButtonText: I18n('close')
});
if (result.value) {
debug('配置测试成功');
await Swal.fire({
icon: 'success',
title: I18n('testSuccess'),
timer: 2e3,
timerProgressBar: true
});
await showConfigDialog();
} else if (result.value !== undefined) {
debug('配置测试失败');
await Swal.fire({
icon: 'error',
title: I18n('testFailed'),
timer: 2e3,
timerProgressBar: true
});
await showConfigDialog();
}
};
const handleUpload = async () => {
debug('开始处理数据上传');
const options = GM_getValue('gistOptions');
if (!options?.TOKEN || !options?.GIST_ID || !options?.FILE_NAME) {
debug('配置信息不完整');
await Swal.fire({
icon: 'error',
title: I18n('saveAndTestNotice')
});
await showConfigDialog();
return;
}
debug('显示数据处理提示');
Swal.fire({
icon: 'info',
title: I18n('processingData'),
allowOutsideClick: false
});
const data = {};
const names = GM_listValues();
const syncHistory = $('#sync-history').prop('checked');
debug('开始收集数据', {
namesCount: names.length,
syncHistory: syncHistory
});
for (const name of names) {
if (name === 'gistOptions' || /^[\w]+?Auth$/.test(name)) {
continue;
}
if (!syncHistory && /^[\w]+?Tasks-/.test(name)) {
continue;
}
data[name] = GM_getValue(name);
}
debug('数据收集完成', {
dataKeysCount: Object.keys(data).length
});
Swal.update({
icon: 'info',
title: I18n('updatingData')
});
const success = await setGistData(options.TOKEN, options.GIST_ID, options.FILE_NAME, data);
debug('数据上传完成', {
success: success
});
await Swal.fire({
icon: success ? 'success' : 'error',
title: I18n(success ? 'syncDataSuccess' : 'syncDataFailed'),
timer: 2e3,
timerProgressBar: true
});
};
const handleDownload = async () => {
debug('开始处理数据下载');
const options = GM_getValue('gistOptions');
if (!options?.TOKEN || !options?.GIST_ID || !options?.FILE_NAME) {
debug('配置信息不完整');
await Swal.fire({
icon: 'error',
title: I18n('saveAndTestNotice')
});
await showConfigDialog();
return;
}
debug('显示数据下载提示');
Swal.fire({
icon: 'info',
title: I18n('downloadingData'),
allowOutsideClick: false
});
const data = await getGistData(options.TOKEN, options.GIST_ID, options.FILE_NAME);
if (!data || typeof data === 'boolean') {
debug('未检测到远程数据');
await Swal.fire({
icon: 'error',
title: I18n('checkedNoData')
});
await showConfigDialog();
return;
}
debug('开始保存数据');
Swal.update({
icon: 'info',
title: I18n('savingData')
});
const syncHistory = $('#sync-history').prop('checked');
let savedCount = 0;
for (const [name, value] of Object.entries(data)) {
if (!syncHistory && /^[\w]+?Tasks-/.test(name)) {
continue;
}
GM_setValue(name, value);
savedCount += 1;
}
debug('数据保存完成', {
savedCount: savedCount
});
await Swal.fire({
icon: 'success',
title: I18n('syncDataSuccess'),
timer: 2e3,
timerProgressBar: true
});
};
unsafeWindow.handleUpload = handleUpload;
unsafeWindow.handleDownload = handleDownload;
await showConfigDialog();
} catch (error) {
debug('同步选项发生错误', {
error: error
});
throwError(error, 'syncOptions');
await Swal.fire({
icon: 'error',
title: I18n('error'),
text: error instanceof Error ? error.message : 'Unknown error occurred',
timer: 3e3,
timerProgressBar: true
});
}
};
const VALID_SIDES_X = [ 'right', 'left' ];
const VALID_SIDES_Y = [ 'top', 'bottom' ];
class Setting {
name='Setting';
buttons=[ 'saveGlobalOptions', 'syncData', 'tasksHistory' ];
syncData=syncOptions;
selectors={
body: 'body',
autoTaskInfo: '#auto-task-info',
autoTaskButtons: '#auto-task-buttons',
showButtonDiv: 'div.show-button-div',
positionInputs: 'input[name^="position"]',
hotKeyInputs: 'input[name^="hotKey"]'
};
tasksHistory() {
debug('打开任务历史记录页面');
GM_openInTab('https://auto-task.hclonely.com/history.html', {
active: true
});
}
static test() {
const {host: host, pathname: pathname} = window.location;
const isMatch = [ 'auto-task.hclonely.com', 'auto-task-doc.js.org' ].includes(host) && pathname === '/setting.html';
debug('检查设置页面匹配', {
host: host,
pathname: pathname,
isMatch: isMatch
});
return isMatch;
}
before() {
try {
debug('开始清空页面内容');
$(this.selectors.body).html('').addClass('auto-task-options');
debug('页面内容已清空');
} catch (error) {
debug('清空页面内容失败', {
error: error
});
throwError(error, 'Setting.before');
}
}
async after() {
try {
debug('开始初始化设置页面');
await this.#initializeEnvironment();
this.#initializeGlobalSettings();
this.#setupSocialButtons();
this.#setupPositionHandlers();
this.#setupHotKeyHandlers();
debug('设置页面初始化完成');
} catch (error) {
debug('设置页面初始化失败', {
error: error
});
throwError(error, 'Setting.after');
}
}
saveGlobalOptions() {
try {
debug('开始保存全局选项');
saveData();
debug('全局选项保存完成');
} catch (error) {
debug('保存全局选项失败', {
error: error
});
throwError(error, 'Setting.saveGlobalOptions');
}
}
async #initializeEnvironment() {
try {
debug('开始初始化环境信息');
const userAgent = await browser.getInfo();
debug('获取用户代理信息', {
userAgent: userAgent
});
const environmentHtml = this.#generateEnvironmentHtml(userAgent);
$(this.selectors.body).append(`<h2>${I18n('environment')}</h2>${environmentHtml}`);
debug('环境信息初始化完成');
} catch (error) {
debug('初始化环境信息失败', {
error: error
});
throwError(error, 'Setting.initializeEnvironment');
}
}
#generateEnvironmentHtml(userAgent) {
return `\n <form id="environmentForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('type')}</td>\n <td>${I18n('name')}</td>\n <td>${I18n('version')}</td>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>${I18n('os')}</td>\n <td>${userAgent.system}</td>\n <td>${userAgent.systemVersion}</td>\n </tr>\n <tr>\n <td>${I18n('browser')}</td>\n <td>${userAgent.browser}</td>\n <td>${userAgent.browserVersion}</td>\n </tr>\n <tr>\n <td>${I18n('scriptManager')}</td>\n <td>${GM_info.scriptHandler}</td>\n <td>${GM_info.version}</td>\n </tr>\n <tr>\n <td>${I18n('script')}</td>\n <td>${GM_info.script.name}</td>\n <td>${GM_info.script.version}</td>\n </tr>\n </tbody>\n </table>\n </form>\n `;
}
#initializeGlobalSettings() {
debug('开始初始化全局设置');
changeGlobalOptions('page');
whiteListOptions('page');
debug('全局设置初始化完成');
}
#setupSocialButtons() {
debug('开始设置社交媒体按钮');
this.#addSocialButton('other.twitterVerifyId', 'getTwitterUserId', 'twitterUser');
this.#addSocialButton('other.youtubeVerifyChannel', 'getYoutubeChannelId', 'youtubeChannel');
debug('社交媒体按钮设置完成');
}
#addSocialButton(inputName, buttonId, socialType) {
debug('添加社交媒体按钮', {
inputName: inputName,
buttonId: buttonId,
socialType: socialType
});
$(`input[name="${inputName}"]`).after(`<button id="${buttonId}" type="button">${I18n(`get${buttonId}`)}</button>`);
$(`#${buttonId}`).on('click', (() => this.#getId(socialType)));
debug('社交媒体按钮添加完成');
}
#setupPositionHandlers() {
debug('开始设置位置处理器');
$(this.selectors.positionInputs).on('input', (event => {
const input = $(event.target);
const type = input.attr('name')?.replace('position.', '');
if (!type) {
debug('无效的位置类型');
return;
}
debug('处理位置变化', {
type: type
});
this.#handlePositionChange(type);
}));
debug('位置处理器设置完成');
}
#handlePositionChange(type) {
debug('开始处理位置变化', {
type: type
});
const config = this.#getPositionConfig(type);
if (!config) {
debug('获取位置配置失败');
return;
}
const {distance: distance, sideX: sideX, sideY: sideY} = config;
if (!this.#isValidPosition(distance, sideX, sideY)) {
debug('无效的位置配置', {
distance: distance,
sideX: sideX,
sideY: sideY
});
return;
}
const [x, y] = distance.split(',');
const target = this.#getPositionTarget(type);
if (!target) {
debug('获取目标元素失败');
return;
}
debug('更新元素位置', {
target: target,
sideX: sideX,
sideY: sideY,
x: x,
y: y
});
this.#updateElementPosition(target, sideX, sideY, x, y);
}
#getPositionConfig(type) {
debug('获取位置配置', {
type: type
});
const baseType = type.replace(/(?:button|log|show)(?:SideX|SideY|Distance)$/, '');
const distance = $(`input[name="position.${baseType}Distance"]`).val();
const sideX = $(`input[name="position.${baseType}SideX"]`).val();
const sideY = $(`input[name="position.${baseType}SideY"]`).val();
const config = {
distance: distance,
sideX: sideX,
sideY: sideY
};
debug('位置配置', config);
return config;
}
#isValidPosition(distance, sideX, sideY) {
const isValid = VALID_SIDES_X.includes(sideX) && VALID_SIDES_Y.includes(sideY) && /^[\d]+?,[\d]+$/.test(distance);
debug('验证位置配置', {
distance: distance,
sideX: sideX,
sideY: sideY,
isValid: isValid
});
return isValid;
}
#getPositionTarget(type) {
const targetMap = {
button: this.selectors.autoTaskButtons,
showButton: this.selectors.showButtonDiv,
log: this.selectors.autoTaskInfo
};
const baseType = type.replace(/(?:SideX|SideY|Distance)$/, '');
return targetMap[baseType];
}
#updateElementPosition(selector, sideX, sideY, x, y) {
debug('更新元素位置', {
selector: selector,
sideX: sideX,
sideY: sideY,
x: x,
y: y
});
const $element = $(selector);
const oppositeX = sideX === 'right' ? 'left' : 'right';
const oppositeY = sideY === 'top' ? 'bottom' : 'top';
$element.css(sideX, `${x}px`).css(sideY, `${y}px`).css(oppositeX, '').css(oppositeY, '');
debug('元素位置更新完成');
}
#setupHotKeyHandlers() {
debug('开始设置热键处理器');
$(this.selectors.hotKeyInputs).attr('readonly', 'readonly').off('keydown').on('keydown', this.#handleHotKeyPress);
debug('热键处理器设置完成');
}
#handleHotKeyPress(event) {
debug('处理热键按下事件', {
key: event.key
});
const functionKeys = [];
if (event.altKey) {
functionKeys.push('alt');
}
if (event.ctrlKey) {
functionKeys.push('ctrl');
}
if (event.shiftKey) {
functionKeys.push('shift');
}
const key = event.key.length === 1 ? event.key.toLowerCase() : '';
const value = functionKeys.length ? `${functionKeys.join(' + ')} + ${key}` : key;
debug('设置热键值', {
functionKeys: functionKeys,
key: key,
value: value
});
$(event.target).val(value);
}
async #getId(social) {
try {
debug('开始获取社交媒体ID', {
social: social
});
const result = await Swal.fire({
title: I18n('getId', I18n(social)),
html: this.#generateIdInputHtml(social),
showCancelButton: true,
cancelButtonText: I18n('close'),
showConfirmButton: false
});
if (!result.isDismissed) {
debug('用户确认获取ID');
await this.#handleIdRetrieval(social);
} else {
debug('用户取消获取ID');
}
} catch (error) {
debug('获取社交媒体ID失败', {
error: error
});
throwError(error, 'Setting.getId');
}
}
#generateIdInputHtml(social) {
return `\n <input id="socialLink" class="swal2-input" placeholder="在此处输入链接获取id">\n <button id="link2id" data-type="${social}" class="swal2-confirm swal2-styled">获取id</button>\n `;
}
async #handleIdRetrieval(social) {
const link = $('#socialLink').val();
if (!link) {
debug('链接为空');
return;
}
debug('开始处理ID获取', {
social: social,
link: link
});
let id = '';
if (social === 'twitterUser') {
const name = link.match(/https:\/\/twitter\.com\/(.+)/)?.[1] || link;
debug('获取Twitter用户ID', {
name: name
});
id = await (new Twitter).userName2id(name) || '';
} else if (social === 'youtubeChannel') {
const name = this.#extractYoutubeUrl(link);
debug('获取YouTube频道信息', {
name: name
});
const info = await getInfo(name, 'channel');
id = info?.params?.channelId || '';
}
debug('ID获取结果', {
id: id
});
$('#socialLink').val(id);
}
#extractYoutubeUrl(link) {
debug('提取YouTube URL', {
link: link
});
if (/^https:\/\/(www\.)?google\.com.*?\/url\?.*?url=https:\/\/www.youtube.com\/.*/.test(link)) {
const extractedUrl = link.match(/url=(https:\/\/www\.youtube\.com\/.*)/)?.[1] || link;
debug('从Google搜索结果提取URL', {
extractedUrl: extractedUrl
});
return extractedUrl;
}
return link;
}
}
class History extends Keylol {
name='History';
buttons=[ 'doTask', 'undoTask', 'selectAll', 'selectNone', 'invertSelect', 'clearHistory' ];
static test() {
try {
const {host: host, pathname: pathname} = window.location;
const isMatch = [ 'auto-task.hclonely.com', 'auto-task-doc.js.org' ].includes(host) && pathname === '/history.html';
debug('检查是否为历史记录页面', {
host: host,
pathname: pathname,
isMatch: isMatch
});
return isMatch;
} catch (error) {
debug('检查历史记录页面时出错', {
error: error
});
throwError(error, 'History.test');
return false;
}
}
before() {
try {
debug('开始初始化历史记录页面');
$('body').html('<div class="container"></div>').addClass('auto-task-history');
debug('页面基础结构已创建');
const data = GM_listValues() || [];
debug('获取存储的所有值', {
count: data.length
});
const tasksHistory = data.map((value => /^[\w]+?Tasks-/.test(value) ? value : null)).filter((value => value));
debug('筛选任务历史记录', {
count: tasksHistory.length
});
tasksHistory.forEach((item => {
debug('处理任务项', {
item: item
});
this.#addItem(item);
}));
debug('历史记录页面初始化完成');
} catch (error) {
debug('初始化历史记录页面时出错', {
error: error
});
throwError(error, 'History.before');
}
}
clearHistory() {
try {
debug('开始清除历史记录');
const data = GM_listValues() || [];
debug('获取存储的所有值', {
count: data.length
});
const tasksHistory = data.map((value => /^[\w]+?Tasks-/.test(value) ? value : null)).filter((value => value));
debug('筛选要清除的任务历史记录', {
count: tasksHistory.length
});
tasksHistory.forEach((item => {
debug('删除任务项', {
item: item
});
GM_deleteValue(item);
}));
debug('历史记录清除完成');
Swal.fire({
title: I18n('clearHistoryFinished'),
icon: 'success'
});
} catch (error) {
debug('清除历史记录时出错', {
error: error
});
throwError(error, 'History.clearHistory');
}
}
#addItem(item) {
try {
debug('开始添加任务项', {
item: item
});
const tasksData = GM_getValue(item);
if (!tasksData?.tasks) {
debug('任务数据无效', {
item: item
});
return;
}
const {title: title, link: link} = this.#getTaskInfo(item);
if (!title || !link) {
debug('获取任务信息失败', {
item: item
});
return;
}
debug('生成任务HTML', {
item: item,
title: title,
link: link
});
const html = this.#generateTaskHtml(tasksData.tasks);
this.#appendTaskToContainer(item, title, link, html, tasksData.time);
this.#bindDeleteEvent();
debug('任务项添加完成', {
item: item
});
} catch (error) {
debug('添加任务项时出错', {
error: error,
item: item
});
throwError(error, 'History.addItem');
}
}
#getTaskInfo(item) {
try {
debug('开始获取任务信息', {
item: item
});
const [website, id] = item.split('-');
debug('解析任务标识符', {
website: website,
id: id
});
const taskInfoMap = {
fawTasks: {
title: `Freeanywhere[${id}]`,
link: `https://freeanywhere.net/#/giveaway/${id}`
},
gasTasks: {
title: `Giveawaysu[${id}]`,
link: `https://giveaway.su/giveaway/view/${id}`
},
gcTasks: {
title: `GiveeClub[${id}]`,
link: `https://givee.club/event/${id}`
},
gkTasks: {
title: `Givekey[${id}]`,
link: `https://givekey.ru/giveaway/${id}`
},
gleamTasks: {
title: `Gleam[${id}]`,
link: `https://gleam.io${id}`
},
khTasks: {
title: `keyhub[${id}]`,
link: `https://key-hub.eu/giveaway/${id}`
},
prysTasks: {
title: `Prys[${id}]`,
link: `https://prys.revadike.com/giveaway/?id=${id}`
}
};
const result = taskInfoMap[website] || {
title: '',
link: ''
};
debug('获取任务信息结果', {
result: result
});
return result;
} catch (error) {
debug('获取任务信息时出错', {
error: error,
item: item
});
throwError(error, 'History.getTaskInfo');
return {
title: '',
link: ''
};
}
}
#generateTaskHtml(tasks) {
try {
debug('开始生成任务HTML');
let html = '';
for (const [social, types] of Object.entries(tasks)) {
for (const [type, taskList] of Object.entries(types)) {
for (const task of taskList) {
debug('处理任务', {
social: social,
type: type,
task: task
});
const displayTask = task.length > 55 ? `${task.slice(0, 55)}...` : task;
html += `<li>\n <font class="auto-task-capitalize">${social}.${I18n(type.replace('Link', ''))}: </font>\n <a href="${task}" target="_blank">${displayTask}</a>\n </li>`;
}
}
}
debug('任务HTML生成完成');
return html;
} catch (error) {
debug('生成任务HTML时出错', {
error: error
});
throwError(error, 'History.generateTaskHtml');
return '';
}
}
#appendTaskToContainer(item, title, link, html, time) {
try {
debug('开始添加任务到容器', {
item: item,
title: title,
link: link
});
$('.container').append(`\n <div class="card" data-name="${item}">\n <div class="title">\n <a href="${link}" target="_blank">${title}</a>\n <span class="delete-task" data-name="${item}" title="${I18n('deleteTask')}">\n <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2734" width="32" height="32">\n <path d="M607.897867 768.043004c-17.717453 0-31.994625-14.277171-31.994625-31.994625L575.903242 383.935495c0-17.717453 14.277171-31.994625 31.994625-31.994625s31.994625 14.277171 31.994625 31.994625l0 351.94087C639.892491 753.593818 625.61532 768.043004 607.897867 768.043004z" p-id="2735" fill="#d81e06"></path>\n <path d="M415.930119 768.043004c-17.717453 0-31.994625-14.277171-31.994625-31.994625L383.935495 383.935495c0-17.717453 14.277171-31.994625 31.994625-31.994625 17.717453 0 31.994625 14.277171 31.994625 31.994625l0 351.94087C447.924744 753.593818 433.647573 768.043004 415.930119 768.043004z" p-id="2736" fill="#d81e06"></path>\n <path d="M928.016126 223.962372l-159.973123 0L768.043004 159.973123c0-52.980346-42.659499-95.983874-95.295817-95.983874L351.94087 63.989249c-52.980346 0-95.983874 43.003528-95.983874 95.983874l0 63.989249-159.973123 0c-17.717453 0-31.994625 14.277171-31.994625 31.994625s14.277171 31.994625 31.994625 31.994625l832.032253 0c17.717453 0 31.994625-14.277171 31.994625-31.994625S945.73358 223.962372 928.016126 223.962372zM319.946246 159.973123c0-17.545439 14.449185-31.994625 31.994625-31.994625l320.806316 0c17.545439 0 31.306568 14.105157 31.306568 31.994625l0 63.989249L319.946246 223.962372 319.946246 159.973123 319.946246 159.973123z" p-id="2737" fill="#d81e06"></path>\n <path d="M736.048379 960.010751 288.123635 960.010751c-52.980346 0-95.983874-43.003528-95.983874-95.983874L192.139761 383.591466c0-17.717453 14.277171-31.994625 31.994625-31.994625s31.994625 14.277171 31.994625 31.994625l0 480.435411c0 17.717453 14.449185 31.994625 31.994625 31.994625l448.096758 0c17.717453 0 31.994625-14.277171 31.994625-31.994625L768.215018 384.795565c0-17.717453 14.277171-31.994625 31.994625-31.994625s31.994625 14.277171 31.994625 31.994625l0 479.231312C832.032253 916.835209 789.028725 960.010751 736.048379 960.010751z" p-id="2738" fill="#d81e06"></path>\n </svg>\n </span>\n </div>\n <ul>${html}</ul>\n <span class="time">${I18n('lastChangeTime')}: ${dayjs(time).format('YYYY-MM-DD HH:mm:ss')}</span>\n </div>\n `);
debug('任务已添加到容器', {
item: item
});
} catch (error) {
debug('添加任务到容器时出错', {
error: error,
item: item
});
throwError(error, 'History.appendTaskToContainer');
}
}
#bindDeleteEvent() {
try {
debug('开始绑定删除事件');
$('span.delete-task').on('click', (function() {
const itemName = $(this).attr('data-name');
debug('点击删除按钮', {
itemName: itemName
});
if (!itemName) {
debug('删除失败:未找到任务名称');
Swal.fire({
title: I18n('clearTaskFailed'),
icon: 'error'
});
return;
}
GM_deleteValue(itemName);
$(`div.card[data-name="${itemName}"]`).remove();
debug('任务删除成功', {
itemName: itemName
});
Swal.fire({
title: I18n('clearTaskFinished'),
text: itemName,
icon: 'success'
});
}));
debug('删除事件绑定完成');
} catch (error) {
debug('绑定删除事件时出错', {
error: error
});
throwError(error, 'History.bindDeleteEvent');
}
}
}
const defaultTasksTemplate$1 = {
steam: {
groupLinks: [],
wishlistLinks: [],
followLinks: [],
curatorLinks: [],
curatorLikeLinks: []
},
twitter: {
userLinks: [],
retweetLinks: []
},
twitch: {
channelLinks: []
},
discord: {
serverLinks: []
},
youtube: {
channelLinks: []
},
extra: {
giveawayHopper: []
}
};
const defaultTasks$1 = JSON.stringify(defaultTasksTemplate$1);
class GiveawayHopper extends Website {
name='GiveawayHopper';
undoneTasks=JSON.parse(defaultTasks$1);
socialTasks=JSON.parse(defaultTasks$1);
tasks=[];
buttons=[ 'doTask', 'undoTask', 'verifyTask' ];
static test() {
const {host: host} = window.location;
const isMatch = host === 'giveawayhopper.com';
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('登录检查失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
const giveawayIdResult = this.#getGiveawayId();
debug('获取抽奖ID', {
success: giveawayIdResult,
id: this.giveawayId
});
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'GiveawayHopper.after');
}
}
async init() {
try {
debug('初始化 GiveawayHopper');
const logStatus = echoLog({
text: I18n('initing')
});
const leftKeyResult = await this.#checkLeftKey();
if (!leftKeyResult) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'GiveawayHopper.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
if (!this.giveawayId) {
debug('未找到抽奖ID,尝试获取');
await this.#getGiveawayId();
}
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('恢复已保存的任务信息');
this.socialTasks = GM_getValue(`giveawayHopperTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$1);
}
debug('请求任务列表');
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://giveawayhopper.com/api/v1/campaigns/${this.giveawayId}/with-auth`,
method: 'GET',
responseType: 'json',
headers: {
authorization: `Bearer ${window.sessionStorage.gw_auth}`,
'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1])
}
});
if (result !== 'Success') {
debug('请求任务列表失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || !data?.response?.tasks) {
debug('任务列表数据异常', {
status: data?.status,
response: data?.response
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('获取到任务列表', {
count: data.response.tasks.length
});
this.tasks = data.response.tasks;
for (const task of data.response.tasks) {
if (task.isDone) {
debug('跳过已完成任务', {
taskId: task.id,
type: task.type
});
continue;
}
debug('处理任务', {
taskId: task.id,
category: task.category,
type: task.type
});
await httpRequest({
url: `https://giveawayhopper.com/api/v1/campaigns/${this.giveawayId}/tasks/${task.id}/visited`,
method: 'GET',
responseType: 'json',
headers: {
authorization: `Bearer ${window.sessionStorage.gw_auth}`,
'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1])
}
});
if (task.category === 'Steam' && task.type === 'JoinGroup') {
debug('处理 Steam 组任务');
const steamGroupLink = await getRedirectLink(`https://steamcommunity.com/gid/${task.group_id}`);
if (!steamGroupLink) {
debug('获取 Steam 组链接失败');
continue;
}
debug('添加 Steam 组链接', {
action: action,
link: steamGroupLink
});
if (action === 'undo') {
this.socialTasks.steam.groupLinks.push(steamGroupLink);
}
if (action === 'do') {
this.undoneTasks.steam.groupLinks.push(steamGroupLink);
}
continue;
}
if (task.category === 'Discord' && task.type === 'JoinServer') {
const discordLink = `https://discord.gg/${task.invite_code}`;
debug('添加 Discord 服务器链接', {
action: action,
link: discordLink
});
if (action === 'undo') {
this.socialTasks.discord.serverLinks.push(discordLink);
}
if (action === 'do') {
this.undoneTasks.discord.serverLinks.push(discordLink);
}
continue;
}
if ([ 'TikTok', 'YouTube', 'General' ].includes(task.category)) {
debug('跳过特殊任务类型', {
category: task.category
});
continue;
}
debug('发现未知任务类型', {
category: task.category,
type: task.type
});
echoLog({}).warning(`${I18n('unKnownTaskType')}: ${task.category}-${task.type}`);
}
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.uniqueTasks(this.socialTasks);
debug('任务分类完成', {
undoneTasks: this.undoneTasks,
socialTasks: this.socialTasks
});
GM_setValue(`giveawayHopperTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'GiveawayHopper.classifyTask');
return false;
}
}
async verifyTask() {
try {
debug('开始验证任务');
for (const task of this.tasks) {
if (task.isDone) {
debug('跳过已完成任务', {
taskId: task.id
});
continue;
}
debug('验证任务', {
taskId: task.id,
name: task.displayName?.replace(':target', task.targetName) || task.name
});
const logStatus = echoLog({
text: `${I18n('verifyingTask')}[${task.displayName?.replace(':target', task.targetName) || task.name}]...`
});
if (!task.link) {
debug('获取任务链接');
task.link = this.#getTaskLink(task);
}
if (task.link) {
debug('访问任务链接', {
link: task.link
});
await this.#visitTaskLink(task);
}
await delay(1e3);
const verifyResult = await this.#verifyTask(task, logStatus);
debug('任务验证结果', {
taskId: task.id,
success: verifyResult
});
if (!verifyResult) {
continue;
}
}
debug('所有任务验证完成');
return true;
} catch (error) {
debug('任务验证失败', {
error: error
});
throwError(error, 'GiveawayHopper.verifyTask');
return false;
}
}
#getTaskLink(task) {
try {
debug('生成任务链接', {
category: task.category,
type: task.type
});
let link = '';
if (task.category === 'YouTube' && task.type === 'FollowAccount') {
link = `https://www.youtube.com/@${task.targetName}`;
} else if (task.category === 'TikTok' && task.type === 'FollowAccount') {
link = `https://www.tiktok.com/@${task.targetName}`;
} else if (task.category === 'Steam' && task.type === 'JoinGroup') {
link = '';
} else if (task.category === 'Discord' && task.type === 'JoinServer') {
link = '';
}
debug('生成的任务链接', {
link: link
});
return link;
} catch (error) {
debug('生成任务链接失败', {
error: error
});
throwError(error, 'GiveawayHopper.getTaskLink');
return '';
}
}
async #visitTaskLink(task) {
debug('访问任务链接', {
taskId: task.id,
link: task.link
});
await httpRequest({
url: `https://giveawayhopper.com/fw?url=${encodeURIComponent(task.link)}&src=campaign&src_id=${this.giveawayId}&ref=task&ref_id=${task.id}&token=${window.sessionStorage.gw_auth}`,
method: 'GET',
headers: {
authorization: `Bearer ${window.sessionStorage.gw_auth}`,
'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1])
}
});
}
async #verifyTask(task, logStatus) {
debug('验证任务', {
taskId: task.id,
category: task.category,
type: task.type
});
const postData = {
taskcategory: task.category,
taskname: task.type
};
if ([ 'YouTube', 'TikTok' ].includes(task.category)) {
postData.username = '1';
}
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: `https://giveawayhopper.com/api/v1/campaigns/${this.giveawayId}/tasks/${task.id}/complete`,
method: 'POST',
headers: {
authorization: `Bearer ${window.sessionStorage.gw_auth}`,
'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1]),
'content-type': 'application/json'
},
dataType: 'json',
data: JSON.stringify(postData)
});
if (result !== 'Success') {
debug('任务验证请求失败', {
result: result,
statusText: statusText,
status: status
});
logStatus.error(`${result}:${statusText}(${status})`);
return false;
}
if (data?.status !== 200 || !data?.response?.completed) {
debug('任务验证响应异常', {
status: data?.status,
response: data?.response
});
logStatus.error(`Error:${data?.statusText}(${data?.status})`);
return false;
}
debug('任务验证成功', {
taskId: task.id
});
logStatus.success();
return true;
}
#getGiveawayId() {
try {
debug('从URL获取抽奖ID');
const giveawayId = window.location.pathname.split('/').at(-1);
if (!giveawayId) {
debug('获取抽奖ID失败');
echoLog({
text: I18n('getFailed', 'GiveawayId')
});
return false;
}
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'GiveawayHopper.getGiveawayId');
return false;
}
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
const needLogin = $('div.widget-connections-edit:contains("Log in")').length > 0;
if (needLogin) {
debug('未登录,自动点击登录按钮');
$('div.widget-connections-edit:contains("Log in") a')[0].click();
}
debug('登录检查完成', {
needLogin: needLogin
});
return true;
} catch (error) {
debug('登录检查失败', {
error: error
});
throwError(error, 'GiveawayHopper.checkLogin');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const keyCount = parseInt($('p.widget-single-prize span').text()?.match(/\d+/)?.[0] || '0', 10);
debug('剩余密钥数量', {
keyCount: keyCount
});
if (keyCount > 0) {
return true;
}
debug('没有剩余密钥,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('noKeysLeft'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'GiveawayHopper.checkLeftKey');
return false;
}
}
}
const defaultTasksTemplate = {
steam: {
groupLinks: [],
curatorLinks: [],
wishlistLinks: [],
followLinks: []
},
youtube: {
channelLinks: []
}
};
const defaultTasks = JSON.stringify(defaultTasksTemplate);
class Prys extends Website {
name='Prys';
socialTasks=JSON.parse(defaultTasks);
undoneTasks=JSON.parse(defaultTasks);
buttons=[ 'doTask', 'undoTask', 'verifyTask' ];
static test() {
const {host: host} = window.location;
const isMatch = host === 'prys.revadike.com';
debug('检查网站匹配', {
host: host,
isMatch: isMatch
});
return isMatch;
}
async after() {
try {
debug('开始执行后续操作');
if (!this.#checkLogin()) {
debug('检查登录失败');
echoLog({}).warning(I18n('checkLoginFailed'));
}
if (!await this.#checkLeftKey()) {
debug('检查剩余密钥失败');
echoLog({}).warning(I18n('checkLeftKeyFailed'));
}
} catch (error) {
debug('后续操作失败', {
error: error
});
throwError(error, 'Prys.after');
}
}
init() {
try {
debug('开始初始化');
const logStatus = echoLog({
text: I18n('initing')
});
if ($('button:contains("Sign")').length > 0) {
debug('需要登录');
logStatus.warning(I18n('needLogin'));
return false;
}
if (!this.#getGiveawayId()) {
debug('获取抽奖ID失败');
return false;
}
this.initialized = true;
debug('初始化完成');
logStatus.success();
return true;
} catch (error) {
debug('初始化失败', {
error: error
});
throwError(error, 'Prys.init');
return false;
}
}
async classifyTask(action) {
try {
debug('开始分类任务', {
action: action
});
const logStatus = echoLog({
text: I18n('getTasksInfo')
});
if (action === 'undo') {
debug('恢复已保存的任务信息');
this.socialTasks = GM_getValue(`prysTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks);
}
const steps = $('#steps tbody tr');
debug('找到任务步骤', {
count: steps.length
});
for (let eq = 0; eq < steps.length; eq += 1) {
if (steps.eq(eq).find('span:contains(Success)').length === 0) {
debug('点击检查按钮', {
step: eq
});
checkClick(eq);
}
}
const pro = [];
for (const step of steps) {
const isSuccess = $(step).find('span:contains(Success)').length > 0;
if (isSuccess && action === 'do') {
debug('跳过已完成的任务');
continue;
}
const appLink = $(step).find('a[href*=\'store.steampowered.com/app/\']').attr('href');
if (appLink) {
const taskType = $(step).find('a[href*=\'store.steampowered.com/app/\']').text().includes('wishlist') ? 'wishlistLinks' : 'followLinks';
debug('添加 Steam 应用任务', {
type: taskType,
link: appLink
});
if (action === 'undo') {
this.socialTasks.steam[taskType].push(appLink);
}
if (action === 'do') {
this.undoneTasks.steam[taskType].push(appLink);
}
continue;
}
const curatorLink = $(step).find('a[href*=\'store.steampowered.com/curator/\']').attr('href');
if (curatorLink) {
debug('添加 Steam 鉴赏家任务', {
link: curatorLink
});
if (action === 'undo') {
this.socialTasks.steam.curatorLinks.push(curatorLink);
}
if (action === 'do') {
this.undoneTasks.steam.curatorLinks.push(curatorLink);
}
continue;
}
const groupLink = $(step).find('a[href*=\'steamcommunity.com/groups/\']').attr('href');
if (groupLink) {
debug('添加 Steam 组任务', {
link: groupLink
});
if (action === 'undo') {
this.socialTasks.steam.groupLinks.push(groupLink);
}
if (action === 'do') {
this.undoneTasks.steam.groupLinks.push(groupLink);
}
continue;
}
const gidLink = $(step).find('a[href*=\'steamcommunity.com/gid\']').attr('href');
if (gidLink) {
debug('处理 Steam GID 链接', {
link: gidLink
});
pro.push(getRedirectLink(gidLink).then((finalUrl => {
if (!finalUrl || !/^https?:\/\/steamcommunity\.com\/groups\//.test(finalUrl)) {
debug('无效的 Steam 组链接', {
finalUrl: finalUrl
});
return false;
}
debug('添加 Steam 组任务(从 GID)', {
link: finalUrl
});
if (action === 'undo') {
this.socialTasks.steam.groupLinks.push(finalUrl);
}
if (action === 'do') {
this.undoneTasks.steam.groupLinks.push(finalUrl);
}
})));
}
}
await Promise.allSettled(pro);
debug('任务分类完成');
logStatus.success();
this.undoneTasks = this.uniqueTasks(this.undoneTasks);
this.socialTasks = this.uniqueTasks(this.socialTasks);
if (window.DEBUG) {
console.log('%cAuto-Task[Debug]:', 'color:blue', JSON.stringify(this));
}
GM_setValue(`prysTasks-${this.giveawayId}`, {
tasks: this.socialTasks,
time: (new Date).getTime()
});
return true;
} catch (error) {
debug('任务分类失败', {
error: error
});
throwError(error, 'Prys.classifyTask');
return false;
}
}
async verifyTask() {
try {
debug('开始验证任务');
const checks = $('#steps tbody a[id^=check]');
if (checks.length === 0) {
debug('没有需要验证的任务');
echoLog({}).success(I18n('allTasksComplete'));
return;
}
const pro = [];
for (const check of checks) {
const id = $(check).attr('id')?.match(/[\d]+/)?.[0];
if (!id) {
debug('跳过无效任务ID');
continue;
}
const taskDes = $(check).parent()?.prev()?.html()?.trim();
debug('验证任务', {
id: id,
taskDes: taskDes
});
const status = echoLog({
text: `${I18n('verifyingTask')}${taskDes}...`
});
pro.push(new Promise((resolve => {
this.#checkStep(id, resolve, status);
})));
}
await Promise.all(pro);
debug('所有任务验证完成');
echoLog({}).success(I18n('allTasksComplete'));
} catch (error) {
debug('验证任务失败', {
error: error
});
throwError(error, 'Prys.verifyTask');
}
}
#getGiveawayId() {
try {
debug('开始获取抽奖ID');
const giveawayId = window.location.search.match(/id=([\d]+)/)?.[1];
if (giveawayId) {
this.giveawayId = giveawayId;
debug('获取抽奖ID成功', {
giveawayId: giveawayId
});
return true;
}
debug('获取抽奖ID失败');
echoLog({}).error(I18n('getFailed', 'GiveawayId'));
return false;
} catch (error) {
debug('获取抽奖ID出错', {
error: error
});
throwError(error, 'Prys.getGiveawayId');
return false;
}
}
async #checkLeftKey() {
try {
debug('检查剩余密钥');
if (!globalOptions.other.checkLeftKey) {
debug('跳过密钥检查');
return true;
}
const leftKey = $('#header').text().match(/([\d]+).*?prize.*?left/)?.[1];
debug('检查剩余密钥数量', {
leftKey: leftKey
});
if (leftKey !== '0') {
return true;
}
debug('没有剩余密钥,显示确认对话框');
const {value: value} = await Swal.fire({
icon: 'warning',
title: I18n('notice'),
text: I18n('noKeysLeft'),
confirmButtonText: I18n('confirm'),
cancelButtonText: I18n('cancel'),
showCancelButton: true
});
if (value) {
debug('用户确认关闭窗口');
window.close();
}
return true;
} catch (error) {
debug('检查剩余密钥失败', {
error: error
});
throwError(error, 'Prys.checkLeftKey');
return false;
}
}
#checkLogin() {
try {
debug('检查登录状态');
if (!globalOptions.other.checkLogin) {
debug('跳过登录检查');
return true;
}
if ($('button:contains("Sign")').length > 0) {
debug('未登录');
echoLog({}).warning(I18n('needLogin'));
}
debug('登录检查完成');
return true;
} catch (error) {
debug('检查登录失败', {
error: error
});
throwError(error, 'Prys.checkLogin');
return false;
}
}
#checkStep(step, resolve, status, captcha = null) {
try {
debug('开始检查步骤', {
step: step,
hasCaptcha: !!captcha
});
if (step !== 'captcha') {
debug('更新步骤状态为检查中');
$(`#check${step}`).replaceWith(`<span id="check${step}"><i class="fa fa-refresh fa-spin fa-fw"></i> Checking...</span>`);
}
debug('发送检查请求');
$.post('/api/check_step', {
step: step,
id: getURLParameter('id'),
'g-recaptcha-response': captcha
}, (json => {
resolve();
debug('收到检查响应', {
success: json.success
});
if (step !== 'captcha') {
if (json.success) {
debug('步骤检查成功');
$(`#check${step}`).replaceWith(`<span class="text-success" id="check${step}"><i class="fa fa-check"></i> Success</span>`);
status.success();
} else {
debug('步骤检查失败');
$(`#check${step}`).replaceWith(`<a id="check${step}" href="javascript:checkStep(${step})"><i class="fa fa-question"></i> Check</a>`);
status.error(json.response?.error || 'Error');
}
}
if (!json.response) {
return;
}
if (json.response.prize) {
debug('获得奖品', {
prize: json.response.prize
});
showAlert('success', `Here is your prize:<h1 role="button" align="middle" style="word-wrap: break-word;">${json.response.prize}</h2>`);
}
if (!json.response.captcha) {
return;
}
debug('需要验证码');
if (json.success) {
showAlert('info', json.response.captcha);
} else {
showAlert('warning', json.response.captcha);
}
captchaCheck();
})).fail((() => {
resolve();
debug('请求失败');
$(`#check${step}`).replaceWith(`<a id="check${step}" href="javascript:checkStep(${step})"><i class="fa fa-question"></i> Check</a>`);
status.error('Error:0');
}));
} catch (error) {
debug('检查步骤失败', {
error: error
});
throwError(error, 'prys.checkStep');
resolve(false);
}
}
}
const Websites = [ FreeAnyWhere, GiveawaySu, Indiedb, Keyhub, Givekey, GiveeClub, OpiumPulses, Keylol, Opquests, Gleam, SweepWidget, Setting, History, GiveawayHopper, Prys ];
const generateFormHtml = options => {
debug('开始生成网站选项表单HTML', {
options: options
});
const tableRows = Object.entries(options).map((([option, value]) => `\n <tr>\n <td>${option}</td>\n <td>\n <input\n class="editOption"\n type="text"\n name="${option}"\n value="${value}"\n />\n </td>\n </tr>\n `)).join('');
const formHtml = `\n <form id="websiteOptionsForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('option')}</td>\n <td>${I18n('value')}</td>\n </tr>\n </thead>\n <tbody>\n ${tableRows}\n </tbody>\n </table>\n </form>\n `;
debug('表单HTML生成完成');
return formHtml;
};
const saveOptions = (website, options, formValues) => {
debug('开始保存网站选项', {
website: website,
formValues: formValues
});
formValues.forEach((({name: name, value: value}) => {
options[name] = value;
debug('更新选项值', {
name: name,
value: value
});
}));
GM_setValue(`${website}Options`, options);
debug('选项已保存到存储', {
website: website
});
Swal.fire({
title: I18n('changeWebsiteOptionsSuccess'),
icon: 'success'
});
};
const websiteOptions = async (website, options) => {
try {
debug('开始设置网站选项', {
website: website
});
if (!website || typeof website !== 'string') {
debug('无效的网站参数', {
website: website
});
throw new Error('Invalid website parameter');
}
if (!options || typeof options !== 'object') {
debug('无效的选项参数', {
options: options
});
throw new Error('Invalid options parameter');
}
debug('显示选项编辑对话框');
const result = await Swal.fire({
title: I18n('websiteOptions'),
html: generateFormHtml(options),
showConfirmButton: true,
confirmButtonText: I18n('save'),
showCancelButton: true,
cancelButtonText: I18n('close')
});
if (result.isConfirmed) {
debug('用户确认保存选项');
const form = document.getElementById('websiteOptionsForm');
if (!form) {
debug('未找到表单元素');
throw new Error('Form element not found');
}
const formData = $('#websiteOptionsForm').serializeArray();
debug('获取表单数据', {
formData: formData
});
saveOptions(website, options, formData);
} else {
debug('用户取消保存选项');
}
} catch (error) {
debug('设置网站选项时发生错误', {
error: error
});
throwError(error, 'websiteOptions');
}
};
const UPDATE_LINKS = {
github: 'https://github.com/HCLonely/auto-task/raw/main/',
jsdelivr: 'https://cdn.jsdelivr.net/gh/HCLonely/auto-task@main/',
standby: 'https://auto-task.hclonely.com/'
};
const checkUpdate = async (updateLink, auto) => {
try {
debug('开始检查更新', {
updateLink: updateLink,
auto: auto
});
const checkUrl = `${updateLink}package.json?time=${Date.now()}`;
debug('构建检查URL', {
checkUrl: checkUrl
});
const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({
url: checkUrl,
responseType: 'json',
method: 'GET',
timeout: 3e4
});
if (result === 'Success' && data?.response?.version) {
debug('成功获取更新信息', {
version: data.response.version
});
return data.response;
}
if (!auto) {
const errorMessage = data?.response?.version ? `${I18n('checkUpdateFailed')}[${data?.statusText}(${data?.status})]` : `${I18n('checkUpdateFailed')}[${result}:${statusText}(${status})]`;
debug('检查更新失败', {
errorMessage: errorMessage
});
echoLog({}).error(errorMessage);
} else {
debug('自动检查更新失败', {
result: result,
statusText: statusText,
status: status
});
}
return false;
} catch (error) {
debug('检查更新发生错误', {
error: error
});
throwError(error, 'checkUpdate');
return false;
}
};
const hasNewVersion = (currentVersion, remoteVersion) => {
try {
debug('开始比较版本号', {
currentVersion: currentVersion,
remoteVersion: remoteVersion
});
const [currentRealVersion] = currentVersion.split('-');
const [remoteRealVersion, isPreview] = remoteVersion.split('-');
if (isPreview && !globalOptions.other.receivePreview) {
debug('不接收预览版本', {
isPreview: isPreview
});
return false;
}
const currentVersionParts = currentRealVersion.split('.').map(Number);
const remoteVersionParts = remoteRealVersion.split('.').map(Number);
debug('版本号解析', {
currentVersionParts: currentVersionParts,
remoteVersionParts: remoteVersionParts
});
for (let i = 0; i < 3; i++) {
if (remoteVersionParts[i] > currentVersionParts[i]) {
debug('发现新版本', {
position: i,
current: currentVersion,
remote: remoteVersion
});
return true;
}
if (remoteVersionParts[i] < currentVersionParts[i]) {
debug('远程版本较旧', {
position: i,
current: currentVersion,
remote: remoteVersion
});
return false;
}
}
debug('版本号相同');
return false;
} catch (error) {
debug('比较版本号时发生错误', {
error: error
});
throwError(error, 'compareVersion');
return false;
}
};
const getUpdateLink = updateSource => {
debug('获取更新链接', {
updateSource: updateSource
});
const source = updateSource.toLowerCase();
const link = UPDATE_LINKS[source] || UPDATE_LINKS.github;
debug('选择的更新链接', {
source: source,
link: link
});
return link;
};
const showUpdateInfo = (packageData, currentVersion, updateLink) => {
debug('准备显示更新信息', {
currentVersion: currentVersion,
newVersion: packageData.version
});
if (hasNewVersion(currentVersion, packageData.version)) {
const scriptUrl = `${updateLink}dist/${GM_info.script.name}.user.js`;
debug('发现新版本,显示更新通知', {
scriptUrl: scriptUrl
});
echoLog({
html: `<li><font>${I18n('newVersionNotice', packageData.version, scriptUrl)}</font></li>`
});
const changeList = packageData.change?.map((change => `<li>${change}</li>`)).join('') || '';
debug('显示更新日志', {
changeListLength: packageData.change?.length
});
echoLog({
html: `<li>${I18n('updateText', packageData.version)}</li><ol class="update-text">${changeList}<li>${I18n('updateHistory')}</li></ol>`
});
} else {
debug('当前已是最新版本');
}
};
const updateChecker = async () => {
try {
debug('开始检查更新流程');
const currentVersion = GM_info.script.version;
const updateSource = globalOptions.other.autoUpdateSource;
debug('当前配置', {
currentVersion: currentVersion,
updateSource: updateSource
});
let packageData = false;
if ([ 'github', 'jsdelivr', 'standby' ].includes(updateSource.toLowerCase())) {
debug('使用指定的更新源', {
updateSource: updateSource
});
const updateLink = getUpdateLink(updateSource);
packageData = await checkUpdate(updateLink, false);
} else {
debug('按优先级尝试不同的更新源');
for (const source of [ 'github', 'jsdelivr', 'standby' ]) {
debug('尝试更新源', {
source: source
});
packageData = await checkUpdate(UPDATE_LINKS[source], true);
if (packageData) {
debug('成功获取更新信息', {
source: source
});
break;
}
}
}
if (!packageData) {
debug('所有更新源检查失败');
echoLog({}).error(I18n('checkUpdateFailed'));
return;
}
showUpdateInfo(packageData, currentVersion, getUpdateLink(updateSource));
} catch (error) {
debug('更新检查过程发生错误', {
error: error
});
throwError(error, 'updateChecker');
}
};
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
let getRandomValues;
const rnds8 = new Uint8Array(16);
function rng() {
if (!getRandomValues) {
if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');
}
getRandomValues = crypto.getRandomValues.bind(crypto);
}
return getRandomValues(rnds8);
}
const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto);
var native = {
randomUUID: randomUUID
};
function v4(options, buf, offset) {
if (native.randomUUID && true && !options) {
return native.randomUUID();
}
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
return unsafeStringify(rnds);
}
function fawExtension() {
const hostname = window.location.hostname;
function IsJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
$(window).on('load', (function() {
console.log('👌 gamesforfarm extension');
if (hostname == 'freeanywhere.net' || hostname == 'give.gamesforfarm.local' || hostname == 'gamesforfarm-testing.ru') {
const steam = $('.games_for_farm_site').data('steam');
const avatar = $('.games_for_farm_site').data('avatar');
const name = $('.games_for_farm_site').data('name');
const lang = $('.games_for_farm_site').data('lang');
let need_update = true;
GM_addValueChangeListener('FAW_STORAGE', (function(newValue, oldValue) {
if (need_update == false) {
return;
}
GM_getValue('FAW_STORAGE', (function(STORAGE) {
$.ajax({
type: 'POST',
url: '/php/extension/user_data_update.php',
data: {
extension: JSON.stringify(STORAGE)
},
success: function(data) {}
});
}));
}));
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (Object.keys(STORAGE).length === 0 || !STORAGE['tasks'] || !STORAGE['user'] || !STORAGE['games'] || !STORAGE['settings']) {
GM_deleteValue('FAW_STORAGE');
let new_storage = {};
new_storage['tasks'] = {};
new_storage['user'] = {};
new_storage['games'] = {};
new_storage['settings'] = {};
if (steam) {
new_storage['user']['steam'] = steam;
}
if (avatar) {
new_storage['user']['avatar'] = avatar;
}
if (name) {
new_storage['user']['name'] = name;
}
if (lang) {
new_storage['user']['lang'] = lang;
}
if (steam) {
$.ajax({
type: 'POST',
url: '/php/extension/user_games_get.php',
data: {
steam: steam
},
success: function(data) {
if (data != '' && IsJsonString(data)) {
const user_games = JSON.parse(data);
new_storage['games'] = user_games;
new_storage['settings']['game_update'] = parseInt(Date.now() / 1e3);
GM_setValue('FAW_STORAGE', new_storage);
}
}
});
} else {
GM_setValue('FAW_STORAGE', new_storage);
}
}
if (STORAGE['user'] && STORAGE['settings'] && STORAGE['user']['steam']) {
if (!STORAGE['settings']['game_update']) {
STORAGE['settings']['game_update'] = 0;
}
let time_now = parseInt(Date.now() / 1e3);
let time_update = parseInt(STORAGE['settings']['game_update']);
if (time_now - time_update > 60 * 60 * 24) {
$.ajax({
type: 'POST',
url: '/php/extension/user_games_get.php',
data: {
steam: STORAGE['user']['steam']
},
success: function(data) {
if (data != '' && IsJsonString(data)) {
const games = JSON.parse(data);
STORAGE['games'] = games;
STORAGE['settings']['game_update'] = parseInt(Date.now() / 1e3);
GM_setValue('FAW_STORAGE', STORAGE);
}
}
});
}
}
if (steam && STORAGE['user']) {
if (!STORAGE['user']['steam']) {
STORAGE['user']['steam'] = steam;
}
if (STORAGE['user']['steam'] != steam) {
$.ajax({
type: 'POST',
url: '/php/extension/user_data_get.php',
data: {
steam: steam
},
success: function(data) {
if (!data) {
return;
}
const db_storage = JSON.parse(data);
if (db_storage) {
GM_deleteValue('FAW_STORAGE');
need_update = false;
GM_setValue('FAW_STORAGE', db_storage);
setTimeout((function() {
need_update = true;
}), 100);
}
}
});
} else {
if (avatar) {
STORAGE['user']['avatar'] = avatar;
}
if (name) {
STORAGE['user']['name'] = name;
}
if (lang) {
STORAGE['user']['lang'] = lang;
}
GM_setValue('FAW_STORAGE', STORAGE);
}
}
if (STORAGE['tasks']) {
let update_tasks = [];
let is_update = false;
let time_now = parseInt(Date.now() / 1e3);
$.each(STORAGE['tasks'], (function(index, val) {
if (val['time'] && time_now - parseInt(val['time']) > 2 * 60 * 60) {
is_update = true;
return;
}
update_tasks.push(val);
}));
if (is_update == true) {
STORAGE['tasks'] = update_tasks;
GM_setValue('FAW_STORAGE', STORAGE);
}
}
if (STORAGE['discord']) {
if (STORAGE['discord'] && STORAGE['discord'].length > 0) {
$.ajax({
type: 'POST',
url: '/php/extension/discord_levels_update.php',
data: {
discord: JSON.stringify(STORAGE['discord'])
},
success: function(data) {
if (data.indexOf('success') != -1) {
alert('Данные discord уровней обновлены');
delete STORAGE['discord'];
GM_setValue('FAW_STORAGE', STORAGE);
} else {
alert('Возникла ошибка при обновлении discord уровней');
delete STORAGE['discord'];
GM_setValue('FAW_STORAGE', STORAGE);
}
},
error: function() {
alert('Возникла ошибка при обновлении discord уровней');
delete STORAGE['discord'];
GM_setValue('FAW_STORAGE', STORAGE);
}
});
}
}
function check_tasks_button() {
let tasks_done = true;
$('.game__content-tasks__task').each((function(index, el) {
if ($(this).hasClass('done') == false) {
tasks_done = false;
}
}));
if (tasks_done == true) {
$('.js-get-key').removeClass('inactive');
} else {
$('.js-get-key').addClass('inactive');
}
}
if ($('.games_for_farm_extension.work').length > 0) {
$('.games_for_farm_extension.not_work').remove();
$('.games_for_farm_extension.work').slideDown(200);
}
if ($('.game__content-tasks__task .task-check-extension').length > 0) {
$('.task-check-extension').removeClass('js-extentions-modal');
$('.game__content-tasks__task[data-extension=\'1\'] .task-link a').removeClass('js-extentions-modal');
$('.game__content-tasks__task .task-check-extension').on('click', (function(event) {
event.preventDefault();
let $button = $(this);
if ($button.hasClass('loading')) {
return;
}
let $parrent = $(this).parent('.game__content-tasks__task');
const type = $parrent.data('type');
const id = $parrent.data('id');
const data = $parrent.data('data');
const extension = $parrent.data('extension');
if (extension == false) {
return;
}
$button.addClass('loading');
const STORAGE = GM_getValue('FAW_STORAGE') || {};
$.ajax({
type: 'POST',
url: '/php/extension/user_data_update.php',
data: {
extension: JSON.stringify(STORAGE)
},
success: function(update) {
const getTime_start = (new Date).getTime();
$.ajax({
type: 'POST',
url: '/php/extension/user_task_update.php',
data: {
id: id,
type: type,
data: data
},
success: function(data) {
const getTime_end = (new Date).getTime();
console.log('👌 checking task in ' + (getTime_end - getTime_start) + ' ms');
if (data.indexOf('good') != -1) {
setTimeout((function() {
$parrent.addClass('done');
$parrent.removeClass('error');
$button.removeClass('loading');
check_tasks_button();
}), 1250);
} else if (data.indexOf('bad') != -1) {
setTimeout((function() {
$parrent.addClass('error');
$parrent.removeClass('done');
$button.removeClass('loading');
check_tasks_button();
}), 1250);
} else {
setTimeout((function() {
$parrent.removeClass('error');
$parrent.removeClass('done');
$button.removeClass('loading');
check_tasks_button();
}), 1250);
}
},
error: function() {
setTimeout((function() {
$parrent.removeClass('error');
$parrent.removeClass('done');
$button.removeClass('loading');
check_tasks_button();
}), 1250);
}
});
},
error: function() {
setTimeout((function() {
$parrent.removeClass('error');
$parrent.removeClass('done');
$button.removeClass('loading');
check_tasks_button();
}), 1250);
}
});
}));
}
}
function storage_tasks_update(tasks, type, value, action) {
let result = [];
let is_find = false;
$.each(tasks, (function(index, val) {
if (!val['type'] || !val['data']) {
return;
}
if (val['type'] == type && val['data'] == value) {
is_find = true;
return;
}
result.push(val);
}));
const obj = {};
switch (action) {
case 'add':
if (is_find == true) {
return;
}
const task = {};
task['type'] = type;
task['data'] = value;
task['time'] = parseInt(Date.now() / 1e3);
result.push(task);
obj['tasks'] = result;
GM_setValue('FAW_STORAGE', obj);
break;
case 'remove':
if (is_find == false) {
return;
}
obj['tasks'] = result;
GM_setValue('FAW_STORAGE', obj);
}
}
if (hostname == 'store.steampowered.com' || hostname == 'steamcommunity.com') {
if (document.querySelector('span[id^=\'CuratorUnFollowBtn_\']')) {
const curator_id = $('span[id^=\'CuratorUnFollowBtn_\']').attr('id').split('_')[1];
const follow_btn = '#CuratorFollowBtn_' + curator_id;
const unfollow_btn = '#CuratorUnFollowBtn_' + curator_id;
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
const follow = $(follow_btn).css('display');
const unfollow = $(unfollow_btn).css('display');
if (unfollow && unfollow == 'none') {
storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'remove');
}
if (follow && follow == 'none') {
storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'add');
}
$(follow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'add');
}));
$(unfollow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'remove');
}));
}
if (document.querySelector('.followStatsBlock')) {
const user_id = $('#HeaderUserInfoName a').attr('href').split('/').pop();
const follow_btn = '#FollowUserOptionAdd';
const unfollow_btn = '#FollowUserOptionFollowing, .followOption.remove';
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
const follow = $(follow_btn).css('visibility');
const unfollow = $(unfollow_btn).css('visibility');
if (unfollow && unfollow == 'hidden') {
storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'remove');
}
if (follow && follow == 'hidden') {
storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'add');
}
$(follow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'add');
}));
$(unfollow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'remove');
}));
}
if (document.querySelector('#ScrollingItemControls')) {
const manual_id = $('#PublishedFileFavorite input[name=\'id\']').val();
const follow_btn = '#FavoriteItemOptionAdd';
const unfollow_btn = '#FavoriteItemOptionFavorited, .favoriteOption.removefavorite';
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
const follow = $(follow_btn).css('visibility');
const unfollow = $(unfollow_btn).css('visibility');
if (unfollow && unfollow == 'hidden') {
storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'remove');
}
if (follow && follow == 'hidden') {
storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'add');
}
$(follow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'add');
}));
$(unfollow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'remove');
}));
}
if (document.querySelector('#queueBtnFollow')) {
const game_id = $('.game_page_background').data('miniprofile-appid');
const follow_btn = '#queueBtnFollow .queue_btn_inactive';
const unfollow_btn = '#queueBtnFollow .queue_btn_active';
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
const follow = $(follow_btn).css('display');
const unfollow = $(unfollow_btn).css('display');
if (unfollow && unfollow == 'none') {
storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'remove');
}
if (follow && follow == 'none') {
storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'add');
}
$(follow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'add');
}));
$(unfollow_btn).on('click', (function(event) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'remove');
}));
}
}
if (hostname == 'www.youtube.com' || hostname == 'm.youtube.com') {
setInterval((function() {
if (document.querySelector('yt-subscribe-button-view-model')) {
const channel_id = $('meta[itemprop="identifier"]').attr('content');
const subscribe = [ 'Подписаться', 'Падпісацца', 'Підписатися', 'Abonnieren', 'Subscribe', 'Suscribirse', 'Mag-subscribe', 'S\'abonner', 'Iscriviti', 'Subskrybuj', 'Subscrever', 'Abonează-te', '订阅', 'チャンネル登録', '訂閱' ];
const subscribed = [ 'Вы подписаны', 'Вы падпісаны', 'Ви підписалися', 'Abonniert', 'Subscribed', 'Suscrito', 'Naka-subscribe', 'Abonné', 'Iscritto', 'Subskrybujesz', 'Subscrito', 'Abonat(ă)', '已订阅', '登録済み', '已訂閱' ];
const $parent = $('yt-subscribe-button-view-model');
const text = $parent.text();
if (subscribe.indexOf(text) != -1 || subscribed.indexOf(text) != -1) {
if (subscribe.indexOf(text) != -1) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'remove');
}
if (subscribed.indexOf(text) != -1) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'add');
}
} else {
if ($('.ytSubscribePlusButtonViewModelHost').length != 0) {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'add');
} else {
const color = $parent.find('button').css('color');
if (color == '#0f0f0f' || color == 'rgb(15, 15, 15)') {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'remove');
}
if (color == '#f1f1f1' || color == 'rgb(241, 241, 241)') {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'add');
}
}
}
}
if (document.querySelector('.ytLikeButtonViewModelHost')) {
let video_id;
if (hostname == 'm.youtube.com') {
video_id = $('link[rel="canonical"]').attr('href').split('v=').pop();
} else {
video_id = $('meta[itemprop="identifier"]').attr('content');
}
if ($('.ytLikeButtonViewModelHost button').attr('aria-pressed') == 'false') {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'youtube_video_like', video_id, 'remove');
}
if ($('.ytLikeButtonViewModelHost button').attr('aria-pressed') == 'true') {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['tasks']) {
return;
}
storage_tasks_update(STORAGE['tasks'], 'youtube_video_like', video_id, 'add');
}
}
}), 600);
}
}));
if (hostname == 'mee6.xyz') {
var message = false;
$(document).on('keydown', '', (function(event) {
if (event.code == 'End') {
let USERS = [];
$('.md\\:block').each((function(index, el) {
const user = {};
user['name'] = $(this).find('.justify-start p').text();
user['level'] = $(this).find('.leaderboardPlayerStat .items-center').text();
USERS.push(user);
if (user['level'] == 0) {
const obj = {};
obj['discord'] = USERS;
GM_setValue('FAW_STORAGE', obj);
if (message == false) {
alert('Можно переходить на freeanywhere.net');
message = true;
}
return false;
}
}));
}
}));
}
if (hostname == 'gamesforfarm.com') {
const STORAGE = GM_getValue('FAW_STORAGE') || {};
if (!STORAGE['settings']) {
return;
}
if (!STORAGE['games']) {
return;
}
if (STORAGE['settings']['hide_games'] && STORAGE['settings']['hide_games'] == true) {
$('.product__item').each((function(index, el) {
const image = $(this).find('.product__box-image img').data('src');
const cart = $(this).find('.product__box-props a').attr('href');
if (image && image.indexOf('/apps/') != -1) {
const id = parseInt(image.split('/apps/')[1].split('/')[0]);
if (id && isNaN(id) == false) {
if (STORAGE['games'][id]) {
$(this).css('opacity', '.2');
}
}
} else if (cart && cart.indexOf('/app/') != -1) {
const id = parseInt(cart.split('/app/')[1].split('/')[0]);
if (id && isNaN(id) == false) {
if (STORAGE['games'][id]) {
$(this).css('opacity', '.2');
}
}
}
}));
}
}
}
try {
consoleLogHook();
} catch (error) {
console.error('Auto-Task[Warning]: consoleLogHook 初始化失败', error);
}
window.STYLE = GM_addStyle(style + GM_getResourceText('style'));
window.DEBUG = !!globalOptions.other?.debug;
window.TRACE = !!globalOptions.other?.debug && typeof console.trace === 'function';
const handleTwitchAuth = async () => {
debug('开始处理Twitch认证');
const authToken = Cookies.get('auth-token');
const isLogin = !!Cookies.get('login');
if (isLogin) {
const authData = {
authToken: authToken,
clientVersion: window.__twilightBuildID,
clientId: window.commonOptions?.headers?.['Client-ID'],
deviceId: window.commonOptions?.headers?.['Device-ID'],
clientSessionId: window.localStorage.local_storage_app_session_id.replace(/"/g, '')
};
GM_setValue('twitchAuth', authData);
window.close();
await Swal.fire('', I18n('closePageNotice'));
} else {
await Swal.fire('', I18n('needLogin'));
}
};
const handleRedditAuth = async () => {
debug('开始处理Reddit认证');
const betaButton = $('#redesign-beta-optin-btn');
if (betaButton.length > 0) {
betaButton[0].click();
return;
}
window.close();
await Swal.fire('', I18n('closePageNotice'));
};
const handleDiscordAuth = async () => {
debug('开始处理Discord认证');
const LocalStorage = window.localStorage;
const allLocalStorage = getAllLocalStorageAsObjects(LocalStorage);
const discordAuth = allLocalStorage.token;
if (discordAuth && discordAuth.length > 0) {
const browserInfo = await browser.getInfo();
GM_setValue('discordAuth', {
auth: discordAuth,
xSuperProperties: window.btoa(JSON.stringify({
os: browserInfo.system,
browser: browserInfo.browser,
device: '',
system_locale: browserInfo.language,
...allLocalStorage.deviceProperties || {},
browser_user_agent: navigator.userAgent,
browser_version: browserInfo.browserVersion,
os_version: browserInfo.systemVersion,
referrer: '',
referring_domain: '',
referrer_current: '',
referring_domain_current: '',
release_channel: 'stable',
client_build_number: unsafeWindow.GLOBAL_ENV.BUILD_NUMBER,
client_event_source: null,
has_client_mods: false,
client_launch_id: v4(),
client_heartbeat_session_id: allLocalStorage.LAST_CLIENT_HEARTBEAT_SESSION?.uuid,
client_app_state: 'focused'
}))
});
window.close();
Swal.fire('', I18n('closePageNotice'));
} else {
Swal.fire({
text: I18n('getDiscordAuthFailed'),
icon: 'error'
});
}
};
const handleSteamStoreAuth = async () => {
debug('开始处理Steam商店认证');
const storeSessionID = document.body.innerHTML.match(/g_sessionID = "(.+?)";/)?.[1];
if (storeSessionID) {
GM_deleteValue('ATv4_updateStoreAuth');
GM_setValue('steamStoreAuth', {
storeSessionID: storeSessionID
});
window.close();
await Swal.fire('', I18n('closePageNotice'));
} else {
await Swal.fire({
title: 'Error: Get "sessionID" failed',
icon: 'error'
});
}
};
const handleSteamCommunityAuth = async () => {
debug('开始处理Steam社区认证');
const steam64Id = document.body.innerHTML.match(/g_steamID = "(.+?)";/)?.[1];
const communitySessionID = document.body.innerHTML.match(/g_sessionID = "(.+?)";/)?.[1];
if (steam64Id && communitySessionID) {
GM_deleteValue('ATv4_updateCommunityAuth');
GM_setValue('steamCommunityAuth', {
steam64Id: steam64Id,
communitySessionID: communitySessionID
});
window.close();
await Swal.fire('', I18n('closePageNotice'));
} else {
setTimeout((async () => {
await Swal.fire({
title: 'Error: Get "sessionID" failed',
icon: 'error'
});
}), 3e3);
}
};
const initializeUI = website => {
debug('初始化UI元素', {
website: website.name
});
const $body = $('body');
$body.append(`\n <div id="auto-task-info"\n style="display:${globalOptions.other.defaultShowLog ? 'block' : 'none'};\n ${globalOptions.position.logSideX}:${globalOptions.position.logDistance.split(',')[0]}px;\n ${globalOptions.position.logSideY}:${globalOptions.position.logDistance.split(',')[1]}px;">\n </div>\n <div id="auto-task-buttons"\n style="display:${globalOptions.other.defaultShowButton ? 'block' : 'none'};\n ${globalOptions.position.buttonSideX}:${globalOptions.position.buttonDistance.split(',')[0]}px;\n ${globalOptions.position.buttonSideY}:${globalOptions.position.buttonDistance.split(',')[1]}px;">\n </div>\n <div class="show-button-div"\n style="display:${globalOptions.other.defaultShowButton ? 'none' : 'block'};\n ${globalOptions.position.showButtonSideX}:${globalOptions.position.showButtonDistance.split(',')[0]}px;\n ${globalOptions.position.showButtonSideY}:${globalOptions.position.showButtonDistance.split(',')[1]}px;">\n <a class="auto-task-website-btn"\n href="javascript:void(0);"\n target="_self"\n title="${I18n('showButton')}"> </a>\n </div>\n `);
const $autoTaskInfo = $('#auto-task-info');
const $autoTaskButtons = $('#auto-task-buttons');
const $showButtonDiv = $('div.show-button-div');
$showButtonDiv.on('click', (() => {
$autoTaskButtons.show();
$showButtonDiv.hide();
}));
if (website.buttons && $autoTaskButtons.children().length === 0) {
$autoTaskButtons.addClass(`${website.name}-buttons`);
for (const button of website.buttons) {
if (website[button]) {
const btnElement = $(`<p><a class="auto-task-website-btn ${website.name}-button" href="javascript:void(0);" target="_self">${I18n(button)}</a></p>`);
btnElement.find('a.auto-task-website-btn').on('click', (() => {
website[button]();
}));
$autoTaskButtons.append(btnElement);
}
}
}
const hideButtonElement = $(`<p><a class="auto-task-website-btn ${website.name}-button" href="javascript:void(0);" target="_self">${I18n('hideButton')}</a></p>`);
hideButtonElement.find('a.auto-task-website-btn').on('click', (() => {
$autoTaskButtons.hide();
$showButtonDiv.show();
}));
const toggleLogElement = $(`<p><a id="toggle-log" class="auto-task-website-btn ${website.name}-button" href="javascript:void(0);" target="_self" data-status="${globalOptions.other.defaultShowLog ? 'show' : 'hide'}">${globalOptions.other.defaultShowLog ? I18n('hideLog') : I18n('showLog')}</a></p>`);
const toggleLog = () => {
const $toggleLog = $('#toggle-log');
const status = $toggleLog.attr('data-status');
if (status === 'show') {
$autoTaskInfo.hide();
$toggleLog.attr('data-status', 'hide').text(I18n('showLog'));
} else {
$autoTaskInfo.show();
$toggleLog.attr('data-status', 'show').text(I18n('hideLog'));
}
};
toggleLogElement.find('a.auto-task-website-btn').on('click', toggleLog);
$autoTaskButtons.append(hideButtonElement).append(toggleLogElement);
if (website.options) {
GM_registerMenuCommand(I18n('changeWebsiteOptions'), (() => {
websiteOptions(website.name, website.options);
}));
}
};
const initializeHotkeys = website => {
debug('初始化热键', {
website: website.name
});
keyboardJS.bind(globalOptions.hotKey.doTaskKey, (() => {
if (website.doTask) {
website.doTask();
}
}));
keyboardJS.bind(globalOptions.hotKey.undoTaskKey, (() => {
if (website.undoTask) {
website.undoTask();
}
}));
keyboardJS.bind(globalOptions.hotKey.toggleLogKey, (() => {
const $toggleLog = $('#toggle-log');
const status = $toggleLog.attr('data-status');
const $autoTaskInfo = $('#auto-task-info');
if (status === 'show') {
$autoTaskInfo.hide();
$toggleLog.attr('data-status', 'hide').text(I18n('showLog'));
} else {
$autoTaskInfo.show();
$toggleLog.attr('data-status', 'show').text(I18n('hideLog'));
}
}));
};
const checkSteamASFStatus = async () => {
debug('检查Steam ASF状态');
if (!globalOptions.ASF.AsfEnabled || !globalOptions.ASF.AsfIpcUrl || !globalOptions.ASF.AsfIpcPassword) {
return;
}
const stopPlayTime = GM_getValue('stopPlayTime', 0) || 0;
if (stopPlayTime === 0 || stopPlayTime >= Date.now()) {
return;
}
const stopPlayTimeMinutes = Math.floor((Date.now() - stopPlayTime) / 6e4);
await Swal.fire({
title: I18n('stopPlayTimeTitle'),
text: I18n('stopPlayTimeText', stopPlayTimeMinutes.toString()),
icon: 'warning',
confirmButtonText: I18n('confirm')
});
let steamASF = new SteamASF(globalOptions.ASF);
try {
const isInitialized = await steamASF.init();
if (!isInitialized) {
return;
}
const isGamesStopped = await steamASF.stopPlayGames();
if (!isGamesStopped) {
return;
}
const taskLink = GM_getValue('taskLink', []) || [];
for (const link of taskLink) {
GM_openInTab(link, {
active: true
});
}
GM_setValue('stopPlayTime', 0);
GM_setValue('playedGames', []);
GM_setValue('taskLink', []);
} catch (error) {
console.error('SteamASF operation failed:', error);
} finally {
steamASF = null;
}
};
const checkVersionAndNotice = () => {
debug('检查版本和通知');
const {scriptHandler: scriptHandler} = GM_info;
if (scriptHandler !== 'Tampermonkey') {
debug('未知脚本管理器', {
scriptHandler: scriptHandler
});
echoLog({}).warning(I18n('unknownScriptHandler'));
return;
}
const [v1, v2] = GM_info.version?.split('.') || [];
if (!(parseInt(v1, 10) >= 5 && parseInt(v2, 10) >= 2)) {
echoLog({}).error(I18n('versionNotMatched'));
}
if (!GM_getValue('notice')) {
Swal.fire({
title: I18n('swalNotice'),
icon: 'warning'
}).then((() => {
GM_openInTab(I18n('noticeLink'), {
active: true
});
GM_setValue('notice', (new Date).getTime());
}));
echoLog({
html: `<li><font class="warning">${I18n('echoNotice', I18n('noticeLink'))}</font></li>`
}).font?.find('a').on('click', (() => {
GM_setValue('notice', (new Date).getTime());
}));
}
};
const loadScript = async () => {
debug('主程序入口 loadScript 开始');
if (window.name === 'ATv4_twitchAuth' && window.location.hostname === 'www.twitch.tv') {
debug('检测到Twitch认证窗口');
await handleTwitchAuth();
return;
}
if (window.name === 'ATv4_redditAuth' && window.location.hostname === 'www.reddit.com') {
debug('检测到Reddit认证窗口');
await handleRedditAuth();
return;
}
let website;
for (const Website of Websites) {
if (Website.test()) {
debug('识别到支持的网站', {
website: Website.name
});
website = new Website;
break;
}
}
if (!website) {
debug('未识别到支持的网站,脚本停止加载');
console.log('%c%s', 'color:#ff0000', 'Auto-Task[Warning]: 脚本停止加载,当前网站不支持!');
return;
}
if (website.before) {
debug('执行网站 before 钩子');
await website.before();
}
initializeUI(website);
initializeHotkeys(website);
if (website.after) {
debug('执行网站 after 钩子');
await website.after();
}
if (website.name !== 'Setting') {
debug('注册全局菜单命令');
GM_registerMenuCommand(I18n('changeGlobalOptions'), (() => {
changeGlobalOptions('swal');
}));
GM_registerMenuCommand(I18n('settingPage'), (() => {
GM_openInTab('https://auto-task.hclonely.com/setting.html', {
active: true
});
}));
}
debug('脚本加载完成');
console.log('%c%s', 'color:#1bbe1a', 'Auto-Task[Load]: 脚本加载完成');
if (window.DEBUG) {
echoLog({}).warning(I18n('debugModeNotice'));
}
await checkSteamASFStatus();
checkVersionAndNotice();
updateChecker();
};
try {
debug('主程序入口开始', {
hostname: window.location.hostname,
windowName: window.name
});
if ([ 'freeanywhere.net', 'give.gamesforfarm.local', 'gamesforfarm-testing.ru', 'store.steampowered.com', 'steamcommunity.com', 'www.youtube.com', 'm.youtube.com', 'mee6.xyz', 'gamesforfarm.com' ].includes(window.location.hostname) && $('.task-check-extension').length > 0) {
debug('检测到freeanywhere.com,加载扩展');
fawExtension();
}
if (window.location.hostname === 'discord.com') {
if (window.name === 'ATv4_discordAuth') {
debug('检测到Discord认证窗口');
handleDiscordAuth();
} else {
debug('检测到Discord主站');
const discordAuth = window.localStorage?.getItem('token')?.replace(/^"|"$/g, '');
if (discordAuth && discordAuth.length > 0) {
debug('获取到Discord认证token');
GM_setValue('discordAuth', {
auth: discordAuth
});
}
}
} else if (window.location.hostname === 'opquests.com') {
debug('检测到opquests.com,加载主脚本');
loadScript();
} else if ((window.name === 'ATv4_updateStoreAuth' || GM_getValue('ATv4_updateStoreAuth')) && window.location.host === 'store.steampowered.com') {
debug('检测到Steam商店认证窗口');
$((() => {
if ($('[data-miniprofile]').length === 0) {
return;
}
handleSteamStoreAuth();
}));
} else if ((window.name === 'ATv4_updateCommunityAuth' || GM_getValue('ATv4_updateCommunityAuth')) && window.location.host === 'steamcommunity.com') {
debug('检测到Steam社区认证窗口');
$((() => {
handleSteamCommunityAuth();
}));
} else {
if (window.location.hostname === 'key-hub.eu') {
debug('检测到key-hub.eu,设置全局变量');
unsafeWindow.keyhubtracker = 1;
unsafeWindow.gaData = {};
}
debug('加载主脚本');
$(loadScript);
}
} catch (error) {
debug('主程序入口发生异常', {
error: error
});
}
})(Swal, Cookies, browser, util, dayjs, keyboardJS);