Steam自动打赏 — 极速多账户版
// ==UserScript== // @name Auto_Award_Muli // @name:zh-CN Steam自动打赏【极速多账户版】 // @namespace https://blog.chrxw.com // @contributionURL https://afdian.com/@chr233 // @version 2.3 // @description Steam自动打赏 — 极速多账户版 // @description:zh-CN Steam自动打赏 — 极速多账户版 // @author Chr_ // @include /^https:\/\/steamcommunity\.com\/id\/[^/]+/?$/ // @include /^https:\/\/steamcommunity\.com\/profiles\/\d+/?$/ // @connect steamcommunity.com // @connect steampowered.com // @connect api.steampowered.com // @license AGPL-3.0 // @icon https://blog.chrxw.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_registerMenuCommand // ==/UserScript== (() => { 'use strict'; // 多语言 const LANG = { 'ZH': { 'done': '完成', 'changeLang': '修改语言', 'langName': '中文', 'operating': '操作进行中……', 'runningLog': '运行日志', 'close': '关闭', 'logWaring': '【日志不会保存, 打赏记录可以在【打赏历史】中查看】', 'botListTitle': '【机器人账户管理】【ID | 账户名 | SteamID | 点数余额】', 'addCurrentAccount': '添加当前账号', 'delSelectAccount': '删除选中账号', 'reloadAccountPoints': '刷新所有账号点数', 'historyTitle': '【点数打赏历史记录】【ID | 昵称 | SteamID | 收到点数】', 'profile': '个人资料', 'deleteSelectedHistory': '删除选中', 'clearHistory': '清空历史', 'reloadHistory': '刷新历史', 'feedBack': '作者', 'feedBackTitle': '觉得好用也欢迎给我打赏', 'notSelected': '---自动使用当前登录账号---', 'steamID64': 'Steam 64位 ID', 'awardPoints': '打赏点数(收到)', 'recommands': '评测', 'screenshots': '截图', 'artworks': '艺术作品', 'setToCurrentUser': '设置为当前用户', 'calculator': '打赏计算器', 'save': '保存', 'reset': '重置', 'awardHistory': '打赏历史', 'startAward': '开始打赏', 'stopAward': '停止打赏', 'stop': '停止', 'senderBotAccount': '打赏机器人账户: ', 'receverAccount': '被打赏人SteamID: ', 'sendPoints': '打赏点数(收到): ', 'awardPrefer': '打赏类型(优先级从上到下从左到右): ', 'botList': '机器人列表', 'fetchLoginAccount': '获取登陆账户……', 'fetchToken': '获取Token……', 'fetchPoints': '获取点数信息……', 'success': '成功', 'failure': '失败', 'error': '错误', 'confirm': '确认', 'tips': '提示', 'addAccountSuccessTips1': '添加账户成功', 'addAccountSuccessTips2': '当前账户可用点数', 'deleteAccountConfirmTips': '确定要删除选定的账号吗?', 'deleteAccountDoneTips1': '删除了', 'deleteAccountDoneTips2': '个机器人', 'notSelectedAnyBotsTips': '尚未选中任何机器人!', 'currentProcess': '当前进度', 'updateFailed': '更新出错', 'fetchingAccountPoints': '读取账户点数中……', 'allDataLoaded': '所有数据刷新完毕', 'someDataLoadFailed': '部分数据刷新失败, 如果点数显示为【-1】,代表数据刷新失败', 'botListEmpty': '机器人列表为空', 'noBotAccountTips': '-- 无机器人账号, 请使用【➕添加当前账号】自动添加 --', 'awardTaskWasResetTips': '机器人账号已修改, 打赏设置已重置!', 'noHistoryTips': '-- 无历史记录, 执行打赏任务后会自动记录 --', 'steamIDisEmpty': '未填入SteamID!', 'fetchingProfile': '获取个人资料中……', 'fetchingAwardableItems': '获取可打赏项目……', 'fetchError': '读取出错', 'awardableAmount': '可打赏约', 'nickName': '用户名', 'totalPoints': '总计点数', 'calcTips': '根据项目数量计算所得, 不准确', 'profileNotExistsTips': '个人资料不存在, 请检查SteamID是否正确, 或者使用【🤵设置为当前用户】自动获取。', 'profileLoadFailedTips': '网络错误, 读取个人资料失败', 'notSelectedAnyHistoryTips': '未选中历史记录!', 'clearHistoryConfirmTips': '确定要清除打赏历史记录吗?', 'clearHistorySuccess': '清除成功', 'historyListEmpty': '历史记录是空的!', 'deleteHistoryConfirmTips': '确定要删除选定的打赏历史记录吗?', 'deleteR###ltTips1': '删除了', 'deleteR###ltTips2': '条打赏历史记录', 'notSelectedAwardBotsTips': '尚未选择打赏机器人!', 'steamIDEmptyWithTips': '未填写【被打赏人SteamID】, 建议使用【🤵设置为当前用户】功能!', 'steamIDErrorWithTips': '【被打赏人SteamID】格式有误, 建议使用【🤵设置为当前用户】功能!', 'pointsErrorWithTips': '【打赏点数】格式有误, 只能为整数!', 'awardTypeEmptyTips': '请选择【打赏类型】!', 'awardReadyToStartTips': '设置保存成功, 可以【✅开始打赏】了', 'resetConfigConfirmTips': '确定要重置设定吗?', 'configResetSuccessTips': '设置已清除', 'awardTaskDataInvalid': '任务数据非法', 'fetchingTargetProfile': '读取被打赏人个人资料……', 'awardConfig': '打赏设置', 'targetNickName': '被打赏人昵称', 'targetReceivePoints': '预计收到点数', 'targetBot': '打赏机器人', 'taskReadyToStartTips': '打赏任务【2秒】后开始, 点击【⛔停止打赏】可以提前终止操作!', 'taskFailedProfileNotFound': '未找到个人资料, 打赏进程停止!', 'taskAlreadyStartTips': '打赏任务已经开始了!', 'taskEndManually': '打赏任务手动终止, 点击【❌关闭】可以关闭面板.', 'taskNotStart': '打赏任务未开始!', 'running': '运行', 'taskStartPointsSummary': '开始打赏, 剩余打赏 / 预计打赏', 'fetchAwardItemFailedRetry': '获取打赏项目失败, 重试……', 'fetchNoAwardItemSkip': '没有合适的打赏, 跳过……', 'beforeSendAward': '将要打赏', 'itemAndTotal': '项, 总计', 'points': '点', 'sendingAwards': '发送打赏中……', 'fetchSuccessAndFailed': '请求成功 / 请求失败', 'fetchAwardItemFailedRetryIn2Min': '获取打赏项目失败, 【2秒】后重试……', 'awardSuccess': '成功打赏', 'taskFinishedPointsSummary': '打赏完成, 剩余打赏 / 预计打赏', 'updateBotPointsBalance': '更新机器人点数余额……', 'bot': '机器人', 'pointsBalanceUpdat###ccess': '点数余额更新成功, 可用点数', 'lackOfPointsTaskEnd': '点数余额不足, 终止操作', 'pointBalanceUpdateFailed': '点数余额更新失败', 'fetchAwardItemFailedSkip': '获取打赏项目失败, 跳过……', 'taskEndListEmpty': '列表为空, 结束', 'fetchCompletedTotal': '获取成功, 共', 'entries': '个', 'objectID': '项目ID', 'noAwardableObjectSkip': '没有合适的打赏, 跳过', 'willAward': '将要打赏', 'requestsSummary': '请求成功 / 请求失败', 'wait2Seconds': '*等待2秒,防止打赏过多*', 'botDataError': '机器人数据错误, 无法开始打赏!', 'awardTaskFinish': '✅打赏任务完成, 点击【❌关闭】可以关闭面板。', 'awardTaskNotFinish': '⛔打赏任务未完成, 点击【❌关闭】可以关闭面板。', 'cancel': '取消', 'steamStoreNotLogin': '【STEAM商店】未登录,请重新登录', 'parseDataFailedMaybeNetworkError': '解析数据失败, 可能是Token失效或者网络错误', 'typeError': 'type错误', 'networkError': '网络错误', 'parseError': '解析出错', 'importAccount': '手动导入账号', 'importAccountSteamId64': '请输入Steam64位ID', 'importAccountInvalidSteamId64': '请输入正确的SteamID', 'importAccountGetToken': '请访问【 https://store.steampowered.com/pointssummary/ajaxgetasyncconfig 】,并把所有内容粘贴到下面', 'importAccountNickName': '请输入机器人显示昵称', 'importAccountNickNameTips': '手动添加', 'importAccountSuccess': '添加账号成功, 请手动刷新点数', }, 'EN': { 'done': 'Done', 'changeLang': 'Change Language', 'langName': 'English', 'operating': 'Operating……', 'runningLog': 'Log', 'close': 'Close', 'logWaring': '【Log will not save, award history will list in【History】】', 'botListTitle': '【Bot Accounts】【ID | NickName | SteamID | Points Balance】', 'addCurrentAccount': 'Add Account', 'delSelectAccount': 'Del Selected', 'reloadAccountPoints': 'Refresh Points', 'historyTitle': '【History】【ID | NickName | SteamID | Received Points】', 'profile': 'Profile', 'deleteSelectedHistory': 'Del Selected', 'clearHistory': 'Clear', 'reloadHistory': 'Reload', 'feedBack': 'Author', 'feedBackTitle': '觉得好用也欢迎给我打赏', 'notSelected': '---Use Current Account---', 'steamID64': 'Steam 64 ID', 'awardPoints': 'Points (Receive)', 'recommands': 'Recommands', 'screenshots': 'Screenshots', 'artworks': 'Artworks', 'setToCurrentUser': 'Set to page\'s user', 'calculator': 'Calculator', 'save': 'Save', 'reset': 'Reset', 'awardHistory': 'History', 'startAward': 'Start Award', 'stopAward': 'Stop', 'stop': 'Stopped', 'senderBotAccount': 'Award Bot: ', 'receverAccount': 'Target SteamID: ', 'sendPoints': 'Points (Receive): ', 'awardPrefer': 'Award Type (Up to down left to right): ', 'botList': 'Bots List', 'fetchLoginAccount': 'Fetching current logined account……', 'fetchToken': 'Fetching token……', 'fetchPoints': 'Fetching points balance……', 'success': 'Success', 'failure': 'Failure', 'error': 'Error', 'confirm': 'Confirm', 'tips': 'Tips', 'addAccountSuccessTips1': 'Add account successful', 'addAccountSuccessTips2': 'Current account\'s points balance', 'deleteAccountConfirmTips': 'Are sure to delete selected bots?', 'deleteAccountDoneTips1': 'Deleted', 'deleteAccountDoneTips2': 'bots', 'notSelectedAnyBotsTips': 'You have not selected any bot!', 'currentProcess': 'Current process', 'updateFailed': 'Update Failed', 'fetchingAccountPoints': 'Fetching points balance……', 'allDataLoaded': 'All data loaded', 'someDataLoadFailed': 'Some data loaded failed, if some bot\'s balance is 【-1】, it means fetch error', 'botListEmpty': 'Bot list is empty', 'noBotAccountTips': '-- No Bot Account, you can add via【➕Add ACcount】(Loginin required) --', 'awardTaskWasResetTips': 'Bot account had been modified, Award config reseted!', 'noHistoryTips': '-- No History Records, It Will Record When Awarding --', 'steamIDisEmpty': 'You must specify SteamID!', 'fetchingProfile': 'Fetching profile……', 'fetchingAwardableItems': 'Fetching awardable items……', 'fetchError': 'Fetch error', 'awardableAmount': '可打赏约', 'nickName': 'Nickname', 'totalPoints': 'Total points', 'calcTips': 'According to the total number of the items, inaccurate', 'profileNotExistsTips': 'Profile not found, please check if the steamID is correct, or use 【🤵Set to page\'s user】 instead', 'profileLoadFailedTips': 'Network error, fetch profile failed', 'notSelectedAnyHistoryTips': 'No records selected!', 'clearHistoryConfirmTips': 'Are you sure to clear all award records?', 'clearHistorySuccess': 'History cleared', 'historyListEmpty': 'History is empty!', 'deleteHistoryConfirmTips': 'Are you sure to delete seleted award records?', 'deleteR###ltTips1': 'Deleted', 'deleteR###ltTips2': 'history records', 'notSelectedAwardBotsTips': 'No bots selected!', 'steamIDEmptyWithTips': '【Target SteamID】is empty, It is recommended to use【🤵Set to page\'s user】!', 'steamIDErrorWithTips': '【Target SteamID】is invalid, It is recommended to use【🤵Set to page\'s user】!', 'pointsErrorWithTips': '【Points】is invalid, only integers are accepted!', 'awardTypeEmptyTips': 'Please select【Award Type】!', 'awardReadyToStartTips': 'Config saved, it is ready to【✅Start Award】', 'resetConfigConfirmTips': 'Are you sure to reset the config?', 'configResetSuccessTips': 'Config reseted!', 'awardTaskDataInvalid': 'Task data invalid', 'fetchingTargetProfile': 'Fetching target user\'s profile……', 'awardConfig': 'Award config', 'targetNickName': 'Target user\'s nickname', 'targetReceivePoints': 'Expected points received', 'targetBot': 'Selected bot', 'taskReadyToStartTips': 'Award task will start in 【2 seconds】, click【⛔Stop】to interrupt award task!', 'taskFailedProfileNotFound': 'Profile not found, award task end!', 'taskAlreadyStartTips': 'Award task is already running!', 'taskEndManually': 'Award task interrupted manually, click【❌Close】to hide the log panel.', 'taskNotStart': 'Award task not running!', 'running': 'Running', 'taskStartPointsSummary': 'Start sending award, points left / points expected', 'fetchAwardItemFailedRetry': 'Fetch awardable items failed, retry……', 'fetchNoAwardItemSkip': 'No suitable award items, skip……', 'beforeSendAward': 'Will send award', 'itemAndTotal': 'items, total', 'points': 'points', 'sendingAwards': 'Sending awards……', 'fetchSuccessAndFailed': 'Success / Failure', 'fetchAwardItemFailedRetryIn2Min': 'Fetch awardable items failed, will retry in【2 seconds】……', 'awardSuccess': 'Successful send award', 'taskFinishedPointsSummary': 'Award task complete, points left / points expected', 'updateBotPointsBalance': 'Update bot\'s points balance……', 'bot': 'bots', 'pointsBalanceUpdat###ccess': 'points balance, avilable points', 'lackOfPointsTaskEnd': 'Lack of points balance, stop operation', 'pointBalanceUpdateFailed': 'Update points balance failed', 'fetchAwardItemFailedSkip': 'Fetch awardable items failed, skip……', 'taskEndListEmpty': 'Award items list is empty, end', 'fetchCompletedTotal': 'Fetch success, total', 'entries': 'entries', 'objectID': 'Target ID', 'noAwardableObjectSkip': 'No suitable award items, skip...', 'willAward': 'Will send award', 'requestsSummary': 'Success / Failure', 'wait2Seconds': '*Delay 2 seconds, to avoid exceed award*', 'botDataError': 'Bot data error, can\'t start award task!', 'awardTaskFinish': '✅Award task completed, click【❌Close】to hide the log panel', 'awardTaskNotFinish': '⛔Award task not completed, click【❌close】to hide the log panel', 'cancel': 'Cancel', 'steamStoreNotLogin': '【STEAM Store】not logined, please sign in first', 'parseDataFailedMaybeNetworkError': 'Parse data failed, maybe token expired or network error', 'typeError': 'Type Error', 'networkError': 'Network Error', 'parseError': 'Parse Error', 'importAccount': '手动导入账号', 'importAccountSteamId64': '请输入Steam64位ID', 'importAccountInvalidSteamId64': '请输入正确的SteamID', 'importAccountGetToken': '请访问【 https://store.steampowered.com/pointssummary/ajaxgetasyncconfig 】,并把所有内容粘贴到下面', 'importAccountNickName': '请输入机器人显示昵称', 'importAccountNickNameTips': '手动添加', 'importAccountSuccess': '添加账号成功, 请手动刷新点数', } }; // 判断语言 let language = GM_getValue("lang", null); if (!(language in LANG)) { showAlert('申明', `<p>本脚本现已免费提供</p><p>如果你在<a href="https://afdian.com/a/chr233">爱发电</a>以外的地方购买了本脚本, 请申请退款</p><p>觉得好用也欢迎给 <a href="https://steamcommunity.com/id/Chr_">作者</a> 打赏</p>`, true); language = "ZH"; GM_setValue("lang", language); } // 获取翻译文本 function t(key) { return LANG[language][key] || key; } {// 自动弹出提示 const languageTips = GM_getValue("languageTips", true); if (languageTips && language === "ZH") { if (!document.querySelector("html").lang.startsWith("zh")) { ShowConfirmDialog("tips", "Auto Award now support English, switch?", "Using English", "Don't show again") .done(() => { GM_setValue("lang", "EN"); GM_setValue("languageTips", false); window.location.reload(); }) .fail((bool) => { if (bool) { showAlert("", "You can switch the plugin's language using TamperMonkey's menu."); GM_setValue("languageTips", false); } }); } } } GM_registerMenuCommand(`${t("changeLang")} (${t("langName")})`, () => { switch (language) { case "EN": language = "ZH"; break; case "ZH": language = "EN"; break; } GM_setValue("lang", language); window.location.reload(); }); GM_registerMenuCommand(t("importAccount"), () => { const steamID = prompt(t("importAccountSteamId64")); const v_steamID = parseInt(steamID); if (v_steamID !== v_steamID) { alert(t("importAccountInvalidSteamId64")); return; } const ajaxJson = prompt(t("importAccountGetToken")); const json = JSON.parse(ajaxJson); const token = json?.data?.webapi_token; if (!token) { alert(t("parseError")); return; } let botCount = 0; for (let _ in GBots) { botCount++; } const nick = prompt(t("importAccountNickName"), `${t("importAccountNickNameTips")} ${botCount}`); alert(t("importAccountSuccess")); GBots[steamID] = { nick, token, points: -1 }; GM_setValue('bots', GBots); flashBotList(); }); //机器人账号 let GBots = {}; //打赏历史记录 let GHistory = {}; //打赏任务 let GTask = {}; //面板状态 let GPanel = {}; //控件字典 let GObjs = {}; //初始化 (() => { loadConf(); graphGUI(); flashBotList(); flashHistoryList(); const { panelMain, panelLeft } = GPanel; if (panelMain) { GPanel.panelMain = false; panelSwitch(); } if (panelLeft) { GPanel.panelLeft = false; leftPanelSwitch(); } if (!isEmptyObject(GTask)) { GTask.work = false; } appllyTask(); })(); //==================================================================================== //添加控制面板 function graphGUI() { function genButton(text, foo, enable = true) { const b = document.createElement('button'); b.textContent = text; b.className = 'aam_button'; b.disabled = !enable; b.addEventListener('click', foo); return b; } function genDiv(cls = 'aam_div') { const d = document.createElement('div'); d.className = cls; return d; } function genA(text, url) { const a = document.createElement('a'); a.textContent = text; a.className = 'aam_a'; a.target = '_blank'; a.href = url; return a; } function genInput(value, tips, number = false) { const i = document.createElement('input'); i.className = 'aam_input'; if (value) { i.value = value; } if (tips) { i.placeholder = tips; } if (number) { i.type = 'number'; i.step = 100; i.min = 0; } return i; } function genTextArea(value, tips) { const i = document.createElement('textarea'); i.className = 'aam_textarea'; if (value) { i.value = value; } if (tips) { i.placeholder = tips; } return i; } function genCheckbox(name, checked = false) { const l = document.createElement('label'); const i = document.createElement('input'); const s = genSpace(name); i.textContent = name; i.title = name; i.type = 'checkbox'; i.className = 'aam_checkbox'; i.checked = checked; l.appendChild(i); l.appendChild(s); return [l, i]; } function genSelect(choose = [], choice = null) { const s = document.createElement('select'); s.className = 'aam_select'; choose.forEach(([text, value]) => { s.options.add(new Option(text, value)); }); if (choice) { s.value = choice; } return s; } function genList(choose = [], choice = null) { const s = genSelect(choose, choice); s.className = 'aam_list'; s.setAttribute('multiple', 'multiple'); return s; } function genP(text) { const p = document.createElement('p'); p.textContent = text; return p; } function genSpan(text = ' ') { const s = document.createElement('span'); s.textContent = text; return s; } const genSpace = genSpan; function genBr() { return document.createElement('br'); } function genHr() { return document.createElement('hr'); } function genMidBtn(text, foo) { const a = document.createElement('a'); const s = genSpan(text); a.className = 'btn_profile_action btn_medium'; a.addEventListener('click', foo); a.appendChild(s); return [a, s]; } const btnArea = document.querySelector('.profile_header_actions'); const [btnSwitch, bSwitch] = genMidBtn('⭕', panelSwitch); btnArea.appendChild(genSpace()); btnArea.appendChild(btnSwitch); btnArea.appendChild(genSpace()); const panelArea = document.querySelector('.profile_leftcol'); const panelMain = genDiv('aam_panel profile_customization'); panelMain.style.display = 'none'; panelArea.insertBefore(panelMain, panelArea.firstChild); const busyPanel = genDiv('aam_busy'); const busyPanelContent = genDiv('aam_busy_content'); const busyMessage = genP(t('operating')); const busyImg = new Image(); busyImg.src = 'https://steamcommunity-a.akamaihd.net/public/images/login/throbber.gif'; busyPanelContent.appendChild(busyMessage); busyPanelContent.appendChild(busyImg); busyPanel.appendChild(busyPanelContent); panelMain.appendChild(busyPanel); const workPanel = genDiv('aam_busy aam_work'); const workLog = genTextArea('', t('runningLog'),); const workHide = genButton(`❌${t('close')}`, () => { workScreen(false, null); }, true); workPanel.appendChild(workLog); workPanel.appendChild(genSpan(t('logWaring'))); workPanel.appendChild(workHide); panelMain.appendChild(workPanel); const leftPanel = genDiv('aam_left'); const accountPanel = genDiv('aam_account'); const accountTitle = genSpan(t('botListTitle')); const accountList = genList([], null); const accountBtns = genDiv('aam_btns'); const acAdd = genButton(`➕${t('addCurrentAccount')}`, accountAdd); const acDel = genButton(`➖${t('delSelectAccount')}`, accountDel); const acUpdate = genButton(`🔄${t('reloadAccountPoints')}`, flashAllAccounts); accountBtns.appendChild(acAdd); accountBtns.appendChild(acDel); accountBtns.appendChild(acUpdate); accountPanel.appendChild(accountTitle); accountPanel.appendChild(genBr()); accountPanel.appendChild(accountList); accountPanel.appendChild(accountBtns); leftPanel.appendChild(accountPanel); const historyPanel = genDiv('aam_history'); historyPanel.style.display = 'none'; const historyTitle = genSpan(t('historyTitle')); const historyList = genList([], null); const historyBtns = genDiv('aam_btns'); const hsProfile = genButton(`🌏${t('profile')}`, showProfile); const hsDelete = genButton(`➖${t('deleteSelectedHistory')}`, deleteHistory); const hsClear = genButton(`🗑️${t('clearHistory')}`, clearHistory); const hsReload = genButton(`🔄${t('reloadHistory')}`, flashHistoryList); historyBtns.appendChild(hsProfile); historyBtns.appendChild(hsDelete); historyBtns.appendChild(hsClear); historyBtns.appendChild(hsReload); historyPanel.appendChild(historyTitle); historyPanel.appendChild(genBr()); historyPanel.appendChild(historyList); historyPanel.appendChild(historyBtns); leftPanel.appendChild(historyPanel); panelMain.appendChild(leftPanel); const awardPanel = genDiv('aam_award'); const feedbackLink = genA(t('feedBack'), 'https://steamcommunity.com/id/Chr_/'); feedbackLink.title = t('feedBackTitle'); const awardBot = genSelect([[t('notSelected'), '']], null); const awardSteamID = genInput('', t('steamID64'), false); const awardPoints = genInput('', t('awardPoints'), true); const [awardCProfile, awardProfile] = genCheckbox(t('profile'), true); const [awardCRecommand, awardRecommand] = genCheckbox(t('recommands'), true); const [awardCScreenshot, awardScreenshot] = genCheckbox(t('screenshots'), true); const [awardCImage, awardImage] = genCheckbox(t('artworks'), true); const awardBtns1 = genDiv('aam_btns'); const awardBtnCurrent = genButton(`🤵${t('setToCurrentUser')}`, getCurrentProfile); const awardBtnCalc = genButton(`📊${t('calculator')}`, calcAwardItems); const awardBtns2 = genDiv('aam_btns'); const awardBtnSet = genButton(`💾${t('save')}`, applyAwardConfig); const awardBtnReset = genButton(`🔨${t('reset')}`, restoreAwardConfig); const hSwitch = genButton(`🕒${t('awardHistory')}`, leftPanelSwitch); const awardBtns3 = genDiv('aam_btns aam_award_btns'); const awardBtnStart = genButton(`✅${t('startAward')}`, startAward, false); const awardBtnStop = genButton(`⛔${t('stopAward')}`, stopAward, false); const awardStatus = genSpan(`🟥 ${t('stop')}`); awardBtns1.appendChild(awardBtnCurrent); awardBtns1.appendChild(awardBtnCalc); awardBtns2.appendChild(awardBtnSet); awardBtns2.appendChild(awardBtnReset); awardBtns2.appendChild(hSwitch); awardBtns3.appendChild(awardBtnStart); awardBtns3.appendChild(awardBtnStop); awardBtns3.appendChild(awardStatus); awardPanel.appendChild(genSpan(t('senderBotAccount'))); awardPanel.appendChild(feedbackLink); awardPanel.appendChild(genBr()); awardPanel.appendChild(awardBot); awardPanel.appendChild(genSpan(t('receverAccount'))); awardPanel.appendChild(genBr()); awardPanel.appendChild(awardSteamID); awardPanel.appendChild(awardBtns1); awardPanel.appendChild(genSpan(t('sendPoints'))); awardPanel.appendChild(genBr()); awardPanel.appendChild(awardPoints); awardPanel.appendChild(genSpan(t('awardPrefer'))); awardPanel.appendChild(genBr()); awardPanel.appendChild(awardCProfile); awardPanel.appendChild(awardCRecommand); awardPanel.appendChild(genBr()); awardPanel.appendChild(awardCScreenshot); awardPanel.appendChild(awardCImage); awardPanel.appendChild(genBr()); awardPanel.appendChild(awardBtns2); awardPanel.appendChild(genHr()); awardPanel.appendChild(awardBtns3); panelMain.appendChild(awardPanel); Object.assign(GObjs, { bSwitch, hSwitch, panelMain, busyPanel, busyMessage, workPanel, workLog, workHide, accountPanel, accountList, historyPanel, historyList, awardBot, awardSteamID, awardPoints, awardStatus, awardProfile, awardRecommand, awardScreenshot, awardImage, awardBtnStart, awardBtnStop, awardBtnSet, awardBtnReset }); } //面板显示开关 function panelSwitch() { const { bSwitch, panelMain } = GObjs; if (GPanel.panelMain !== true) { panelMain.style.display = ''; bSwitch.textContent = '🔴'; GPanel.panelMain = true; } else { panelMain.style.display = 'none'; bSwitch.textContent = '⭕'; GPanel.panelMain = false; } GM_setValue('panel', GPanel); } //左侧面板切换 function leftPanelSwitch() { const { hSwitch, accountPanel, historyPanel } = GObjs; if (GPanel.panelLeft !== true) { accountPanel.style.display = 'none'; historyPanel.style.display = ''; hSwitch.textContent = `🤖${t('botList')}`; GPanel.panelLeft = true; } else { historyPanel.style.display = 'none'; accountPanel.style.display = ''; hSwitch.textContent = `🕒${t('awardHistory')}`; GPanel.panelLeft = false; } GM_setValue('panel', GPanel); } //添加账户 function accountAdd() { let v_nick, v_token, v_steamID; loadScreen(true, t('fetchLoginAccount')); getMySteamID() .then(({ nick, steamID }) => { v_nick = nick; v_steamID = steamID; loadScreen(true, t('fetchToken')); return getToken(); }) .then((tk) => { v_token = tk; loadScreen(true, t('fetchPoints')); return getPoints(v_steamID, tk); }) .then((points) => { showAlert(t('success'), `<p>${t('addAccountSuccessTips1')}</p><p>${t('addAccountSuccessTips2')}: ${points} ${t('points')}</p>`, true); GBots[v_steamID] = { nick: v_nick, token: v_token, points }; GM_setValue('bots', GBots); flashBotList(); }) .catch((reason) => { showAlert(t('error'), reason, false); }).finally(() => { loadScreen(false, null); }); } //删除账户 function accountDel() { const { accountList } = GObjs; if (accountList.selectedIndex >= 0) { showConfirm(t('confirm'), t('deleteAccountConfirmTips'), () => { let i = 0; for (const opt of accountList.selectedOptions) { delete GBots[opt.value]; i++; } flashBotList(); GM_setValue('bots', GBots); showAlert(t('tips'), `${t('deleteAccountDoneTips1')} ${i} ${t('deleteAccountDoneTips2')}`, true); }, null); } else { showAlert(t('tips'), t('notSelectedAnyBotsTips'), false); } } //刷新账户点数 async function flashAllAccounts() { //刷新点数 function makePromise(sid, tk) { return new Promise((resolve, reject) => { getPoints(sid, tk) .then((points) => { GBots[sid].points = points; loadScreen(true, `${t('currentProcess')}: ${++fin} / ${count}`); }).catch((reason) => { GBots[sid].points = -1; // GBots[sid].nick = '读取失败'; loadScreen(true, `${sid} ${t('updateFailed')}: ${reason}`); }).finally(() => { GM_setValue('bots', GBots); resolve(); }); }); } let count = 0, fin = 0; for (const _ in GBots) { count++; } if (count > 0) { loadScreen(true, t('fetchingAccountPoints')); const pList = []; for (const steamID in GBots) { const { token } = GBots[steamID]; pList.push(makePromise(steamID, token)); } Promise.all(pList) .finally(() => { loadScreen(false, null); flashBotList(); if (fin >= count) { showAlert(t('done'), t('allDataLoaded'), true); } else { showAlert(t('done'), t('someDataLoadFailed'), true); } }); } else { showAlert(t('error'), t('botListEmpty'), false); } } //刷新账户列表 function flashBotList() { const { bot } = GTask; const { accountList, awardBot } = GObjs; accountList.options.length = 0; awardBot.options.length = 0; awardBot.options.add(new Option(t('notSelected'), '')); let i = 1; let flag = false; if (!isEmptyObject(GBots)) { for (const steamID in GBots) { const { nick, points } = GBots[steamID]; const pointsStr = parseInt(points).toLocaleString(); accountList.options.add(new Option(`${i} | ${nick} | ${steamID} | ${pointsStr} 点`, steamID)); awardBot.options.add(new Option(`${i++} | ${nick} | ${pointsStr} 点`, steamID)); if (steamID === bot) { flag = true; awardBot.selectedIndex = i - 1; } } } else { accountList.options.add(new Option(t('noBotAccountTips'), '')); } if ((!isEmptyObject(GTask)) && (!flag)) { GTask = {}; GM_setValue('task', GTask); appllyTask(); showAlert(t('tips'), t('awardTaskWasResetTips'), false); } } //刷新历史记录列表 function flashHistoryList() { const { historyList } = GObjs; historyList.options.length = 0; let i = 1; if (!isEmptyObject(GHistory)) { for (const steamID in GHistory) { const [nick, points] = GHistory[steamID]; const pointsStr = parseInt(points).toLocaleString(); historyList.options.add(new Option(`${i++} | ${nick} | ${steamID} | ${pointsStr} 点`, steamID)); } } else { historyList.options.add(new Option(t('noHistoryTips'), '')); } } //历史记录增加点数 function addHistory(steamID, nick, points) { if (GHistory[steamID] !== undefined) { GHistory[steamID] = [nick, GHistory[steamID][1] + points]; } else { GHistory[steamID] = [nick, points]; } GM_setValue('history', GHistory); } //获取当前个人资料 function getCurrentProfile() { const { awardSteamID } = GObjs; awardSteamID.value = g_rgProfileData.steamid; } //计算可打赏项目 function calcAwardItems() { const { awardSteamID } = GObjs; const steamID = awardSteamID.value.trim(); if (steamID === '') { showAlert(t('error'), t('steamIDisEmpty!'), false); } else { loadScreen(true, t('fetchingProfile')); getProfile(steamID) .then(([succ, nick]) => { if (succ) { loadScreen(true, t('fetchingAwardableItems')); const pList = [ getAwardCounts(steamID, 'r'), getAwardCounts(steamID, 's'), getAwardCounts(steamID, 'i') ]; Promise.all(pList) .then((r###lt) => { const data = {}; let sum = 0; for (const [type, succ, count] of r###lt) { if (succ) { const points = count * 6600; data[type] = `${count} , ${t('awardableAmount')}: ${points.toLocaleString()} ${t('points')}`; sum += points; } else { data[type] = t('fetchError'); } } let text = `<p>${t('nickName')}: ${nick}</p><p>${t('recommands')}: ${data.r}</p><p>${t('screenshots')}: ${data.s}</p><p>${t('artworks')}: ${data.i}</p><p>${t('totalPoints')}: ${sum.toLocaleString()}</p><p>*${t('calcTips')}*</p>`; showAlert(t('tips'), text, true); }) .finally(() => { loadScreen(false, null); }); } else { showAlert(t('error'), t('profileNotExistsTips'), false); loadScreen(false, null); } }) .catch((reason) => { showAlert(t('error'), `<p>${t('profileLoadFailedTips')}</p><p>${reason}</p>`, false); loadScreen(false, null); }); } } //查看个人资料 function showProfile() { const { historyList } = GObjs; const i = historyList.selectedIndex; if (i > -1) { const { value } = historyList.options[i]; if (value != '') { window.open(`https://steamcommunity.com/profiles/${value}`); } } else { showAlert(t('tips'), t('notSelectedAnyHistoryTips'), false); } } //清除历史 function clearHistory() { if (!isEmptyObject(GHistory)) { showConfirm(t('confirm'), t('clearHistoryConfirmTips'), () => { GHistory = {}; flashHistoryList(); GM_setValue('history', GHistory); showAlert(t('tips'), t('clearHistorySuccess'), true); }, null); } else { showAlert(t('tips'), t('historyListEmpty'), false); } } //删除历史 function deleteHistory() { const { historyList } = GObjs; if (historyList.selectedIndex >= 0) { showConfirm(t('confirm'), t('deleteHistoryConfirmTips'), () => { let i = 0; for (const opt of historyList.selectedOptions) { delete GHistory[opt.value]; i++; } flashHistoryList(); GM_setValue('history', GHistory); showAlert(t('tips'), `${t('deleteR###ltTips1')} ${i} ${t('deleteR###ltTips2')}`, true); }, null); } else { showAlert(t('tips'), t('notSelectedAnyHistoryTips'), false); } } //保存打赏设置 async function applyAwardConfig() { const { awardBtnStart, awardBtnStop, awardBot, awardSteamID, awardPoints, awardProfile, awardRecommand, awardScreenshot, awardImage } = GObjs; awardBtnStart.disabled = awardBtnStop.disabled = true; let bot = awardBot.value; let points = parseInt(awardPoints.value); let steamID = String(awardSteamID.value).trim(); let type = 0; if (!awardProfile.checked) { type += 1; } if (!awardRecommand.checked) { type += 2; } if (!awardScreenshot.checked) { type += 4; } if (!awardImage.checked) { type += 8; } if (bot == '') { // 未选择机器人则自动使用当前登录账号 loadScreen(true, t('fetchLoginAccount')); const nick = document.querySelector("#account_pulldown")?.textContent?.trim(); if (g_steamID && nick) { try { loadScreen(true, t('fetchToken')); const token = await getToken(); loadScreen(true, t('fetchPoints')); const points = await getPoints(g_steamID, token); GBots[g_steamID] = { nick: nick, token: token, points }; GM_setValue('bots', GBots); flashBotList(); bot = g_steamID; } catch (reason) { showAlert(t('error'), reason, false); } finally { loadScreen(false, null); } } } if (bot == '') { showAlert(t('error'), t('notSelectedAwardBotsTips'), false); } else if (steamID === '') { showAlert(t('error'), t('steamIDEmptyWithTips'), false); } else if (!steamID.match(/^\d+$/)) { showAlert(t('error'), t('steamIDErrorWithTips'), false); } else if (points !== points || points < 100) { showAlert(t('error'), t('pointsErrorWithTips'), false); } else if (type === 15) { showAlert(t('error'), t('awardTypeEmptyTips'), false); } else { points = Math.ceil(points / 100) * 100; GTask = { bot, steamID, points, type, work: false, nick: null }; awardBtnStart.disabled = awardBtnStop.disabled = false; GM_setValue('task', GTask); showAlert(t('tips'), t('awardReadyToStartTips'), true); } } //重置打赏设置 function restoreAwardConfig() { showConfirm(t('confirm'), t('resetConfigConfirmTips'), () => { GTask = {}; GM_setValue('task', GTask); appllyTask(); showAlert(t('tips'), t('configResetSuccessTips'), true); }, null); } //读取设置到界面 function appllyTask() { const { awardBtnStart, awardBtnStop, awardBot, awardSteamID, awardPoints, awardProfile, awardRecommand, awardScreenshot, awardImage } = GObjs; const { bot, steamID, points, type } = GTask; awardBtnStart.disabled = awardBtnStop.disabled = isEmptyObject(GTask); awardBot.value = bot ? bot : ''; awardSteamID.value = steamID ? steamID : ''; awardPoints.value = points ? points : ''; awardProfile.checked = !Boolean(type & 1); awardRecommand.checked = !Boolean(type & 2); awardScreenshot.checked = !Boolean(type & 4); awardImage.checked = !Boolean(type & 8); } //开始自动打赏 async function startAward() { if (isEmptyObject(GTask)) { showAlert(t('error'), t('awardTaskDataInvalid'), false); return; } const { steamID, work, points, bot, nick: taskNick } = GTask; const { nick: botNick } = GBots[bot]; const pointsStr = parseInt(points).toLocaleString(); if (!work) { spaceLine(1); if (!taskNick) { loadScreen(true, t('fetchingTargetProfile')); getProfile(steamID) .then(([succ, nickName]) => { if (succ) { GTask.work = true; GTask.nick = nickName; GM_setValue('task', GTask); print(`${t('awardConfig')}:\n〖${t('targetNickName')}: ${nickName}, ${t('targetReceivePoints')}: ${pointsStr}, ${t('targetBot')}: ${botNick}〗`); print(t('taskReadyToStartTips')); workScreen(true); setTimeout(() => { autoAward(); }, 2000); } else { print(t('taskFailedProfileNotFound'), 'E'); showAlert(t('error'), t('profileNotExistsTips'), false); } }) .catch((reason) => { showAlert(t('error'), `<p>${t('profileLoadFailedTips')}</p><p>${reason}</p>`, false); }).finally(() => { loadScreen(false, null); }); } else { GTask.work = true; GM_setValue('task', GTask); print(`〖${t('targetNickName')}: ${taskNick}, ${t('targetReceivePoints')}: ${pointsStr}, ${t('targetBot')}: ${botNick}〗`); print(t('taskReadyToStartTips')); workScreen(true); setTimeout(() => { autoAward(); }, 2000); } } else { print(t('taskAlreadyStartTips')); } } //停止自动打赏 async function stopAward() { if (isEmptyObject(GTask)) { showAlert(t('error'), t('awardTaskDataInvalid'), false); return; } const { work } = GTask; if (work) { spaceLine(4); print(t('taskEndManually')); GTask.work = false; GM_setValue('task', GTask); showStatus(t('stop'), false); } else { showAlert(t('error'), t('taskNotStart'), false); } } //打赏项目 const reactionsDict = { 1: 300, 2: 300, 3: 300, 4: 300, 5: 300, 6: 300, 7: 300, 8: 300, 9: 600, 10: 1200, 11: 2400, 12: 300, 13: 2400, 14: 600, 15: 1200, 16: 600, 17: 4800, 18: 300, 19: 600, 20: 1200, 21: 300, 22: 600, 23: 300 }; const reactionValues = [ 300, 300, 300, 300, 300, 300, 300, 300, 600, 1200, 2400, 300, 2400, 600, 1200, 600, 4800, 300, 600, 1200, 300, 600, 300 ]; const reactionIDs = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ]; //自动打赏 async function autoAward() { //打赏类型 const reactionType = { 'p': ['3', t('profile')], 'r': ['1', t('recommands')], 's': ['2', t('screenshots')], 'i': ['2', t('artworks')] }; const { bot, steamID, type, points: pointsGoal, nick: taskNick } = GTask; const { nick: botNick, token } = GBots[bot]; appllyTask(); addHistory(steamID, taskNick, 0); showStatus(t('running'), true); let pointsLeft = pointsGoal; if (token) { const workflow = []; if (!Boolean(type & 8)) { workflow.push('i'); }; if (!Boolean(type & 4)) { workflow.push('s'); }; if (!Boolean(type & 2)) { workflow.push('r'); }; if (!Boolean(type & 1)) { workflow.push('p'); }; while (GTask.work && workflow.length > 0) { const award_type = workflow.pop(); const [target_type, target_name] = reactionType[award_type]; let process = genProgressBar((pointsGoal - pointsLeft) / pointsGoal * 100); spaceLine(3); print(`【${target_name}】${t('taskStartPointsSummary')}: ${pointsLeft.toLocaleString()} / ${pointsGoal.toLocaleString()} ${t('points')}`); print(`${t('currentProcess')}: ${process}`); spaceLine(3); let coast = 0; if (target_type === '3') { //个人资料 let GoldReactions = null; for (let i = 0; i < 3; i++) { //重试3次 if (GoldReactions === null) { //旧打赏列表为空,重新读取并打赏 const [succOld, oldReactions] = await getAwardRecords(token, target_type, steamID); if (!succOld) { print(t('fetchAwardItemFailedRetry')); continue; } GoldReactions = oldReactions; const todoReactions = selectFitableReactions(pointsLeft, GoldReactions); if (todoReactions.length === 0) { print(`【${target_name}】${t('fetchNoAwardItemSkip')}`); break; } coast = sumReactionsPoints(todoReactions); print(`【${target_name}】${t('beforeSendAward')}: ${todoReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`); const plist = []; for (const id of todoReactions) { plist.push(sendAwardReaction(token, target_type, steamID, id)); } print(t('sendingAwards')); const r###lt = await Promise.all(plist); const [succ, fail] = countSuccess(r###lt); print(`${t('fetchSuccessAndFailed')}: ${succ} / ${fail}`); } //统计新的打赏列表,计算打赏点数 const [succNew, newReactions] = await getAwardRecords(token, target_type, steamID); if (!succNew) { print(t('fetchAwardItemFailedRetryIn2Min')); await aiosleep(2500); continue; } const diffReactions = filterDiffReactions(newReactions, GoldReactions); coast = sumReactionsPoints(diffReactions); pointsLeft -= coast; addHistory(steamID, taskNick, coast); print(`【${target_name}】${t('awardSuccess')}: ${diffReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`); break; } GTask.points = pointsLeft; if (pointsLeft <= 0) { GTask.work = false; } GM_setValue('task', GTask); process = genProgressBar((pointsGoal - pointsLeft) / pointsGoal * 100); spaceLine(3); print(`【${target_name}】${t('taskFinishedPointsSummary')}: ${pointsLeft.toLocaleString()} / ${pointsGoal.toLocaleString()} ${t('points')}`); print(`${t('currentProcess')}: ${process}`); spaceLine(3); print(t('updateBotPointsBalance')); await getPoints(bot, token) .then((p) => { GBots[bot].points = p; GM_setValue('bots', GBots); print(`${t('bot')}【${botNick}】${t('pointsBalanceUpdat###ccess')}: ${p.toLocaleString()} ${t('points')}`); if (p < 300) { print(`${t('bot')}【${botNick}】${t('lackOfPointsTaskEnd')}`); GTask.work = false; } }).catch((r) => { print(`${t('bot')}【${botNick}】${t('pointBalanceUpdateFailed')}: ${r}`); }); } else { //截图 let page = 1; while (GTask.work) { let j = 0; print(t('fetchingAwardableItems')); const [succ, items] = await getAwardItems(steamID, award_type, page++); if (!succ) { page--; if (++j < 3) { print(t('fetchAwardItemFailedRetryIn2Min')); await aiosleep(2500); continue; } else { print(t('fetchAwardItemFailedSkip')); break; } } if (items.length === 0) { print(`【${target_name}】${t('taskEndListEmpty')}`); break; } print(`【${target_name}】${t('fetchCompletedTotal')} ${items.length} ${t('entries')}`); for (const itemID of items) { print(`【${target_name}】${t('objectID')}: ${itemID}`); let GoldReactions = null; for (let i = 0; i < 3; i++) { if (GoldReactions === null) { //旧打赏列表为空,重新读取并打赏 const [succOld, oldReactions] = await getAwardRecords(token, target_type, itemID); if (!succOld) { print(t('fetchAwardItemFailedRetry')); continue; } GoldReactions = oldReactions; const todoReactions = selectFitableReactions(pointsLeft, GoldReactions); if (todoReactions.length === 0) { print(`【${target_name}】${t('noAwardableObjectSkip')}`); break; } coast = sumReactionsPoints(todoReactions); print(`【${target_name}】${t('willAward')}: ${todoReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`); const plist = []; for (const id of todoReactions) { plist.push(sendAwardReaction(token, target_type, itemID, id)); } print(t('sendingAwards')); const r###lt = await Promise.all(plist); const [succ, fail] = countSuccess(r###lt); print(`${t('requestsSummary')}: ${succ} / ${fail}`); } print(t('wait2Seconds')); await asleep(2000); //统计新的打赏列表,计算打赏点数 const [succNew, newReactions] = await getAwardRecords(token, target_type, itemID); if (!succNew) { print(t('fetchAwardItemFailedRetry')); continue; } const diffReactions = filterDiffReactions(newReactions, GoldReactions); coast = sumReactionsPoints(diffReactions); pointsLeft -= coast; addHistory(steamID, taskNick, coast); print(`【${target_name}】${t('awardSuccess')}: ${diffReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`); break; } GTask.points = pointsLeft; if (pointsLeft <= 0) { GTask.work = false; } GM_setValue('task', GTask); process = genProgressBar((pointsGoal - pointsLeft) / pointsGoal * 100); spaceLine(3); print(`【${target_name}】${t('taskFinishedPointsSummary')}: ${pointsLeft.toLocaleString()} / ${pointsGoal.toLocaleString()} ${t('points')}`); print(`${t('currentProcess')}: ${process}`); spaceLine(3); print(t('updateBotPointsBalance')); await getPoints(bot, token) .then((p) => { GBots[bot].points = p; GM_setValue('bots', GBots); print(`${t('bot')}【${botNick}】${t('pointsBalanceUpdat###ccess')}: ${p.toLocaleString()} ${t('points')}`); if (p < 300) { print(`${t('bot')}【${botNick}】${t('lackOfPointsTaskEnd')}`); GTask.work = false; } }).catch((r) => { print(`${t('bot')}【${botNick}】${t('pointBalanceUpdateFailed')}: ${r}`); }); if (!GTask.work) { break; } } } } if (workflow.length > 0) { await aiosleep(1500); } } } else { delete GBots[bot]; GM_setValue('bots', GBots); print(t('botDataError')); showAlert(t('error'), t('botDataError'), false); } spaceLine(4); if (pointsLeft <= 0) { GTask = {}; print(t('awardTaskFinish')); } else { GTask.work = false; print(t('awardTaskNotFinish')); } GM_setValue('task', GTask); appllyTask(); showStatus(t('stop'), false); flashHistoryList(); } //==================================================================================== //显示提示 function showAlert(title, text, succ = true) { ShowAlertDialog(`${succ ? '✅' : '❌'}${title}`, `<div>${text}</div>`); } //显示确认 function showConfirm(title, text, done = null, cancel = null) { ShowConfirmDialog(`⚠️${title}`, `<div>${text}</div>`, t('confirm'), t('cancel')) .done(() => { if (done) { done(); } }) .fail(() => { if (cancel) { cancel(); } }); } //显示状态 function showStatus(text, run = true) { const { awardStatus, workHide } = GObjs; workHide.disabled = run; awardStatus.textContent = `${run ? '🟩' : '🟥'} ${text}`; } //读取设置 function loadConf() { const bots = GM_getValue('bots'); GBots = isEmptyObject(bots) ? {} : bots; const hs = GM_getValue('history'); GHistory = isEmptyObject(hs) ? {} : hs; const task = GM_getValue('task'); GTask = isEmptyObject(task) ? {} : task; const panel = GM_getValue('panel'); GPanel = isEmptyObject(panel) ? {} : panel; } //保存设置 function saveConf() { GM_setValue('bots', GBots); GM_setValue('history', GHistory); GM_setValue('task', GTask); GM_setValue('panel', GPanel); } //是不是空对象 function isEmptyObject(obj) { for (const _ in obj) { return false; } return true; } //显示加载面板 function loadScreen(show = true, msg = t('operating')) { const { busyPanel, busyMessage } = GObjs; if (show) { busyPanel.style.opacity = '1'; busyPanel.style.visibility = 'visible'; if (msg) { busyMessage.textContent = msg; } } else { busyPanel.style.opacity = '0'; busyPanel.style.visibility = 'hidden'; } } //显示日志面板 function workScreen(show = true) { const { workPanel } = GObjs; if (show) { workPanel.style.opacity = '1'; workPanel.style.visibility = 'visible'; } else { workPanel.style.opacity = '0'; workPanel.style.visibility = 'hidden'; } } //生成进度条 const BAR_STYLE = '⣀⣄⣤⣦⣶⣷⣿'; function genProgressBar(percent) { const full_symbol = '⣿'; const none_symbol = '⣀'; const percentStr = ` ${percent.toFixed(2)}%`; if (percent >= 100) { return full_symbol.repeat(40) + percentStr; } else { percent = percent / 100; let full = Math.floor(percent * 40); let rest = percent * 40 - full; let middle = Math.floor(rest * 6); if (percent !== 0 && full === 0 && middle === 0) { middle = 1; } let d = Math.abs(percent - (full + middle / 6) / 40) * 100; if (d < Number.POSITIVE_INFINITY) { let m = BAR_STYLE[middle]; if (full === 40) { m = ""; } return full_symbol.repeat(full) + m + BAR_STYLE[0].repeat(39 - full) + percentStr; } return none_symbol.repeat(40) + percentStr; } } //日志时间 function formatTime() { const date = new Date(); return `${date.toLocaleDateString()} ${date.toTimeString().substr(0, 8)}`; } //输出日志 function print(msg, level = 'I') { const { workLog } = GObjs; const time = formatTime(); workLog.value += `${time} - ${level} - ${msg}\n`; workLog.scrollTop = workLog.scrollHeight; console.log(`${time} - ${level} - ${msg}`); } //画分割线 function spaceLine(style = 1) { switch (style) { case 1: print('#'.repeat(68)); return; case 2: print('='.repeat(68)); return; case 3: print('+'.repeat(68)); return; case 4: print('~'.repeat(68)); return; } } //异步延时 function asleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } //==================================================================================== //计算合适的打赏项目 function selectFitableReactions(goal, doneList) { const fitableList = []; const aviableList = []; for (const id of reactionIDs) { if (doneList.indexOf(id) === -1) { aviableList.push(id); } } aviableList.sort((a, b) => { return reactionsDict[a] - reactionsDict[b]; }); for (const id of aviableList) { if (goal < 100) { break; } const value = reactionsDict[id] / 3; if (goal >= value) { fitableList.push(id); goal -= value; } } return fitableList; } //获取新增打赏项目 function filterDiffReactions(newList, oldList) { const diffList = []; for (const id of newList) { if (oldList.indexOf(id) === -1) { diffList.push(id); } } return diffList; } //计算打赏项目点数开销 function sumReactionsPoints(reactions) { let points = 0; for (const id of reactions) { points += reactionsDict[id]; } return points / 3; } //统计成功失败 function countSuccess(r###lt) { let succ = 0, fail = 0; for (const r of r###lt) { if (r) { succ++; } else { fail++; } } return ([succ, fail]); } //异步延时 function aiosleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } //==================================================================================== function getMySteamID() { return new Promise((resolve, reject) => { try { const steamID = g_steamID; const nick = document.querySelector("div.playerAvatar>a>img")?.getAttribute("alt"); if (nick && steamID) { resolve({ nick, steamID }); } else { reject(t('steamStoreNotLogin')); } } catch (err) { reject(err); } }); } function getToken() { return new Promise((resolve, reject) => { try { let token = document.querySelector("#application_config")?.getAttribute("data-loyalty_webapi_token"); if (isEmptyObject(token)) { reject(t('steamStoreNotLogin')); } else { token = token.replace(/"/g, ""); resolve(token); } } catch (err) { reject(err); } }); } function getPoints(steamID, token) { return new Promise((resolve, reject) => { $http.get(`https://api.steampowered.com/ILoyaltyRewardsService/GetSummary/v1/?access_token=${token}&steamid=${steamID}`) .then(({ response }) => { if (isEmptyObject(response)) { reject(t('steamStoreNotLogin')); } try { const points = parseInt(response.summary.points); if (points === points) { resolve(points); } else { reject(t('parseDataFailedMaybeNetworkError')); } } catch (e) { reject(t('parseDataFailedMaybeNetworkError')); } }) .catch((reason) => { reject(reason); }); }); } function getProfile(steamID) { return new Promise((resolve, reject) => { $http.getText(`https://steamcommunity.com/profiles/${steamID}/?xml=1`) .then((text) => { try { const match = text.match(/<steamID><!\[CDATA\[([\s\S]*)\]\]><\/steamID>/) || text.match(/<steamID>([\s\S]*)<\/steamID>/); if (match) { resolve([true, match[1].substring()]); } else { resolve([false, null]); } } catch (e) { reject(e); } }) .catch((reason) => { reject(reason); }); }); } function getAwardCounts(steamID, type) { let subPath, preg; switch (type) { case 'r': subPath = 'recommended/?l=schinese'; preg = /共 (\d+) 项条目/; break; case 's': subPath = 'screenshots/?l=schinese&appid=0&sort=newestfirst&browsefilter=myfiles&view=grid'; preg = /共 (\d+) 张/; break; case 'i': subPath = 'images/?l=schinese&appid=0&sort=newestfirst&browsefilter=myfiles&view=grid'; preg = /共 (\d+) 张/; break; default: throw 'type错误'; } return new Promise((resolve, reject) => { $http.getText(`https://steamcommunity.com/profiles/${steamID}/${subPath}`) .then((text) => { try { const match = text.match(preg); const count = match ? Number(match[1]) : 0; resolve([type, true, count]); } catch (e) { resolve([type, false, 0]); } }) .catch((reason) => { console.error(reason); resolve([type, false, 0]); }); }); } function getAwardItems(steamID, type, p = 1) { let subPath, preg; switch (type) { case 'r': subPath = `recommended/?p=${p}&l=schinese`; preg = /id="RecommendationVoteUpBtn(\d+)"/g; break; case 's': subPath = `screenshots/?p=${p}&view=grid&l=schinese`; preg = /id="imgWallHover(\d+)"/g; break; case 'i': subPath = `images/?p=${p}&view=grid&l=schinese`; preg = /id="imgWallHover(\d+)"/g; break; default: throw t('typeError'); } return new Promise((resolve, reject) => { $http.getText(`https://steamcommunity.com/profiles/${steamID}/${subPath}`) .then((text) => { try { const r###lt = []; const matches = text.matchAll(preg); for (const match of matches) { r###lt.push(match[1]); } resolve([true, r###lt]); } catch (e) { console.error(e); resolve([false, e]); } }) .catch((reason) => { console.error(reason); resolve([false, reason]); }); }); } function getAwardRecords(token, targetType, targetID) { return new Promise((resolve, reject) => { const params = `access_token=${token}&target_type=${targetType}&targetid=${targetID}`; $http.get('https://api.steampowered.com/ILoyaltyRewardsService/GetReactions/v1/?' + params) .then(({ response }) => { const { reactionids } = response; resolve([true, reactionids || []]); }) .catch((reason) => { console.error(reason); resolve([false, null]); }); }); } function sendAwardReaction(token, targetType, targetID, reactionID) { return new Promise((resolve, reject) => { const params = `access_token=${token}&target_type=${targetType}&targetid=${targetID}&reactionid=${reactionID}`; $http.post('https://api.steampowered.com/ILoyaltyRewardsService/AddReaction/v1/?' + params) .then((json) => { console.log(json); resolve(true); }) .catch((reason) => { console.error(reason); resolve(false); }); }); } })(); //==================================================================================== class Request { constructor(timeout = 3000) { this.timeout = timeout; } get(url, opt = {}) { return this.baseRequest(url, 'GET', opt, 'json'); } getHtml(url, opt = {}) { return this.baseRequest(url, 'GET', opt, ''); } getText(url, opt = {}) { return this.baseRequest(url, 'GET', opt, 'text'); } post(url, data, opt = {}) { opt.data = JSON.stringify(data); return this.baseRequest(url, 'POST', opt, 'json'); } baseRequest(url, method = 'GET', opt = {}, responseType = 'json') { Object.assign(opt, { url, method, responseType, timeout: this.timeout }); return new Promise((resolve, reject) => { opt.ontimeout = opt.onerror = reject; opt.onload = ({ readyState, status, response, responseText }) => { if (readyState === 4 && status === 200) { if (responseType == 'json') { resolve(response); } else if (responseType == 'text') { resolve(responseText); } } else { console.error("网络错误"); console.log(readyState); console.log(status); console.log(response); reject("解析失败"); } }; GM_xmlhttpRequest(opt); }); } } const $http = new Request(); //CSS表 GM_addStyle(`.aam_panel, .aam_work { padding: 10px; display: flex; } .aam_work { z-index: 500 !important; } .aam_busy { width: 100%; height: 100%; z-index: 700; position: absolute; top: 0; left: 0; background: rgba(0, 0, 0, 0.7); display: table; visibility: hidden; opacity: 0; transition: all 0.1s; } .aam_busy_content { display: table-cell; vertical-align: middle; text-align: center; } .aam_left { width: 61%; padding-right: 10px; } .aam_award { width: 39%; } .aam_list, .aam_select, .aam_input, .aam_textarea { background-color: #fff !important; color: #000 !important; border: none !important; border-radius: 0 !important; } .aam_input { width: 98% !important; text-align: center; } .aam_list { height: 230px; } .aam_textarea { height: calc(100% - 85px); width: calc(100% - 45px); resize: none; font-size: 12px; } .aam_left > div > *, .aam_award:not(span, button) > * { width: 100%; margin-bottom: 5px; } .aam_btns > button:not(:last-child) { margin-right: 4px; } .aam_award_btns { z-index: 600; bottom: 10px; position: absolute; } .aam_work > * { position: absolute; } .aam_work > span { bottom: 12%; left: 70px; } .aam_work > button { bottom: 11%; } .aam_a { margin-left: 110px; }`);