Auto_Award_Muli

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

目前为 2022-02-28 提交的版本。查看 最新版本

// ==UserScript==
// @name         Auto_Award_Muli
// @name:zh-CN   Steam自动打赏【极速多账户版】
// @namespace    https://blog.chrxw.com
// @version      1.2
// @description  Steam自动打赏 — 极速多账户版
// @description:zh-CN  Steam自动打赏 — 极速多账户版
// @author       Chr_
// @include      /https://steamcommunity\.com/(id|profiles)/[^\/]+/?$/
// @connect      steamcommunity.com
// @connect      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
// ==/UserScript==

(() => {
    'use strict';
    //机器人账号
    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.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('操作进行中……');
        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('', '运行日志',);
        const workHide = genButton('❌关闭', () => { workScreen(false, null); }, true);

        workPanel.appendChild(workLog);
        workPanel.appendChild(genSpan('【日志不会保存,打赏记录可以在【打赏历史】中查看】'));
        workPanel.appendChild(workHide);

        panelMain.appendChild(workPanel);

        const leftPanel = genDiv('aam_left');
        const accountPanel = genDiv('aam_account');
        const accountTitle = genSpan('【机器人账户管理】【ID | 账户名 | SteamID | 点数余额】');
        const accountList = genList([], null);
        const accountBtns = genDiv('aam_btns');
        const acAdd = genButton('➕添加当前账号', accountAdd);
        const acDel = genButton('➖删除选中账号', accountDel);
        const acUpdate = genButton('🔄刷新所有账号点数', 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('【点数打赏历史记录】【ID | 昵称 | SteamID | 收到点数】');
        const historyList = genList([], null);
        const historyBtns = genDiv('aam_btns');
        const hsProfile = genButton('🌏个人资料', showProfile);
        const hsDelete = genButton('➖删除选中', deleteHistory);
        const hsClear = genButton('🗑️清空历史', clearHistory);
        const hsReload = genButton('🔄刷新历史', 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('反馈', 'https://blog.chrxw.com/scripts.html');
        const awardBot = genSelect([['---未选择---', '']], null);
        const awardSteamID = genInput('', 'Steam 64位 ID', false);
        const awardPoints = genInput('', '打赏点数(收到)', true);
        const [awardCProfile, awardProfile] = genCheckbox('个人资料', true);
        const [awardCRecommand, awardRecommand] = genCheckbox('评测', true);
        const [awardCScreenshot, awardScreenshot] = genCheckbox('截图', true);
        const [awardCImage, awardImage] = genCheckbox('艺术作品', true);
        const awardBtns1 = genDiv('aam_btns');
        const awardBtnCurrent = genButton('🤵设置为当前用户', getCurrentProfile);
        const awardBtnCalc = genButton('📊打赏计算器', calcAwardItems);
        const awardBtns2 = genDiv('aam_btns');
        const awardBtnSet = genButton('💾保存', applyAwardConfig);
        const awardBtnReset = genButton('🔨重置', restoreAwardConfig);
        const hSwitch = genButton('🕒打赏历史', leftPanelSwitch);
        const awardBtns3 = genDiv('aam_btns aam_award_btns');
        const awardBtnStart = genButton('✅开始打赏', startAward, false);
        const awardBtnStop = genButton('⛔停止打赏', stopAward, false);
        const awardStatus = genSpan('🟥 停止');

        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('打赏机器人账户:'));
        awardPanel.appendChild(feedbackLink);
        awardPanel.appendChild(genBr());
        awardPanel.appendChild(awardBot);
        awardPanel.appendChild(genSpan('被打赏人SteamID:'));
        awardPanel.appendChild(genBr());
        awardPanel.appendChild(awardSteamID);
        awardPanel.appendChild(awardBtns1);
        awardPanel.appendChild(genSpan('打赏点数(收到):'));
        awardPanel.appendChild(genBr());
        awardPanel.appendChild(awardPoints);
        awardPanel.appendChild(genSpan('打赏类型(优先级从左到右):'));
        awardPanel.appendChild(genBr());
        awardPanel.appendChild(awardCProfile);
        awardPanel.appendChild(awardCRecommand);
        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 = '🤖机器人列表';
            GPanel.panelLeft = true;
        } else {
            historyPanel.style.display = 'none';
            accountPanel.style.display = '';
            hSwitch.textContent = '🕒打赏历史';
            GPanel.panelLeft = false;
        }
        GM_setValue('panel', GPanel);
    }
    //添加账户
    function accountAdd() {
        let v_nick, v_token, v_steamID;
        loadScreen(true, '获取登陆账户……');
        getMySteamID()
            .then(({ nick, steamID }) => {
                v_nick = nick;
                v_steamID = steamID;
                loadScreen(true, '获取Token……');
                return getToken();
            })
            .then((tk) => {
                v_token = tk;
                loadScreen(true, '获取点数信息……');
                return getPoints(v_steamID, tk);
            })
            .then((points) => {
                showAlert('成功', `添加账户成功\n当前账户可用点数: ${points}`, true);
                GBots[v_steamID] = { nick: v_nick, token: v_token, points }
                GM_setValue('bots', GBots);
                flashBotList();
            })
            .catch((reason) => {
                showAlert('错误', reason, false);
            }).finally(() => {
                loadScreen(false, null);
            });
    }
    //删除账户
    function accountDel() {
        const { accountList } = GObjs;
        if (accountList.selectedIndex >= 0) {
            showConfirm('确认', '确定要删除选定的账号吗?', () => {
                let i = 0;
                for (const opt of accountList.selectedOptions) {
                    delete GBots[opt.value];
                    i++;
                }
                flashBotList();
                GM_setValue('bots', GBots);
                showAlert('提示', `删除了 ${i} 个机器人`, true);
            }, null);
        } else {
            showAlert('提示', '尚未选中任何机器人!', false);
        }
    }
    //刷新账户点数
    async function flashAllAccounts() {
        //刷新点数
        function makePromise(sid, tk) {
            return new Promise((resolve, reject) => {
                getPoints(sid, tk)
                    .then((points) => {
                        GBots[sid].points = points;
                        loadScreen(true, `当前进度: ${++fin} / ${count}`);
                    }).catch((reason) => {
                        GBots[sid].points = -1;
                        // GBots[sid].nick = '读取失败';
                        loadScreen(true, `${sid} 更新出错: ${reason}`);
                    }).finally(() => {
                        GM_setValue('bots', GBots);
                        resolve();
                    });
            });
        }
        let count = 0, fin = 0;
        for (const _ in GBots) {
            count++;
        }
        if (count > 0) {
            loadScreen(true, '读取账户点数中……');
            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('完成', '所有数据刷新完毕', true);
                    } else {
                        showAlert('完成', '部分数据刷新失败,如果点数显示为【-1】,代表数据刷新失败', true);
                    }
                });
        } else {
            showAlert('错误', '机器人列表为空', false);
        }
    }
    //刷新账户列表
    function flashBotList() {
        const { bot } = GTask;
        const { accountList, awardBot } = GObjs;
        accountList.options.length = 0;
        awardBot.options.length = 0;
        awardBot.options.add(new Option('---未选择---', ''))
        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('-- 无机器人账号,请使用【➕添加当前账号】自动添加 --', ''));
        }
        if ((!isEmptyObject(GTask)) && (!flag)) {
            GTask = {};
            GM_setValue('task', GTask);
            appllyTask();
            showAlert('提示', '机器人账号已修改,打赏设置已重置!', 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('-- 无历史记录,执行打赏任务后会自动记录 --', ''));
        }
    }
    //历史记录增加点数
    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('错误', '未填入SteamID!', fales);
        } else {
            loadScreen(true, '获取个人资料中……');
            getProfile(steamID)
                .then(([succ, nick]) => {
                    if (succ) {
                        loadScreen(true, '统计可打赏项目中……');
                        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} 篇,可打赏约:${points.toLocaleString()} 点`;
                                        sum += points;
                                    } else {
                                        data[type] = '读取出错';
                                    }
                                }
                                let text = `<p>用户名:${nick}</p><p>评测:${data.r}</p><p>截图:${data.s}</p><p>艺术作品:${data.i}</p><p>总计点数:${sum.toLocaleString()} 点</p><p>*根据项目数量计算所得,不准确*</p>`
                                showAlert('提示', text, true);
                            })
                            .finally(() => {
                                loadScreen(false, null);
                            });
                    } else {
                        showAlert('错误', '个人资料不存在,请检查SteamID是否正确,或者使用【🤵设置为当前用户】自动获取。', false);
                        loadScreen(false, null);
                    }
                })
                .catch((reason) => {
                    showAlert('错误', `<p>网络错误,读取个人资料失败</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('提示', '未选中历史记录!', false);
        }
    }
    //清除历史
    function clearHistory() {
        if (!isEmptyObject(GHistory)) {
            showConfirm('确认', '确定要清除打赏历史记录吗?', () => {
                GHistory = {};
                flashHistoryList();
                GM_setValue('history', GHistory);
                showAlert('提示', '清除成功', true);
            }, null);
        } else {
            showAlert('提示', '历史记录是空的!', false);
        }
    }
    //删除历史
    function deleteHistory() {
        const { historyList } = GObjs;
        if (historyList.selectedIndex >= 0) {
            showConfirm('确认', '确定要删除选定的打赏历史记录吗?', () => {
                let i = 0;
                for (const opt of historyList.selectedOptions) {
                    delete GHistory[opt.value];
                    i++;
                }
                flashHistoryList();
                GM_setValue('history', GHistory);
                showAlert('提示', `删除了 ${i} 条历史记录`, true);
            }, null);
        } else {
            showAlert('提示', '尚未选中历史记录!', false);
        }
    }
    //保存打赏设置
    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 == '') {
            showAlert('错误', '尚未选择打赏机器人!', false);
        } else if (steamID === '') {
            showAlert('错误', '未填写【被打赏人SteamID】,建议使用【💬设置为当前用户】功能!', false);
        } else if (!steamID.match(/^\d+$/)) {
            showAlert('错误', '【被打赏人SteamID】格式有误,建议使用【💬设置为当前用户】功能!', false);
        } else if (points !== points || points < 100) {
            showAlert('错误', '【打赏点数】格式有误,只能为整数!', false);
        } else if (type === 15) {
            showAlert('错误', '请选择【打赏类型】!', 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('提示', '设置保存成功,可以【✅开始打赏】了', true);
        }
    }
    //重置打赏设置
    function restoreAwardConfig() {
        showConfirm('确认', '确定要重置设定吗?', () => {
            GTask = {};
            GM_setValue('task', GTask);
            appllyTask();
            showAlert('提示', '设置已清除', 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('错误', '任务数据非法', 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, '读取被打赏人个人资料……');
                getProfile(steamID)
                    .then(([succ, nickName]) => {
                        if (succ) {
                            GTask.work = true;
                            GTask.nick = nickName;
                            GM_setValue('task', GTask);
                            print(`打赏设置:\n〖被打赏人昵称:${nickName},预计收到点数:${pointsStr} 点,机器人:${botNick}〗`);
                            print('打赏任务【2秒】后开始,点击【⛔停止打赏】可以提前终止操作!');
                            workScreen(true);
                            setTimeout(() => {
                                autoAward();
                            }, 2000);
                        } else {
                            print('未找到个人资料,打赏进程停止!', 'E');
                            showAlert('错误', '个人资料不存在,请检查SteamID是否正确。<br>建议使用【🤵设置为当前用户】自动获取。', false);
                        }
                    })
                    .catch((reason) => {
                        showAlert('错误', `<p>网络错误,读取个人资料失败</p><p>${reason}</p>`, false)
                    }).finally(() => {
                        loadScreen(false, null);
                    });
            } else {
                GTask.work = true;
                GM_setValue('task', GTask);
                print(`〖被打赏人昵称:${taskNick},预计收到点数:${pointsStr} 点,机器人:${botNick}〗`);
                print('打赏任务【2秒】后开始,点击【⛔停止打赏】可以提前终止操作!');
                workScreen(true);
                setTimeout(() => {
                    autoAward();
                }, 2000);
            }
        } else {
            print('打赏任务已经开始了!');
        }
    }
    //停止自动打赏
    async function stopAward() {
        if (isEmptyObject(GTask)) {
            showAlert('错误', '任务数据非法', false);
            return;
        }
        const { work } = GTask;
        if (work) {
            spaceLine(4);
            print('打赏任务手动终止,点击【❌关闭】可以关闭面板。');
            GTask.work = false;
            GM_setValue('task', GTask);
            showStatus('停止', false);
        } else {
            showAlert('错误', '打赏任务未开始!', 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', '个人资料'], 'r': ['1', '评测'], 's': ['2', '截图'], 'i': ['2', '艺术作品']
        };
        const { bot, steamID, type, points: pointsGoal, nick: taskNick } = GTask;
        const { nick: botNick, token } = GBots[bot];

        appllyTask();
        addHistory(steamID, taskNick, 0);
        showStatus('运行', 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}】开始打赏,剩余打赏 / 预计打赏:${pointsLeft.toLocaleString()} 点 / ${pointsGoal.toLocaleString()} 点`);
                print(`当前进度:${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('获取打赏项目失败,重试……');
                                continue;
                            }
                            GoldReactions = oldReactions;
                            const todoReactions = selectFitableReactions(pointsLeft, GoldReactions);
                            if (todoReactions.length === 0) {
                                print(`【${target_name}】没有合适的打赏,跳过`);
                                break;
                            }
                            coast = sumReactionsPoints(todoReactions);
                            print(`【${target_name}】将要打赏:${todoReactions.length} 项,共计:${coast.toLocaleString()} 点`)
                            const plist = [];
                            for (const id of todoReactions) {
                                plist.push(sendAwardReaction(token, target_type, steamID, id));
                            }
                            print('发送打赏中……');
                            const result = await Promise.all(plist);
                            const [succ, fail] = countSuccess(result);
                            print(`请求成功 / 请求失败:${succ} / ${fail}`);
                        }
                        //统计新的打赏列表,计算打赏点数
                        const [succNew, newReactions] = await getAwardRecords(token, target_type, steamID);
                        if (!succNew) {
                            print('获取打赏项目失败,【2秒后】重试……');
                            await aiosleep(2000);
                            continue;
                        }
                        const diffReactions = filterDiffReactions(newReactions, GoldReactions);
                        coast = sumReactionsPoints(diffReactions);
                        pointsLeft -= coast;
                        addHistory(steamID, taskNick, coast);
                        print(`【${target_name}】成功打赏:${diffReactions.length} 项,共计:${coast.toLocaleString()} 点`);
                        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}】打赏完成,剩余打赏 / 预计打赏:${pointsLeft.toLocaleString()} 点 / ${pointsGoal.toLocaleString()} 点`);
                    print(`当前进度:${process}`);
                    spaceLine(3);

                    print('更新机器人点数余额……');

                    await getPoints(bot, token)
                        .then((p) => {
                            GBots[bot].points = p;
                            GM_setValue('bots', GBots);
                            print(`机器人【${botNick}】点数余额更新成功,可用点数:${p.toLocaleString()} 点`);
                            if (p < 300) {
                                print(`机器人【${botNick}】点数余额不足,终止操作`);
                                GTask.work = false;
                            }
                        }).catch((r) => {
                            print(`机器人【${botNick}】点数余额更新失败:${r}`);
                        });


                } else { //截图 
                    let page = 1;
                    while (GTask.work) {
                        let j = 0;
                        print('获取可打赏项目……');
                        const [succ, items] = await getAwardItems(steamID, award_type, page++);
                        if (!succ) {
                            page--;
                            if (++j < 3) {
                                print(`获取可打赏项目失败,【2秒后】重试……`);
                                await aiosleep(2000);
                                continue;
                            } else {
                                print(`获取可打赏项目失败,跳过……`);
                                break;
                            }
                        }
                        if (items.length === 0) {
                            print(`【${target_name}】列表为空,结束`);
                            break;
                        }

                        print(`【${target_name}】获取成功,共 ${items.length} 个`);

                        for (const itemID of items) {

                            print(`【${target_name}】项目ID:${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('获取打赏项目失败,重试……');
                                        continue;
                                    }
                                    GoldReactions = oldReactions;
                                    const todoReactions = selectFitableReactions(pointsLeft, GoldReactions);
                                    if (todoReactions.length === 0) {
                                        print(`【${target_name}】没有合适的打赏,跳过`);
                                        break;
                                    }
                                    coast = sumReactionsPoints(todoReactions);
                                    print(`【${target_name}】将要打赏:${todoReactions.length} 项,共计:${coast.toLocaleString()} 点`)
                                    const plist = [];
                                    for (const id of todoReactions) {
                                        plist.push(sendAwardReaction(token, target_type, itemID, id));
                                    }
                                    print('发送打赏中……');
                                    const result = await Promise.all(plist);
                                    const [succ, fail] = countSuccess(result);
                                    print(`请求成功 / 请求失败:${succ} / ${fail}`);
                                }
                                print('*等待2秒,防止打赏过多.*');
                                await asleep(2000);
                                //统计新的打赏列表,计算打赏点数
                                const [succNew, newReactions] = await getAwardRecords(token, target_type, itemID);
                                if (!succNew) {
                                    print('获取打赏项目失败,重试……');
                                    continue;
                                }
                                const diffReactions = filterDiffReactions(newReactions, GoldReactions);
                                coast = sumReactionsPoints(diffReactions);
                                pointsLeft -= coast;
                                addHistory(steamID, taskNick, coast);
                                print(`【${target_name}】成功打赏:${diffReactions.length} 项,共计:${coast.toLocaleString()} 点`);
                                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}】打赏完成,剩余打赏 / 预计打赏:${pointsLeft.toLocaleString()} 点 / ${pointsGoal.toLocaleString()} 点`);
                            print(`当前进度:${process}`);
                            spaceLine(3);

                            print('更新机器人点数余额……');

                            await getPoints(bot, token)
                                .then((p) => {
                                    GBots[bot].points = p;
                                    GM_setValue('bots', GBots);
                                    print(`机器人【${botNick}】点数余额更新成功,可用点数:${p.toLocaleString()} 点`);
                                    if (p < 300) {
                                        print(`机器人【${botNick}】点数余额不足,终止操作`);
                                        GTask.work = false;
                                    }
                                }).catch((r) => {
                                    print(`机器人【${botNick}】点数余额更新失败:${r}`);
                                });

                            if (!GTask.work) {
                                break;
                            }
                        }
                    }
                }
                if (workflow.length > 0) {
                    await aiosleep(1000);
                }
            }
        } else {
            delete GBots[bot];
            GM_setValue('bots', GBots);
            print('机器人数据错误,无法开始打赏!');
            showAlert('错误', '机器人数据错误,无法开始打赏!', false);
        }
        spaceLine(4);
        if (pointsLeft <= 0) {
            GTask = {};
            print('✅打赏任务完成,点击【❌关闭】可以关闭面板。');
        } else {
            GTask.work = false;
            print('⛔打赏任务未完成,点击【❌关闭】可以关闭面板。');
        }
        GM_setValue('task', GTask);
        appllyTask();
        showStatus('停止', 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>`, '确认', '取消')
            .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 = '操作进行中……') {
        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) => {
            $http.getText('https://store.steampowered.com/account/?l=english')
                .then((text) => {
                    let match1 = text.match(/pageheader">([\s\S]+)'s account/);
                    let match2 = text.match(/Steam ID: (\d+)/);

                    if (match1 && match2) {
                        resolve({ nick: match1[1], steamID: match2[1] });
                    } else {
                        reject('【STEAM商店】未登录(不可用),请重新登录(不可用)');
                    }
                })
                .catch((reason) => {
                    reject(reason);
                });
        });
    }
    function getToken() {
        return new Promise((resolve, reject) => {
            $http.get('https://store.steampowered.com/pointssummary/ajaxgetasyncconfig')
                .then(({ data }) => {
                    if (isEmptyObject(data)) {
                        reject('【STEAM商店】未登录(不可用),请重新登录(不可用)');
                    }
                    resolve(data.webapi_token);
                })
                .catch((reason) => {
                    reject(reason);
                });
        });
    }
    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('【STEAM商店】未登录(不可用),请重新登录(不可用)');
                    }
                    try {
                        const points = parseInt(response.summary.points);
                        if (points === points) {
                            resolve(points);
                        } else {
                            reject('解析数据失败,可能是Token失效或者网络错误');
                        }
                    } catch (e) {
                        reject('解析数据失败,可能是Token失效或者网络错误');
                    }
                })
                .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';
                preg = /共 (\d+) 张/;
                break;
            case 'i':
                subPath = 'images/?l=schinese';
                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 'type错误';
        }
        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;
}`);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址