// ==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;
}`);