// ==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/jquery@3.6.0/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/js-cookie@3.0.1/dist/js.cookie.min.js
// @require https://cdn.jsdelivr.net/npm/js-sha1@0.6.0/src/sha1.min.js
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.js
// @resource style https://cdn.jsdelivr.net/npm/sweetalert2@11.3.5/dist/sweetalert2.min.css
// @require https://cdn.jsdelivr.net/npm/keyboardjs@2.6.4/dist/keyboard.min.js
// @require https://cdn.jsdelivr.net/npm/dayjs@1.10.7/dayjs.min.js
// @require https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@3e60eeb368f25f237a512e7553fd6d70f36dc74c/targets/wasm_exec.min.js
// @require https://cdn.jsdelivr.net/npm/node-inspect-extracted@3.1.0/dist/inspect.min.js
// @require https://cdn.jsdelivr.net/npm/browser-tool@1.3.2/dist/browser.min.js
// @noframes
// @downloadURL https://update.greasyfork.icu/scripts/437922/auto-task.user.js
// @updateURL https://update.greasyfork.icu/scripts/437922/auto-task.meta.js
// ==/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: '登录检测需要登录的网站自动登录,部分网站支持',
checkLeftKey: '剩余Key检测赠Key活动结束提示是否关闭,部分网站支持',
twitterVerifyId: '通过尝试关注该账号验证Twitter凭证默认为Twitter官方帐号 783214不想关注官方账号可以改为自己的帐号',
youtubeVerifyChannel: '通过尝试订阅该频道验证YouTube凭证默认为YouTube官方频道 UCrXUsMBcfTVqwAS7DKg9C0Q不想关注官方频道可以改为自己的频道',
autoUpdateSource: '更新源github: 需代理,实时更新jsdelivr: 可不用代理,更新有延迟standby: 备用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: '按钮区域水平方向定位(实时预览功能仅在设置页面可用)left: 靠左 | right: 靠右',
buttonSideY: '按钮区域垂直方向定位(实时预览功能仅在设置页面可用)top: 靠上 | bottom: 靠下',
buttonDistance: '按钮区域距边缘的距离(实时预览功能仅在设置页面可用)格式: X距离,Y距离',
showButtonSideX: '显示按钮水平方向定位(实时预览功能仅在设置页面可用)left: 靠左 | right: 靠右',
showButtonSideY: '显示按钮垂直方向定位(实时预览功能仅在设置页面可用)top: 靠上 | bottom: 靠下',
showButtonDistance: '显示按钮距边缘的距离(实时预览功能仅在设置页面可用)格式: X距离,Y距离',
logSideX: '日志区域水平方向定位(实时预览功能仅在设置页面可用)left: 靠左 | right: 靠右',
logSideY: '日志区域垂直方向定位(实时预览功能仅在设置页面可用)top: 靠上 | bottom: 靠下',
logDistance: '日志区域距边缘的距离(实时预览功能仅在设置页面可用)格式: X距离,Y距离',
hotKey: '快捷键',
doTaskKey: '做任务快捷键(实时预览功能仅在设置页面可用)',
undoTaskKey: '撤销任务快捷键(实时预览功能仅在设置页面可用)',
toggleLogKey: '显示/隐藏日志快捷键(实时预览功能仅在设置页面可用)',
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, 点此更新',
language: '语言目前仅支持zh: 中文, en: 英文',
gistOptions: 'Gist 设置',
swalNotice: '检测到您第一次安装V4版本脚本,请前往阅读用前必读内容!',
echoNotice: '检测到您第一次安装V4版本脚本,请点此前往阅读用前必读内容!',
noticeLink: 'https://auto-task-doc.js.org/guide/#用前必读',
toGithub: '前往Github反馈',
toKeylol: '前往其乐论坛反馈',
copySuccess: '错误信息已复制到剪切板,是否前往其乐论坛反馈?',
copyFailed: '请复制下方错误信息后前往Keylol论坛反馈!',
updateText: '%0 版本更新内容:',
Active: '进行中',
Ended: '已结束',
Banned: '已封禁',
Paused: '已暂停',
notStart: '未开始',
noRemoteData: '检测到远程无数据',
errorRemoteDataFormat: '远程数据格式错误',
updateHistory: '历史更新记录点此查看',
AsfEnabled: '使用ASF做Steam相关任务(需ASFEnhance插件)',
steamWeb: '同时使用Steam Web API做任务',
preferASF: '优先使用ASF',
AsfIpcUrl: 'ASF IPC 地址',
AsfIpcPassword: 'ASF IPC 密码',
versionNotMatched: '脚本管理器版本过低,需TamperMonkey >= 5.2.0或TamperMonkey Beta >= 5.2.6196',
curatorLimitNotice: '失败:可能是鉴赏家关注数量限制,请取关部分鉴赏家后再试',
unknownScriptHandler: '未知脚本管理器,适用性未知',
debugModeNotice: '检测到 DEBUG 模式已开启,非必要请关闭!',
steamWebApiKey: 'Steam Web API 密钥
用于检测游戏挂机状态,点此申请',
groups: '组',
officialGroups: '官方组',
wishlists: '愿望单',
follows: '游戏关注',
forums: '论坛',
workshops: '创意工坊收藏',
curators: '鉴赏家',
workshopVotes: '创意工坊点赞',
announcements: '社区通知',
steamCommunity: 'Steam社区',
steamStore: 'Steam商店',
licenses: '入库免费游戏',
playtests: '请求访问权限',
playTime: '挂时长',
needLoginSteamStore: '请先登录Steam商店',
needLoginSteamCommunity: '请先登录Steam社区',
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: '请先登录Instagram',
insBanned: '您的Instagram账户已被封禁',
verifyingInsAuth: '正在验证Instagram凭证...',
gettingInsUserId: '正在获取Instagram用户Id',
followingIns: '正在关注Instagram用户',
unfollowingIns: '正在取关Instagram用户',
reddits: '社区/用户',
loginReddit: '请先登录Reddit',
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: '请先登录Vk',
gettingVkId: '正在获取Vk任务Id',
joiningVkGroup: '正在加入Vk组',
leavingVkGroup: '正在退出Vk组',
joiningVkPublic: '正在加入Vk社区',
leavingVkPublic: '正在退出Vk社区',
sendingVkWall: '正在转发Vk动态',
deletingVkWall: '正在撤销转发Vk动态',
youtubeChannel: 'YouTube频道',
likes: '点赞',
loginYtb: '请先登录YouTube',
tryUpdateYtbAuth: '请尝试更新YouTube凭证',
gettingYtbToken: '正在获取YouTube Token...',
followingYtbChannel: '正在订阅YouTube频道',
unfollowingYtbChannel: '正在退订YouTube频道',
likingYtbVideo: '正在点赞YouTube视频',
unlikingYtbVideo: '正在取消点赞YouTube视频',
giveKeyNoticeBefore: '每次验证间隔15s',
giveKeyNoticeAfter: '如果没有key, 请在https://givekey.ru/profile查看',
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 detectionNeed to log in to the website automatically log in, part of this website supports.',
checkLeftKey: 'Key remaining detectionThe 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.The default is the official Twitter account 783214.' + '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.' + 'The default is the official YouTube channel UCrXUsMBcfTVqwAS7DKg9C0Q.' + 'If you don\'t want to follow the official channel, you can change it to your own channel.',
autoUpdateSource: 'The source to updategithub: Fast update.jsdelivr: Update is delayed.' + 'standby: Standby source.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).' + '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).' + '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).' + ' Format: X distance, Y distance',
showButtonSideX: 'ShowButton horizontal positioning (real-time preview function is only available on the setting page).' + 'left: left | right: right',
showButtonSideY: 'ShowButton vertical positioning (real-time preview function is only available on the setting page).' + 'top: top | bottom: bottom',
showButtonDistance: 'The distance between the ShowButton and the edge (real-time preview function is only available on the setting page).' + ' Format: X distance, Y distance',
logSideX: 'Horizontal positioning of the log area (real-time preview function is only available on the setting page).' + 'left: left | right: right',
logSideY: 'Vertical positioning of the log area (real-time preview function is only available on the setting page).' + '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).' + ' Format: X distance, Y distance',
hotKey: 'Shortcut key',
doTaskKey: 'DoTask shortcut keys (real-time preview function is only available on the settings page).',
undoTaskKey: 'UndoTask shortcut keys (real-time preview function is only available on the settings page).',
toggleLogKey: 'ShowLog/HideLog shortcut keys (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, click to update',
language: 'Language 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 click here 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: 'Click here' + ' to view the historical update record.',
AsfEnabled: 'Use ASF to do Steam related tasks (requires ASFEnhance 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 unfollow some curators 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
Used to detect game idle status, click to apply',
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 log in to the Steam Store',
needLoginSteamCommunity: 'Please log in to the Steam Community',
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 log in to Instagram',
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 log in to Reddit',
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 log in to Vk',
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 log in to YouTube',
tryUpdateYtbAuth: 'Please try to update YouTube token',
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 https://givekey.ru/profile',
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 = '\n\n';
var Web = '\n';
var Discord$1 = '\n';
var Twitch$1 = '\n';
var Instagram = '';
var Twitter$1 = '\n';
var Reddit$1 = '';
var Youtube$1 = '';
var Vk$1 = '';
var AutoTask = '';
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) => `${text}`;
const createBaseElement = content => $(`
${content}`).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 $(`${I18n('skipTask')}[${text}(${id})](${I18n('whiteList')})`);
case 'globalOptionsSkip':
return $(`${I18n('skipTaskOption')}${text}`);
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(``);
} else {
ele.prepend(`${before}`);
}
} else {
const base64Svg = btoa(ICONS['[AutoTask]']);
ele.prepend(``);
}
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(//)?.[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(//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(//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(//)?.[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: `${I18n('giveKeyNoticeAfter')}`
});
return true;
} catch (error) {
debug('任务验证失败', {
error: error
});
throwError(error, 'Givekey.verifyTask');
return false;
}
}
async #verify(task) {
try {
debug('验证任务', {
taskId: task
});
const logStatus = echoLog({
html: `${I18n('verifyingTask')}${task}...`
});
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(``);
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')}${name}...`
});
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(/