Auto_Award_Muli

Steam自动打赏 — 极速多账户版

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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': '确定要删除选定的打赏历史记录吗?',
            'deleteResultTips1': '删除了',
            'deleteResultTips2': '条打赏历史记录',
            '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': '机器人',
            'pointsBalanceUpdateSuccess': '点数余额更新成功, 可用点数',
            '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?',
            'deleteResultTips1': 'Deleted',
            'deleteResultTips2': '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',
            'pointsBalanceUpdateSuccess': '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((result) => {
                                const data = {};
                                let sum = 0;
                                for (const [type, succ, count] of result) {
                                    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('deleteResultTips1')} ${i} ${t('deleteResultTips2')}`, 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 result = await Promise.all(plist);
                            const [succ, fail] = countSuccess(result);
                            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('pointsBalanceUpdateSuccess')}: ${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 result = await Promise.all(plist);
                                    const [succ, fail] = countSuccess(result);
                                    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('pointsBalanceUpdateSuccess')}: ${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(result) {
        let succ = 0, fail = 0;
        for (const r of result) {
            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 result = [];
                        const matches = text.matchAll(preg);
                        for (const match of matches) {
                            result.push(match[1]);
                        }
                        resolve([true, result]);
                    } 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;
}`);