// ==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 detection
Need to log in to the website automatically log in, part of this website supports.', checkLeftKey: 'Key remaining detection
The end of the giveaway event prompts whether to close or not, part of this website supports.', twitterVerifyId: 'Verify Twitter token by trying to follow the account.
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 update
github: 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\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'; var Web = '\n\tfavicon\n\t\n\t\t\n\t\n\t\n\t\n\n'; var Discord$1 = '\n\tfavicon\n\t\n\t\t\n\t\n\t\n\t\n\n'; var Twitch$1 = '\n\tfavicon-32-e29e246c157142c94346\n\t\n\t\t\n\t\n\t\n\t\n\n'; var Instagram = '\n\tlam-fZmwmvn\n\t\n\t\t\n\t\n\t\n\t\n'; var Twitter$1 = '\n\ticon-ios\n\t\n\t\t\n\t\n\t\n\t\n\n'; var Reddit$1 = '\n\t192x192\n\t\n\t\t\n\t\n\t\n\t\n'; var Youtube$1 = '\n\tfavicon_48x48\n\t\n\t\t\n\t\n\t\n\t\n'; var Vk$1 = '\n\tfav_logo\n\t\n\t\t\n\t\n\t\n\t\n'; var AutoTask = '\n\tfavicon\n\t\n\t\t\n\t\n\t\n\t\n'; 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(/