您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
自动完成 Freeanywhere,Giveawaysu,GiveeClub,Givekey,Gleam,Indiedb,keyhub,OpiumPulses,Opquests,SweepWidget 等网站的任务。
// ==UserScript== // @name auto-task // @namespace auto-task // @version 5.0.4 // @description 自动完成 Freeanywhere,Giveawaysu,GiveeClub,Givekey,Gleam,Indiedb,keyhub,OpiumPulses,Opquests,SweepWidget 等网站的任务。 // @description:en Automatically complete the tasks of FreeAnyWhere, GiveawaySu, GiveeClub, Givekey, Gleam, Indiedb, keyhub, OpiumPulses, Opquests, SweepWidget websites. // @author HCLonely // @license MIT // @run-at document-start // @homepage https://auto-task-doc.js.org/ // @supportURL https://github.com/HCLonely/auto-task/issues // @icon https://auto-task.hclonely.com/favicon.ico // @tag games // @include *://freeanywhere.net/* // @include *://giveaway.su/giveaway/view/* // @include *://givee.club/*/event/* // @include *://givekey.ru/giveaway/* // @include *://www.indiedb.com/giveaways* // @include *://key-hub.eu/giveaway/* // @include *://keylol.com/* // @include *://www.opiumpulses.com/giveaways // @include *://prys.revadike.com/giveaway/?id=* // @include *://opquests.com/quests/* // @include *://gleam.io/* // @include *://sweepwidget.com/view/* // @include *://giveawayhopper.com/c/* // @include *://discord.com/* // @include *://www.twitch.tv/* // @include *://www.youtube.com/* // @include *://m.youtube.com/* // @include *://*.reddit.com/* // @include *://twitter.com/settings/account?k* // @include *://x.com/settings/account* // @include *://steamcommunity.com/* // @include *://store.steampowered.com/* // @include *://give.gamesforfarm.local/* // @include *://gamesforfarm-testing.ru/* // @include *://mee6.xyz/* // @include *://gamesforfarm.com/* // @include https://auto-task.hclonely.com/setting.html // @include https://auto-task.hclonely.com/history.html // @include https://auto-task-doc.js.org/setting.html // @include https://auto-task-doc.js.org/history.html // @grant GM_setValue // @grant GM_getValue // @grant GM_listValues // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_info // @grant GM_openInTab // @grant GM_setClipboard // @grant GM_getResourceText // @grant GM_cookie // @grant GM_addValueChangeListener // @grant GM_removeValueChangeListener // @grant unsafeWindow // @grant window.close // @grant window.localStorage // @grant window.sessionStorage // @grant window.focus // @connect auto-task.hclonely.com // @connect auto-task-doc.js.org // @connect cdn.jsdelivr.net // @connect store.steampowered.com // @connect steamcommunity.com // @connect login.steampowered.com // @connect twitter.com // @connect x.com // @connect abs.twimg.com // @connect api.twitter.com // @connect youtube.com // @connect www.youtube.com // @connect facebook.com // @connect instagram.com // @connect vk.com // @connect twitch.tv // @connect www.twitch.tv // @connect gql.twitch.tv // @connect github.com // @connect discordapp.com // @connect discord.gg // @connect discord.com // @connect www.reddit.com // @connect oauth.reddit.com // @connect raw.githubusercontent.com // @connect t.me // @connect bit.ly // @connect giveaway.su // @connect google.com // @connect www.vloot.io // @connect givee.club // @connect gleam.io // @connect www.indiedb.com // @connect key-hub.eu // @connect opquests.com // @connect itch.io // @connect auto-task.hclonely.com // @connect giveawayhopper.com // @connect freeanywhere.net // @connect * // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js.cookie.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/src/sha1.min.js // @require https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.js // @resource style https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.min.css // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/keyboard.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js // @require https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@3e60eeb368f25f237a512e7553fd6d70f36dc74c/targets/wasm_exec.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/inspect.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.min.js // @noframes // ==/UserScript== console.log('%c%s', 'color:blue', 'Auto-Task[Load]: 脚本开始加载'); /* * @Author : HCLonely * @Date : 2025-06-15 14:59:17 * @LastEditTime : 2025-08-18 19:05:01 * @LastEditors : HCLonely * @FilePath : /auto-task/src/scripts/checkDependence.js * @Description : */ const neededDependencies = ['jQuery', 'Cookies', 'sha1', 'Swal', 'keyboardJS', 'dayjs', 'Go', 'util', 'browser']; const missingDependencies = neededDependencies.filter(dependency => typeof window[dependency] === 'undefined'); if (missingDependencies.length > 0) { console.log('%c%s', 'color:red', `[Auto-Task] 脚本加载失败,缺少的依赖:${missingDependencies.join(', ')}`); if (confirm(`[Auto-Task] 脚本依赖加载失败,请刷新重试或安装全依赖版本,是否前往安装全依赖版本?\n缺少的依赖:${missingDependencies.join(', ')}`)) { GM_openInTab('https://github.com/HCLonely/auto-task/raw/main/dist/auto-task.all.user.js', { active: true }); } } (function(Swal, Cookies, browser, util, dayjs, keyboardJS) { 'use strict'; const tokenKeyPattern = /token|auth|session|jwt|key|secret|api[-_]?key|bearer|authorization|access[-_]?token|refresh[-_]?token|sid/i; const tokenStringPatterns = [ /([A-Za-z0-9-_]{10,})\.([A-Za-z0-9-_]{10,})\.([A-Za-z0-9-_]{10,})/g, /(Bearer|Basic)\s+([A-Za-z0-9\-._~+/]+=*)/gi, /\b([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\b/gi, /\b(eyJ[A-Za-z0-9\-_]+)\b/g ]; const maskToken = str => { if (typeof str !== 'string' || str.length < 8) { return str; } return str.replace(/^([A-Za-z0-9\-_+/=]{4})[A-Za-z0-9\-_+/=]+([A-Za-z0-9\-_+/=]{4})$/, '$1***$2'); }; const maskObject = obj => { if (Array.isArray(obj)) { return obj.map(maskObject); } else if (obj && typeof obj === 'object') { const newObj = {}; for (const key in obj) { if (tokenKeyPattern.test(key) && typeof obj[key] === 'string') { newObj[key] = maskToken(obj[key]); } else { newObj[key] = maskObject(obj[key]); } } return newObj; } if (typeof obj === 'string' && obj.length > 8) { return maskString(obj); } return obj; }; const maskString = str => { let masked = str; for (const pattern of tokenStringPatterns) { masked = masked.replace(pattern, ((match, ...groups) => { if (groups.length >= 3 && match.includes('.')) { return groups.map((seg => seg.length > 8 ? `${seg.slice(0, 4)}***${seg.slice(-4)}` : seg)).join('.'); } if (match.length > 8) { return `${match.slice(0, 4)}***${match.slice(-4)}`; } return match; })); } return masked; }; const maskArgs = args => args.map((arg => { if (typeof arg === 'string') { return maskString(arg); } else if (typeof arg === 'object' && arg !== null) { return maskObject(arg); } return arg; })); const consoleLogHook = () => { const originalLog = console.log; window.__allLogs = window.__allLogs || []; console.log = function(...args) { const maskedArgs = maskArgs(args); window.__allLogs.push(maskedArgs); originalLog.apply(console, maskedArgs); }; }; const defaultGlobalOptions = { doTask: { discord: { servers: true }, twitch: { channels: true }, twitter: { users: true, retweets: true }, vk: { names: true }, youtube: { channels: true, likes: true }, reddit: { reddits: true }, steam: { groups: true, officialGroups: true, wishlists: true, follows: true, forums: true, workshops: true, curators: true, workshopVotes: true, announcements: true, licenses: true, playtests: true, playTime: true } }, undoTask: { discord: { servers: true }, twitch: { channels: true }, twitter: { users: true, retweets: true }, vk: { names: true }, youtube: { channels: true, likes: true }, reddit: { reddits: true }, steam: { groups: true, officialGroups: true, wishlists: true, follows: true, forums: true, workshops: true, curators: true, playTime: true } }, ASF: { AsfEnabled: false, AsfIpcUrl: '', AsfIpcPassword: '', AsfBotname: 'asf', steamWeb: false, preferASF: false, steamWebApiKey: '' }, position: { buttonSideX: 'right', buttonSideY: 'top', buttonDistance: '15,30', showButtonSideX: 'right', showButtonSideY: 'top', showButtonDistance: '15,30', logSideX: 'right', logSideY: 'bottom', logDistance: '10,10' }, hotKey: { doTaskKey: 'alt + d', undoTaskKey: 'alt + u', toggleLogKey: 'alt + l' }, other: { twitterVerifyId: '783214', youtubeVerifyChannel: 'UCrXUsMBcfTVqwAS7DKg9C0Q', autoUpdateSource: 'jsdelivr', language: 'zh', checkLogin: true, checkLeftKey: true, defaultShowButton: true, defaultShowLog: true, debug: false, receivePreview: true } }; const userDefinedGlobalOptions = GM_getValue('globalOptions') || {}; const deepMerge = (target, source) => { try { const result = { ...target }; for (const [key, value] of Object.entries(source)) { const targetValue = target[key]; if (isObject(value) && isObject(targetValue)) { result[key] = deepMerge(targetValue, value); } else if (value !== undefined) { result[key] = value; } } return result; } catch (error) { console.log('%c%s', 'color:white;background:red', `Auto-Task[Error]: deepMerge\n${error.stack}`); return target; } }; const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value); const globalOptions = deepMerge(defaultGlobalOptions, userDefinedGlobalOptions); var style = '.colorful-button,#auto-task-buttons a.auto-task-website-btn,.show-button-div a.auto-task-website-btn,body.auto-task-options .auto-task-form table button{position:relative !important;padding:14px 28px !important;text-align:center !important;color:#fff !important;text-decoration:none !important;background:#2196f3 !important;border-radius:30px !important;text-transform:capitalize !important;font-weight:600 !important;letter-spacing:.5px !important;border:none !important;transition:all .2s ease !important;display:inline-block !important;line-height:1.5 !important;margin:8px !important;margin-bottom:12px !important;box-sizing:border-box !important;min-height:50px !important;min-width:140px !important;outline:none !important;vertical-align:middle !important;white-space:nowrap !important;font-size:18px !important}.colorful-button:hover,#auto-task-buttons a.auto-task-website-btn:hover,.show-button-div a.auto-task-website-btn:hover,body.auto-task-options .auto-task-form table button:hover{background:#1976d2 !important;box-shadow:0 4px 8px rgba(0,0,0,.1) !important;cursor:pointer !important;color:#fff !important;text-decoration:none !important}.colorful-button:active,#auto-task-buttons a.auto-task-website-btn:active,.show-button-div a.auto-task-website-btn:active,body.auto-task-options .auto-task-form table button:active{transform:translateY(1px) !important;color:#fff !important;text-decoration:none !important}.colorful-button:focus,#auto-task-buttons a.auto-task-website-btn:focus,.show-button-div a.auto-task-website-btn:focus,body.auto-task-options .auto-task-form table button:focus{color:#fff !important;text-decoration:none !important;outline:none !important}#auto-task-info{position:fixed;bottom:10px;right:10px;width:60%;max-width:500px;max-height:60%;overflow-y:auto;color:#000;background-color:#fff;padding-left:5px;z-index:999999999 !important;border:solid 2px #add8e6;border-radius:10px;font-size:14px !important}#auto-task-info li{text-align:left;display:block !important;align-items:baseline !important}#auto-task-info li .before-icon{display:inline-block !important;width:14px !important;height:14px !important;position:relative !important;top:2px !important;margin-right:5px !important;background-size:14px !important;background-repeat:no-repeat !important;flex-shrink:0 !important}#auto-task-info li font.before{color:#57bae8;margin-right:5px}#auto-task-info li a.high-light{color:#00aeff;font-weight:bold}#auto-task-info .success{color:green}#auto-task-info .error{color:red}#auto-task-info .warning{color:blue}#auto-task-info .info{color:#ff0}#auto-task-info .update-text{color:green;border:solid 2px #8dcb69;margin:5px 10px 5px 20px;border-radius:10px;padding:5px 20px}.auto-task-keylol{display:inline-block;text-transform:capitalize;margin-left:10px;text-decoration:none !important;border:solid 1px;border-radius:5px;padding:0 2px}.auto-task-keylol[selected=selected]{background-color:blue !important;color:#fff !important}.auto-task-form table{font-family:verdana,arial,sans-serif;font-size:11px;color:#333;border-width:1px;border-color:#999;border-collapse:collapse;width:100%}.auto-task-form table thead td{border-width:1px;padding:8px;border-style:solid;border-color:#a9c6c9;font-weight:bold;background-color:#fff}.auto-task-form table tbody tr{background-color:#d4e3e5}.auto-task-form table tbody tr:hover{background-color:#ff6 !important}.auto-task-form table tbody tr th{background-color:#c3dde0;border-width:1px;padding:8px;border-style:solid;border-color:#a9c6c9;text-transform:capitalize}.auto-task-form table tbody tr td{border-width:1px;padding:8px;border-style:solid;border-color:#a9c6c9}.swal2-modal{width:70% !important;max-width:1000px !important}.swal2-modal #swal2-title{text-align:center !important}body.auto-task-options{padding-top:10px;text-align:center}body.auto-task-options .auto-task-form{width:80%;max-width:1000px;margin:0 auto;padding-bottom:20px}body.auto-task-options .auto-task-form table input.editOption{width:80%}body.auto-task-options .auto-task-form table #getTwitterUserId,body.auto-task-options .auto-task-form table #getYoutubeChannelId{margin-top:5px}body.auto-task-options .auto-task-form table button{z-index:1;position:relative !important;padding:5px 20px !important;text-align:center !important;color:#fff !important;text-decoration:none !important;background:#2196f3 !important;border-radius:30px !important;text-transform:capitalize !important;font-weight:600 !important;letter-spacing:.5px !important;border:none !important;transition:all .2s ease !important;display:inline-block !important;line-height:1 !important;margin:8px !important;box-sizing:border-box !important;min-height:30px !important;min-width:140px !important;outline:none !important;vertical-align:middle !important;white-space:nowrap !important;font-size:15px !important}body.auto-task-options .auto-task-form table input[type=text]{outline-style:none;border:1px solid #ccc;border-radius:3px;padding:5px 10px;font-size:14px}body.auto-task-options .auto-task-form table input[type=text]:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}body.auto-task-options .auto-task-form table label{position:relative;width:160px;height:80px;cursor:pointer;transform:scale(0.25);margin:-25% 0;top:-30px;display:inline-block}body.auto-task-options .auto-task-form table label input{position:relative;z-index:1;appearance:none}body.auto-task-options .auto-task-form table label input:checked~span{background:#05be05;box-shadow:0 15px 25px rgba(5,190,5,.4)}body.auto-task-options .auto-task-form table label input:checked~span i{left:84px}body.auto-task-options .auto-task-form table label input:checked~span i::before{background:#05be05;box-shadow:35px 0 0 #05be05}body.auto-task-options .auto-task-form table label input:checked~span i::after{bottom:12px;height:15px;border-bottom-left-radius:15px;border-bottom-right-radius:15px;background:#05be05}body.auto-task-options .auto-task-form table label span{position:absolute;top:0;left:0;width:100%;height:100%;background:#fe0000;border-radius:80px;transition:.5s;box-shadow:0 15px 25px rgba(254,0,0,.4)}body.auto-task-options .auto-task-form table label span i{position:absolute;top:4px;left:4px;width:72px;height:72px;background:#fff;border-radius:50%}body.auto-task-options .auto-task-form table label span i::before{content:"";position:absolute;top:22px;left:12px;width:12px;height:12px;border-radius:50%;background:#fe0000;box-shadow:35px 0 0 #fe0000;transition:.5s}body.auto-task-options .auto-task-form table label span i::after{content:"";position:absolute;bottom:15px;left:calc(50% - 15px);width:30px;height:6px;border-radius:6px;background:#fe0000;transition:.5s}body.auto-task-history{font-size:15px;font-weight:400;line-height:1.5}body.auto-task-history .container a{color:#007bff;text-decoration:none;background-color:rgba(0,0,0,0)}body.auto-task-history .container .card{width:80%;max-width:800px;border-radius:10px;background:rgba(118,118,118,.1019607843);border-top:1px solid hsla(0,0%,100%,.5019607843);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);box-shadow:0 15px 25px rgba(0,0,0,.1019607843);margin:20px auto;position:relative;display:flex;flex-direction:column;word-wrap:break-word;-webkit-background-clip:border-box;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}body.auto-task-history .container .card .title{text-align:center;font-size:30px;font-weight:bold;margin:5px 0}body.auto-task-history .container .card .title a:hover{text-decoration:none;background:#93e1ff;border-radius:10px;padding:3px}body.auto-task-history .container .card ul{margin-bottom:25px}body.auto-task-history .container .card ul li{margin-bottom:5px;line-height:20px}body.auto-task-history .container .card ul a:hover{text-decoration:underline}body.auto-task-history .container .card .delete-task{right:10px;width:38px;height:35px;position:absolute;font-size:24px;cursor:pointer;border-radius:10px}body.auto-task-history .container .card .delete-task:hover{background:#fff}body.auto-task-history .container .card .time{right:5px;position:absolute;bottom:0;color:#e83e8c;font-family:\'SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace\';font-size:15px}#auto-task-buttons,.show-button-div{position:fixed !important;top:30px;right:15px;width:150px !important;z-index:999999999 !important;padding:8px !important;border-radius:12px !important}#auto-task-buttons p,.show-button-div p{line-height:normal !important;height:auto !important;text-align:center !important;margin:8px 0 !important;padding:0 !important;font-size:16px !important;color:#333 !important}#auto-task-buttons a.auto-task-website-btn,.show-button-div a.auto-task-website-btn{width:140px !important;min-height:30px !important;line-height:1.5 !important;font-size:16px !important;display:block !important;margin:0 auto !important;padding:8px 16px !important}.show-button-div{width:40px !important;cursor:pointer !important;padding:4px !important}.show-button-div a.auto-task-website-btn{right:-15px !important}.show-button-div a.auto-task-website-btn::after{content:"✓" !important;position:absolute !important;left:12px !important;top:50% !important;transform:translateY(-50%) !important;font-size:18px !important;font-weight:bold !important;color:#fff !important}.auto-task-capitalize{text-transform:capitalize !important}.swal2-file:focus,.swal2-input:focus,.swal2-textarea:focus{box-shadow:inset 0px 0px 4px 1px rgba(100,150,200,.5) !important}.swal2-checkbox-custom{align-items:center;justify-content:center;background:#fff;color:inherit;margin:1em auto}.swal2-checkbox-custom input{flex-shrink:0;margin:0 .4em}.giveaway-actions #getKey{display:none !important}.auto-task-giveaway-status{color:#fff;border-radius:10px;padding:0 5px;margin-left:5px}.auto-task-giveaway-status.active{background-color:#5cb85c}.auto-task-giveaway-status.not-active{background-color:#d9534f}'; const data$1 = { website: '网站', type: '类型', edit: '编辑', whiteList: '白名单', skipTask: '跳过撤销任务', whiteListOptions: '白名单设置', changeWhiteListOption: '设置白名单(%0)', whiteListNotFound: '找不到此项白名单: %0', changeWhiteListSuccess: '白名单修改成功,刷新生效!', changeWebsiteOptions: '网站设置', changeGlobalOptions: '全局设置', ok: '是', save: '保存', close: '关闭', return: '返回', option: '选项', value: '值', websiteOptions: '当前网站设置', changeWebsiteOptionsSuccess: '更改当前网站设置成功,刷新生效!', changeGlobalOptionsSuccess: '更改全局设置成功,刷新生效!', needLogin: '请先登录!', getTasksInfo: '正在获取并处理任务信息...', gettingKey: '正在获取Key...', verifyingTask: '正在验证任务', notice: '自动任务脚本提醒', noKeysLeft: '此页面已经没有剩余key了,是否关闭?', giveawayEnded: '此活动已结束,是否关闭?', giveawayNotWork: '此活动因某些原因(已结束/暂停/未开始...)不可用(如果是脚本误判请及时反馈),是否关闭?', confirm: '确定', cancel: '取消', unKnown: '未知', unKnownTaskType: '未识别的任务', doing: '正在做任务', allTasksComplete: '所有任务已完成!', getTaskIdFailed: '获取任务Id失败!', initSuccess: '%0 初始化成功!', initFailed: '%0 初始化失败!', errorLink: '链接错误: %0', needInit: '请先初始化', verifyingAuth: '正在验证%0凭证...', updatingAuth: '正在更新%0凭证...', refreshingToken: '正在刷新%0凭证...', settingToken: '正在设置%0凭证...', steamStoreTab: 'Steam商店(弹窗)', steamCommunityTab: 'Steam社区(弹窗)', initing: '正在初始化...', getFailed: '获取%0失败!', checkLoginFailed: '检测登录状态失败!', checkLeftKeyFailed: '检测剩余Key失败!', userId: '用户Id', joiningGiveaway: '正在加入赠Key', needJoinGiveaway: '需要先加入赠Key', cannotUndo: '此网站不支持取消任务', verifyAuth: '正在验证 %0 凭证...', closePageNotice: '如果此页面没有自动关闭,请自行关闭本页面。', errorReport: '检测到脚本报错,是否前往反馈BUG?', visitingLink: '正在访问链接...', doTask: '做任务', undoTask: '撤销任务', verifyTask: '验证任务', getKey: '获取Key', selectAll: '全选', selectNone: '全不选', invertSelect: '反选', doFreeTask: '加入免费赠品', doPointTask: '加入点数赠品', skipTaskOption: '设置中已配置跳过任务', other: '其他', globalOptions: '全局设置', checkLogin: '登录检测</br>需要登录的网站自动登录,部分网站支持', checkLeftKey: '剩余Key检测</br>赠Key活动结束提示是否关闭,部分网站支持', twitterVerifyId: '通过尝试关注该账号验证Twitter凭证</br>默认为Twitter官方帐号 783214</br>不想关注官方账号可以改为自己的帐号', youtubeVerifyChannel: '通过尝试订阅该频道验证YouTube凭证</br>默认为YouTube官方频道 UCrXUsMBcfTVqwAS7DKg9C0Q</br>不想关注官方频道可以改为自己的频道', autoUpdateSource: '更新源</br>github: 需代理,实时更新</br>jsdelivr: 可不用代理,更新有延迟</br>standby: 备用</br>auto: 依次使用github, jsdelivr, standby源进行尝试更新', saveGlobalOptions: '保存设置', settingPage: '设置页面', name: '名称', version: '版本', scriptManager: '脚本管理器', script: '脚本', environment: '环境', os: '系统', browser: '浏览器', getId: '获取 %0 id', getTwitterUserId: '获取Twitter用户id(获取id功能仅在设置页面可用)', getYoutubeChannelId: '获取Youtube频道id(获取id功能仅在设置页面可用)', showButton: '显示按钮', hideButton: '隐藏按钮', showLog: '显示日志', hideLog: '隐藏日志', defaultShowButton: '默认显示按钮', defaultShowLog: '默认显示日志', debug: '输出调试日志,不要开启此选项!', receivePreview: '接收预览版更新', position: '组件位置', buttonSideX: '按钮区域水平方向定位(实时预览功能仅在设置页面可用)</br>left: 靠左 | right: 靠右', buttonSideY: '按钮区域垂直方向定位(实时预览功能仅在设置页面可用)</br>top: 靠上 | bottom: 靠下', buttonDistance: '按钮区域距边缘的距离(实时预览功能仅在设置页面可用)</br>格式: X距离,Y距离', showButtonSideX: '显示按钮水平方向定位(实时预览功能仅在设置页面可用)</br>left: 靠左 | right: 靠右', showButtonSideY: '显示按钮垂直方向定位(实时预览功能仅在设置页面可用)</br>top: 靠上 | bottom: 靠下', showButtonDistance: '显示按钮距边缘的距离(实时预览功能仅在设置页面可用)</br>格式: X距离,Y距离', logSideX: '日志区域水平方向定位(实时预览功能仅在设置页面可用)</br>left: 靠左 | right: 靠右', logSideY: '日志区域垂直方向定位(实时预览功能仅在设置页面可用)</br>top: 靠上 | bottom: 靠下', logDistance: '日志区域距边缘的距离(实时预览功能仅在设置页面可用)</br>格式: X距离,Y距离', hotKey: '快捷键', doTaskKey: '做任务快捷键</br>(实时预览功能仅在设置页面可用)', undoTaskKey: '撤销任务快捷键</br>(实时预览功能仅在设置页面可用)', toggleLogKey: '显示/隐藏日志快捷键</br>(实时预览功能仅在设置页面可用)', tasksHistory: '任务历史', clearHistory: '清空历史', clearHistoryFinished: '已清空任务历史!', deleteTask: '删除任务', lastChangeTime: '最后一次修改时间', clearTaskFinished: '删除以下任务完成!', clearTaskFailed: '删除任务失败,没有找到任务名!', syncData: '数据同步', settingData: '正在上传数据...', gettingData: '正在获取数据...', help: '帮助', fileName: '文件名', upload2gist: '同步到Gist', downloadFromGist: '从Gist同步', saveAndTest: '保存配置并测试', testSuccess: '测试成功!', testFailed: '测试失败!', saveAndTestNotice: '请先保存配置并测试!', processingData: '正在处理数据...', updatingData: '正在上传数据...', syncDataSuccess: '同步数据成功!', syncDataFailed: '同步数据失败,请在控制台查看错误信息!', downloadingData: '正在下载数据...', checkedNoData: '没有检测到远程数据,请确认配置是否正确!', savingData: '正在保存数据...', syncHistory: '同步任务历史', checkUpdateFailed: '检测更新失败', newVersionNotice: '检测到新版本V%0, <a class="high-light" href="%1" target="_blank">点此更新</a>', language: '语言</br>目前仅支持zh: 中文, en: 英文', gistOptions: 'Gist 设置', swalNotice: '检测到您第一次安装V4版本脚本,请前往阅读用前必读内容!', echoNotice: '检测到您第一次安装V4版本脚本,请<a class="high-light" href="%0" target="_blank">点此前往</a>阅读用前必读内容!', noticeLink: 'https://auto-task-doc.js.org/guide/#用前必读', toGithub: '前往Github反馈', toKeylol: '前往其乐论坛反馈', copySuccess: '错误信息已复制到剪切板,是否前往其乐论坛反馈?', copyFailed: '请复制下方错误信息后前往Keylol论坛反馈!', updateText: '%0 版本更新内容:', Active: '进行中', Ended: '已结束', Banned: '已封禁', Paused: '已暂停', notStart: '未开始', noRemoteData: '检测到远程无数据', errorRemoteDataFormat: '远程数据格式错误', updateHistory: '历史更新记录<a class="high-light" href="https://auto-task-doc.js.org/logs/" target="_blank">点此查看</a>', AsfEnabled: '使用ASF做Steam相关任务(需<a href="https://github.com/chr233/ASFEnhance" target="_blank">ASFEnhance</a>插件)', steamWeb: '同时使用Steam Web API做任务', preferASF: '优先使用ASF', AsfIpcUrl: 'ASF IPC 地址', AsfIpcPassword: 'ASF IPC 密码', versionNotMatched: '脚本管理器版本过低,需TamperMonkey >= 5.2.0或TamperMonkey Beta >= 5.2.6196', curatorLimitNotice: '失败:可能是鉴赏家关注数量限制,请<a class="high-light" href="https://store.steampowered.com/curators/home/" target="_blank">取关部分鉴赏家</a>后再试', unknownScriptHandler: '未知脚本管理器,适用性未知', debugModeNotice: '检测到 DEBUG 模式已开启,非必要请关闭!', steamWebApiKey: 'Steam Web API 密钥<br>用于检测游戏挂机状态,<a href="https://steamcommunity.com/dev/apikey" target="_blank">点此申请</a>', groups: '组', officialGroups: '官方组', wishlists: '愿望单', follows: '游戏关注', forums: '论坛', workshops: '创意工坊收藏', curators: '鉴赏家', workshopVotes: '创意工坊点赞', announcements: '社区通知', steamCommunity: 'Steam社区', steamStore: 'Steam商店', licenses: '入库免费游戏', playtests: '请求访问权限', playTime: '挂时长', needLoginSteamStore: '请先<a href="https://store.steampowered.com/login/" target="_blank">登录Steam商店</a>', needLoginSteamCommunity: '请先<a href="https://steamcommunity.com/login/home/" target="_blank">登录Steam社区</a>', joiningSteamGroup: '正在加入Steam组', leavingSteamGroup: '正在退出Steam组', gettingSteamGroupId: '正在获取Steam组Id', joiningSteamOfficialGroup: '正在加入Steam官方组', leavingSteamOfficialGroup: '正在退出Steam官方组', gettingSteamOfficialGroupId: '正在获取Steam官方组Id', subscribingForum: '正在订阅Steam论坛', unsubscribingForum: '正在取消订阅Steam论坛', gettingForumId: '正在获取Steam论坛Id', followingCurator: '正在关注Steam鉴赏家', unfollowingCurator: '正在取关Steam鉴赏家', gettingCuratorId: '正在获取Steam鉴赏家Id', addingToWishlist: '正在添加游戏到Steam愿望单', removingFromWishlist: '正在从Steam愿望单移除游戏', followingGame: '正在关注Steam游戏', unfollowingGame: '正在取关Steam游戏', favoritingWorkshop: '正在收藏Steam创意工坊物品', unfavoritingWorkshop: '正在取消收藏Steam创意工坊物品', gettingWorkshopAppId: '正在获取Steam创意工坊物品Id', votingUpWorkshop: '正在点赞Steam创意工坊物品', gettingAnnouncementParams: '正在获取Steam通知信息', likingAnnouncement: '正在点赞Steam通知', changingArea: '正在更换Steam地区: %0...', notNeededChangeArea: '当前地区不需要更换', noAnotherArea: '请检测是否开启正确开启代理', gettingAreaInfo: '正在获取Steam地区信息...', changeAreaNotice: '疑似锁区游戏,尝试换区执行', steamFinishNotice: 'Steam任务完成,尝试将购物车地区换回', gettingSubid: '正在获取游戏subid', addingFreeLicense: '正在入库', missParams: '缺少参数', gettingLicenses: '正在获取Licenses...', requestingPlayTestAccess: '正在请求访问权限', gettingDemoAppid: '正在获取Steam游戏的Demo Appid', tryChangeAreaNotice: '此功能无法检测游戏是否限区,因此会尝试换区后再入库,换区失败也不影响后续入库', gettingUserInfo: '正在获取Steam用户社区凭证...', retry: '重试', owned: '已拥有', redirect: '重定向', noSubid: '无法获取,跳过', noASFInstance: '未启用ASF,跳过挂时长任务', initingASF: '正在初始化ASF...', playingGames: '正在挂游戏时长[%0]...', stoppingPlayGames: '正在停止挂游戏时长...', stopPlayTimeTitle: 'Steam游戏挂机时长满足,是否结束挂机?', stopPlayTimeText: '挂机已超时:%0 分钟', ASFNotSupportted: '当前功能(%0)ASF无法实现,跳过', checkingPlayGamesStatus: '正在检查挂游戏时长状态...', gettingSteamId: '正在获取Steam ID...', checkingPlayStatus: '正在检查挂机状态...', noPlayStatus: '游戏未运行', servers: '服务器', joiningDiscordServer: '正在加入Discord服务器', leavingDiscordServer: '正在退出Discord服务器', gettingDiscordGuild: '正在获取Discord服务器Id', getDiscordAuthFailed: '获取Discord凭证失败,请检测Discord帐号是否已登录', discordImportantNotice: '重要提醒!!!', discordImportantNoticeText: '由于Discord网站后台更新,目前使用此脚本加组后可能会导致Discord帐号被强制退出,且需要两步验证才能正常登录,请谨慎使用!!!', continueDiscordTask: '本次执行Discord任务', skipDiscordTask: '本次跳过Discord任务', continueAndDontRemindAgain: '总是执行Discord任务且不再提醒', gettingDiscordXContextProperties: '正在获取Discord加群参数', captchaNeeded: '检测到人机验证,请手动完成!', users: '用户', loginIns: '请先<a href="https://www.instagram.com/accounts/login/" target="_blank">登录Instagram</a>', insBanned: '您的Instagram账户已被封禁', verifyingInsAuth: '正在验证Instagram凭证...', gettingInsUserId: '正在获取Instagram用户Id', followingIns: '正在关注Instagram用户', unfollowingIns: '正在取关Instagram用户', reddits: '社区/用户', loginReddit: '请先<a href="https://www.reddit.com/login/" target="_blank">登录Reddit</a>', changingRedditVersion: '正在切换Reddit为新版页面...', joiningReddit: '正在加入Reddit社区', leavingReddit: '正在退出Reddit社区', followingRedditUser: '正在关注Reddit用户', unfollowingRedditUser: '正在取关Reddit用户', channels: '频道', followingTwitchChannel: '正在关注Twitch频道', unfollowingTwitchChannel: '正在取关Twitch频道', gettingTwitchChannelId: '正在获取Twitch频道Id', checkingTwitchIntegrity: '正在检查Twitch完整性...', twitterUser: '推特用户', retweets: '转推', gettingTwitterUserId: '正在获取推特用户Id', followingTwitterUser: '正在关注推特用户', unfollowingTwitterUser: '正在取关推特用户', retweetting: '正在转推', unretweetting: '正在撤销转推', names: '组/社区/动态', loginVk: '请先<a href="https://vk.com/login/" target="_blank">登录Vk</a>', gettingVkId: '正在获取Vk任务Id', joiningVkGroup: '正在加入Vk组', leavingVkGroup: '正在退出Vk组', joiningVkPublic: '正在加入Vk社区', leavingVkPublic: '正在退出Vk社区', sendingVkWall: '正在转发Vk动态', deletingVkWall: '正在撤销转发Vk动态', youtubeChannel: 'YouTube频道', likes: '点赞', loginYtb: '请先<a href="https://accounts.google.com/ServiceLogin?service=youtube" target="_blank">登录YouTube</a>', tryUpdateYtbAuth: '请尝试<a href="https://www.youtube.com/#auth" target="_blank">更新YouTube凭证</a>', gettingYtbToken: '正在获取YouTube Token...', followingYtbChannel: '正在订阅YouTube频道', unfollowingYtbChannel: '正在退订YouTube频道', likingYtbVideo: '正在点赞YouTube视频', unlikingYtbVideo: '正在取消点赞YouTube视频', giveKeyNoticeBefore: '每次验证间隔15s', giveKeyNoticeAfter: '如果没有key, 请在<a href="https://givekey.ru/profile" target="_blank">https://givekey.ru/profile</a>查看', noPoints: '点数不够,跳过抽奖', getNeedPointsFailed: '获取所需点数失败,跳过抽奖', joiningLottery: '正在加入抽奖', doingGleamTask: '正在做Gleam任务...', gettingGleamLink: '正在获取Gleam任务链接...', gleamTaskNotice: '如果此页面长时间未关闭,请完成任一任务后自行关闭!', verifiedGleamTasks: '已尝试验证所有任务,验证失败的任务请尝试手动验证或完成!', campaign: '检测到人机验证,请手动完成!3秒后重新检测...', gsNotice: '为避免得到"0000-0000-0000"key, 已自动屏蔽"Grab Key"按钮,获取key时请关闭脚本!', giveeClubVerifyNotice: '正在验证任务...', giveeClubVerifyFinished: '请等待验证完成后自行加入赠Key', doingKeyhubTask: '正在做Keyhub任务...', SweepWidgetNotice: '正在处理并验证任务,每次验证任务有1~3s间隔防止触发验证过快警告...', tasksNotCompleted: '任务未完成', notConnect: '社交平台未连接,跳过任务: %0', tgTaskNotice: '检测到Telegram任务,需要手动完成', updatingUserData: '正在更新用户数据...', gettingUserGames: '正在获取用户游戏...', confirmingTask: '正在跳过警告页面...', unSupporttedTaskType: '不支持的任务类型: %0', taskNotFinished: '有任务未完成,不获取密钥', logCopied: '完整日志已复制到剪切板,请前往反馈!' }; const data = { website: 'Website', type: 'Type', edit: 'Edit', whiteList: 'Whitelist', skipTask: 'Skip undo task', whiteListOptions: 'Whitelist options', changeWhiteListOption: 'Whitelist option(%0)', whiteListNotFound: 'Cannot find this whitelist: %0', changeWhiteListSuccess: 'The whitelist is successfully modified, and the page refresh will take effect!', changeWebsiteOptions: 'Website options', changeGlobalOptions: 'Global options', ok: 'OK', save: 'Save', close: 'Close', return: 'Return', option: 'Option', value: 'Value', websiteOptions: 'Current website settings', changeWebsiteOptionsSuccess: 'The current website setting is changed successfully, and the page refresh will take effect!', changeGlobalOptionsSuccess: 'The global setting is changed successfully, and the refresh will take effect!', needLogin: 'Please log in first!', getTasksInfo: 'Obtaining and processing task information...', gettingKey: 'Getting Key...', verifyingTask: 'Verifying task', notice: 'Automatic task script notice', noKeysLeft: 'There are no more keys left on this page. Do you want to close it?', giveawayEnded: 'This event has ended, do you want to close it?', giveawayNotWork: 'This activity is unavailable for some reasons (banned/ended/paused/not started...)' + ' (if it is a script misjudgment, please give us feedback in time), is it closed?', confirm: 'Confirm', cancel: 'Cancel', unKnown: 'Unknown', unKnownTaskType: 'Unrecognized task', doing: 'Doing a task', allTasksComplete: 'All tasks have been completed!', getTaskIdFailed: 'Failed to obtain task Id!', initSuccess: '%0 was initialized successfully!', initFailed: '%0 initialization failed!', errorLink: 'Link error: %0', needInit: 'Please initialize first', verifyingAuth: 'Verifying %0 token...', updatingAuth: 'Update %0 token...', refreshingToken: 'Refreshing %0 token...', settingToken: 'Setting %0 token...', steamStoreTab: 'Steam store (new tab)', steamCommunityTab: 'Steam community(new tab)', initing: 'Initializing...', getFailed: 'Failed to get %0!', checkLoginFailed: 'Failed to detect login status!', checkLeftKeyFailed: 'Failed to detect the remaining keys!', userId: 'User Id', joiningGiveaway: 'Joining giveaway', needJoinGiveaway: 'Need to join the giveaway first', cannotUndo: 'This website does not support canceling tasks', verifyAuth: 'Verifying %0 token...', closePageNotice: 'f this page does not close automatically, please close this page yourself.', errorReport: 'A script error is detected, do you want to report the BUG?', visitingLink: 'Visiting link ...', doTask: 'DoTask', undoTask: 'UndoTask', verifyTask: 'Verify', getKey: 'GetKey', selectAll: 'SelectAll', selectNone: 'SelectNone', invertSelect: 'InvertSelect', doFreeTask: 'FreeTask', doPointTask: 'PointTask', skipTaskOption: 'Skip task has been configured in the settings', other: 'Other', globalOptions: 'Global Options', checkLogin: 'Login detection</br>Need to log in to the website automatically log in, part of this website supports.', checkLeftKey: 'Key remaining detection</br>The end of the giveaway event prompts whether to close or not, part of this website supports.', twitterVerifyId: 'Verify Twitter token by trying to follow the account.</br>The default is the official Twitter account 783214.</br>' + 'If you don\'t want to follow the official account, you can change it to your own account.', youtubeVerifyChannel: 'Verify YouTube token by trying to subscribe to the channel.</br>' + 'The default is the official YouTube channel UCrXUsMBcfTVqwAS7DKg9C0Q.</br>' + 'If you don\'t want to follow the official channel, you can change it to your own channel.', autoUpdateSource: 'The source to update</br>github: Fast update.</br>jsdelivr: Update is delayed.</br>' + 'standby: Standby source.</br>auto: Try to update using github, jsdelivr, standby sources in turn.', saveGlobalOptions: 'SaveSettings', settingPage: 'Setting Page', name: 'Name', version: 'Version', scriptManager: 'Script Manager', script: 'Script', environment: 'Environment', os: 'OS', browser: 'Browser', getId: 'Get %0 id', getTwitterUserId: 'Get Twitter user id (Get id function is only available on the settings page).', getYoutubeChannelId: 'Get Youtube channel id (Get id function is only available on the settings page).', showButton: 'ShowButton', hideButton: 'HideButton', showLog: 'ShowLog', hideLog: 'HideLog', defaultShowButton: 'Default display button', defaultShowLog: 'Display log by default', debug: 'Output debug log, do not enable this option!', receivePreview: 'Receive preview updates', position: 'Component position', buttonSideX: 'Horizontal positioning of the button area (real-time preview function is only available on the setting page).' + '</br>left: left | right: right', buttonSideY: 'The button area is positioned in the vertical direction (real-time preview function is only available on the settings page).' + '</br>top: top | bottom: bottom', buttonDistance: 'The distance between the button area and the edge (the real-time preview function is only available on the setting page).' + '</br> Format: X distance, Y distance', showButtonSideX: 'ShowButton horizontal positioning (real-time preview function is only available on the setting page).' + '</br>left: left | right: right', showButtonSideY: 'ShowButton vertical positioning (real-time preview function is only available on the setting page).' + '</br>top: top | bottom: bottom', showButtonDistance: 'The distance between the ShowButton and the edge (real-time preview function is only available on the setting page).' + '</br> Format: X distance, Y distance', logSideX: 'Horizontal positioning of the log area (real-time preview function is only available on the setting page).' + '</br>left: left | right: right', logSideY: 'Vertical positioning of the log area (real-time preview function is only available on the setting page).' + '</br>top: top | bottom: bottom', logDistance: 'The distance between the log area and the edge (the real-time preview function is only available on the setting page).' + '</br> Format: X distance, Y distance', hotKey: 'Shortcut key', doTaskKey: 'DoTask shortcut keys</br> (real-time preview function is only available on the settings page).', undoTaskKey: 'UndoTask shortcut keys</br> (real-time preview function is only available on the settings page).', toggleLogKey: 'ShowLog/HideLog shortcut keys</br> (real-time preview function is only available on the settings page).', tasksHistory: 'TasksHistory', clearHistory: 'Clear history', clearHistoryFinished: 'The mission history has been cleared!', deleteTask: 'Delete task', lastChangeTime: 'Last Change Time', clearTaskFinished: 'Delete the following tasks completed!', clearTaskFailed: 'Failed to delete the task, the task name was not found!', syncData: 'DataSync', settingData: 'Uploading data...', gettingData: 'Getting data...', help: 'Help', fileName: 'Filename', upload2gist: 'Sync to Gist', downloadFromGist: 'Sync from Gist', saveAndTest: 'Save configuration and test', testSuccess: 'Test success!', testFailed: 'Test failed!', saveAndTestNotice: 'Please save the configuration and test first!', processingData: 'Processing data...', updatingData: 'Uploading data...', syncDataSuccess: 'Synchronized data successfully!', syncDataFailed: 'Failed to synchronize data, please check the error message on the console!', downloadingData: 'Downloading data...', checkedNoData: 'No remote data is detected, please confirm whether the configuration is correct!', savingData: 'Saving data...', syncHistory: 'Synchronize tasks history', checkUpdateFailed: 'Check update failed', newVersionNotice: 'Checked a new version V%0, <a class="high-light" href="%1" target="_blank">click to update</a>', language: 'Language</br> Currently only supports zh: Chinese, en: English', gistOptions: 'Gist Settings', swalNotice: 'It is detected that you are installing the V4 version script for the first time' + ', please go to read the READ ME FIRST content before use!', echoNotice: 'It is detected that you are installing the V4 version script for the first time' + ', please <a class="high-light" href="%0" target="_blank">click here</a> to read the READ ME FIRST content before use!', noticeLink: 'https://auto-task-doc.js.org/en/guide/#read-me-first', toGithub: 'Feedback(Github)', toKeylol: 'Feedback(Keylol)', copySuccess: 'The error message has been copied to the clipboard. Do you want to go to the Keylol forum to give feedback?', copyFailed: 'Please copy the error information below and report back to the Keylol forum!', updateText: 'Updates in version %0:', Active: 'Active', Ended: 'Ended', Banned: 'Banned', Paused: 'Paused', notStart: 'notStart', noRemoteData: 'No data remotely', errorRemoteDataFormat: 'Remote data has wrong format', updateHistory: '<a class="high-light" href="https://auto-task-doc.js.org/logs/" target="_blank">Click here</a>' + ' to view the historical update record.', AsfEnabled: 'Use ASF to do Steam related tasks (requires <a href="https://github.com/chr233/ASFEnhance" target="_blank">ASFEnhance</a> plugin)', steamWeb: 'Use Steam Web API to do Steam related tasks simultaneously', preferASF: 'Prefer ASF to do Steam related tasks', AsfIpcUrl: 'ASF IPC URL', AsfIpcPassword: 'ASF IPC Password', curatorLimitNotice: 'Failed: Maybe the curator follow limit is reached, please <a class="high-light" href="https://store.steampowered.com/curators/home/" target="_blank">unfollow some curators</a> and try again', unknownScriptHandler: 'Unknown script handler, compatibility unknown', debugModeNotice: 'Detected DEBUG mode enabled, please close it if it is not needed!', steamWebApiKey: 'Steam Web API Key<br>Used to detect game idle status, <a href="https://steamcommunity.com/dev/apikey" target="_blank">click to apply</a>', groups: 'Group', officialGroups: 'Official Group', wishlists: 'Wishlist', follows: 'Follow Game', forums: 'Forum', workshops: 'Favorite Workshop', curators: 'Curator', workshopVotes: 'Voteup Workshop', announcements: 'Announcement', steamCommunity: 'Steam Community', steamStore: 'Steam Store', licenses: 'Add License', playtests: 'Playtest Access', needLoginSteamStore: 'Please <a href="https://store.steampowered.com/login/" target="_blank">log in to the Steam Store</a>', needLoginSteamCommunity: 'Please <a href="https://steamcommunity.com/login/home/" target="_blank">log in to the Steam Community</a>', joiningSteamGroup: 'Joining Steam Group', leavingSteamGroup: 'Leaving Steam Group', gettingSteamGroupId: 'Getting Steam Group Id', joiningSteamOfficialGroup: 'Joining Steam Official Group', leavingSteamOfficialGroup: 'Leaving Steam Official Group', gettingSteamOfficialGroupId: 'Getting Steam Official Group Id', subscribingForum: 'Subscribing the Steam Forum', unsubscribingForum: 'Unsubscribing the Steam Forum', gettingForumId: 'Getting Steam Forum Id', followingCurator: 'Following Steam Curator', unfollowingCurator: 'Unfollowing Steam Curator', gettingCuratorId: 'Getting Steam Curator Id', addingToWishlist: 'Adding the game to the Steam wishlist', removingFromWishlist: 'Removing the game from the Steam wishlist', followingGame: 'Following Steam games', unfollowingGame: 'Unfollowing Steam games', favoritingWorkshop: 'Favouring Steam Workshop Items', unfavoritingWorkshop: 'Unfavoriting Steam Workshop Items', gettingWorkshopAppId: 'Getting Steam Workshop Item Id', votingUpWorkshop: 'Liking Steam workshop items', gettingAnnouncementParams: 'Getting Steam announcement information', likingAnnouncement: 'Liking Steam announcement', changingArea: 'Changing Steam area: %0...', notNeededChangeArea: 'The current area does not need to be changed', noAnotherArea: 'Please check whether the proxy is turned on correctly', gettingAreaInfo: 'Getting Steam area information...', changeAreaNotice: 'Suspected of a locked zone game, try to change the zone to execute', steamFinishNotice: 'Steam task completed, try to change the shopping cart area back to ', gettingSubid: 'Getting subid', addingFreeLicense: 'Adding free license', missParams: 'Missing parameters', gettingLicenses: 'Getting licenses...', requestingPlayTestAccess: 'Requesting play test access', gettingDemoAppid: 'Getting demo appid for steam game', tryChangeAreaNotice: 'This function cannot detect whether the game is limited, so it will try to change the area before entering the library' + '. Failure to change the area will not affect the subsequent storage.', versionNotMatched: 'The script manager version is too low, requiring TamperMonkey >= 5.2.0 or TamperMonkey Beta >= 5.2.6196', gettingUserInfo: 'Getting steam user community credentials...', retry: 'Retry', owned: 'Owned', redirect: 'Redirect', noSubid: 'skip due to unrecognized', noASFInstance: 'ASF is not enabled, skip idle time task', initingASF: 'Initing ASF...', playingGames: 'Playing games [%0]...', stoppingPlayGames: 'Stopping play games...', stopPlayTimeTitle: 'The Steam game idle time has finished. Do you want to end it?', stopPlayTimeText: 'Time out: %0 minutes', ASFNotSupportted: 'The current function (%0) cannot be implemented by ASF, skipping', checkingPlayGamesStatus: 'Checking play games status...', gettingSteamId: 'Getting Steam ID...', checkingPlayStatus: 'Checking play status...', noPlayStatus: 'Game not running', servers: 'Server', joiningDiscordServer: 'Joining Discord Server', leavingDiscordServer: 'Leaving Discord Server', gettingDiscordGuild: 'Getting Discord server Id', getDiscordAuthFailed: 'Failed to get Discord token, please check whether the Discord account is logged in', discordImportantNotice: 'Important Reminder! ! !', discordImportantNoticeText: 'Due to the background update of the Discord website, currently using this script to join a group may cause the Discord account to be forcibly logged out, and two-step verification is required to log in normally, please use it with caution! ! !', continueDiscordTask: 'Do Discord tasks this time.', skipDiscordTask: 'Skip Discord tasks this time.', continueAndDontRemindAgain: 'Always do Discord tasks and do not remind again.', gettingDiscordXContextProperties: 'Getting Discord X context properties...', captchaNeeded: 'Captcha detected, please complete it manually!', users: 'User', loginIns: 'Please <a href="https://www.instagram.com/accounts/login/" target="_blank">log in to Instagram</a>', insBanned: 'Your Instagram account has been banned', verifyingInsAuth: 'Verifying Instagram token...', gettingInsUserId: 'Getting Instagram user Id', followingIns: 'Following Instagram user', unfollowingIns: 'Unfollowing Instagram user', reddits: 'Reddit/User', loginReddit: 'Please <a href="https://www.reddit.com/login/" target="_blank">log in to Reddit</a>', changingRedditVersion: 'Switching Reddit to a new version page...', joiningReddit: 'Joining the Reddit', leavingReddit: 'Leaving the Reddit', followingRedditUser: 'Following Reddit User', unfollowingRedditUser: 'Unfollowing Reddit User', channels: 'Channel', followingTwitchChannel: 'Following Twitch Channel', unfollowingTwitchChannel: 'Unfollowing Twitch Channel', gettingTwitchChannelId: 'Getting Twitch Channel Id', checkingTwitchIntegrity: 'Checking Twitch integrity...', twitterUser: 'Twitter User', retweets: 'Retweet', gettingTwitterUserId: 'Getting Twitter User Id', followingTwitterUser: 'Following Twitter User', unfollowingTwitterUser: 'Unfollowing Twitter User', retweetting: 'Retweetting', unretweetting: 'Unretweetting', names: 'Group/Public/Wall', loginVk: 'Please <a href="https://vk.com/login/" target="_blank">log in to Vk</a>', gettingVkId: 'Getting Vk task Id', joiningVkGroup: 'Joining Vk Group', leavingVkGroup: 'Leaving Vk Group', joiningVkPublic: 'Joining Vk Public', leavingVkPublic: 'Leaving Vk Public', sendingVkWall: 'Sending Vk Wall', deletingVkWall: 'Deleting Vk Wall', youtubeChannel: 'YouTube Channel', likes: 'Like', loginYtb: 'Please <a href="https://accounts.google.com/ServiceLogin?service=youtube" target="_blank">log in to YouTube</a>', tryUpdateYtbAuth: 'Please try to <a href="https://www.youtube.com/#auth" target="_blank">update YouTube token</a>', gettingYtbToken: 'Getting YouTube Token...', followingYtbChannel: 'Subscribing to YouTube channel', unfollowingYtbChannel: 'Unsubscribing to YouTube channel', likingYtbVideo: 'Liking YouTube video', unlikingYtbVideo: 'Unliking YouTube video', giveKeyNoticeBefore: 'Each verification interval is 15s', giveKeyNoticeAfter: 'If there is no key, please check at <a href="https://givekey.ru/profile" target="_blank">https://givekey.ru/profile</a>', noPoints: 'Not enough points, skip the lottery', getNeedPointsFailed: 'ailed to obtain the required points, skip the lottery', joiningLottery: 'Joining the lottery', doingGleamTask: 'Doing Gleam Task...', gettingGleamLink: 'Getting Gleam task link...', gleamTaskNotice: 'If this page has not been closed for a long time, please close it yourself after completing any task!', verifiedGleamTasks: 'Attempted to verify all tasks. If the verification fails, please try to verify manually or complete it!', campaign: 'ReCAPTCHA detected, please complete it manually! 3 seconds later, re-verify...', gsNotice: 'In order to avoid getting the "0000-0000-0000" key, the "Grab Key" button has been hidden,' + ' please close the script when obtaining the key!', giveeClubVerifyNotice: 'Verifying task...', giveeClubVerifyFinished: 'Wait for the verification to complete and join it by yourself', doingKeyhubTask: 'Doing Keyhub Task...', SweepWidgetNotice: 'The task is being processed and verified. ' + 'There is an interval of 1~3s for each verification task to prevent the triggering of too fast verification warning...', tasksNotCompleted: 'Tasks Not Completed', notConnect: 'Social platform is not connectted, skip task: %0', tgTaskNotice: 'The telegram task is checked, need to do it yourself!', updatingUserData: 'Updating user data...', gettingUserGames: 'Getting user games...', confirmingTask: 'Confirming task...', unSupporttedTaskType: 'Unsupportted task type: %0', taskNotFinished: 'There are tasks not completed, do not get the key', logCopied: 'Full log has been copied to the clipboard, please go to feedback!' }; const languages = { zh: data$1, en: data }; const SUPPORTED_LANGUAGES = [ 'zh', 'en' ]; const getCurrentLanguage = () => { const userLanguage = globalOptions.other.language; return SUPPORTED_LANGUAGES.includes(userLanguage) ? userLanguage : 'en'; }; const replacePlaceholders = (text, args) => text.replace(/%([\d]+)/g, ((_, index) => args[parseInt(index, 10)] || '')); const I18n = (key, ...args) => { const currentLanguage = getCurrentLanguage(); const translation = languages[currentLanguage]?.[key]; if (!translation) { console.warn(`Missing translation for key: ${key} in language: ${currentLanguage}`); return key; } return replacePlaceholders(translation, args); }; var DebugLevel; (function(DebugLevel) { DebugLevel['ERROR'] = 'error'; DebugLevel['WARN'] = 'warn'; DebugLevel['INFO'] = 'info'; DebugLevel['DEBUG'] = 'debug'; DebugLevel['TRACE'] = 'trace'; })(DebugLevel || (DebugLevel = {})); const defaultConfig = { enabled: false, level: DebugLevel.INFO, prefix: 'Auto-Task', styles: { error: 'color:#ff0000;font-weight:bold', warn: 'color:#ffa500', info: 'color:#a7a7a7', debug: 'color:#808080', trace: 'color:#87ceeb' }, showTimestamp: true }; class Debugger { config; levelPriority; constructor(config = {}) { this.config = { ...defaultConfig, ...config }; this.levelPriority = { error: 0, warn: 1, info: 2, debug: 3, trace: 4 }; } getTimestamp() { return (new Date).toLocaleString(); } shouldLog(level) { return this.config.enabled && this.levelPriority[level] <= this.levelPriority[this.config.level]; } formatMessage(level, message) { const parts = [ this.config.prefix ]; if (this.config.showTimestamp) { parts.push(`[${this.getTimestamp()}]`); } parts.push(`[${level.toUpperCase()}]:`); parts.push(message); return parts.join(' '); } log(level, message, ...args) { if (!this.shouldLog(level)) { return; } const formattedMessage = this.formatMessage(level, message); const style = this.config.styles[level]; if (args.length > 0) { console.groupCollapsed('%c%s', style, formattedMessage); args.forEach((arg => { console.log(util.inspect(arg, { showHidden: true, depth: null, colors: false })); })); console.groupEnd(); } else { console.log('%c%s', style, formattedMessage); } } error(message, ...args) { this.log(DebugLevel.ERROR, message, ...args); } warn(message, ...args) { this.log(DebugLevel.WARN, message, ...args); } info(message, ...args) { this.log(DebugLevel.INFO, message, ...args); } debug(message, ...args) { this.log(DebugLevel.DEBUG, message, ...args); } trace(message, ...args) { this.log(DebugLevel.TRACE, message, ...args); if (this.shouldLog(DebugLevel.TRACE)) { console.trace(); } } updateConfig(config) { this.config = { ...this.config, ...config }; } enable() { this.config.enabled = true; } disable() { this.config.enabled = false; } setLevel(level) { this.config.level = level; } } let debugInstance; const initDebug = () => { if (!debugInstance) { debugInstance = new Debugger({ enabled: window.DEBUG || false }); if (window.DEBUG) { debugInstance.setLevel(DebugLevel.DEBUG); } } return debugInstance; }; const debug = (...args) => { const instance = initDebug(); return instance.debug(...args); }; const getRunLogs = () => { debug('开始获取运行日志'); const logElements = $('#auto-task-info>li'); const logs = logElements.length > 0 ? $.makeArray(logElements).map((element => element.innerText)).join('\n') : ''; debug('运行日志获取完成', { logsLength: logs.length }); return logs; }; const getEnvironmentInfo = async () => { debug('开始获取环境信息'); const envInfo = { website: window.location.href, browser: JSON.stringify(await browser.getInfo(), null, 2), manager: `${GM_info.scriptHandler} ${GM_info.version}`, userScript: GM_info.script.version, logs: '', runLogs: getRunLogs() }; debug('环境信息获取完成', envInfo); return envInfo; }; const buildGithubIssueParams = async (name, errorStack, envInfo) => { debug('开始构建GitHub Issue参数', { name: name, errorStackLength: errorStack.length }); const params = { title: `[BUG] 脚本报错: ${name}`, labels: 'bug', template: 'bug_report.yml', website: envInfo.website, browser: envInfo.browser, manager: envInfo.manager, 'user-script': envInfo.userScript, logs: errorStack || '', 'run-logs': '' }; const runLogs = window.__allLogs.join('\n'); await GM_setClipboard(runLogs); debug('GitHub Issue参数构建完成', params); return params; }; const generateGithubLink = async (name, errorStack, envInfo) => { debug('开始生成GitHub Issue链接'); const params = new URLSearchParams(await buildGithubIssueParams(name, errorStack, envInfo)); const link = `https://github.com/HCLonely/auto-task/issues/new?${params.toString()}`; debug('GitHub Issue链接生成完成', { link: link }); return link; }; const logError = (name, errorStack) => { debug('记录错误日志', { name: name }); console.log('%c%s', 'color:white;background:red', `Auto-Task[Error]: ${name}\n${errorStack}`); }; const handleErrorReport = async (platform, name, errorStack, envInfo) => { debug('开始处理错误报告', { platform: platform, name: name }); { const githubLink = await generateGithubLink(name, errorStack, envInfo); debug('打开GitHub Issue链接', { githubLink: githubLink }); GM_openInTab(githubLink, { active: true }); } }; async function throwError(error, name) { debug('开始处理错误', { name: name, error: error }); if (window.TRACE) { debug('启用跟踪模式'); console.trace('%cAuto-Task[Trace]:', 'color:blue'); } const errorStack = error.stack || ''; logError(name, errorStack); debug('获取环境信息'); const envInfo = await getEnvironmentInfo(); envInfo.logs = errorStack; debug('显示错误报告对话框'); const {isConfirmed: isConfirmed} = await Swal.fire({ title: I18n('errorReport'), icon: 'error', showCancelButton: true, confirmButtonText: I18n('toGithub'), cancelButtonText: I18n('close') }); if (isConfirmed) { debug('用户确认提交错误报告'); await handleErrorReport('github', name, errorStack, envInfo); Swal.fire({ title: I18n('logCopied'), icon: 'success', showConfirmButton: false, showCancelButton: true, cancelButtonText: I18n('close') }); } else { debug('用户取消提交错误报告'); } } const parseHeaders = headerString => { debug('开始解析HTTP头', { headerString: headerString }); const headers = {}; if (!headerString) { debug('HTTP头为空,返回空对象'); return headers; } headerString.split('\n').forEach((header => { const [name, ...values] = header.trim().split(':'); const value = values.join(':').trim(); if (!name || !value) { return; } if (headers[name]) { headers[name] = Array.isArray(headers[name]) ? [ ...headers[name], value ] : [ headers[name], value ]; } else { headers[name] = value; } })); if (headers['set-cookie'] && !Array.isArray(headers['set-cookie'])) { headers['set-cookie'] = [ headers['set-cookie'] ]; } debug('HTTP头解析完成', { headers: headers }); return headers; }; const processResponse = (data, options) => { debug('开始处理响应数据', { responseType: options.responseType }); const headers = parseHeaders(data.responseHeaders); data.responseHeadersText = data.responseHeaders; data.responseHeaders = headers; data.finalUrl = headers.location || data.finalUrl; debug('响应头处理完成', { finalUrl: data.finalUrl }); if (options.responseType === 'json' && data?.response && typeof data.response !== 'object') { debug('尝试解析JSON响应'); try { data.response = JSON.parse(data.responseText); debug('JSON解析成功'); } catch { debug('JSON解析失败,保持原始响应'); } } }; const httpRequest = async (options, times = 0) => { debug('开始HTTP请求', { url: options.url, method: options.method, retryTimes: times }); if (window.TRACE) { console.trace('%cAuto-Task[Trace]:', 'color:blue'); } try { const result = await new Promise((resolve => { const requestObj = { fetch: true, timeout: 3e4, ontimeout: data => { debug('请求超时', { url: options.url }); resolve({ result: 'Error', statusText: 'Timeout', status: 601, data: data, options: options }); }, onabort: () => { debug('请求被中止', { url: options.url }); resolve({ result: 'Error', statusText: 'Aborted', status: 602, data: undefined, options: options }); }, onerror: data => { debug('请求发生错误', { url: options.url, error: data }); resolve({ result: 'Error', statusText: 'Error', status: 603, data: data, options: options }); }, onload: data => { debug('请求加载完成', { url: options.url, status: data.status }); processResponse(data, options); resolve({ result: 'Success', statusText: 'Load', status: 600, data: data, options: options }); }, ...options, responseType: options.dataType || options.responseType }; debug('发送请求', { requestObj: requestObj }); GM_xmlhttpRequest(requestObj); })); if (window.DEBUG) { console.log('%cAuto-Task[httpRequest]:', 'color:blue', result); } if (result.status !== 600 && times < 2) { debug('请求失败,准备重试', { status: result.status, retryTimes: times + 1 }); return await httpRequest(options, times + 1); } debug('请求完成', { status: result.status, result: result.result }); return result; } catch (error) { debug('请求发生JavaScript错误', { error: error }); console.log('%cAuto-Task[httpRequest]:', 'color:red', JSON.stringify({ errorMsg: error, options: options })); throwError(error, 'httpRequest'); return { result: 'JsError', statusText: 'Error', status: 604, error: error, options: options }; } }; var ASF = '<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" version="1.1">\n<g id="surface1">\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.372549%,17.254902%,26.274511%);fill-opacity:1;" d="M 7.652344 0.175781 C 8.046875 0.351562 8.210938 0.59375 8.441406 0.953125 C 9.007812 1.746094 9.644531 2.539062 10.640625 2.761719 C 12.011719 2.949219 13.429688 2.882812 14.808594 2.796875 C 14.90625 2.792969 15.003906 2.785156 15.101562 2.78125 C 15.1875 2.773438 15.273438 2.769531 15.363281 2.761719 C 15.429688 2.769531 15.496094 2.777344 15.566406 2.78125 C 15.761719 3.082031 15.757812 3.191406 15.75 3.539062 C 15.75 3.625 15.746094 3.710938 15.746094 3.800781 C 15.742188 3.867188 15.742188 3.933594 15.738281 4 C 15.824219 4.027344 15.910156 4.058594 16 4.085938 C 15.988281 4.34375 15.976562 4.597656 15.960938 4.851562 C 15.957031 4.925781 15.957031 5 15.953125 5.074219 C 15.945312 5.175781 15.945312 5.175781 15.941406 5.285156 C 15.9375 5.378906 15.9375 5.378906 15.929688 5.480469 C 15.914062 5.652344 15.914062 5.652344 15.824219 5.914062 C 15.816406 6.058594 15.8125 6.203125 15.8125 6.34375 C 15.8125 6.433594 15.8125 6.519531 15.808594 6.613281 C 15.808594 6.707031 15.808594 6.800781 15.808594 6.898438 C 15.804688 7.097656 15.804688 7.300781 15.800781 7.5 C 15.796875 7.816406 15.792969 8.132812 15.792969 8.449219 C 15.789062 8.753906 15.785156 9.058594 15.78125 9.363281 C 15.78125 9.457031 15.78125 9.550781 15.78125 9.648438 C 15.777344 9.738281 15.777344 9.824219 15.773438 9.914062 C 15.773438 10.03125 15.773438 10.03125 15.773438 10.148438 C 15.738281 10.347656 15.738281 10.347656 15.625 10.503906 C 15.386719 10.671875 15.152344 10.625 14.871094 10.609375 C 14.867188 10.675781 14.859375 10.742188 14.855469 10.808594 C 14.816406 11.367188 14.746094 11.890625 14.609375 12.433594 C 14.3125 12.535156 14.105469 12.507812 13.804688 12.441406 C 13.6875 12.414062 13.6875 12.414062 13.570312 12.390625 C 13.480469 12.367188 13.480469 12.367188 13.390625 12.347656 C 13.359375 12.515625 13.359375 12.515625 13.324219 12.683594 C 13.226562 13.144531 13.054688 13.566406 12.871094 14 C 12.75 13.988281 12.628906 13.980469 12.511719 13.96875 C 12.410156 13.957031 12.410156 13.957031 12.308594 13.949219 C 12.050781 13.90625 11.8125 13.828125 11.566406 13.738281 C 11.492188 13.867188 11.492188 13.867188 11.417969 14 C 11.355469 14.113281 11.292969 14.222656 11.226562 14.335938 C 11.195312 14.394531 11.164062 14.449219 11.132812 14.507812 C 11 14.738281 10.882812 14.941406 10.695312 15.128906 C 10.257812 15.097656 9.957031 14.863281 9.601562 14.625 C 9.402344 14.503906 9.402344 14.503906 9.222656 14.542969 C 9.015625 14.617188 8.925781 14.703125 8.777344 14.863281 C 8.730469 14.914062 8.683594 14.964844 8.632812 15.019531 C 8.535156 15.125 8.433594 15.234375 8.335938 15.339844 C 7.972656 15.726562 7.972656 15.726562 7.675781 15.792969 C 7.4375 15.726562 7.367188 15.660156 7.21875 15.460938 C 7 15.1875 6.773438 14.941406 6.523438 14.695312 C 6.375 14.550781 6.230469 14.40625 6.085938 14.261719 C 6.023438 14.195312 5.957031 14.128906 5.890625 14.0625 C 2.175781 10.347656 2.175781 10.347656 1.945312 10.117188 C 1.738281 9.910156 1.527344 9.707031 1.316406 9.503906 C 1.160156 9.351562 1 9.199219 0.84375 9.042969 C 0.722656 8.925781 0.722656 8.925781 0.597656 8.808594 C 0.519531 8.734375 0.445312 8.660156 0.363281 8.582031 C 0.296875 8.515625 0.226562 8.445312 0.152344 8.375 C -0.03125 8.132812 -0.0351562 8.039062 0 7.738281 C 0.117188 7.570312 0.117188 7.570312 0.28125 7.402344 C 0.34375 7.339844 0.40625 7.277344 0.472656 7.210938 C 0.542969 7.144531 0.613281 7.074219 0.683594 7.003906 C 0.757812 6.933594 0.828125 6.859375 0.902344 6.785156 C 1.101562 6.585938 1.304688 6.386719 1.503906 6.1875 C 1.714844 5.980469 1.921875 5.773438 2.132812 5.5625 C 2.480469 5.214844 2.832031 4.863281 3.183594 4.515625 C 3.683594 4.023438 4.175781 3.53125 4.671875 3.039062 C 5.015625 2.695312 5.359375 2.355469 5.699219 2.015625 C 5.785156 1.929688 5.867188 1.847656 5.949219 1.765625 C 6.222656 1.492188 6.496094 1.222656 6.773438 0.949219 C 6.839844 0.882812 6.910156 0.8125 6.980469 0.742188 C 7.078125 0.648438 7.078125 0.648438 7.171875 0.550781 C 7.226562 0.496094 7.285156 0.441406 7.339844 0.386719 C 7.476562 0.261719 7.476562 0.261719 7.652344 0.175781 Z M 7.652344 0.175781 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(5.490196%,7.843138%,14.901961%);fill-opacity:1;" d="M 10.433594 2.78125 C 11.101562 2.777344 11.765625 2.773438 12.433594 2.769531 C 12.742188 2.769531 13.050781 2.765625 13.359375 2.761719 C 13.714844 2.757812 14.070312 2.757812 14.425781 2.757812 C 14.539062 2.753906 14.648438 2.753906 14.761719 2.753906 C 14.867188 2.753906 14.96875 2.753906 15.074219 2.753906 C 15.210938 2.753906 15.210938 2.753906 15.347656 2.75 C 15.421875 2.761719 15.492188 2.773438 15.566406 2.78125 C 15.761719 3.082031 15.757812 3.191406 15.75 3.539062 C 15.75 3.625 15.746094 3.710938 15.746094 3.800781 C 15.742188 3.867188 15.742188 3.933594 15.738281 4 C 15.824219 4.027344 15.910156 4.058594 16 4.085938 C 15.988281 4.332031 15.980469 4.578125 15.96875 4.824219 C 15.964844 4.929688 15.964844 4.929688 15.960938 5.035156 C 15.9375 5.492188 15.863281 5.90625 15.738281 6.347656 C 15.675781 6.3125 15.613281 6.273438 15.546875 6.234375 C 15.011719 5.921875 14.472656 5.609375 13.9375 5.296875 C 13.761719 5.195312 13.582031 5.089844 13.40625 4.988281 C 12.996094 4.746094 12.585938 4.507812 12.167969 4.273438 C 10.847656 3.527344 10.847656 3.527344 10.433594 2.957031 C 10.433594 2.898438 10.433594 2.839844 10.433594 2.78125 Z M 10.433594 2.78125 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(95.686275%,96.078432%,96.470588%);fill-opacity:1;" d="M 7.566406 5.304688 C 7.878906 5.410156 7.90625 5.480469 8.058594 5.761719 C 8.117188 5.859375 8.117188 5.859375 8.175781 5.964844 C 8.269531 6.195312 8.277344 6.363281 8.261719 6.609375 C 8.355469 6.550781 8.449219 6.492188 8.550781 6.433594 C 8.777344 6.296875 8.855469 6.261719 9.128906 6.261719 C 9.136719 6.425781 9.140625 6.589844 9.148438 6.753906 C 9.152344 6.894531 9.152344 6.894531 9.15625 7.035156 C 9.125 7.34375 9.058594 7.492188 8.871094 7.738281 C 8.703125 7.871094 8.703125 7.871094 8.527344 7.972656 C 8.46875 8.007812 8.410156 8.042969 8.351562 8.078125 C 8.292969 8.109375 8.234375 8.140625 8.175781 8.175781 C 8.113281 8.207031 8.054688 8.238281 7.996094 8.273438 C 7.710938 8.398438 7.511719 8.335938 7.21875 8.261719 C 6.832031 8.070312 6.476562 7.859375 6.261719 7.476562 C 6.167969 7.203125 6.164062 7.007812 6.167969 6.722656 C 6.167969 6.636719 6.171875 6.550781 6.171875 6.460938 C 6.171875 6.394531 6.171875 6.328125 6.175781 6.261719 C 6.492188 6.332031 6.753906 6.457031 7.042969 6.609375 C 7.050781 6.480469 7.050781 6.480469 7.058594 6.347656 C 7.113281 5.9375 7.332031 5.636719 7.566406 5.304688 Z M 7.566406 5.304688 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(5.882353%,8.235294%,15.294118%);fill-opacity:1;" d="M 15.652344 6.433594 C 15.679688 6.433594 15.710938 6.433594 15.738281 6.433594 C 15.746094 6.988281 15.75 7.539062 15.753906 8.09375 C 15.753906 8.28125 15.757812 8.46875 15.757812 8.65625 C 15.761719 8.925781 15.761719 9.195312 15.765625 9.464844 C 15.765625 9.550781 15.765625 9.636719 15.769531 9.722656 C 15.769531 9.800781 15.769531 9.878906 15.769531 9.960938 C 15.769531 10.027344 15.769531 10.097656 15.769531 10.167969 C 15.730469 10.398438 15.664062 10.472656 15.476562 10.609375 C 15.15625 10.625 15.15625 10.625 14.871094 10.609375 C 14.867188 10.675781 14.859375 10.742188 14.855469 10.808594 C 14.816406 11.367188 14.746094 11.890625 14.609375 12.433594 C 14.3125 12.535156 14.105469 12.507812 13.804688 12.441406 C 13.726562 12.421875 13.648438 12.40625 13.570312 12.390625 C 13.480469 12.367188 13.480469 12.367188 13.390625 12.347656 C 13.449219 12.027344 13.535156 11.730469 13.644531 11.425781 C 13.679688 11.332031 13.710938 11.238281 13.746094 11.144531 C 13.78125 11.046875 13.816406 10.953125 13.851562 10.851562 C 13.902344 10.710938 13.902344 10.710938 13.957031 10.566406 C 14.054688 10.289062 14.160156 10.015625 14.261719 9.738281 C 14.292969 9.65625 14.320312 9.574219 14.351562 9.488281 C 14.613281 8.792969 14.613281 8.792969 14.929688 8.523438 C 15.359375 8.140625 15.347656 7.753906 15.40625 7.203125 C 15.441406 6.886719 15.472656 6.707031 15.652344 6.433594 Z M 15.652344 6.433594 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(13.725491%,18.431373%,37.254903%);fill-opacity:1;" d="M 13.566406 9.824219 C 13.609375 9.914062 13.609375 9.914062 13.652344 10 C 13.539062 10.21875 13.417969 10.425781 13.289062 10.636719 C 13.207031 10.769531 13.125 10.90625 13.046875 11.039062 C 13.003906 11.109375 12.960938 11.183594 12.914062 11.253906 C 12.691406 11.632812 12.46875 12.015625 12.25 12.398438 C 12.207031 12.472656 12.160156 12.550781 12.117188 12.628906 C 11.8125 13.160156 11.53125 13.703125 11.269531 14.253906 C 11.113281 14.582031 10.933594 14.855469 10.695312 15.128906 C 10.3125 15.078125 10.042969 14.933594 9.710938 14.734375 C 9.574219 14.648438 9.574219 14.648438 9.429688 14.566406 C 9.359375 14.523438 9.289062 14.480469 9.21875 14.433594 C 9.253906 14.046875 9.523438 13.835938 9.789062 13.582031 C 9.832031 13.539062 9.875 13.496094 9.921875 13.449219 C 10.15625 13.21875 10.402344 12.996094 10.65625 12.78125 C 11.15625 12.34375 11.621094 11.871094 12.089844 11.398438 C 12.25 11.238281 12.414062 11.078125 12.574219 10.914062 C 12.679688 10.8125 12.78125 10.707031 12.886719 10.605469 C 12.933594 10.554688 12.980469 10.507812 13.03125 10.457031 C 13.234375 10.253906 13.40625 10.066406 13.566406 9.824219 Z M 13.566406 9.824219 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(95.294118%,95.686275%,96.078432%);fill-opacity:1;" d="M 6.175781 10.347656 C 6.503906 10.464844 6.769531 10.617188 7.054688 10.820312 C 7.128906 10.871094 7.203125 10.925781 7.277344 10.976562 C 7.445312 11.105469 7.59375 11.234375 7.738281 11.390625 C 7.769531 11.316406 7.769531 11.316406 7.800781 11.238281 C 7.941406 10.992188 8.105469 10.882812 8.335938 10.722656 C 8.410156 10.671875 8.488281 10.617188 8.566406 10.5625 C 8.761719 10.449219 8.90625 10.386719 9.128906 10.347656 C 9.21875 11.453125 9.21875 11.453125 8.914062 11.832031 C 8.617188 12.113281 8.285156 12.273438 7.914062 12.433594 C 7.882812 12.921875 7.855469 13.410156 7.824219 13.914062 C 7.683594 13.914062 7.539062 13.914062 7.390625 13.914062 C 7.390625 13.398438 7.390625 12.878906 7.390625 12.347656 C 7.246094 12.320312 7.105469 12.289062 6.957031 12.261719 C 6.5625 12.078125 6.359375 11.875 6.175781 11.476562 C 6.144531 11.097656 6.152344 10.726562 6.175781 10.347656 Z M 6.175781 10.347656 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(8.235294%,11.764706%,23.137255%);fill-opacity:1;" d="M 14.433594 9.042969 C 14.523438 9.21875 14.523438 9.21875 14.484375 9.378906 C 14.460938 9.445312 14.4375 9.507812 14.414062 9.574219 C 14.386719 9.648438 14.359375 9.71875 14.335938 9.792969 C 14.308594 9.871094 14.277344 9.949219 14.25 10.027344 C 14.222656 10.105469 14.195312 10.183594 14.164062 10.265625 C 14.007812 10.703125 13.84375 11.132812 13.675781 11.566406 C 13.503906 11.996094 13.386719 12.429688 13.285156 12.878906 C 13.179688 13.269531 13.03125 13.632812 12.871094 14 C 12.378906 13.96875 12 13.910156 11.566406 13.652344 C 11.703125 13.144531 11.960938 12.71875 12.222656 12.265625 C 12.269531 12.1875 12.316406 12.105469 12.363281 12.023438 C 12.460938 11.859375 12.554688 11.695312 12.648438 11.53125 C 12.765625 11.332031 12.882812 11.128906 12.996094 10.925781 C 13.402344 10.222656 13.859375 9.621094 14.433594 9.042969 Z M 14.433594 9.042969 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.433594 2.78125 C 11.101562 2.777344 11.765625 2.773438 12.433594 2.769531 C 12.742188 2.769531 13.050781 2.765625 13.359375 2.761719 C 13.714844 2.757812 14.070312 2.757812 14.425781 2.757812 C 14.539062 2.753906 14.648438 2.753906 14.761719 2.753906 C 14.867188 2.753906 14.96875 2.753906 15.074219 2.753906 C 15.210938 2.753906 15.210938 2.753906 15.347656 2.75 C 15.421875 2.761719 15.492188 2.773438 15.566406 2.78125 C 15.761719 3.082031 15.757812 3.191406 15.75 3.539062 C 15.75 3.625 15.746094 3.710938 15.746094 3.800781 C 15.742188 3.867188 15.742188 3.933594 15.738281 4 C 14.917969 3.957031 14.128906 3.839844 13.324219 3.6875 C 13.066406 3.640625 12.808594 3.59375 12.546875 3.550781 C 12.363281 3.515625 12.175781 3.484375 11.992188 3.449219 C 11.78125 3.414062 11.566406 3.378906 11.355469 3.34375 C 11.226562 3.324219 11.226562 3.324219 11.097656 3.304688 C 11.027344 3.292969 10.953125 3.28125 10.878906 3.273438 C 10.664062 3.207031 10.570312 3.132812 10.433594 2.957031 C 10.433594 2.898438 10.433594 2.839844 10.433594 2.78125 Z M 10.433594 2.78125 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(95.686275%,96.078432%,96.470588%);fill-opacity:1;" d="M 9.128906 8.261719 C 9.183594 9.257812 9.183594 9.257812 9.011719 9.695312 C 8.605469 10.082031 8.152344 10.3125 7.597656 10.398438 C 7.09375 10.3125 6.6875 10.128906 6.347656 9.738281 C 6.074219 9.320312 6.136719 8.828125 6.175781 8.347656 C 6.539062 8.394531 6.765625 8.519531 7.066406 8.734375 C 7.140625 8.789062 7.214844 8.839844 7.292969 8.894531 C 7.476562 9.042969 7.476562 9.042969 7.566406 9.21875 C 7.621094 9.21875 7.679688 9.21875 7.738281 9.21875 C 7.835938 9.117188 7.933594 9.015625 8.03125 8.914062 C 8.738281 8.261719 8.738281 8.261719 9.128906 8.261719 Z M 9.128906 8.261719 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(7.843138%,11.372549%,22.745098%);fill-opacity:1;" d="M 11.042969 3.476562 C 11.648438 3.675781 12.246094 3.886719 12.84375 4.101562 C 12.984375 4.152344 12.984375 4.152344 13.125 4.207031 C 14.054688 4.542969 14.984375 4.878906 15.914062 5.21875 C 15.867188 5.597656 15.816406 5.972656 15.738281 6.347656 C 15.675781 6.3125 15.613281 6.273438 15.546875 6.234375 C 15.011719 5.921875 14.472656 5.609375 13.9375 5.296875 C 13.667969 5.140625 13.402344 4.984375 13.132812 4.828125 C 12.804688 4.636719 12.476562 4.445312 12.148438 4.253906 C 12.011719 4.179688 11.878906 4.101562 11.746094 4.023438 C 11.683594 3.988281 11.621094 3.949219 11.558594 3.914062 C 11.140625 3.671875 11.140625 3.671875 11.042969 3.476562 Z M 11.042969 3.476562 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0.392157%);fill-opacity:1;" d="M 15.652344 6.433594 C 15.679688 6.433594 15.710938 6.433594 15.738281 6.433594 C 15.746094 6.988281 15.75 7.539062 15.753906 8.09375 C 15.753906 8.28125 15.757812 8.46875 15.757812 8.65625 C 15.761719 8.925781 15.761719 9.195312 15.765625 9.464844 C 15.765625 9.550781 15.765625 9.636719 15.769531 9.722656 C 15.769531 9.839844 15.769531 9.839844 15.769531 9.960938 C 15.769531 10.027344 15.769531 10.097656 15.769531 10.167969 C 15.730469 10.394531 15.664062 10.472656 15.476562 10.609375 C 15.199219 10.625 15.199219 10.625 14.957031 10.609375 C 14.875 10.285156 14.871094 10.046875 14.933594 9.71875 C 14.949219 9.636719 14.964844 9.550781 14.980469 9.464844 C 15 9.378906 15.015625 9.289062 15.03125 9.199219 C 15.050781 9.113281 15.066406 9.027344 15.082031 8.933594 C 15.203125 8.289062 15.203125 8.289062 15.304688 8.085938 C 15.34375 7.792969 15.375 7.5 15.40625 7.203125 C 15.441406 6.886719 15.472656 6.707031 15.652344 6.433594 Z M 15.652344 6.433594 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(86.274511%,87.450981%,89.019608%);fill-opacity:1;" d="M 8.609375 3.390625 C 8.78125 3.390625 8.953125 3.390625 9.128906 3.390625 C 9.128906 4.109375 9.128906 4.824219 9.128906 5.566406 C 8.988281 5.59375 8.84375 5.621094 8.695312 5.652344 C 8.609375 5.566406 8.609375 5.566406 8.597656 5.355469 C 8.597656 5.265625 8.601562 5.175781 8.601562 5.082031 C 8.601562 4.984375 8.601562 4.886719 8.601562 4.785156 C 8.601562 4.683594 8.601562 4.578125 8.601562 4.472656 C 8.605469 4.371094 8.605469 4.265625 8.605469 4.160156 C 8.605469 3.902344 8.605469 3.648438 8.609375 3.390625 Z M 8.609375 3.390625 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(93.725491%,94.117647%,94.509804%);fill-opacity:1;" d="M 6.175781 3.390625 C 6.316406 3.390625 6.460938 3.390625 6.609375 3.390625 C 6.609375 4.136719 6.609375 4.882812 6.609375 5.652344 C 6.464844 5.625 6.320312 5.59375 6.175781 5.566406 C 6.175781 4.847656 6.175781 4.128906 6.175781 3.390625 Z M 6.175781 3.390625 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(93.333334%,93.725491%,94.117647%);fill-opacity:1;" d="M 7.476562 2.433594 C 7.59375 2.433594 7.707031 2.433594 7.824219 2.433594 C 7.824219 3.179688 7.824219 3.925781 7.824219 4.695312 C 7.683594 4.695312 7.539062 4.695312 7.390625 4.695312 C 7.390625 4.335938 7.386719 3.976562 7.386719 3.613281 C 7.386719 3.511719 7.382812 3.410156 7.382812 3.300781 C 7.382812 3.203125 7.382812 3.105469 7.382812 3.003906 C 7.382812 2.914062 7.382812 2.824219 7.382812 2.730469 C 7.390625 2.523438 7.390625 2.523438 7.476562 2.433594 Z M 7.476562 2.433594 "/>\n<path style=" stroke:none;fill-rule:nonzero;fill:rgb(5.490196%,7.843138%,14.901961%);fill-opacity:1;" d="M 15.390625 6.871094 C 15.511719 7.203125 15.449219 7.484375 15.390625 7.824219 C 15.304688 7.769531 15.21875 7.710938 15.128906 7.652344 C 15.171875 7.328125 15.207031 7.148438 15.390625 6.871094 Z M 15.390625 6.871094 "/>\n</g>\n</svg>\n'; var Web = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="16" height="16">\n\t<title>favicon</title>\n\t<defs>\n\t\t<image width="256" height="256" id="img1" href="data:image/png;base64,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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>\n'; var Discord$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="16" height="16">\n\t<title>favicon</title>\n\t<defs>\n\t\t<image width="256" height="256" id="img1" href="data:image/png;base64,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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>\n'; var Twitch$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16">\n\t<title>favicon-32-e29e246c157142c94346</title>\n\t<defs>\n\t\t<image width="28" height="32" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgBAMAAADzmwVxAAAAAXNSR0IB2cksfwAAACdQTFRFAAAAk0b/kUb/////wZf/yKP/j0b/j0X/j0b/kUb/j0D/kUb/j0j/8nvBLQAAAA10Uk5TAH//////gJCgzxDfIMV/78EAAACZSURBVHicY2RgYFRkgANGBgYhAWQuUFIQxnvPCJJE4oJ0InFBOpG4Sgxg7ntBMKI9F81ebFzGdyhc+SvIXKa3dshc+as3kbhMb+MawFygANNbBga2GwwwrvwFBoaKNBhXGCjJXp4A4xoAJWe4McC4QMA51QGZO8sFEuwMDEoQKQS3KoUBiQsyFIkb18CAxFW/yYDMTVgA4wIAtrc0x9ZZthYAAAAASUVORK5CYII="/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="2" y="0"/>\n</svg>\n'; var Instagram = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" width="16" height="16">\n\t<title>lam-fZmwmvn</title>\n\t<defs>\n\t\t<image width="76" height="76" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEwAAABMCAMAAADwSaEZAAAAAXNSR0IB2cksfwAAAvdQTFRFR3BM1TeUehf6exj7gBn8bxf9bxX9eA32hAnukgXlngTjqATisAPguQLewAThxAHdygLd0AHazwDU3gDA3gDB7ROe1gDY3wDI2gDHYBlFeBf9cxn8gRf9ihf9kQvx3ADW6AC/kRf9oQzxrw3v4wDX5gDO7AC0uAnomRf86wDV8ADG8QPR8wC69AqfoBf8qBf74BTc6xTW8wCr9xOVvzT24WHu3oP39qzy9sr5+N78/ej6/t31+Xrj+lbX9xvS9wnJ//7+//////n69QCesRn7/gLD/gG+/gGy/8vq/ivO/gG5//X7/QKq9wea6k3l+zrS+yq8/gCj+ACPuhr6/gq3/gCc7xbO/A/BxRr4/RO4/gCW/VDD/2nM/gCQ0hzz/7Pe/gGJ/gmw/UG4+h2r/gGC3Bru6Bvd/QJ79BrD/hOt/pDO5Rro/hKk9xu2/gJ08Brf+x6g/hKY/S+j/QJt/SCW/hOQ/SKQ/m+3/haH/SKI/lul/+vw/QBn/EOT/h2B/hV8/iF6/iR0/idu/uHk/i1//QFi/its/oar/T94/ixm/Spd/mON/lZw/snV/i5V/omZ/hNw/kNZ//r2/jNO/jtT/jNi/gBf/l1c/jpJ/nh//tfS/jlc/jFH/jND/khR/jk//T9D/hFn/ipM/kM6/js1/kZB/sS1/R1k/lA9/Us1/kEq/oN1/qmz/g5c/nNI/lEv/hZb/lg2/kgf/h5Z/lgq/lQX/l8m/mI4/iJS/mMY/oxO/mch/mcv/FEP/FwG/pdo/m0b/WUB/rd//mwC/nQY/nQn/m4s/nMC/qOJ/ndk/vHc/noV/noC/oQd/p0//oIS/nsi/unI/tuq/oIC/rA9/ooB/owT/vbn/pAE/pUP/pYB/pwI/p0A/qkf/tBr/GMY/XQD+3QI/qQC/qoC/td8+4QW/q8A/rsW/rQA/rgA/rsA/r4A/YUD/SGH/sEA/JUF/sUA/JIG/skA/DJm/JgH/asD/s0A/EFI+68J/bcE+1E0+0w6/L0Fi9deiwAAAP10Uk5TAA5AcL3X////////////////5a9+QP//zwL/nP////////////////7/////xP//////df/////////////////////////////////l/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+XBcP///z/////////V1f+M/6v/oavV/9VwwMFw5VUJIaQAAAukSURBVHicbZhbbBxXGYD/f2Zn9uZL7NiNL4nTFLnUqZpKlaqKRCBIgApIWhKE1PahPKAiRFNaCdSqL4U+0odKlUorQXkAoSCQQLRqSwlKpIKaFAJNlFtDTdzarr221/a63uvc+S9nZ9eB8Xr27Myc7/zXc/4zCOmBcph2wg35kZ7wxg9Kl9WkQ0gbltVp/y8q/Qik3XZYhHAt2QIbhWU7RQ3B2va1/y8QYEpUko4aZUqjAKVRReBYOSUBrA3D6nD5JmqVsSMJWLAFBJCHJuZa3IrWktHSaAm7WEOCoz4oJFjtkCzoVk1AdLQURe3qetIeilkpiS9lIshwawlTUofDnZ2A/tQUm/lmHty19ZES/x61ykpKx7cjRi2PLI8vqs9Svdq81EtAVDq7sO4wbHR5pBtFmCz4plViEDJhC4cFo3MozYawACplcgBmhg1pC4qaJbrKpDZIEe0jJILERKOnxrDsZRLV3mGlQZIhlAtYS1G7u+JHjpaVNhPU+Gr08PA9gP9CtEduQEENBiQXcAumjY0gcRDXYGhVaAz3XRYtuIzWmLGkaMgsx7Jy3CvR0w2sBKwYIWllNGHWYqH10GngItpjDApZKkENb9pZxhOqm4byW12olwK0XSiNLQKMc5QOAGxgZpfcMihwMjkjqbELSHc0jS2XchixQtGCoa3gbfKEq0oMbzg5MELxx8rfYLNmDGmEJej3IcyNQ3kMFkkwKOC+jj2GYcPJ8qMJeLEzKNb3b6BxeCZBxtEe/rAfcdws7t6k3/14pz7DsQb9C2L5wuqOOUAOgiEjcjsK0qOwNNRg+bI9VY5BBzYHiYpfTB/oh/kCs7wggFpv644rjBpc7zJgt1ebkcteCosO0zYH1/vp8jfaDzjBLBksQW9ojh/2KV/dwQpIzGjmqKGMHzJOOUtm9SeqDj/iAE0g+ABnqibKTJHGLq5Q4mLr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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>'; var Twitter$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="16" height="16">\n\t<title>icon-ios</title>\n\t<defs>\n\t\t<image width="1024" height="1024" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAMAAABIw9uxAAAAAXNSR0IB2cksfwAAAwBQTFRFAAAABAQEFxcXGBgYDAwMERERCgoKfn5+wMDAv7+/k5OTFhYWKioqp6enq6urJycnPDw83Nzc////+/v7f39/BwcHKSkpu7u78vLyc3NzXl5e7+/v6+vrU1NTExMTra2t9/f3e3t7BgYGCQkJiIiI+fn50tLSLy8vDw8Pjo6O/f39mJiYrq6ur6+vGRkZCAgIh4eH/v7+Gxsb1NTUhISEAQEBZGRk8PDwvLy8ICAgVVVV6enp7OzsVlZWAwMDWFhY4+Pj0NDQODg4AgIC1tbWNTU1QkJC4eHh2NjYPz8/pKSktLS0Li4uxcXFy8vL+vr6wsLC7u7ubW1tBQUFSEhI5eXl8/PzfHx89vb2Nzc3EhISkpKS/Pz8nZ2dEBAQDQ0NuLi4hYWFIyMjwcHBi4uLYmJi7e3tvr6+Q0ND3d3dXFxczMzMZ2dn8fHx29vbOzs72traQEBAj4+Pvb29Hh4eMjIyx8fHHR0dt7e3JiYmaGhoNjY2oaGh39/fmZmZpqamFRUVrKysl5eXZmZmw8PDJCQkMDAwz8/Pa2tr6urqycnJMzMzUFBQ4uLifX19+Pj4xMTE5+fnFBQUo6OjnJycISEhb29vR0dH5OTkRUVFmpqalJSUcHBw9fX1xsbGJSUleHh4Dg4ObGxsKCgodHR0QUFB5ubmPj4+1dXV4ODgGhoaubm5eXl5hoaGOTk56Ojom5ubkZGRXV1dd3d3qKioUVFRysrKRkZGqqqqtra29PT0aWlpg4ODHBwcCwsLlpaWenp6sbGxcXFxT09Pzs7O19fXSkpK3t7e2dnZIiIijIyMgICAYGBgbm5u0dHRUlJScnJylZWVsLCwurq6Tk5OVFRU09PTNDQ0Ojo6KysrPT09Y2NjZWVlW1tbjY2NtbW1yMjIYWFhn5+fWlpadnZ2TU1Nzc3NpaWlLS0tgYGBV1dXoqKiWVlZS0tLdXV1s7OzHx8fgoKCoKCgkJCQREREX19fTExMSUlJqampMTExLCwsiYmJsrKyampqioqKnp6enA0qUQAAUvtJREFUeJzt3X1gjmX/P/DrZFt5aNTQ4reNzTC5Lasrj9tks7Kk1iwNbR5KMVHa8rDGPM00NzeNodCEydMippIVlqhMSTGR4ksSIk+F7HfXfd9luGzXcb2P6zyv83i//vne9/fb8TnOb+y96zrO4/gcmoWIlKXp/QBEpB8GAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwB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ABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHCGABECmMAECmMAUCkMAYAkcIYAEQKYwAQKYwBQKQwBgCRwhgARApjABApjAFApDAGAJHC/j9GYmsPmw8LugAAAABJRU5ErkJggg=="/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>\n'; var Reddit$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" width="16" height="16">\n\t<title>192x192</title>\n\t<defs>\n\t\t<image width="192" height="192" id="img1" href="data:image/png;base64,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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>'; var Youtube$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="16" height="16">\n\t<title>favicon_48x48</title>\n\t<defs>\n\t\t<image width="48" height="48" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAAXNSR0IB2cksfwAAARRQTFRF////AAAA/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/xBA/wAz/56x/5uv/xZF/wAz/wAz/6Cz/////+/y/26L/wQ2/wAz/wAz/9Pc/0Bm/6m6/x1K/wAz//X3/3uV/wg5/wAz/93k/01x/7fF/yVR/yZR/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAz/wAzgb8Y2wAAAFx0Uk5TAAAJNFp0i52ptMDQ3ODl8Pj/99uoWS+z+zD6uxL+DEhAcm2Vkav/v////7zU///////T6P/////0////8///////8ufSlEEUDVe1/bkyOF13jKC2wd3mqp92XDdJ2BKpAAABRElEQVR4nO2VwUtCQRDG53vV0/DwEhNDXoREdCjC0CK6Rlj9vYHdg04SiSAUEUFE8gwJi0roNb0tHwS7Gzu3iOawh5nvt+zOzs6AhIZ/wAXA1/rdxl7ERMz+K3PukXlmwJwCRWDksHeGvd4nMDv14nic6ShOAK/kqk+Itz5QfnbWE/kRQjwJgIyPhaFATxSg8iAC8lgciIAClu41Z7FvByawHGnOEnBnA8pYvdWcoaqPazNQQe3KACQJx6URiBF4RoAoiwsD8I6tcwtAOaCrxVaQ13cJ008SoKMFfwTUO7WFgHcmAYxHEl56XZ7WnVMjYH84YWnU5MUnLu/dlj1qsIL8iwqbwAYaXVGbqcobGeYgbJW015Y14yTvdZw4yLeBw3SgzFdVNXTW0Dw42myNvWqANJrM+zyJURY4vlHS3zgU/wDwAXpXWYPo2LOhAAAAAElFTkSuQmCC"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>'; var Vk$1 = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="16" height="16">\n\t<title>fav_logo</title>\n\t<defs>\n\t\t<image width="256" height="256" id="img1" href="data:image/png;base64,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"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>'; var AutoTask = '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16">\n\t<title>favicon</title>\n\t<defs>\n\t\t<image width="32" height="32" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IB2cksfwAAAgdQTFRFPHjYPnnYUIXbR3/ZUofbaJbgZpTfSIDaPXjYUYfbapfhbJnhZ5XgRn7ZP3rYU4jca5jhbZriaZfgR3/aVIncapjhZZTgRn/aVIjcbpvicp3iZ5bgQXvZVYncjrHorsfutMvvvtLyj7HoVorcdqDj0+H26/H7/v7+////1eH2apfgYpLfm7nq8fX87fP71uL2vNDxnLrq7fL7/v7/7PL7faXkWIvdRH3ZfqXk7vP7/f3+1+P2k7TpkbPpnLrrY5PfRn/ZPXnYXo/eu9DweqPkeKHjtMvwdaDjRX7ZQXvYWYzd1OL2dJ/jaJfgeKHk1eL2bpriWozdkLLoaZfhbZrhdZ/i7PH7WozeeKLk8/b8nbvrr8fvQHrYb5vitszwuM7wapjgfKTkz971/P3+9/n9mrnqtczwuM3wZpXgcp7jwdTyl7fqeaLkvtLxxtfzc53i1OH2+vz+t83wkrPoxNbzdJ7jZpTgjbDo6fD62uX39Pf8rsfvkbPoT4TaYZLfr8fuc57jcJviQnzYja/n+vv+rcbucZziYJHfXpDeqsTtkrPpQnzZd6Dj0+D21uP2UYbcYZHfdqHj8vb8q8Xussnv7/T7YpPfSoLaQHvYX5Dfnbrr0+D1h6vmYJHegajln7zrSYHZw9XzTIPaUIbbT4XbXY7ea5nhUofcW43dV4rdXI7dXI7eX5DeWOgbkgAAAmNJREFUeJx10l1IFFEUB/DzT/c6uTpb9CCIbYFBZVIhpuXLYD6oYCTSt9LXkgj7lL1kPVVbSSGbD5ZhUqD0AUKxrNqTK5KhRoo+WFgbZUHuw6Ls+jEz7jbdGbVYZ3deZuacH/ece+4FxTwAousisX9JXCiJgcDTfJFwIiDqeb1MMD4Q0hDR36oVgXgAGTJ4EdJkdRN+mYFokyPpeg1NS5pNmTGD7JANKz1wgUkz2AMss9UVdInhdeCgHvTLerM7QlY5Dej/D4oHpOHdbCM+ruJ8zIczx/LeroFyvEeRP2Ur+pixzdQc2xdtJz5EAisAlT4UT/+xjpYNzemiBD1sc/5X66gxD5BY7iUmCrtGZHXbjMLUI+P70SsUfM7vUVUhTKiZmgJVYKTQw9sr9Sgn9Ga76PiAtOilk08VFPA8nUJfyXOq5qmOs2ERniBd7K7AE1KdTZAmaA3U4g0qX1fhIY84uwygXkVhaRNbK3FGeUdVFjRzYBOkzAeEOQl089V3RitNWq782A7cW2YkaXqTqUHJxxsQG1wsd2GD3Td/q7Ha3kiaZZGlC0LN3fkok41B3Y+4mF0pyxr0phmDslDZgbbagcFr9drqqOG+fgcvTzf/NiZJatHRsbyhQ7gs/zusVtS7W4UCz5IekiYahiYd/Z0xp5l1o97dNrtXsX2jnMAnONNdftOVEyqWfy4YF8YaXVi6fd4E+NK59ox9NB6Y3tLdXiubQUcdn1g2+b2U7K4xXzn+2Qmn8XH42AU5HqDycw59ny1Jl0IUFxBe1IFakh/5KAEgZAXxzBGihIDEx5b23pjIX32q0uJJAW9GAAAAAElFTkSuQmCC"/>\n\t</defs>\n\t<style>\n\t</style>\n\t<use id="Background" href="#img1" x="0" y="0"/>\n</svg>'; const ICONS = { '[ASF]': ASF, '[Web]': Web, '[Discord]': Discord$1, '[Twitch]': Twitch$1, '[Instagram]': Instagram, '[Twitter]': Twitter$1, '[Reddit]': Reddit$1, '[Youtube]': Youtube$1, '[Vk]': Vk$1, '[AutoTask]': AutoTask }; const generateLink = (url, text) => `<a href="${url}" target="_blank">${text}</a>`; const createBaseElement = content => $(`<li>${content}<font class="log-status"></font></li>`).addClass('card-text'); const createPlatformElement = (type, text, id) => { const urlGenerators = { group: text => `https://steamcommunity.com/groups/${text}`, officialGroup: text => `https://steamcommunity.com/games/${text}`, forum: text => `https://steamcommunity.com/app/${text}/discussions/`, curator: text => `https://store.steampowered.com/${text?.includes('/') ? text : `curator/${text}`}`, app: text => `https://store.steampowered.com/app/${text}`, sub: text => `https://steamdb.info/sub/${text}/`, workshop: text => `https://steamcommunity.com/sharedfiles/filedetails/?id=${text}`, announcement: (text, id) => `https://store.steampowered.com/news/app/${text}/view/${id}`, discord: { invite: text => `https://discord.com/invite/${text}`, server: text => `https://discord.com/channels/@me/${text}` }, twitch: text => `https://www.twitch.tv/${text}`, instagram: text => `https://www.instagram.com/${text}/`, twitter: text => `https://x.com/${text}`, reddit: { subreddit: text => `https://www.reddit.com/r/${text}/`, user: text => `https://www.reddit.com/user/${text?.replace('u_', '')}` }, youtube: { channel: text => `https://www.youtube.com/channel/${text}`, video: text => `https://www.youtube.com/watch?v=${text}` }, vk: text => `https://vk.com/${text}/` }; const typeMap = { joiningSteamGroup: [ 'group' ], leavingSteamGroup: [ 'group' ], gettingSteamGroupId: [ 'group' ], joiningSteamOfficialGroup: [ 'officialGroup' ], leavingSteamOfficialGroup: [ 'officialGroup' ], gettingSteamOfficialGroupId: [ 'officialGroup' ], subscribingForum: [ 'forum' ], unsubscribingForum: [ 'forum' ], gettingForumId: [ 'forum' ], followingCurator: [ 'curator' ], unfollowingCurator: [ 'curator' ], gettingCuratorId: [ 'curator' ], addingToWishlist: [ 'app' ], removingFromWishlist: [ 'app' ], followingGame: [ 'app' ], unfollowingGame: [ 'app' ], gettingSubid: [ 'app' ], addingFreeLicense: [ 'app', 'sub' ], requestingPlayTestAccess: [ 'app' ], gettingDemoAppid: [ 'app' ], favoritingWorkshop: [ 'workshop' ], unfavoritingWorkshop: [ 'workshop' ], gettingWorkshopAppId: [ 'workshop' ], votingUpWorkshop: [ 'workshop' ], gettingAnnouncementParams: [ 'announcement' ], likingAnnouncement: [ 'announcement' ], joiningDiscordServer: [ 'discord', 'invite' ], gettingDiscordGuild: [ 'discord', 'invite' ], gettingDiscordXContextProperties: [ 'discord', 'invite' ], leavingDiscordServer: [ 'discord', 'server' ], followingTwitchChannel: [ 'twitch' ], unfollowingTwitchChannel: [ 'twitch' ], gettingTwitchChannelId: [ 'twitch' ], gettingInsUserId: [ 'instagram' ], followingIns: [ 'instagram' ], unfollowingIns: [ 'instagram' ], gettingTwitterUserId: [ 'twitter' ], followingTwitterUser: [ 'twitter' ], unfollowingTwitterUser: [ 'twitter' ], joiningReddit: [ 'reddit', 'subreddit' ], leavingReddit: [ 'reddit', 'subreddit' ], followingRedditUser: [ 'reddit', 'user' ], unfollowingRedditUser: [ 'reddit', 'user' ], followingYtbChannel: [ 'youtube', 'channel' ], unfollowingYtbChannel: [ 'youtube', 'channel' ], likingYtbVideo: [ 'youtube', 'video' ], unlikingYtbVideo: [ 'youtube', 'video' ], gettingVkId: [ 'vk' ], joiningVkGroup: [ 'vk' ], leavingVkGroup: [ 'vk' ], joiningVkPublic: [ 'vk' ], leavingVkPublic: [ 'vk' ], sendingVkWall: [ 'vk' ], deletingVkWall: [ 'vk' ] }; const urlConfig = typeMap[type]; if (!urlConfig || !text) { return null; } const [platform, subType] = urlConfig; const urlGenerator = urlGenerators[platform]; if (typeof urlGenerator === 'function') { const url = urlGenerator(text, id); const displayText = platform === 'announcement' ? id || '' : text; return createBaseElement(`${I18n(type)}[${generateLink(url, displayText)}]...`); } if (subType && typeof urlGenerator === 'object') { const subGenerator = urlGenerator[subType]; if (typeof subGenerator === 'function') { const displayText = type.includes('RedditUser') ? text.replace('u_', '') : text; return createBaseElement(`${I18n(type)}[${generateLink(subGenerator(text), displayText)}]...`); } } return null; }; const createSpecialElement = (type, text, html, id) => { switch (type) { case 'retweetting': case 'unretweetting': return createBaseElement(`${I18n(type)}${text}...`); case 'visitingLink': return createBaseElement(`${I18n('visitingLink')}[${generateLink(text || '', text || '')}]...`); case 'verifyingInsAuth': case 'text': return createBaseElement(I18n(text || '')); case 'html': return $(text || html || ''); case 'whiteList': return $(`<li><font class="warning">${I18n('skipTask')}[${text}(${id})](${I18n('whiteList')})</font></li>`); case 'globalOptionsSkip': return $(`<li>${I18n('skipTaskOption')}<font class="warning">${text}</font></li>`); default: return createBaseElement(`${I18n('unKnown')}:${type}(${text})...`); } }; const echoLog = ({type: type, text: text, html: html, id: id, before: before}) => { const emptyStatus = { success: () => emptyStatus, error: () => emptyStatus, warning: () => emptyStatus, info: () => emptyStatus, view: () => emptyStatus, remove: () => emptyStatus }; try { let ele; if (!type && !text && !html) { ele = createBaseElement(''); } else if (text && !type) { ele = createBaseElement(text); } else if (html && !type) { ele = $(html); } else if (type) { const platformElement = createPlatformElement(type, text, id); ele = platformElement || createSpecialElement(type, text, html, id); } else { ele = createBaseElement(''); } if (before) { if (before in ICONS) { const iconKey = before; const svgContent = ICONS[iconKey]; const base64Svg = btoa(svgContent); ele.prepend(`<font class="before-icon" style="background-image: url('data:image/svg+xml;base64,${base64Svg}')"></font>`); } else { ele.prepend(`<font class="before">${before}</font>`); } } else { const base64Svg = btoa(ICONS['[AutoTask]']); ele.prepend(`<font class="before-icon" style="background-image: url('data:image/svg+xml;base64,${base64Svg}')"></font>`); } ele.addClass('card-text'); $('#auto-task-info').append(ele); ele[0]?.scrollIntoView(); const font = ele.find('font.log-status'); const status = { font: font, success(text = 'Success', html = false) { this.font?.attr('class', '').addClass('success'); html ? this.font?.html(text) : this.font?.text(text); return this; }, error(text = 'Error', html = false) { this.font?.attr('class', '').addClass('error'); html ? this.font?.html(text) : this.font?.text(text); return this; }, warning(text = 'Warning', html = false) { this.font?.attr('class', '').addClass('warning'); html ? this.font?.html(text) : this.font?.text(text); return this; }, info(text = 'Info', html = false) { this.font?.attr('class', '').addClass('info'); html ? this.font?.html(text) : this.font?.text(text); return this; }, view() { this.font?.[0].scrollIntoView(); return this; }, remove() { this.font?.parent().remove(); return this; } }; return status; } catch (error) { throwError(error, 'echoLog'); return emptyStatus; } }; const unique = array => { try { return Array.from(new Set(array)); } catch (error) { throwError(error, 'unique'); return []; } }; const delay = (time = 1e3) => new Promise((resolve => setTimeout((() => resolve(true)), time))); const getRedirectLink = async (link, redirectOnce = false) => { try { if (!link) { return null; } const redirectLinksCache = GM_getValue('redirectLinks') || {}; const cachedLink = redirectLinksCache[link]; if (cachedLink) { debug('使用缓存的重定向链接', { original: link, cached: cachedLink }); return cachedLink; } const {data: data} = await httpRequest({ url: link, method: 'GET', redirect: redirectOnce ? 'manual' : 'follow' }); if (data?.finalUrl) { redirectLinksCache[link] = data.finalUrl; GM_setValue('redirectLinks', redirectLinksCache); debug('获取新的重定向链接', { original: link, final: data.finalUrl }); return data.finalUrl; } debug('未找到重定向链接', { link: link }); return null; } catch (error) { throwError(error, 'getRedirectLink'); return null; } }; const visitLink = async (link, options) => { try { debug('开始访问链接', { link: link, options: options }); const logStatus = echoLog({ type: 'visitLink', text: link }); const {result: result, statusText: statusText, status: status} = await httpRequest({ url: link, method: 'GET', ...options }); if (result === 'Success') { debug('链接访问成功', { link: link }); logStatus.success(); return true; } debug('链接访问失败', { link: link, result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } catch (error) { throwError(error, 'visitLink'); return false; } }; const getUrlQuery = url => { try { debug('开始解析URL查询参数', { url: url || window.location.href }); const searchParams = url ? new URL(url, window.location.origin).searchParams : new URLSearchParams(window.location.search); const query = {}; for (const [key, value] of searchParams.entries()) { query[key] = value; } debug('URL查询参数解析结果', query); return query; } catch (error) { throwError(error, 'getUrlQuery'); return {}; } }; const stringToColour = str => { try { let hash = 0; for (let i = 0; i < str.length; i++) { hash = str.charCodeAt(i) + ((hash << 5) - hash); } const rgb = Array.from({ length: 3 }, ((_, i) => { const value = hash >> i * 8 & 255; return value.toString(16).padStart(2, '0'); })); const color = `#${rgb.join('')}`; return color; } catch (error) { throwError(error, 'stringToColour'); return '#ffffff'; } }; const getAllLocalStorageAsObjects = localStorage => { try { debug('开始将所有LocalStorage转换为对象'); const result = {}; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (!key) { continue; } const value = localStorage.getItem(key); try { result[key] = JSON.parse(value); } catch (error) { result[key] = value; console.error(error); } } debug('所有LocalStorage转换为对象完成', result); return result; } catch (error) { debug('将所有LocalStorage转换为对象失败', { error: error }); throwError(error, 'getAllLocalStorageAsObjects'); return {}; } }; class Social { tasks; getRealParams(name, links, doTask, link2param) { try { debug('开始获取实际参数', { name: name, linksCount: links.length, doTask: doTask }); let realParams = []; if (links.length > 0) { debug('处理链接参数'); const convertedLinks = links.map((link => link2param(link))).filter((link => link !== undefined)); debug('链接参数处理结果', { convertedLinksCount: convertedLinks.length }); realParams = [ ...realParams, ...convertedLinks ]; } if (!doTask && this.tasks[name]?.length) { debug('处理任务参数', { taskCount: this.tasks[name].length }); realParams = [ ...realParams, ...this.tasks[name] ]; } const uniqueParams = unique(realParams); debug('参数处理完成', { originalCount: realParams.length, uniqueCount: uniqueParams.length }); return uniqueParams; } catch (error) { debug('获取实际参数时发生错误', { error: error }); throwError(error, 'Social.getRealParams'); return []; } } } class Discord extends Social { tasks; whiteList; #auth=GM_getValue('discordAuth') || {}; #cache=GM_getValue('discordCache') || {}; #xContextPropertiesCache=GM_getValue('discordXContextPropertiesCache') || {}; #initialized=false; constructor() { super(); const defaultTasksTemplate = { servers: [] }; this.tasks = defaultTasksTemplate; this.whiteList = { ...defaultTasksTemplate, ...GM_getValue('whiteList')?.discord || {} }; } async init(action) { try { debug('开始初始化Discord模块', { action: action }); if (!GM_getValue('dontRemindDiscordAgain')) { debug('显示Discord重要提示对话框'); const result = await Swal.fire({ title: I18n('discordImportantNotice'), text: I18n('discordImportantNoticeText'), showCancelButton: true, showDenyButton: true, confirmButtonText: I18n('continueDiscordTask'), cancelButtonText: I18n('skipDiscordTask'), denyButtonText: I18n('continueAndDontRemindAgain') }).then((({isConfirmed: isConfirmed, isDenied: isDenied}) => { if (isConfirmed) { return true; } if (isDenied) { GM_setValue('dontRemindDiscordAgain', true); return true; } return false; })); if (!result) { this.#initialized = false; return 'skip'; } } if (action === 'do' && !globalOptions.doTask.discord.servers || action === 'undo' && !globalOptions.undoTask.discord.servers) { this.#initialized = false; debug('检测到用户已禁用Discord任务,跳过初始化'); return 'skip'; } if (this.#initialized) { debug('Discord模块已初始化'); return true; } if (!this.#auth.auth || !this.#auth.xSuperProperties) { debug('未找到Discord授权信息,尝试更新授权'); if (await this.#updateAuth()) { this.#initialized = true; return true; } return false; } const isVerified = await this.#verifyAuth(); if (isVerified) { debug('Discord授权验证成功'); echoLog({ before: '[Discord]' }).success(I18n('initSuccess', 'Discord')); this.#initialized = true; return true; } GM_setValue('discordAuth', { auth: null }); debug('Discord授权验证失败,尝试重新获取授权'); if (await this.#updateAuth()) { echoLog({ before: '[Discord]' }).success(I18n('initSuccess', 'Discord')); this.#initialized = true; return true; } echoLog({ before: '[Discord]' }).error(I18n('initFailed', 'Discord')); return false; } catch (error) { throwError(error, 'Discord.init'); return false; } } async #verifyAuth() { try { debug('开始验证Discord授权'); const logStatus = echoLog({ text: I18n('verifyingAuth', 'Discord'), before: '[Discord]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://discord.com/api/v6/users/@me', method: 'HEAD', headers: { authorization: this.#auth.auth } }); if (result !== 'Success') { debug('Discord授权验证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('Discord授权验证状态码错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('Discord授权验证成功'); logStatus.success(); return true; } catch (error) { throwError(error, 'Discord.verifyAuth'); return false; } } async #updateAuth() { try { debug('开始更新Discord授权'); const logStatus = echoLog({ text: I18n('updatingAuth', 'Discord'), before: '[Discord]' }); return await new Promise((resolve => { const newTab = GM_openInTab('https://discord.com/channels/@me', { active: true, insert: true, setParent: true }); newTab.name = 'ATv4_discordAuth'; newTab.onclose = async () => { const {auth: auth, xSuperProperties: xSuperProperties} = GM_getValue('discordAuth'); if (auth && xSuperProperties) { debug('成功获取新的Discord授权'); this.#auth = { auth: auth, xSuperProperties: xSuperProperties }; logStatus.success(); resolve(await this.#verifyAuth()); } else { debug('获取Discord授权失败'); logStatus.error('Error: Update discord auth failed!'); resolve(false); } }; })); } catch (error) { throwError(error, 'Discord.updateAuth'); return false; } } async #joinServer(inviteId) { try { debug('开始加入Discord服务器', { inviteId: inviteId }); const logStatus = echoLog({ type: 'joiningDiscordServer', text: inviteId, before: '[Discord]' }); const xContextProperties = await this.#getXContextProperties(inviteId); if (!xContextProperties) { debug('获取加群参数失败,无法加入服务器', { inviteId: inviteId }); logStatus.error('Error: Failed to get xContextProperties'); return false; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://discord.com/api/v9/invites/${inviteId}`, method: 'POST', dataType: 'json', headers: { 'content-type': 'application/json', authorization: this.#auth.auth, origin: 'https://discord.com', referer: `https://discord.com/invite/${inviteId}`, 'x-super-properties': this.#auth.xSuperProperties, 'x-context-properties': xContextProperties }, data: '{"session_id":null}' }); if (result !== 'Success' || data?.status !== 200) { debug('加入Discord服务器失败', { result: result, statusText: statusText, status: status }); if (status === 400) { debug('加入Discord服务器失败,状态码为400,需完成人机验证'); logStatus.error(I18n('captchaNeeded')); return false; } logStatus.error(`${result}:${statusText}(${status})`); return false; } const guild = data.response?.guild?.id; if (!guild) { debug('获取服务器ID失败'); logStatus.error('Error: Failed to get guild ID'); return false; } debug('成功加入Discord服务器', { guild: guild }); logStatus.success(); this.#setCache(inviteId, guild); this.tasks.servers = unique([ ...this.tasks.servers, inviteId ]); return true; } catch (error) { throwError(error, 'Discord.joinServer'); return false; } } async #leaveServer(inviteId) { try { debug('开始退出Discord服务器', { inviteId: inviteId }); if (this.whiteList.servers.includes(inviteId)) { debug('服务器在白名单中,跳过退出操作', { inviteId: inviteId }); echoLog({ type: 'whiteList', text: 'Discord.leaveServer', id: inviteId, before: '[Discord]' }); return true; } const guild = await this.#getGuild(inviteId); if (!guild) { debug('获取服务器ID失败,无法退出服务器', { inviteId: inviteId }); return false; } const logStatus = echoLog({ type: 'leavingDiscordServer', text: guild, before: '[Discord]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://discord.com/api/v9/users/@me/guilds/${guild}`, method: 'DELETE', headers: { authorization: this.#auth.auth, 'x-super-properties': this.#auth.xSuperProperties } }); if (result !== 'Success' || data?.status !== 204) { debug('退出Discord服务器失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } debug('成功退出Discord服务器', { guild: guild }); logStatus.success(); return true; } catch (error) { throwError(error, 'Discord.leaveServer'); return false; } } async #getXContextProperties(inviteId) { try { debug('开始获取Discord加群参数', { inviteId: inviteId }); const logStatus = echoLog({ type: 'gettingDiscordXContextProperties', text: inviteId, before: '[Discord]' }); const cachedXContextProperties = this.#xContextPropertiesCache[inviteId]; if (cachedXContextProperties) { debug('从缓存中获取到加群参数', { inviteId: inviteId, cachedXContextProperties: cachedXContextProperties }); logStatus.success(); return cachedXContextProperties; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://discord.com/api/v9/invites/${inviteId}?with_counts=true&with_expiration=true&with_permissions=true`, responseType: 'json', method: 'GET', headers: { authorization: this.#auth.auth, 'x-super-properties': this.#auth.xSuperProperties } }); if (result !== 'Success' || data?.status !== 200) { debug('获取加群参数失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } const guild = data.response?.guild?.id; if (!guild) { debug('加群参数中未找到ID', { inviteId: inviteId }); logStatus.error('Error: Failed to get guild ID'); return false; } const xContextProperties = { location: 'Accept Invite Page', location_guild_id: data.response?.guild?.id, location_channel_id: data.response?.channel?.id, location_channel_type: data.response?.channel?.type }; debug('成功获取加群参数', xContextProperties); logStatus.success(); this.#setXContextPropertiesCache(inviteId, window.btoa(JSON.stringify(xContextProperties))); this.#setCache(inviteId, guild); return window.btoa(JSON.stringify(xContextProperties)); } catch (error) { throwError(error, 'Discord.getXContextProperties'); return false; } } async #getGuild(inviteId) { try { debug('开始获取Discord服务器ID', { inviteId: inviteId }); const logStatus = echoLog({ type: 'gettingDiscordGuild', text: inviteId, before: '[Discord]' }); const cachedGuild = this.#cache[inviteId]; if (cachedGuild) { debug('从缓存中获取到服务器ID', { inviteId: inviteId, cachedGuild: cachedGuild }); logStatus.success(); return cachedGuild; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://discord.com/api/v9/invites/${inviteId}`, responseType: 'json', method: 'GET', headers: { authorization: this.#auth.auth, 'x-super-properties': this.#auth.xSuperProperties } }); if (result !== 'Success' || data?.status !== 200) { debug('获取服务器信息失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } const guild = data.response?.guild?.id; if (!guild) { debug('服务器信息中未找到ID', { inviteId: inviteId }); logStatus.error('Error: Failed to get guild ID'); return false; } debug('成功获取服务器ID', { inviteId: inviteId, guild: guild }); logStatus.success(); this.#setCache(inviteId, guild); return guild; } catch (error) { throwError(error, 'Discord.getGuild'); return false; } } async toggle({doTask: doTask = true, serverLinks: serverLinks = []}) { try { debug('开始处理Discord服务器任务', { doTask: doTask, serverLinksCount: serverLinks.length }); if (!this.#initialized) { debug('Discord模块未初始化'); echoLog({ text: I18n('needInit'), before: '[Discord]' }); return false; } if (doTask && !globalOptions.doTask.discord.servers || !doTask && !globalOptions.undoTask.discord.servers) { debug('根据全局选项跳过Discord服务器任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'discord.servers', before: '[Discord]' }); return true; } const realServers = this.getRealParams('servers', serverLinks, doTask, (link => link.match(/invite\/(.+)/)?.[1])); debug('处理后的服务器列表', { count: realServers.length, servers: realServers }); if (realServers.length === 0) { debug('没有需要处理的服务器'); return true; } const results = []; for (const server of realServers) { results.push(doTask ? this.#joinServer(server) : this.#leaveServer(server)); await delay(1e3); } return await Promise.allSettled(results).then((() => true)); } catch (error) { throwError(error, 'Discord.toggleServers'); return false; } } #setCache(inviteId, guild) { try { debug('设置Discord服务器缓存', { inviteId: inviteId, guild: guild }); this.#cache[inviteId] = guild; GM_setValue('discordCache', this.#cache); debug('Discord服务器缓存设置成功'); } catch (error) { debug('设置Discord服务器缓存失败', { error: error }); throwError(error, 'Discord.setCache'); } } #setXContextPropertiesCache(inviteId, xContextProperties) { try { debug('设置Discord加群参数缓存', { inviteId: inviteId, xContextProperties: xContextProperties }); this.#xContextPropertiesCache[inviteId] = xContextProperties; GM_setValue('discordXContextPropertiesCache', this.#xContextPropertiesCache); } catch (error) { debug('设置Discord加群参数缓存失败', { error: error }); throwError(error, 'Discord.setXContextPropertiesCache'); } } } class Reddit extends Social { tasks; whiteList; #auth; #initialized=false; constructor() { super(); const defaultTasksTemplate = { reddits: [] }; debug('初始化Reddit实例'); this.tasks = defaultTasksTemplate; this.whiteList = { ...defaultTasksTemplate, ...GM_getValue('whiteList')?.reddit || {} }; } async init() { try { debug('开始初始化Reddit模块'); if (this.#initialized) { debug('Reddit模块已初始化'); return true; } const isVerified = await this.#updateAuth(); if (isVerified) { debug('Reddit授权验证成功'); echoLog({ before: '[Reddit]' }).success(I18n('initSuccess', 'Reddit')); this.#initialized = true; return true; } debug('Reddit初始化失败'); echoLog({ before: '[Reddit]' }).error(I18n('initFailed', 'Reddit')); return false; } catch (error) { debug('Reddit初始化发生错误', { error: error }); throwError(error, 'Reddit.init'); return false; } } async #useBeta() { try { debug('开始切换Reddit为新版'); const logStatus = echoLog({ text: I18n('changingRedditVersion'), before: '[Reddit]' }); return await new Promise((resolve => { const newTab = GM_openInTab('https://www.reddit.com/', { active: true, insert: true, setParent: true }); newTab.name = 'ATv4_redditAuth'; newTab.onclose = async () => { debug('新版Reddit标签页已关闭'); logStatus.success(); resolve(await this.#updateAuth(true)); }; })); } catch (error) { debug('切换Reddit版本时发生错误', { error: error }); throwError(error, 'Reddit.useBeta'); return false; } } async #updateAuth(beta = false) { try { debug('开始更新Reddit授权', { beta: beta }); const logStatus = echoLog({ text: I18n('updatingAuth', 'Reddit'), before: '[Reddit]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://www.reddit.com/', method: 'GET', nochche: true, headers: { 'Cache-Control': 'no-cache' } }); if (result !== 'Success') { debug('获取Reddit页面失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.responseText.includes('www.reddit.com/login/')) { debug('需要登录Reddit'); logStatus.error(`Error:${I18n('loginReddit')}`, true); return false; } if (data?.status !== 200) { debug('Reddit页面状态码错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } if (data.responseText.includes('redesign-beta-optin-btn') && !beta) { debug('检测到旧版Reddit,需要切换到新版'); return await this.#useBeta(); } const accessToken = data.responseText.match(/"accessToken":"(.*?)","expires":"(.*?)"/)?.[1]; if (!accessToken) { debug('未找到Reddit访问令牌'); logStatus.error('Error: Parameter "accessToken" not found!'); return false; } debug('成功获取Reddit访问令牌'); this.#auth = { token: accessToken }; logStatus.success(); return true; } catch (error) { debug('更新Reddit授权时发生错误', { error: error }); throwError(error, 'Reddit.updateAuth'); return false; } } async #toggleTask({name: name, doTask: doTask = true}) { try { debug('开始处理Reddit任务', { name: name, doTask: doTask }); if (!doTask && this.whiteList.reddits.includes(name)) { debug('Reddit在白名单中,跳过取消订阅', { name: name }); echoLog({ type: 'whiteList', text: 'Reddit.undoTask', id: name, before: '[Reddit]' }); return true; } let type = doTask ? 'joiningReddit' : 'leavingReddit'; if (/^u_/.test(name)) { type = doTask ? 'followingRedditUser' : 'unfollowingRedditUser'; } debug('任务类型', { type: type, name: name }); const logStatus = echoLog({ type: type, text: name, before: '[Reddit]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://oauth.reddit.com/api/subscribe?redditWebClient=desktop2x&app=desktop2x-client-production&raw_json=1&gilding_detail=1', method: 'POST', headers: { authorization: `Bearer ${this.#auth.token}`, 'content-type': 'application/x-www-form-urlencoded' }, data: $.param({ action: doTask ? 'sub' : 'unsub', sr_name: name, api_type: 'json' }) }); if (result !== 'Success') { debug('Reddit任务请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('Reddit任务状态码错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('Reddit任务处理成功', { name: name, doTask: doTask }); logStatus.success(); if (doTask) { this.tasks.reddits = unique([ ...this.tasks.reddits, name ]); } return true; } catch (error) { debug('处理Reddit任务时发生错误', { error: error }); throwError(error, 'Reddit.toggleTask'); return false; } } async toggle({doTask: doTask = true, redditLinks: redditLinks = []}) { try { debug('开始处理Reddit链接任务', { doTask: doTask, redditLinksCount: redditLinks.length }); if (!this.#initialized) { debug('Reddit模块未初始化'); echoLog({ text: I18n('needInit'), before: '[Reddit]' }); return false; } if (doTask && !globalOptions.doTask.reddit.reddits || !doTask && !globalOptions.undoTask.reddit.reddits) { debug('根据全局选项跳过Reddit任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'reddit.reddits', before: '[Reddit]' }); return true; } const realReddits = this.getRealParams('reddits', redditLinks, doTask, (link => { const name = link.match(/https?:\/\/www\.reddit\.com\/r\/([^/]*)/)?.[1]; const userName = link.match(/https?:\/\/www\.reddit\.com\/user\/([^/]*)/)?.[1]; if (userName) { return name || userName; } return name; })); debug('处理后的Reddit列表', { count: realReddits.length, reddits: realReddits }); if (realReddits.length === 0) { debug('没有需要处理的Reddit链接'); return true; } const prom = []; for (const name of realReddits) { prom.push(this.#toggleTask({ name: name, doTask: doTask })); await delay(1e3); } return await Promise.all(prom).then((() => true)); } catch (error) { debug('处理Reddit链接任务时发生错误', { error: error }); throwError(error, 'Reddit.toggle'); return false; } } } class Twitch extends Social { tasks; whiteList; #auth=GM_getValue('twitchAuth') || {}; #cache=GM_getValue('twitchCache') || {}; #initialized=false; #integrityToken; constructor() { super(); const defaultTasksTemplate = { channels: [] }; debug('初始化Twitch实例'); this.tasks = defaultTasksTemplate; this.whiteList = { ...defaultTasksTemplate, ...GM_getValue('whiteList')?.twitch || {} }; } async init() { try { debug('开始初始化Twitch模块'); if (this.#initialized) { debug('Twitch模块已初始化'); return true; } if (!this.#auth.authToken || !this.#auth.clientId || !this.#auth.clientVersion || !this.#auth.deviceId || !this.#auth.clientSessionId) { if (await this.#updateAuth()) { this.#initialized = true; return true; } return false; } const isVerified = await this.#verifyAuth(true); if (isVerified) { debug('Twitch授权验证成功'); echoLog({ before: '[Twitch]' }).success(I18n('initSuccess', 'Twitch')); this.#initialized = true; return true; } GM_setValue('twitchAuth', null); if (await this.#updateAuth()) { debug('Twitch重新授权成功'); echoLog({ before: '[Twitch]' }).success(I18n('initSuccess', 'Twitch')); this.#initialized = true; return true; } debug('Twitch初始化失败'); echoLog({ before: '[Twitch]' }).error(I18n('initFailed', 'Twitch')); return false; } catch (error) { debug('Twitch初始化发生错误', { error: error }); throwError(error, 'Twitch.init'); return false; } } async #verifyAuth(isFirst) { try { debug('开始验证Twitch授权'); const logStatus = echoLog({ text: I18n('verifyingAuth', 'Twitch'), before: '[Twitch]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://gql.twitch.tv/gql', method: 'POST', dataType: 'json', headers: { Authorization: `OAuth ${this.#auth.authToken}`, 'Client-Id': this.#auth.clientId }, data: '[{"operationName":"FrontPageNew_User","variables":{"limit":1},"extensions":{"persistedQuery":{"version":1,' + '"sha256Hash":"64bd07a2cbaca80699d62636d966cf6395a5d14a1f0a14282067dcb28b13eb11"}}}]' }); if (result !== 'Success') { debug('Twitch授权验证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || !data.response?.[0]?.data?.currentUser) { debug('Twitch授权验证状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } await this.#integrity(isFirst); debug('Twitch授权验证成功'); logStatus.success(); return true; } catch (error) { debug('Twitch授权验证发生错误', { error: error }); throwError(error, 'Twitch.verifyAuth'); return false; } } async #integrity(isFirst = true, ct = '') { try { debug('开始检查Twitch完整性', { isFirst: isFirst, ct: ct }); const logStatus = echoLog({ text: I18n('checkingTwitchIntegrity'), before: '[Twitch]' }); if (isFirst && (!this.#auth.authToken || !this.#auth.clientId || !this.#auth.clientVersion || !this.#auth.deviceId || !this.#auth.clientSessionId)) { return await this.#updateAuth(false); } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://gql.twitch.tv/integrity', method: 'POST', dataType: 'json', anonymous: true, headers: { Origin: 'https://www.twitch.tv', Referer: 'https://www.twitch.tv/', Authorization: `OAuth ${this.#auth.authToken}`, 'Client-Id': this.#auth.clientId, 'Client-Version': this.#auth.clientVersion, 'X-Device-Id': this.#auth.deviceId, 'Client-Session-Id': this.#auth.clientSessionId, 'x-kpsdk-ct': ct } }); if (result !== 'Success') { debug('Twitch完整性检查请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (!ct && data?.responseHeaders?.['x-kpsdk-ct']) { debug('需要重新检查Twitch完整性'); return await this.#integrity(isFirst, data.responseHeaders['x-kpsdk-ct']); } if (data?.status !== 200 || !data.response?.token) { debug('Twitch完整性检查状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } this.#integrityToken = data.response.token; debug('Twitch完整性检查成功'); logStatus.success(); return true; } catch (error) { debug('Twitch完整性检查发生错误', { error: error }); throwError(error, 'Twitch.integrity'); return false; } } async #updateAuth(isFirst = true) { try { debug('开始更新Twitch授权', { isFirst: isFirst }); const logStatus = echoLog({ text: I18n('updatingAuth', 'Twitch'), before: '[Twitch]' }); return await new Promise((resolve => { const newTab = GM_openInTab('https://www.twitch.tv/', { active: true, insert: true, setParent: true }); newTab.name = 'ATv4_twitchAuth'; newTab.onclose = async () => { const auth = GM_getValue('twitchAuth'); if (auth) { debug('成功获取新的Twitch授权'); this.#auth = auth; logStatus.success(); resolve(await this.#verifyAuth(isFirst)); } else { debug('获取Twitch授权失败'); logStatus.error('Error: Update twitch auth failed!'); resolve(false); } }; })); } catch (error) { debug('更新Twitch授权时发生错误', { error: error }); throwError(error, 'Twitch.updateAuth'); return false; } } async #toggleChannel({name: name, doTask: doTask = true}) { try { debug('开始处理Twitch频道任务', { name: name, doTask: doTask }); if (!doTask && this.whiteList.channels.includes(name)) { debug('Twitch频道在白名单中,跳过取消关注', { name: name }); echoLog({ type: 'whiteList', text: 'Twitch.unfollowChannel', id: name, before: '[Twitch]' }); return true; } const channelId = await this.#getChannelId(name); if (!channelId) { return false; } const logStatus = echoLog({ type: `${doTask ? '' : 'un'}followingTwitchChannel`, text: name, before: '[Twitch]' }); const followData = `[{"operationName":"FollowButton_FollowUser","variables":{"input":{"disableNotifications":false,"targetID":"${channelId}` + '"}},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"800e7346bdf7e5278a3c1d3f21b2b56e2639928f86815677a7126b093b2fdd08"}}}]'; const unfollowData = `[{"operationName":"FollowButton_UnfollowUser","variables":{"input":{"targetID":"${channelId}"}},` + '"extensions":{"persistedQuery":{"version":1,"sha256Hash":"f7dae976ebf41c755ae2d758546bfd176b4eeb856656098bb40e0a672ca0d880"}}}]'; const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://gql.twitch.tv/gql', method: 'POST', dataType: 'json', anonymous: true, headers: { Origin: 'https://www.twitch.tv', Referer: 'https://www.twitch.tv/', Authorization: `OAuth ${this.#auth.authToken}`, 'Client-Id': this.#auth.clientId, 'Client-Version': this.#auth.clientVersion, 'X-Device-Id': this.#auth.deviceId, 'Client-Session-Id': this.#auth.clientSessionId, 'Client-Integrity': this.#integrityToken }, data: doTask ? followData : unfollowData }); if (result !== 'Success') { debug('Twitch频道操作请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || data.response?.[0] && data.response[0].errors) { debug('Twitch频道操作状态错误', { status: data?.status, statusText: data?.statusText, errors: data?.response?.[0].errors }); logStatus.error(`Error:${data?.response?.[0].errors?.[0]?.message || `${data?.statusText}(${data?.status})`}`); return false; } debug('Twitch频道操作成功', { name: name, doTask: doTask }); logStatus.success(); if (doTask) { this.tasks.channels = unique([ ...this.tasks.channels, name ]); } return true; } catch (error) { debug('处理Twitch频道任务时发生错误', { error: error }); throwError(error, 'Twitch.toggleChannel'); return false; } } async #getChannelId(name) { try { debug('开始获取Twitch频道ID', { name: name }); const logStatus = echoLog({ type: 'gettingTwitchChannelId', text: name, before: '[Twitch]' }); const cachedChannelId = this.#cache[name]; if (cachedChannelId) { debug('从缓存获取到Twitch频道ID', { name: name, id: cachedChannelId }); logStatus.success(); return cachedChannelId; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://gql.twitch.tv/gql', method: 'POST', headers: { Authorization: `OAuth ${this.#auth.authToken}`, 'Client-Id': this.#auth.clientId }, responseType: 'json', data: `[{"operationName":"ActiveWatchParty","variables":{"channelLogin":"${name}"},` + '"extensions":{"persistedQuery":{"version":1,"sha256Hash":"4a8156c97b19e3a36e081cf6d6ddb5dbf9f9b02ae60e4d2ff26ed70aebc80a30"}}}]' }); if (result !== 'Success') { debug('获取Twitch频道ID请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取Twitch频道ID状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const newChannelId = data.response?.[0]?.data?.user?.id; if (!newChannelId) { debug('未找到Twitch频道ID', { name: name }); logStatus.error(`Error:${data?.statusText || 'Unknown'}(${data?.status || 'Unknown'})`); return false; } debug('成功获取Twitch频道ID', { name: name, id: newChannelId }); this.#setCache(name, newChannelId); logStatus.success(); return newChannelId; } catch (error) { debug('获取Twitch频道ID时发生错误', { error: error }); throwError(error, 'Twitch.getChannelId'); return false; } } async toggle({doTask: doTask = true, channelLinks: channelLinks = []}) { try { debug('开始处理Twitch链接任务', { doTask: doTask, channelLinksCount: channelLinks.length }); if (!this.#initialized) { debug('Twitch模块未初始化'); echoLog({ text: I18n('needInit'), before: '[Twitch]' }); return false; } const prom = []; if (doTask && !globalOptions.doTask.twitch.channels || !doTask && !globalOptions.undoTask.twitch.channels) { debug('根据全局选项跳过Twitch任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'twitch.channels', before: '[Twitch]' }); } else { const realChannels = this.getRealParams('channels', channelLinks, doTask, (link => link.match(/https:\/\/(www\.)?twitch\.tv\/(.+)/)?.[2])); debug('处理后的Twitch频道列表', { count: realChannels.length, channels: realChannels }); if (realChannels.length > 0) { for (const channel of realChannels) { prom.push(this.#toggleChannel({ name: channel, doTask: doTask })); await delay(1e3); } } } return Promise.all(prom).then((() => true)); } catch (error) { debug('处理Twitch链接任务时发生错误', { error: error }); throwError(error, 'Twitch.toggle'); return false; } } #setCache(name, id) { try { debug('设置Twitch频道ID缓存', { name: name, id: id }); this.#cache[name] = id; GM_setValue('twitchCache', this.#cache); } catch (error) { debug('设置Twitch频道ID缓存时发生错误', { error: error }); throwError(error, 'Twitch.setCache'); } } } const encodeSha256 = async data => { const encoder = new TextEncoder; const dataBuffer = encoder.encode(data); const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer); return Array.from(new Uint8Array(hashBuffer)); }; const encodeBase64 = data => { let binary = ''; const bytes = new Uint8Array(data); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary).replace(/=/g, ''); }; const decodeBase64 = data => { const binaryString = atob(data); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return Array.from(bytes); }; const generateTransactionId = async (method, path, key, animationKey) => { const DEFAULT_KEYWORD = 'obfiowerehiring'; const ADDITIONAL_RANDOM_NUMBER = 3; const timeNow = Math.floor((Date.now() - 1682924400 * 1e3) / 1e3); const timeNowBytes = [ timeNow & 255, timeNow >> 8 & 255, timeNow >> 16 & 255, timeNow >> 24 & 255 ]; const data = `${method}!${path}!${timeNow}${DEFAULT_KEYWORD}${animationKey}`; const hashBytes = await encodeSha256(data); const keyBytes = decodeBase64(key); const randomNum = Math.floor(Math.random() * 256); const bytesArr = [ ...keyBytes, ...timeNowBytes, ...hashBytes.slice(0, 16), ADDITIONAL_RANDOM_NUMBER ]; const out = new Uint8Array(bytesArr.length + 1); out[0] = randomNum; bytesArr.forEach(((item, index) => { out[index + 1] = item ^ randomNum; })); return encodeBase64(out); }; const url = 'https://raw.githubusercontent.com/fa0311/x-client-transaction-id-pair-dict/refs/heads/main/pair.json'; const getTID = async () => { const res = await fetch(url); const json = await res.json(); return async (method, path) => { const randomPair = json[Math.floor(Math.random() * json.length)]; const {animationKey: animationKey, verification: verification} = randomPair; const tid = await generateTransactionId(method, path, verification, animationKey); return tid; }; }; const parseResponseHeaders = headerStr => { const headers = {}; if (!headerStr) { return headers; } headerStr.split('\r\n').forEach((line => { if (line) { const parts = line.split(':'); const key = parts.shift()?.trim(); const value = parts.join(':').trim(); if (key) { if (key.toLowerCase() === 'set-cookie') { if (headers[key]) { if (Array.isArray(headers[key])) { headers[key].push(value); } else { headers[key] = [ headers[key], value ]; } } else { headers[key] = value; } } else { headers[key] = value; } } } })); return headers; }; const axiosGM = function(config) { const finalConfig = { ...axiosGM.defaults, ...config }; const retries = finalConfig.retry ?? 0; const retryDelay = finalConfig.retryDelay ?? 0; const requestAttempt = attempt => new Promise(((resolve, reject) => { GM_xmlhttpRequest({ method: finalConfig.method ? finalConfig.method.toUpperCase() : 'GET', url: finalConfig.url, headers: finalConfig.headers, data: finalConfig.data, responseType: finalConfig.responseType || 'json', timeout: finalConfig.timeout, fetch: finalConfig.fetch ?? true, onload(response) { const axiosResponse = { data: response.response || response.responseText, status: response.status, statusText: response.statusText, headers: parseResponseHeaders(response.responseHeaders), config: finalConfig, request: response }; resolve(axiosResponse); }, onerror(error) { if (attempt < retries) { setTimeout((() => { requestAttempt(attempt + 1).then(resolve).catch(reject); }), retryDelay); } else { reject(error); } }, ontimeout() { if (attempt < retries) { setTimeout((() => { requestAttempt(attempt + 1).then(resolve).catch(reject); }), retryDelay); } else { reject('Error: timeout'); } } }); })); return requestAttempt(0); }; axiosGM.defaults = {}; axiosGM.get = function(url, config = {}) { return axiosGM({ ...config, url: url, method: 'GET' }); }; axiosGM.post = function(url, data, config = {}) { return axiosGM({ ...config, url: url, data: data, method: 'POST' }); }; axiosGM.head = function(url, config = {}) { return axiosGM({ ...config, url: url, method: 'HEAD' }); }; axiosGM.create = function(instanceDefaults = {}) { const instance = config => { const mergedConfig = { ...axiosGM.defaults, ...instanceDefaults, ...config }; return axiosGM(mergedConfig); }; instance.defaults = { ...axiosGM.defaults, ...instanceDefaults }; instance.get = function(url, config = {}) { return instance({ ...config, url: url, method: 'GET' }); }; instance.post = function(url, data, config = {}) { return instance({ ...config, url: url, data: data, method: 'POST' }); }; instance.head = function(url, config = {}) { return instance({ ...config, url: url, method: 'HEAD' }); }; instance.create = axiosGM.create; return instance; }; const getFwdForSdkUrl = async () => { const rawHtml = await axiosGM({ url: 'https://x.com', method: 'GET' }); return [ ...rawHtml.data.matchAll(/"(loader\.FwdForSdk)":"([^"]+?)"/g) ]; }; const fwdForSdkExpoter = async url => { const {data: data} = await axiosGM.get(url); const regex = /Uint8Array\(\w\)\.set\(\[(.*?)\]\)/; if (!regex.test(data)) { return false; } const json = `[${data.match(regex)?.[1]}]`; const obj = JSON.parse(json); return new Uint8Array(obj); }; const getWasmData = async () => { const fwdForSdkUrl = await getFwdForSdkUrl(); for (const url of fwdForSdkUrl) { const sdkData = await fwdForSdkExpoter(`https://abs.twimg.com/responsive-web/client-web/${url[1]}.${url[2]}a.js`); if (sdkData) { return sdkData; } } return false; }; const getFwdForSdk = async () => { debug('开始获取 XFwdForSdk'); const wasmData = await getWasmData(); debug('获取 wasmData 成功', { wasmData: wasmData }); const go = new Go; const wasmModule = await WebAssembly.instantiate(wasmData, { ...go.importObject, env: { ...go.importObject.env, memory: new WebAssembly.Memory({ initial: 10 }), table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }) } }); debug('初始化 wasmModule 成功'); go.run(wasmModule.instance); debug('运行 wasmModule 成功'); const {str: str, expiryTimeMillis: expiryTimeMillis} = await globalThis.getForwardedForStr(); debug('获取 XFwdForSdk 成功', { str: str, expiryTimeMillis: expiryTimeMillis }); return { str: str, expiryTimeMillis: parseInt(expiryTimeMillis, 10) }; }; const generateSecCHUA = () => { if (navigator.userAgentData && navigator.userAgentData.brands) { return navigator.userAgentData.brands.map((brand => `"${brand.brand}";v="${brand.version}"`)).join(', '); } return '"Google Chrome";v="125", "Chromium";v="125", "Not-A.Brand";v="99"'; }; class Twitter extends Social { tasks; whiteList; #verifyId=globalOptions.other.twitterVerifyId; #auth=GM_getValue('twitterAuth') || {}; #cache=GM_getValue('twitterCache') || {}; #initialized=false; #getTID; #FwdForSdk; #headers={}; constructor() { super(); const defaultTasksTemplate = { users: [], retweets: [], likes: [] }; debug('初始化Twitter实例'); this.tasks = defaultTasksTemplate; this.whiteList = { ...defaultTasksTemplate, ...GM_getValue('whiteList')?.twitter || {} }; this.#headers = { authorization: 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA', 'X-Twitter-Auth-Type': 'OAuth2Session', 'X-Twitter-Active-User': 'yes', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'sec-ch-ua-platform': '"Windows"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua': generateSecCHUA() }; } async init() { try { debug('开始初始化Twitter模块'); if (this.#initialized) { debug('Twitter模块已初始化'); return true; } debug('获取Twitter授权信息'); if (!await this.#updateAuth()) { return false; } debug('创建Twitter会话和SDK'); this.#getTID = await getTID(); this.#FwdForSdk = await getFwdForSdk(); const isVerified = await this.#verifyAuth(); if (isVerified) { debug('Twitter授权验证成功'); echoLog({ before: '[Twitter]' }).success(I18n('initSuccess', 'Twitter')); this.#initialized = true; return true; } debug('Twitter授权失效,尝试重新获取'); GM_setValue('twitterAuth', null); if (await this.#updateAuth()) { debug('Twitter重新授权成功'); echoLog({ before: '[Twitter]' }).success(I18n('initSuccess', 'Twitter')); this.#initialized = true; return true; } debug('Twitter初始化失败'); echoLog({ before: '[Twitter]' }).error(I18n('initFailed', 'Twitter')); return false; } catch (error) { debug('Twitter初始化发生错误', { error: error }); throwError(error, 'Twitter.init'); return false; } } async #verifyAuth() { try { debug('开始验证Twitter授权'); return await this.#toggleUser({ name: 'verify', doTask: true, verify: true }); } catch (error) { debug('Twitter授权验证发生错误', { error: error }); throwError(error, 'Twitter.verifyAuth'); return false; } } async #updateAuth() { try { debug('开始更新Twitter授权'); const logStatus = echoLog({ text: I18n('updatingAuth', 'Twitter'), before: '[Twitter]' }); return await new Promise((resolve => { GM_cookie.list({ url: 'https://x.com/settings/account' }, (async (cookies, error) => { if (!error) { const ct0 = cookies.find((cookie => cookie.name === 'ct0'))?.value; const isLogin = cookies.find((cookie => cookie.name === 'twid'))?.value; if (isLogin && ct0) { debug('成功获取Twitter授权信息'); GM_setValue('twitterAuth', { ct0: ct0 }); this.#auth = { ct0: ct0 }; this.#headers['x-csrf-token'] = ct0; this.#headers['x-twitter-client-language'] = cookies.find((cookie => cookie.name === 'lang'))?.value || 'en'; logStatus.success(); resolve(true); } else { debug('获取Twitter授权失败:未登录'); logStatus.error(I18n('needLogin')); resolve(false); } } else { debug('获取Twitter授权失败', { error: error }); logStatus.error('Error: Update twitter auth failed!'); resolve(false); } })); })); } catch (error) { debug('更新Twitter授权时发生错误', { error: error }); throwError(error, 'Twitter.updateToken'); return false; } } async #toggleUser({name: name, doTask: doTask = true, verify: verify = false, retry: retry = false}) { try { debug('开始处理Twitter用户任务', { name: name, doTask: doTask, verify: verify, retry: retry }); if (!doTask && !verify && this.whiteList.users.includes(name)) { debug('Twitter用户在白名单中,跳过取消关注', { name: name }); echoLog({ type: 'whiteList', text: 'Twitter.unfollowUser', id: name, before: '[Twitter]' }); return true; } const userId = verify ? this.#verifyId : await this.userName2id(name); if (!userId) { return false; } const logStatus = verify ? echoLog({ text: I18n('verifyingAuth', 'Twitter'), before: '[Twitter]' }) : echoLog({ type: `${doTask ? '' : 'un'}followingTwitterUser`, text: name, before: '[Twitter]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://x.com/i/api/1.1/friendships/${doTask ? 'create' : 'destroy'}.json`, method: 'POST', headers: { ...this.#headers, 'Content-Type': 'application/x-www-form-urlencoded', 'x-client-transaction-id': await this.#getTID('POST', `/i/api/1.1/friendships/${doTask ? 'create' : 'destroy'}.json`), 'x-xp-forwarded-for': this.#FwdForSdk.str }, responseType: 'json', data: $.param({ include_profile_interstitial_type: 1, include_blocking: 1, include_blocked_by: 1, include_followed_by: 1, include_want_retweets: 1, include_mute_edge: 1, include_can_dm: 1, include_can_media_tag: 1, skip_status: 1, id: userId }) }); if (result !== 'Success') { debug('Twitter用户操作请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200) { debug('Twitter用户操作成功', { name: name, doTask: doTask }); logStatus.success(); if (doTask && !verify) { this.tasks.users = unique([ ...this.tasks.users, name ]); } return true; } if (verify && data?.status === 403) { if (data.response?.errors?.[0]?.code === 158) { debug('Twitter授权验证成功(已关注)'); logStatus.success(); return true; } if (data.response?.errors?.[0]?.code === 353 && !retry && data.responseHeaders?.['set-cookie']) { const newCt0 = data.responseHeaders['set-cookie']?.find((cookie => cookie.includes('ct0=')))?.split(';')?.at(0)?.split('=')?.at(-1); if (newCt0) { debug('获取到新的Twitter授权Token,重试操作'); this.#auth.ct0 = newCt0; GM_setValue('twitterAuth', this.#auth); logStatus.warning(I18n('retry')); return this.#toggleUser({ name: name, doTask: doTask, verify: verify, retry: true }); } } } debug('Twitter用户操作失败', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } catch (error) { debug('处理Twitter用户任务时发生错误', { error: error }); throwError(error, 'Twitter.toggleUser'); return false; } } async userName2id(name) { try { debug('开始获取Twitter用户ID', { name: name }); const logStatus = echoLog({ type: 'gettingTwitterUserId', text: name, before: '[Twitter]' }); const cachedUserId = this.#cache[name]; if (cachedUserId) { debug('从缓存获取到Twitter用户ID', { name: name, id: cachedUserId }); logStatus.success(); return cachedUserId; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://x.com/i/api/graphql/jUKA--0QkqGIFhmfRZdWrQ/UserByScreenName' + `?variables=%7B%22screen_name%22%3A%22${name}%22%7D&features=%7B%22responsive_web_grok_bio_auto_translation_is_enabled%22%3Afalse%2C%22hidden_profile_subscriptions_enabled%22%3Atrue%2C%22payments_enabled%22%3Afalse%2C%22profile_label_improvements_pcf_label_in_post_enabled%22%3Atrue%2C%22rweb_tipjar_consumption_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_is_identity_verified_enabled%22%3Atrue%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22responsive_web_twitter_article_notes_tab_enabled%22%3Atrue%2C%22subscriptions_feature_can_gift_premium%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D&fieldToggles=%7B%22withAuxiliaryUserLabels%22%3Atrue%7D`, method: 'GET', headers: { ...this.#headers, 'content-type': 'application/json', referer: `https://x.com/${name}`, 'x-client-transaction-id': await this.#getTID('GET', '/i/api/graphql/jUKA--0QkqGIFhmfRZdWrQ/UserByScreenName' + `?variables=%7B%22screen_name%22%3A%22${name}%22%7D&features=%7B%22responsive_web_grok_bio_auto_translation_is_enabled%22%3Afalse%2C%22hidden_profile_subscriptions_enabled%22%3Atrue%2C%22payments_enabled%22%3Afalse%2C%22profile_label_improvements_pcf_label_in_post_enabled%22%3Atrue%2C%22rweb_tipjar_consumption_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_is_identity_verified_enabled%22%3Atrue%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22responsive_web_twitter_article_notes_tab_enabled%22%3Atrue%2C%22subscriptions_feature_can_gift_premium%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D&fieldToggles=%7B%22withAuxiliaryUserLabels%22%3Atrue%7D`), 'x-xp-forwarded-for': this.#FwdForSdk.str }, responseType: 'json' }); if (result !== 'Success') { debug('获取Twitter用户ID请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取Twitter用户ID状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } let response = data.response || (typeof data.responseText === 'object' ? data.responseText : null); if (!response) { try { response = JSON.parse(data.responseText); } catch (error) { response = null; } } const fetchedUserId = response?.data?.user?.result?.rest_id; if (!fetchedUserId) { debug('未找到Twitter用户ID', { name: name }); logStatus.error(`Error:${data.statusText}(${data.status})`); return false; } debug('成功获取Twitter用户ID', { name: name, id: fetchedUserId }); this.#setCache(name, fetchedUserId); logStatus.success(); return fetchedUserId; } catch (error) { debug('获取Twitter用户ID时发生错误', { error: error }); throwError(error, 'Twitter.getUserId'); return false; } } async #toggleRetweet({retweetId: retweetId, doTask: doTask = true, retry: retry = false}) { try { debug('开始处理Twitter转推任务', { retweetId: retweetId, doTask: doTask, retry: retry }); if (!doTask && this.whiteList.retweets.includes(retweetId)) { debug('Twitter转推在白名单中,跳过取消', { retweetId: retweetId }); echoLog({ type: 'whiteList', text: 'Twitter.unretweet', id: retweetId, before: '[Twitter]' }); return true; } const logStatus = echoLog({ type: `${doTask ? '' : 'un'}retweetting`, text: retweetId, before: '[Twitter]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://x.com/i/api/graphql/${doTask ? 'ojPdsZsimiJrUGLR1sjUtA/CreateRetweet' : 'iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet'}`, method: 'POST', headers: { ...this.#headers, 'Content-Type': 'application/json', origin: 'https://x.com', referer: 'https://x.com/home', 'x-client-transaction-id': await this.#getTID('POST', `/i/api/graphql/${doTask ? 'ojPdsZsimiJrUGLR1sjUtA/CreateRetweet' : 'iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet'}`), 'x-xp-forwarded-for': this.#FwdForSdk.str }, data: `{"variables":{"${doTask ? '' : 'source_'}tweet_id":"${retweetId}","dark_request":false},"queryId":"${doTask ? 'ojPdsZsimiJrUGLR1sjUtA' : 'iQtK4dl5hBmXewYZuEOKVw'}"}`, responseType: 'json' }); if (result !== 'Success') { debug('Twitter转推操作请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 403 && data.response?.errors?.[0]?.code === 353 && !retry && data.responseHeaders?.['set-cookie']) { const newCt0 = data.responseHeaders['set-cookie']?.find((cookie => cookie.includes('ct0=')))?.split(';')?.at(0)?.split('=')?.at(-1); if (newCt0) { debug('获取到新的Twitter授权Token,重试操作'); this.#auth.ct0 = newCt0; GM_setValue('twitterAuth', this.#auth); logStatus.warning(I18n('retry')); return this.#toggleRetweet({ retweetId: retweetId, doTask: doTask, retry: true }); } } if (data?.status !== 200 && !(data?.status === 403 && data.response?.errors?.[0]?.code === 327)) { debug('Twitter转推操作状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } if (data.response?.errors && data.response?.errors?.[0]?.code !== 327) { debug('Twitter转推操作出错', { error: data.response?.errors?.[0]?.message }); logStatus.error(`Error:${data.response?.errors?.[0]?.message}`); return false; } debug('Twitter转推操作成功', { retweetId: retweetId, doTask: doTask }); logStatus.success(); if (doTask) { this.tasks.retweets = unique([ ...this.tasks.retweets, retweetId ]); } return true; } catch (error) { debug('处理Twitter转推任务时发生错误', { error: error }); throwError(error, 'Twitter.toggleRetweet'); return false; } } async toggle({doTask: doTask = true, userLinks: userLinks = [], retweetLinks: retweetLinks = []}) { try { debug('开始处理Twitter链接任务', { doTask: doTask, userLinksCount: userLinks.length, retweetLinksCount: retweetLinks.length }); if (!this.#initialized) { debug('Twitter模块未初始化'); echoLog({ text: I18n('needInit'), before: '[Twitter]' }); return false; } if (doTask && !globalOptions.doTask.twitter.users || !doTask && !globalOptions.undoTask.twitter.users) { debug('根据全局选项跳过Twitter用户任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'twitter.users', before: '[Twitter]' }); } else { const realUsers = this.getRealParams('users', userLinks, doTask, (link => link.match(/https:\/\/x\.com\/([^/]+)/)?.[1] || link.match(/https:\/\/twitter\.com\/([^/]+)/)?.[1])); debug('处理后的Twitter用户列表', { count: realUsers.length, users: realUsers }); if (realUsers.length > 0) { for (const user of realUsers) { if (Date.now() > this.#FwdForSdk.expiryTimeMillis) { debug('Twitter SDK过期,重新获取', { expiryTimeMillis: this.#FwdForSdk.expiryTimeMillis }); this.#FwdForSdk = await getFwdForSdk(); } await this.#toggleUser({ name: user, doTask: doTask }); await delay(1e3); } } } if (doTask && !globalOptions.doTask.twitter.retweets || !doTask && !globalOptions.undoTask.twitter.retweets) { debug('根据全局选项跳过Twitter转推任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'twitter.retweets', before: '[Twitter]' }); } else { const realRetweets = this.getRealParams('retweets', retweetLinks, doTask, (link => link.match(/https:\/\/x\.com\/.*?\/status\/([\d]+)/)?.[1] || link.match(/https:\/\/twitter\.com\/.*?\/status\/([\d]+)/)?.[1])); debug('处理后的Twitter转推列表', { count: realRetweets.length, retweets: realRetweets }); if (realRetweets.length > 0) { for (const retweet of realRetweets) { if (Date.now() > this.#FwdForSdk.expiryTimeMillis) { debug('Twitter SDK过期,重新获取'); this.#FwdForSdk = await getFwdForSdk(); } await this.#toggleRetweet({ retweetId: retweet, doTask: doTask }); await delay(1e3); } } } return true; } catch (error) { debug('处理Twitter链接任务时发生错误', { error: error }); throwError(error, 'Twitter.toggle'); return false; } } #setCache(name, id) { try { debug('设置Twitter用户ID缓存', { name: name, id: id }); this.#cache[name] = id; GM_setValue('twitterCache', this.#cache); } catch (error) { debug('设置Twitter用户ID缓存时发生错误', { error: error }); throwError(error, 'Twitter.setCache'); } } } class Vk extends Social { tasks; whiteList; #username=''; #cache=GM_getValue('vkCache') || {}; #initialized=false; constructor() { super(); const defaultTasksTemplate = { names: [] }; debug('初始化Vk实例'); this.tasks = defaultTasksTemplate; this.whiteList = { ...defaultTasksTemplate, ...GM_getValue('whiteList')?.vk || {} }; } async init() { try { debug('开始初始化Vk模块'); if (this.#initialized) { debug('Vk模块已初始化'); return true; } const isVerified = await this.#verifyAuth(); if (isVerified) { debug('Vk授权验证成功'); echoLog({ before: '[Vk]' }).success(I18n('initSuccess', 'Vk')); this.#initialized = true; return true; } debug('Vk初始化失败'); echoLog({ before: '[Vk]' }).error(I18n('initFailed', 'Vk')); return false; } catch (error) { debug('Vk初始化发生错误', { error: error }); throwError(error, 'Vk.init'); return false; } } async #verifyAuth() { try { debug('开始验证Vk授权'); const logStatus = echoLog({ text: I18n('verifyAuth', 'Vk'), before: '[Vk]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://vk.com/im', method: 'GET' }); if (result !== 'Success') { debug('Vk授权验证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.finalUrl.includes('vk.com/login')) { debug('Vk授权验证失败:需要登录'); logStatus.error(`Error:${I18n('loginVk')}`, true); return false; } if (data?.status !== 200) { debug('Vk授权验证状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } this.#username = data.responseText.match(/TopNavBtn__profileLink" href="\/(.*?)"/)?.[1] || ''; debug('Vk授权验证成功'); logStatus.success(); return true; } catch (error) { debug('Vk授权验证发生错误', { error: error }); throwError(error, 'Vk.verifyAuth'); return false; } } async #toggleGroup(name, dataParam, doTask = true) { try { debug('开始处理Vk群组任务', { name: name, doTask: doTask }); const logStatus = echoLog({ type: doTask ? 'joiningVkGroup' : 'leavingVkGroup', text: name, before: '[Vk]' }); if (dataParam.groupAct === 'enter' && !doTask || dataParam.groupAct === 'leave' && doTask) { debug('Vk群组操作已完成,跳过', { name: name, doTask: doTask }); logStatus.success(); return true; } const reqData = { act: doTask ? 'enter' : 'leave', al: 1, gid: dataParam.groupId, hash: dataParam.groupHash }; if (doTask) { reqData.context = '_'; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://vk.com/al_groups.php', method: 'POST', headers: { origin: 'https://vk.com', referer: `https://vk.com/${name}`, 'content-type': 'application/x-www-form-urlencoded' }, data: $.param(reqData) }); if (result !== 'Success') { debug('Vk群组操作请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('Vk群组操作状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('Vk群组操作成功', { name: name, doTask: doTask }); logStatus.success(); if (doTask) { this.tasks.names = unique([ ...this.tasks.names, name ]); } return true; } catch (error) { debug('处理Vk群组任务时发生错误', { error: error }); throwError(error, 'Vk.toggleGroup'); return false; } } async #togglePublic(name, dataParam, doTask = true) { try { debug('开始处理Vk公共页面任务', { name: name, doTask: doTask }); const logStatus = echoLog({ type: doTask ? 'joiningVkPublic' : 'leavingVkPublic', text: name, before: '[Vk]' }); if (dataParam.publicJoined && doTask || !dataParam.publicJoined && !doTask) { debug('Vk公共页面操作已完成,跳过', { name: name, doTask: doTask }); logStatus.success(); return true; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://vk.com/al_public.php', method: 'POST', headers: { origin: 'https://vk.com', referer: `https://vk.com/${name}`, 'content-type': 'application/x-www-form-urlencoded' }, data: $.param({ act: doTask ? 'a_enter' : 'a_leave', al: 1, pid: dataParam.publicPid, hash: dataParam.publicHash }) }); if (result !== 'Success') { debug('Vk公共页面操作请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('Vk公共页面操作状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('Vk公共页面操作成功', { name: name, doTask: doTask }); logStatus.success(); if (doTask) { this.tasks.names = unique([ ...this.tasks.names, name ]); } return true; } catch (error) { debug('处理Vk公共页面任务时发生错误', { error: error }); throwError(error, 'Vk.togglePublic'); return false; } } async #toggleLikeWall(name, dataParam, doTask = true) { try { debug('开始处理Vk点赞任务', { name: name, doTask: doTask }); const logStatus = echoLog({ type: doTask ? 'likingVkPublic' : 'unlikingVkPublic', text: name, before: '[Vk]' }); const postData = { act: 'a_set_reaction', al: 1, event_subtype: 'post_modal', from: 'wall_page', hash: dataParam.hash, object: dataParam.object, track_code: dataParam.trackCode, wall: 2 }; if (doTask) { postData.reaction_id = 0; } const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({ url: 'https://vk.com/like.php?act=a_set_reaction', method: 'POST', headers: { origin: 'https://vk.com', referer: `https://vk.com/${name}`, 'content-type': 'application/x-www-form-urlencoded' }, data: $.param(postData) }); if (resultR !== 'Success') { debug('Vk点赞操作请求失败', { result: resultR, statusText: statusTextR, status: statusR }); logStatus.error(`${resultR}:${statusTextR}(${statusR})`); return false; } if (dataR?.status !== 200) { debug('Vk点赞操作状态错误', { status: dataR?.status, statusText: dataR?.statusText }); logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`); return false; } if (dataR.response?.payload?.[1]?.[1]?.like_my !== true) { debug('Vk点赞操作验证失败'); logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`); return false; } debug('Vk点赞操作成功', { name: name, doTask: doTask }); logStatus.success(); return true; } catch (error) { debug('处理Vk点赞任务时发生错误', { error: error }); throwError(error, 'Vk.sendWall'); return false; } } async #sendWall(name) { try { debug('开始处理Vk转发任务', { name: name }); const logStatus = echoLog({ type: 'sendingVkWall', text: name, before: '[Vk]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://vk.com/like.php', method: 'POST', headers: { origin: 'https://vk.com', referer: `https://vk.com/${name}`, 'content-type': 'application/x-www-form-urlencoded' }, data: $.param({ act: 'publish_box', al: 1, object: name }) }); if (result !== 'Success') { debug('Vk转发操作请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('Vk转发操作状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const hash = data.responseText.match(/shHash:[\s]*'(.*?)'/)?.[1]; if (!hash) { debug('获取Vk转发hash失败'); logStatus.error('Error: Get "hash" failed'); return false; } const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({ url: 'https://vk.com/like.php', method: 'POST', headers: { origin: 'https://vk.com', referer: `https://vk.com/${name}`, 'content-type': 'application/x-www-form-urlencoded' }, data: $.param({ Message: '', act: 'a_do_publish', al: 1, close_comments: 0, friends_only: 0, from: 'box', hash: hash, list: '', mark_as_ads: 0, mute_notifications: 0, object: name, ret_data: 1, to: 0 }) }); if (resultR !== 'Success') { debug('Vk转发确认请求失败', { result: resultR, statusText: statusTextR, status: statusR }); logStatus.error(`${resultR}:${statusTextR}(${statusR})`); return false; } if (dataR?.status !== 200) { debug('Vk转发确认状态错误', { status: dataR?.status, statusText: dataR?.statusText }); logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`); return false; } const jsonData = JSON.parse(dataR.responseText?.replace('\x3c!--', '') || '{}'); if (jsonData?.payload?.[1]?.[1]?.share_my !== true) { debug('Vk转发确认验证失败'); logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`); return false; } debug('Vk转发操作成功', { name: name }); logStatus.success(); const postId = jsonData?.payload?.[1]?.[1]?.post_id; const ownerId = jsonData?.payload?.[1]?.[1]?.owner_id; if (postId && ownerId) { this.#setCache(name, `${ownerId}_${postId}`); } this.tasks.names = unique([ ...this.tasks.names, name ]); return true; } catch (error) { debug('处理Vk转发任务时发生错误', { error: error }); throwError(error, 'Vk.sendWall'); return false; } } async #deleteWall(name, dataParams) { try { debug('开始处理Vk删除墙任务', { name: name }); const logStatus = echoLog({ type: 'deletingVkWall', text: name, before: '[Vk]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://vk.com/al_wall.php?act=delete', method: 'POST', headers: { origin: 'https://vk.com', referer: `https://vk.com/${this.#username}?w=wall${this.#cache[name]}%2Fall`, 'content-type': 'application/x-www-form-urlencoded' }, data: $.param({ act: 'delete', al: 1, confirm: 0, from: 'wkview', hash: dataParams.wallHash, post: this.#cache[name] }) }); if (result !== 'Success') { debug('Vk删除墙请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('Vk删除墙状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const jsonData = JSON.parse(data.responseText?.replace('\x3c!--', '') || '{}'); if (!jsonData?.payload?.[1]?.[1]) { debug('Vk删除墙验证失败'); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('Vk删除墙操作成功', { name: name }); logStatus.success(); return true; } catch (error) { debug('处理Vk删除墙任务时发生错误', { error: error }); throwError(error, 'Vk.deleteWall'); return false; } } async #getId(name, doTask) { try { debug('开始获取Vk ID', { name: name, doTask: doTask }); let url = `https://vk.com/${name}`; if (/^wall-/.test(name)) { if (doTask) { return { type: 'sendWall' }; } if (!this.#cache[name]) { return { type: 'unSupport' }; } url = `https://vk.com/${this.#username}?w=wall${this.#cache[name]}`; } const logStatus = echoLog({ type: 'gettingVkId', text: name, before: '[Vk]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: url, method: 'GET' }); if (result !== 'Success') { debug('获取Vk ID请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取Vk ID状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const [, groupAct, groupId, , groupHash] = data.responseText.match(/Groups.(enter|leave)\(.*?,.*?([\d]+?), ('|')(.*?)('|')/) || []; const publicHash = data.responseText.match(/"enterHash":"(.*?)"/)?.[1]; const publicPid = data.responseText.match(/"public_id":([\d]+?),/)?.[1]; const publicJoined = !data.responseText.includes('Public.subscribe'); if (groupAct && groupId && groupHash) { debug('获取到Vk群组ID', { groupAct: groupAct, groupId: groupId, groupHash: groupHash }); logStatus.success(); return { groupAct: groupAct, groupId: groupId, groupHash: groupHash, type: 'group' }; } if (publicHash && publicPid) { debug('获取到Vk公共页面ID', { publicHash: publicHash, publicPid: publicPid, publicJoined: publicJoined }); logStatus.success(); return { publicHash: publicHash, publicPid: publicPid, publicJoined: publicJoined, type: 'public' }; } if (name.includes('action=like')) { const hash = data.responseText.match(/data-reaction-hash="(.*?)"/)?.[1]; const trackCode = data.responseText.match(/data-post-track-code="(.*?)"/)?.[1]; const object = name.match(/wall-[\w_]+/)?.[0]; if (hash && trackCode && object) { debug('获取到Vk点赞ID', { hash: hash, trackCode: trackCode, object: object }); logStatus.success(); return { type: 'likeWall', hash: hash, trackCode: trackCode, object: object }; } } if (data.responseText.includes('wall.deletePost') && !doTask) { const wallHash = data.responseText.match(/wall\.deletePost\(this, '.*?', '(.*?)'\)/)?.[1]; if (wallHash) { debug('获取到Vk删除墙ID', { wallHash: wallHash }); logStatus.success(); return { type: 'deleteWall', wallHash: wallHash }; } } if (name.includes('wall') && doTask) { debug('获取到Vk墙ID'); logStatus.success(); return { type: 'sendWall' }; } debug('未找到Vk ID参数'); logStatus.error('Error: Parameters not found!'); return false; } catch (error) { debug('获取Vk ID时发生错误', { error: error }); throwError(error, 'Vk.getId'); return false; } } async #toggleVk({name: name, doTask: doTask = true}) { try { debug('开始处理Vk任务', { name: name, doTask: doTask }); if (!doTask && this.whiteList.names.includes(name)) { debug('Vk任务在白名单中,跳过', { name: name }); echoLog({ type: 'whiteList', text: 'Vk.undoTask', id: name, before: '[Vk]' }); return true; } const formatName = name.replace(/\/$/, ''); const data = await this.#getId(formatName, doTask); if (!data) { return false; } switch (data.type) { case 'group': return await this.#toggleGroup(formatName, data, doTask); case 'public': return await this.#togglePublic(formatName, data, doTask); case 'likeWall': return await this.#toggleLikeWall(formatName, data, doTask); case 'sendWall': return doTask ? await this.#sendWall(formatName) : true; case 'deleteWall': return doTask ? true : await this.#deleteWall(formatName, data); default: debug('未知的Vk任务类型', { type: data.type }); return false; } } catch (error) { debug('处理Vk任务时发生错误', { error: error }); throwError(error, 'Vk.toggleVk'); return false; } } async toggle({doTask: doTask = true, nameLinks: nameLinks = []}) { try { debug('开始处理Vk链接任务', { doTask: doTask, nameLinksCount: nameLinks.length }); if (!this.#initialized) { debug('Vk模块未初始化'); echoLog({ text: I18n('needInit'), before: '[Vk]' }); return false; } const prom = []; if (doTask && !globalOptions.doTask.vk.names || !doTask && !globalOptions.undoTask.vk.names) { debug('根据全局选项跳过Vk任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'vk.names', before: '[Vk]' }); } else { const realNames = this.getRealParams('names', nameLinks, doTask, (link => link.match(/https:\/\/vk\.com\/([^/]+)/)?.[1])); debug('处理后的Vk链接列表', { count: realNames.length, names: realNames }); if (realNames.length > 0) { for (const name of realNames) { prom.push(this.#toggleVk({ name: name, doTask: doTask })); await delay(1e3); } } } return Promise.all(prom).then((() => true)); } catch (error) { debug('处理Vk链接任务时发生错误', { error: error }); throwError(error, 'Vk.toggle'); return false; } } #setCache(name, postId) { try { debug('设置Vk缓存', { name: name, postId: postId }); this.#cache[name] = postId; GM_setValue('vkCache', this.#cache); } catch (error) { debug('设置Vk缓存时发生错误', { error: error }); throwError(error, 'Vk.setCache'); } } } const getInfo = async function(link, type) { try { debug('开始获取YouTube信息', { link: link, type: type }); const logStatus = echoLog({ text: I18n('gettingYtbToken'), before: '[Youtube]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success') { debug('获取YouTube信息请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return {}; } if (data?.status !== 200) { debug('获取YouTube信息状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return {}; } if (data.responseText.includes('accounts.google.com/ServiceLogin?service=youtube')) { debug('获取YouTube信息失败:需要登录'); logStatus.error(`Error:${I18n('loginYtb')}`, true); return { needLogin: true }; } const apiKey = data.responseText.match(/"INNERTUBE_API_KEY":"(.*?)"/)?.[1]; const context = (data.responseText.match(/\(\{"INNERTUBE_CONTEXT":([\w\W]*?)\}\)/) || data.responseText.match(/"INNERTUBE_CONTEXT":([\w\W]*?\}),"INNERTUBE/))?.[1] || '{}'; const {client: client, request: request} = JSON.parse(context); if (!apiKey || !client || !request) { debug('获取YouTube信息失败:缺少必要参数'); logStatus.error('Error: Parameter "apiKey" not found!'); return {}; } client.hl = 'en'; if (type === 'channel') { const channelId = data.responseText.match(/"channelId":"(.+?)"/)?.[1]; if (!channelId) { debug('获取YouTube频道ID失败'); logStatus.error('Error: Get "channelId" failed!'); return {}; } debug('成功获取YouTube频道信息', { channelId: channelId }); logStatus.success(); return { params: { apiKey: apiKey, client: client, request: request, channelId: channelId } }; } if (type === 'likeVideo') { const videoId = data.responseText.match(/<link rel="shortlinkUrl" href="https:\/\/youtu\.be\/(.*?)">/)?.[1]; const likeParams = data.responseText.match(/"likeParams":"(.*?)"/)?.[1]; if (!videoId) { debug('获取YouTube视频ID失败'); logStatus.error('Error: Get "videoId" failed!'); return {}; } debug('成功获取YouTube视频信息', { videoId: videoId }); logStatus.success(); return { params: { apiKey: apiKey, client: client, request: request, videoId: videoId, likeParams: likeParams } }; } debug('未知的YouTube信息类型', { type: type }); logStatus.error('Error: Unknown type'); return {}; } catch (error) { debug('获取YouTube信息时发生错误', { error: error }); throwError(error, 'Youtube.getInfo'); return {}; } }; class Youtube extends Social { tasks; whiteList; #auth=GM_getValue('youtubeAuth') || {}; #initialized=false; #verifyChannel=`https://www.youtube.com/channel/${globalOptions.other.youtubeVerifyChannel}`; constructor() { super(); const defaultTasksTemplate = { channels: [], likes: [] }; debug('初始化YouTube实例'); this.tasks = defaultTasksTemplate; this.whiteList = { ...defaultTasksTemplate, ...GM_getValue('whiteList')?.youtube || {} }; } async init() { try { debug('开始初始化YouTube模块'); if (this.#initialized) { debug('YouTube模块已初始化'); return true; } if (!this.#auth.PAPISID) { debug('YouTube授权信息不完整,需要更新授权'); if (await this.#updateAuth()) { this.#initialized = true; return true; } return false; } const isVerified = await this.#verifyAuth(); if (isVerified) { debug('YouTube授权验证成功'); echoLog({ before: '[Youtube]' }).success(I18n('initSuccess', 'Youtube')); this.#initialized = true; return true; } debug('YouTube授权失效,尝试重新获取'); GM_setValue('youtubeAuth', null); if (await this.#updateAuth()) { debug('YouTube重新授权成功'); echoLog({ before: '[Youtube]' }).success(I18n('initSuccess', 'Youtube')); this.#initialized = true; return true; } debug('YouTube初始化失败'); echoLog({ before: '[Youtube]' }).error(I18n('initFailed', 'Youtube')); return false; } catch (error) { debug('YouTube初始化发生错误', { error: error }); throwError(error, 'Youtube.init'); return false; } } async #verifyAuth() { try { debug('开始验证YouTube授权'); return await this.#toggleChannel({ link: this.#verifyChannel, doTask: true, verify: true }); } catch (error) { debug('YouTube授权验证发生错误', { error: error }); throwError(error, 'Youtube.verifyAuth'); return false; } } async #updateAuth() { try { debug('开始更新YouTube授权'); const logStatus = echoLog({ text: I18n('updatingAuth', 'Youtube'), before: '[Youtube]' }); return await new Promise((resolve => { GM_cookie.list({ url: 'https://www.youtube.com/@YouTube' }, (async (cookies, error) => { if (!error) { const PAPISID = cookies.find((cookie => cookie.name === '__Secure-3PAPISID'))?.value; if (PAPISID) { debug('成功获取YouTube新授权信息'); GM_setValue('youtubeAuth', { PAPISID: PAPISID }); this.#auth = { PAPISID: PAPISID }; logStatus.success(); resolve(await this.#verifyAuth()); } else { debug('获取YouTube授权失败:未登录'); logStatus.error(I18n('needLogin')); resolve(false); } } else { debug('获取YouTube授权失败', { error: error }); logStatus.error('Error: Update youtube auth failed!'); resolve(false); } })); })); } catch (error) { debug('更新YouTube授权时发生错误', { error: error }); throwError(error, 'Youtube.updateAuth'); return false; } } #getInfo(link, type) { debug('调用获取YouTube信息方法', { link: link, type: type }); return getInfo(link, type); } async #toggleChannel({link: link, doTask: doTask = true, verify: verify = false}) { try { debug('开始处理YouTube频道任务', { link: link, doTask: doTask, verify: verify }); const {params: params, needLogin: needLogin} = await this.#getInfo(link, 'channel'); const {apiKey: apiKey, client: client, request: request, channelId: channelId} = params || {}; if (needLogin) { debug('YouTube频道操作失败:需要登录'); echoLog({ html: I18n('loginYtb'), before: '[Youtube]' }); return false; } if (!(apiKey && client && request && channelId)) { debug('YouTube频道操作失败:获取参数失败'); echoLog({ text: '"getYtbToken" failed', before: '[Youtube]' }); return false; } if (!doTask && !verify && this.whiteList.channels.includes(channelId)) { debug('YouTube频道在白名单中,跳过取消订阅', { channelId: channelId }); echoLog({ type: 'whiteList', text: 'Youtube.unfollowChannel', id: channelId, before: '[Youtube]' }); return true; } const logStatus = verify ? echoLog({ text: I18n('verifyingAuth', 'Youtube'), before: '[Youtube]' }) : echoLog({ type: doTask ? 'followingYtbChannel' : 'unfollowingYtbChannel', text: channelId, before: '[Youtube]' }); const nowTime = parseInt(String((new Date).getTime() / 1e3), 10); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://www.youtube.com/youtubei/v1/subscription/${doTask ? '' : 'un'}subscribe?key=${apiKey}&prettyPrint=false`, method: 'POST', headers: { origin: 'https://www.youtube.com', referer: `https://www.youtube.com/channel/${channelId}`, 'content-type': 'application/json', 'x-goog-authuser': '0', 'x-goog-visitor-id': client?.visitorData, 'x-origin': 'https://www.youtube.com', authorization: `SAPISIDHASH ${nowTime}_${sha1(`${nowTime} ${this.#auth.PAPISID} https://www.youtube.com`)}` }, data: JSON.stringify({ context: { client: client, request: { sessionId: request?.sessionId, internalExperimentFlags: [], consistencyTokenJars: [] }, user: {} }, channelIds: [ channelId ], params: doTask ? 'EgIIAhgA' : 'CgIIAhgA' }) }); if (result !== 'Success') { debug('YouTube频道操作请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('YouTube频道操作状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const isSubscribed = doTask && (/"subscribed":true/.test(data.responseText) || data.responseText.includes('The subscription already exists')); const isUnsubscribed = !doTask && /"subscribed":false/.test(data.responseText); const isVerified = verify && data.responseText.includes('You may not subscribe to yourself'); if (isSubscribed || isUnsubscribed || isVerified) { debug('YouTube频道操作成功', { doTask: doTask, verify: verify }); logStatus.success(); if (doTask && !verify) { this.tasks.channels = unique([ ...this.tasks.channels, link ]); } return true; } debug('YouTube频道操作失败,需要更新授权'); logStatus.error(I18n('tryUpdateYtbAuth'), true); return false; } catch (error) { debug('处理YouTube频道任务时发生错误', { error: error }); throwError(error, 'Youtube.toggleChannel'); return false; } } async #toggleLikeVideo({link: link, doTask: doTask = true}) { try { debug('开始处理YouTube视频点赞任务', { link: link, doTask: doTask }); const {params: params, needLogin: needLogin} = await this.#getInfo(link, 'likeVideo'); const {apiKey: apiKey, client: client, request: request, videoId: videoId, likeParams: likeParams} = params || {}; if (needLogin) { debug('YouTube视频点赞失败:需要登录'); echoLog({ html: `${I18n('loginYtb')}`, before: '[Youtube]' }); return false; } if (!(apiKey && client && request && videoId && likeParams)) { debug('YouTube视频点赞失败:获取参数失败'); echoLog({ text: '"getYtbToken" failed', before: '[Youtube]' }); return false; } if (!doTask && this.whiteList.likes.includes(videoId)) { debug('YouTube视频在白名单中,跳过取消点赞', { videoId: videoId }); echoLog({ type: 'whiteList', text: 'Youtube.unlikeVideo', id: videoId, before: '[Youtube]' }); return true; } const logStatus = echoLog({ type: doTask ? 'likingYtbVideo' : 'unlikingYtbVideo', text: videoId, before: '[Youtube]' }); const nowTime = parseInt(String((new Date).getTime() / 1e3), 10); const likeVideoData = { context: { client: client, request: { sessionId: request.sessionId, internalExperimentFlags: [], consistencyTokenJars: [] }, user: {} }, target: { videoId: videoId } }; if (doTask && !likeParams) { debug('YouTube视频点赞失败:缺少likeParams参数'); logStatus.error('Empty likeParams'); return false; } if (doTask) { likeVideoData.params = likeParams; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://www.youtube.com/youtubei/v1/like/${doTask ? '' : 'remove'}like?key=${apiKey}`, method: 'POST', headers: { origin: 'https://www.youtube.com', referer: `https://www.youtube.com/watch?v=${videoId}`, 'content-type': 'application/json', 'x-goog-authuser': '0', 'x-goog-visitor-id': client.visitorData, 'x-origin': 'https://www.youtube.com', authorization: `SAPISIDHASH ${nowTime}_${sha1(`${nowTime} ${this.#auth.PAPISID} https://www.youtube.com`)}` }, data: JSON.stringify(likeVideoData) }); if (result !== 'Success') { debug('YouTube视频点赞请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('YouTube视频点赞状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const isLiked = doTask && data.responseText.includes('Added to Liked videos'); const isUnliked = !doTask && (data.responseText.includes('Removed from Liked videos') || data.responseText.includes('Dislike removed')); if (isLiked || isUnliked) { debug('YouTube视频点赞操作成功', { doTask: doTask }); logStatus.success(); if (doTask) { this.tasks.likes = unique([ ...this.tasks.likes, link ]); } return true; } debug('YouTube视频点赞失败,需要更新授权'); logStatus.error(I18n('tryUpdateYtbAuth'), true); return false; } catch (error) { debug('处理YouTube视频点赞任务时发生错误', { error: error }); throwError(error, 'Youtube.toggleLikeVideo'); return false; } } async toggle({doTask: doTask = true, channelLinks: channelLinks = [], videoLinks: videoLinks = []}) { try { debug('开始处理YouTube链接任务', { doTask: doTask, channelLinksCount: channelLinks.length, videoLinksCount: videoLinks.length }); if (!this.#initialized) { debug('YouTube模块未初始化'); echoLog({ text: I18n('needInit'), before: '[Youtube]' }); return false; } const prom = []; const shouldProcessChannels = doTask && globalOptions.doTask.youtube.channels || !doTask && globalOptions.undoTask.youtube.channels; const shouldProcessVideos = doTask && globalOptions.doTask.youtube.likes || !doTask && globalOptions.undoTask.youtube.likes; if (!shouldProcessChannels) { debug('根据全局选项跳过YouTube频道任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'youtube.channels', before: '[Youtube]' }); } else { const realChannels = this.getRealParams('channels', channelLinks, doTask, (link => { if (/^https:\/\/(www\.)?google\.com.*?\/url\?.*?url=https:\/\/www\.youtube\.com\/.*/.test(link)) { return link.match(/url=(https:\/\/www\.youtube\.com\/.*)/)?.[1]; } return link; })); debug('处理后的YouTube频道链接列表', { count: realChannels.length, channels: realChannels }); if (realChannels.length > 0) { for (const channel of realChannels) { prom.push(this.#toggleChannel({ link: channel, doTask: doTask })); await delay(1e3); } } } if (!shouldProcessVideos) { debug('根据全局选项跳过YouTube视频任务', { doTask: doTask }); echoLog({ type: 'globalOptionsSkip', text: 'youtube.likes', before: '[Youtube]' }); } else { const realLikes = this.getRealParams('likes', videoLinks, doTask, (link => { if (/^https:\/\/(www\.)?google\.com.*?\/url\?.*?url=https:\/\/www\.youtube\.com\/.*/.test(link)) { return link.match(/url=(https:\/\/www\.youtube\.com\/.*)/)?.[1]; } return link; })); debug('处理后的YouTube视频链接列表', { count: realLikes.length, videos: realLikes }); if (realLikes.length > 0) { for (const video of realLikes) { prom.push(this.#toggleLikeVideo({ link: video, doTask: doTask })); await delay(1e3); } } } return Promise.all(prom).then((() => true)); } catch (error) { debug('处理YouTube链接任务时发生错误', { error: error }); throwError(error, 'Youtube.toggle'); return false; } } } class SteamASF { #asfOptions; #botName='asf'; #groupInfo; #steamWebApiKey; #steamId; constructor({AsfIpcUrl: AsfIpcUrl, AsfIpcPassword: AsfIpcPassword, AsfBotname: AsfBotname, steamWebApiKey: steamWebApiKey}) { debug('初始化SteamASF实例', { AsfIpcUrl: AsfIpcUrl, AsfBotname: AsfBotname }); const asfCommandsUrl = new URL('/Api/Command/', AsfIpcUrl); this.#asfOptions = { url: asfCommandsUrl.href, method: 'POST', responseType: 'json', headers: { accept: 'application/json', 'Content-Type': 'application/json', Host: asfCommandsUrl.host, Origin: asfCommandsUrl.origin, Referer: asfCommandsUrl.href, Authentication: AsfIpcPassword } }; if (AsfBotname) { this.#botName = AsfBotname; } if (steamWebApiKey) { this.#steamWebApiKey = steamWebApiKey; } debug('SteamASF实例初始化完成', { botName: this.#botName }); } async init() { try { debug('开始初始化ASF'); const logStatus = echoLog({ text: I18n('initingASF'), before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: '{"Command":"!stats"}' }); if (result !== 'Success') { debug('ASF初始化请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.response?.Success === true && data.response.Message === 'OK' && data.response.Result) { debug('ASF初始化成功'); logStatus.success(); return true; } if (data?.response?.Result || data?.response?.Message) { debug('ASF初始化失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(data?.response?.Result || data.response.Message); return false; } debug('ASF初始化失败', { statusText: data?.statusText, status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } catch (error) { debug('ASF初始化发生错误', { error: error }); throwError(error, 'SteamASF.init'); return false; } } async joinGroup(groupName) { try { debug('开始加入Steam组', { groupName: groupName }); const logStatus = echoLog({ type: 'joiningSteamGroup', text: groupName, before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!JOINGROUP ${this.#botName} ${groupName}` }) }); if (result !== 'Success') { debug('加入Steam组请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '已加入', '已申请', 'Joined', 'Applied', 'Присоединился', 'costs' ].find((text => data.response?.Result?.includes(text)))) { debug('成功加入Steam组', { groupName: groupName }); logStatus.success(); return true; } debug('加入Steam组失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('加入Steam组时发生错误', { error: error, groupName: groupName }); throwError(error, 'SteamASF.joinGroup'); return false; } } joinOfficialGroup=this.joinGroup; leaveOfficialGroup=this.leaveGroup; async leaveGroup(groupName) { try { debug('开始退出Steam组', { groupName: groupName }); if (!this.#groupInfo) { debug('未找到组信息,尝试获取组ID'); if (!await this.#getGroupId()) { return false; } } const groupId = await this.#groupInfo[groupName]; if (!groupId) { debug('未找到组ID', { groupName: groupName }); return false; } const logStatus = echoLog({ type: 'leavingSteamGroup', text: groupName, before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!LEAVEGROUP ${this.#botName} ${groupId}` }) }); if (result !== 'Success') { debug('退出Steam组请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) { debug('成功退出Steam组', { groupName: groupName }); logStatus.success(); return true; } debug('退出Steam组失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('退出Steam组时发生错误', { error: error, groupName: groupName }); throwError(error, 'SteamASF.leaveGroup'); return false; } } async #getGroupId() { try { debug('开始获取Steam组ID列表'); const logStatus = echoLog({ type: 'gettingSteamGroupId', text: 'All', before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!GROUPLIST ${this.#botName}` }) }); if (result !== 'Success') { debug('获取Steam组ID列表请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && data.response?.Result?.includes('|')) { this.#groupInfo = Object.fromEntries(data.response.Result.split('\n').map((line => { const [, name, id] = line.trim().split('|'); if (name && id) { return [ name, id ]; } return null; })).filter((ele => ele))); debug('成功获取Steam组ID列表', { groupCount: Object.keys(this.#groupInfo).length }); logStatus.success(); return true; } debug('获取Steam组ID列表失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('获取Steam组ID列表时发生错误', { error: error }); throwError(error, 'SteamASF.getGroupID'); return false; } } async addToWishlist(gameId) { try { debug('开始添加游戏到愿望单', { gameId: gameId }); const logStatus = echoLog({ type: 'addingToWishlist', text: gameId, before: '[ASF]' }); const gameStatus = await this.#checkGame(gameId); if (gameStatus.wishlist === true) { debug('游戏已在愿望单中', { gameId: gameId }); logStatus.success(); return true; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!ADDWISHLIST ${this.#botName} ${gameId}` }) }); if (result !== 'Success') { debug('添加游戏到愿望单请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) { debug('成功添加游戏到愿望单', { gameId: gameId }); logStatus.success(); return true; } debug('添加游戏到愿望单失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('添加游戏到愿望单时发生错误', { error: error, gameId: gameId }); throwError(error, 'SteamASF.addToWishlist'); return false; } } async removeFromWishlist(gameId) { try { debug('开始从愿望单移除游戏', { gameId: gameId }); const logStatus = echoLog({ type: 'removingFromWishlist', text: gameId, before: '[ASF]' }); const gameStatus = await this.#checkGame(gameId); if (gameStatus.wishlist === false) { debug('游戏已不在愿望单中', { gameId: gameId }); logStatus.success(); return true; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!REMOVEWISHLIST ${this.#botName} ${gameId}` }) }); if (result !== 'Success') { debug('从愿望单移除游戏请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) { debug('成功从愿望单移除游戏', { gameId: gameId }); logStatus.success(); return true; } debug('从愿望单移除游戏失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('从愿望单移除游戏时发生错误', { error: error, gameId: gameId }); throwError(error, 'SteamASF.removeFromWishlist'); return false; } } async toggleFollowGame(gameId, doTask) { try { debug('开始处理游戏关注状态', { gameId: gameId, doTask: doTask }); const logStatus = echoLog({ type: `${doTask ? '' : 'un'}followingGame`, text: gameId, before: '[ASF]' }); const gameStatus = await this.#checkGame(gameId); if (doTask && gameStatus.followed === true || !doTask && gameStatus.followed === false) { debug('游戏关注状态已符合要求', { gameId: gameId, doTask: doTask, currentStatus: gameStatus.followed }); logStatus.success(); return true; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!${doTask ? '' : 'UN'}FOLLOWGAME ${this.#botName} ${gameId}` }) }); if (result !== 'Success') { debug('处理游戏关注状态请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) { debug('成功处理游戏关注状态', { gameId: gameId, doTask: doTask }); logStatus.success(); return true; } debug('处理游戏关注状态失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('处理游戏关注状态时发生错误', { error: error, gameId: gameId, doTask: doTask }); throwError(error, 'SteamASF.toggleFollowGame'); return false; } } async #checkGame(gameId) { try { debug('开始检查游戏状态', { gameId: gameId }); const {result: result, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!CHECK ${this.#botName} ${gameId}` }) }); if (result !== 'Success') { debug('检查游戏状态请求失败', { result: result }); return {}; } if (data?.status !== 200 || !data.response?.Result?.includes(gameId)) { debug('检查游戏状态响应无效', { status: data?.status }); return {}; } const matchedResult = data.response.Result.split('\n').find((result => result.includes(gameId)))?.split('|'); if (!matchedResult || matchedResult.length <= 3) { debug('未找到游戏状态信息', { gameId: gameId }); return {}; } const status = { wishlist: matchedResult.at(-3).trim() === '√' || matchedResult.at(-2).trim() === '√', followed: matchedResult.at(-1).trim() === '√' }; debug('成功获取游戏状态', { gameId: gameId, status: status }); return status; } catch (error) { debug('检查游戏状态时发生错误', { error: error, gameId: gameId }); throwError(error, 'SteamASF.checkGame'); return {}; } } async toggleCurator(curatorId, doTask = true) { try { debug('开始处理鉴赏家关注状态', { curatorId: curatorId, doTask: doTask }); const logStatus = echoLog({ type: doTask ? 'followingCurator' : 'unfollowingCurator', text: curatorId, before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!${doTask ? '' : 'UN'}FOLLOWCURATOR ${this.#botName} ${curatorId}` }) }); if (result !== 'Success') { debug('处理鉴赏家关注状态请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) { debug('成功处理鉴赏家关注状态', { curatorId: curatorId, doTask: doTask }); logStatus.success(); return true; } if (data?.status === 200) { debug('处理鉴赏家关注状态失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(I18n('curatorLimitNotice')); return false; } debug('处理鉴赏家关注状态失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('处理鉴赏家关注状态时发生错误', { error: error, curatorId: curatorId, doTask: doTask }); throwError(error, 'Steam.toggleCurator'); return false; } } async addLicense(id) { try { debug('开始添加许可证', { id: id }); const [type, ids] = id.split('-'); const idsArr = ids.split(','); if (type === 'appid') { const logStatus = echoLog({ type: 'addingFreeLicense', text: ids, before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!addlicense ${this.#botName} ${idsArr.map((id => `app/${id}`)).join(',')}` }) }); if (result !== 'Success') { debug('添加应用许可证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ 'AlreadyPurchased', 'OK' ].find((text => data.response?.Result?.includes(text)))) { debug('成功添加应用许可证', { ids: ids }); logStatus.success(); return true; } debug('添加应用许可证失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } if (type === 'subid') { const logStatus = echoLog({ type: 'addingFreeLicenseSubid', text: ids, before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!addlicense ${this.#botName} ${idsArr.map((id => `sub/${id}`)).join(',')}` }) }); if (result !== 'Success') { debug('添加订阅许可证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && data.response?.Result) { const resultLines = data.response.Result.split('\n'); debug('处理订阅许可证结果', { resultLines: resultLines }); idsArr.forEach((subid => { const targetLine = resultLines.find((text => text.includes(subid))); if (targetLine && [ '成功', 'Success', 'Успех' ].find((text => targetLine.includes(text)))) { debug('成功添加订阅许可证', { subid: subid }); echoLog({ before: '[ASF]' }).success(targetLine); } else { debug('添加订阅许可证失败', { subid: subid, targetLine: targetLine }); echoLog({ before: '[ASF]' }).error(targetLine); } })); return true; } debug('添加订阅许可证失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } debug('无效的许可证类型', { type: type }); return false; } catch (error) { debug('添加许可证时发生错误', { error: error, id: id }); throwError(error, 'SteamASF.addLicense'); return false; } } async requestPlayTestAccess(id) { try { debug('开始请求游戏试玩权限', { id: id }); const logStatus = echoLog({ type: 'requestingPlayTestAccess', text: id, before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!REQUESTACCESS ${this.#botName} ${id}` }) }); if (result !== 'Success') { debug('请求游戏试玩权限请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '成功', 'Success', 'Успех' ].find((text => data.response?.Result?.includes(text)))) { debug('成功请求游戏试玩权限', { id: id }); logStatus.success(); return true; } debug('请求游戏试玩权限失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('请求游戏试玩权限时发生错误', { error: error, id: id }); throwError(error, 'SteamASF.requestPlayTestAccess'); return false; } } async playGames(ids) { try { debug('开始挂游戏时长', { ids: ids }); const logStatus = echoLog({ text: I18n('playingGames', ids), before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!play ${this.#botName} ${ids}` }) }); if (result !== 'Success') { debug('挂游戏时长请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '正在运行', '正在掛', 'Playing', 'Играет' ].find((text => data.response?.Result?.includes(text)))) { debug('成功开始挂游戏时长', { ids: ids }); logStatus.success(); return true; } debug('开始挂游戏时长失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('挂游戏时长时发生错误', { error: error, ids: ids }); throwError(error, 'SteamASF.playGames'); return false; } } async getSteamIdASF() { try { debug('开始获取Steam ID'); const logStatus = echoLog({ text: I18n('gettingSteamId'), before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!steamid ${this.#botName}` }) }); if (result !== 'Success' || data?.status !== 200) { debug('获取Steam ID请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return ''; } if (data.response?.Result) { const steamId = data.response.Result.trim()?.split(/\s+/)?.at(-1); if (steamId) { debug('成功获取Steam ID', steamId); logStatus.success(); return steamId; } } debug('获取Steam ID失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return ''; } catch (error) { debug('获取Steam ID时发生错误', { error: error }); throwError(error, 'SteamASF.getSteamIdASF'); return ''; } } async getSteamIdWeb() { try { debug('开始获取Steam ID'); const logStatus = echoLog({ text: I18n('gettingSteamId'), before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://store.steampowered.com', method: 'GET', headers: { host: 'store.steampowered.com' } }); if (result !== 'Success' || data?.status !== 200) { debug('获取Steam ID请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return ''; } const steamId = data.responseText.match(/steamid":"(\d+)/)?.[1]; if (steamId) { debug('成功获取Steam ID', steamId); logStatus.success(); return steamId; } debug('获取Steam ID失败', { data: data }); logStatus.error(`${result}:${statusText}(${status})`); return ''; } catch (error) { debug('获取Steam ID时发生错误', { error: error }); throwError(error, 'SteamASF.getSteamIdWeb'); return ''; } } async getSteamId() { const steamId = await this.getSteamIdWeb(); if (steamId) { return steamId; } return this.getSteamIdASF(); } async checkPlayStatus(ids) { try { debug('开始检查挂游戏时长状态'); if (!this.#steamWebApiKey) { debug('未设置Steam Web API Key'); return 'skip'; } if (!this.#steamId) { const steamId = await this.getSteamId(); if (!steamId) { debug('未获取到Steam ID'); return 'skip'; } this.#steamId = steamId; } const logStatus = echoLog({ text: I18n('checkingPlayStatus'), before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${this.#steamWebApiKey}&steamids=${this.#steamId}`, method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (result !== 'Success') { debug('检查挂游戏时长状态请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200) { debug('挂游戏时长状态正常', { data: data }); const playedIds = new Set(data.responseText?.match(/\d+/g)); const neededIds = new Set(ids.match(/\d+/g)); if (neededIds.intersection(playedIds).size > 0) { logStatus.success(); return true; } logStatus.warning(I18n('noPlayStatus')); return false; } debug('挂游戏时长状态异常', { data: data }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } catch (error) { debug('检查挂游戏时长状态时发生错误', { error: error }); throwError(error, 'SteamASF.checkPlayStatus'); return false; } } async stopPlayGames() { try { debug('开始停止挂游戏时长'); const logStatus = echoLog({ text: I18n('stoppingPlayGames'), before: '[ASF]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ ...this.#asfOptions, data: JSON.stringify({ Command: `!resume ${this.#botName}` }) }); if (result !== 'Success') { debug('停止挂游戏时长请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status === 200 && [ '已经恢复', '已恢复', '已經繼續', '已繼續', 'resumed', 'возобновлён' ].find((text => data.response?.Result?.includes(text)))) { debug('成功停止挂游戏时长'); logStatus.success(); return true; } debug('停止挂游戏时长失败', { result: data?.response?.Result, message: data?.response?.Message }); logStatus.error(`Error:${data?.response?.Result || data?.response?.Message || data?.statusText}(${data?.status})`); return false; } catch (error) { debug('停止挂游戏时长时发生错误', { error: error }); throwError(error, 'SteamASF.stopPlayGames'); return false; } } async #unsupportted(name) { try { debug('尝试使用不支持的功能', { name: name }); echoLog({ before: '[ASF]' }).warning(I18n('ASFNotSupportted', name)); return false; } catch (error) { debug('处理不支持的功能时发生错误', { error: error, name: name }); throwError(error, 'SteamASF.unsupportted'); return false; } } async toggleForum() { return this.#unsupportted('toggleForum'); } async toggleFavoriteWorkshop() { return this.#unsupportted('toggleFavoriteWorkshop'); } async voteUpWorkshop() { return this.#unsupportted('voteUpWorkshop'); } async likeAnnouncement() { return this.#unsupportted('likeAnnouncement'); } } class SteamWeb { #cache={ ...{ group: {}, officialGroup: {}, forum: {}, workshop: {}, curator: {} }, ...GM_getValue('steamCache') }; #auth={}; #storeInitialized=false; #communityInitialized=false; #area='CN'; #oldArea; #areaStatus='end'; constructor() { debug('初始化SteamWeb实例'); } async init(type = 'all') { try { debug('开始初始化SteamWeb', { type: type }); const initStoreResult = await this.initStore(); debug('Steam商店初始化完成', { initStoreResult: initStoreResult }); if (type === 'store') { return initStoreResult; } const initCommunityResult = await this.initCommunity(initStoreResult); debug('Steam社区初始化完成', { initCommunityResult: initCommunityResult }); return initCommunityResult; } catch (error) { debug('SteamWeb初始化发生错误', { error: error, type: type }); throwError(error, 'SteamWeb.init'); return false; } } async initStore() { try { debug('开始初始化Steam商店'); if (this.#storeInitialized) { return true; } let storeInitialized = await this.#updateStoreAuth(); if (!storeInitialized) { storeInitialized = await this.#updateStoreAuthTab(); } this.#storeInitialized = storeInitialized; if (!this.#storeInitialized) { echoLog({ before: '[Web]' }).error(I18n('initFailed', 'Steam')); return false; } echoLog({ before: '[Web]' }).success(I18n('initSuccess', 'SteamStore')); debug('Steam商店初始化完成'); return true; } catch (error) { debug('Steam商店初始化发生错误', { error: error }); throwError(error, 'SteamWeb.initStore'); return false; } } async initCommunity(initStoreResult) { try { debug('开始初始化Steam社区'); if (this.#communityInitialized) { return true; } let communityInitialized = await this.#updateCommunityAuth(initStoreResult); if (!communityInitialized) { communityInitialized = await this.#updateCommunityAuthTab(); GM_setValue('steamCommunityAuth', null); } this.#communityInitialized = communityInitialized; if (!this.#communityInitialized) { echoLog({ before: '[Web]' }).error(I18n('initFailed', 'Steam')); return false; } echoLog({ before: '[Web]' }).success(I18n('initSuccess', 'SteamCommunity')); debug('Steam社区初始化完成'); return true; } catch (error) { debug('Steam社区初始化发生错误', { error: error }); throwError(error, 'SteamWeb.initCommunity'); return false; } } async #refreshToken(type = 'steamStore') { try { debug('开始刷新令牌', { type: type }); const host = { steamStore: 'store.steampowered.com', steamCommunity: 'steamcommunity.com' }; const logStatus = echoLog({ text: I18n('refreshingToken', I18n(type)), before: '[Web]' }); debug('准备刷新令牌请求数据'); const formData = new FormData; formData.append('redir', `https://${host[type]}/`); debug('发送刷新令牌请求'); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://login.steampowered.com/jwt/ajaxrefresh', method: 'POST', responseType: 'json', headers: { Host: 'login.steampowered.com', Origin: `https://${host[type]}`, Referer: `https://${host[type]}/` }, data: formData }); debug('收到刷新令牌响应', { result: result, status: status, statusText: statusText }); if (result !== 'Success') { debug('请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (!data?.response?.success) { debug('响应不成功', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('开始设置新令牌'); if (!await this.#setToken(data.response, type)) { debug('设置新令牌失败'); logStatus.error('Error'); return false; } debug('成功刷新令牌'); logStatus.success(); return true; } catch (error) { debug('刷新令牌时发生错误', { error: error }); throwError(error, 'SteamWeb.refreshToken'); return false; } } async #setToken(param, type) { try { const host = { steamStore: 'store.steampowered.com', steamCommunity: 'steamcommunity.com' }; debug('开始设置Steam令牌', { type: type }); const logStatus = echoLog({ text: I18n('settingToken', I18n(type)), before: '[Web]' }); debug('准备表单数据'); const formData = new FormData; formData.append('steamID', param.steamID); formData.append('nonce', param.nonce); formData.append('redir', param.redir); formData.append('auth', param.auth); debug('表单数据准备完成', { steamID: param.steamID, nonce: param.nonce, redir: param.redir }); debug('发送设置令牌请求'); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://${host[type]}/login/settoken`, method: 'POST', headers: { Accept: 'application/json, text/plain, */*', Host: host[type], Origin: `https://${host[type]}` }, data: formData }); debug('收到设置令牌响应', { result: result, status: status, statusText: statusText }); if (result !== 'Success') { debug('请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('成功设置令牌'); logStatus.success(); return true; } catch (error) { debug('设置令牌时发生错误', { error: error, type: type }); throwError(error, 'SteamWeb.setToken'); return false; } } async #updateStoreAuth(retry = false) { try { debug('开始更新Steam商店身份验证'); const logStatus = echoLog({ text: I18n('updatingAuth', I18n('steamStore')), before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://store.steampowered.com/', method: 'GET', headers: { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Upgrade-Insecure-Requests': '1' }, redirect: 'manual' }); debug('收到Steam商店身份验证响应', { result: result, statusText: statusText, status: status }); if (data?.status !== 200) { if (![ 301, 302 ].includes(data?.status)) { debug('Steam商店身份验证状态错误', { status: data?.status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (!await this.#refreshToken('steamStore')) { debug('Steam商店身份验证刷新失败'); logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true); return false; } if (retry) { debug('Steam商店身份验证重试失败'); logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true); return false; } debug('Steam商店身份验证重试中'); logStatus.warning(I18n('retry')); return this.#updateStoreAuth(true); } if (!data.responseText.includes('data-miniprofile=')) { if (await this.#refreshToken('steamStore')) { debug('Steam商店身份验证需要重试'); logStatus.warning(I18n('retry')); if (retry) { debug('Steam商店身份验证重试次数超限'); logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true); return false; } return this.#updateStoreAuth(true); } debug('Steam商店身份验证失败:需要登录'); logStatus.error(`Error:${I18n('needLoginSteamStore')}`, true); return false; } const storeSessionID = data.responseText.match(/g_sessionID = "(.+?)";/)?.[1]; if (!storeSessionID) { debug('Steam商店身份验证失败:获取sessionID失败'); logStatus.error('Error: Get "sessionID" failed'); return false; } this.#auth.storeSessionID = storeSessionID; debug('Steam商店身份验证更新成功', { storeSessionID: storeSessionID }); logStatus.success(); return true; } catch (error) { debug('更新Steam商店身份验证时发生错误', { error: error }); throwError(error, 'SteamWeb.updateStoreAuth'); return false; } } async #updateStoreAuthTab() { try { debug('开始通过新标签页更新Steam商店身份验证'); const logStatus = echoLog({ text: I18n('updatingAuth', I18n('steamStoreTab')), before: '[Web]' }); return await new Promise((resolve => { GM_deleteValue('steamStoreAuth'); GM_setValue('ATv4_updateStoreAuth', true); const newTab = GM_openInTab('https://store.steampowered.com/', { active: true, setParent: true }); debug('打开Steam商店新标签页'); newTab.name = 'ATv4_updateStoreAuth'; const listenerId = GM_addValueChangeListener('steamStoreAuth', ((key, oldValue, newValue) => { debug('监听到Steam商店身份验证值变化', { oldValue: oldValue, newValue: newValue }); GM_removeValueChangeListener(listenerId); GM_deleteValue('ATv4_updateStoreAuth'); newTab?.close(); window.focus(); if (newValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) { this.#auth.storeSessionID = newValue.storeSessionID; debug('Steam商店身份验证更新成功', { storeSessionID: newValue.storeSessionID }); logStatus.success(); resolve(true); return; } debug('Steam商店身份验证更新失败'); logStatus.error('Failed'); resolve(false); })); newTab.onclose = () => { debug('Steam商店新标签页已关闭'); GM_deleteValue('ATv4_updateStoreAuth'); }; })); } catch (error) { debug('通过新标签页更新Steam商店身份验证时发生错误', { error: error }); throwError(error, 'SteamWeb.updateStoreAuthTab'); return false; } } async #updateCommunityAuthTab() { try { debug('开始通过新标签页更新Steam社区身份验证'); const logStatus = echoLog({ text: I18n('updatingAuth', I18n('steamCommunityTab')), before: '[Web]' }); return await new Promise((resolve => { GM_deleteValue('steamCommunityAuth'); GM_setValue('ATv4_updateCommunityAuth', true); const newTab = GM_openInTab('https://steamcommunity.com/my', { active: true, setParent: true }); debug('打开Steam社区新标签页'); newTab.name = 'ATv4_updateCommunityAuth'; const listenerId = GM_addValueChangeListener('steamCommunityAuth', ((key, oldValue, newValue) => { debug('监听到Steam社区身份验证值变化', { oldValue: oldValue, newValue: newValue }); GM_removeValueChangeListener(listenerId); GM_deleteValue('ATv4_updateCommunityAuth'); newTab?.close(); window.focus(); if (newValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) { this.#auth.steam64Id = newValue.steam64Id; this.#auth.communitySessionID = newValue.communitySessionID; debug('Steam社区身份验证更新成功', { steam64Id: newValue.steam64Id, communitySessionID: newValue.communitySessionID }); logStatus.success(); resolve(true); return; } debug('Steam社区身份验证更新失败'); logStatus.error('Failed'); resolve(false); })); newTab.onclose = () => { debug('Steam社区新标签页已关闭'); GM_deleteValue('ATv4_updateCommunityAuth'); }; })); } catch (error) { debug('通过新标签页更新Steam社区身份验证时发生错误', { error: error }); throwError(error, 'SteamWeb.updateCommunityAuthTab'); return false; } } async #updateCommunityAuth(initStoreResult, retry = false) { try { debug('开始更新Steam社区身份验证'); const logStatus = echoLog({ text: I18n('gettingUserInfo'), before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://steamcommunity.com/my', method: 'GET', headers: { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', Host: 'steamcommunity.com', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate' }, redirect: 'follow' }); debug('收到Steam社区身份验证响应', { result: result, statusText: statusText, status: status }); if (data?.status !== 200) { debug('Steam社区身份验证状态错误', { status: data?.status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data.finalUrl.includes('https://steamcommunity.com/login/home')) { if (initStoreResult) { if (await this.#refreshToken('steamCommunity')) { debug('Steam社区身份验证需要重试'); logStatus.warning(I18n('retry')); if (retry) { debug('Steam社区身份验证重试次数超限'); logStatus.error(`Error:${I18n('needLoginSteamCommunity')}`, true); return false; } return this.#updateCommunityAuth(initStoreResult, retry); } } debug('Steam社区身份验证失败:需要登录'); logStatus.error(`Error:${I18n('needLoginSteamCommunity')}`, true); return false; } const steam64Id = data.responseText.match(/g_steamID = "(.+?)";/)?.[1]; const communitySessionID = data.responseText.match(/g_sessionID = "(.+?)";/)?.[1]; if (!steam64Id || !communitySessionID) { debug('Steam社区身份验证失败:获取身份信息失败'); logStatus.error('Error: Get "sessionID" failed'); return false; } this.#auth.steam64Id = steam64Id; this.#auth.communitySessionID = communitySessionID; debug('Steam社区身份验证更新成功', { steam64Id: steam64Id, communitySessionID: communitySessionID }); logStatus.success(); return true; } catch (error) { debug('更新Steam社区身份验证时发生错误', { error: error }); throwError(error, 'SteamWeb.updateCommunityAuth'); return false; } } async #getAreaInfo() { try { debug('开始获取Steam地区信息'); const logStatus = echoLog({ text: I18n('gettingAreaInfo'), before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://store.steampowered.com/cart/', method: 'GET' }); debug('获取地区信息请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success' || data?.status !== 200) { debug('获取地区信息失败', { result: result, status: data?.status, statusText: data?.statusText }); logStatus.error(result === 'Success' ? `Error:${data?.statusText}(${data?.status})` : `${result}:${statusText}(${status})`); return {}; } const cartConfigRaw = data.responseText.match(/data-cart_config="(.*?)"/)?.[1]; debug('cartConfigRaw提取结果', { cartConfigRaw: cartConfigRaw }); const temp = document.createElement('div'); temp.innerHTML = cartConfigRaw || '{}'; const cartConfigStr = temp.textContent || temp.innerText; let cartConfig; try { cartConfig = JSON.parse(cartConfigStr); debug('cartConfig解析成功', { cartConfig: cartConfig }); } catch (error) { debug('cartConfig解析失败', { error: error }); logStatus.error('Error: get country info filed'); console.error(error); return {}; } if (!cartConfig.rgUserCountryOptions) { debug('未找到可更换地区'); logStatus.warning('Warning: Area cannot be changed'); return {}; } const userInfoRaw = data.responseText.match(/data-userinfo="(.*?)"/)?.[1]; debug('userInfoRaw提取结果', { userInfoRaw: userInfoRaw }); const temp1 = document.createElement('div'); temp1.innerHTML = userInfoRaw || '{}'; const userInfoStr = temp1.textContent || temp1.innerText; let userInfo; try { userInfo = JSON.parse(userInfoStr); debug('userInfo解析成功', { userInfo: userInfo }); } catch (error) { debug('userInfo解析失败', { error: error }); logStatus.error('Error: get country info filed'); console.error(error); return {}; } const currentArea = userInfo.country_code; const areas = Object.keys(cartConfig.rgUserCountryOptions).filter((area => area !== 'help')); debug('地区信息提取', { currentArea: currentArea, areas: areas }); if (!currentArea || areas.length === 0) { debug('未获取到当前地区或可更换地区为空', { currentArea: currentArea, areas: areas }); logStatus.error('Error: get country info filed'); return {}; } this.#area = currentArea; debug('获取地区信息成功', { currentArea: currentArea, areas: areas }); logStatus.success(); return { currentArea: currentArea, areas: areas }; } catch (error) { debug('获取地区信息时发生异常', { error: error }); throwError(error, 'SteamWeb.getAreaInfo'); return {}; } } async #changeArea(area) { try { debug('开始更换Steam地区', { area: area }); if (this.#areaStatus === 'waiting') { debug('当前地区状态为waiting,等待状态改变'); await new Promise((resolve => { const checker = setInterval((() => { if (this.#areaStatus !== 'waiting') { clearInterval(checker); resolve(true); } })); })); } if (this.#area === area || !area && this.#area !== 'CN') { debug('无需更换地区', { currentArea: this.#area, targetArea: area }); return true; } this.#areaStatus = 'waiting'; let aimedArea = area; if (!aimedArea) { debug('未指定目标地区,自动获取可用地区'); const {currentArea: currentArea, areas: areas} = await this.#getAreaInfo(); debug('获取到地区信息', { currentArea: currentArea, areas: areas }); if (!currentArea || !areas) { debug('获取地区信息失败', { currentArea: currentArea, areas: areas }); this.#areaStatus = 'error'; return false; } if (currentArea !== 'CN') { debug('当前地区不是CN,无需更换', { currentArea: currentArea }); this.#areaStatus = 'skip'; echoLog({ text: I18n('notNeededChangeArea'), before: '[Web]' }); return 'skip'; } const anotherArea = areas.filter((area => area && area !== 'CN')); debug('可更换的其他地区', { anotherArea: anotherArea }); if (!anotherArea || anotherArea.length === 0) { debug('没有可用的其他地区'); this.#areaStatus = 'noAnotherArea'; echoLog({ text: I18n('noAnotherArea'), before: '[Web]' }); return false; } [aimedArea] = anotherArea; debug('选定目标地区', { aimedArea: aimedArea }); } const logStatus = echoLog({ text: I18n('changingArea', aimedArea), before: '[Web]' }); debug('发送更换地区请求', { aimedArea: aimedArea }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://store.steampowered.com/country/setcountry', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ cc: aimedArea, sessionid: this.#auth.storeSessionID }) }); debug('更换地区请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success' || data?.status !== 200 || data.responseText !== 'true') { debug('更换地区失败', { result: result, status: data?.status, statusText: data?.statusText, responseText: data?.responseText }); this.#areaStatus = 'error'; logStatus.error(result === 'Success' ? `Error:${data?.statusText}(${data?.status})` : `${result}:${statusText}(${status})`); return 'CN'; } const {currentArea: currentArea} = await this.#getAreaInfo(); debug('更换后获取到的当前地区', { currentArea: currentArea }); if (currentArea) { this.#area = currentArea; if (!this.#oldArea) { this.#oldArea = currentArea; } } if (currentArea !== aimedArea) { debug('更换后当前地区与目标地区不符', { currentArea: currentArea, aimedArea: aimedArea }); this.#areaStatus = 'error'; logStatus.error('Error: change country filed'); return 'CN'; } this.#areaStatus = 'success'; debug('更换地区成功', { currentArea: currentArea }); logStatus.success(); return currentArea; } catch (error) { debug('更换地区时发生异常', { error: error }); this.#areaStatus = 'error'; throwError(error, 'SteamWeb.changeArea'); return false; } } async joinGroup(groupName) { try { debug('开始加入Steam组', { groupName: groupName }); const logStatus = echoLog({ type: 'joiningSteamGroup', text: groupName, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/groups/${groupName}`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ action: 'join', sessionID: this.#auth.communitySessionID }) }); if (result !== 'Success') { debug('加入Steam组请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || data.responseText.includes('grouppage_join_area')) { debug('加入Steam组失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('成功加入Steam组', { groupName: groupName }); logStatus.success(); return true; } catch (error) { debug('加入Steam组时发生错误', { error: error, groupName: groupName }); throwError(error, 'SteamWeb.joinGroup'); return false; } } async leaveGroup(groupName) { try { debug('开始退出Steam组', { groupName: groupName }); const groupId = await this.#getGroupId(groupName); if (!groupId) { return false; } const logStatus = echoLog({ type: 'leavingSteamGroup', text: groupName, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/profiles/${this.#auth.steam64Id}/home_process`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ sessionID: this.#auth.communitySessionID, action: 'leaveGroup', groupId: groupId }) }); if (result !== 'Success') { debug('退出Steam组请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || !data.finalUrl.includes('groups')) { debug('退出Steam组失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const hasGroupLink = $(data.responseText.replace(/<img.*?>/g, '').toLowerCase()).find(`a[href='https://steamcommunity.com/groups/${groupName.toLowerCase()}']`).length > 0; if (hasGroupLink) { debug('Error: Group link still exists'); return false; } debug('成功退出Steam组', { groupName: groupName }); logStatus.success(); return true; } catch (error) { debug('退出Steam组时发生错误', { error: error, groupName: groupName }); throwError(error, 'SteamWeb.leaveGroup'); return false; } } async #getGroupId(groupName) { try { debug('开始获取Steam组ID', { groupName: groupName }); const logStatus = echoLog({ type: 'gettingSteamGroupId', text: groupName, before: '[Web]' }); const cachedGroupId = this.#cache.group[groupName]; if (cachedGroupId) { debug('从缓存中获取到组ID', { groupName: groupName, cachedGroupId: cachedGroupId }); logStatus.success(); return cachedGroupId; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/groups/${groupName}`, method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }); debug('获取组ID请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('获取组ID请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取组ID响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const matchedGroupId = data.responseText.match(/OpenGroupChat\( '([0-9]+)'/)?.[1]; debug('正则提取组ID结果', { matchedGroupId: matchedGroupId }); if (!matchedGroupId) { debug('未能提取到组ID', { groupName: groupName }); logStatus.error(`Error:${data.statusText}(${data.status})`); return false; } this.#setCache('group', groupName, matchedGroupId); debug('组ID已缓存', { groupName: groupName, matchedGroupId: matchedGroupId }); logStatus.success(); return matchedGroupId; } catch (error) { debug('获取组ID时发生异常', { error: error, groupName: groupName }); throwError(error, 'SteamWeb.getGroupID'); return false; } } async joinOfficialGroup(gameId) { try { debug('开始加入Steam官方组', { gameId: gameId }); const logStatus = echoLog({ type: 'joiningSteamOfficialGroup', text: gameId, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/games/${gameId}?action=join&sessionID=${this.#auth.communitySessionID}`, method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }); if (result !== 'Success') { debug('加入Steam官方组请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || data.responseText.includes('id="publicGroupJoin"')) { debug('加入Steam官方组失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const groupId = data.responseText.match(/steam:\/\/friends\/joinchat\/([0-9]+)/)?.[1]; if (groupId) { this.#setCache('officialGroup', gameId, groupId); } debug('成功加入Steam官方组', { gameId: gameId }); logStatus.success(); return true; } catch (error) { debug('加入Steam官方组时发生错误', { error: error, gameId: gameId }); throwError(error, 'SteamWeb.joinOfficialGroup'); return false; } } async leaveOfficialGroup(gameId) { try { debug('开始退出Steam官方组', { gameId: gameId }); const groupId = await this.#getOfficialGroupId(gameId); if (!groupId) { return false; } const logStatus = echoLog({ type: 'leavingSteamOfficialGroup', text: gameId, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/profiles/${this.#auth.steam64Id}/home_process`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ sessionID: this.#auth.communitySessionID, action: 'leaveGroup', groupId: groupId }) }); if (result !== 'Success') { debug('退出Steam官方组请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('退出Steam官方组失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({ url: `https://steamcommunity.com/games/${gameId}`, method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }); if (resultR !== 'Success') { debug('退出Steam官方组时发生错误', { error: resultR, status: statusR, statusText: statusTextR }); logStatus.error(`${resultR}:${statusTextR}(${statusR})`); return false; } if (dataR?.status !== 200 || !dataR.responseText.includes('id="publicGroupJoin"')) { debug('退出Steam官方组失败', { status: dataR?.status }); logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`); return false; } debug('成功退出Steam官方组', { gameId: gameId }); logStatus.success(); return true; } catch (error) { debug('退出Steam官方组时发生错误', { error: error, gameId: gameId }); throwError(error, 'SteamWeb.leaveOfficialGroup'); return false; } } async #getOfficialGroupId(gameId) { try { debug('开始获取Steam官方群组ID', { gameId: gameId }); const logStatus = echoLog({ type: 'gettingSteamOfficialGroupId', text: gameId, before: '[Web]' }); const cachedGroupId = this.#cache.officialGroup[gameId]; if (cachedGroupId) { debug('从缓存中获取到官方群组ID', { gameId: gameId, cachedGroupId: cachedGroupId }); logStatus.success(); return cachedGroupId; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/games/${gameId}`, method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }); debug('获取官方群组ID请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('获取官方群组ID请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取官方群组ID响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const matchedGroupId = data.responseText.match(/steam:\/\/friends\/joinchat\/([0-9]+)/)?.[1]; debug('正则提取官方群组ID结果', { matchedGroupId: matchedGroupId }); if (!matchedGroupId) { debug('未能提取到官方群组ID', { gameId: gameId }); logStatus.error(`Error:${data.statusText}(${data.status})`); return false; } this.#setCache('officialGroup', gameId, matchedGroupId); debug('官方群组ID已缓存', { gameId: gameId, matchedGroupId: matchedGroupId }); logStatus.success(); return matchedGroupId; } catch (error) { debug('获取官方群组ID时发生异常', { error: error, gameId: gameId }); throwError(error, 'SteamWeb.getGroupID'); return false; } } async addToWishlist(gameId) { try { debug('开始添加游戏到愿望单', { gameId: gameId }); const logStatus = echoLog({ type: 'addingToWishlist', text: gameId, before: '[Web]' }); const {result: result, data: data} = await httpRequest({ url: 'https://store.steampowered.com/api/addtowishlist', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ sessionid: this.#auth.storeSessionID, appid: gameId }), dataType: 'json' }); if (result === 'Success' && data?.status === 200 && data.response?.success === true) { debug('成功添加游戏到愿望单', { gameId: gameId }); logStatus.success(); return true; } const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({ url: `https://store.steampowered.com/app/${gameId}`, method: 'GET' }); if (resultR !== 'Success') { debug('添加游戏到愿望单请求失败', { result: resultR, status: statusR, statusText: statusTextR }); logStatus.error(`${resultR}:${statusTextR}(${statusR})`); return false; } if (dataR?.status !== 200) { debug('添加游戏到愿望单失败', { status: dataR?.status }); logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`); return false; } if (this.#area === 'CN' && dataR.responseText.includes('id="error_box"')) { debug('changeAreaNotice'); if (!await this.#changeArea()) { return false; } return await this.addToWishlist(gameId); } if (dataR.responseText.includes('class="queue_actions_ctn"') && dataR.responseText.includes('class="already_in_library"')) { debug('成功添加游戏到愿望单', { gameId: gameId }); logStatus.success(); return true; } if (dataR.responseText.includes('class="queue_actions_ctn"') && dataR.responseText.includes('id="add_to_wishlist_area_success" style="display: none;') || !dataR.responseText.includes('class="queue_actions_ctn"')) { debug('添加游戏到愿望单失败', { status: dataR.statusText }); logStatus.error(`Error:${dataR.statusText}(${dataR.status})`); return false; } debug('成功添加游戏到愿望单', { gameId: gameId }); logStatus.success(); return true; } catch (error) { debug('添加游戏到愿望单时发生错误', { error: error, gameId: gameId }); throwError(error, 'SteamWeb.addToWishlist'); return false; } } async removeFromWishlist(gameId) { try { debug('开始从愿望单移除游戏', { gameId: gameId }); const logStatus = echoLog({ type: 'removingFromWishlist', text: gameId, before: '[Web]' }); const {result: result, data: data} = await httpRequest({ url: 'https://store.steampowered.com/api/removefromwishlist', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ sessionid: this.#auth.storeSessionID, appid: gameId }), dataType: 'json' }); if (result === 'Success' && data?.status === 200 && data.response?.success === true) { debug('成功从愿望单移除游戏', { gameId: gameId }); logStatus.success(); return true; } const {result: resultR, statusText: statusTextR, status: statusR, data: dataR} = await httpRequest({ url: `https://store.steampowered.com/app/${gameId}`, method: 'GET' }); if (resultR !== 'Success') { debug('从愿望单移除游戏请求失败', { result: resultR, status: statusR, statusText: statusTextR }); logStatus.error(`${resultR}:${statusTextR}(${statusR})`); return false; } if (dataR?.status !== 200) { debug('从愿望单移除游戏失败', { status: dataR?.status }); logStatus.error(`Error:${dataR?.statusText}(${dataR?.status})`); return false; } if (this.#area === 'CN' && dataR.responseText.includes('id="error_box"')) { debug('changeAreaNotice'); const result = await this.#changeArea(); if (!result || result === 'CN' || result === 'skip') { return false; } return await this.removeFromWishlist(gameId); } if (dataR.responseText.includes('class="queue_actions_ctn"') && (dataR.responseText.includes('ds_owned_flag ds_flag') || dataR.responseText.includes('add_to_wishlist_area'))) { debug('成功从愿望单移除游戏', { gameId: gameId }); logStatus.success(); return true; } debug('从愿望单移除游戏请求失败', { result: resultR, status: statusR, statusText: statusTextR }); logStatus.error(`Error:${dataR.statusText}(${dataR.status})`); return false; } catch (error) { debug('从愿望单移除游戏时发生错误', { error: error, gameId: gameId }); throwError(error, 'SteamWeb.removeFromWishlist'); return false; } } async toggleFollowGame(gameId, doTask) { try { debug('开始处理游戏关注状态', { gameId: gameId, doTask: doTask }); const logStatus = echoLog({ type: `${doTask ? '' : 'un'}followingGame`, text: gameId, before: '[Web]' }); const requestData = { sessionid: this.#auth.storeSessionID, appid: gameId }; if (!doTask) { requestData.unfollow = '1'; } const {result: result, data: data} = await httpRequest({ url: 'https://store.steampowered.com/explore/followgame/', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param(requestData) }); if (result === 'Success' && data?.status === 200 && data.responseText === 'true') { debug('成功处理游戏关注状态', { gameId: gameId, doTask: doTask }); logStatus.success(); return true; } const followed = await this.#isFollowedGame(gameId); if (this.#area === 'CN' && followed === 'areaLocked') { debug('changeAreaNotice'); if (!await this.#changeArea()) { return false; } return await this.toggleFollowGame(gameId, doTask); } if (doTask === followed) { debug('成功处理游戏关注状态', { gameId: gameId, doTask: doTask }); logStatus.success(); return true; } debug('处理游戏关注状态请求失败', { result: result }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } catch (error) { debug('处理游戏关注状态时发生错误', { error: error, gameId: gameId, doTask: doTask }); throwError(error, 'SteamWeb.toggleFollowGame'); return false; } } async #isFollowedGame(gameId) { try { debug('开始判断Steam游戏是否已关注', { gameId: gameId }); const {result: result, data: data} = await httpRequest({ url: `https://store.steampowered.com/app/${gameId}`, method: 'GET' }); debug('获取游戏页面请求结果', { result: result, status: data?.status }); if (result !== 'Success') { debug('请求失败', { result: result }); return false; } if (data?.status !== 200) { debug('响应状态错误', { status: data?.status }); return false; } if (this.#area === 'CN' && data.responseText.includes('id="error_box"')) { debug('地区锁定,返回areaLocked', { area: this.#area }); return 'areaLocked'; } const isFollowed = $(data.responseText.replace(/<img.*?>/g, '')).find('.queue_control_button.queue_btn_follow>.btnv6_blue_hoverfade.btn_medium.queue_btn_active').css('display') !== 'none'; debug('关注状态判断结果', { isFollowed: isFollowed }); return isFollowed; } catch (error) { debug('判断游戏关注状态时发生异常', { error: error, gameId: gameId }); throwError(error, 'SteamWeb.isFollowedGame'); return false; } } async toggleForum(gameId, doTask = true) { try { debug('开始处理论坛订阅状态', { gameId: gameId, doTask: doTask }); const forumId = await this.#getForumId(gameId); if (!forumId) { return false; } const logStatus = echoLog({ type: `${doTask ? '' : 'un'}subscribingForum`, text: gameId, before: '[Web]' }); const [id, feature] = forumId.split('_'); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/forum/${id}/General/${doTask ? '' : 'un'}subscribe/${feature || '0'}/`, method: 'POST', responseType: 'json', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ sessionid: this.#auth.communitySessionID }) }); if (result !== 'Success') { debug('处理论坛订阅状态请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return true; } if (data?.status !== 200 || data.response?.success !== 1 && data.response?.success !== 29) { debug('处理论坛订阅状态失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return true; } debug('成功处理论坛订阅状态', { gameId: gameId, doTask: doTask }); logStatus.success(); return true; } catch (error) { debug('处理论坛订阅状态时发生错误', { error: error, gameId: gameId, doTask: doTask }); throwError(error, 'SteamWeb.toggleForum'); return false; } } async #getForumId(gameId) { try { debug('开始获取Steam论坛ID', { gameId: gameId }); const logStatus = echoLog({ type: 'gettingForumId', text: gameId, before: '[Web]' }); const cachedForumId = this.#cache.forum[gameId]; if (cachedForumId) { debug('从缓存中获取到论坛ID', { gameId: gameId, cachedForumId: cachedForumId }); logStatus.success(); return cachedForumId; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/app/${gameId}/discussions/`, method: 'GET' }); debug('获取论坛ID请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('获取论坛ID请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取论坛ID响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const matchedForumId = data.responseText?.match(/General_([\d]+(_[\d]+)?)/)?.[1]; debug('正则提取论坛ID结果', { matchedForumId: matchedForumId }); if (!matchedForumId) { debug('未能提取到论坛ID', { gameId: gameId }); logStatus.error(`Error:${data.statusText}(${data.status})`); return false; } this.#setCache('forum', gameId, matchedForumId); debug('论坛ID已缓存', { gameId: gameId, matchedForumId: matchedForumId }); logStatus.success(); return matchedForumId; } catch (error) { debug('获取论坛ID时发生异常', { error: error, gameId: gameId }); throwError(error, 'SteamWeb.getForumId'); return false; } } async toggleFavoriteWorkshop(id, doTask = true) { try { debug('开始处理创意工坊收藏状态', { id: id, doTask: doTask }); const appid = await this.#getWorkshopAppId(id); if (!appid) { return false; } const logStatus = echoLog({ type: doTask ? 'favoritingWorkshop' : 'unfavoritingWorkshop', text: id, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/sharedfiles/${doTask ? '' : 'un'}favorite`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ id: id, appid: appid, sessionid: this.#auth.communitySessionID }) }); if (result !== 'Success') { debug('处理创意工坊收藏状态请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || data.responseText) { debug('处理创意工坊收藏状态失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('成功处理创意工坊收藏状态', { id: id, doTask: doTask }); logStatus.success(); return true; } catch (error) { debug('处理创意工坊收藏状态时发生错误', { error: error, id: id, doTask: doTask }); throwError(error, 'SteamWeb.toggleFavoriteWorkshop'); return false; } } async #getWorkshopAppId(id) { try { debug('开始获取Steam创意工坊AppId', { id: id }); const logStatus = echoLog({ type: 'gettingWorkshopAppId', text: id, before: '[Web]' }); const cachedAppId = this.#cache.workshop[id]; if (cachedAppId) { debug('从缓存中获取到AppId', { id: id, cachedAppId: cachedAppId }); logStatus.success(); return cachedAppId; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://steamcommunity.com/sharedfiles/filedetails/?id=${id}`, method: 'GET' }); debug('获取创意工坊AppId请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('获取创意工坊AppId请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取创意工坊AppId响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const matchedAppId = data.responseText.match(/<input type="hidden" name="appid" value="([\d]+?)" \/>/)?.[1]; debug('正则提取AppId结果', { matchedAppId: matchedAppId }); if (!matchedAppId) { debug('未能提取到AppId', { id: id }); logStatus.error('Error: getWorkshopAppId failed'); return false; } debug('AppId已缓存', { id: id, matchedAppId: matchedAppId }); return matchedAppId; } catch (error) { debug('获取创意工坊AppId时发生异常', { error: error, id: id }); throwError(error, 'SteamWeb.getWorkshopAppId'); return false; } } async voteUpWorkshop(id) { try { debug('开始点赞创意工坊物品', { id: id }); const logStatus = echoLog({ type: 'votingUpWorkshop', text: id, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://steamcommunity.com/sharedfiles/voteup', method: 'POST', responseType: 'json', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ id: id, sessionid: this.#auth.communitySessionID }) }); if (result !== 'Success') { debug('点赞创意工坊物品请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return true; } if (data?.status !== 200 || data.response?.success !== 1) { debug('点赞创意工坊物品失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return true; } debug('成功点赞创意工坊物品', { id: id }); logStatus.success(); return true; } catch (error) { debug('点赞创意工坊物品时发生错误', { error: error, id: id }); throwError(error, 'SteamWeb.voteUpWorkshop'); return false; } } async toggleCurator(curatorId, doTask = true) { try { debug('开始处理鉴赏家关注状态', { curatorId: curatorId, doTask: doTask }); const logStatus = echoLog({ type: doTask ? 'followingCurator' : 'unfollowingCurator', text: curatorId, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://store.steampowered.com/curators/ajaxfollow', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $.param({ clanid: curatorId, sessionid: this.#auth.storeSessionID, follow: doTask }), dataType: 'json' }); if (result !== 'Success') { debug('处理鉴赏家关注状态请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.response?.success?.success === 25) { debug('处理鉴赏家关注状态失败', { status: data?.status, success: data?.response?.success, message: data?.response?.msg }); logStatus.error(I18n('curatorLimitNotice')); return false; } if (data?.status !== 200 || data.response?.success?.success !== 1) { debug('处理鉴赏家关注状态失败', { status: data?.status, success: data?.response?.success }); logStatus.error(`Error:${data?.statusText}(${data?.response?.success}` || `${data?.status})`); return false; } debug('成功处理鉴赏家关注状态', { curatorId: curatorId, doTask: doTask }); logStatus.success(); return true; } catch (error) { debug('处理鉴赏家关注状态时发生错误', { error: error, curatorId: curatorId, doTask: doTask }); throwError(error, 'SteamWeb.toggleCurator'); return false; } } async #getAnnouncementParams(appId, viewId) { try { debug('开始获取Steam公告参数', { appId: appId, viewId: viewId }); const logStatus = echoLog({ type: 'gettingAnnouncementParams', text: appId, id: viewId, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/events/ajaxgetpartnerevent?appid=${appId}&announcement_gid=${viewId}&lang_list=6_0&last_modified_time=0&origin=https:%2F%2Fstore.steampowered.com&for_edit=false`, method: 'GET', responseType: 'json', headers: { Host: 'store.steampowered.com', Referer: `https://store.steampowered.com/news/app/${appId}/view/${viewId}` } }); debug('获取公告参数请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('获取公告参数请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return {}; } if (data?.status !== 200 || data?.response?.success !== 1) { debug('获取公告参数响应状态错误', { status: data?.status, statusText: data?.statusText, response: data?.response }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return {}; } const {clanid: clanid, gid: gid} = data.response.event?.announcement_body || {}; debug('公告参数提取', { clanid: clanid, gid: gid }); if (!clanid) { debug('未能提取到clanid', { appId: appId, viewId: viewId }); logStatus.error(`Error:${data.statusText}(${data.status})`); return {}; } logStatus.success(); debug('获取公告参数成功', { clanId: clanid, gid: gid }); return { clanId: clanid, gid: gid }; } catch (error) { debug('获取公告参数时发生异常', { error: error, appId: appId, viewId: viewId }); throwError(error, 'SteamWeb.likeAnnouncement'); return {}; } } async likeAnnouncement(id) { try { debug('开始点赞公告', { id: id }); const [appId, viewId] = id.split('/'); if (!(appId && viewId)) { echoLog({ before: '[Web]' }).error(`${I18n('missParams')}(id)`); return false; } const {clanId: clanId, gid: gid} = await this.#getAnnouncementParams(appId, viewId); if (!clanId) { return false; } const logStatus = echoLog({ type: 'likingAnnouncement', text: appId, id: viewId, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/updated/ajaxrateupdate/${gid || viewId}`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Host: 'store.steampowered.com', Origin: 'https://store.steampowered.com', Referer: `https://store.steampowered.com/news/app/${appId}/view/${viewId}` }, data: $.param({ sessionid: this.#auth.storeSessionID, voteup: 1, clanid: clanId, ajax: 1 }), dataType: 'json' }); if (result !== 'Success') { debug('点赞公告请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || data.response.success !== 1) { debug('点赞公告失败', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('成功点赞公告', { id: id }); logStatus.success(); return true; } catch (error) { debug('点赞公告时发生错误', { error: error, id: id }); throwError(error, 'SteamWeb.likeAnnouncement'); return false; } } async #appid2subid(id) { try { debug('开始将AppId转换为SubId', { id: id }); const logStatus = echoLog({ type: 'gettingSubid', text: id, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/app/${id}`, method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }); debug('获取App页面请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('获取App页面请求失败', { result: result }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取App页面响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } if (data.responseText.includes('ds_owned_flag ds_flag') || data.responseText.includes('class="already_in_library"')) { debug('App已拥有', { id: id }); logStatus.success(I18n('owned')); return true; } if (this.#area === 'CN' && data.responseText.includes('id="error_box"')) { debug('地区锁定,尝试更换地区', { area: this.#area }); logStatus.warning(I18n('changeAreaNotice')); const result = await this.#changeArea(); if (!result || result === 'CN' || result === 'skip') { debug('更换地区失败或未更换', { result: result }); return false; } return await this.#appid2subid(id); } let subid = data.responseText.match(/name="subid" value="([\d]+?)"/)?.[1]; debug('正则提取subid结果1', { subid: subid }); if (subid) { logStatus.success(); return subid; } subid = data.responseText.match(/AddFreeLicense\(\s*(\d+)/)?.[1]; debug('正则提取subid结果2', { subid: subid }); if (subid) { logStatus.success(); return subid; } debug('未能提取到subid', { id: id }); logStatus.error(`Error:${I18n('noSubid')}`); return false; } catch (error) { debug('AppId转SubId时发生异常', { error: error, id: id }); throwError(error, 'SteamWeb.appid2subid'); return false; } } async #getLicenses() { try { debug('开始获取Steam用户许可证信息'); const logStatus = echoLog({ text: I18n('gettingLicenses'), before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/dynamicstore/userdata/?t=${(new Date).getTime()}`, method: 'GET', responseType: 'json' }); debug('获取许可证请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('获取许可证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取许可证响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('获取到的许可证列表', { licenses: data.response?.rgOwnedPackages }); logStatus.remove(); return data.response?.rgOwnedPackages; } catch (error) { debug('获取许可证时发生异常', { error: error }); throwError(error, 'SteamWeb.getLicenses'); return false; } } async addLicense(id) { try { debug('开始添加许可证', { id: id }); const [type, ids] = id.split('-'); debug('解析许可证ID', { type: type, ids: ids }); if (type !== 'appid' && type !== 'subid') { debug('无效的许可证类型', { type: type }); return false; } if (type === 'appid') { debug('处理appid类型许可证', { ids: ids }); const subid = await this.#appid2subid(ids); debug('appid转换为subid结果', { appid: ids, subid: subid }); if (!subid) { debug('appid转换失败', { appid: ids }); return false; } if (subid === true) { debug('appid已拥有', { appid: ids }); return true; } const logStatus = echoLog({ type: 'addingFreeLicense', text: ids, before: '[Web]' }); debug('开始添加免费许可证', { subid: subid }); if (!await this.#addFreeLicense(subid, logStatus)) { debug('添加免费许可证失败', { subid: subid }); return false; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/app/${ids}`, method: 'GET' }); debug('验证许可证添加状态', { result: result, status: status, statusText: statusText }); if (result !== 'Success') { debug('验证请求失败', { result: result, status: status, statusText: statusText }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('验证响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } if (!data.responseText.includes('ds_owned_flag ds_flag') && !data.responseText.includes('class="already_in_library"')) { debug('未找到游戏拥有标记', { status: data.status, statusText: data.statusText }); logStatus.error(`Error:${data.statusText}(${data.status})`); return false; } debug('appid许可证添加成功', { appid: ids }); logStatus.success(); return true; } if (this.#area === 'CN') { debug('当前区域为CN,尝试更改区域', { currentArea: this.#area }); echoLog({ before: '[Web]' }).success(I18n('tryChangeAreaNotice')); await this.#changeArea(); } const logStatusArr = {}; const idsArr = ids.split(','); debug('处理subid类型许可证', { idsArr: idsArr }); for (const subid of idsArr) { debug('开始处理单个subid', { subid: subid }); const logStatus = echoLog({ type: 'addingFreeLicense', text: subid, before: '[Web]' }); if (!await this.#addFreeLicense(subid, logStatus)) { debug('添加subid许可证失败', { subid: subid }); return false; } logStatusArr[subid] = logStatus; } const licenses = await this.#getLicenses(); debug('获取许可证列表', { licenses: licenses }); if (!licenses) { debug('获取许可证列表失败'); return false; } for (const subid of idsArr) { const hasLicense = licenses.includes(parseInt(subid, 10)); debug('验证许可证添加状态', { subid: subid, hasLicense: hasLicense }); if (hasLicense) { logStatusArr[subid].success(); } else { logStatusArr[subid].error(); } } debug('所有subid许可证处理完成', { idsArr: idsArr }); return true; } catch (error) { debug('添加许可证过程发生错误', { error: error, id: id }); throwError(error, 'SteamWeb.addLicense'); return false; } } async #addFreeLicense(id, logStatusPre) { try { debug('开始添加免费Steam游戏许可证', { id: id }); const logStatus = logStatusPre || echoLog({ type: 'addingFreeLicenseSubid', text: id, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/freelicense/addfreelicense/${id}`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Host: 'store.steampowered.com', Origin: 'https://store.steampowered.com', Referer: 'https://store.steampowered.com/account/licenses/' }, data: $.param({ ajax: true, sessionid: this.#auth.storeSessionID }), dataType: 'json' }); debug('添加免费许可证请求结果', { result: result, statusText: statusText, status: status }); if (result !== 'Success') { debug('添加免费许可证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('添加免费许可证响应状态错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } if (this.#area === 'CN' && data.responseText.includes('id="error_box"')) { debug('地区锁定,尝试更换地区', { area: this.#area }); logStatus.warning(I18n('changeAreaNotice')); const result = await this.#changeArea(); if (!result || [ 'CN', 'skip' ].includes(result)) { debug('更换地区失败或未更换', { result: result }); return false; } return await this.#addFreeLicense(id); } debug('成功添加免费许可证', { id: id }); logStatus.success(); return true; } catch (error) { debug('添加免费许可证时发生异常', { error: error, id: id }); throwError(error, 'SteamWeb.addFreeLicense'); return false; } } async requestPlayTestAccess(id) { debug('开始请求游戏试玩权限', { id: id }); try { debug('开始请求游戏试玩权限', { id: id }); const logStatus = echoLog({ type: 'requestingPlayTestAccess', text: id, before: '[Web]' }); debug('准备发送试玩权限请求', { id: id }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/ajaxrequestplaytestaccess/${id}`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Host: 'store.steampowered.com', Origin: 'https://store.steampowered.com', Referer: `https://store.steampowered.com/app/${id}` }, data: $.param({ sessionid: this.#auth.storeSessionID }), dataType: 'json' }); debug('收到试玩权限请求响应', { result: result, status: status, statusText: statusText, responseData: data }); if (result !== 'Success') { debug('请求失败', { result: result, status: status, statusText: statusText }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || data?.response?.success !== 1) { debug('响应状态错误', { status: data?.status, statusText: data?.statusText, success: data?.response?.success }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('成功请求游戏试玩权限', { id: id }); logStatus.success(); return true; } catch (error) { debug('请求游戏试玩权限时发生错误', { error: error, id: id }); throwError(error, 'SteamWeb.requestPlayTestAccess'); return false; } } async resetArea() { try { debug('检查区域设置状态', { currentArea: this.#area, oldArea: this.#oldArea, needReset: Boolean(this.#oldArea && this.#area !== this.#oldArea) }); if (this.#oldArea && this.#area !== this.#oldArea) { debug('需要重置区域', { fromArea: this.#area, toArea: this.#oldArea }); echoLog({ before: '[Web]' }).warning(I18n('steamFinishNotice') + this.#oldArea); const changeResult = await this.#changeArea(this.#oldArea); debug('区域重置结果', { success: changeResult, targetArea: this.#oldArea }); } else { debug('无需重置区域', { currentArea: this.#area, oldArea: this.#oldArea }); } debug('区域重置流程完成'); return true; } catch (error) { debug('重置区域时发生错误', { error: error }); throwError(error, 'SteamWeb.resetArea'); return false; } } #setCache(type, name, id) { try { debug('开始设置缓存', { type: type, name: name, id: id }); this.#cache[type][name] = id; GM_setValue('steamCache', this.#cache); debug('设置缓存成功', { type: type, name: name, id: id }); } catch (error) { debug('设置缓存时发生异常', { error: error, type: type, name: name, id: id }); throwError(error, 'SteamWeb.setCache'); } } } class Steam extends Social { tasks; whiteList; #cache={ ...{ group: {}, officialGroup: {}, forum: {}, workshop: {}, curator: {} }, ...GM_getValue('steamCache') }; #TaskExecutor=[]; constructor() { super(); debug('初始化Steam实例'); const defaultTasksTemplate = { groups: [], officialGroups: [], wishlists: [], follows: [], forums: [], workshops: [], workshopVotes: [], curators: [], curatorLikes: [], announcements: [], licenses: [], playtests: [], playTime: [] }; this.tasks = defaultTasksTemplate; this.whiteList = { ...defaultTasksTemplate, ...GM_getValue('whiteList')?.steam || {} }; this.#TaskExecutor = this.#getTaskExecutionOrder(globalOptions.ASF.AsfEnabled, globalOptions.ASF.steamWeb, globalOptions.ASF.preferASF); debug('Steam实例初始化完成', { taskExecutorCount: this.#TaskExecutor.length }); } async init(type = 'all') { try { debug('开始初始化Steam模块', { type: type }); for (let i = 0; i < this.#TaskExecutor.length; i++) { debug(`初始化执行器 ${i + 1}/${this.#TaskExecutor.length}`); if (!await this.#TaskExecutor[i].init(type)) { debug(`执行器 ${i + 1} 初始化失败,移除该执行器`); this.#TaskExecutor.splice(i, 1); } } debug('Steam模块初始化完成', { remainingExecutors: this.#TaskExecutor.length }); return this.#TaskExecutor.length > 0; } catch (error) { debug('Steam初始化发生错误', { error: error }); throwError(error, 'Steam.init'); return false; } } async #joinGroup(groupName) { try { debug('开始加入Steam组', { groupName: groupName }); for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.joinGroup(groupName)) { debug('成功加入Steam组', { groupName: groupName }); this.tasks.groups = unique([ ...this.tasks.groups, groupName ]); return true; } } debug('加入Steam组失败', { groupName: groupName }); return false; } catch (error) { debug('加入Steam组时发生错误', { error: error, groupName: groupName }); throwError(error, 'Steam.joinGroup'); return false; } } async #leaveGroup(groupName) { try { debug('开始退出Steam组', { groupName: groupName }); if (this.whiteList.groups.includes(groupName)) { debug('Steam组在白名单中,跳过退出', { groupName: groupName }); echoLog({ type: 'whiteList', text: 'Steam.leaveGroup', id: groupName }); return true; } for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.leaveGroup(groupName)) { debug('成功退出Steam组', { groupName: groupName }); return true; } } debug('退出Steam组失败', { groupName: groupName }); return false; } catch (error) { debug('退出Steam组时发生错误', { error: error, groupName: groupName }); throwError(error, 'Steam.leaveGroup'); return false; } } async #joinOfficialGroup(gameId) { try { debug('开始加入Steam官方组', { gameId: gameId }); for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.joinOfficialGroup(gameId)) { debug('成功加入Steam官方组', { gameId: gameId }); return true; } } debug('加入Steam官方组失败', { gameId: gameId }); return false; } catch (error) { debug('加入Steam官方组时发生错误', { error: error, gameId: gameId }); throwError(error, 'Steam.joinOfficialGroup'); return false; } } async #leaveOfficialGroup(gameId) { try { debug('开始退出Steam官方组', { gameId: gameId }); if (this.whiteList.officialGroups.includes(gameId)) { debug('Steam官方组在白名单中,跳过退出', { gameId: gameId }); echoLog({ type: 'whiteList', text: 'Steam.leaveOfficialGroup', id: gameId }); return true; } for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.leaveOfficialGroup(gameId)) { debug('成功退出Steam官方组', { gameId: gameId }); this.tasks.officialGroups = unique([ ...this.tasks.officialGroups, gameId ]); return true; } } debug('退出Steam官方组失败', { gameId: gameId }); return false; } catch (error) { debug('退出Steam官方组时发生错误', { error: error, gameId: gameId }); throwError(error, 'Steam.leaveOfficialGroup'); return false; } } async #addToWishlist(gameId) { try { debug('开始添加游戏到愿望单', { gameId: gameId }); for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.addToWishlist(gameId)) { debug('成功添加游戏到愿望单', { gameId: gameId }); this.tasks.wishlists = unique([ ...this.tasks.wishlists, gameId ]); return true; } } debug('添加游戏到愿望单失败', { gameId: gameId }); return false; } catch (error) { debug('添加游戏到愿望单时发生错误', { error: error, gameId: gameId }); throwError(error, 'Steam.addToWishlist'); return false; } } async #removeFromWishlist(gameId) { try { debug('开始从愿望单移除游戏', { gameId: gameId }); if (this.whiteList.wishlists.includes(gameId)) { debug('游戏在愿望单白名单中,跳过移除', { gameId: gameId }); echoLog({ type: 'whiteList', text: 'Steam.removeFromWishlist', id: gameId }); return true; } for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.removeFromWishlist(gameId)) { debug('成功从愿望单移除游戏', { gameId: gameId }); return true; } } debug('从愿望单移除游戏失败', { gameId: gameId }); return false; } catch (error) { debug('从愿望单移除游戏时发生错误', { error: error, gameId: gameId }); throwError(error, 'Steam.removeFromWishlist'); return false; } } async #toggleFollowGame(gameId, doTask) { try { debug('开始处理游戏关注状态', { gameId: gameId, doTask: doTask }); if (!doTask && this.whiteList.follows.includes(gameId)) { debug('游戏在关注白名单中,跳过取关', { gameId: gameId }); echoLog({ type: 'whiteList', text: 'Steam.unfollowGame', id: gameId }); return true; } for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.toggleFollowGame(gameId, doTask)) { if (doTask) { debug('成功关注游戏', { gameId: gameId }); this.tasks.follows = unique([ ...this.tasks.follows, gameId ]); } else { debug('成功取关游戏', { gameId: gameId }); } return true; } } debug('处理游戏关注状态失败', { gameId: gameId, doTask: doTask }); return false; } catch (error) { debug('处理游戏关注状态时发生错误', { error: error, gameId: gameId, doTask: doTask }); throwError(error, 'Steam.toggleFollowGame'); return false; } } async #toggleForum(gameId, doTask = true) { try { debug('开始处理论坛订阅状态', { gameId: gameId, doTask: doTask }); if (!doTask && this.whiteList.forums.includes(gameId)) { debug('论坛在白名单中,跳过取消订阅', { gameId: gameId }); echoLog({ type: 'whiteList', text: 'Steam.unsubscribeForum', id: gameId }); return true; } for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.toggleForum(gameId, doTask)) { if (doTask) { debug('成功订阅论坛', { gameId: gameId }); this.tasks.forums = unique([ ...this.tasks.forums, gameId ]); } else { debug('成功取消订阅论坛', { gameId: gameId }); } return true; } } debug('处理论坛订阅状态失败', { gameId: gameId, doTask: doTask }); return false; } catch (error) { debug('处理论坛订阅状态时发生错误', { error: error, gameId: gameId, doTask: doTask }); throwError(error, 'Steam.toggleForum'); return true; } } async #toggleFavoriteWorkshop(id, doTask = true) { try { debug('开始处理创意工坊收藏状态', { id: id, doTask: doTask }); if (!doTask && this.whiteList.workshops.includes(id)) { debug('创意工坊物品在白名单中,跳过取消收藏', { id: id }); echoLog({ type: 'whiteList', text: 'Steam.unfavoriteWorkshop', id: id }); return true; } for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.toggleFavoriteWorkshop(id)) { if (doTask) { debug('成功收藏创意工坊物品', { id: id }); this.tasks.workshops = unique([ ...this.tasks.workshops, id ]); } else { debug('成功取消收藏创意工坊物品', { id: id }); } return true; } } debug('处理创意工坊收藏状态失败', { id: id, doTask: doTask }); return false; } catch (error) { debug('处理创意工坊收藏状态时发生错误', { error: error, id: id, doTask: doTask }); throwError(error, 'Steam.toggleFavoriteWorkshop'); return false; } } async #voteUpWorkshop(id) { try { debug('开始点赞创意工坊物品', { id: id }); for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.voteUpWorkshop(id)) { debug('成功点赞创意工坊物品', { id: id }); return true; } } debug('点赞创意工坊物品失败', { id: id }); return false; } catch (error) { debug('点赞创意工坊物品时发生错误', { error: error, id: id }); throwError(error, 'Steam.voteupWorkshop'); return true; } } async #toggleCurator(curatorId, doTask = true) { try { debug('开始处理鉴赏家关注状态', { curatorId: curatorId, doTask: doTask }); if (!doTask && this.whiteList.curators.includes(curatorId)) { debug('鉴赏家在白名单中,跳过取关', { curatorId: curatorId }); echoLog({ type: 'whiteList', text: 'Steam.unfollowCurator', id: curatorId }); return true; } for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.toggleCurator(curatorId, doTask)) { if (doTask) { debug('成功关注鉴赏家', { curatorId: curatorId }); this.tasks.curators = unique([ ...this.tasks.curators, curatorId ]); } else { debug('成功取关鉴赏家', { curatorId: curatorId }); } return true; } } debug('处理鉴赏家关注状态失败', { curatorId: curatorId, doTask: doTask }); return false; } catch (error) { debug('处理鉴赏家关注状态时发生错误', { error: error, curatorId: curatorId, doTask: doTask }); throwError(error, 'Steam.toggleCurator'); return false; } } async getCuratorId(path, name) { try { debug('开始获取鉴赏家ID', { path: path, name: name }); const logStatus = echoLog({ type: 'gettingCuratorId', text: `${path}/${name}`, before: '[Web]' }); const curatorId = this.#cache.curator[`${path}/${name}`]; if (curatorId) { debug('从缓存获取到鉴赏家ID', { path: path, name: name, curatorId: curatorId }); logStatus.success(); return curatorId; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/${path}/${name}`, method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }); if (result === 'Success') { if (data?.status === 200) { const curatorId = data.responseText.match(/g_pagingData.*?"clanid":([\d]+)/)?.[1]; if (curatorId) { debug('成功获取鉴赏家ID', { path: path, name: name, curatorId: curatorId }); this.#setCache('curator', `${path}/${name}`, curatorId); logStatus.success(); return curatorId; } debug('未找到鉴赏家ID', { path: path, name: name, status: data.status }); logStatus.error(`Error:${data.statusText}(${data.status})`); return false; } debug('获取鉴赏家页面失败', { path: path, name: name, status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('请求鉴赏家页面失败', { path: path, name: name, result: result, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } catch (error) { debug('获取鉴赏家ID时发生错误', { error: error, path: path, name: name }); throwError(error, 'SteamWeb.getCuratorID'); return false; } } async #toggleCuratorLike(link, doTask = true) { try { debug('开始处理鉴赏家点赞状态', { link: link, doTask: doTask }); const [path, name] = link.split('/'); if (!(path && name)) { debug('无效的鉴赏家链接', { link: link }); echoLog({ text: I18n('errorLink', link), before: '[Web]' }); return false; } const curatorId = await this.getCuratorId(path, name); if (curatorId) { debug('获取到鉴赏家ID,开始处理点赞', { curatorId: curatorId, doTask: doTask }); return await this.#toggleCurator(curatorId, doTask); } debug('未获取到鉴赏家ID', { link: link }); return false; } catch (error) { debug('处理鉴赏家点赞状态时发生错误', { error: error, link: link, doTask: doTask }); throwError(error, 'Steam.toggleCuratorLike'); return false; } } async #likeAnnouncement(id) { try { debug('开始点赞公告', { id: id }); for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.likeAnnouncement(id)) { debug('成功点赞公告', { id: id }); return true; } } debug('点赞公告失败', { id: id }); return false; } catch (error) { debug('点赞公告时发生错误', { error: error, id: id }); throwError(error, 'Steam.likeAnnouncement'); return false; } } async #addLicense(id) { try { debug('开始添加许可证', { id: id }); for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.addLicense(id)) { debug('成功添加许可证', { id: id }); return true; } } debug('添加许可证失败', { id: id }); return false; } catch (error) { debug('添加许可证时发生错误', { error: error, id: id }); throwError(error, 'Steam.addLicense'); return false; } } async #requestPlayTestAccess(id) { try { debug('开始请求游戏试玩权限', { id: id }); for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.requestPlayTestAccess(id)) { debug('成功请求游戏试玩权限', { id: id }); return true; } } debug('请求游戏试玩权限失败', { id: id }); return false; } catch (error) { debug('请求游戏试玩权限时发生错误', { error: error, id: id }); throwError(error, 'Steam.requestPlayTestAccess'); return false; } } async #getDemoAppid(id) { try { debug('开始获取游戏试玩ID', { id: id }); const logStatus = echoLog({ type: 'gettingDemoAppid', text: id, before: '[Web]' }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://store.steampowered.com/app/${id}`, method: 'GET', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Host: 'store.steampowered.com', Origin: 'https://store.steampowered.com', Referer: `https://store.steampowered.com/app/${id}` } }); if (result === 'Success') { if (data?.status === 200) { const demoAppid = data.responseText.match(/steam:\/\/(install|run)\/(\d+)/)?.[2]; debug('成功获取游戏试玩ID', { id: id, demoAppid: demoAppid }); logStatus.success(); return demoAppid || false; } debug('获取游戏页面失败', { id: id, status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('请求游戏页面失败', { id: id, result: result, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } catch (error) { debug('获取游戏试玩ID时发生错误', { error: error, id: id }); throwError(error, 'Steam.getDemoAppid'); return false; } } async #playGames(ids, playTime, doTask = true) { try { debug('开始处理游戏挂时长', { ids: ids, playTime: playTime, doTask: doTask }); if (playTime <= 0) { debug('游戏时长小于等于0,跳过挂时长'); return true; } const asf = this.#TaskExecutor.find((e => e instanceof SteamASF)); if (!asf) { debug('未找到ASF实例'); echoLog({}).warning(I18n('noASFInstance')); return false; } if (!doTask) { debug('停止挂时长'); return await asf.stopPlayGames(); } const idsArr = await Promise.all(ids.split(',').map((async id => { try { const demoAppid = await this.#getDemoAppid(id); return demoAppid ? `${id},${demoAppid}` : id; } catch (error) { debug('获取游戏试玩ID失败', { error: error, id: id }); return id; } }))); const uniqueIds = unique(idsArr.join(',').split(',')); debug('处理后的游戏ID列表', { uniqueIds: uniqueIds }); debug('开始尝试入库游戏', { uniqueIds: uniqueIds }); await Promise.all(uniqueIds.map((async id => { for (const taskExecutor of this.#TaskExecutor) { if (await taskExecutor.addLicense(`appid-${id}`)) { debug('成功入库游戏', { id: id }); return true; } } return false; }))); await asf.playGames(uniqueIds.join(',')); const status = await asf.checkPlayStatus(uniqueIds.join(',')); if (status !== true) { await delay(3e3); await asf.playGames(uniqueIds.join(',')); const status = await asf.checkPlayStatus(uniqueIds.join(',')); if (!status) { debug('启动游戏失败'); return false; } } const stopPlayTime = Date.now() + (playTime + 10) * 60 * 1e3; const stopPlayTimeOld = GM_getValue('stopPlayTime', 0) || 0; GM_setValue('stopPlayTime', Math.max(stopPlayTime, stopPlayTimeOld)); const playedGames = GM_getValue('playedGames', []) || []; GM_setValue('playedGames', unique([ ...playedGames, ...uniqueIds ])); const taskLink = GM_getValue('taskLink', []) || []; GM_setValue('taskLink', unique([ ...taskLink, window.location.href ])); debug('游戏挂时长状态更新完成'); return true; } catch (error) { debug('处理游戏挂时长时发生错误', { error: error, ids: ids, playTime: playTime }); throwError(error, 'Steam.playGames'); return false; } } async toggle({doTask: doTask = true, groupLinks: groupLinks = [], officialGroupLinks: officialGroupLinks = [], wishlistLinks: wishlistLinks = [], followLinks: followLinks = [], forumLinks: forumLinks = [], workshopLinks: workshopLinks = [], workshopVoteLinks: workshopVoteLinks = [], curatorLinks: curatorLinks = [], curatorLikeLinks: curatorLikeLinks = [], announcementLinks: announcementLinks = [], licenseLinks: licenseLinks = [], playtestLinks: playtestLinks = [], playTimeLinks: playTimeLinks = []}) { try { debug('开始处理Steam任务', { doTask: doTask, linksCount: { groups: groupLinks.length, officialGroups: officialGroupLinks.length, wishlists: wishlistLinks.length, follows: followLinks.length, forums: forumLinks.length, workshops: workshopLinks.length, workshopVotes: workshopVoteLinks.length, curators: curatorLinks.length, curatorLikes: curatorLikeLinks.length, announcements: announcementLinks.length, licenses: licenseLinks.length, playtests: playtestLinks.length, playTime: playTimeLinks.length } }); const allLinks = [ ...groupLinks, ...officialGroupLinks, ...forumLinks, ...workshopLinks, ...workshopVoteLinks, ...wishlistLinks, ...followLinks, ...curatorLinks, ...curatorLikeLinks, ...announcementLinks, ...licenseLinks, ...playtestLinks, ...playTimeLinks ]; if (allLinks.length > 0 && this.#TaskExecutor.length === 0) { debug('Steam模块未初始化'); echoLog({ text: I18n('needInit') }); return false; } const tasks = []; if (this.shouldProcessTask('groups', doTask)) { debug('开始处理群组任务'); const realGroups = this.getRealParams('groups', groupLinks, doTask, (link => link.match(/groups\/(.+)\/?/)?.[1]?.split('/')?.[0])); debug('处理后的群组列表', { count: realGroups.length, groups: realGroups }); for (const group of realGroups) { tasks.push(doTask ? this.#joinGroup(group) : this.#leaveGroup(group)); await delay(1e3); } } if (this.shouldProcessTask('officialGroups', doTask)) { const realOfficialGroups = this.getRealParams('officialGroups', officialGroupLinks, doTask, (link => link.match(/games\/(.+)\/?/)?.[1])); for (const officialGroup of realOfficialGroups) { tasks.push(doTask ? this.#joinOfficialGroup(officialGroup) : this.#leaveOfficialGroup(officialGroup)); await delay(1e3); } } if (this.shouldProcessTask('wishlists', doTask)) { const realWishlists = this.getRealParams('wishlists', wishlistLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1])); for (const game of realWishlists) { tasks.push(doTask ? this.#addToWishlist(game) : this.#removeFromWishlist(game)); await delay(1e3); } } if (this.shouldProcessTask('follows', doTask)) { const realFollows = this.getRealParams('follows', followLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1])); for (const game of realFollows) { tasks.push(this.#toggleFollowGame(game, doTask)); await delay(1e3); } } if (this.shouldProcessTask('playTime', doTask)) { const realGames = this.getRealParams('playTime', playTimeLinks, doTask, (link => `${link.split('-')[0]}-${link.match(/app\/([\d]+)/)?.[1] || ''}`)); if (realGames.length > 0) { const maxTime = Math.max(...realGames.map((info => parseInt(info.split('-')[0], 10) || 0))); const games = realGames.filter((info => { const [time, game] = info.split('-'); return (parseInt(time, 10) || 0) > 0 && game; })).map((info => info.split('-')[1])); tasks.push(this.#playGames(games.join(','), maxTime, doTask)); await delay(1e3); } } if (this.shouldProcessTask('forums', doTask)) { const realForums = this.getRealParams('forums', forumLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1])); for (const forum of realForums) { tasks.push(this.#toggleForum(forum, doTask)); await delay(1e3); } } if (this.shouldProcessTask('workshops', doTask)) { const realWorkshops = this.getRealParams('workshops', workshopLinks, doTask, (link => link.match(/\?id=([\d]+)/)?.[1])); for (const workshop of realWorkshops) { tasks.push(this.#toggleFavoriteWorkshop(workshop, doTask)); await delay(1e3); } } if (doTask && globalOptions.doTask.steam.workshopVotes) { const realworkshopVotes = this.getRealParams('workshopVotes', workshopVoteLinks, doTask, (link => link.match(/\?id=([\d]+)/)?.[1])); for (const workshop of realworkshopVotes) { tasks.push(this.#voteUpWorkshop(workshop)); await delay(1e3); } } if (this.shouldProcessTask('curators', doTask)) { const realCurators = this.getRealParams('curators', curatorLinks, doTask, (link => link.match(/curator\/([\d]+)/)?.[1])); const realCuratorLikes = this.getRealParams('curatorLikes', curatorLikeLinks, doTask, (link => link.match(/https?:\/\/store\.steampowered\.com\/(.*?)\/([^/?]+)/)?.slice(1, 3).join('/'))); for (const curator of realCurators) { tasks.push(this.#toggleCurator(curator, doTask)); await delay(1e3); } for (const curatorLike of realCuratorLikes) { tasks.push(this.#toggleCuratorLike(curatorLike, doTask)); await delay(1e3); } } if (doTask && globalOptions.doTask.steam.announcements) { const realAnnouncements = this.getRealParams('announcements', announcementLinks, doTask, (link => { if (link.includes('store.steampowered.com')) { return link.match(/store\.steampowered\.com\/news\/app\/([\d]+)\/view\/([\d]+)/)?.slice(1, 3).join('/'); } return link.match(/steamcommunity\.com\/games\/([\d]+)\/announcements\/detail\/([\d]+)/)?.slice(1, 3).join('/'); })); for (const id of realAnnouncements) { tasks.push(this.#likeAnnouncement(id)); await delay(1e3); } } if (doTask && globalOptions.doTask.steam.licenses && licenseLinks.length > 0) { for (const ids of licenseLinks) { const [type, idsStr] = ids.split('-'); const idsArr = idsStr.split(','); for (const id of idsArr) { tasks.push(this.#addLicense(`${type}-${id}`)); await delay(1e3); } } } if (doTask && globalOptions.doTask.steam.playtests) { const realPlaytests = this.getRealParams('playtests', playtestLinks, doTask, (link => link.match(/app\/([\d]+)/)?.[1])); for (const id of realPlaytests) { tasks.push(this.#requestPlayTestAccess(id)); await delay(1e3); } } debug('开始执行所有任务'); const results = await Promise.all(tasks); this.#TaskExecutor.find((e => e instanceof SteamWeb))?.resetArea(); debug('所有任务执行完成', { success: results.every((result => result)) }); return results.every((result => result)); } catch (error) { debug('处理Steam任务时发生错误', { error: error }); throwError(error, 'Steam.toggle'); return false; } } shouldProcessTask(taskType, doTask) { debug('检查是否处理任务', { taskType: taskType, doTask: doTask }); if (doTask) { const result = globalOptions.doTask.steam[taskType]; debug('检查doTask配置', { taskType: taskType, result: result }); return globalOptions.doTask.steam[taskType]; } const undoTaskType = taskType; return undoTaskType in globalOptions.undoTask.steam && globalOptions.undoTask.steam[undoTaskType]; } #setCache(type, name, id) { try { this.#cache[type][name] = id; GM_setValue('steamCache', this.#cache); } catch (error) { throwError(error, 'SteamWeb.setCache'); } } #getTaskExecutionOrder(asfEnabled, steamWebEnabled, preferASF) { if (!asfEnabled) { return [ new SteamWeb ]; } if (!steamWebEnabled) { return [ new SteamASF(globalOptions.ASF) ]; } return preferASF ? [ new SteamASF(globalOptions.ASF), new SteamWeb ] : [ new SteamWeb, new SteamASF(globalOptions.ASF) ]; } } class Website { undoneTasks; socialTasks; giveawayId; socialInitialized={ discord: false, instagram: false, reddit: false, twitch: false, twitter: false, vk: false, youtube: false, steamStore: false, steamCommunity: false }; initialized=false; steamTaskType={ steamStore: false, steamCommunity: false }; social={}; async #bind(name, init) { try { debug('开始绑定社交媒体', { name: name }); const result = await init; debug('绑定结果', { name: name, result: result }); return { name: name, result: result }; } catch (error) { debug('绑定失败', { name: name, error: error }); throwError(error, 'Website.bind'); return { name: name, result: false }; } } async initSocial(action) { try { debug('开始初始化社交媒体', { action: action }); const pro = []; const tasks = action === 'do' ? this.undoneTasks : this.socialTasks; if (tasks.discord) { const hasDiscord = Object.values(tasks.discord).reduce(((total, arr) => [ ...total, ...arr ])).length > 0; debug('检查 Discord 任务', { hasDiscord: hasDiscord }); if (hasDiscord && (!this.socialInitialized.discord || !this.social.discord)) { debug('初始化 Discord'); this.social.discord = new Discord; pro.push(this.#bind('discord', this.social.discord.init(action))); } } if (tasks.reddit) { const hasReddit = Object.values(tasks.reddit).reduce(((total, arr) => [ ...total, ...arr ])).length > 0; debug('检查 Reddit 任务', { hasReddit: hasReddit }); if (hasReddit && (!this.socialInitialized.reddit || !this.social.reddit)) { debug('初始化 Reddit'); this.social.reddit = new Reddit; pro.push(this.#bind('reddit', this.social.reddit.init())); } } if (tasks.twitch) { const hasTwitch = Object.values(tasks.twitch).reduce(((total, arr) => [ ...total, ...arr ])).length > 0; debug('检查 Twitch 任务', { hasTwitch: hasTwitch }); if (hasTwitch && (!this.socialInitialized.twitch || !this.social.twitch)) { debug('初始化 Twitch'); this.social.twitch = new Twitch; pro.push(this.#bind('twitch', this.social.twitch.init())); } } if (tasks.twitter) { const hasTwitter = Object.values(tasks.twitter).reduce(((total, arr) => [ ...total, ...arr ])).length > 0; debug('检查 Twitter 任务', { hasTwitter: hasTwitter }); if (hasTwitter && (!this.socialInitialized.twitter || !this.social.twitter)) { debug('初始化 Twitter'); this.social.twitter = new Twitter; pro.push(this.#bind('twitter', this.social.twitter.init())); } } if (tasks.vk) { const hasVk = Object.values(tasks.vk).reduce(((total, arr) => [ ...total, ...arr ])).length > 0; debug('检查 VK 任务', { hasVk: hasVk }); if (hasVk && (!this.socialInitialized.vk || !this.social.vk)) { debug('初始化 VK'); this.social.vk = new Vk; pro.push(this.#bind('vk', this.social.vk.init())); } } if (tasks.youtube) { const hasYoutube = Object.values(tasks.youtube).reduce(((total, arr) => [ ...total, ...arr ])).length > 0; debug('检查 YouTube 任务', { hasYoutube: hasYoutube }); if (hasYoutube && (!this.socialInitialized.youtube || !this.social.youtube)) { debug('初始化 YouTube'); this.social.youtube = new Youtube; pro.push(this.#bind('youtube', this.social.youtube.init())); } } if (tasks.steam) { const steamLength = Object.values(tasks.steam).reduce(((total, arr) => [ ...total, ...arr ])).length; debug('检查 Steam 任务', { steamLength: steamLength }); if (steamLength > 0) { if (!this.social.steam) { debug('创建 Steam 实例'); this.social.steam = new Steam; } const steamCommunityLength = Object.keys(tasks.steam).map((type => [ 'groupLinks', 'officialGroupLinks', 'forumLinks', 'workshopLinks', 'workshopVoteLinks' ].includes(type) ? tasks.steam?.[type]?.length || 0 : 0)).reduce(((total, number) => total + number), 0); debug('Steam 社区任务数量', { steamCommunityLength: steamCommunityLength }); if (steamLength - steamCommunityLength > 0) { this.steamTaskType.steamStore = true; if (!this.socialInitialized.steamStore) { debug('初始化 Steam 商店'); pro.push(this.#bind('steamStore', this.social.steam.init('store'))); } } if (steamCommunityLength > 0) { if (!this.socialInitialized.steamCommunity) { this.steamTaskType.steamCommunity = true; debug('初始化 Steam 社区'); pro.push(this.#bind('steamCommunity', this.social.steam.init('community'))); } } } } if (tasks.links && tasks.links.length > 0) { debug('初始化链接访问', { linksCount: tasks.links.length }); this.social.visitLink = visitLink; } debug('等待所有社交媒体初始化完成'); return await Promise.all(pro).then((result => { let checked = true; for (const data of result) { if (data.result) { debug('社交媒体初始化成功', { name: data.name }); this.socialInitialized[data.name] = data.result; } else { debug('社交媒体初始化失败', { name: data.name }); checked = false; } } debug('社交媒体初始化完成', { allSuccess: checked }); return checked; })); } catch (error) { debug('初始化社交媒体失败', { error: error }); throwError(error, 'Website.initSocial'); return false; } } uniqueTasks(allTasks) { try { debug('开始去重任务'); const result = {}; for (const [social, types] of Object.entries(allTasks)) { debug('处理社交媒体任务', { social: social }); result[social] = {}; for (const [type, tasks] of Object.entries(types)) { debug('处理任务类型', { social: social, type: type }); result[social][type] = unique(tasks); } } debug('任务去重完成'); return result; } catch (error) { debug('任务去重失败', { error: error }); throwError(error, 'Website.uniqueTasks'); return allTasks; } } async toggleTask(action) { try { debug('开始切换任务状态', { action: action }); if (!this.initialized && !this.init()) { debug('初始化失败'); return false; } if (!await this.classifyTask(action)) { debug('任务分类失败'); return false; } debug('初始化社交媒体'); await this.initSocial(action); const pro = []; const doTask = action === 'do'; const tasks = doTask ? this.undoneTasks : this.socialTasks; if (this.socialInitialized.discord === true && this.social.discord) { debug('处理 Discord 任务'); pro.push(this.social.discord.toggle({ doTask: doTask, ...tasks.discord })); } if (this.socialInitialized.reddit === true && this.social.reddit) { debug('处理 Reddit 任务'); pro.push(this.social.reddit.toggle({ doTask: doTask, ...tasks.reddit })); } if (this.socialInitialized.twitch === true && this.social.twitch) { debug('处理 Twitch 任务'); pro.push(this.social.twitch.toggle({ doTask: doTask, ...tasks.twitch })); } if (this.socialInitialized.twitter === true && this.social.twitter) { debug('处理 Twitter 任务'); pro.push(this.social.twitter.toggle({ doTask: doTask, ...tasks.twitter })); } if (this.socialInitialized.vk === true && this.social.vk) { debug('处理 VK 任务'); pro.push(this.social.vk.toggle({ doTask: doTask, ...tasks.vk })); } if (this.socialInitialized.youtube === true && this.social.youtube) { debug('处理 YouTube 任务'); pro.push(this.social.youtube.toggle({ doTask: doTask, ...tasks.youtube })); } if ((this.steamTaskType.steamCommunity ? this.socialInitialized.steamCommunity === true : true) && (this.steamTaskType.steamStore ? this.socialInitialized.steamStore === true : true) && this.social.steam) { debug('处理 Steam 任务'); pro.push(this.social.steam.toggle({ doTask: doTask, ...tasks.steam })); } if (this.social.visitLink && tasks.links && doTask) { debug('处理链接任务', { linksCount: tasks.links.length }); for (const link of tasks.links) { pro.push(this.social.visitLink(link)); } } if (doTask && tasks.extra && this.extraDoTask) { const hasExtra = Object.values(tasks.extra).reduce(((total, arr) => [ ...total, ...arr ])).length > 0; if (hasExtra) { debug('处理额外任务'); pro.push(this.extraDoTask(tasks.extra)); } } debug('等待所有任务完成'); await Promise.all(pro); debug('所有任务完成'); echoLog({}).success(I18n('allTasksComplete')); return true; } catch (error) { debug('切换任务失败', { error: error }); throwError(error, 'Website.toggleTask'); return false; } } async doTask() { try { debug('开始执行任务'); const result = await this.toggleTask('do'); debug('任务执行完成', { success: result }); return result; } catch (error) { debug('执行任务失败', { error: error }); throwError(error, 'Website.doTask'); return false; } } async undoTask() { try { debug('开始撤销任务'); const result = await this.toggleTask('undo'); debug('任务撤销完成', { success: result }); return result; } catch (error) { debug('撤销任务失败', { error: error }); throwError(error, 'Website.undoTask'); return false; } } } const defaultTasksTemplate$6 = { steam: { groupLinks: [], wishlistLinks: [], curatorLinks: [], followLinks: [], playTimeLinks: [] }, discord: { serverLinks: [] }, vk: { nameLinks: [] }, youtube: { channelLinks: [] }, extra: { website: [] } }; const defaultTasks$8 = JSON.stringify(defaultTasksTemplate$6); class FreeAnyWhere extends Website { name='FreeAnyWhere'; tasks=[]; socialTasks=JSON.parse(defaultTasks$8); undoneTasks=JSON.parse(defaultTasks$8); games; buttons=[ 'doTask', 'undoTask', 'verifyTask', 'getKey' ]; static test() { const isMatch = window.location.host === 'freeanywhere.net'; debug('检查网站匹配', { host: window.location.host, isMatch: isMatch }); return isMatch; } async init() { try { debug('初始化 FreeAnyWhere', { url: window.location.href }); const logStatus = echoLog({ text: I18n('initing') }); debug('检测登录状态'); if ($('div.header__login a[href*=logout]').length === 0) { debug('未登录,准备跳转到登录页面'); window.open('https://freeanywhere.net/game.php?steam_login', '_self'); logStatus.warning(I18n('needLogin')); return false; } debug('检测是否为登录页面'); if (window.location.href.includes('/login')) { logStatus.warning(I18n('needLogin')); return false; } if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } const giveawayIdSuccess = this.#getGiveawayId(); debug('获取抽奖ID结果', { success: giveawayIdSuccess, id: this.giveawayId }); this.initialized = true; logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'Freeanywhere.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('获取已保存的任务信息'); this.socialTasks = GM_getValue(`fawTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$8); } const tasks = $('div.game__content-tasks__task').map(((index, element) => ({ id: $(element).attr('data-id'), social: $(element).find('div.task-img img').attr('alt'), link: $(element).find('div.task-link a').attr('href'), title: $(element).find('div.task-link').text().trim(), type: $(element).attr('data-type'), data: $(element).attr('data-data'), isSuccess: $(element).hasClass('done') }))).toArray(); debug('获取到的任务列表', { tasksCount: tasks.length, tasks: tasks }); if (tasks.length === 0) { logStatus.success(); return false; } if (action === 'verify') { this.tasks = []; } for (const task of tasks) { await this.#processTask(task, action); } logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.uniqueTasks(this.socialTasks); debug('任务分类结果', { undoneTasks: this.undoneTasks, socialTasks: this.socialTasks }); GM_setValue(`fawTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Freeanywhere.classifyTask'); return false; } } async #processTask(task, action) { try { debug('处理任务', { task: task, action: action }); const {id: id, social: social, title: title, type: type, link: link, data: data, isSuccess: isSuccess} = task; const taskInfo = { id: id, title: title, social: social, type: type, data: data }; if (action === 'verify' && !isSuccess) { debug('添加到验证任务列表', taskInfo); this.tasks.push(taskInfo); return; } debug('处理特定类型任务', { type: type, action: action, isSuccess: isSuccess }); switch (type) { case 'steam_account_verify': case 'site_email_verify': debug('跳过任务', { type: type }); break; case 'steam_game_sub': if (action === 'undo' && link) { this.socialTasks.steam.followLinks.push(link); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.steam.followLinks.push(link); } break; case 'steam_game_wishlist': if (action === 'undo' && link) { this.socialTasks.steam.wishlistLinks.push(link); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.steam.wishlistLinks.push(link); } break; case 'steam_group_sub': if (action === 'undo' && link) { this.socialTasks.steam.groupLinks.push(link); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.steam.groupLinks.push(link); } break; case 'site_visit': if (action === 'do' && !isSuccess) { this.undoneTasks.extra.website.push(`id=${id}&type=${type}&task=true`); } break; case 'vk_community_sub': if (action === 'undo' && link) { this.socialTasks.vk.nameLinks.push(link); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.vk.nameLinks.push(link); } break; case 'vk_post_like': if (action === 'undo' && link) { this.socialTasks.vk.nameLinks.push(`${link}&action=like`); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.vk.nameLinks.push(`${link}&action=like`); } break; case 'discord_server_sub': if (action === 'undo' && link) { this.socialTasks.discord.serverLinks.push(link); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.discord.serverLinks.push(link); } break; case 'youtube_channel_sub': if (action === 'undo' && link) { this.socialTasks.youtube.channelLinks.push(link); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.youtube.channelLinks.push(link); } break; case 'steam_game_playtime': if (action === 'undo' && link) { this.socialTasks.steam.playTimeLinks.push(`${title.match(/(\d+)\s*min/)?.[1] || '0'}-${link}`); } if (action === 'do' && !isSuccess && link) { this.undoneTasks.steam.playTimeLinks.push(`${title.match(/(\d+)\s*min/)?.[1] || '0'}-${link}`); } break; case 'telegram_channel_sub': debug('跳过 Telegram 任务'); echoLog({}).warning(`${I18n('tgTaskNotice')}`); break; case 'none': debug('跳过未连接的任务', { type: type }); echoLog({}).warning(`${I18n('notConnect', type)}`); break; default: debug('未知任务类型', { type: type }); echoLog({}).warning(`${I18n('unKnownTaskType', type)}`); break; } } catch (error) { debug('处理任务失败', { error: error }); throwError(error, 'FreeAnyWhere.processTask'); } } async verifyTask() { try { debug('开始验证任务'); if (!this.initialized && !await this.init()) { debug('未初始化'); return false; } if (this.tasks.length === 0 && !await this.classifyTask('verify')) { debug('任务列表为空', this.tasks); return false; } debug('开始验证任务列表', { tasks: this.tasks }); const pro = []; for (const task of this.tasks) { pro.push(this.#verify(task)); await delay(1e3); } const result = await Promise.allSettled(pro); debug('任务验证结果', { result: result }); echoLog({}).success(I18n('allTasksComplete')); if (result.every((item => item.status === 'fulfilled' && item.value === true))) { return !!await this.getKey(true); } return false; } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Freeanywhere.verifyTask'); return false; } } async extraDoTask({website: website}) { try { debug('执行额外任务', { website: website }); const promises = website.map((link => this.#doVisitWebsite(link))); const results = await Promise.allSettled(promises); debug('额外任务执行结果', { results: results }); return true; } catch (error) { debug('执行额外任务失败', { error: error }); throwError(error, 'FreeAnyWhere.extraDoTask'); return false; } } async #doVisitWebsite(link) { try { debug('访问网站', { link: link }); const logStatus = echoLog({ text: I18n('visitingLink') }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://freeanywhere.net/php/task_site_visit_done.php', method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: link }); if (result !== 'Success') { debug('访问失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.responseText.indexOf('bad') !== -1 || data?.responseText.length > 50) { debug('访问响应异常', { responseText: data?.responseText }); logStatus.error(data?.responseText); return false; } debug('访问成功'); logStatus.success(); return true; } catch (error) { debug('访问网站失败', { error: error }); throwError(error, 'FreeAnyWhere.doVisitWebsite'); return false; } } async getKey(initialized) { try { debug('开始获取密钥', { initialized: initialized }); if (!initialized && !this.initialized && !await this.init()) { debug('未初始化'); return false; } const logStatus = echoLog({ text: I18n('gettingKey') }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://freeanywhere.net/php/user_get_key.php', method: 'POST' }); if (result !== 'Success') { debug('获取密钥失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.responseText.indexOf('bad') !== -1 || data?.responseText.length > 50) { debug('密钥响应异常', { responseText: data?.responseText }); logStatus.error(data?.responseText); return false; } debug('获取密钥成功', { key: data.responseText }); logStatus.success(); echoLog({}).success(data.responseText); return data.responseText; } catch (error) { debug('获取密钥失败', { error: error }); throwError(error, 'FreeAnyWhere.getKey'); return false; } } async #verify(task) { try { if ($('.task-check-extension').length > 0) { return this.#verifyWithExtension(task); } return this.#verifyWithoutExtension(task); } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Freeanywhere.verify'); return false; } } async #verifyWithExtension(task) { try { await this.#updateUserData(); debug('验证任务', { task: task }); const logStatus = echoLog({ text: `${I18n('verifyingTask')}${task.title.trim()}...` }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://freeanywhere.net/php/extension/user_task_update.php', method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: `id=${task.id}&type=${task.type}${task.data && task.data !== 'none' ? `&data=${task.data}` : ''}` }); if (result !== 'Success' || !data?.responseText) { debug('验证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } const response = data.responseText.trim(); if (response !== 'good') { debug('验证响应异常', { response: response, statusText: data?.statusText, status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('验证成功'); logStatus.success(); return true; } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Freeanywhere.verifyWithExtension'); return false; } } async #verifyWithoutExtension(task) { try { debug('验证任务', { task: task }); const logStatus = echoLog({ text: `${I18n('verifyingTask')}${task.title.trim()}...` }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://freeanywhere.net/php/user_task_update.php', method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: `id=${task.id}&type=${task.type}${task.data && task.data !== 'none' ? `&data=${task.data}` : ''}` }); if (result !== 'Success' || !data?.responseText) { debug('验证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } const response = data.responseText.trim(); if (response !== 'good') { debug('验证响应异常', { response: response, statusText: data?.statusText, status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('验证成功'); logStatus.success(); return true; } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Freeanywhere.verifyWithoutExtension'); return false; } } async #updateUserData() { try { let postData = ''; const userData = GM_getValue('FAW_STORAGE') || {}; if (Object.keys(userData).length === 0 || !userData.tasks || !userData.user || !userData.games || !userData.settings) { if (!this.games) { await this.#userGamesGet(); } if (!this.games) { debug('获取用户游戏失败'); return false; } postData = `extension=${encodeURIComponent(JSON.stringify({ games: this.games, settings: { game_update: Math.floor(Date.now() / 1e3) }, tasks: {}, user: { avatar: $('header.games_for_farm_site').attr('data-avatar'), lang: $('header.games_for_farm_site').attr('data-lang'), name: $('header.games_for_farm_site').attr('data-name'), steam: $('header.games_for_farm_site').attr('data-steam') } }))}`; } else { postData = `extension=${encodeURIComponent(JSON.stringify(userData))}`; } debug('更新用户数据'); const logStatus = echoLog({ text: `${I18n('updatingUserData')}` }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://freeanywhere.net/php/extension/user_data_update.php', method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: postData }); if (data?.status !== 200) { debug('验证请求失败', { result: result, statusText: statusText, status: status, data: data }); logStatus.error(`${result}:${statusText}(${status})`); return false; } debug('验证成功'); logStatus.success(); return true; } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Freeanywhere.updateUserData'); return false; } } async #userGamesGet() { try { debug('获取用户游戏'); const logStatus = echoLog({ text: `${I18n('gettingUserGames')}` }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://freeanywhere.net/php/extension/user_games_get.php', method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: `steam=${$('header.games_for_farm_site').attr('data-steam')}`, dataType: 'json' }); if (result !== 'Success' || data?.status !== 200 || !data?.responseText) { debug('验证请求失败', { result: result, statusText: statusText, status: status, data: data }); logStatus.error(`${result}:${statusText}(${status})`); return false; } debug('验证成功'); this.games = data.response; logStatus.success(); return true; } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Freeanywhere.userGamesGet'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const giveawayStatus = $('div.card-info__lable-info').text()?.includes('Giveaway ended'); debug('Giveaway状态', { giveawayStatus: giveawayStatus }); if (!giveawayStatus) { return true; } debug('没有剩余密钥,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('giveawayEnded'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'FreeAnyWhere.checkLeftKey'); return false; } } #getGiveawayId() { try { debug('开始获取抽奖ID'); const giveawayId = $('link[rel="canonical"]').attr('href')?.match(/n=([\d]+)/)?.[1]; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({}).error(I18n('getFailed', 'GiveawayId')); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'FreeAnyWhere.getGiveawayId'); return false; } } } const defaultTasks$7 = { steam: { groupLinks: [], wishlistLinks: [], curatorLinks: [], curatorLikeLinks: [], followLinks: [], forumLinks: [], announcementLinks: [], workshopVoteLinks: [], playtestLinks: [], playTimeLinks: [] }, discord: { serverLinks: [] }, vk: { nameLinks: [] }, twitch: { channelLinks: [] }, reddit: { redditLinks: [] }, youtube: { channelLinks: [], likeLinks: [] }, twitter: { userLinks: [], retweetLinks: [] } }; class GiveawaySu extends Website { name='GiveawaySu'; socialTasks=defaultTasks$7; undoneTasks=defaultTasks$7; buttons=[ 'doTask', 'undoTask' ]; static test() { const url = window.location.href; const isMatch = /^https?:\/\/giveaway\.su\/giveaway\/view\/[\d]+/.test(url); debug('检查网站匹配', { url: url, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('登录检查失败'); echoLog({}).warning(I18n('checkLoginFailed')); } if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } debug('显示网站通知'); echoLog({}).warning(I18n('gsNotice')); } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'Giveawaysu.after'); } } init() { try { debug('初始化 GiveawaySu'); const logStatus = echoLog({ text: I18n('initing') }); if ($('a.steam-login').length > 0) { debug('发现未登录状态,重定向到 Steam 登录'); window.open('/steam/redirect', '_self'); logStatus.warning(I18n('needLogin')); return false; } const giveawayIdResult = this.#getGiveawayId(); if (!giveawayIdResult) { debug('获取抽奖ID失败'); return false; } this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'Giveawaysu.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('恢复已保存的任务信息'); this.socialTasks = GM_getValue(`gasTasks-${this.giveawayId}`)?.tasks || defaultTasks$7; return true; } const tasks = $('#actions tr'); if (!tasks.length) { debug('未找到任务'); logStatus.warning(I18n('noTasks')); return true; } debug('检查并处理 Discord 和 Twitch 绑定'); if ($('div.bind-discord').is(':visible')) { debug('点击 Discord 绑定按钮'); $('div.bind-discord a')[0]?.click(); } if ($('div.bind-twitch').is(':visible')) { debug('点击 Twitch 绑定按钮'); $('div.bind-twitch a')[0]?.click(); } const processTask = async task => { const td = $(task).find('td:not(".hidden")'); const colorfulTask = td.eq(1).find('a:not([data-trigger="link"])'); const colorlessTask = td.eq(2).find('a:not([data-trigger="link"])'); const taskDes = colorfulTask.length > 0 ? colorfulTask : colorlessTask; if (!taskDes.length) { debug('跳过无效任务'); return true; } const taskIcon = td.eq(0).find('i').attr('class') || ''; const taskName = taskDes.text().trim(); const taskHref = taskDes.attr('href'); debug('处理任务', { taskIcon: taskIcon, taskName: taskName, taskHref: taskHref }); if (taskIcon.includes('ban') || /disable adblock/gi.test(taskName)) { debug('跳过禁用任务'); return true; } if (!taskHref) { debug('任务链接为空'); return false; } try { debug('获取重定向链接'); const taskLink = await getRedirectLink(taskHref); if (!taskLink) { debug('获取重定向链接失败'); return false; } debug('分类任务', { taskLink: taskLink, taskIcon: taskIcon, taskName: taskName }); this.#classifyTaskByType(taskLink, taskIcon, taskName); return true; } catch (error) { debug('获取重定向链接失败', { error: error }); throwError(error, 'Giveawaysu.classifyTask->getRedirectLink'); return false; } }; debug('开始处理所有任务'); const results = await Promise.all(Array.from(tasks).map(processTask)); const success = results.some((result => result)); if (!success) { debug('所有任务处理失败'); logStatus.error(I18n('allTasksFailed')); return false; } debug('任务处理完成'); logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.undoneTasks; debug('保存任务信息'); GM_setValue(`gasTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Giveawaysu.classifyTask'); return false; } } static TASK_PATTERNS={ wishlist: /wishlist.*game|add.*wishlist/gim, follow: /follow.*button/gim, twitter: /(on twitter)|(Follow.*on.*Facebook)/gim, vkGroup: /join.*vk.*group/gim, youtubeVideo: /(watch|like).*video/gim, youtubeChannel: /subscribe.*youtube.*channel/gim, watchArt: /watch.*art/gim, reddit: /subscribe.*subreddit|follow.*reddit/gim, twitchChannel: /follow.*twitch.*channel/gim, instagram: /follow.*instagram/gim, discord: /join.*discord/gim, playtest: /request.*playtest/gim, steamForum: /subscribe.*steam.*forum/gim, curator: /(follow|subscribe).*curator/gim, curatorLink: /^https?:\/\/store\.steampowered\.com\/curator\//, announcement: /like.*announcement/gim, steamGroup: /join/gi }; #classifyTaskByType(taskLink, taskIcon, taskName) { try { debug('开始分类任务', { taskLink: taskLink, taskIcon: taskIcon, taskName: taskName }); const {TASK_PATTERNS: TASK_PATTERNS} = GiveawaySu; if (taskIcon.includes('steam') && TASK_PATTERNS.steamGroup.test(taskName)) { debug('添加 Steam 组任务'); this.undoneTasks.steam.groupLinks.push(taskLink); return; } if (TASK_PATTERNS.announcement.test(taskName)) { debug('添加 Steam 公告任务'); this.undoneTasks.steam.announcementLinks.push(taskLink); return; } if (TASK_PATTERNS.curator.test(taskName) && TASK_PATTERNS.curatorLink.test(taskLink)) { debug('添加 Steam 鉴赏家关注任务'); this.undoneTasks.steam.curatorLinks.push(taskLink); return; } if (taskIcon.includes('steam') && /follow|subscribe/gim.test(taskName)) { debug('添加 Steam 鉴赏家点赞任务'); this.undoneTasks.steam.curatorLikeLinks.push(taskLink); return; } if (TASK_PATTERNS.steamForum.test(taskName)) { debug('添加 Steam 论坛任务'); this.undoneTasks.steam.forumLinks.push(taskLink); return; } if (taskIcon.includes('thumbs-up') && /^https?:\/\/steamcommunity\.com\/sharedfiles\/filedetails\/\?id=[\d]+/.test(taskLink)) { debug('添加 Steam 创意工坊投票任务'); this.undoneTasks.steam.workshopVoteLinks.push(taskLink); return; } if (taskIcon.includes('plus') && TASK_PATTERNS.playtest.test(taskName)) { debug('添加 Steam 游戏测试任务'); this.undoneTasks.steam.playtestLinks.push(taskLink); return; } if (taskIcon.includes('discord') || TASK_PATTERNS.discord.test(taskName)) { debug('添加 Discord 服务器任务'); this.undoneTasks.discord.serverLinks.push(taskLink); return; } if (taskIcon.includes('instagram') || TASK_PATTERNS.instagram.test(taskName)) { debug('跳过 Instagram 任务'); return; } if (taskIcon.includes('twitch') || TASK_PATTERNS.twitchChannel.test(taskName)) { debug('添加 Twitch 频道任务'); this.undoneTasks.twitch.channelLinks.push(taskLink); return; } if (taskIcon.includes('reddit') || TASK_PATTERNS.reddit.test(taskName)) { debug('添加 Reddit 任务'); this.undoneTasks.reddit.redditLinks.push(taskLink); return; } if (TASK_PATTERNS.watchArt.test(taskName)) { debug('添加创意工坊物品任务'); this.undoneTasks.steam.workshopVoteLinks.push(taskLink); return; } if (TASK_PATTERNS.youtubeChannel.test(taskName)) { debug('添加 YouTube 频道任务'); this.undoneTasks.youtube.channelLinks.push(taskLink); return; } if (TASK_PATTERNS.youtubeVideo.test(taskName) || (taskIcon.includes('youtube') || taskIcon.includes('thumbs-up')) && TASK_PATTERNS.youtubeVideo.test(taskName)) { debug('添加 YouTube 视频任务'); this.undoneTasks.youtube.likeLinks.push(taskLink); return; } if (taskIcon.includes('vk') || TASK_PATTERNS.vkGroup.test(taskName)) { debug('添加 VK 任务'); this.undoneTasks.vk.nameLinks.push(taskLink); return; } if (TASK_PATTERNS.twitter.test(taskName)) { debug('跳过 Twitter 任务'); return; } if (TASK_PATTERNS.wishlist.test(taskName)) { debug('添加 Steam 愿望单任务'); this.undoneTasks.steam.wishlistLinks.push(taskLink); } if (TASK_PATTERNS.follow.test(taskName)) { debug('添加 Steam 关注任务'); this.undoneTasks.steam.followLinks.push(taskLink); return; } debug('未识别的任务类型', { taskLink: taskLink, taskIcon: taskIcon, taskName: taskName }); } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Giveawaysu.classifyTaskByType'); } } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } const needLogin = $('a.steam-login').length > 0; if (needLogin) { debug('未登录,重定向到 Steam 登录'); window.open('/steam/redirect', '_self'); } debug('登录检查完成', { needLogin: needLogin }); return true; } catch (error) { debug('登录检查失败', { error: error }); throwError(error, 'Giveawaysu.checkLogin'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const isEnded = $('.giveaway-ended').length > 0; const hasNoKeys = $('.giveaway-key').length === 0; debug('检查抽奖状态', { isEnded: isEnded, hasNoKeys: hasNoKeys }); if (!(isEnded && hasNoKeys)) { return true; } debug('没有剩余密钥,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('noKeysLeft'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'Giveawaysu.checkLeftKey'); return false; } } #getGiveawayId() { try { debug('从URL获取抽奖ID'); const giveawayId = window.location.href.match(/\/view\/([\d]+)/)?.[1]; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({ text: I18n('getFailed', 'GiveawayId') }); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'Giveawaysu.getGiveawayId'); return false; } } } class Indiedb { name='Indiedb'; buttons=[ 'doTask' ]; static test() { const {host: host} = window.location; const isMatch = host === 'www.indiedb.com'; debug('检查网站匹配', { host: host, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('检查登录失败'); echoLog({}).warning(I18n('checkLoginFailed')); } if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'Indiedb.after'); } } async doTask() { try { debug('开始执行任务'); if (!await this.#join()) { debug('加入抽奖失败'); return false; } return await this.#do(); } catch (error) { debug('执行任务失败', { error: error }); throwError(error, 'Indiedb.doTask'); return false; } } async #join() { try { debug('开始加入抽奖'); if ($('a.buttonenter:contains(Register to join)').length > 0) { debug('需要登录'); echoLog({}).error(I18n('needLogin')); return false; } const currentoption = $('a.buttonenter.buttongiveaway'); const buttonText = currentoption.text(); debug('检查按钮状态', { buttonText: buttonText }); if (/success/gim.test($('a.buttonenter.buttongiveaway').text())) { debug('已成功加入抽奖'); return true; } if (!/join giveaway/gim.test(buttonText)) { debug('需要加入抽奖'); echoLog({}).warning(I18n('needJoinGiveaway')); return false; } const logStatus = echoLog({ text: `${I18n('joiningGiveaway')}...` }); debug('发送加入请求'); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: currentoption.attr('href'), method: 'POST', data: 'ajax=t', dataType: 'json', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Accept: 'application/json, text/javascript, */*; q=0.01', Origin: window.location.origin, referer: window.location.href } }); if (result !== 'Success') { debug('请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('尝试备用加入方法'); if (await this.#join2()) { debug('备用加入方法成功'); logStatus.success('Success'); return true; } debug('加入失败', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } if (!data.response?.success) { debug('响应失败', { text: data.response?.text }); logStatus.error(`Error${data.response?.text ? `:${data.response?.text}` : ''}`); return false; } debug('加入成功'); currentoption.addClass('buttonentered').text('Success - Giveaway joined'); $('#giveawaysjoined').slideDown(); $('#giveawaysrecommend').slideDown(); logStatus.success(`Success${data.response?.text ? `:${data.response?.text}` : ''}`); return true; } catch (error) { debug('加入抽奖失败', { error: error }); throwError(error, 'Indiedb.join'); return false; } } async #join2() { try { debug('开始备用加入方法'); return await new Promise((resolve => { const targetNode = document.getElementById('giveawaysjoined'); const config = { attributes: true }; const observer = new MutationObserver((() => { if ($('#giveawaysjoined').is(':visible')) { debug('检测到加入成功'); resolve(true); observer.disconnect(); } })); observer.observe(targetNode, config); debug('点击加入按钮'); $('a.buttonenter.buttongiveaway')[0]?.click(); setTimeout((() => { debug('加入超时'); resolve(false); observer.disconnect(); }), 3e4); })); } catch (error) { debug('备用加入方法失败', { error: error }); throwError(error, 'Indiedb.join2'); return false; } } async #do() { try { debug('开始执行任务'); const id = $('script').map(((index, script) => { if (!/\$\(document\)/gim.test(script.innerHTML)) { return null; } return [ script.innerHTML.match(/"\/[\d]+"/gim)?.[0]?.match(/[\d]+/)?.[0], script.innerHTML.match(/"\/newsletter\/ajax\/subscribeprofile\/optin\/[\d]+"/gim)?.[0]?.match(/[\d]+/)?.[0] ]; })); if (id.length < 2) { debug('获取任务ID失败'); echoLog({}).error(I18n('getFailed', 'TaskId')); return false; } const pro = []; const tasks = $('#giveawaysjoined a[class*=promo]'); debug('找到任务', { count: tasks.length }); for (const task of tasks) { const promo = $(task); if (promo.hasClass('buttonentered')) { debug('跳过已完成任务'); continue; } const taskText = promo.parents('p').text(); debug('处理任务', { taskText: taskText }); const status = echoLog({ text: `${I18n('doing')}:${taskText}...` }); if (/the-challenge-of-adblock/gim.test(promo.attr('href'))) { debug('跳过未知任务类型'); status.error(`Error:${I18n('unKnownTaskType')}`); continue; } if (/facebookpromo|twitterpromo|visitpromo/gim.test(task.className)) { let text = ''; if (promo.hasClass('facebookpromo')) { text = 'facebookpromo'; } else if (promo.hasClass('twitterpromo')) { text = 'twitterpromo'; } else { text = 'visitpromo'; } debug('处理社交媒体任务', { type: text }); pro.push(this.#handleSocialPromo(text, id[0], status, promo)); } else if (promo.hasClass('emailoptinpromo')) { debug('处理邮件订阅任务'); pro.push(this.#handleEmailPromo(id[1], status, promo)); } else if (promo.hasClass('watchingpromo')) { debug('处理关注任务'); pro.push(this.#handleWatchingPromo(promo, status)); } else { debug('处理默认任务'); pro.push(this.#handleDefaultPromo(promo, status)); } } await Promise.all(pro); debug('所有任务完成'); echoLog({}).success(I18n('allTasksComplete')); return true; } catch (error) { debug('执行任务失败', { error: error }); throwError(error, 'Indiedb.do'); return false; } } async #handleSocialPromo(text, id, status, promo) { try { debug('处理社交媒体任务', { text: text, id: id }); return await new Promise((resolve => { $.ajax({ type: 'POST', url: urlPath(`/giveaways/ajax/${text}/${id}`), timeout: 6e4, dataType: 'json', data: { ajax: 't' }, error(response, error, exception) { debug('请求失败', { response: response, error: error, exception: exception }); if (window.DEBUG) { console.log('%cAuto-Task[Debug]:', 'color:red', { response: response, error: error, exception: exception }); } status.error('Error:An error has occurred performing the action requested. Please try again shortly.'); resolve(true); }, success(response) { if (response.success) { debug('任务完成', { response: response }); status.success(`Success:${response.text}`); promo.addClass('buttonentered').closest('p').html(promo.closest('p').find('span').html()); } else { debug('任务失败', { response: response }); status.error(`Error:${response.text}`); } resolve(true); } }); })); } catch (error) { debug('处理社交媒体任务失败', { error: error }); throwError(error, 'Indiedb.handleSocialPromo'); return false; } } async #handleEmailPromo(id, status, promo) { try { debug('处理邮件订阅任务', { id: id }); return await new Promise((resolve => { $.ajax({ type: 'POST', url: urlPath(`/newsletter/ajax/subscribeprofile/optin/${id}`), timeout: 6e4, dataType: 'json', data: { ajax: 't', emailsystoggle: 4 }, error(response, error, exception) { debug('请求失败', { response: response, error: error, exception: exception }); if (window.DEBUG) { console.log('%cAuto-Task[Debug]:', 'color:red', { response: response, error: error, exception: exception }); } status.error('Error:An error has occurred performing the action requested. Please try again shortly.'); resolve(true); }, success(response) { if (response.success) { debug('任务完成', { response: response }); status.success(`Success:${response.text}`); promo.toggleClass('buttonentered').closest('p').html(promo.closest('p').find('span').html()); } else { debug('任务失败', { response: response }); status.error(`Error:${response.text}`); } resolve(true); } }); })); } catch (error) { debug('处理邮件订阅任务失败', { error: error }); throwError(error, 'Indiedb.handleEmailPromo'); return false; } } async #handleWatchingPromo(promo, status) { try { debug('处理关注任务'); return await new Promise((resolve => { const href = promo.attr('href'); if (!href) { debug('无效的链接'); status.error('Error: Invalid href'); resolve(true); return; } const data = getUrlQuery(href); data.ajax = 't'; const [baseUrl] = href.split(/[?#]/); if (!baseUrl) { debug('无效的URL'); status.error('Error: Invalid URL'); resolve(true); return; } debug('发送请求', { url: baseUrl, data: data }); $.ajax({ type: 'POST', url: urlPath(baseUrl), timeout: 6e4, dataType: 'json', data: data, error(response, error, exception) { debug('请求失败', { response: response, error: error, exception: exception }); if (window.DEBUG) { console.log('%cAuto-Task[Debug]:', 'color:red', { response: response, error: error, exception: exception }); } status.error('Error:An error has occurred performing the action requested. Please try again shortly.'); resolve(true); }, success(response) { if (response.success) { debug('任务完成', { response: response }); status.success(`Success:${response.text}`); promo.toggleClass('buttonentered').closest('p').html(promo.closest('p').find('span').html()); } else { debug('任务失败', { response: response }); status.error(`Error:${response.text}`); } resolve(true); } }); })); } catch (error) { debug('处理关注任务失败', { error: error }); throwError(error, 'Indiedb.handleWatchingPromo'); return false; } } async #handleDefaultPromo(promo, status) { try { debug('处理默认任务'); return await new Promise((resolve => { const href = promo.attr('href'); if (!href) { debug('无效的链接'); status.error('Error: Invalid href'); resolve(true); return; } debug('发送请求', { url: href }); $.ajax({ type: 'POST', url: urlPath(href), timeout: 6e4, dataType: 'json', data: { ajax: 't' }, error(response, error, exception) { debug('请求失败', { response: response, error: error, exception: exception }); if (window.DEBUG) { console.log('%cAuto-Task[Debug]:', 'color:red', { response: response, error: error, exception: exception }); } status.error('Error:An error has occurred performing the action requested. Please try again shortly.'); resolve(true); }, success(response) { if (response.success) { debug('任务完成', { response: response }); status.success(`Success:${response.text}`); promo.toggleClass('buttonentered').closest('p').html(promo.closest('p').find('span').html()); } else { debug('任务失败', { response: response }); status.error(`Error:${response.text}`); } resolve(true); } }); })); } catch (error) { debug('处理默认任务失败', { error: error }); throwError(error, 'Indiedb.handleDefaultPromo'); return false; } } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } if ($('a.buttonenter:contains(Register to join)').length > 0) { debug('未登录,重定向到登录页面'); window.open('/members/login', '_self'); } debug('登录检查完成'); return true; } catch (error) { debug('检查登录失败', { error: error }); throwError(error, 'Indiedb.checkLogin'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const hasEndedButton = $('a.buttonenter:contains("next time"), a.buttonenter:contains("Giveaway is closed")').length > 0; debug('检查抽奖状态', { hasEndedButton: hasEndedButton }); if (!hasEndedButton) { return true; } debug('抽奖已结束,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('giveawayEnded'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'Indiedb.checkLeftKey'); return false; } } } const defaultTasksTemplate$5 = { steam: { groupLinks: [], officialGroupLinks: [], wishlistLinks: [], curatorLinks: [] }, discord: { serverLinks: [] }, extra: { videoTasks: [] }, links: [] }; const defaultTasks$6 = JSON.stringify(defaultTasksTemplate$5); class Keyhub extends Website { name='Keyhub'; socialTasks=JSON.parse(defaultTasks$6); undoneTasks=JSON.parse(defaultTasks$6); buttons=[ 'doTask', 'undoTask' ]; static test() { const {host: host} = window.location; const isMatch = host === 'key-hub.eu'; debug('检查网站匹配', { host: host, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('检查登录失败'); echoLog({}).warning(I18n('checkLoginFailed')); } if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } debug('隐藏 NSFW 内容'); $('.NSFW').hide(); } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'Keyhub.after'); } } init() { try { debug('开始初始化'); const logStatus = echoLog({ text: I18n('initing') }); if ($('a[href*="/connect/steam"]').length > 0) { debug('需要登录 Steam'); window.open('/connect/steam', '_self'); logStatus.warning(I18n('needLogin')); return false; } if (!this.#getGiveawayId()) { debug('获取抽奖ID失败'); return false; } debug('隐藏 VPN 覆盖层'); $('#VPNoverlay').hide(); $('#mainArticleSection').show(); this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'Keyhub.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('恢复已保存的任务信息'); this.socialTasks = GM_getValue(`khTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$6); } const tasks = $('.task:not(".googleads")').filter(((index, element) => action === 'do' ? $(element).find('i.fa-check-circle:visible').length === 0 : true)).find('a'); debug('找到任务', { count: tasks.length }); for (const task of tasks) { let link = $(task).attr('href'); const taskDes = $(task).text().trim(); debug('处理任务', { taskDes: taskDes, link: link }); if (!link) { debug('跳过无链接任务'); continue; } if (/\/away\?data=/.test(link) || /steamcommunity\.com\/gid\//.test(link)) { debug('获取重定向链接'); link = await getRedirectLink(link) || link; } if (/https?:\/\/key-hub\.eu\/connect\/discord/.test(link)) { debug('处理 Discord 连接任务'); GM_openInTab(link, { active: true }); continue; } if (/steamcommunity\.com\/groups\//.test(link)) { debug('处理 Steam 组任务'); if (action === 'undo') { this.socialTasks.steam.groupLinks.push(link); } if (action === 'do') { this.undoneTasks.steam.groupLinks.push(link); } continue; } if (/steamcommunity\.com\/games\/[\d]+/.test(link)) { debug('处理 Steam 官方组任务'); if (action === 'undo') { this.socialTasks.steam.officialGroupLinks.push(link); } if (action === 'do') { this.undoneTasks.steam.officialGroupLinks.push(link); } continue; } if (/store\.steampowered\.com\/app\//.test(link) && /wishlist/gim.test(taskDes)) { debug('处理 Steam 愿望单任务'); if (action === 'undo') { this.socialTasks.steam.wishlistLinks.push(link); } if (action === 'do') { this.undoneTasks.steam.wishlistLinks.push(link); } continue; } if (/store\.steampowered\.com\/curator\//.test(link)) { debug('处理 Steam 鉴赏家任务'); if (action === 'undo') { this.socialTasks.steam.curatorLinks.push(link); } if (action === 'do') { this.undoneTasks.steam.curatorLinks.push(link); } continue; } if (/^https?:\/\/discord\.com\/invite\//.test(link)) { debug('处理 Discord 服务器任务'); if (action === 'undo') { this.socialTasks.discord.serverLinks.push(link); } if (action === 'do') { this.undoneTasks.discord.serverLinks.push(link); } continue; } if (/^javascript:videoTask.+/.test(link)) { debug('处理视频任务'); if (action === 'do') { const taskData = link.match(/javascript:videoTask\('.+?','(.+?)'/)?.[1]; if (taskData) { debug('添加视频任务', { taskData: taskData }); this.undoneTasks.extra.videoTasks.push(taskData); } } continue; } if (this.#isSkippableLink(link)) { debug('跳过可忽略的链接', { link: link }); continue; } debug('未知任务类型', { taskDes: taskDes, link: link }); echoLog({}).warning(`${I18n('unKnownTaskType')}: ${taskDes}(${link})`); } debug('任务分类完成'); logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.uniqueTasks(this.socialTasks); if (window.DEBUG) { console.log('%cAuto-Task[Debug]:', 'color:blue', JSON.stringify(this)); } debug('保存任务信息'); GM_setValue(`khTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Keyhub.classifyTask'); return false; } } #isSkippableLink(link) { return /^https?:\/\/www\.instagram\.com\/.*/.test(link) || /^https?:\/\/twitter\.com\/.*/.test(link) || /^https?:\/\/www\.twitch\.tv\/.*/.test(link) || /^https?:\/\/www\.facebook\.com\/.*/.test(link) || /^https?:\/\/www\.youtube\.com\/.*/.test(link) || /^https?:\/\/store\.steampowered\.com\/developer\//.test(link) || /^https?:\/\/.*?\.itch\.io\/.*/.test(link) || /^https?:\/\/key-hub\.eu.*/.test(link) || /^https?:\/\/store\.steampowered\.com\/app\/.*/.test(link) || /^https?:\/\/qr\.streamelements\.com\/.*/.test(link) || /^https?:\/\/store\.steampowered\.com\/news\/app\/.*/.test(link); } async #doScriptTask(data) { try { debug('执行脚本任务', { data: data }); const logStatus = echoLog({ text: I18n('doingKeyhubTask') }); const {result: result, statusText: statusText, status: status, data: response} = await httpRequest({ url: `/away?data=${data}`, method: 'GET', headers: { origin: 'https://key-hub.eu', referer: 'https://key-hub.eu/' } }); if (result !== 'Success') { debug('请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (response?.status !== 200) { debug('响应错误', { status: response?.status, statusText: response?.statusText }); logStatus.error(`Error:${response?.statusText}(${response?.status})`); return false; } debug('任务完成'); logStatus.success(); return true; } catch (error) { debug('执行脚本任务失败', { error: error }); throwError(error, 'Keyhub.doScriptTask'); return false; } } async extraDoTask({videoTasks: videoTasks}) { try { debug('开始执行额外任务', { count: videoTasks.length }); const pro = []; for (const data of videoTasks) { pro.push(this.#doScriptTask(data)); } return Promise.all(pro).then((() => { debug('所有额外任务完成'); return true; })); } catch (error) { debug('执行额外任务失败', { error: error }); throwError(error, 'Keyhub.extraDoTask'); return false; } } #getGiveawayId() { try { debug('获取抽奖ID'); const giveawayId = window.location.href.match(/giveaway\/([\d]+)/)?.[1]; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({}).error(I18n('getFailed', 'GiveawayId')); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'Keyhub.getGiveawayId'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const leftKey = $('#keysleft').text().trim(); debug('检查剩余密钥数量', { leftKey: leftKey }); if (leftKey !== '0') { return true; } debug('没有剩余密钥,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('noKeysLeft'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'Keyhub.checkLeftKey'); return false; } } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } if ($('a[href*="/connect/steam"]').length > 0) { debug('未登录,重定向到 Steam 登录页面'); window.open('/connect/steam', '_self'); } debug('登录检查完成'); return true; } catch (error) { debug('检查登录失败', { error: error }); throwError(error, 'Keyhub.checkLogin'); return false; } } } const defaultTasksTemplate$4 = { steam: { groupLinks: [], wishlistLinks: [], curatorLinks: [], curatorLikeLinks: [] }, twitter: { userLinks: [] }, vk: { nameLinks: [] }, discord: { serverLinks: [] } }; const defaultTasks$5 = JSON.stringify(defaultTasksTemplate$4); class Givekey extends Website { name='Givekey'; tasks=[]; socialTasks=JSON.parse(defaultTasks$5); undoneTasks=JSON.parse(defaultTasks$5); userId; buttons=[ 'doTask', 'undoTask', 'verifyTask' ]; static test() { const url = window.location.host; const isMatch = url === 'givekey.ru'; debug('检查网站匹配', { url: url, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); await new Promise((resolve => { const checker = setInterval((() => { if ($('#navbarDropdown').length > 0) { debug('导航栏元素已加载'); clearInterval(checker); resolve(true); } }), 500); })); if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'Givekey.after'); } } init() { try { debug('初始化 Givekey'); const logStatus = echoLog({ text: I18n('initing') }); if ($('a[href*="/auth/steam"]').length > 0) { debug('未登录,重定向到 Steam 登录页面'); window.open('/auth/steam', '_self'); logStatus.warning(I18n('needLogin')); return false; } if (!this.#getGiveawayId()) { debug('获取抽奖ID失败'); return false; } const userId = $('meta[name="user-id"]').attr('content'); if (!userId) { debug('获取用户ID失败'); logStatus.error(I18n('getFailed', I18n('userId'))); return false; } this.userId = userId; this.initialized = true; debug('初始化完成', { userId: userId }); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'Givekey.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('恢复已保存的任务信息'); this.socialTasks = GM_getValue(`gkTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$5); } const tasks = $('.card-body:has("button") .row'); debug('找到任务元素', { count: tasks.length }); for (const task of tasks) { const taskEle = $(task); const button = taskEle.find('button'); const isSuccess = /Complete/i.test(button.text().trim()); debug('处理任务', { isSuccess: isSuccess }); if (isSuccess && action !== 'undo') { debug('跳过已完成的任务'); continue; } const checkButton = taskEle.find('#task_check'); const taskId = checkButton.attr('data-id'); if (taskId) { debug('添加任务ID', { taskId: taskId }); this.tasks.push(taskId); } if (action === 'verify') { continue; } const taskLink = taskEle.find('a'); let href = taskLink.attr('href'); if (!href) { debug('任务链接为空'); continue; } const text = taskLink.text().trim(); if (!text) { debug('任务描述为空'); continue; } if (/^https?:\/\/givekey\.ru\/giveaway\/[\d]+\/execution_task/.test(href)) { debug('获取重定向链接', { href: href }); href = await getRedirectLink(href); } if (!href) { debug('获取重定向链接失败'); continue; } const icon = taskEle.find('i'); await this.#classifyTaskByType(href, text, icon, isSuccess, action); } debug('任务分类完成'); logStatus.success(); this.tasks = unique(this.tasks); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.uniqueTasks(this.socialTasks); debug('保存任务信息'); GM_setValue(`gkTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Givekey.classifyTask'); return false; } } async verifyTask() { try { debug('开始验证任务'); if (!this.initialized && !this.init()) { debug('初始化失败'); return false; } if (this.tasks.length === 0 && !await this.classifyTask('verify')) { debug('任务分类失败'); return false; } echoLog({}).warning(I18n('giveKeyNoticeBefore')); const taskLength = this.tasks.length; debug('开始验证任务', { taskCount: taskLength }); for (let i = 0; i < taskLength; i++) { await this.#verify(this.tasks[i]); if (i < taskLength - 1) { debug('等待15秒'); await delay(15e3); } } debug('所有任务验证完成'); echoLog({}).success(I18n('allTasksComplete')); echoLog({ html: `<li><font class="warning">${I18n('giveKeyNoticeAfter')}</font></li>` }); return true; } catch (error) { debug('任务验证失败', { error: error }); throwError(error, 'Givekey.verifyTask'); return false; } } async #verify(task) { try { debug('验证任务', { taskId: task }); const logStatus = echoLog({ html: `<li>${I18n('verifyingTask')}${task}...<font></font></li>` }); const csrfToken = $('meta[name="csrf-token"]').attr('content'); if (!csrfToken) { debug('CSRF token 未找到'); logStatus.error('CSRF token not found'); return false; } debug('发送验证请求'); const response = await $.ajax({ url: 'https://givekey.ru/giveaway/task', method: 'POST', data: `id=${task}&user_id=${this.userId}`, dataType: 'json', headers: { 'X-CSRF-TOKEN': csrfToken } }); if (!response) { debug('未收到响应'); logStatus.error('No response received'); return false; } debug('处理响应', { response: response }); if (response.btn) { $(`button[data-id=${this.userId}]`).html(response.btn); } if (response.status === 'ok') { $(`.task_check_${response.id}`).html(`<button class="btn btn-success mb-2 btn-block" disabled>${response.btn}</button>`); debug('任务验证成功'); logStatus.success(); return true; } if (response.status === 'end') { debug('获得密钥'); logStatus.success(); echoLog({}).success(response.key); return true; } debug('验证失败', { error: response.msg }); logStatus.error(`Error:${response.msg}`); return false; } catch (error) { debug('验证过程出错', { error: error }); throwError(error, 'Givekey.verify'); return false; } } #getGiveawayId() { try { debug('从URL获取抽奖ID'); const giveawayId = window.location.href.match(/giveaway\/([\d]+)/)?.[1]; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({ text: I18n('getFailed', 'GiveawayId') }); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'Givekey.getGiveawayId'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const keysCount = $('#keys_count').text(); debug('检查密钥数量', { keysCount: keysCount }); if (keysCount) { return true; } debug('没有剩余密钥,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('noKeysLeft'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'Givekey.checkLeftKey'); return false; } } async #classifyTaskByType(href, text, icon, isSuccess, action) { try { debug('开始分类任务类型', { href: href, text: text, isSuccess: isSuccess, action: action }); if (/^https?:\/\/vk\.com\//.test(href)) { debug('添加 VK 任务'); this.socialTasks.vk.nameLinks.push(href); if (action === 'do' && !isSuccess) { this.undoneTasks.vk.nameLinks.push(href); } return; } if (/^https?:\/\/steamcommunity\.com\/groups/.test(href)) { debug('添加 Steam 组任务'); this.socialTasks.steam.groupLinks.push(href); if (action === 'do' && !isSuccess) { this.undoneTasks.steam.groupLinks.push(href); } return; } if (/^https?:\/\/store\.steampowered\.com\/app\//.test(href)) { debug('添加 Steam 愿望单任务'); this.socialTasks.steam.wishlistLinks.push(href); if (action === 'do' && !isSuccess) { this.undoneTasks.steam.wishlistLinks.push(href); } return; } if (/Subscribe/gi.test(text) && icon.hasClass('fa-steam-square')) { if (/^https?:\/\/store\.steampowered\.com\/curator\//.test(href)) { debug('添加 Steam 鉴赏家关注任务'); this.socialTasks.steam.curatorLinks.push(href); if (action === 'do' && !isSuccess) { this.undoneTasks.steam.curatorLinks.push(href); } } else { debug('添加 Steam 鉴赏家点赞任务'); this.socialTasks.steam.curatorLikeLinks.push(href); if (action === 'do' && !isSuccess) { this.undoneTasks.steam.curatorLikeLinks.push(href); } } return; } if (/^https?:\/\/twitter\.com\//.test(href) && /Subscribe/gi.test(text)) { debug('添加 Twitter 关注任务'); this.socialTasks.twitter.userLinks.push(href); if (action === 'do' && !isSuccess) { this.undoneTasks.twitter.userLinks.push(href); } return; } if (icon.hasClass('fa-discord') || /^https?:\/\/discord\.com\/invite\//.test(href)) { debug('添加 Discord 服务器任务'); this.socialTasks.discord.serverLinks.push(href); if (action === 'do' && !isSuccess) { this.undoneTasks.discord.serverLinks.push(href); } return; } debug('未识别的任务类型', { href: href, text: text }); echoLog({}).warning(`${I18n('unKnownTaskType')}: ${text}(${href})`); } catch (error) { debug('任务类型分类失败', { error: error }); throwError(error, 'Givekey.classifyTaskByType'); } } } class GiveeClub extends GiveawaySu { name='GiveeClub'; buttons=[ 'doTask', 'undoTask', 'verifyTask' ]; static test() { const url = window.location.href; const isMatch = /^https?:\/\/givee\.club\/.*?\/event\/[\d]+/.test(url); debug('检查网站匹配', { url: url, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('登录检查失败'); echoLog({}).warning(I18n('checkLoginFailed')); } if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'GiveeClub.after'); } } init() { try { debug('初始化 GiveeClub'); const logStatus = echoLog({ text: I18n('initing') }); if (!this.#checkLogin()) { debug('登录检查失败'); logStatus.warning(I18n('needLogin')); return false; } const giveawayIdResult = this.#getGiveawayId(); if (!giveawayIdResult) { debug('获取抽奖ID失败'); return false; } this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'GiveeClub.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('恢复已保存的任务信息'); this.socialTasks = GM_getValue(`gcTasks-${this.giveawayId}`)?.tasks || defaultTasks$7; return true; } debug('初始化未完成任务列表'); this.undoneTasks = defaultTasks$7; const tasks = $('.event-actions tr'); const processTask = async task => { const taskDes = $(task).find('.event-action-label a'); const taskIcon = $(task).find('.event-action-icon i').attr('class') || ''; const taskName = taskDes.text().trim(); const taskType = $(task).find('button[data-type]')?.attr('data-type') || ''; const taskFinished = $(task).find('.event-action-buttons .btn-success')?.length; const appId = taskDes.attr('data-steam-wishlist-appid'); debug('处理任务', { taskName: taskName, taskType: taskType, taskIcon: taskIcon, taskFinished: taskFinished, appId: appId }); if (taskIcon.includes('ban') || /AdBlock/i.test(taskName) || taskIcon.includes('envelope') || taskFinished) { debug('跳过无效或已完成任务'); return true; } const taskHref = taskDes.attr('href'); if (!taskHref) { debug('任务链接为空'); return false; } try { debug('获取重定向链接', { taskHref: taskHref }); const taskLink = await getRedirectLink(taskHref, taskType.includes('steam')); if (!taskLink) { debug('获取重定向链接失败'); return false; } if (taskType === 'steam.game.wishlist' && appId) { debug('添加 Steam 愿望单任务', { appId: appId }); this.undoneTasks.steam.wishlistLinks.push(`https://store.steampowered.com/app/${appId}`); return true; } debug('分类任务', { taskLink: taskLink, taskType: taskType }); this.#classifyTaskByType(taskLink, taskType, taskIcon, taskName, taskDes); return true; } catch (error) { debug('获取重定向链接失败', { error: error }); throwError(error, 'GiveeClub.classifyTask->getRedirectLink'); return false; } }; debug('开始处理所有任务'); await Promise.all(Array.from(tasks).map(processTask)); debug('任务处理完成'); logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.undoneTasks; debug('保存任务信息'); GM_setValue(`gcTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'GiveeClub.classifyTask'); return false; } } #classifyTaskByType(taskLink, taskType, taskIcon, taskName, taskDes) { try { debug('开始分类任务', { taskLink: taskLink, taskType: taskType, taskIcon: taskIcon, taskName: taskName }); if (taskType === 'steam.group.join' && /^https?:\/\/steamcommunity\.com\/groups/.test(taskLink)) { debug('添加 Steam 组任务'); this.undoneTasks.steam.groupLinks.push(taskLink); return; } if (/like.*announcement/gi.test(taskName)) { debug('添加 Steam 公告任务'); this.undoneTasks.steam.announcementLinks.push(taskLink); return; } if (taskType === 'steam.game.wishlist' && /^https?:\/\/store\.steampowered\.com\/app\//.test(taskLink)) { debug('添加 Steam 愿望单任务'); this.undoneTasks.steam.wishlistLinks.push(taskLink); return; } if (taskType === 'steam.game.wishlist' && taskDes.attr('data-steam-wishlist-appid')) { debug('添加 Steam 愿望单任务(通过 appId)'); this.undoneTasks.steam.wishlistLinks.push(`https://store.steampowered.com/app/${taskDes.attr('data-steam-wishlist-appid')}`); return; } if (taskType === 'steam.game.follow' && /^https?:\/\/store\.steampowered\.com\/app\//.test(taskLink)) { debug('添加 Steam 游戏关注任务'); this.undoneTasks.steam.followLinks.push(taskLink); return; } if (/^https?:\/\/store\.steampowered\.com\/curator\//.test(taskLink)) { debug('添加 Steam 鉴赏家关注任务'); this.undoneTasks.steam.curatorLinks.push(taskLink); return; } if (taskIcon.includes('steam') && /follow|subscribe/gim.test(taskName)) { debug('添加 Steam 鉴赏家点赞任务'); this.undoneTasks.steam.curatorLikeLinks.push(taskLink); return; } if (/subscribe.*steam.*forum/gim.test(taskName)) { debug('添加 Steam 论坛任务'); this.undoneTasks.steam.forumLinks.push(taskLink); return; } if (taskType === 'steam.game.playtime' && /^https?:\/\/store\.steampowered\.com\/app\//.test(taskLink)) { const time = taskDes.text().match(/(\d+)(?:\.\d+)?/gim)?.[0] || '0'; debug('添加 Steam 游戏时长任务', { time: time }); this.undoneTasks.steam.playTimeLinks.push(`${time}-${taskLink}`); return; } if (taskIcon.includes('discord')) { debug('添加 Discord 服务器任务'); this.undoneTasks.discord.serverLinks.push(taskLink); return; } if (taskIcon.includes('instagram')) { debug('跳过 Instagram 任务'); return; } if (taskIcon.includes('twitch')) { debug('添加 Twitch 频道任务'); this.undoneTasks.twitch.channelLinks.push(taskLink); return; } if (taskIcon.includes('reddit')) { debug('添加 Reddit 任务'); this.undoneTasks.reddit.redditLinks.push(taskLink); return; } if (/watch.*art/gim.test(taskName)) { debug('添加创意工坊物品任务'); this.undoneTasks.steam.workshopVoteLinks.push(taskLink); return; } if (/subscribe.*youtube.*channel/gim.test(taskName)) { debug('添加 YouTube 频道任务'); this.undoneTasks.youtube.channelLinks.push(taskLink); return; } if (/(watch|like).*youtube.*video/gim.test(taskName) || (taskIcon.includes('youtube') || taskIcon.includes('thumbs-up')) && /(watch|like).*video/gim.test(taskName)) { debug('添加 YouTube 视频任务'); this.undoneTasks.youtube.likeLinks.push(taskLink); return; } if (taskIcon.includes('vk') || /join.*vk.*group/gim.test(taskName)) { debug('添加 VK 任务'); this.undoneTasks.vk.nameLinks.push(taskLink); return; } if (taskIcon.includes('twitter')) { if (/https?:\/\/(twitter|x)\.com\/[^/]+\/?$/gim.test(taskLink)) { debug('添加 Twitter 用户关注任务'); this.undoneTasks.twitter.userLinks.push(taskLink); return; } if (/https?:\/\/(twitter|x)\.com\/[^/]+?\/status\/[\d]+/gim.test(taskLink)) { debug('添加 Twitter 转发任务'); this.undoneTasks.twitter.retweetLinks.push(taskLink); return; } } if (/(on twitter)|(Follow.*on.*Facebook)/gim.test(taskName)) { debug('跳过 Twitter/Facebook 任务'); return; } if (/follow.*button/gim.test(taskName)) { debug('添加 Steam 关注任务'); this.undoneTasks.steam.followLinks.push(taskLink); return; } debug('未识别的任务类型', { taskLink: taskLink, taskType: taskType, taskIcon: taskIcon, taskName: taskName }); } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'GiveeClub.classifyTaskByType'); return; } } async verifyTask() { try { debug('开始验证任务'); const logStatus = echoLog({ text: I18n('giveeClubVerifyNotice') }); const taskButtons = $('.event-actions tr button').has('i.glyphicon-refresh').not('[data-type="user.adblock"]'); debug('找到需要验证的任务按钮', { count: taskButtons.length }); for (const button of taskButtons) { debug('点击验证按钮', { type: $(button).attr('data-type') }); button.click(); if ($(button).attr('data-type') !== 'steam.game.wishlist') { debug('等待1秒'); await delay(1e3); } } debug('任务验证完成'); logStatus.warning(I18n('giveeClubVerifyFinished')); return true; } catch (error) { debug('任务验证失败', { error: error }); throwError(error, 'Givekey.verifyTask'); return false; } } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } const needLogin = $('a[href*="/account/auth"]').length > 0; if (needLogin) { debug('未登录,重定向到登录页面'); window.open($('a[href*="/account/auth"]').attr('href'), '_self'); } debug('登录检查完成', { needLogin: needLogin }); return true; } catch (error) { debug('登录检查失败', { error: error }); throwError(error, 'GiveeClub.checkLogin'); return false; } } #getGiveawayId() { try { debug('从URL获取抽奖ID'); const giveawayId = window.location.href.match(/\/event\/([\d]+)/)?.[1]; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({ text: I18n('getFailed', 'GiveawayId') }); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'GiveeClub.getGiveawayId'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const isEnded = $('.event-ended').length > 0; const hasNoWinner = $('.event-winner').length === 0; debug('检查抽奖状态', { isEnded: isEnded, hasNoWinner: hasNoWinner }); if (!(isEnded && hasNoWinner)) { return true; } debug('没有剩余密钥,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('giveawayEnded'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'GiveeClub.checkLeftKey'); return false; } } } const defaultOptions$1 = { maxPoint: '99999999' }; class OpiumPulses { name='OpiumPulses'; options={ ...defaultOptions$1, ...GM_getValue('OpiumPulsesOptions') }; maxPoints=99999999; myPoints=0; buttons=[ 'doFreeTask', 'doPointTask' ]; static test() { const {host: host} = window.location; const isMatch = host === 'www.opiumpulses.com'; debug('检查网站匹配', { host: host, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('检查登录失败'); echoLog({}).warning(I18n('checkLoginFailed')); } debug('解析最大积分', { maxPoint: this.options.maxPoint }); this.maxPoints = parseInt(this.options.maxPoint, 10); } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'OpiumPulses.after'); } } async doFreeTask() { try { debug('开始执行免费任务'); this.#toggleTask('FREE'); } catch (error) { debug('执行免费任务失败', { error: error }); throwError(error, 'OpiumPulses.doFreeTask'); } } async doPointTask() { try { debug('开始执行积分任务'); const pointsText = $('.page-header__nav-func-user-nav-items.points-items').text(); const pointsMatch = pointsText.match(/[\d]+/gim)?.[0] || '0'; this.myPoints = parseInt(pointsMatch, 10); debug('获取当前积分', { pointsText: pointsText, pointsMatch: pointsMatch, myPoints: this.myPoints }); this.#toggleTask('points'); } catch (error) { debug('执行积分任务失败', { error: error }); throwError(error, 'OpiumPulses.doPointTask'); } } async #toggleTask(type) { try { debug('开始切换任务', { type: type }); const items = $(`.giveaways-page-item:contains('${type}'):not(:contains('ENTERED'))`); debug('找到未参与的抽奖项目', { count: items.length }); for (const item of items) { const pointsText = $(item).find('.giveaways-page-item-header-points').text(); const needPoints = parseInt(pointsText.match(/[\d]+/gim)?.[0] || '999999', 10); const name = $(item).find('.giveaways-page-item-footer-name').text().trim(); debug('处理抽奖项目', { name: name, needPoints: needPoints }); if (type === 'points') { if (needPoints > this.myPoints) { debug('积分不足', { needPoints: needPoints, myPoints: this.myPoints }); echoLog({}).warning(`${I18n('noPoints')}: ${name}`); continue; } if (!needPoints) { debug('获取所需积分失败'); echoLog({}).warning(`${I18n('getNeedPointsFailed')}: ${name}`); continue; } if (needPoints > this.maxPoints) { debug('超过最大积分限制', { needPoints: needPoints, maxPoints: this.maxPoints }); continue; } } const logStatus = echoLog({ text: `${I18n('joiningLottery')}<a href="${$(item).find('a.giveaways-page-item-img-btn-more').attr('href')}" target="_blank">${name}</a>...` }); const aElement = $(item).find('a.giveaways-page-item-img-btn-enter:contains(\'enter\')'); if (aElement?.attr('onclick')?.includes('checkUser')) { const giveawayId = aElement.attr('onclick')?.match(/[\d]+/)?.[0]; if (giveawayId) { debug('执行用户检查', { giveawayId: giveawayId }); checkUser(giveawayId); } } if (!aElement.attr('href')) { debug('无效的链接'); logStatus.error('Error: No "href".'); continue; } debug('发送加入请求', { url: aElement.attr('href') }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: aElement.attr('href'), method: 'GET' }); if (result !== 'Success') { debug('请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); continue; } debug('发送最终请求', { url: data?.finalUrl }); const {result: result0, statusText: statusText0, status: status0, data: data0} = await httpRequest({ url: data?.finalUrl, method: 'GET' }); if (!data0?.responseText) { debug('响应无效', { result: result0, statusText: statusText0, status: status0 }); logStatus.error(`${result0}:${statusText0}(${status0})`); continue; } if (/You're not eligible to enter/gim.test(data0.responseText)) { debug('用户不符合参与条件'); logStatus.error('You\'re not eligible to enter'); continue; } if (!/You've entered this giveaway/gim.test(data0.responseText)) { debug('加入抽奖失败', { result: result0, statusText: statusText0, status: status0 }); logStatus.error(`${result0}:${statusText0}(${status0})`); continue; } debug('加入抽奖成功'); logStatus.success(); if (type === 'points') { const points = data0.responseText.match(/Points:[\s]*?([\d]+)/)?.[1]; if (points) { debug('更新用户积分', { points: points }); this.myPoints = parseInt(points, 10); } } } debug('任务处理完成'); echoLog({ text: '-----END-----' }); } catch (error) { debug('切换任务失败', { error: error }); throwError(error, 'OpiumPulses.toggleTask'); } } init() { debug('初始化完成'); return true; } classifyTask() { debug('任务分类完成'); return true; } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } if ($('a[href*="/site/login"]').length > 1) { debug('未登录,重定向到登录页面'); window.open('/site/login', '_self'); } debug('登录检查完成'); return true; } catch (error) { debug('检查登录失败', { error: error }); throwError(error, 'OpiumPulses.checkLogin'); return false; } } } const leftKeyChecker = { async classify(link) { try { debug('开始分类链接', { link: link }); let result = false; if (/^https?:\/\/giveaway\.su\/giveaway\/view\/[\d]+/.test(link)) { debug('匹配到 giveaway.su 链接'); result = await this.giveawaySu(link); } else if (/^https?:\/\/givee\.club\/[\w]+?\/event\/[\d]+/.test(link)) { debug('匹配到 givee.club 链接'); result = await this.giveeClub(link); } else if (/^https?:\/\/gleam\.io\/.+?\/.+/.test(link)) { debug('匹配到 gleam.io 链接'); result = await this.gleam(link); } else if (/^https?:\/\/www\.indiedb\.com\/giveaways\/.+/.test(link)) { debug('匹配到 indiedb.com 链接'); result = await this.indieDb(link); } else if (/^https?:\/\/key-hub\.eu\/giveaway\/[\d]+/.test(link)) { debug('匹配到 key-hub.eu 链接'); result = await this.keyhub(link); } else if (/^https?:\/\/opquests\.com\/quests\/[\d]+/.test(link)) { debug('匹配到 opquests.com 链接'); result = await this.opquests(link); } else if (/^https?:\/\/itch\.io\/s\/[\d]+?\/.*/.test(link)) { debug('匹配到 itch.io 链接'); result = await this.itch(link); } else if (/^https?:\/\/freeanywhere\.net\/game\?n=[\d]+/.test(link)) { debug('匹配到 freeanywhere.net 链接'); result = await this.freeanywhere(link); } else { debug('未匹配到支持的链接格式'); } debug('链接分类完成', { result: result }); return result; } catch (error) { debug('链接分类出错', { error: error }); throwError(error, 'leftKeyChecker.classify'); return false; } }, async giveawaySu(link) { try { debug('开始检查 giveaway.su 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } if (data.responseText.includes('class="steam-login"')) { debug('检测到未登录状态'); return false; } if (data.responseText.includes('class="giveaway-ended"')) { debug('检测到抽奖已结束'); return 'Ended'; } debug('检测到抽奖进行中'); return 'Active'; } catch (error) { debug('检查 giveaway.su 链接出错', { error: error }); throwError(error, 'leftKeyChecker.giveawaySu'); return false; } }, async giveeClub(link) { try { debug('开始检查 givee.club 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } if (data.responseText.includes('class="event-winner"')) { debug('检测到已中奖'); return 'Won'; } if (data.responseText.includes('class="event-ended"')) { debug('检测到活动已结束'); return 'Ended'; } debug('检测到活动进行中'); return 'Active'; } catch (error) { debug('检查 givee.club 链接出错', { error: error }); throwError(error, 'leftKeyChecker.giveeClub'); return false; } }, async gleam(link) { try { debug('开始检查 gleam.io 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } if (/incentives":{"[\d]+?":\[".+?"\]/.test(data.responseText)) { debug('检测到已中奖'); return 'Won'; } const campaignDiv = data.responseText.match(/<div class='popup-blocks-container'[\w\W]+?'>/)?.[0]; if (!campaignDiv) { debug('未找到活动信息'); return false; } const campaignString = $(campaignDiv).attr('ng-init')?.match(/initCampaign\(([\w\W]+?)\)$/)?.[1]; if (!campaignString) { debug('未找到活动初始化数据'); return false; } const {campaign: campaign} = JSON.parse(campaignString); debug('解析活动数据', { campaign: campaign }); if (campaign.banned) { debug('检测到活动已被禁止'); return 'Banned'; } if (campaign.finished) { debug('检测到活动已结束'); return 'Ended'; } if (campaign.paused) { debug('检测到活动已暂停'); return 'Paused'; } if ((new Date).getTime() < campaign.starts_at * 1e3) { debug('检测到活动未开始'); return 'NotStart'; } debug('检测到活动进行中'); return 'Active'; } catch (error) { debug('检查 gleam.io 链接出错', { error: error }); throwError(error, 'leftKeyChecker.gleam'); return false; } }, async indieDb(link) { try { debug('开始检查 indiedb.com 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } if (data.responseText.includes('Congrats you WON')) { debug('检测到已中奖'); return 'Won'; } if (data.responseText.includes('Giveaway is closed') || data.responseText.includes('next time')) { debug('检测到抽奖已结束'); return 'Ended'; } debug('检测到抽奖进行中'); return 'Active'; } catch (error) { debug('检查 indiedb.com 链接出错', { error: error }); throwError(error, 'leftKeyChecker.indieDb'); return false; } }, async keyhub(link) { try { debug('开始检查 key-hub.eu 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } const keysleft = data.responseText.match(/<span id="keysleft">([\d]+?)<\/span>/)?.[1]; if (!keysleft) { debug('未找到剩余密钥信息'); return false; } debug('检测到剩余密钥数量', { keysleft: keysleft }); if (keysleft === '0') { debug('检测到密钥已用完'); return 'Ended'; } debug('检测到活动进行中'); return `Active(${keysleft})`; } catch (error) { debug('检查 key-hub.eu 链接出错', { error: error }); throwError(error, 'leftKeyChecker.keyhub'); return false; } }, async opquests(link) { try { debug('开始检查 opquests.com 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (data?.status === 404) { debug('检测到活动不存在'); return 'Ended'; } if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } const keysleft = data.responseText.match(/<div class="">[\s]*?([\d]+?)[\s]*?of/)?.[1]; if (!keysleft) { debug('未找到剩余密钥信息'); return false; } debug('检测到剩余密钥数量', { keysleft: keysleft }); if (keysleft === '0') { debug('检测到密钥已用完'); return 'Ended'; } debug('检测到活动进行中'); return `Active(${keysleft})`; } catch (error) { debug('检查 opquests.com 链接出错', { error: error }); throwError(error, 'leftKeyChecker.opquests'); return false; } }, async itch(link) { try { debug('开始检查 itch.io 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } const endDate = data.responseText.match(/{"start_date":"[0-9A-Z-:]+?".*?"end_date":"([0-9A-Z-:]+?)".*?}/)?.[1]; if (!endDate) { debug('未找到结束日期信息'); return false; } debug('检测到活动结束日期', { endDate: endDate }); if ((new Date).getTime() > new Date(endDate).getTime()) { debug('检测到活动已结束'); return 'Ended'; } const formattedEndDate = dayjs(endDate).format('YYYY-MM-DD HH:mm:ss'); debug('检测到活动进行中', { formattedEndDate: formattedEndDate }); return `Active(${formattedEndDate})`; } catch (error) { debug('检查 itch.io 链接出错', { error: error }); throwError(error, 'leftKeyChecker.itch'); return false; } }, async freeanywhere(link) { try { debug('开始检查 freeanywhere.net 链接', { link: link }); const {result: result, data: data} = await httpRequest({ url: link, method: 'GET' }); if (result !== 'Success' || data?.status !== 200) { debug('请求失败', { result: result, status: data?.status }); return false; } const giveawayStatus = data.responseText.includes('Giveaway ended'); if (giveawayStatus) { debug('检测到活动已结束'); return 'Ended'; } debug('检测到活动进行中'); return 'Active'; } catch (error) { debug('检查 freeanywhere.net 链接出错', { error: error }); throwError(error, 'leftKeyChecker.freeanywhere'); return false; } } }; const defaultTasksTemplate$3 = { steam: { groupLinks: [], wishlistLinks: [], curatorLinks: [], curatorLikeLinks: [], followLinks: [], forumLinks: [], announcementLinks: [], workshopVoteLinks: [], licenseLinks: [] }, discord: { serverLinks: [] }, vk: { nameLinks: [] }, twitch: { channelLinks: [] }, reddit: { redditLinks: [] }, twitter: { userLinks: [], retweetLinks: [] }, youtube: { channelLinks: [], likeLinks: [] } }; const defaultTasks$4 = JSON.stringify(defaultTasksTemplate$3); class Keylol extends Website { name='Keylol'; socialTasks=JSON.parse(defaultTasks$4); undoneTasks=JSON.parse(defaultTasks$4); buttons=[ 'doTask', 'undoTask', 'selectAll', 'selectNone', 'invertSelect' ]; static CONFIG={ LINK_PATTERNS: { DISCORD: /^https?:\/\/discord\.com\/invite\/.+/, REDDIT: /^https?:\/\/www\.reddit\.com\/(r|user)\/.+/, INSTAGRAM: /^https:\/\/www\.instagram\.com\/.+/, TWITTER: /^https:\/\/(twitter|x)\.com\/.+/, TWITTER_RETWEET: /https:\/\/(twitter|x)\.com\/.*?\/status\/[\d]+/, TWITCH: /^https:\/\/(www\.)?twitch\.tv\/.+/, VK: /^https:\/\/vk\.com\/.+/, STEAM_CURATOR: /curator\/[\d]+/, STEAM_PUBLISHER: /(publisher|developer|franchise)\/.+/, STEAM_NEWS: /news(hub)?\/app\/[\d]+\/view\/[\d]+/, STEAM_APP: /app\/[\d]+/, STEAM_GROUP: /groups\/.+/, STEAM_ANNOUNCEMENT: /announcements\/detail\/[\d]+/, YOUTUBE: /youtube\.com/ }, SELECTORS: { MAIN_POST: { KEYLOL: '#postlist>div[id^="post_"]:first', DEFAULT: 'div.container' } } }; static test() { const {host: host} = window.location; const link = $('.subforum_left_title_left_up a').eq(3).attr('href'); const isMatch = host === 'keylol.com' && (!!link?.includes('319') || !!link?.includes('234')); debug('检查网站匹配', { host: host, link: link, isMatch: isMatch }); return isMatch; } init() { debug('初始化 Keylol'); return true; } after() { try { debug('开始处理页面链接'); const mainPost = $(this.name === 'Keylol' ? Keylol.CONFIG.SELECTORS.MAIN_POST.KEYLOL : Keylol.CONFIG.SELECTORS.MAIN_POST.DEFAULT); const allLinks = mainPost.find('a'); debug('找到所有链接', { count: allLinks.length }); allLinks.each(((_, link) => { const $link = $(link); const href = $link.attr('href'); if (!href) { return; } this.#classifyAndProcessLink($link, href); })); debug('开始处理抽奖链接'); this.#processGiveawayLinks(mainPost); if (this.name === 'Keylol') { debug('开始处理 Keylol 特定链接'); this.#processKeylolSpecificLinks(mainPost); } debug('设置 MutationObserver'); this.#setupMutationObserver(); } catch (error) { debug('处理页面链接失败', { error: error }); throwError(error, 'keylol.after'); } } #classifyAndProcessLink($link, href) { debug('分类处理链接', { href: href }); const {LINK_PATTERNS: LINK_PATTERNS} = Keylol.CONFIG; switch (true) { case LINK_PATTERNS.DISCORD.test(href): debug('发现 Discord 链接'); this.#addBtn($link[0], 'discord', 'serverLinks', href); break; case LINK_PATTERNS.REDDIT.test(href): debug('发现 Reddit 链接'); this.#addBtn($link[0], 'reddit', 'redditLinks', href); break; case LINK_PATTERNS.TWITTER.test(href): if (LINK_PATTERNS.TWITTER_RETWEET.test(href)) { debug('发现 Twitter 转发链接'); this.#addBtn($link[0], 'twitter', 'retweetLinks', href); } else { debug('发现 Twitter 用户链接'); this.#addBtn($link[0], 'twitter', 'userLinks', href); } break; case LINK_PATTERNS.TWITCH.test(href): debug('发现 Twitch 链接'); this.#addBtn($link[0], 'twitch', 'channelLinks', href); break; case LINK_PATTERNS.VK.test(href): debug('发现 VK 链接'); this.#addBtn($link[0], 'vk', 'nameLinks', href); break; case href.includes('store.steampowered.com'): debug('发现 Steam 商店链接'); this.#processSteamStoreLink($link[0], href); break; case href.includes('steamcommunity.com'): debug('发现 Steam 社区链接'); this.#processSteamCommunityLink($link[0], href); break; case LINK_PATTERNS.YOUTUBE.test(href): debug('发现 YouTube 链接'); this.#addBtn($link[0], 'youtube', 'channelLinks', href); this.#addBtn($link[0], 'youtube', 'likeLinks', href); break; } } #processSteamStoreLink(element, href) { debug('处理 Steam 商店链接', { href: href }); const {LINK_PATTERNS: LINK_PATTERNS} = Keylol.CONFIG; if (LINK_PATTERNS.STEAM_CURATOR.test(href)) { debug('发现 Steam 鉴赏家链接'); this.#addBtn(element, 'steam', 'curatorLinks', href); } else if (LINK_PATTERNS.STEAM_PUBLISHER.test(href)) { debug('发现 Steam 发行商链接'); this.#addBtn(element, 'steam', 'curatorLikeLinks', href); } else if (LINK_PATTERNS.STEAM_NEWS.test(href)) { debug('发现 Steam 新闻链接'); this.#addBtn(element, 'steam', 'announcementLinks', href); } else if (LINK_PATTERNS.STEAM_APP.test(href)) { debug('发现 Steam 应用链接'); this.#addBtn(element, 'steam', 'followLinks', href); this.#addBtn(element, 'steam', 'wishlistLinks', href); } } #processSteamCommunityLink(element, href) { debug('处理 Steam 社区链接', { href: href }); const {LINK_PATTERNS: LINK_PATTERNS} = Keylol.CONFIG; if (LINK_PATTERNS.STEAM_GROUP.test(href)) { debug('发现 Steam 组链接'); this.#addBtn(element, 'steam', 'groupLinks', href); } else if (LINK_PATTERNS.STEAM_ANNOUNCEMENT.test(href)) { debug('发现 Steam 公告链接'); this.#addBtn(element, 'steam', 'announcementLinks', href); } } #processGiveawayLinks(mainPost) { debug('开始处理抽奖链接'); const giveawayLinks = mainPost.find('a[href*="giveaway.su/giveaway/view/"],' + 'a[href*="givee.club/"],' + 'a[href*="gleam.io/"],' + 'a[href*="www.indiedb.com/giveaways/"],' + 'a[href*="key-hub.eu/giveaway/"],' + 'a[href*="opquests.com/quests/"],' + 'a[href*="freeanywhere.net/game?n="],' + 'a[href*="itch.io/s/"]:visible'); debug('找到抽奖链接', { count: giveawayLinks.length }); giveawayLinks.each(((_, link) => { const href = $(link).attr('href'); if (!href) { return; } debug('检查抽奖链接状态', { href: href }); leftKeyChecker.classify(href).then((status => { if (!status) { return; } const statusClass = /^Active/.test(status) ? 'active' : 'not-active'; const statusTitle = /^Active/.test(status) ? I18n('Active') : I18n(status); debug('更新抽奖链接状态', { href: href, status: status, statusClass: statusClass }); $(`a[href="${href}"]`).after(`<font class="auto-task-giveaway-status ${statusClass}" title="${statusTitle}">${status}</font>`); })).catch((error => { debug('检查抽奖链接状态失败', { href: href, error: error }); throwError(error, 'keylol.after -> leftKeyChecker'); })); })); } #processKeylolSpecificLinks(mainPost) { debug('开始处理 Keylol 特定链接'); const asfLinks = mainPost.find('a[href^="#asf"]:visible'); debug('找到 ASF 链接', { count: asfLinks.length }); asfLinks.each(((_, link) => { const href = $(link).attr('href'); if (!href) { return; } debug('处理 ASF 链接', { href: href }); const $link = $(`a[href="${href}"]`); $link.after('<span style="color: #ccc; margin: 0 -5px 0 5px"> | </span>'); this.#addBtn($link.next()[0], 'steam', 'licenseLinks', `appid-${href.replace('#asf', '')}`); })); const steamDbLinks = mainPost.find('a[href*="steamdb.info/sub/"]:visible'); debug('找到 SteamDB 链接', { count: steamDbLinks.length }); steamDbLinks.each(((_, link) => { const href = $(link).attr('href'); if (!href) { return; } const subid = href.match(/^https:\/\/steamdb\.info\/sub\/([\d]+)/)?.[1]; if (!subid) { return; } debug('处理 SteamDB 链接', { href: href, subid: subid }); this.#addBtn(link, 'steam', 'licenseLinks', `subid-${subid}`); })); const asfBlocks = mainPost.find('.blockcode:contains("addlicense"):visible'); debug('找到 ASF 代码块', { count: asfBlocks.length }); asfBlocks.each(((_, block) => { const appid = [ ...block.innerText.matchAll(/a(pp)?\/([\d]+)/g) ].map((matched => matched?.[2])).filter((id => id)); if (appid.length > 0) { debug('处理 ASF 代码块 appid', { appid: appid }); this.#addBtn($(block).children('em')[0], 'steam', 'licenseLinks', `appid-${appid.join(',')}`); } const subid = block.innerText.match(/[\d]+/g)?.filter((matched => !appid.includes(matched))); if (subid?.length) { debug('处理 ASF 代码块 subid', { subid: subid }); this.#addBtn($(block).children('em')[0], 'steam', 'licenseLinks', `subid-${subid.join(',')}`); } })); } #setupMutationObserver() { debug('设置 MutationObserver'); if ($('#threadindex').length > 0) { const [elementTargetNode] = $('#postlist').children('div[id^="post_"]'); const elementObserver = new MutationObserver((() => { debug('检测到 DOM 变化,重新处理页面'); elementObserver.disconnect(); this.after(); })); elementObserver.observe(elementTargetNode, { childList: true }); debug('MutationObserver 设置完成'); } } classifyTask(action) { try { debug('开始分类任务', { action: action }); this.socialTasks = JSON.parse(defaultTasks$4); this.undoneTasks = JSON.parse(defaultTasks$4); const selectedBtns = $('.auto-task-keylol[selected="selected"]:visible').get(); debug('找到选中的按钮', { count: selectedBtns.length }); for (const btn of selectedBtns) { const social = btn.getAttribute('data-social'); const type = btn.getAttribute('data-type'); const link = btn.getAttribute('data-link'); debug('处理任务按钮', { social: social, type: type, link: link }); if (!(social && type && link)) { debug('跳过无效任务按钮'); continue; } if (!(social in this.undoneTasks)) { debug('跳过未知社交平台', { social: social }); continue; } if (action === 'do' && type in this.undoneTasks[social]) { debug('添加到未完成任务', { social: social, type: type, link: link }); this.undoneTasks[social][type].push(link); } if (action === 'undo' && type in this.socialTasks[social]) { debug('添加到社交任务', { social: social, type: type, link: link }); this.socialTasks[social][type].push(link); } } this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.uniqueTasks(this.socialTasks); debug('任务分类完成', { undoneTasks: this.undoneTasks, socialTasks: this.socialTasks }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Keylol.classifyTask'); return false; } } selectAll() { try { debug('选择所有可见任务'); const tasks = $('.auto-task-keylol:visible'); tasks.attr('selected', 'selected'); debug('选择完成', { count: tasks.length }); } catch (error) { debug('选择所有任务失败', { error: error }); throwError(error, 'Keylol.selectAll'); } } selectNone() { try { debug('取消选择所有可见任务'); const tasks = $('.auto-task-keylol:visible'); tasks.removeAttr('selected'); debug('取消选择完成', { count: tasks.length }); } catch (error) { debug('取消选择所有任务失败', { error: error }); throwError(error, 'Keylol.selectNone'); } } invertSelect() { try { debug('反转选择所有可见任务'); const tasks = $('.auto-task-keylol:visible'); tasks.each(((_, element) => { const $element = $(element); if ($element.attr('selected')) { $element.removeAttr('selected'); } else { $element.attr('selected', 'selected'); } })); debug('反转选择完成', { count: tasks.length }); } catch (error) { debug('反转选择任务失败', { error: error }); throwError(error, 'Keylol.invertSelect'); } } #addBtn(before, social, linkType, link) { try { debug('添加任务按钮', { social: social, linkType: linkType, link: link }); if (!before || !social || !linkType || !link) { debug('跳过无效按钮参数'); return; } const button = $('<a>', { href: 'javascript:void(0);', class: 'auto-task-keylol', target: '_self', 'data-social': social, 'data-type': linkType, 'data-link': link, text: linkType.replace('Links', ''), onclick: 'this.getAttribute("selected") ? this.removeAttribute("selected") : this.setAttribute("selected", "selected")' }); $(before).after(button); debug('按钮添加成功'); } catch (error) { debug('添加按钮失败', { error: error, social: social, linkType: linkType }); throwError(error, `keylol.addBtn: ${social}/${linkType}`); } } } const defaultTasks$3 = { steam: { groupLinks: [], wishlistLinks: [], followLinks: [], curatorLikeLinks: [], playTimeLinks: [] }, twitter: { userLinks: [], retweetLinks: [] }, discord: { serverLinks: [] } }; class Opquests extends Website { name='Opquests'; undoneTasks={ ...defaultTasks$3 }; buttons=[ 'doTask', 'verifyTask', 'getKey' ]; static test() { const {host: host} = window.location; const isMatch = host === 'opquests.com'; debug('检查网站匹配', { host: host, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('检查登录失败'); echoLog({}).warning(I18n('checkLoginFailed')); } const opquestsVerifyTasks = GM_getValue('opquestsVerifyTasks') || []; debug('获取待验证任务', { count: opquestsVerifyTasks.length }); if (opquestsVerifyTasks.length > 0) { const taskId = opquestsVerifyTasks.pop(); debug('处理任务', { taskId: taskId }); GM_setValue('opquestsVerifyTasks', opquestsVerifyTasks); const [verifyBtn] = $(`#task_id[value="${taskId}"]`).parent().find('button[type="button"],button[type="submit"]').has('i.fa-check'); if (verifyBtn) { debug('点击验证按钮'); verifyBtn.click(); return; } debug('未找到验证按钮,继续处理下一个任务'); this.after(); return; } if (GM_getValue('opquestsVerifyTasks')) { debug('清除验证任务缓存'); GM_deleteValue('opquestsVerifyTasks'); } } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'Opquests.after'); } } init() { try { debug('开始初始化'); const logStatus = echoLog({ text: I18n('initing') }); if ($('a[href*="/auth/redirect"]').length > 0) { debug('需要登录'); window.open('/auth/redirect', '_self'); logStatus.warning(I18n('needLogin')); return false; } if (!this.#getGiveawayId()) { debug('获取抽奖ID失败'); return false; } this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'Opquests.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); if (action === 'undo') { debug('不支持撤销操作'); echoLog({ text: I18n('cannotUndo') }); return false; } const logStatus = echoLog({ text: I18n('getTasksInfo') }); const tasks = $('.w-full:contains("Validate") .items-center'); debug('找到任务', { count: tasks.length }); for (const task of tasks) { const link = $(task).find('a:contains("Open")').attr('href'); if (!link) { debug('跳过无链接任务'); continue; } const taskDes = $(task).find('div').eq(1).text().trim(); debug('处理任务', { taskDes: taskDes, link: link }); if (/steamcommunity\.com\/groups\//.test(link)) { debug('添加 Steam 组任务'); this.undoneTasks.steam.groupLinks.push(link); continue; } if (/store\.steampowered\.com\/app\//.test(link)) { if (/wishlist/gim.test(taskDes)) { debug('添加 Steam 愿望单任务'); this.undoneTasks.steam.wishlistLinks.push(link); } else if (/follow/gim.test(taskDes)) { debug('添加 Steam 关注任务'); this.undoneTasks.steam.followLinks.push(link); } else if (/play/gim.test(taskDes)) { const time = parseInt(taskDes.replace(/\s/gim, '').match(/(\d+)hours/im)?.[1] || '0', 10) * 60; debug('添加 Steam 游戏时长任务', { time: time }); this.undoneTasks.steam.playTimeLinks.push(`${time}-${link}`); } continue; } if (/store\.steampowered\.com\/(publisher|developer|curator)\//.test(link) && /follow/gim.test(taskDes)) { debug('添加 Steam 鉴赏家关注任务'); this.undoneTasks.steam.curatorLikeLinks.push(link); continue; } if (link.includes('//x.com/')) { if (/follow/gim.test(taskDes)) { debug('添加 Twitter 关注任务'); this.undoneTasks.twitter.userLinks.push(link); continue; } if (link.includes('status') && /Repost/gim.test(taskDes)) { debug('添加 Twitter 转发任务'); this.undoneTasks.twitter.retweetLinks.push(link); continue; } } if (link.includes('//discord.com/')) { if (/join/gim.test(taskDes)) { debug('添加 Discord 加入任务'); this.undoneTasks.discord.serverLinks.push(link); continue; } } if (/clash\.gg/.test(link)) { debug('跳过不支持的 Clash.gg 任务'); echoLog({}).warning(`${I18n('unSupporttedTaskType')}: ${taskDes}(${link})`); continue; } debug('未知任务类型'); echoLog({}).warning(`${I18n('unKnownTaskType')}: ${taskDes}(${link})`); } debug('任务分类完成'); logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); if (window.DEBUG) { console.log('%cAuto-Task[Debug]:', 'color:blue', JSON.stringify(this)); } return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Opquests.classifyTask'); return false; } } async verifyTask() { try { debug('开始验证任务'); if (!this.initialized) { debug('未初始化,执行初始化'); this.init(); } const tasks = $.makeArray($('.items-center').has('input[name="task_id"]')).map((ele => $(ele).find('input[name="task_id"]').val())); debug('获取待验证任务', { count: tasks.length }); GM_setValue('opquestsVerifyTasks', tasks); await this.#confirm(); debug('执行后续操作'); this.after(); return true; } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Opquests.verifyTask'); return false; } } async #confirm() { try { debug('开始确认任务'); const logStatus = echoLog({ html: `<li>${I18n('confirmingTask')}...<font></font></li>` }); debug('发送确认请求'); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://opquests.com/quests/${this.giveawayId}?confirm=1`, method: 'GET', nochche: true, headers: { origin: 'https://opquests.com', referer: `https://opquests.com/warning?id=${this.giveawayId}` } }); if (result !== 'Success') { debug('请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('响应错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('确认成功'); logStatus.success(); return true; } catch (error) { debug('确认任务失败', { error: error }); throwError(error, 'Opquests.confirm'); return false; } } async getKey(isButton) { try { if ($('[name="task_id"]').length > 0) { debug('有任务未完成,不获取密钥'); echoLog({}).warning(I18n('taskNotFinished')); return false; } debug('开始获取密钥', { isButton: isButton }); const logStatus = echoLog({ text: I18n('gettingKey') }); debug('发送获取密钥请求'); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://opquests.com/keys', method: 'GET' }); if (result !== 'Success') { debug('请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (!data?.responseText) { debug('响应无效', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const questTitle = $('h1.font-bold').text().trim().replace(' Quest', ''); const key = $(data.responseText).find(`div.items-center:contains("${questTitle}")`).find('div.font-bold').next().text(); debug('查找密钥', { questTitle: questTitle, hasKey: !!key }); if (!key) { debug('未找到密钥'); logStatus.error('Error: Key was not found'); if (isButton) { debug('重定向到密钥页面'); window.open('https://opquests.com/keys', '_self'); } return false; } debug('获取密钥成功'); logStatus.success(); echoLog({}).success(key); return true; } catch (error) { debug('获取密钥失败', { error: error }); throwError(error, 'Opquests.getKey'); return false; } } #getGiveawayId() { try { debug('开始获取抽奖ID'); const giveawayId = window.location.href.match(/quests\/([\d]+)/)?.[1]; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({}).error(I18n('getFailed', 'GiveawayId')); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'Opquests.getGiveawayId'); return false; } } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } if ($('a[href*="/auth/redirect"]').length > 0) { debug('未登录,重定向到登录页面'); window.open('/auth/redirect', '_self'); } debug('登录检查完成'); return true; } catch (error) { debug('检查登录失败', { error: error }); throwError(error, 'Opquests.checkLogin'); return false; } } } const defaultTasksTemplate$2 = { steam: { groupLinks: [], wishlistLinks: [], followLinks: [], curatorLinks: [], curatorLikeLinks: [], playTimeLinks: [] }, twitter: { userLinks: [], retweetLinks: [] }, twitch: { channelLinks: [] }, discord: { serverLinks: [] }, youtube: { channelLinks: [] }, extra: { gleam: [] } }; const defaultTasks$2 = JSON.stringify(defaultTasksTemplate$2); class Gleam extends Website { name='Gleam'; undoneTasks=JSON.parse(defaultTasks$2); socialTasks=JSON.parse(defaultTasks$2); buttons=[ 'doTask', 'undoTask', 'verifyTask' ]; static test() { const {host: host} = window.location; const isMatch = host === 'gleam.io'; debug('检查网站匹配', { host: host, isMatch: isMatch }); return isMatch; } before() { try { debug('重写全局对话框函数'); unsafeWindow.confirm = () => {}; unsafeWindow.alert = () => {}; unsafeWindow.prompt = () => {}; } catch (error) { debug('重写全局对话框函数失败', { error: error }); throwError(error, 'Gleam.before'); } } async after() { try { debug('开始执行后续操作'); if (window.location.search.includes('8b07d23f4bfa65f9')) { debug('检测到特殊查询参数,开始处理任务'); const checkComplete = setInterval((() => { if ($('.entry-content .entry-method i.fa-check').length > 0) { debug('任务已完成,关闭窗口'); clearInterval(checkComplete); window.close(); } })); await this.verifyTask(); echoLog({}).warning(I18n('gleamTaskNotice')); } else if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'Gleam.after'); } } init() { try { debug('初始化 Gleam'); const logStatus = echoLog({ text: I18n('initing') }); if (!this.#getGiveawayId()) { debug('获取抽奖ID失败'); return false; } this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'Gleam.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('恢复已保存的任务信息'); this.socialTasks = GM_getValue(`gleamTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$2); } const tasks = $('.entry-content .entry-method'); debug('找到任务元素', { count: tasks.length }); for (const task of tasks) { const $task = $(task); if (action === 'do' && $task.find('i.fa-question').length === 0) { debug('跳过已完成的任务'); continue; } const socialIcon = $task.find('.icon-wrapper i'); const taskInfo = $task.find('.user-links'); const taskText = taskInfo.text().trim(); const expandInfo = $task.find('.expandable'); const aElements = expandInfo.find('a.btn'); debug('处理任务', { taskText: taskText }); if (aElements.length > 0) { debug('处理可点击元素', { count: aElements.length }); for (const element of aElements) { const $element = $(element); const href = $element.attr('href'); $element.removeAttr('href')[0].click(); $element.attr('href', href); } } if (socialIcon.hasClass('fa-twitter') || socialIcon.hasClass('fa-x-twitter')) { const link = $task.find('a[href^="https://twitter.com/"],a[href^="https://x.com/"]').attr('href'); if (!link) { continue; } if (/follow/gi.test(taskText)) { if (action === 'undo') { this.socialTasks.twitter.userLinks.push(link); } if (action === 'do') { this.undoneTasks.twitter.userLinks.push(link); } continue; } if (/retweet/gim.test(taskText)) { if (action === 'undo') { this.socialTasks.twitter.retweetLinks.push(link); } if (action === 'do') { this.undoneTasks.twitter.retweetLinks.push(link); } continue; } } if (socialIcon.hasClass('fa-twitch') && /follow/gim.test(taskText)) { const link = $task.find('a[href^="https://twitch.tv/"]').attr('href'); if (!link) { continue; } if (action === 'undo') { this.socialTasks.twitch.channelLinks.push(link); } if (action === 'do') { this.undoneTasks.twitch.channelLinks.push(link); } continue; } if (socialIcon.hasClass('fa-discord') && /join/gim.test(taskText)) { let link = $task.find('a[href^="https://discord.com/invite/"]').attr('href'); if (!link) { const ggLink = $task.find('a[href^="https://discord.gg/"]').attr('href')?.match(/discord\.gg\/([^/]+)/)?.[1]; if (!ggLink) { continue; } link = `https://discord.com/invite/${ggLink}`; } if (action === 'undo') { this.socialTasks.discord.serverLinks.push(link); } if (action === 'do') { this.undoneTasks.discord.serverLinks.push(link); } continue; } if (socialIcon.hasClass('fa-external-link-square-alt')) { continue; } if (socialIcon.hasClass('fa-youtube') && /subscribe/gim.test(taskText)) { const link = $task.find('a[href^="https://www.youtube.com/channel/"]').attr('href'); if (!link) { continue; } if (action === 'undo') { this.socialTasks.youtube.channelLinks.push(link); } if (action === 'do') { this.undoneTasks.youtube.channelLinks.push(link); } continue; } if (socialIcon.attr('class')?.includes('steam')) { if (/join.*group/gi.test(taskText)) { const link = $task.find('a[href^="https://steamcommunity.com/groups/"]').attr('href'); if (!link) { continue; } if (action === 'undo') { this.socialTasks.steam.groupLinks.push(link); } if (action === 'do') { this.undoneTasks.steam.groupLinks.push(link); } continue; } if (/follow.*curator/gi.test(taskText)) { const link = $task.find('a[href^="https://store.steampowered.com/curator/"]').attr('href'); if (!link) { continue; } if (action === 'undo') { this.socialTasks.steam.curatorLinks.push(link); } if (action === 'do') { this.undoneTasks.steam.curatorLinks.push(link); } continue; } if (/play.*hours/gi.test(taskText)) { const link = $task.find('a[href^="https://steamcommunity.com/app/"],a[href^="https://store.steampowered.com/app/"]').attr('href'); if (!link) { continue; } if (action === 'undo') { this.socialTasks.steam.playTimeLinks.push(link); } if (action === 'do') { this.undoneTasks.steam.playTimeLinks.push(link); } continue; } if (/Sign up/gi.test(taskText)) { continue; } } if (socialIcon.hasClass('fa-bullhorn') && /Complete|Increase/gi.test(taskText)) { if (action !== 'do') { continue; } const gleamLink = await this.#getGleamLink(taskText); if (!gleamLink) { continue; } this.undoneTasks.extra.gleam.push(gleamLink); continue; } if (socialIcon.hasClass('fa-question') || socialIcon.hasClass('fa-reddit') || socialIcon.hasClass('fa-instagram') || socialIcon.hasClass('fa-facebook-f') || socialIcon.hasClass('fa-telegram-plane') || socialIcon.hasClass('fa-telegram') || socialIcon.hasClass('fa-vk') || socialIcon.hasClass('fa-envelope') || socialIcon.hasClass('fa-gift') || socialIcon.hasClass('fa-square-up-right') || socialIcon.hasClass('fa-gamepad-modern') || socialIcon.hasClass('fa-dollar-sign') || socialIcon.hasClass('fa-tiktok') || socialIcon.hasClass('fa-gamepad-alt') || socialIcon.hasClass('fa-bag-shopping') || socialIcon.hasClass('fa-swords') || socialIcon.hasClass('fa-shield') && taskText.includes('one of our giveaways') || socialIcon.hasClass('fa-shield') && taskText.includes('Check out') || socialIcon.hasClass('fa-shield') && taskText.includes('vloot.io')) { continue; } echoLog({}).warning(`${I18n('unKnownTaskType')}: ${taskText}`); } debug('任务分类完成'); logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.uniqueTasks(this.socialTasks); debug('保存任务信息'); GM_setValue(`gleamTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Gleam.classifyTask'); return false; } } async extraDoTask({gleam: gleam}) { try { debug('开始执行额外任务', { count: gleam.length }); const pro = []; for (const link of gleam) { pro.push(this.#doGleamTask(link)); } return Promise.all(pro).then((() => true)); } catch (error) { debug('执行额外任务失败', { error: error }); throwError(error, 'Gleam.extraDoTask'); return false; } } async #checkCampaign() { try { debug('检测人机验证'); let logStatus; if ($('[campaign-key="campaign.key"]').length > 0) { logStatus = echoLog({ text: I18n('campaign') }); debug('检测到人机验证'); await delay(3e3); logStatus.warning(I18n('retry')); await this.#checkCampaign(); return true; } logStatus?.success(); return false; } catch (error) { debug('检测人机验证失败', { error: error }); throwError(error, 'Gleam.checkCampaign'); return false; } } async verifyTask() { try { debug('开始验证任务'); echoLog({ text: `${I18n('verifyingTask')}...` }); const tasks = $('.entry-content .entry-method'); unsafeWindow._OxA = '_OxA'; for (const task of tasks) { const campaign = await this.#checkCampaign(); if (campaign) { return this.verifyTask(); } const $task = $(task); if ($task.find('i.fa-check').length > 0) { debug('跳过已完成的任务'); continue; } debug('处理任务验证'); const taskInfo = $task.find('.user-links'); taskInfo[0].click(); const aElements = $task.find('.expandable').find('a.btn'); if (aElements.length > 0) { debug('处理可点击元素', { count: aElements.length }); for (const element of aElements) { const $element = $(element); const href = $element.attr('href'); $element.removeAttr('href')[0].click(); $element.attr('href', href); } } debug('处理计时器'); unsafeWindow.$hookTimer?.setSpeed(1e3); const visitBtn = $task.find('.expandable').find('span:contains(more seconds),button:contains(more seconds)').filter(':visible'); if (visitBtn.length > 0 && unsafeWindow.$hookTimer) { debug('处理访问按钮'); const newTab = GM_openInTab('', { active: true }); await delay(1e3); newTab?.close(); window.focus(); } await delay(3e3); unsafeWindow.$hookTimer?.setSpeed(1); const expandInfo = $task.find('.expandable'); const [input] = expandInfo.find('input'); if (input) { debug('处理输入框'); const evt = new Event('input', { bubbles: true, cancelable: true, composed: true }); const valuelimit = [ ...expandInfo.text().matchAll(/"(.+?)"/g) ].at(-1)?.[1]; input.value = valuelimit || 'vloot'; input.dispatchEvent(evt); await delay(1e3); } await this.#checkSync(); const continueBtn = $task.find('.expandable').find('span:contains(Continue),button:contains(Continue),a:contains(Continue)'); for (const button of continueBtn) { debug('点击继续按钮'); button.click(); await delay(500); await this.#checkSync(); } } debug('任务验证完成'); echoLog({ text: I18n('verifiedGleamTasks') }); return true; } catch (error) { debug('任务验证失败', { error: error }); throwError(error, 'Gleam.verifyTask'); return false; } } async #checkSync() { try { debug('开始检查同步状态'); return await new Promise((resolve => { const checker = setInterval((() => { if ($('.entry-content .entry-method i.fa-sync').length === 0) { debug('同步完成'); clearInterval(checker); resolve(true); } }), 500); })); } catch (error) { debug('检查同步状态失败', { error: error }); throwError(error, 'Gleam.checkSync'); return false; } } async #doGleamTask(link) { try { debug('执行 Gleam 任务', { link: link }); const logStatus = echoLog({ text: I18n('doingGleamTask') }); return await new Promise((resolve => { GM_openInTab(`${link}?8b07d23f4bfa65f9`, { active: true, insert: true, setParent: true }).onclose = () => { debug('任务完成'); logStatus.success(); resolve(true); }; })); } catch (error) { debug('执行 Gleam 任务失败', { error: error }); throwError(error, 'Gleam.doGleamTask'); return false; } } #getGiveawayId() { try { debug('获取抽奖ID'); const giveawayId = window.location.pathname; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({ text: I18n('getFailed', 'GiveawayId') }); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'Gleam.getGiveawayId'); return false; } } async #getGleamLink(title) { try { debug('获取 Gleam 链接', { title: title }); const logStatus = echoLog({ text: I18n('gettingGleamLink') }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: 'https://www.vloot.io/api/v1/giveaways', method: 'GET', responseType: 'json' }); if (result === 'Success') { if (data?.status === 200 && data?.response?.Success === true && data?.response?.Data) { const {link: link} = data.response.Data.find((giveaway => title.replace(/[\s]/g, '').toLowerCase().includes(giveaway.title.replace(/[\s]/g, '').toLowerCase()))) || {}; if (link) { debug('获取链接成功', { link: link }); logStatus.success(); return link; } debug('获取链接失败'); logStatus.error(`Error:${I18n('getLinkFailed')}`); return false; } debug('API响应错误', { status: data?.status, statusText: data?.statusText }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('请求失败', { result: result, status: status, statusText: statusText }); logStatus.error(`${result}:${statusText}(${status})`); return false; } catch (error) { debug('获取 Gleam 链接失败', { error: error }); throwError(error, 'Gleam.getGleamLink'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const campaignString = $('div.popup-blocks-container').attr('ng-init')?.match(/initCampaign\(([\w\W]+?)\)$/)?.[1]; if (!campaignString) { debug('未找到活动配置信息'); return false; } const {campaign: campaign, incentive: incentive} = JSON.parse(campaignString); const controllerString = $('div.campaign.reward').attr('ng-init')?.match(/initContestant\(([\w\W]+?)\);/)?.[1]; let ownedKey = false; if (controllerString) { if (JSON.parse(controllerString).contestant?.claims?.incentives?.[incentive.id]?.length) { debug('用户已拥有密钥'); ownedKey = true; } } const isGiveawayInvalid = campaign.banned || campaign.finished && !ownedKey || campaign.paused || (new Date).getTime() < campaign.starts_at * 1e3; debug('检查抽奖状态', { banned: campaign.banned, finished: campaign.finished, ownedKey: ownedKey, paused: campaign.paused, notStarted: (new Date).getTime() < campaign.starts_at * 1e3 }); if (!isGiveawayInvalid) { return true; } debug('抽奖无效,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('giveawayNotWork'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'Gleam.checkLeftKey'); return false; } } } const defaultOptions = { username: '', email: '' }; class SweepWidget extends Website { name='SweepWidget'; options={ ...defaultOptions, ...GM_getValue('SweepWidgetOptions') }; buttons=[ 'doTask' ]; static test() { const {host: host} = window.location; const isMatch = /^sweepwidget\.com$/.test(host); debug('检查网站匹配', { host: host, isMatch: isMatch }); return /^https?:\/\/sweepwidget\.com\/view\/[\d]+/.test(window.location.href); } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('检查登录失败'); echoLog({}).warning(I18n('checkLoginFailed')); } } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'SweepWidget.after'); } } init() { try { debug('开始初始化'); const logStatus = echoLog({ text: I18n('initing') }); if (!this.#checkLogin()) { debug('需要登录'); logStatus.warning(I18n('needLogin')); return false; } if (!this.#getGiveawayId()) { debug('获取抽奖ID失败'); return false; } this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'SweepWidget.init'); return false; } } classifyTask() { debug('任务分类完成'); return true; } async doTask() { try { debug('开始执行任务'); if ($('#unlock_rewards_main_wrapper').length === 0) { debug('未显示奖励界面,尝试登录'); if ($('input[name="sw__login_name"]:visible').length > 0) { debug('填写用户名', { username: this.options.username }); $('input[name="sw__login_name"]').val(this.options.username); } if ($('input[name="sw__login_email"]:visible').length > 0) { debug('填写邮箱', { email: this.options.email }); $('input[name="sw__login_email"]').val(this.options.email); } if ($('#sw_login_button:visible').length > 0) { debug('点击登录按钮'); $('#sw_login_button')[0].click(); } const isEntered = await this.#checkEnter(); if (!isEntered) { debug('进入抽奖失败'); return false; } } const logStatus = echoLog({ text: I18n('SweepWidgetNotice') }); const tasks = $('#sw_inner_entry_methods_l2_wrapper>div.sw_entry'); debug('找到任务列表', { count: tasks.length }); for (const task of tasks) { const $task = $(task); if ($task.find('i.fa-check:visible').length > 0) { debug('跳过已完成的任务'); continue; } const title = $task.find('.sw_text_inner'); const aElement = $task.find('a.sw_link'); const link = aElement.attr('href'); if (!link) { debug('跳过无效链接的任务'); continue; } debug('处理任务', { title: title.text(), link: link }); title[0].click(); aElement.attr('href', '#a').attr('target', '_self'); aElement[0]?.click(); await delay(300); aElement.attr('href', link).attr('target', '_blank'); debug('填写测试文本'); $task.find('input[type="text"]').val('test'); const verifyBtn = $task.find('input.sw_verify'); if (verifyBtn.prop('disabled') === true) { debug('验证按钮被禁用,尝试重新激活'); title[0].click(); await delay(300); title[0].click(); await delay(300); } debug('点击验证按钮'); $task.find('input.sw_verify').removeAttr('disabled')[0]?.click(); await this.#checkFinish($task); const randomDelay = parseInt(`${Math.random() * (3e3 - 1e3 + 1) + 1e3}`, 10); debug('等待随机延迟', { delay: randomDelay }); await delay(randomDelay); } debug('所有任务执行完成'); logStatus.success(); return true; } catch (error) { debug('执行任务失败', { error: error }); throwError(error, 'SweepWidget.doTask'); return false; } } #checkLogin() { try { debug('检查登录状态'); if ($('#twitter_login_button').length > 0) { debug('点击 Twitter 登录按钮'); $('#twitter_login_button')[0].click(); } debug('登录检查完成'); return true; } catch (error) { debug('检查登录失败', { error: error }); throwError(error, 'SweepWidget.checkLogin'); return false; } } #getGiveawayId() { try { debug('开始获取抽奖ID'); const giveawayId = window.location.href.match(/\/view\/([\d]+)/)?.[1]; if (!giveawayId) { debug('获取抽奖ID失败'); echoLog({ text: I18n('getFailed', 'GiveawayId') }); return false; } this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'SweepWidget.getGiveawayId'); return false; } } async #checkEnter() { try { debug('开始检查是否进入抽奖'); return new Promise((resolve => { const checker = setInterval((() => { if ($('#unlock_rewards_main_wrapper').length === 0) { debug('等待进入抽奖...'); return; } debug('成功进入抽奖'); clearInterval(checker); resolve(true); }), 500); })); } catch (error) { debug('检查进入抽奖失败', { error: error }); throwError(error, 'SweepWidget.checkEnter'); return false; } } async #checkFinish($task) { try { debug('开始检查任务完成状态'); return new Promise((resolve => { const checker = setInterval((() => { const isCompleted = $task.find('i.fa-check:visible').length > 0 || $task.find('.sw_entry_input:visible').length === 0; if (!isCompleted) { debug('等待任务完成...'); return; } debug('任务完成'); clearInterval(checker); resolve(true); }), 500); })); } catch (error) { debug('检查任务完成状态失败', { error: error }); throwError(error, 'SweepWidget.checkFinish'); return false; } } } const processFormData = formData => { debug('开始处理表单数据', { formDataLength: formData.length }); const data = {}; formData.forEach((value => { data[value.name] = value.value; })); debug('表单数据处理完成', { processedData: data }); return data; }; const updateGlobalOption = (element, data) => { const name = $(element).attr('name'); if (!name) { debug('元素缺少name属性', { element: element }); return; } debug('开始更新全局选项', { name: name }); const keys = name.split('.'); const value = data[name]; const processedValue = value ? value === 'on' ? true : value : value ?? false; debug('处理选项值', { keys: keys, originalValue: value, processedValue: processedValue }); if (keys.length === 3) { globalOptions[keys[0]][keys[1]][keys[2]] = processedValue; debug('更新三级选项', { path: keys.join('.'), value: processedValue }); } else if (keys.length === 2) { globalOptions[keys[0]][keys[1]] = processedValue; debug('更新二级选项', { path: keys.join('.'), value: processedValue }); } }; const generateFormRow = (type, option, data, isFirstOption, totalOptions) => { debug('开始生成表单行', { type: type, option: option, isFirstOption: isFirstOption, totalOptions: totalOptions }); const backgroundColor = `${stringToColour(type)}44`; const headerBackgroundColor = `${stringToColour(type)}66`; if ([ 'other', 'position', 'hotKey', 'ASF' ].includes(type)) { const header = isFirstOption ? `<th rowspan="${totalOptions}" style="background-color: ${headerBackgroundColor}">${I18n(type)}</th>` : ''; if (typeof data === 'boolean') { debug('生成布尔类型选项行', { type: type, option: option, value: data }); return `\n <tr style="background-color: ${backgroundColor}">\n ${header}\n <td>${I18n(option)}</td>\n <td>\n <label>\n <input type="checkbox" name="${type}.${option}"${data ? ' checked="checked"' : ''}/>\n <span><i></i></span>\n </label>\n </td>\n </tr>`; } debug('生成文本类型选项行', { type: type, option: option, value: data }); return `\n <tr style="background-color: ${backgroundColor}">\n ${header}\n <td>${I18n(option)}</td>\n <td>\n <input class="editOption" type="text" name="${type}.${option}" value="${data}"/>\n </td>\n </tr>`; } debug('生成社交媒体选项行', { type: type, option: option, dataKeys: Object.keys(data) }); return Object.entries(data).map((([socialType, value]) => `\n <tr style="background-color: ${stringToColour(option)}66">\n ${isFirstOption ? `<th rowspan="${totalOptions}" style="background-color: ${headerBackgroundColor}">${I18n(type)}</th>` : ''}\n <td>${option}.${I18n(socialType)}</td>\n <td>\n <label>\n <input type="checkbox" name="${type}.${option}.${socialType}"${value ? ' checked="checked"' : ''}/>\n <span><i></i></span>\n </label>\n </td>\n </tr>`)).join(''); }; const generateGlobalOptionsForm = () => { debug('开始生成全局选项表单'); const formRows = Object.entries(globalOptions).map((([type, data1]) => { debug('处理选项类型', { type: type, optionsCount: Object.keys(data1).length }); return Object.entries(data1).map((([option, data2], index) => { const totalOptions = [ 'other', 'position', 'hotKey', 'ASF' ].includes(type) ? Object.keys(data1).length : Object.values(data1).reduce(((acc, cur) => acc + Object.keys(cur).length), 0); return generateFormRow(type, option, data2, index === 0, totalOptions); })).join(''); })).join(''); debug('表单生成完成'); return `\n <form id="globalOptionsForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('type')}</td>\n <td>${I18n('option')}</td>\n <td>${I18n('value')}</td>\n </tr>\n </thead>\n <tbody>\n ${formRows}\n </tbody>\n </table>\n </form>`; }; const saveData = () => { try { debug('开始保存全局选项数据'); const formData = $('#globalOptionsForm').serializeArray(); debug('获取表单数据', { formDataLength: formData.length }); const data = processFormData(formData); debug('开始更新全局选项'); $.makeArray($('#globalOptionsForm input')).forEach((element => { updateGlobalOption(element, data); })); GM_setValue('globalOptions', globalOptions); debug('全局选项保存完成'); Swal.fire({ title: I18n('changeGlobalOptionsSuccess'), icon: 'success' }); } catch (error) { debug('保存全局选项时发生错误', { error: error }); throwError(error, 'saveData'); } }; const changeGlobalOptions = showType => { try { debug('开始显示全局选项配置界面', { showType: showType }); const formHtml = generateGlobalOptionsForm(); if (showType === 'swal') { debug('使用弹窗显示选项'); Swal.fire({ title: I18n('globalOptions'), html: formHtml, showConfirmButton: true, confirmButtonText: I18n('save'), showCancelButton: true, cancelButtonText: I18n('close') }).then((({isConfirmed: isConfirmed}) => { if (isConfirmed) { debug('用户确认保存选项'); saveData(); } else { debug('用户取消保存选项'); } })); } else { debug('使用页面内显示选项'); $('body').append(`<h2>${I18n('globalOptions')}</h2>${formHtml}`); } } catch (error) { debug('显示全局选项配置界面时发生错误', { error: error }); throwError(error, 'changeGlobalOptions'); } }; const defaultWhiteList = { discord: { servers: [] }, instagram: { users: [] }, twitch: { channels: [] }, twitter: { users: [], retweets: [], likes: [] }, vk: { names: [] }, youtube: { channels: [], likes: [] }, reddit: { reddits: [] }, steam: { groups: [], officialGroups: [], wishlists: [], follows: [], forums: [], workshops: [], curators: [], workshopVotes: [], curatorLikes: [], announcements: [], licenses: [], playtests: [], playTime: [] } }; const REGEX_PATTERNS = { DISCORD_INVITE: /invite\/(.+)/, INSTAGRAM_USER: /https:\/\/www\.instagram\.com\/(.+)?\//, TWITCH_CHANNEL: /https:\/\/(www\.)?twitch\.tv\/(.+)/, TWITTER_USER: /https:\/\/twitter\.com\/(.+)/, TWITTER_STATUS: /https:\/\/twitter\.com\/.*?\/status\/([\d]+)/, VK_NAME: /https:\/\/vk\.com\/([^/]+)/, REDDIT_USER: /https?:\/\/www\.reddit\.com\/user\/([^/]*)/, REDDIT_SUBREDDIT: /https?:\/\/www\.reddit\.com\/r\/([^/]*)/, STEAM_GROUP: /groups\/(.+)\/?/, STEAM_APP: /app\/([\d]+)/, STEAM_WORKSHOP: /\?id=([\d]+)/, STEAM_CURATOR: /curator\/([\d]+)/, STEAM_STORE: /https?:\/\/store\.steampowered\.com\/(.*?)\/([^/?]+)/ }; const link2id = async function(type) { try { debug('开始从链接获取ID', { type: type }); const link = $('#socialLink').val(); let id = ''; switch (type) { case 'discord.servers': id = REGEX_PATTERNS.DISCORD_INVITE.exec(link)?.[1] || ''; break; case 'instagram.users': id = REGEX_PATTERNS.INSTAGRAM_USER.exec(link)?.[1] || ''; break; case 'twitch.channels': id = REGEX_PATTERNS.TWITCH_CHANNEL.exec(link)?.[2] || ''; break; case 'twitter.users': id = REGEX_PATTERNS.TWITTER_USER.exec(link)?.[1] || ''; break; case 'twitter.retweets': id = REGEX_PATTERNS.TWITTER_STATUS.exec(link)?.[1] || ''; break; case 'vk.names': id = REGEX_PATTERNS.VK_NAME.exec(link)?.[1] || ''; break; case 'youtube.channels': id = (await getInfo(link, 'channel'))?.params?.channelId || ''; break; case 'youtube.likes': id = (await getInfo(link, 'likeVideo'))?.params?.videoId || ''; break; case 'reddit.reddits': { const userMatch = REGEX_PATTERNS.REDDIT_USER.exec(link); const subredditMatch = REGEX_PATTERNS.REDDIT_SUBREDDIT.exec(link); id = userMatch?.[1] || subredditMatch?.[1] || ''; break; } case 'steam.groups': id = REGEX_PATTERNS.STEAM_GROUP.exec(link)?.[1] || ''; break; case 'steam.wishlists': case 'steam.follows': case 'steam.forums': case 'steam.playtests': case 'steam.playTime': id = REGEX_PATTERNS.STEAM_APP.exec(link)?.[1] || ''; break; case 'steam.workshops': id = REGEX_PATTERNS.STEAM_WORKSHOP.exec(link)?.[1] || ''; break; case 'steam.curators': { if (link.includes('curator')) { id = REGEX_PATTERNS.STEAM_CURATOR.exec(link)?.[1] || ''; } else { const storeMatch = REGEX_PATTERNS.STEAM_STORE.exec(link); if (!storeMatch) { break; } const [, param1, param2] = storeMatch; const steam = new Steam; if (await steam.init()) { id = await steam.getCuratorId(param1, param2) || ''; } } break; } } debug('从链接获取ID结果', { type: type, id: id }); return id; } catch (error) { debug('从链接获取ID时发生错误', { error: error }); throwError(error, 'link2id'); return I18n('getFailed', 'id'); } }; const disabledType = { steam: [ 'workshopVotes', 'curatorLikes', 'announcements' ], twitter: [ 'likes' ] }; const assignWhiteList = whiteList => { try { debug('开始合并白名单'); const newWhiteList = {}; for (const [key, value] of Object.entries(defaultWhiteList)) { newWhiteList[key] = { ...value, ...whiteList[key] }; } debug('白名单合并完成'); return newWhiteList; } catch (error) { debug('合并白名单时发生错误', { error: error }); throwError(error, 'assignWhiteList'); return defaultWhiteList; } }; const whiteListOptions = function(showType) { try { debug('开始显示白名单选项', { showType: showType }); const whiteList = assignWhiteList(GM_getValue('whiteList') || {}); let whiteListOptionsForm = `<form id="whiteListForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('website')}</td>\n <td>${I18n('type')}</td>\n <td>${I18n('edit')}</td>\n </tr>\n </thead>\n <tbody>`; for (const [social, types] of Object.entries(whiteList)) { const validTypes = Object.keys(types).filter((type => !disabledType[social]?.includes(type))); whiteListOptionsForm += validTypes.map(((type, index) => { const bgColor = `${stringToColour(social)}66`; return `\n <tr style="background-color: ${bgColor}">\n ${index === 0 ? `<th rowspan="${validTypes.length}" style="background-color: ${bgColor}">${social}</th>` : ''}\n <td>${I18n(type)}</td>\n <td><button type="button" class="editWhiteList" data-value="${social}.${type}">${I18n('edit')}</button></td>\n </tr>`; })).join(''); } whiteListOptionsForm += '</tbody></table></form>'; if (showType === 'swal') {} else { debug('使用页面显示白名单选项'); $('body').append(`<h2>${I18n('whiteList')}</h2>${whiteListOptionsForm}`); } $('.editWhiteList').on('click', (function() { const value = $(this).attr('data-value'); if (!value) { return; } const [social, type] = value.split('.'); const currentList = whiteList[social]?.[type]; if (!currentList) { debug('未找到白名单配置', { social: social, type: type }); echoLog({}).warning(I18n('whiteListNotFound', value)); return; } debug('编辑白名单', { social: social, type: type }); Swal.fire({ title: I18n('changeWhiteListOption', value), input: 'textarea', html: `\n <input id="socialLink" class="swal2-input" placeholder="在此处输入链接获取id">\n <button id="link2id" data-type="${value}" class="swal2-confirm swal2-styled">获取id</button>\n <p style="margin-bottom:0 !important;">在下方填写白名单,每行一个</p>\n `, inputValue: currentList.join('\n'), showConfirmButton: true, confirmButtonText: I18n('save'), showCancelButton: true, cancelButtonText: I18n('close'), showDenyButton: true, denyButtonText: I18n('return') }).then((({isDenied: isDenied, isConfirmed: isConfirmed, value: value}) => { if (isDenied) { debug('返回白名单选项'); if (showType === 'swal') {} return; } if (isConfirmed && value) { debug('保存白名单更改', { social: social, type: type, value: value }); whiteList[social][type] = value.split('\n').filter(Boolean); GM_setValue('whiteList', whiteList); Swal.fire({ title: I18n('changeWhiteListSuccess'), icon: 'success' }); } })); $('#link2id').on('click', (async function() { const type = $(this).attr('data-type'); if (!type) { return; } debug('从链接获取ID按钮点击', { type: type }); const id = await link2id(type); $('#socialLink').val(id); })); })); } catch (error) { debug('显示白名单选项时发生错误', { error: error }); throwError(error, 'whiteListOptions'); } }; const setGistData = async (token, gistId, fileName, content) => { try { debug('开始设置Gist数据', { gistId: gistId, fileName: fileName }); const logStatus = echoLog({ text: I18n('settingData') }); const contentData = JSON.stringify({ files: { [fileName]: { content: JSON.stringify(content) } } }); debug('准备发送的数据', { contentData: contentData }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://api.github.com/gists/${gistId}`, headers: { Accept: 'application/vnd.github.v3+json', Authorization: `token ${token}` }, data: contentData, responseType: 'json', method: 'POST', timeout: 3e4 }); if (result !== 'Success') { debug('设置Gist数据失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } const expectedContent = JSON.stringify(content); if (data?.status !== 200 || data?.response?.files?.[fileName]?.content !== expectedContent) { debug('设置Gist数据验证失败', { status: data?.status, content: data?.response?.files?.[fileName]?.content }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('设置Gist数据成功'); logStatus.success(); return true; } catch (error) { debug('设置Gist数据发生错误', { error: error }); throwError(error, 'setGistData'); return false; } }; const getGistData = async (token, gistId, fileName, test = false) => { try { debug('开始获取Gist数据', { gistId: gistId, fileName: fileName, test: test }); const logStatus = echoLog({ text: I18n('gettingData') }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://api.github.com/gists/${gistId}`, headers: { Accept: 'application/vnd.github.v3+json', Authorization: `token ${token}` }, responseType: 'json', method: 'GET', timeout: 3e4 }); if (result !== 'Success') { debug('获取Gist数据失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200) { debug('获取Gist数据状态码错误', { status: data?.status }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } const content = data.response?.files?.[fileName]?.content; if (!content) { debug('获取的Gist数据为空'); logStatus.error(`Error:${I18n('noRemoteData')}`); return false; } if (test) { debug('Gist数据测试成功'); logStatus.success(); return true; } try { const formatedContent = JSON.parse(content); debug('Gist数据解析成功', { contentLength: Object.keys(formatedContent).length }); logStatus.success(); return formatedContent; } catch (error) { debug('Gist数据解析失败', { error: error }); logStatus.error(`Error:${I18n('errorRemoteDataFormat')}`); console.log('%c%s', 'color:white;background:red', `Auto-Task[Error]: getGistData\n${error.stack}`); return false; } } catch (error) { debug('获取Gist数据发生错误', { error: error }); throwError(error, 'getGistData'); return false; } }; const syncOptions = async () => { try { debug('开始同步选项配置'); const defaultOptions = { TOKEN: '', GIST_ID: '', FILE_NAME: '', SYNC_HISTORY: true }; let syncOptions = GM_getValue('gistOptions') || defaultOptions; debug('获取已保存的同步选项', syncOptions); const createForm = options => `\n <div class="gist-options-form">\n <p>\n <label for="github-token">Github Token</label>\n <input\n id="github-token"\n class="swal2-input"\n placeholder="Github Token"\n value="${options.TOKEN}"\n autocomplete="off"\n spellcheck="false"\n />\n </p>\n <p>\n <label for="gist-id">Gist ID</label>\n <input\n id="gist-id"\n class="swal2-input"\n placeholder="Gist ID"\n value="${options.GIST_ID}"\n autocomplete="off"\n spellcheck="false"\n />\n </p>\n <p>\n <label for="file-name">${I18n('fileName')}</label>\n <input\n id="file-name"\n class="swal2-input"\n placeholder="${I18n('fileName')}"\n value="${options.FILE_NAME}"\n autocomplete="off"\n spellcheck="false"\n />\n </p>\n <p class="sync-history-wrapper">\n <label for="sync-history" class="swal2-checkbox-custom">\n <input\n id="sync-history"\n type="checkbox"\n ${options.SYNC_HISTORY ? 'checked="checked"' : ''}\n />\n <span class="swal2-label">${I18n('syncHistory')}</span>\n </label>\n </p>\n <div class="button-group">\n <button id="upload-data" type="button" class="swal2-confirm swal2-styled" onclick="handleUpload()">\n ${I18n('upload2gist')}\n </button>\n <button id="download-data" type="button" class="swal2-confirm swal2-styled" onclick="handleDownload()">\n ${I18n('downloadFromGist')}\n </button>\n </div>\n </div>\n `; const showConfigDialog = async () => { debug('显示配置对话框'); const result = await Swal.fire({ title: I18n('gistOptions'), html: createForm(syncOptions), focusConfirm: false, showLoaderOnConfirm: true, footer: `<a href="https://auto-task-doc.js.org/guide/#%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5" target="_blank">${I18n('help')}</a>`, preConfirm: async () => { const options = { TOKEN: $('#github-token').val().trim(), GIST_ID: $('#gist-id').val().trim(), FILE_NAME: $('#file-name').val().trim(), SYNC_HISTORY: $('#sync-history').prop('checked') }; debug('保存新的同步选项', options); GM_setValue('gistOptions', options); syncOptions = options; return await getGistData(options.TOKEN, options.GIST_ID, options.FILE_NAME, true); }, allowOutsideClick: () => !Swal.isLoading(), confirmButtonText: I18n('saveAndTest'), showCancelButton: true, cancelButtonText: I18n('close') }); if (result.value) { debug('配置测试成功'); await Swal.fire({ icon: 'success', title: I18n('testSuccess'), timer: 2e3, timerProgressBar: true }); await showConfigDialog(); } else if (result.value !== undefined) { debug('配置测试失败'); await Swal.fire({ icon: 'error', title: I18n('testFailed'), timer: 2e3, timerProgressBar: true }); await showConfigDialog(); } }; const handleUpload = async () => { debug('开始处理数据上传'); const options = GM_getValue('gistOptions'); if (!options?.TOKEN || !options?.GIST_ID || !options?.FILE_NAME) { debug('配置信息不完整'); await Swal.fire({ icon: 'error', title: I18n('saveAndTestNotice') }); await showConfigDialog(); return; } debug('显示数据处理提示'); Swal.fire({ icon: 'info', title: I18n('processingData'), allowOutsideClick: false }); const data = {}; const names = GM_listValues(); const syncHistory = $('#sync-history').prop('checked'); debug('开始收集数据', { namesCount: names.length, syncHistory: syncHistory }); for (const name of names) { if (name === 'gistOptions' || /^[\w]+?Auth$/.test(name)) { continue; } if (!syncHistory && /^[\w]+?Tasks-/.test(name)) { continue; } data[name] = GM_getValue(name); } debug('数据收集完成', { dataKeysCount: Object.keys(data).length }); Swal.update({ icon: 'info', title: I18n('updatingData') }); const success = await setGistData(options.TOKEN, options.GIST_ID, options.FILE_NAME, data); debug('数据上传完成', { success: success }); await Swal.fire({ icon: success ? 'success' : 'error', title: I18n(success ? 'syncDataSuccess' : 'syncDataFailed'), timer: 2e3, timerProgressBar: true }); }; const handleDownload = async () => { debug('开始处理数据下载'); const options = GM_getValue('gistOptions'); if (!options?.TOKEN || !options?.GIST_ID || !options?.FILE_NAME) { debug('配置信息不完整'); await Swal.fire({ icon: 'error', title: I18n('saveAndTestNotice') }); await showConfigDialog(); return; } debug('显示数据下载提示'); Swal.fire({ icon: 'info', title: I18n('downloadingData'), allowOutsideClick: false }); const data = await getGistData(options.TOKEN, options.GIST_ID, options.FILE_NAME); if (!data || typeof data === 'boolean') { debug('未检测到远程数据'); await Swal.fire({ icon: 'error', title: I18n('checkedNoData') }); await showConfigDialog(); return; } debug('开始保存数据'); Swal.update({ icon: 'info', title: I18n('savingData') }); const syncHistory = $('#sync-history').prop('checked'); let savedCount = 0; for (const [name, value] of Object.entries(data)) { if (!syncHistory && /^[\w]+?Tasks-/.test(name)) { continue; } GM_setValue(name, value); savedCount += 1; } debug('数据保存完成', { savedCount: savedCount }); await Swal.fire({ icon: 'success', title: I18n('syncDataSuccess'), timer: 2e3, timerProgressBar: true }); }; unsafeWindow.handleUpload = handleUpload; unsafeWindow.handleDownload = handleDownload; await showConfigDialog(); } catch (error) { debug('同步选项发生错误', { error: error }); throwError(error, 'syncOptions'); await Swal.fire({ icon: 'error', title: I18n('error'), text: error instanceof Error ? error.message : 'Unknown error occurred', timer: 3e3, timerProgressBar: true }); } }; const VALID_SIDES_X = [ 'right', 'left' ]; const VALID_SIDES_Y = [ 'top', 'bottom' ]; class Setting { name='Setting'; buttons=[ 'saveGlobalOptions', 'syncData', 'tasksHistory' ]; syncData=syncOptions; selectors={ body: 'body', autoTaskInfo: '#auto-task-info', autoTaskButtons: '#auto-task-buttons', showButtonDiv: 'div.show-button-div', positionInputs: 'input[name^="position"]', hotKeyInputs: 'input[name^="hotKey"]' }; tasksHistory() { debug('打开任务历史记录页面'); GM_openInTab('https://auto-task.hclonely.com/history.html', { active: true }); } static test() { const {host: host, pathname: pathname} = window.location; const isMatch = [ 'auto-task.hclonely.com', 'auto-task-doc.js.org' ].includes(host) && pathname === '/setting.html'; debug('检查设置页面匹配', { host: host, pathname: pathname, isMatch: isMatch }); return isMatch; } before() { try { debug('开始清空页面内容'); $(this.selectors.body).html('').addClass('auto-task-options'); debug('页面内容已清空'); } catch (error) { debug('清空页面内容失败', { error: error }); throwError(error, 'Setting.before'); } } async after() { try { debug('开始初始化设置页面'); await this.#initializeEnvironment(); this.#initializeGlobalSettings(); this.#setupSocialButtons(); this.#setupPositionHandlers(); this.#setupHotKeyHandlers(); debug('设置页面初始化完成'); } catch (error) { debug('设置页面初始化失败', { error: error }); throwError(error, 'Setting.after'); } } saveGlobalOptions() { try { debug('开始保存全局选项'); saveData(); debug('全局选项保存完成'); } catch (error) { debug('保存全局选项失败', { error: error }); throwError(error, 'Setting.saveGlobalOptions'); } } async #initializeEnvironment() { try { debug('开始初始化环境信息'); const userAgent = await browser.getInfo(); debug('获取用户代理信息', { userAgent: userAgent }); const environmentHtml = this.#generateEnvironmentHtml(userAgent); $(this.selectors.body).append(`<h2>${I18n('environment')}</h2>${environmentHtml}`); debug('环境信息初始化完成'); } catch (error) { debug('初始化环境信息失败', { error: error }); throwError(error, 'Setting.initializeEnvironment'); } } #generateEnvironmentHtml(userAgent) { return `\n <form id="environmentForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('type')}</td>\n <td>${I18n('name')}</td>\n <td>${I18n('version')}</td>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>${I18n('os')}</td>\n <td>${userAgent.system}</td>\n <td>${userAgent.systemVersion}</td>\n </tr>\n <tr>\n <td>${I18n('browser')}</td>\n <td>${userAgent.browser}</td>\n <td>${userAgent.browserVersion}</td>\n </tr>\n <tr>\n <td>${I18n('scriptManager')}</td>\n <td>${GM_info.scriptHandler}</td>\n <td>${GM_info.version}</td>\n </tr>\n <tr>\n <td>${I18n('script')}</td>\n <td>${GM_info.script.name}</td>\n <td>${GM_info.script.version}</td>\n </tr>\n </tbody>\n </table>\n </form>\n `; } #initializeGlobalSettings() { debug('开始初始化全局设置'); changeGlobalOptions('page'); whiteListOptions('page'); debug('全局设置初始化完成'); } #setupSocialButtons() { debug('开始设置社交媒体按钮'); this.#addSocialButton('other.twitterVerifyId', 'getTwitterUserId', 'twitterUser'); this.#addSocialButton('other.youtubeVerifyChannel', 'getYoutubeChannelId', 'youtubeChannel'); debug('社交媒体按钮设置完成'); } #addSocialButton(inputName, buttonId, socialType) { debug('添加社交媒体按钮', { inputName: inputName, buttonId: buttonId, socialType: socialType }); $(`input[name="${inputName}"]`).after(`<button id="${buttonId}" type="button">${I18n(`get${buttonId}`)}</button>`); $(`#${buttonId}`).on('click', (() => this.#getId(socialType))); debug('社交媒体按钮添加完成'); } #setupPositionHandlers() { debug('开始设置位置处理器'); $(this.selectors.positionInputs).on('input', (event => { const input = $(event.target); const type = input.attr('name')?.replace('position.', ''); if (!type) { debug('无效的位置类型'); return; } debug('处理位置变化', { type: type }); this.#handlePositionChange(type); })); debug('位置处理器设置完成'); } #handlePositionChange(type) { debug('开始处理位置变化', { type: type }); const config = this.#getPositionConfig(type); if (!config) { debug('获取位置配置失败'); return; } const {distance: distance, sideX: sideX, sideY: sideY} = config; if (!this.#isValidPosition(distance, sideX, sideY)) { debug('无效的位置配置', { distance: distance, sideX: sideX, sideY: sideY }); return; } const [x, y] = distance.split(','); const target = this.#getPositionTarget(type); if (!target) { debug('获取目标元素失败'); return; } debug('更新元素位置', { target: target, sideX: sideX, sideY: sideY, x: x, y: y }); this.#updateElementPosition(target, sideX, sideY, x, y); } #getPositionConfig(type) { debug('获取位置配置', { type: type }); const baseType = type.replace(/(?:button|log|show)(?:SideX|SideY|Distance)$/, ''); const distance = $(`input[name="position.${baseType}Distance"]`).val(); const sideX = $(`input[name="position.${baseType}SideX"]`).val(); const sideY = $(`input[name="position.${baseType}SideY"]`).val(); const config = { distance: distance, sideX: sideX, sideY: sideY }; debug('位置配置', config); return config; } #isValidPosition(distance, sideX, sideY) { const isValid = VALID_SIDES_X.includes(sideX) && VALID_SIDES_Y.includes(sideY) && /^[\d]+?,[\d]+$/.test(distance); debug('验证位置配置', { distance: distance, sideX: sideX, sideY: sideY, isValid: isValid }); return isValid; } #getPositionTarget(type) { const targetMap = { button: this.selectors.autoTaskButtons, showButton: this.selectors.showButtonDiv, log: this.selectors.autoTaskInfo }; const baseType = type.replace(/(?:SideX|SideY|Distance)$/, ''); return targetMap[baseType]; } #updateElementPosition(selector, sideX, sideY, x, y) { debug('更新元素位置', { selector: selector, sideX: sideX, sideY: sideY, x: x, y: y }); const $element = $(selector); const oppositeX = sideX === 'right' ? 'left' : 'right'; const oppositeY = sideY === 'top' ? 'bottom' : 'top'; $element.css(sideX, `${x}px`).css(sideY, `${y}px`).css(oppositeX, '').css(oppositeY, ''); debug('元素位置更新完成'); } #setupHotKeyHandlers() { debug('开始设置热键处理器'); $(this.selectors.hotKeyInputs).attr('readonly', 'readonly').off('keydown').on('keydown', this.#handleHotKeyPress); debug('热键处理器设置完成'); } #handleHotKeyPress(event) { debug('处理热键按下事件', { key: event.key }); const functionKeys = []; if (event.altKey) { functionKeys.push('alt'); } if (event.ctrlKey) { functionKeys.push('ctrl'); } if (event.shiftKey) { functionKeys.push('shift'); } const key = event.key.length === 1 ? event.key.toLowerCase() : ''; const value = functionKeys.length ? `${functionKeys.join(' + ')} + ${key}` : key; debug('设置热键值', { functionKeys: functionKeys, key: key, value: value }); $(event.target).val(value); } async #getId(social) { try { debug('开始获取社交媒体ID', { social: social }); const result = await Swal.fire({ title: I18n('getId', I18n(social)), html: this.#generateIdInputHtml(social), showCancelButton: true, cancelButtonText: I18n('close'), showConfirmButton: false }); if (!result.isDismissed) { debug('用户确认获取ID'); await this.#handleIdRetrieval(social); } else { debug('用户取消获取ID'); } } catch (error) { debug('获取社交媒体ID失败', { error: error }); throwError(error, 'Setting.getId'); } } #generateIdInputHtml(social) { return `\n <input id="socialLink" class="swal2-input" placeholder="在此处输入链接获取id">\n <button id="link2id" data-type="${social}" class="swal2-confirm swal2-styled">获取id</button>\n `; } async #handleIdRetrieval(social) { const link = $('#socialLink').val(); if (!link) { debug('链接为空'); return; } debug('开始处理ID获取', { social: social, link: link }); let id = ''; if (social === 'twitterUser') { const name = link.match(/https:\/\/twitter\.com\/(.+)/)?.[1] || link; debug('获取Twitter用户ID', { name: name }); id = await (new Twitter).userName2id(name) || ''; } else if (social === 'youtubeChannel') { const name = this.#extractYoutubeUrl(link); debug('获取YouTube频道信息', { name: name }); const info = await getInfo(name, 'channel'); id = info?.params?.channelId || ''; } debug('ID获取结果', { id: id }); $('#socialLink').val(id); } #extractYoutubeUrl(link) { debug('提取YouTube URL', { link: link }); if (/^https:\/\/(www\.)?google\.com.*?\/url\?.*?url=https:\/\/www.youtube.com\/.*/.test(link)) { const extractedUrl = link.match(/url=(https:\/\/www\.youtube\.com\/.*)/)?.[1] || link; debug('从Google搜索结果提取URL', { extractedUrl: extractedUrl }); return extractedUrl; } return link; } } class History extends Keylol { name='History'; buttons=[ 'doTask', 'undoTask', 'selectAll', 'selectNone', 'invertSelect', 'clearHistory' ]; static test() { try { const {host: host, pathname: pathname} = window.location; const isMatch = [ 'auto-task.hclonely.com', 'auto-task-doc.js.org' ].includes(host) && pathname === '/history.html'; debug('检查是否为历史记录页面', { host: host, pathname: pathname, isMatch: isMatch }); return isMatch; } catch (error) { debug('检查历史记录页面时出错', { error: error }); throwError(error, 'History.test'); return false; } } before() { try { debug('开始初始化历史记录页面'); $('body').html('<div class="container"></div>').addClass('auto-task-history'); debug('页面基础结构已创建'); const data = GM_listValues() || []; debug('获取存储的所有值', { count: data.length }); const tasksHistory = data.map((value => /^[\w]+?Tasks-/.test(value) ? value : null)).filter((value => value)); debug('筛选任务历史记录', { count: tasksHistory.length }); tasksHistory.forEach((item => { debug('处理任务项', { item: item }); this.#addItem(item); })); debug('历史记录页面初始化完成'); } catch (error) { debug('初始化历史记录页面时出错', { error: error }); throwError(error, 'History.before'); } } clearHistory() { try { debug('开始清除历史记录'); const data = GM_listValues() || []; debug('获取存储的所有值', { count: data.length }); const tasksHistory = data.map((value => /^[\w]+?Tasks-/.test(value) ? value : null)).filter((value => value)); debug('筛选要清除的任务历史记录', { count: tasksHistory.length }); tasksHistory.forEach((item => { debug('删除任务项', { item: item }); GM_deleteValue(item); })); debug('历史记录清除完成'); Swal.fire({ title: I18n('clearHistoryFinished'), icon: 'success' }); } catch (error) { debug('清除历史记录时出错', { error: error }); throwError(error, 'History.clearHistory'); } } #addItem(item) { try { debug('开始添加任务项', { item: item }); const tasksData = GM_getValue(item); if (!tasksData?.tasks) { debug('任务数据无效', { item: item }); return; } const {title: title, link: link} = this.#getTaskInfo(item); if (!title || !link) { debug('获取任务信息失败', { item: item }); return; } debug('生成任务HTML', { item: item, title: title, link: link }); const html = this.#generateTaskHtml(tasksData.tasks); this.#appendTaskToContainer(item, title, link, html, tasksData.time); this.#bindDeleteEvent(); debug('任务项添加完成', { item: item }); } catch (error) { debug('添加任务项时出错', { error: error, item: item }); throwError(error, 'History.addItem'); } } #getTaskInfo(item) { try { debug('开始获取任务信息', { item: item }); const [website, id] = item.split('-'); debug('解析任务标识符', { website: website, id: id }); const taskInfoMap = { fawTasks: { title: `Freeanywhere[${id}]`, link: `https://freeanywhere.net/#/giveaway/${id}` }, gasTasks: { title: `Giveawaysu[${id}]`, link: `https://giveaway.su/giveaway/view/${id}` }, gcTasks: { title: `GiveeClub[${id}]`, link: `https://givee.club/event/${id}` }, gkTasks: { title: `Givekey[${id}]`, link: `https://givekey.ru/giveaway/${id}` }, gleamTasks: { title: `Gleam[${id}]`, link: `https://gleam.io${id}` }, khTasks: { title: `keyhub[${id}]`, link: `https://key-hub.eu/giveaway/${id}` }, prysTasks: { title: `Prys[${id}]`, link: `https://prys.revadike.com/giveaway/?id=${id}` } }; const result = taskInfoMap[website] || { title: '', link: '' }; debug('获取任务信息结果', { result: result }); return result; } catch (error) { debug('获取任务信息时出错', { error: error, item: item }); throwError(error, 'History.getTaskInfo'); return { title: '', link: '' }; } } #generateTaskHtml(tasks) { try { debug('开始生成任务HTML'); let html = ''; for (const [social, types] of Object.entries(tasks)) { for (const [type, taskList] of Object.entries(types)) { for (const task of taskList) { debug('处理任务', { social: social, type: type, task: task }); const displayTask = task.length > 55 ? `${task.slice(0, 55)}...` : task; html += `<li>\n <font class="auto-task-capitalize">${social}.${I18n(type.replace('Link', ''))}: </font>\n <a href="${task}" target="_blank">${displayTask}</a>\n </li>`; } } } debug('任务HTML生成完成'); return html; } catch (error) { debug('生成任务HTML时出错', { error: error }); throwError(error, 'History.generateTaskHtml'); return ''; } } #appendTaskToContainer(item, title, link, html, time) { try { debug('开始添加任务到容器', { item: item, title: title, link: link }); $('.container').append(`\n <div class="card" data-name="${item}">\n <div class="title">\n <a href="${link}" target="_blank">${title}</a>\n <span class="delete-task" data-name="${item}" title="${I18n('deleteTask')}">\n <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2734" width="32" height="32">\n <path d="M607.897867 768.043004c-17.717453 0-31.994625-14.277171-31.994625-31.994625L575.903242 383.935495c0-17.717453 14.277171-31.994625 31.994625-31.994625s31.994625 14.277171 31.994625 31.994625l0 351.94087C639.892491 753.593818 625.61532 768.043004 607.897867 768.043004z" p-id="2735" fill="#d81e06"></path>\n <path d="M415.930119 768.043004c-17.717453 0-31.994625-14.277171-31.994625-31.994625L383.935495 383.935495c0-17.717453 14.277171-31.994625 31.994625-31.994625 17.717453 0 31.994625 14.277171 31.994625 31.994625l0 351.94087C447.924744 753.593818 433.647573 768.043004 415.930119 768.043004z" p-id="2736" fill="#d81e06"></path>\n <path d="M928.016126 223.962372l-159.973123 0L768.043004 159.973123c0-52.980346-42.659499-95.983874-95.295817-95.983874L351.94087 63.989249c-52.980346 0-95.983874 43.003528-95.983874 95.983874l0 63.989249-159.973123 0c-17.717453 0-31.994625 14.277171-31.994625 31.994625s14.277171 31.994625 31.994625 31.994625l832.032253 0c17.717453 0 31.994625-14.277171 31.994625-31.994625S945.73358 223.962372 928.016126 223.962372zM319.946246 159.973123c0-17.545439 14.449185-31.994625 31.994625-31.994625l320.806316 0c17.545439 0 31.306568 14.105157 31.306568 31.994625l0 63.989249L319.946246 223.962372 319.946246 159.973123 319.946246 159.973123z" p-id="2737" fill="#d81e06"></path>\n <path d="M736.048379 960.010751 288.123635 960.010751c-52.980346 0-95.983874-43.003528-95.983874-95.983874L192.139761 383.591466c0-17.717453 14.277171-31.994625 31.994625-31.994625s31.994625 14.277171 31.994625 31.994625l0 480.435411c0 17.717453 14.449185 31.994625 31.994625 31.994625l448.096758 0c17.717453 0 31.994625-14.277171 31.994625-31.994625L768.215018 384.795565c0-17.717453 14.277171-31.994625 31.994625-31.994625s31.994625 14.277171 31.994625 31.994625l0 479.231312C832.032253 916.835209 789.028725 960.010751 736.048379 960.010751z" p-id="2738" fill="#d81e06"></path>\n </svg>\n </span>\n </div>\n <ul>${html}</ul>\n <span class="time">${I18n('lastChangeTime')}: ${dayjs(time).format('YYYY-MM-DD HH:mm:ss')}</span>\n </div>\n `); debug('任务已添加到容器', { item: item }); } catch (error) { debug('添加任务到容器时出错', { error: error, item: item }); throwError(error, 'History.appendTaskToContainer'); } } #bindDeleteEvent() { try { debug('开始绑定删除事件'); $('span.delete-task').on('click', (function() { const itemName = $(this).attr('data-name'); debug('点击删除按钮', { itemName: itemName }); if (!itemName) { debug('删除失败:未找到任务名称'); Swal.fire({ title: I18n('clearTaskFailed'), icon: 'error' }); return; } GM_deleteValue(itemName); $(`div.card[data-name="${itemName}"]`).remove(); debug('任务删除成功', { itemName: itemName }); Swal.fire({ title: I18n('clearTaskFinished'), text: itemName, icon: 'success' }); })); debug('删除事件绑定完成'); } catch (error) { debug('绑定删除事件时出错', { error: error }); throwError(error, 'History.bindDeleteEvent'); } } } const defaultTasksTemplate$1 = { steam: { groupLinks: [], wishlistLinks: [], followLinks: [], curatorLinks: [], curatorLikeLinks: [] }, twitter: { userLinks: [], retweetLinks: [] }, twitch: { channelLinks: [] }, discord: { serverLinks: [] }, youtube: { channelLinks: [] }, extra: { giveawayHopper: [] } }; const defaultTasks$1 = JSON.stringify(defaultTasksTemplate$1); class GiveawayHopper extends Website { name='GiveawayHopper'; undoneTasks=JSON.parse(defaultTasks$1); socialTasks=JSON.parse(defaultTasks$1); tasks=[]; buttons=[ 'doTask', 'undoTask', 'verifyTask' ]; static test() { const {host: host} = window.location; const isMatch = host === 'giveawayhopper.com'; debug('检查网站匹配', { host: host, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('登录检查失败'); echoLog({}).warning(I18n('checkLoginFailed')); } const giveawayIdResult = this.#getGiveawayId(); debug('获取抽奖ID', { success: giveawayIdResult, id: this.giveawayId }); } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'GiveawayHopper.after'); } } async init() { try { debug('初始化 GiveawayHopper'); const logStatus = echoLog({ text: I18n('initing') }); const leftKeyResult = await this.#checkLeftKey(); if (!leftKeyResult) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'GiveawayHopper.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); if (!this.giveawayId) { debug('未找到抽奖ID,尝试获取'); await this.#getGiveawayId(); } const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('恢复已保存的任务信息'); this.socialTasks = GM_getValue(`giveawayHopperTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks$1); } debug('请求任务列表'); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://giveawayhopper.com/api/v1/campaigns/${this.giveawayId}/with-auth`, method: 'GET', responseType: 'json', headers: { authorization: `Bearer ${window.sessionStorage.gw_auth}`, 'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1]) } }); if (result !== 'Success') { debug('请求任务列表失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || !data?.response?.tasks) { debug('任务列表数据异常', { status: data?.status, response: data?.response }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('获取到任务列表', { count: data.response.tasks.length }); this.tasks = data.response.tasks; for (const task of data.response.tasks) { if (task.isDone) { debug('跳过已完成任务', { taskId: task.id, type: task.type }); continue; } debug('处理任务', { taskId: task.id, category: task.category, type: task.type }); await httpRequest({ url: `https://giveawayhopper.com/api/v1/campaigns/${this.giveawayId}/tasks/${task.id}/visited`, method: 'GET', responseType: 'json', headers: { authorization: `Bearer ${window.sessionStorage.gw_auth}`, 'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1]) } }); if (task.category === 'Steam' && task.type === 'JoinGroup') { debug('处理 Steam 组任务'); const steamGroupLink = await getRedirectLink(`https://steamcommunity.com/gid/${task.group_id}`); if (!steamGroupLink) { debug('获取 Steam 组链接失败'); continue; } debug('添加 Steam 组链接', { action: action, link: steamGroupLink }); if (action === 'undo') { this.socialTasks.steam.groupLinks.push(steamGroupLink); } if (action === 'do') { this.undoneTasks.steam.groupLinks.push(steamGroupLink); } continue; } if (task.category === 'Discord' && task.type === 'JoinServer') { const discordLink = `https://discord.gg/${task.invite_code}`; debug('添加 Discord 服务器链接', { action: action, link: discordLink }); if (action === 'undo') { this.socialTasks.discord.serverLinks.push(discordLink); } if (action === 'do') { this.undoneTasks.discord.serverLinks.push(discordLink); } continue; } if ([ 'TikTok', 'YouTube', 'General' ].includes(task.category)) { debug('跳过特殊任务类型', { category: task.category }); continue; } debug('发现未知任务类型', { category: task.category, type: task.type }); echoLog({}).warning(`${I18n('unKnownTaskType')}: ${task.category}-${task.type}`); } logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.uniqueTasks(this.socialTasks); debug('任务分类完成', { undoneTasks: this.undoneTasks, socialTasks: this.socialTasks }); GM_setValue(`giveawayHopperTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'GiveawayHopper.classifyTask'); return false; } } async verifyTask() { try { debug('开始验证任务'); for (const task of this.tasks) { if (task.isDone) { debug('跳过已完成任务', { taskId: task.id }); continue; } debug('验证任务', { taskId: task.id, name: task.displayName?.replace(':target', task.targetName) || task.name }); const logStatus = echoLog({ text: `${I18n('verifyingTask')}[${task.displayName?.replace(':target', task.targetName) || task.name}]...` }); if (!task.link) { debug('获取任务链接'); task.link = this.#getTaskLink(task); } if (task.link) { debug('访问任务链接', { link: task.link }); await this.#visitTaskLink(task); } await delay(1e3); const verifyResult = await this.#verifyTask(task, logStatus); debug('任务验证结果', { taskId: task.id, success: verifyResult }); if (!verifyResult) { continue; } } debug('所有任务验证完成'); return true; } catch (error) { debug('任务验证失败', { error: error }); throwError(error, 'GiveawayHopper.verifyTask'); return false; } } #getTaskLink(task) { try { debug('生成任务链接', { category: task.category, type: task.type }); let link = ''; if (task.category === 'YouTube' && task.type === 'FollowAccount') { link = `https://www.youtube.com/@${task.targetName}`; } else if (task.category === 'TikTok' && task.type === 'FollowAccount') { link = `https://www.tiktok.com/@${task.targetName}`; } else if (task.category === 'Steam' && task.type === 'JoinGroup') { link = ''; } else if (task.category === 'Discord' && task.type === 'JoinServer') { link = ''; } debug('生成的任务链接', { link: link }); return link; } catch (error) { debug('生成任务链接失败', { error: error }); throwError(error, 'GiveawayHopper.getTaskLink'); return ''; } } async #visitTaskLink(task) { debug('访问任务链接', { taskId: task.id, link: task.link }); await httpRequest({ url: `https://giveawayhopper.com/fw?url=${encodeURIComponent(task.link)}&src=campaign&src_id=${this.giveawayId}&ref=task&ref_id=${task.id}&token=${window.sessionStorage.gw_auth}`, method: 'GET', headers: { authorization: `Bearer ${window.sessionStorage.gw_auth}`, 'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1]) } }); } async #verifyTask(task, logStatus) { debug('验证任务', { taskId: task.id, category: task.category, type: task.type }); const postData = { taskcategory: task.category, taskname: task.type }; if ([ 'YouTube', 'TikTok' ].includes(task.category)) { postData.username = '1'; } const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: `https://giveawayhopper.com/api/v1/campaigns/${this.giveawayId}/tasks/${task.id}/complete`, method: 'POST', headers: { authorization: `Bearer ${window.sessionStorage.gw_auth}`, 'x-xsrf-token': decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1]), 'content-type': 'application/json' }, dataType: 'json', data: JSON.stringify(postData) }); if (result !== 'Success') { debug('任务验证请求失败', { result: result, statusText: statusText, status: status }); logStatus.error(`${result}:${statusText}(${status})`); return false; } if (data?.status !== 200 || !data?.response?.completed) { debug('任务验证响应异常', { status: data?.status, response: data?.response }); logStatus.error(`Error:${data?.statusText}(${data?.status})`); return false; } debug('任务验证成功', { taskId: task.id }); logStatus.success(); return true; } #getGiveawayId() { try { debug('从URL获取抽奖ID'); const giveawayId = window.location.pathname.split('/').at(-1); if (!giveawayId) { debug('获取抽奖ID失败'); echoLog({ text: I18n('getFailed', 'GiveawayId') }); return false; } this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'GiveawayHopper.getGiveawayId'); return false; } } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } const needLogin = $('div.widget-connections-edit:contains("Log in")').length > 0; if (needLogin) { debug('未登录,自动点击登录按钮'); $('div.widget-connections-edit:contains("Log in") a')[0].click(); } debug('登录检查完成', { needLogin: needLogin }); return true; } catch (error) { debug('登录检查失败', { error: error }); throwError(error, 'GiveawayHopper.checkLogin'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const keyCount = parseInt($('p.widget-single-prize span').text()?.match(/\d+/)?.[0] || '0', 10); debug('剩余密钥数量', { keyCount: keyCount }); if (keyCount > 0) { return true; } debug('没有剩余密钥,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('noKeysLeft'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'GiveawayHopper.checkLeftKey'); return false; } } } const defaultTasksTemplate = { steam: { groupLinks: [], curatorLinks: [], wishlistLinks: [], followLinks: [] }, youtube: { channelLinks: [] } }; const defaultTasks = JSON.stringify(defaultTasksTemplate); class Prys extends Website { name='Prys'; socialTasks=JSON.parse(defaultTasks); undoneTasks=JSON.parse(defaultTasks); buttons=[ 'doTask', 'undoTask', 'verifyTask' ]; static test() { const {host: host} = window.location; const isMatch = host === 'prys.revadike.com'; debug('检查网站匹配', { host: host, isMatch: isMatch }); return isMatch; } async after() { try { debug('开始执行后续操作'); if (!this.#checkLogin()) { debug('检查登录失败'); echoLog({}).warning(I18n('checkLoginFailed')); } if (!await this.#checkLeftKey()) { debug('检查剩余密钥失败'); echoLog({}).warning(I18n('checkLeftKeyFailed')); } } catch (error) { debug('后续操作失败', { error: error }); throwError(error, 'Prys.after'); } } init() { try { debug('开始初始化'); const logStatus = echoLog({ text: I18n('initing') }); if ($('button:contains("Sign")').length > 0) { debug('需要登录'); logStatus.warning(I18n('needLogin')); return false; } if (!this.#getGiveawayId()) { debug('获取抽奖ID失败'); return false; } this.initialized = true; debug('初始化完成'); logStatus.success(); return true; } catch (error) { debug('初始化失败', { error: error }); throwError(error, 'Prys.init'); return false; } } async classifyTask(action) { try { debug('开始分类任务', { action: action }); const logStatus = echoLog({ text: I18n('getTasksInfo') }); if (action === 'undo') { debug('恢复已保存的任务信息'); this.socialTasks = GM_getValue(`prysTasks-${this.giveawayId}`)?.tasks || JSON.parse(defaultTasks); } const steps = $('#steps tbody tr'); debug('找到任务步骤', { count: steps.length }); for (let eq = 0; eq < steps.length; eq += 1) { if (steps.eq(eq).find('span:contains(Success)').length === 0) { debug('点击检查按钮', { step: eq }); checkClick(eq); } } const pro = []; for (const step of steps) { const isSuccess = $(step).find('span:contains(Success)').length > 0; if (isSuccess && action === 'do') { debug('跳过已完成的任务'); continue; } const appLink = $(step).find('a[href*=\'store.steampowered.com/app/\']').attr('href'); if (appLink) { const taskType = $(step).find('a[href*=\'store.steampowered.com/app/\']').text().includes('wishlist') ? 'wishlistLinks' : 'followLinks'; debug('添加 Steam 应用任务', { type: taskType, link: appLink }); if (action === 'undo') { this.socialTasks.steam[taskType].push(appLink); } if (action === 'do') { this.undoneTasks.steam[taskType].push(appLink); } continue; } const curatorLink = $(step).find('a[href*=\'store.steampowered.com/curator/\']').attr('href'); if (curatorLink) { debug('添加 Steam 鉴赏家任务', { link: curatorLink }); if (action === 'undo') { this.socialTasks.steam.curatorLinks.push(curatorLink); } if (action === 'do') { this.undoneTasks.steam.curatorLinks.push(curatorLink); } continue; } const groupLink = $(step).find('a[href*=\'steamcommunity.com/groups/\']').attr('href'); if (groupLink) { debug('添加 Steam 组任务', { link: groupLink }); if (action === 'undo') { this.socialTasks.steam.groupLinks.push(groupLink); } if (action === 'do') { this.undoneTasks.steam.groupLinks.push(groupLink); } continue; } const gidLink = $(step).find('a[href*=\'steamcommunity.com/gid\']').attr('href'); if (gidLink) { debug('处理 Steam GID 链接', { link: gidLink }); pro.push(getRedirectLink(gidLink).then((finalUrl => { if (!finalUrl || !/^https?:\/\/steamcommunity\.com\/groups\//.test(finalUrl)) { debug('无效的 Steam 组链接', { finalUrl: finalUrl }); return false; } debug('添加 Steam 组任务(从 GID)', { link: finalUrl }); if (action === 'undo') { this.socialTasks.steam.groupLinks.push(finalUrl); } if (action === 'do') { this.undoneTasks.steam.groupLinks.push(finalUrl); } }))); } } await Promise.allSettled(pro); debug('任务分类完成'); logStatus.success(); this.undoneTasks = this.uniqueTasks(this.undoneTasks); this.socialTasks = this.uniqueTasks(this.socialTasks); if (window.DEBUG) { console.log('%cAuto-Task[Debug]:', 'color:blue', JSON.stringify(this)); } GM_setValue(`prysTasks-${this.giveawayId}`, { tasks: this.socialTasks, time: (new Date).getTime() }); return true; } catch (error) { debug('任务分类失败', { error: error }); throwError(error, 'Prys.classifyTask'); return false; } } async verifyTask() { try { debug('开始验证任务'); const checks = $('#steps tbody a[id^=check]'); if (checks.length === 0) { debug('没有需要验证的任务'); echoLog({}).success(I18n('allTasksComplete')); return; } const pro = []; for (const check of checks) { const id = $(check).attr('id')?.match(/[\d]+/)?.[0]; if (!id) { debug('跳过无效任务ID'); continue; } const taskDes = $(check).parent()?.prev()?.html()?.trim(); debug('验证任务', { id: id, taskDes: taskDes }); const status = echoLog({ text: `${I18n('verifyingTask')}${taskDes}...` }); pro.push(new Promise((resolve => { this.#checkStep(id, resolve, status); }))); } await Promise.all(pro); debug('所有任务验证完成'); echoLog({}).success(I18n('allTasksComplete')); } catch (error) { debug('验证任务失败', { error: error }); throwError(error, 'Prys.verifyTask'); } } #getGiveawayId() { try { debug('开始获取抽奖ID'); const giveawayId = window.location.search.match(/id=([\d]+)/)?.[1]; if (giveawayId) { this.giveawayId = giveawayId; debug('获取抽奖ID成功', { giveawayId: giveawayId }); return true; } debug('获取抽奖ID失败'); echoLog({}).error(I18n('getFailed', 'GiveawayId')); return false; } catch (error) { debug('获取抽奖ID出错', { error: error }); throwError(error, 'Prys.getGiveawayId'); return false; } } async #checkLeftKey() { try { debug('检查剩余密钥'); if (!globalOptions.other.checkLeftKey) { debug('跳过密钥检查'); return true; } const leftKey = $('#header').text().match(/([\d]+).*?prize.*?left/)?.[1]; debug('检查剩余密钥数量', { leftKey: leftKey }); if (leftKey !== '0') { return true; } debug('没有剩余密钥,显示确认对话框'); const {value: value} = await Swal.fire({ icon: 'warning', title: I18n('notice'), text: I18n('noKeysLeft'), confirmButtonText: I18n('confirm'), cancelButtonText: I18n('cancel'), showCancelButton: true }); if (value) { debug('用户确认关闭窗口'); window.close(); } return true; } catch (error) { debug('检查剩余密钥失败', { error: error }); throwError(error, 'Prys.checkLeftKey'); return false; } } #checkLogin() { try { debug('检查登录状态'); if (!globalOptions.other.checkLogin) { debug('跳过登录检查'); return true; } if ($('button:contains("Sign")').length > 0) { debug('未登录'); echoLog({}).warning(I18n('needLogin')); } debug('登录检查完成'); return true; } catch (error) { debug('检查登录失败', { error: error }); throwError(error, 'Prys.checkLogin'); return false; } } #checkStep(step, resolve, status, captcha = null) { try { debug('开始检查步骤', { step: step, hasCaptcha: !!captcha }); if (step !== 'captcha') { debug('更新步骤状态为检查中'); $(`#check${step}`).replaceWith(`<span id="check${step}"><i class="fa fa-refresh fa-spin fa-fw"></i> Checking...</span>`); } debug('发送检查请求'); $.post('/api/check_step', { step: step, id: getURLParameter('id'), 'g-recaptcha-response': captcha }, (json => { resolve(); debug('收到检查响应', { success: json.success }); if (step !== 'captcha') { if (json.success) { debug('步骤检查成功'); $(`#check${step}`).replaceWith(`<span class="text-success" id="check${step}"><i class="fa fa-check"></i> Success</span>`); status.success(); } else { debug('步骤检查失败'); $(`#check${step}`).replaceWith(`<a id="check${step}" href="javascript:checkStep(${step})"><i class="fa fa-question"></i> Check</a>`); status.error(json.response?.error || 'Error'); } } if (!json.response) { return; } if (json.response.prize) { debug('获得奖品', { prize: json.response.prize }); showAlert('success', `Here is your prize:<h1 role="button" align="middle" style="word-wrap: break-word;">${json.response.prize}</h2>`); } if (!json.response.captcha) { return; } debug('需要验证码'); if (json.success) { showAlert('info', json.response.captcha); } else { showAlert('warning', json.response.captcha); } captchaCheck(); })).fail((() => { resolve(); debug('请求失败'); $(`#check${step}`).replaceWith(`<a id="check${step}" href="javascript:checkStep(${step})"><i class="fa fa-question"></i> Check</a>`); status.error('Error:0'); })); } catch (error) { debug('检查步骤失败', { error: error }); throwError(error, 'prys.checkStep'); resolve(false); } } } const Websites = [ FreeAnyWhere, GiveawaySu, Indiedb, Keyhub, Givekey, GiveeClub, OpiumPulses, Keylol, Opquests, Gleam, SweepWidget, Setting, History, GiveawayHopper, Prys ]; const generateFormHtml = options => { debug('开始生成网站选项表单HTML', { options: options }); const tableRows = Object.entries(options).map((([option, value]) => `\n <tr>\n <td>${option}</td>\n <td>\n <input\n class="editOption"\n type="text"\n name="${option}"\n value="${value}"\n />\n </td>\n </tr>\n `)).join(''); const formHtml = `\n <form id="websiteOptionsForm" class="auto-task-form">\n <table class="auto-task-table">\n <thead>\n <tr>\n <td>${I18n('option')}</td>\n <td>${I18n('value')}</td>\n </tr>\n </thead>\n <tbody>\n ${tableRows}\n </tbody>\n </table>\n </form>\n `; debug('表单HTML生成完成'); return formHtml; }; const saveOptions = (website, options, formValues) => { debug('开始保存网站选项', { website: website, formValues: formValues }); formValues.forEach((({name: name, value: value}) => { options[name] = value; debug('更新选项值', { name: name, value: value }); })); GM_setValue(`${website}Options`, options); debug('选项已保存到存储', { website: website }); Swal.fire({ title: I18n('changeWebsiteOptionsSuccess'), icon: 'success' }); }; const websiteOptions = async (website, options) => { try { debug('开始设置网站选项', { website: website }); if (!website || typeof website !== 'string') { debug('无效的网站参数', { website: website }); throw new Error('Invalid website parameter'); } if (!options || typeof options !== 'object') { debug('无效的选项参数', { options: options }); throw new Error('Invalid options parameter'); } debug('显示选项编辑对话框'); const result = await Swal.fire({ title: I18n('websiteOptions'), html: generateFormHtml(options), showConfirmButton: true, confirmButtonText: I18n('save'), showCancelButton: true, cancelButtonText: I18n('close') }); if (result.isConfirmed) { debug('用户确认保存选项'); const form = document.getElementById('websiteOptionsForm'); if (!form) { debug('未找到表单元素'); throw new Error('Form element not found'); } const formData = $('#websiteOptionsForm').serializeArray(); debug('获取表单数据', { formData: formData }); saveOptions(website, options, formData); } else { debug('用户取消保存选项'); } } catch (error) { debug('设置网站选项时发生错误', { error: error }); throwError(error, 'websiteOptions'); } }; const UPDATE_LINKS = { github: 'https://github.com/HCLonely/auto-task/raw/main/', jsdelivr: 'https://cdn.jsdelivr.net/gh/HCLonely/auto-task@main/', standby: 'https://auto-task.hclonely.com/' }; const checkUpdate = async (updateLink, auto) => { try { debug('开始检查更新', { updateLink: updateLink, auto: auto }); const checkUrl = `${updateLink}package.json?time=${Date.now()}`; debug('构建检查URL', { checkUrl: checkUrl }); const {result: result, statusText: statusText, status: status, data: data} = await httpRequest({ url: checkUrl, responseType: 'json', method: 'GET', timeout: 3e4 }); if (result === 'Success' && data?.response?.version) { debug('成功获取更新信息', { version: data.response.version }); return data.response; } if (!auto) { const errorMessage = data?.response?.version ? `${I18n('checkUpdateFailed')}[${data?.statusText}(${data?.status})]` : `${I18n('checkUpdateFailed')}[${result}:${statusText}(${status})]`; debug('检查更新失败', { errorMessage: errorMessage }); echoLog({}).error(errorMessage); } else { debug('自动检查更新失败', { result: result, statusText: statusText, status: status }); } return false; } catch (error) { debug('检查更新发生错误', { error: error }); throwError(error, 'checkUpdate'); return false; } }; const hasNewVersion = (currentVersion, remoteVersion) => { try { debug('开始比较版本号', { currentVersion: currentVersion, remoteVersion: remoteVersion }); const [currentRealVersion] = currentVersion.split('-'); const [remoteRealVersion, isPreview] = remoteVersion.split('-'); if (isPreview && !globalOptions.other.receivePreview) { debug('不接收预览版本', { isPreview: isPreview }); return false; } const currentVersionParts = currentRealVersion.split('.').map(Number); const remoteVersionParts = remoteRealVersion.split('.').map(Number); debug('版本号解析', { currentVersionParts: currentVersionParts, remoteVersionParts: remoteVersionParts }); for (let i = 0; i < 3; i++) { if (remoteVersionParts[i] > currentVersionParts[i]) { debug('发现新版本', { position: i, current: currentVersion, remote: remoteVersion }); return true; } if (remoteVersionParts[i] < currentVersionParts[i]) { debug('远程版本较旧', { position: i, current: currentVersion, remote: remoteVersion }); return false; } } debug('版本号相同'); return false; } catch (error) { debug('比较版本号时发生错误', { error: error }); throwError(error, 'compareVersion'); return false; } }; const getUpdateLink = updateSource => { debug('获取更新链接', { updateSource: updateSource }); const source = updateSource.toLowerCase(); const link = UPDATE_LINKS[source] || UPDATE_LINKS.github; debug('选择的更新链接', { source: source, link: link }); return link; }; const showUpdateInfo = (packageData, currentVersion, updateLink) => { debug('准备显示更新信息', { currentVersion: currentVersion, newVersion: packageData.version }); if (hasNewVersion(currentVersion, packageData.version)) { const scriptUrl = `${updateLink}dist/${GM_info.script.name}.user.js`; debug('发现新版本,显示更新通知', { scriptUrl: scriptUrl }); echoLog({ html: `<li><font>${I18n('newVersionNotice', packageData.version, scriptUrl)}</font></li>` }); const changeList = packageData.change?.map((change => `<li>${change}</li>`)).join('') || ''; debug('显示更新日志', { changeListLength: packageData.change?.length }); echoLog({ html: `<li>${I18n('updateText', packageData.version)}</li><ol class="update-text">${changeList}<li>${I18n('updateHistory')}</li></ol>` }); } else { debug('当前已是最新版本'); } }; const updateChecker = async () => { try { debug('开始检查更新流程'); const currentVersion = GM_info.script.version; const updateSource = globalOptions.other.autoUpdateSource; debug('当前配置', { currentVersion: currentVersion, updateSource: updateSource }); let packageData = false; if ([ 'github', 'jsdelivr', 'standby' ].includes(updateSource.toLowerCase())) { debug('使用指定的更新源', { updateSource: updateSource }); const updateLink = getUpdateLink(updateSource); packageData = await checkUpdate(updateLink, false); } else { debug('按优先级尝试不同的更新源'); for (const source of [ 'github', 'jsdelivr', 'standby' ]) { debug('尝试更新源', { source: source }); packageData = await checkUpdate(UPDATE_LINKS[source], true); if (packageData) { debug('成功获取更新信息', { source: source }); break; } } } if (!packageData) { debug('所有更新源检查失败'); echoLog({}).error(I18n('checkUpdateFailed')); return; } showUpdateInfo(packageData, currentVersion, getUpdateLink(updateSource)); } catch (error) { debug('更新检查过程发生错误', { error: error }); throwError(error, 'updateChecker'); } }; const byteToHex = []; for (let i = 0; i < 256; ++i) { byteToHex.push((i + 256).toString(16).slice(1)); } function unsafeStringify(arr, offset = 0) { return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); } let getRandomValues; const rnds8 = new Uint8Array(16); function rng() { if (!getRandomValues) { if (typeof crypto === 'undefined' || !crypto.getRandomValues) { throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported'); } getRandomValues = crypto.getRandomValues.bind(crypto); } return getRandomValues(rnds8); } const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto); var native = { randomUUID: randomUUID }; function v4(options, buf, offset) { if (native.randomUUID && true && !options) { return native.randomUUID(); } options = options || {}; const rnds = options.random ?? options.rng?.() ?? rng(); if (rnds.length < 16) { throw new Error('Random bytes length must be >= 16'); } rnds[6] = rnds[6] & 15 | 64; rnds[8] = rnds[8] & 63 | 128; return unsafeStringify(rnds); } function fawExtension() { const hostname = window.location.hostname; function IsJsonString(str) { try { JSON.parse(str); } catch (e) { return false; } return true; } $(window).on('load', (function() { console.log('👌 gamesforfarm extension'); if (hostname == 'freeanywhere.net' || hostname == 'give.gamesforfarm.local' || hostname == 'gamesforfarm-testing.ru') { const steam = $('.games_for_farm_site').data('steam'); const avatar = $('.games_for_farm_site').data('avatar'); const name = $('.games_for_farm_site').data('name'); const lang = $('.games_for_farm_site').data('lang'); let need_update = true; GM_addValueChangeListener('FAW_STORAGE', (function(newValue, oldValue) { if (need_update == false) { return; } GM_getValue('FAW_STORAGE', (function(STORAGE) { $.ajax({ type: 'POST', url: '/php/extension/user_data_update.php', data: { extension: JSON.stringify(STORAGE) }, success: function(data) {} }); })); })); const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (Object.keys(STORAGE).length === 0 || !STORAGE['tasks'] || !STORAGE['user'] || !STORAGE['games'] || !STORAGE['settings']) { GM_deleteValue('FAW_STORAGE'); let new_storage = {}; new_storage['tasks'] = {}; new_storage['user'] = {}; new_storage['games'] = {}; new_storage['settings'] = {}; if (steam) { new_storage['user']['steam'] = steam; } if (avatar) { new_storage['user']['avatar'] = avatar; } if (name) { new_storage['user']['name'] = name; } if (lang) { new_storage['user']['lang'] = lang; } if (steam) { $.ajax({ type: 'POST', url: '/php/extension/user_games_get.php', data: { steam: steam }, success: function(data) { if (data != '' && IsJsonString(data)) { const user_games = JSON.parse(data); new_storage['games'] = user_games; new_storage['settings']['game_update'] = parseInt(Date.now() / 1e3); GM_setValue('FAW_STORAGE', new_storage); } } }); } else { GM_setValue('FAW_STORAGE', new_storage); } } if (STORAGE['user'] && STORAGE['settings'] && STORAGE['user']['steam']) { if (!STORAGE['settings']['game_update']) { STORAGE['settings']['game_update'] = 0; } let time_now = parseInt(Date.now() / 1e3); let time_update = parseInt(STORAGE['settings']['game_update']); if (time_now - time_update > 60 * 60 * 24) { $.ajax({ type: 'POST', url: '/php/extension/user_games_get.php', data: { steam: STORAGE['user']['steam'] }, success: function(data) { if (data != '' && IsJsonString(data)) { const games = JSON.parse(data); STORAGE['games'] = games; STORAGE['settings']['game_update'] = parseInt(Date.now() / 1e3); GM_setValue('FAW_STORAGE', STORAGE); } } }); } } if (steam && STORAGE['user']) { if (!STORAGE['user']['steam']) { STORAGE['user']['steam'] = steam; } if (STORAGE['user']['steam'] != steam) { $.ajax({ type: 'POST', url: '/php/extension/user_data_get.php', data: { steam: steam }, success: function(data) { if (!data) { return; } const db_storage = JSON.parse(data); if (db_storage) { GM_deleteValue('FAW_STORAGE'); need_update = false; GM_setValue('FAW_STORAGE', db_storage); setTimeout((function() { need_update = true; }), 100); } } }); } else { if (avatar) { STORAGE['user']['avatar'] = avatar; } if (name) { STORAGE['user']['name'] = name; } if (lang) { STORAGE['user']['lang'] = lang; } GM_setValue('FAW_STORAGE', STORAGE); } } if (STORAGE['tasks']) { let update_tasks = []; let is_update = false; let time_now = parseInt(Date.now() / 1e3); $.each(STORAGE['tasks'], (function(index, val) { if (val['time'] && time_now - parseInt(val['time']) > 2 * 60 * 60) { is_update = true; return; } update_tasks.push(val); })); if (is_update == true) { STORAGE['tasks'] = update_tasks; GM_setValue('FAW_STORAGE', STORAGE); } } if (STORAGE['discord']) { if (STORAGE['discord'] && STORAGE['discord'].length > 0) { $.ajax({ type: 'POST', url: '/php/extension/discord_levels_update.php', data: { discord: JSON.stringify(STORAGE['discord']) }, success: function(data) { if (data.indexOf('success') != -1) { alert('Данные discord уровней обновлены'); delete STORAGE['discord']; GM_setValue('FAW_STORAGE', STORAGE); } else { alert('Возникла ошибка при обновлении discord уровней'); delete STORAGE['discord']; GM_setValue('FAW_STORAGE', STORAGE); } }, error: function() { alert('Возникла ошибка при обновлении discord уровней'); delete STORAGE['discord']; GM_setValue('FAW_STORAGE', STORAGE); } }); } } function check_tasks_button() { let tasks_done = true; $('.game__content-tasks__task').each((function(index, el) { if ($(this).hasClass('done') == false) { tasks_done = false; } })); if (tasks_done == true) { $('.js-get-key').removeClass('inactive'); } else { $('.js-get-key').addClass('inactive'); } } if ($('.games_for_farm_extension.work').length > 0) { $('.games_for_farm_extension.not_work').remove(); $('.games_for_farm_extension.work').slideDown(200); } if ($('.game__content-tasks__task .task-check-extension').length > 0) { $('.task-check-extension').removeClass('js-extentions-modal'); $('.game__content-tasks__task[data-extension=\'1\'] .task-link a').removeClass('js-extentions-modal'); $('.game__content-tasks__task .task-check-extension').on('click', (function(event) { event.preventDefault(); let $button = $(this); if ($button.hasClass('loading')) { return; } let $parrent = $(this).parent('.game__content-tasks__task'); const type = $parrent.data('type'); const id = $parrent.data('id'); const data = $parrent.data('data'); const extension = $parrent.data('extension'); if (extension == false) { return; } $button.addClass('loading'); const STORAGE = GM_getValue('FAW_STORAGE') || {}; $.ajax({ type: 'POST', url: '/php/extension/user_data_update.php', data: { extension: JSON.stringify(STORAGE) }, success: function(update) { const getTime_start = (new Date).getTime(); $.ajax({ type: 'POST', url: '/php/extension/user_task_update.php', data: { id: id, type: type, data: data }, success: function(data) { const getTime_end = (new Date).getTime(); console.log('👌 checking task in ' + (getTime_end - getTime_start) + ' ms'); if (data.indexOf('good') != -1) { setTimeout((function() { $parrent.addClass('done'); $parrent.removeClass('error'); $button.removeClass('loading'); check_tasks_button(); }), 1250); } else if (data.indexOf('bad') != -1) { setTimeout((function() { $parrent.addClass('error'); $parrent.removeClass('done'); $button.removeClass('loading'); check_tasks_button(); }), 1250); } else { setTimeout((function() { $parrent.removeClass('error'); $parrent.removeClass('done'); $button.removeClass('loading'); check_tasks_button(); }), 1250); } }, error: function() { setTimeout((function() { $parrent.removeClass('error'); $parrent.removeClass('done'); $button.removeClass('loading'); check_tasks_button(); }), 1250); } }); }, error: function() { setTimeout((function() { $parrent.removeClass('error'); $parrent.removeClass('done'); $button.removeClass('loading'); check_tasks_button(); }), 1250); } }); })); } } function storage_tasks_update(tasks, type, value, action) { let result = []; let is_find = false; $.each(tasks, (function(index, val) { if (!val['type'] || !val['data']) { return; } if (val['type'] == type && val['data'] == value) { is_find = true; return; } result.push(val); })); const obj = {}; switch (action) { case 'add': if (is_find == true) { return; } const task = {}; task['type'] = type; task['data'] = value; task['time'] = parseInt(Date.now() / 1e3); result.push(task); obj['tasks'] = result; GM_setValue('FAW_STORAGE', obj); break; case 'remove': if (is_find == false) { return; } obj['tasks'] = result; GM_setValue('FAW_STORAGE', obj); } } if (hostname == 'store.steampowered.com' || hostname == 'steamcommunity.com') { if (document.querySelector('span[id^=\'CuratorUnFollowBtn_\']')) { const curator_id = $('span[id^=\'CuratorUnFollowBtn_\']').attr('id').split('_')[1]; const follow_btn = '#CuratorFollowBtn_' + curator_id; const unfollow_btn = '#CuratorUnFollowBtn_' + curator_id; const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } const follow = $(follow_btn).css('display'); const unfollow = $(unfollow_btn).css('display'); if (unfollow && unfollow == 'none') { storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'remove'); } if (follow && follow == 'none') { storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'add'); } $(follow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'add'); })); $(unfollow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_curator_sub', curator_id, 'remove'); })); } if (document.querySelector('.followStatsBlock')) { const user_id = $('#HeaderUserInfoName a').attr('href').split('/').pop(); const follow_btn = '#FollowUserOptionAdd'; const unfollow_btn = '#FollowUserOptionFollowing, .followOption.remove'; const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } const follow = $(follow_btn).css('visibility'); const unfollow = $(unfollow_btn).css('visibility'); if (unfollow && unfollow == 'hidden') { storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'remove'); } if (follow && follow == 'hidden') { storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'add'); } $(follow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'add'); })); $(unfollow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_guides_sub', user_id, 'remove'); })); } if (document.querySelector('#ScrollingItemControls')) { const manual_id = $('#PublishedFileFavorite input[name=\'id\']').val(); const follow_btn = '#FavoriteItemOptionAdd'; const unfollow_btn = '#FavoriteItemOptionFavorited, .favoriteOption.removefavorite'; const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } const follow = $(follow_btn).css('visibility'); const unfollow = $(unfollow_btn).css('visibility'); if (unfollow && unfollow == 'hidden') { storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'remove'); } if (follow && follow == 'hidden') { storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'add'); } $(follow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'add'); })); $(unfollow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_manual_favourite', manual_id, 'remove'); })); } if (document.querySelector('#queueBtnFollow')) { const game_id = $('.game_page_background').data('miniprofile-appid'); const follow_btn = '#queueBtnFollow .queue_btn_inactive'; const unfollow_btn = '#queueBtnFollow .queue_btn_active'; const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } const follow = $(follow_btn).css('display'); const unfollow = $(unfollow_btn).css('display'); if (unfollow && unfollow == 'none') { storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'remove'); } if (follow && follow == 'none') { storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'add'); } $(follow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'add'); })); $(unfollow_btn).on('click', (function(event) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'steam_game_sub', game_id, 'remove'); })); } } if (hostname == 'www.youtube.com' || hostname == 'm.youtube.com') { setInterval((function() { if (document.querySelector('yt-subscribe-button-view-model')) { const channel_id = $('meta[itemprop="identifier"]').attr('content'); const subscribe = [ 'Подписаться', 'Падпісацца', 'Підписатися', 'Abonnieren', 'Subscribe', 'Suscribirse', 'Mag-subscribe', 'S\'abonner', 'Iscriviti', 'Subskrybuj', 'Subscrever', 'Abonează-te', '订阅', 'チャンネル登録', '訂閱' ]; const subscribed = [ 'Вы подписаны', 'Вы падпісаны', 'Ви підписалися', 'Abonniert', 'Subscribed', 'Suscrito', 'Naka-subscribe', 'Abonné', 'Iscritto', 'Subskrybujesz', 'Subscrito', 'Abonat(ă)', '已订阅', '登録済み', '已訂閱' ]; const $parent = $('yt-subscribe-button-view-model'); const text = $parent.text(); if (subscribe.indexOf(text) != -1 || subscribed.indexOf(text) != -1) { if (subscribe.indexOf(text) != -1) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'remove'); } if (subscribed.indexOf(text) != -1) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'add'); } } else { if ($('.ytSubscribePlusButtonViewModelHost').length != 0) { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'add'); } else { const color = $parent.find('button').css('color'); if (color == '#0f0f0f' || color == 'rgb(15, 15, 15)') { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'remove'); } if (color == '#f1f1f1' || color == 'rgb(241, 241, 241)') { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'youtube_channel_sub', channel_id, 'add'); } } } } if (document.querySelector('.ytLikeButtonViewModelHost')) { let video_id; if (hostname == 'm.youtube.com') { video_id = $('link[rel="canonical"]').attr('href').split('v=').pop(); } else { video_id = $('meta[itemprop="identifier"]').attr('content'); } if ($('.ytLikeButtonViewModelHost button').attr('aria-pressed') == 'false') { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'youtube_video_like', video_id, 'remove'); } if ($('.ytLikeButtonViewModelHost button').attr('aria-pressed') == 'true') { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['tasks']) { return; } storage_tasks_update(STORAGE['tasks'], 'youtube_video_like', video_id, 'add'); } } }), 600); } })); if (hostname == 'mee6.xyz') { var message = false; $(document).on('keydown', '', (function(event) { if (event.code == 'End') { let USERS = []; $('.md\\:block').each((function(index, el) { const user = {}; user['name'] = $(this).find('.justify-start p').text(); user['level'] = $(this).find('.leaderboardPlayerStat .items-center').text(); USERS.push(user); if (user['level'] == 0) { const obj = {}; obj['discord'] = USERS; GM_setValue('FAW_STORAGE', obj); if (message == false) { alert('Можно переходить на freeanywhere.net'); message = true; } return false; } })); } })); } if (hostname == 'gamesforfarm.com') { const STORAGE = GM_getValue('FAW_STORAGE') || {}; if (!STORAGE['settings']) { return; } if (!STORAGE['games']) { return; } if (STORAGE['settings']['hide_games'] && STORAGE['settings']['hide_games'] == true) { $('.product__item').each((function(index, el) { const image = $(this).find('.product__box-image img').data('src'); const cart = $(this).find('.product__box-props a').attr('href'); if (image && image.indexOf('/apps/') != -1) { const id = parseInt(image.split('/apps/')[1].split('/')[0]); if (id && isNaN(id) == false) { if (STORAGE['games'][id]) { $(this).css('opacity', '.2'); } } } else if (cart && cart.indexOf('/app/') != -1) { const id = parseInt(cart.split('/app/')[1].split('/')[0]); if (id && isNaN(id) == false) { if (STORAGE['games'][id]) { $(this).css('opacity', '.2'); } } } })); } } } try { consoleLogHook(); } catch (error) { console.error('Auto-Task[Warning]: consoleLogHook 初始化失败', error); } window.STYLE = GM_addStyle(style + GM_getResourceText('style')); window.DEBUG = !!globalOptions.other?.debug; window.TRACE = !!globalOptions.other?.debug && typeof console.trace === 'function'; const handleTwitchAuth = async () => { debug('开始处理Twitch认证'); const authToken = Cookies.get('auth-token'); const isLogin = !!Cookies.get('login'); if (isLogin) { const authData = { authToken: authToken, clientVersion: window.__twilightBuildID, clientId: window.commonOptions?.headers?.['Client-ID'], deviceId: window.commonOptions?.headers?.['Device-ID'], clientSessionId: window.localStorage.local_storage_app_session_id.replace(/"/g, '') }; GM_setValue('twitchAuth', authData); window.close(); await Swal.fire('', I18n('closePageNotice')); } else { await Swal.fire('', I18n('needLogin')); } }; const handleRedditAuth = async () => { debug('开始处理Reddit认证'); const betaButton = $('#redesign-beta-optin-btn'); if (betaButton.length > 0) { betaButton[0].click(); return; } window.close(); await Swal.fire('', I18n('closePageNotice')); }; const handleDiscordAuth = async () => { debug('开始处理Discord认证'); const LocalStorage = window.localStorage; const allLocalStorage = getAllLocalStorageAsObjects(LocalStorage); const discordAuth = allLocalStorage.token; if (discordAuth && discordAuth.length > 0) { const browserInfo = await browser.getInfo(); GM_setValue('discordAuth', { auth: discordAuth, xSuperProperties: window.btoa(JSON.stringify({ os: browserInfo.system, browser: browserInfo.browser, device: '', system_locale: browserInfo.language, ...allLocalStorage.deviceProperties || {}, browser_user_agent: navigator.userAgent, browser_version: browserInfo.browserVersion, os_version: browserInfo.systemVersion, referrer: '', referring_domain: '', referrer_current: '', referring_domain_current: '', release_channel: 'stable', client_build_number: unsafeWindow.GLOBAL_ENV.BUILD_NUMBER, client_event_source: null, has_client_mods: false, client_launch_id: v4(), client_heartbeat_session_id: allLocalStorage.LAST_CLIENT_HEARTBEAT_SESSION?.uuid, client_app_state: 'focused' })) }); window.close(); Swal.fire('', I18n('closePageNotice')); } else { Swal.fire({ text: I18n('getDiscordAuthFailed'), icon: 'error' }); } }; const handleSteamStoreAuth = async () => { debug('开始处理Steam商店认证'); const storeSessionID = document.body.innerHTML.match(/g_sessionID = "(.+?)";/)?.[1]; if (storeSessionID) { GM_deleteValue('ATv4_updateStoreAuth'); GM_setValue('steamStoreAuth', { storeSessionID: storeSessionID }); window.close(); await Swal.fire('', I18n('closePageNotice')); } else { await Swal.fire({ title: 'Error: Get "sessionID" failed', icon: 'error' }); } }; const handleSteamCommunityAuth = async () => { debug('开始处理Steam社区认证'); const steam64Id = document.body.innerHTML.match(/g_steamID = "(.+?)";/)?.[1]; const communitySessionID = document.body.innerHTML.match(/g_sessionID = "(.+?)";/)?.[1]; if (steam64Id && communitySessionID) { GM_deleteValue('ATv4_updateCommunityAuth'); GM_setValue('steamCommunityAuth', { steam64Id: steam64Id, communitySessionID: communitySessionID }); window.close(); await Swal.fire('', I18n('closePageNotice')); } else { setTimeout((async () => { await Swal.fire({ title: 'Error: Get "sessionID" failed', icon: 'error' }); }), 3e3); } }; const initializeUI = website => { debug('初始化UI元素', { website: website.name }); const $body = $('body'); $body.append(`\n <div id="auto-task-info"\n style="display:${globalOptions.other.defaultShowLog ? 'block' : 'none'};\n ${globalOptions.position.logSideX}:${globalOptions.position.logDistance.split(',')[0]}px;\n ${globalOptions.position.logSideY}:${globalOptions.position.logDistance.split(',')[1]}px;">\n </div>\n <div id="auto-task-buttons"\n style="display:${globalOptions.other.defaultShowButton ? 'block' : 'none'};\n ${globalOptions.position.buttonSideX}:${globalOptions.position.buttonDistance.split(',')[0]}px;\n ${globalOptions.position.buttonSideY}:${globalOptions.position.buttonDistance.split(',')[1]}px;">\n </div>\n <div class="show-button-div"\n style="display:${globalOptions.other.defaultShowButton ? 'none' : 'block'};\n ${globalOptions.position.showButtonSideX}:${globalOptions.position.showButtonDistance.split(',')[0]}px;\n ${globalOptions.position.showButtonSideY}:${globalOptions.position.showButtonDistance.split(',')[1]}px;">\n <a class="auto-task-website-btn"\n href="javascript:void(0);"\n target="_self"\n title="${I18n('showButton')}"> </a>\n </div>\n `); const $autoTaskInfo = $('#auto-task-info'); const $autoTaskButtons = $('#auto-task-buttons'); const $showButtonDiv = $('div.show-button-div'); $showButtonDiv.on('click', (() => { $autoTaskButtons.show(); $showButtonDiv.hide(); })); if (website.buttons && $autoTaskButtons.children().length === 0) { $autoTaskButtons.addClass(`${website.name}-buttons`); for (const button of website.buttons) { if (website[button]) { const btnElement = $(`<p><a class="auto-task-website-btn ${website.name}-button" href="javascript:void(0);" target="_self">${I18n(button)}</a></p>`); btnElement.find('a.auto-task-website-btn').on('click', (() => { website[button](); })); $autoTaskButtons.append(btnElement); } } } const hideButtonElement = $(`<p><a class="auto-task-website-btn ${website.name}-button" href="javascript:void(0);" target="_self">${I18n('hideButton')}</a></p>`); hideButtonElement.find('a.auto-task-website-btn').on('click', (() => { $autoTaskButtons.hide(); $showButtonDiv.show(); })); const toggleLogElement = $(`<p><a id="toggle-log" class="auto-task-website-btn ${website.name}-button" href="javascript:void(0);" target="_self" data-status="${globalOptions.other.defaultShowLog ? 'show' : 'hide'}">${globalOptions.other.defaultShowLog ? I18n('hideLog') : I18n('showLog')}</a></p>`); const toggleLog = () => { const $toggleLog = $('#toggle-log'); const status = $toggleLog.attr('data-status'); if (status === 'show') { $autoTaskInfo.hide(); $toggleLog.attr('data-status', 'hide').text(I18n('showLog')); } else { $autoTaskInfo.show(); $toggleLog.attr('data-status', 'show').text(I18n('hideLog')); } }; toggleLogElement.find('a.auto-task-website-btn').on('click', toggleLog); $autoTaskButtons.append(hideButtonElement).append(toggleLogElement); if (website.options) { GM_registerMenuCommand(I18n('changeWebsiteOptions'), (() => { websiteOptions(website.name, website.options); })); } }; const initializeHotkeys = website => { debug('初始化热键', { website: website.name }); keyboardJS.bind(globalOptions.hotKey.doTaskKey, (() => { if (website.doTask) { website.doTask(); } })); keyboardJS.bind(globalOptions.hotKey.undoTaskKey, (() => { if (website.undoTask) { website.undoTask(); } })); keyboardJS.bind(globalOptions.hotKey.toggleLogKey, (() => { const $toggleLog = $('#toggle-log'); const status = $toggleLog.attr('data-status'); const $autoTaskInfo = $('#auto-task-info'); if (status === 'show') { $autoTaskInfo.hide(); $toggleLog.attr('data-status', 'hide').text(I18n('showLog')); } else { $autoTaskInfo.show(); $toggleLog.attr('data-status', 'show').text(I18n('hideLog')); } })); }; const checkSteamASFStatus = async () => { debug('检查Steam ASF状态'); if (!globalOptions.ASF.AsfEnabled || !globalOptions.ASF.AsfIpcUrl || !globalOptions.ASF.AsfIpcPassword) { return; } const stopPlayTime = GM_getValue('stopPlayTime', 0) || 0; if (stopPlayTime === 0 || stopPlayTime >= Date.now()) { return; } const stopPlayTimeMinutes = Math.floor((Date.now() - stopPlayTime) / 6e4); await Swal.fire({ title: I18n('stopPlayTimeTitle'), text: I18n('stopPlayTimeText', stopPlayTimeMinutes.toString()), icon: 'warning', confirmButtonText: I18n('confirm') }); let steamASF = new SteamASF(globalOptions.ASF); try { const isInitialized = await steamASF.init(); if (!isInitialized) { return; } const isGamesStopped = await steamASF.stopPlayGames(); if (!isGamesStopped) { return; } const taskLink = GM_getValue('taskLink', []) || []; for (const link of taskLink) { GM_openInTab(link, { active: true }); } GM_setValue('stopPlayTime', 0); GM_setValue('playedGames', []); GM_setValue('taskLink', []); } catch (error) { console.error('SteamASF operation failed:', error); } finally { steamASF = null; } }; const checkVersionAndNotice = () => { debug('检查版本和通知'); const {scriptHandler: scriptHandler} = GM_info; if (scriptHandler !== 'Tampermonkey') { debug('未知脚本管理器', { scriptHandler: scriptHandler }); echoLog({}).warning(I18n('unknownScriptHandler')); return; } const [v1, v2] = GM_info.version?.split('.') || []; if (!(parseInt(v1, 10) >= 5 && parseInt(v2, 10) >= 2)) { echoLog({}).error(I18n('versionNotMatched')); } if (!GM_getValue('notice')) { Swal.fire({ title: I18n('swalNotice'), icon: 'warning' }).then((() => { GM_openInTab(I18n('noticeLink'), { active: true }); GM_setValue('notice', (new Date).getTime()); })); echoLog({ html: `<li><font class="warning">${I18n('echoNotice', I18n('noticeLink'))}</font></li>` }).font?.find('a').on('click', (() => { GM_setValue('notice', (new Date).getTime()); })); } }; const loadScript = async () => { debug('主程序入口 loadScript 开始'); if (window.name === 'ATv4_twitchAuth' && window.location.hostname === 'www.twitch.tv') { debug('检测到Twitch认证窗口'); await handleTwitchAuth(); return; } if (window.name === 'ATv4_redditAuth' && window.location.hostname === 'www.reddit.com') { debug('检测到Reddit认证窗口'); await handleRedditAuth(); return; } let website; for (const Website of Websites) { if (Website.test()) { debug('识别到支持的网站', { website: Website.name }); website = new Website; break; } } if (!website) { debug('未识别到支持的网站,脚本停止加载'); console.log('%c%s', 'color:#ff0000', 'Auto-Task[Warning]: 脚本停止加载,当前网站不支持!'); return; } if (website.before) { debug('执行网站 before 钩子'); await website.before(); } initializeUI(website); initializeHotkeys(website); if (website.after) { debug('执行网站 after 钩子'); await website.after(); } if (website.name !== 'Setting') { debug('注册全局菜单命令'); GM_registerMenuCommand(I18n('changeGlobalOptions'), (() => { changeGlobalOptions('swal'); })); GM_registerMenuCommand(I18n('settingPage'), (() => { GM_openInTab('https://auto-task.hclonely.com/setting.html', { active: true }); })); } debug('脚本加载完成'); console.log('%c%s', 'color:#1bbe1a', 'Auto-Task[Load]: 脚本加载完成'); if (window.DEBUG) { echoLog({}).warning(I18n('debugModeNotice')); } await checkSteamASFStatus(); checkVersionAndNotice(); updateChecker(); }; try { debug('主程序入口开始', { hostname: window.location.hostname, windowName: window.name }); if ([ 'freeanywhere.net', 'give.gamesforfarm.local', 'gamesforfarm-testing.ru', 'store.steampowered.com', 'steamcommunity.com', 'www.youtube.com', 'm.youtube.com', 'mee6.xyz', 'gamesforfarm.com' ].includes(window.location.hostname) && $('.task-check-extension').length > 0) { debug('检测到freeanywhere.com,加载扩展'); fawExtension(); } if (window.location.hostname === 'discord.com') { if (window.name === 'ATv4_discordAuth') { debug('检测到Discord认证窗口'); handleDiscordAuth(); } else { debug('检测到Discord主站'); const discordAuth = window.localStorage?.getItem('token')?.replace(/^"|"$/g, ''); if (discordAuth && discordAuth.length > 0) { debug('获取到Discord认证token'); GM_setValue('discordAuth', { auth: discordAuth }); } } } else if (window.location.hostname === 'opquests.com') { debug('检测到opquests.com,加载主脚本'); loadScript(); } else if ((window.name === 'ATv4_updateStoreAuth' || GM_getValue('ATv4_updateStoreAuth')) && window.location.host === 'store.steampowered.com') { debug('检测到Steam商店认证窗口'); $((() => { if ($('[data-miniprofile]').length === 0) { return; } handleSteamStoreAuth(); })); } else if ((window.name === 'ATv4_updateCommunityAuth' || GM_getValue('ATv4_updateCommunityAuth')) && window.location.host === 'steamcommunity.com') { debug('检测到Steam社区认证窗口'); $((() => { handleSteamCommunityAuth(); })); } else { if (window.location.hostname === 'key-hub.eu') { debug('检测到key-hub.eu,设置全局变量'); unsafeWindow.keyhubtracker = 1; unsafeWindow.gaData = {}; } debug('加载主脚本'); $(loadScript); } } catch (error) { debug('主程序入口发生异常', { error: error }); } })(Swal, Cookies, browser, util, dayjs, keyboardJS);