Auto_Award_Muli

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

目前为 2023-03-08 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Auto_Award_Muli
  3. // @name:zh-CN Steam自动打赏【极速多账户版】
  4. // @namespace https://blog.chrxw.com
  5. // @version 1.6
  6. // @description Steam自动打赏 — 极速多账户版
  7. // @description:zh-CN Steam自动打赏 — 极速多账户版
  8. // @author Chr_
  9. // @include /https://steamcommunity\.com/(id|profiles)/[^\/]+/?$/
  10. // @connect steamcommunity.com
  11. // @connect steampowered.com
  12. // @license AGPL-3.0
  13. // @icon https://blog.chrxw.com/favicon.ico
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM_addStyle
  18. // @grant GM_setClipboard
  19. // @grant GM_registerMenuCommand
  20. // ==/UserScript==
  21.  
  22. (() => {
  23. 'use strict';
  24.  
  25. // 多语言
  26. const LANG = {
  27. 'ZH': {
  28. 'changeLang': '修改语言',
  29. 'langName': '中文',
  30. 'operating': '操作进行中……',
  31. 'runningLog': '运行日志',
  32. 'close': '关闭',
  33. 'logWaring': '【日志不会保存, 打赏记录可以在【打赏历史】中查看】',
  34. 'botListTitle': '【机器人账户管理】【ID | 账户名 | SteamID | 点数余额】',
  35. 'addCurrentAccount': '添加当前账号',
  36. 'delSelectAccount': '删除选中账号',
  37. 'reloadAccountPoints': '刷新所有账号点数',
  38. 'historyTitle': '【点数打赏历史记录】【ID | 昵称 | SteamID | 收到点数】',
  39. 'profile': '个人资料',
  40. 'deleteSelectedHistory': '删除选中',
  41. 'clearHistory': '清空历史',
  42. 'reloadHistory': '刷新历史',
  43. 'feedBack': '作者',
  44. 'feedBackTitle':'觉得好用也欢迎给我打赏',
  45. 'notSelected': '---未选择---',
  46. 'steamID64': 'Steam 64位 ID',
  47. 'awardPoints': '打赏点数(收到)',
  48. 'recommands': '评测',
  49. 'screenshots': '截图',
  50. 'artworks': '艺术作品',
  51. 'setToCurrentUser': '设置为当前用户',
  52. 'calculator': '打赏计算器',
  53. 'save': '保存',
  54. 'reset': '重置',
  55. 'awardHistory': '打赏历史',
  56. 'startAward': '开始打赏',
  57. 'stopAward': '停止打赏',
  58. 'stop': '停止',
  59. 'senderBotAccount': '打赏机器人账户: ',
  60. 'receverAccount': '被打赏人SteamID: ',
  61. 'sendPoints': '打赏点数(收到): ',
  62. 'awardPrefer': '打赏类型(优先级从上到下从左到右): ',
  63. 'botList': '机器人列表',
  64. 'fetchLoginAccount': '获取登陆账户……',
  65. 'fetchToken': '获取Token……',
  66. 'fetchPoints': '获取点数信息……',
  67. 'success': '成功',
  68. 'failure': '失败',
  69. 'error': '错误',
  70. 'confirm': '确认',
  71. 'tips': '提示',
  72. 'addAccountSuccessTips1': '添加账户成功',
  73. 'addAccountSuccessTips2': '当前账户可用点数',
  74. 'deleteAccountConfirmTips': '确定要删除选定的账号吗?',
  75. 'deleteAccountDoneTips1': '删除了',
  76. 'deleteAccountDoneTips2': '个机器人',
  77. 'notSelectedAnyBotsTips': '尚未选中任何机器人!',
  78. 'currentProcess': '当前进度',
  79. 'updateFailed': '更新出错',
  80. 'fetchingAccountPoints': '读取账户点数中……',
  81. 'allDataLoaded': '所有数据刷新完毕',
  82. 'someDataLoadFailed': '部分数据刷新失败, 如果点数显示为【-1】,代表数据刷新失败',
  83. 'botListEmpty': '机器人列表为空',
  84. 'noBotAccountTips': '-- 无机器人账号, 请使用【➕添加当前账号】自动添加 --',
  85. 'awardTaskWasResetTips': '机器人账号已修改, 打赏设置已重置!',
  86. 'noHistoryTips': '-- 无历史记录, 执行打赏任务后会自动记录 --',
  87. 'steamIDisEmpty': '未填入SteamID!',
  88. 'fetchingProfile': '获取个人资料中……',
  89. 'fetchingAwardableItems': '获取可打赏项目……',
  90. 'fetchError': '读取出错',
  91. 'awardableAmount': '可打赏约',
  92. 'nickName': '用户名',
  93. 'totalPoints': '总计点数',
  94. 'calcTips': '根据项目数量计算所得, 不准确',
  95. 'profileNotExistsTips': '个人资料不存在, 请检查SteamID是否正确, 或者使用【🤵设置为当前用户】自动获取。',
  96. 'profileLoadFailedTips': '网络错误, 读取个人资料失败',
  97. 'notSelectedAnyHistoryTips': '未选中历史记录!',
  98. 'clearHistoryConfirmTips': '确定要清除打赏历史记录吗?',
  99. 'clearHistorySuccess': '清除成功',
  100. 'historyListEmpty': '历史记录是空的!',
  101. 'deleteHistoryConfirmTips': '确定要删除选定的打赏历史记录吗?',
  102. 'deleteResultTips1': '删除了',
  103. 'deleteResultTips2': '条打赏历史记录',
  104. 'notSelectedAwardBotsTips': '尚未选择打赏机器人!',
  105. 'steamIDEmptyWithTips': '未填写【被打赏人SteamID】, 建议使用【🤵设置为当前用户】功能!',
  106. 'steamIDErrorWithTips': '【被打赏人SteamID】格式有误, 建议使用【🤵设置为当前用户】功能!',
  107. 'pointsErrorWithTips': '【打赏点数】格式有误, 只能为整数!',
  108. 'awardTypeEmptyTips': '请选择【打赏类型】!',
  109. 'awardReadyToStartTips': '设置保存成功, 可以【✅开始打赏】了',
  110. 'resetConfigConfirmTips': '确定要重置设定吗?',
  111. 'configResetSuccessTips': '设置已清除',
  112. 'awardTaskDataInvalid': '任务数据非法',
  113. 'fetchingTargetProfile': '读取被打赏人个人资料……',
  114. 'awardConfig': '打赏设置',
  115. 'targetNickName': '被打赏人昵称',
  116. 'targetReceivePoints': '预计收到点数',
  117. 'targetBot': '打赏机器人',
  118. 'taskReadyToStartTips': '打赏任务【2秒】后开始, 点击【⛔停止打赏】可以提前终止操作!',
  119. 'taskFailedProfileNotFound': '未找到个人资料, 打赏进程停止!',
  120. 'taskAlreadyStartTips': '打赏任务已经开始了!',
  121. 'taskEndManually': '打赏任务手动终止, 点击【❌关闭】可以关闭面板.',
  122. 'taskNotStart': '打赏任务未开始!',
  123. 'running': '运行',
  124. 'taskStartPointsSummary': '开始打赏, 剩余打赏 / 预计打赏',
  125. 'fetchAwardItemFailedRetry': '获取打赏项目失败, 重试……',
  126. 'fetchNoAwardItemSkip': '没有合适的打赏, 跳过……',
  127. 'beforeSendAward': '将要打赏',
  128. 'itemAndTotal': '项, 总计',
  129. 'points': '点',
  130. 'sendingAwards': '发送打赏中……',
  131. 'fetchSuccessAndFailed': '请求成功 / 请求失败',
  132. 'fetchAwardItemFailedRetryIn2Min': '获取打赏项目失败, 【2秒】后重试……',
  133. 'awardSuccess': '成功打赏',
  134. 'taskFinishedPointsSummary': '打赏完成, 剩余打赏 / 预计打赏',
  135. 'updateBotPointsBalance': '更新机器人点数余额……',
  136. 'bot': '机器人',
  137. 'pointsBalanceUpdateSuccess': '点数余额更新成功, 可用点数',
  138. 'lackOfPointsTaskEnd': '点数余额不足, 终止操作',
  139. 'pointBalanceUpdateFailed': '点数余额更新失败',
  140. 'fetchAwardItemFailedSkip': '获取打赏项目失败, 跳过……',
  141. 'taskEndListEmpty': '列表为空, 结束',
  142. 'fetchCompletedTotal': '获取成功, 共',
  143. 'entries': '个',
  144. 'objectID': '项目ID',
  145. 'noAwardableObjectSkip': '没有合适的打赏, 跳过',
  146. 'willAward': '将要打赏',
  147. 'requestsSummary': '请求成功 / 请求失败',
  148. 'wait2Seconds': '*等待2秒,防止打赏过多*',
  149. 'botDataError': '机器人数据错误, 无法开始打赏!',
  150. 'awardTaskFinish': '✅打赏任务完成, 点击【❌关闭】可以关闭面板。',
  151. 'awardTaskNotFinish': '⛔打赏任务未完成, 点击【❌关闭】可以关闭面板。',
  152. 'cancel': '取消',
  153. 'steamStoreNotLogin': '【STEAM商店】未登录(不可用),请重新登录(不可用)',
  154. 'parseDataFailedMaybeNetworkError': '解析数据失败, 可能是Token失效或者网络错误',
  155. 'typeError': 'type错误',
  156. 'networkError': '网络错误',
  157. 'parseError': '解析出错'
  158. },
  159. 'EN': {
  160. 'changeLang': 'Change Language',
  161. 'langName': 'English',
  162. 'operating': 'Operating……',
  163. 'runningLog': 'Log',
  164. 'close': 'Close',
  165. 'logWaring': '【Log will not save, award history will list in【History】】',
  166. 'botListTitle': '【Bot Accounts】【ID | NickName | SteamID | Points Balance】',
  167. 'addCurrentAccount': 'Add Account',
  168. 'delSelectAccount': 'Del Selected',
  169. 'reloadAccountPoints': 'Refresh Points',
  170. 'historyTitle': '【History】【ID | NickName | SteamID | Received Points】',
  171. 'profile': 'Profile',
  172. 'deleteSelectedHistory': 'Del Selected',
  173. 'clearHistory': 'Clear',
  174. 'reloadHistory': 'Reload',
  175. 'feedBack': 'Author',
  176. 'feedBackTitle':'觉得好用也欢迎给我打赏',
  177. 'notSelected': '---Not Selected---',
  178. 'steamID64': 'Steam 64 ID',
  179. 'awardPoints': 'Points (Receive)',
  180. 'recommands': 'Recommands',
  181. 'screenshots': 'Screenshots',
  182. 'artworks': 'Artworks',
  183. 'setToCurrentUser': 'Set to page\'s user',
  184. 'calculator': 'Calculator',
  185. 'save': 'Save',
  186. 'reset': 'Reset',
  187. 'awardHistory': 'History',
  188. 'startAward': 'Start Award',
  189. 'stopAward': 'Stop',
  190. 'stop': 'Stopped',
  191. 'senderBotAccount': 'Award Bot: ',
  192. 'receverAccount': 'Target SteamID: ',
  193. 'sendPoints': 'Points (Receive): ',
  194. 'awardPrefer': 'Award Type (Up to down left to right): ',
  195. 'botList': 'Bots List',
  196. 'fetchLoginAccount': 'Fetching current logined account……',
  197. 'fetchToken': 'Fetching token……',
  198. 'fetchPoints': 'Fetching points balance……',
  199. 'success': 'Success',
  200. 'failure': 'Failure',
  201. 'error': 'Error',
  202. 'confirm': 'Confirm',
  203. 'tips': 'Tips',
  204. 'addAccountSuccessTips1': 'Add account successful',
  205. 'addAccountSuccessTips2': 'Current account\'s points balance',
  206. 'deleteAccountConfirmTips': 'Are sure to delete selected bots?',
  207. 'deleteAccountDoneTips1': 'Deleted',
  208. 'deleteAccountDoneTips2': 'bots',
  209. 'notSelectedAnyBotsTips': 'You have not selected any bot!',
  210. 'currentProcess': 'Current process',
  211. 'updateFailed': 'Update Failed',
  212. 'fetchingAccountPoints': 'Fetching points balance……',
  213. 'allDataLoaded': 'All data loaded',
  214. 'someDataLoadFailed': 'Some data loaded failed, if some bot\'s balance is 【-1】, it means fetch error',
  215. 'botListEmpty': 'Bot list is empty',
  216. 'noBotAccountTips': '-- No Bot Account, you can add via【➕Add ACcount】(Loginin required) --',
  217. 'awardTaskWasResetTips': 'Bot account had been modified, Award config reseted!',
  218. 'noHistoryTips': '-- No History Records, It Will Record When Awarding --',
  219. 'steamIDisEmpty': 'You must specify SteamID!',
  220. 'fetchingProfile': 'Fetching profile……',
  221. 'fetchingAwardableItems': 'Fetching awardable items……',
  222. 'fetchError': 'Fetch error',
  223. 'awardableAmount': '可打赏约',
  224. 'nickName': 'Nickname',
  225. 'totalPoints': 'Total points',
  226. 'calcTips': 'According to the total number of the items, inaccurate',
  227. 'profileNotExistsTips': 'Profile not found, please check if the steamID is correct, or use 【🤵Set to page\'s user】 instead',
  228. 'profileLoadFailedTips': 'Network error, fetch profile failed',
  229. 'notSelectedAnyHistoryTips': 'No records selected!',
  230. 'clearHistoryConfirmTips': 'Are you sure to clear all award records?',
  231. 'clearHistorySuccess': 'History cleared',
  232. 'historyListEmpty': 'History is empty!',
  233. 'deleteHistoryConfirmTips': 'Are you sure to delete seleted award records?',
  234. 'deleteResultTips1': 'Deleted',
  235. 'deleteResultTips2': 'history records',
  236. 'notSelectedAwardBotsTips': 'No bots selected!',
  237. 'steamIDEmptyWithTips': '【Target SteamID】is empty, It is recommended to use【🤵Set to page\'s user】!',
  238. 'steamIDErrorWithTips': '【Target SteamID】is invalid, It is recommended to use【🤵Set to page\'s user】!',
  239. 'pointsErrorWithTips': '【Points】is invalid, only integers are accepted!',
  240. 'awardTypeEmptyTips': 'Please select【Award Type】!',
  241. 'awardReadyToStartTips': 'Config saved, it is ready to【✅Start Award】',
  242. 'resetConfigConfirmTips': 'Are you sure to reset the config?',
  243. 'configResetSuccessTips': 'Config reseted!',
  244. 'awardTaskDataInvalid': 'Task data invalid',
  245. 'fetchingTargetProfile': 'Fetching target user\'s profile……',
  246. 'awardConfig': 'Award config',
  247. 'targetNickName': 'Target user\'s nickname',
  248. 'targetReceivePoints': 'Expected points received',
  249. 'targetBot': 'Selected bot',
  250. 'taskReadyToStartTips': 'Award task will start in 【2 seconds】, click【⛔Stop】to interrupt award task!',
  251. 'taskFailedProfileNotFound': 'Profile not found, award task end!',
  252. 'taskAlreadyStartTips': 'Award task is already running!',
  253. 'taskEndManually': 'Award task interrupted manually, click【❌Close】to hide the log panel.',
  254. 'taskNotStart': 'Award task not running!',
  255. 'running': 'Running',
  256. 'taskStartPointsSummary': 'Start sending award, points left / points expected',
  257. 'fetchAwardItemFailedRetry': 'Fetch awardable items failed, retry……',
  258. 'fetchNoAwardItemSkip': 'No suitable award items, skip……',
  259. 'beforeSendAward': 'Will send award',
  260. 'itemAndTotal': 'items, total',
  261. 'points': 'points',
  262. 'sendingAwards': 'Sending awards……',
  263. 'fetchSuccessAndFailed': 'Success / Failure',
  264. 'fetchAwardItemFailedRetryIn2Min': 'Fetch awardable items failed, will retry in【2 seconds】……',
  265. 'awardSuccess': 'Successful send award',
  266. 'taskFinishedPointsSummary': 'Award task complete, points left / points expected',
  267. 'updateBotPointsBalance': 'Update bot\'s points balance……',
  268. 'bot': 'bots',
  269. 'pointsBalanceUpdateSuccess': 'points balance, avilable points',
  270. 'lackOfPointsTaskEnd': 'Lack of points balance, stop operation',
  271. 'pointBalanceUpdateFailed': 'Update points balance failed',
  272. 'fetchAwardItemFailedSkip': 'Fetch awardable items failed, skip……',
  273. 'taskEndListEmpty': 'Award items list is empty, end',
  274. 'fetchCompletedTotal': 'Fetch success, total',
  275. 'entries': 'entries',
  276. 'objectID': 'Target ID',
  277. 'noAwardableObjectSkip': 'No suitable award items, skip...',
  278. 'willAward': 'Will send award',
  279. 'requestsSummary': 'Success / Failure',
  280. 'wait2Seconds': '*Delay 2 seconds, to avoid exceed award*',
  281. 'botDataError': 'Bot data error, can\'t start award task!',
  282. 'awardTaskFinish': '✅Award task completed, click【❌Close】to hide the log panel',
  283. 'awardTaskNotFinish': '⛔Award task not completed, click【❌close】to hide the log panel',
  284. 'cancel': 'Cancel',
  285. 'steamStoreNotLogin': '【STEAM Store】not logined, please sign in first',
  286. 'parseDataFailedMaybeNetworkError': 'Parse data failed, maybe token expired or network error',
  287. 'typeError': 'Type Error',
  288. 'networkError': 'Network Error',
  289. 'parseError': 'Parse Error'
  290. }
  291. }
  292.  
  293. // 判断语言
  294. let language = GM_getValue("lang", null);
  295. if (!(language in LANG)) {
  296. showAlert('申明', `<p>本脚本现已免费提供</p><p>如果你在<a href="https://afdian.net/a/chr233">爱发电</a>以外的地方购买了本脚本, 请申请退款</p><p>觉得好用也欢迎给 <a href="https://steamcommunity.com/id/Chr_">作者</a> 打赏</p>`, true);
  297. language = "ZH";
  298. GM_setValue("lang", language);
  299. }
  300. // 获取翻译文本
  301. function t(key) {
  302. return LANG[language][key] || key;
  303. }
  304. {// 自动弹出提示
  305. const languageTips = GM_getValue("languageTips", true);
  306. if (languageTips && language === "ZH") {
  307. if (!document.querySelector("html").lang.startsWith("zh")) {
  308. ShowConfirmDialog("tips", "Auto Award now support English, switch?", "Using English", "Don't show again")
  309. .done(() => {
  310. GM_setValue("lang", "EN");
  311. GM_setValue("languageTips", false);
  312. window.location.reload();
  313. })
  314. .fail((bool) => {
  315. if (bool) {
  316. showAlert("", "You can switch the plugin's language using TamperMonkey's menu.");
  317. GM_setValue("languageTips", false);
  318. }
  319. });
  320. }
  321. }
  322. }
  323. GM_registerMenuCommand(`${t("changeLang")} (${t("langName")})`, () => {
  324. switch (language) {
  325. case "EN":
  326. language = "ZH";
  327. break;
  328. case "ZH":
  329. language = "EN";
  330. break;
  331. }
  332. GM_setValue("lang", language);
  333. window.location.reload();
  334. });
  335.  
  336. //机器人账号
  337. let GBots = {};
  338. //打赏历史记录
  339. let GHistory = {};
  340. //打赏任务
  341. let GTask = {};
  342. //面板状态
  343. let GPanel = {};
  344. //控件字典
  345. let GObjs = {};
  346.  
  347. //初始化
  348. (() => {
  349. loadConf();
  350.  
  351. graphGUI();
  352. flashBotList();
  353. flashHistoryList();
  354.  
  355. const { panelMain, panelLeft } = GPanel;
  356. if (panelMain) {
  357. GPanel.panelMain = false;
  358. panelSwitch();
  359. }
  360. if (panelLeft) {
  361. GPanel.panelLeft = false;
  362. leftPanelSwitch();
  363. }
  364. if (!isEmptyObject(GTask)) {
  365. GTask.work = false;
  366. }
  367. appllyTask();
  368. })();
  369.  
  370. //====================================================================================
  371. //添加控制面板
  372. function graphGUI() {
  373. function genButton(text, foo, enable = true) {
  374. const b = document.createElement('button');
  375. b.textContent = text;
  376. b.className = 'aam_button';
  377. b.disabled = !enable;
  378. b.addEventListener('click', foo);
  379. return b;
  380. }
  381. function genDiv(cls = 'aam_div') {
  382. const d = document.createElement('div');
  383. d.className = cls;
  384. return d;
  385. }
  386. function genA(text, url) {
  387. const a = document.createElement('a');
  388. a.textContent = text;
  389. a.className = 'aam_a';
  390. a.target = '_blank';
  391. a.href = url;
  392. return a;
  393. }
  394. function genInput(value, tips, number = false) {
  395. const i = document.createElement('input');
  396. i.className = 'aam_input';
  397. if (value) { i.value = value; }
  398. if (tips) { i.placeholder = tips; }
  399. if (number) {
  400. i.type = 'number';
  401. i.step = 100;
  402. i.min = 0;
  403. }
  404. return i;
  405. }
  406. function genTextArea(value, tips) {
  407. const i = document.createElement('textarea');
  408. i.className = 'aam_textarea';
  409. if (value) { i.value = value; }
  410. if (tips) { i.placeholder = tips; }
  411. return i;
  412. }
  413. function genCheckbox(name, checked = false) {
  414. const l = document.createElement('label');
  415. const i = document.createElement('input');
  416. const s = genSpace(name);
  417. i.textContent = name;
  418. i.title = name;
  419. i.type = 'checkbox';
  420. i.className = 'aam_checkbox';
  421. i.checked = checked;
  422. l.appendChild(i);
  423. l.appendChild(s);
  424. return [l, i];
  425. }
  426. function genSelect(choose = [], choice = null) {
  427. const s = document.createElement('select');
  428. s.className = 'aam_select';
  429. choose.forEach(([text, value]) => {
  430. s.options.add(new Option(text, value));
  431. });
  432. if (choice) { s.value = choice; }
  433. return s;
  434. }
  435. function genList(choose = [], choice = null) {
  436. const s = genSelect(choose, choice);
  437. s.className = 'aam_list';
  438. s.setAttribute('multiple', 'multiple');
  439. return s;
  440. }
  441. function genP(text) {
  442. const p = document.createElement('p');
  443. p.textContent = text;
  444. return p;
  445. }
  446. function genSpan(text = ' ') {
  447. const s = document.createElement('span');
  448. s.textContent = text;
  449. return s;
  450. }
  451. const genSpace = genSpan;
  452. function genBr() {
  453. return document.createElement('br');
  454. }
  455. function genHr() {
  456. return document.createElement('hr');
  457. }
  458. function genMidBtn(text, foo) {
  459. const a = document.createElement('a');
  460. const s = genSpan(text);
  461. a.className = 'btn_profile_action btn_medium';
  462. a.addEventListener('click', foo);
  463. a.appendChild(s);
  464. return [a, s];
  465. }
  466.  
  467. const btnArea = document.querySelector('.profile_header_actions');
  468. const [btnSwitch, bSwitch] = genMidBtn('⭕', panelSwitch);
  469. btnArea.appendChild(genSpace());
  470. btnArea.appendChild(btnSwitch);
  471. btnArea.appendChild(genSpace());
  472.  
  473. const panelArea = document.querySelector('.profile_leftcol');
  474. const panelMain = genDiv('aam_panel profile_customization');
  475. panelMain.style.display = 'none';
  476. panelArea.insertBefore(panelMain, panelArea.firstChild);
  477.  
  478. const busyPanel = genDiv('aam_busy');
  479. const busyPanelContent = genDiv('aam_busy_content');
  480. const busyMessage = genP(t('operating'));
  481. const busyImg = new Image();
  482. busyImg.src = 'https://steamcommunity-a.akamaihd.net/public/images/login/throbber.gif';
  483.  
  484. busyPanelContent.appendChild(busyMessage);
  485. busyPanelContent.appendChild(busyImg);
  486.  
  487. busyPanel.appendChild(busyPanelContent);
  488.  
  489. panelMain.appendChild(busyPanel);
  490.  
  491. const workPanel = genDiv('aam_busy aam_work');
  492. const workLog = genTextArea('', t('runningLog'),);
  493. const workHide = genButton(`❌${t('close')}`, () => { workScreen(false, null); }, true);
  494.  
  495. workPanel.appendChild(workLog);
  496. workPanel.appendChild(genSpan(t('logWaring')));
  497. workPanel.appendChild(workHide);
  498.  
  499. panelMain.appendChild(workPanel);
  500.  
  501. const leftPanel = genDiv('aam_left');
  502. const accountPanel = genDiv('aam_account');
  503. const accountTitle = genSpan(t('botListTitle'));
  504. const accountList = genList([], null);
  505. const accountBtns = genDiv('aam_btns');
  506. const acAdd = genButton(`➕${t('addCurrentAccount')}`, accountAdd);
  507. const acDel = genButton(`➖${t('delSelectAccount')}`, accountDel);
  508. const acUpdate = genButton(`🔄${t('reloadAccountPoints')}`, flashAllAccounts);
  509.  
  510. accountBtns.appendChild(acAdd);
  511. accountBtns.appendChild(acDel);
  512. accountBtns.appendChild(acUpdate);
  513.  
  514. accountPanel.appendChild(accountTitle);
  515. accountPanel.appendChild(genBr());
  516. accountPanel.appendChild(accountList);
  517. accountPanel.appendChild(accountBtns);
  518.  
  519. leftPanel.appendChild(accountPanel);
  520.  
  521. const historyPanel = genDiv('aam_history');
  522. historyPanel.style.display = 'none';
  523.  
  524. const historyTitle = genSpan(t('historyTitle'));
  525. const historyList = genList([], null);
  526. const historyBtns = genDiv('aam_btns');
  527. const hsProfile = genButton(`🌏${t('profile')}`, showProfile);
  528. const hsDelete = genButton(`➖${t('deleteSelectedHistory')}`, deleteHistory);
  529. const hsClear = genButton(`🗑️${t('clearHistory')}`, clearHistory);
  530. const hsReload = genButton(`🔄${t('reloadHistory')}`, flashHistoryList);
  531.  
  532. historyBtns.appendChild(hsProfile);
  533. historyBtns.appendChild(hsDelete);
  534. historyBtns.appendChild(hsClear);
  535. historyBtns.appendChild(hsReload);
  536.  
  537. historyPanel.appendChild(historyTitle);
  538. historyPanel.appendChild(genBr());
  539. historyPanel.appendChild(historyList);
  540. historyPanel.appendChild(historyBtns);
  541.  
  542. leftPanel.appendChild(historyPanel);
  543. panelMain.appendChild(leftPanel);
  544.  
  545. const awardPanel = genDiv('aam_award');
  546. const feedbackLink = genA(t('feedBack'), 'https://steamcommunity.com/id/Chr_/');
  547. feedbackLink.title=t('feedBackTitle');
  548. const awardBot = genSelect([[t('notSelected'), '']], null);
  549. const awardSteamID = genInput('', t('steamID64'), false);
  550. const awardPoints = genInput('', t('awardPoints'), true);
  551. const [awardCProfile, awardProfile] = genCheckbox(t('profile'), true);
  552. const [awardCRecommand, awardRecommand] = genCheckbox(t('recommands'), true);
  553. const [awardCScreenshot, awardScreenshot] = genCheckbox(t('screenshots'), true);
  554. const [awardCImage, awardImage] = genCheckbox(t('artworks'), true);
  555. const awardBtns1 = genDiv('aam_btns');
  556. const awardBtnCurrent = genButton(`🤵${t('setToCurrentUser')}`, getCurrentProfile);
  557. const awardBtnCalc = genButton(`📊${t('calculator')}`, calcAwardItems);
  558. const awardBtns2 = genDiv('aam_btns');
  559. const awardBtnSet = genButton(`💾${t('save')}`, applyAwardConfig);
  560. const awardBtnReset = genButton(`🔨${t('reset')}`, restoreAwardConfig);
  561. const hSwitch = genButton(`🕒${t('awardHistory')}`, leftPanelSwitch);
  562. const awardBtns3 = genDiv('aam_btns aam_award_btns');
  563. const awardBtnStart = genButton(`✅${t('startAward')}`, startAward, false);
  564. const awardBtnStop = genButton(`⛔${t('stopAward')}`, stopAward, false);
  565. const awardStatus = genSpan(`🟥 ${t('stop')}`);
  566.  
  567. awardBtns1.appendChild(awardBtnCurrent);
  568. awardBtns1.appendChild(awardBtnCalc);
  569.  
  570. awardBtns2.appendChild(awardBtnSet);
  571. awardBtns2.appendChild(awardBtnReset);
  572. awardBtns2.appendChild(hSwitch);
  573.  
  574. awardBtns3.appendChild(awardBtnStart);
  575. awardBtns3.appendChild(awardBtnStop);
  576. awardBtns3.appendChild(awardStatus);
  577.  
  578. awardPanel.appendChild(genSpan(t('senderBotAccount')));
  579. awardPanel.appendChild(feedbackLink);
  580. awardPanel.appendChild(genBr());
  581. awardPanel.appendChild(awardBot);
  582. awardPanel.appendChild(genSpan(t('receverAccount')));
  583. awardPanel.appendChild(genBr());
  584. awardPanel.appendChild(awardSteamID);
  585. awardPanel.appendChild(awardBtns1);
  586. awardPanel.appendChild(genSpan(t('sendPoints')));
  587. awardPanel.appendChild(genBr());
  588. awardPanel.appendChild(awardPoints);
  589. awardPanel.appendChild(genSpan(t('awardPrefer')));
  590. awardPanel.appendChild(genBr());
  591. awardPanel.appendChild(awardCProfile);
  592. awardPanel.appendChild(awardCRecommand);
  593. awardPanel.appendChild(genBr());
  594. awardPanel.appendChild(awardCScreenshot);
  595. awardPanel.appendChild(awardCImage);
  596. awardPanel.appendChild(genBr());
  597. awardPanel.appendChild(awardBtns2);
  598. awardPanel.appendChild(genHr());
  599. awardPanel.appendChild(awardBtns3);
  600.  
  601. panelMain.appendChild(awardPanel);
  602.  
  603. Object.assign(GObjs, {
  604. bSwitch, hSwitch, panelMain,
  605. busyPanel, busyMessage, workPanel, workLog, workHide,
  606. accountPanel, accountList, historyPanel, historyList,
  607. awardBot, awardSteamID, awardPoints, awardStatus,
  608. awardProfile, awardRecommand, awardScreenshot, awardImage,
  609. awardBtnStart, awardBtnStop, awardBtnSet, awardBtnReset
  610. });
  611. }
  612. //面板显示开关
  613. function panelSwitch() {
  614. const { bSwitch, panelMain } = GObjs;
  615.  
  616. if (GPanel.panelMain !== true) {
  617. panelMain.style.display = '';
  618. bSwitch.textContent = '🔴';
  619. GPanel.panelMain = true;
  620. } else {
  621. panelMain.style.display = 'none';
  622. bSwitch.textContent = '⭕';
  623. GPanel.panelMain = false;
  624. }
  625. GM_setValue('panel', GPanel);
  626. }
  627. //左侧面板切换
  628. function leftPanelSwitch() {
  629. const { hSwitch, accountPanel, historyPanel } = GObjs;
  630. if (GPanel.panelLeft !== true) {
  631. accountPanel.style.display = 'none';
  632. historyPanel.style.display = '';
  633. hSwitch.textContent = `🤖${t('botList')}`;
  634. GPanel.panelLeft = true;
  635. } else {
  636. historyPanel.style.display = 'none';
  637. accountPanel.style.display = '';
  638. hSwitch.textContent = `🕒${t('awardHistory')}`;
  639. GPanel.panelLeft = false;
  640. }
  641. GM_setValue('panel', GPanel);
  642. }
  643. //添加账户
  644. function accountAdd() {
  645. let v_nick, v_token, v_steamID;
  646. loadScreen(true, t('fetchLoginAccount'));
  647. getMySteamID()
  648. .then(({ nick, steamID }) => {
  649. v_nick = nick;
  650. v_steamID = steamID;
  651. loadScreen(true, t('fetchToken'));
  652. return getToken();
  653. })
  654. .then((tk) => {
  655. v_token = tk;
  656. loadScreen(true, t('fetchPoints'));
  657. return getPoints(v_steamID, tk);
  658. })
  659. .then((points) => {
  660. showAlert(t('success'), `<p>${t('addAccountSuccessTips1')}</p><p>${t('addAccountSuccessTips2')}: ${points} ${t('points')}</p>`, true);
  661. GBots[v_steamID] = { nick: v_nick, token: v_token, points }
  662. GM_setValue('bots', GBots);
  663. flashBotList();
  664. })
  665. .catch((reason) => {
  666. showAlert(t('error'), reason, false);
  667. }).finally(() => {
  668. loadScreen(false, null);
  669. });
  670. }
  671. //删除账户
  672. function accountDel() {
  673. const { accountList } = GObjs;
  674. if (accountList.selectedIndex >= 0) {
  675. showConfirm(t('confirm'), t('deleteAccountConfirmTips'), () => {
  676. let i = 0;
  677. for (const opt of accountList.selectedOptions) {
  678. delete GBots[opt.value];
  679. i++;
  680. }
  681. flashBotList();
  682. GM_setValue('bots', GBots);
  683. showAlert(t('tips'), `${t('deleteAccountDoneTips1')} ${i} ${t('deleteAccountDoneTips2')}`, true);
  684. }, null);
  685. } else {
  686. showAlert(t('tips'), t('notSelectedAnyBotsTips'), false);
  687. }
  688. }
  689. //刷新账户点数
  690. async function flashAllAccounts() {
  691. //刷新点数
  692. function makePromise(sid, tk) {
  693. return new Promise((resolve, reject) => {
  694. getPoints(sid, tk)
  695. .then((points) => {
  696. GBots[sid].points = points;
  697. loadScreen(true, `${t('currentProcess')}: ${++fin} / ${count}`);
  698. }).catch((reason) => {
  699. GBots[sid].points = -1;
  700. // GBots[sid].nick = '读取失败';
  701. loadScreen(true, `${sid} ${t('updateFailed')}: ${reason}`);
  702. }).finally(() => {
  703. GM_setValue('bots', GBots);
  704. resolve();
  705. });
  706. });
  707. }
  708. let count = 0, fin = 0;
  709. for (const _ in GBots) {
  710. count++;
  711. }
  712. if (count > 0) {
  713. loadScreen(true, t('fetchingAccountPoints'));
  714. const pList = [];
  715. for (const steamID in GBots) {
  716. const { token } = GBots[steamID];
  717. pList.push(makePromise(steamID, token));
  718. }
  719. Promise.all(pList)
  720. .finally(() => {
  721. loadScreen(false, null);
  722. flashBotList();
  723. if (fin >= count) {
  724. showAlert(t('done'), t('allDataLoaded'), true);
  725. } else {
  726. showAlert(t('done'), t('someDataLoadFailed'), true);
  727. }
  728. });
  729. } else {
  730. showAlert(t('error'), t('botListEmpty'), false);
  731. }
  732. }
  733. //刷新账户列表
  734. function flashBotList() {
  735. const { bot } = GTask;
  736. const { accountList, awardBot } = GObjs;
  737. accountList.options.length = 0;
  738. awardBot.options.length = 0;
  739. awardBot.options.add(new Option(t('notSelected'), ''))
  740. let i = 1;
  741. let flag = false;
  742. if (!isEmptyObject(GBots)) {
  743. for (const steamID in GBots) {
  744. const { nick, points } = GBots[steamID];
  745. const pointsStr = parseInt(points).toLocaleString();
  746. accountList.options.add(new Option(`${i} | ${nick} | ${steamID} | ${pointsStr} 点`, steamID));
  747. awardBot.options.add(new Option(`${i++} | ${nick} | ${pointsStr} 点`, steamID))
  748. if (steamID === bot) {
  749. flag = true;
  750. awardBot.selectedIndex = i - 1;
  751. }
  752. }
  753. } else {
  754. accountList.options.add(new Option(t('noBotAccountTips'), ''));
  755. }
  756. if ((!isEmptyObject(GTask)) && (!flag)) {
  757. GTask = {};
  758. GM_setValue('task', GTask);
  759. appllyTask();
  760. showAlert(t('tips'), t('awardTaskWasResetTips'), false);
  761. }
  762. }
  763. //刷新历史记录列表
  764. function flashHistoryList() {
  765. const { historyList } = GObjs;
  766. historyList.options.length = 0;
  767. let i = 1;
  768. if (!isEmptyObject(GHistory)) {
  769. for (const steamID in GHistory) {
  770. const [nick, points] = GHistory[steamID];
  771. const pointsStr = parseInt(points).toLocaleString();
  772. historyList.options.add(new Option(`${i++} | ${nick} | ${steamID} | ${pointsStr} 点`, steamID));
  773. }
  774. } else {
  775. historyList.options.add(new Option(t('noHistoryTips'), ''));
  776. }
  777. }
  778. //历史记录增加点数
  779. function addHistory(steamID, nick, points) {
  780. if (GHistory[steamID] !== undefined) {
  781. GHistory[steamID] = [nick, GHistory[steamID][1] + points];
  782. } else {
  783. GHistory[steamID] = [nick, points];
  784. }
  785. GM_setValue('history', GHistory);
  786. }
  787. //获取当前个人资料
  788. function getCurrentProfile() {
  789. const { awardSteamID } = GObjs;
  790. awardSteamID.value = g_rgProfileData.steamid;
  791. }
  792. //计算可打赏项目
  793. function calcAwardItems() {
  794. const { awardSteamID } = GObjs;
  795. const steamID = awardSteamID.value.trim();
  796. if (steamID === '') {
  797. showAlert(t('error'), t('steamIDisEmpty!'), false);
  798. } else {
  799. loadScreen(true, t('fetchingProfile'));
  800. getProfile(steamID)
  801. .then(([succ, nick]) => {
  802. if (succ) {
  803. loadScreen(true, t('fetchingAwardableItems'));
  804. const pList = [
  805. getAwardCounts(steamID, 'r'),
  806. getAwardCounts(steamID, 's'),
  807. getAwardCounts(steamID, 'i')
  808. ];
  809. Promise.all(pList)
  810. .then((result) => {
  811. const data = {};
  812. let sum = 0;
  813. for (const [type, succ, count] of result) {
  814. if (succ) {
  815. const points = count * 6600;
  816. data[type] = `${count} , ${t('awardableAmount')}: ${points.toLocaleString()} ${t('points')}`;
  817. sum += points;
  818. } else {
  819. data[type] = t('fetchError');
  820. }
  821. }
  822. 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>`
  823. showAlert(t('tips'), text, true);
  824. })
  825. .finally(() => {
  826. loadScreen(false, null);
  827. });
  828. } else {
  829. showAlert(t('error'), t('profileNotExistsTips'), false);
  830. loadScreen(false, null);
  831. }
  832. })
  833. .catch((reason) => {
  834. showAlert(t('error'), `<p>${t('profileLoadFailedTips')}</p><p>${reason}</p>`, false)
  835. loadScreen(false, null);
  836. });
  837. }
  838. }
  839. //查看个人资料
  840. function showProfile() {
  841. const { historyList } = GObjs;
  842. const i = historyList.selectedIndex;
  843. if (i > -1) {
  844. const { value } = historyList.options[i];
  845. if (value != '') {
  846. window.open(`https://steamcommunity.com/profiles/${value}`);
  847. }
  848. } else {
  849. showAlert(t('tips'), t('notSelectedAnyHistoryTips'), false);
  850. }
  851. }
  852. //清除历史
  853. function clearHistory() {
  854. if (!isEmptyObject(GHistory)) {
  855. showConfirm(t('confirm'), t('clearHistoryConfirmTips'), () => {
  856. GHistory = {};
  857. flashHistoryList();
  858. GM_setValue('history', GHistory);
  859. showAlert(t('tips'), t('clearHistorySuccess'), true);
  860. }, null);
  861. } else {
  862. showAlert(t('tips'), t('historyListEmpty'), false);
  863. }
  864. }
  865. //删除历史
  866. function deleteHistory() {
  867. const { historyList } = GObjs;
  868. if (historyList.selectedIndex >= 0) {
  869. showConfirm(t('confirm'), t('deleteHistoryConfirmTips'), () => {
  870. let i = 0;
  871. for (const opt of historyList.selectedOptions) {
  872. delete GHistory[opt.value];
  873. i++;
  874. }
  875. flashHistoryList();
  876. GM_setValue('history', GHistory);
  877. showAlert(t('tips'), `${t('deleteResultTips1')} ${i} ${t('deleteResultTips2')}`, true);
  878. }, null);
  879. } else {
  880. showAlert(t('tips'), t('notSelectedAnyHistoryTips'), false);
  881. }
  882. }
  883. //保存打赏设置
  884. function applyAwardConfig() {
  885. const {
  886. awardBtnStart, awardBtnStop,
  887. awardBot, awardSteamID, awardPoints,
  888. awardProfile, awardRecommand, awardScreenshot, awardImage
  889. } = GObjs;
  890.  
  891. awardBtnStart.disabled = awardBtnStop.disabled = true;
  892.  
  893. let bot = awardBot.value;
  894. let points = parseInt(awardPoints.value);
  895. let steamID = String(awardSteamID.value).trim();
  896.  
  897. let type = 0;
  898. if (!awardProfile.checked) { type += 1; }
  899. if (!awardRecommand.checked) { type += 2; }
  900. if (!awardScreenshot.checked) { type += 4; }
  901. if (!awardImage.checked) { type += 8; }
  902.  
  903. if (bot == '') {
  904. showAlert(t('error'), t('notSelectedAwardBotsTips'), false);
  905. } else if (steamID === '') {
  906. showAlert(t('error'), t('steamIDEmptyWithTips'), false);
  907. } else if (!steamID.match(/^\d+$/)) {
  908. showAlert(t('error'), t('steamIDErrorWithTips'), false);
  909. } else if (points !== points || points < 100) {
  910. showAlert(t('error'), t('pointsErrorWithTips'), false);
  911. } else if (type === 15) {
  912. showAlert(t('error'), t('awardTypeEmptyTips'), false);
  913. } else {
  914. points = Math.ceil(points / 100) * 100;
  915. GTask = { bot, steamID, points, type, work: false, nick: null };
  916. awardBtnStart.disabled = awardBtnStop.disabled = false;
  917. GM_setValue('task', GTask);
  918. showAlert(t('tips'), t('awardReadyToStartTips'), true);
  919. }
  920. }
  921. //重置打赏设置
  922. function restoreAwardConfig() {
  923. showConfirm(t('confirm'), t('resetConfigConfirmTips'), () => {
  924. GTask = {};
  925. GM_setValue('task', GTask);
  926. appllyTask();
  927. showAlert(t('tips'), t('configResetSuccessTips'), true);
  928. }, null);
  929. }
  930. //读取设置到界面
  931. function appllyTask() {
  932. const {
  933. awardBtnStart, awardBtnStop,
  934. awardBot, awardSteamID, awardPoints,
  935. awardProfile, awardRecommand, awardScreenshot, awardImage
  936. } = GObjs;
  937. const { bot, steamID, points, type } = GTask;
  938.  
  939. awardBtnStart.disabled = awardBtnStop.disabled = isEmptyObject(GTask);
  940.  
  941. awardBot.value = bot ? bot : '';
  942. awardSteamID.value = steamID ? steamID : '';
  943. awardPoints.value = points ? points : '';
  944.  
  945. awardProfile.checked = !Boolean(type & 1);
  946. awardRecommand.checked = !Boolean(type & 2);
  947. awardScreenshot.checked = !Boolean(type & 4);
  948. awardImage.checked = !Boolean(type & 8);
  949. }
  950. //开始自动打赏
  951. async function startAward() {
  952. if (isEmptyObject(GTask)) {
  953. showAlert(t('error'), t('awardTaskDataInvalid'), false);
  954. return;
  955. }
  956. const { steamID, work, points, bot, nick: taskNick } = GTask;
  957. const { nick: botNick } = GBots[bot];
  958. const pointsStr = parseInt(points).toLocaleString();
  959.  
  960. if (!work) {
  961. spaceLine(1);
  962. if (!taskNick) {
  963. loadScreen(true, t('fetchingTargetProfile'));
  964. getProfile(steamID)
  965. .then(([succ, nickName]) => {
  966. if (succ) {
  967. GTask.work = true;
  968. GTask.nick = nickName;
  969. GM_setValue('task', GTask);
  970. print(`${t('awardConfig')}:\n${t('targetNickName')}: ${nickName}, ${t('targetReceivePoints')}: ${pointsStr}, ${t('targetBot')}: ${botNick}〗`);
  971. print(t('taskReadyToStartTips'));
  972. workScreen(true);
  973. setTimeout(() => {
  974. autoAward();
  975. }, 2000);
  976. } else {
  977. print(t('taskFailedProfileNotFound'), 'E');
  978. showAlert(t('error'), t('profileNotExistsTips'), false);
  979. }
  980. })
  981. .catch((reason) => {
  982. showAlert(t('error'), `<p>${t('profileLoadFailedTips')}</p><p>${reason}</p>`, false)
  983. }).finally(() => {
  984. loadScreen(false, null);
  985. });
  986. } else {
  987. GTask.work = true;
  988. GM_setValue('task', GTask);
  989. print(`〖${t('targetNickName')}: ${taskNick}, ${t('targetReceivePoints')}: ${pointsStr}, ${t('targetBot')}: ${botNick}〗`);
  990. print(t('taskReadyToStartTips'));
  991. workScreen(true);
  992. setTimeout(() => {
  993. autoAward();
  994. }, 2000);
  995. }
  996. } else {
  997. print(t('taskAlreadyStartTips'));
  998. }
  999. }
  1000. //停止自动打赏
  1001. async function stopAward() {
  1002. if (isEmptyObject(GTask)) {
  1003. showAlert(t('error'), t('awardTaskDataInvalid'), false);
  1004. return;
  1005. }
  1006. const { work } = GTask;
  1007. if (work) {
  1008. spaceLine(4);
  1009. print(t('taskEndManually'));
  1010. GTask.work = false;
  1011. GM_setValue('task', GTask);
  1012. showStatus(t('stop'), false);
  1013. } else {
  1014. showAlert(t('error'), t('taskNotStart'), false);
  1015. }
  1016. }
  1017. //打赏项目
  1018. const reactionsDict = {
  1019. 1: 300, 2: 300, 3: 300, 4: 300, 5: 300, 6: 300, 7: 300, 8: 300, 9: 600,
  1020. 10: 1200, 11: 2400, 12: 300, 13: 2400, 14: 600, 15: 1200, 16: 600,
  1021. 17: 4800, 18: 300, 19: 600, 20: 1200, 21: 300, 22: 600, 23: 300
  1022. };
  1023. const reactionValues = [
  1024. 300, 300, 300, 300, 300, 300, 300, 300, 600, 1200, 2400, 300,
  1025. 2400, 600, 1200, 600, 4800, 300, 600, 1200, 300, 600, 300
  1026. ];
  1027. const reactionIDs = [
  1028. 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
  1029. 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
  1030. ];
  1031. //自动打赏
  1032. async function autoAward() {
  1033. //打赏类型
  1034. const reactionType = {
  1035. 'p': ['3', t('profile')], 'r': ['1', t('recommands')], 's': ['2', t('screenshots')], 'i': ['2', t('artworks')]
  1036. };
  1037. const { bot, steamID, type, points: pointsGoal, nick: taskNick } = GTask;
  1038. const { nick: botNick, token } = GBots[bot];
  1039.  
  1040. appllyTask();
  1041. addHistory(steamID, taskNick, 0);
  1042. showStatus(t('running'), true);
  1043. let pointsLeft = pointsGoal;
  1044.  
  1045. if (token) {
  1046. const workflow = [];
  1047. if (!Boolean(type & 8)) { workflow.push('i') };
  1048. if (!Boolean(type & 4)) { workflow.push('s') };
  1049. if (!Boolean(type & 2)) { workflow.push('r') };
  1050. if (!Boolean(type & 1)) { workflow.push('p') };
  1051.  
  1052. while (GTask.work && workflow.length > 0) {
  1053. const award_type = workflow.pop();
  1054. const [target_type, target_name] = reactionType[award_type];
  1055. let process = genProgressBar((pointsGoal - pointsLeft) / pointsGoal * 100);
  1056.  
  1057. spaceLine(3);
  1058. print(`【${target_name}】${t('taskStartPointsSummary')}: ${pointsLeft.toLocaleString()} / ${pointsGoal.toLocaleString()} ${t('points')}`);
  1059. print(`${t('currentProcess')}: ${process}`);
  1060. spaceLine(3);
  1061.  
  1062. let coast = 0;
  1063.  
  1064. if (target_type === '3') { //个人资料
  1065. let GoldReactions = null;
  1066. for (let i = 0; i < 3; i++) { //重试3次
  1067. if (GoldReactions === null) { //旧打赏列表为空,重新读取并打赏
  1068. const [succOld, oldReactions] = await getAwardRecords(token, target_type, steamID);
  1069. if (!succOld) {
  1070. print(t('fetchAwardItemFailedRetry'));
  1071. continue;
  1072. }
  1073. GoldReactions = oldReactions;
  1074. const todoReactions = selectFitableReactions(pointsLeft, GoldReactions);
  1075. if (todoReactions.length === 0) {
  1076. print(`【${target_name}】${t('fetchNoAwardItemSkip')}`);
  1077. break;
  1078. }
  1079. coast = sumReactionsPoints(todoReactions);
  1080. print(`【${target_name}】${t('beforeSendAward')}: ${todoReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`)
  1081. const plist = [];
  1082. for (const id of todoReactions) {
  1083. plist.push(sendAwardReaction(token, target_type, steamID, id));
  1084. }
  1085. print(t('sendingAwards'));
  1086. const result = await Promise.all(plist);
  1087. const [succ, fail] = countSuccess(result);
  1088. print(`${t('fetchSuccessAndFailed')}: ${succ} / ${fail}`);
  1089. }
  1090. //统计新的打赏列表,计算打赏点数
  1091. const [succNew, newReactions] = await getAwardRecords(token, target_type, steamID);
  1092. if (!succNew) {
  1093. print(t('fetchAwardItemFailedRetryIn2Min'));
  1094. await aiosleep(2000);
  1095. continue;
  1096. }
  1097. const diffReactions = filterDiffReactions(newReactions, GoldReactions);
  1098. coast = sumReactionsPoints(diffReactions);
  1099. pointsLeft -= coast;
  1100. addHistory(steamID, taskNick, coast);
  1101. print(`【${target_name}】${t('awardSuccess')}: ${diffReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`);
  1102. break;
  1103. }
  1104. GTask.points = pointsLeft;
  1105. if (pointsLeft <= 0) {
  1106. GTask.work = false;
  1107. }
  1108. GM_setValue('task', GTask);
  1109. process = genProgressBar((pointsGoal - pointsLeft) / pointsGoal * 100);
  1110.  
  1111. spaceLine(3);
  1112. print(`【${target_name}】${t('taskFinishedPointsSummary')}: ${pointsLeft.toLocaleString()} / ${pointsGoal.toLocaleString()} ${t('points')}`);
  1113. print(`${t('currentProcess')}: ${process}`);
  1114. spaceLine(3);
  1115.  
  1116. print(t('updateBotPointsBalance'));
  1117.  
  1118. await getPoints(bot, token)
  1119. .then((p) => {
  1120. GBots[bot].points = p;
  1121. GM_setValue('bots', GBots);
  1122. print(`${t('bot')}【${botNick}】${t('pointsBalanceUpdateSuccess')}: ${p.toLocaleString()} ${t('points')}`);
  1123. if (p < 300) {
  1124. print(`${t('bot')}【${botNick}】${t('lackOfPointsTaskEnd')}`);
  1125. GTask.work = false;
  1126. }
  1127. }).catch((r) => {
  1128. print(`${t('bot')}【${botNick}】${t('pointBalanceUpdateFailed')}: ${r}`);
  1129. });
  1130.  
  1131.  
  1132. } else { //截图
  1133. let page = 1;
  1134. while (GTask.work) {
  1135. let j = 0;
  1136. print(t('fetchingAwardableItems'));
  1137. const [succ, items] = await getAwardItems(steamID, award_type, page++);
  1138. if (!succ) {
  1139. page--;
  1140. if (++j < 3) {
  1141. print(t('fetchAwardItemFailedRetryIn2Min'));
  1142. await aiosleep(2000);
  1143. continue;
  1144. } else {
  1145. print(t('fetchAwardItemFailedSkip'));
  1146. break;
  1147. }
  1148. }
  1149. if (items.length === 0) {
  1150. print(`【${target_name}】${t('taskEndListEmpty')}`);
  1151. break;
  1152. }
  1153.  
  1154. print(`【${target_name}】${t('fetchCompletedTotal')} ${items.length} ${t('entries')}`);
  1155.  
  1156. for (const itemID of items) {
  1157.  
  1158. print(`【${target_name}】${t('objectID')}: ${itemID}`);
  1159. let GoldReactions = null;
  1160.  
  1161. for (let i = 0; i < 3; i++) {
  1162. if (GoldReactions === null) { //旧打赏列表为空,重新读取并打赏
  1163. const [succOld, oldReactions] = await getAwardRecords(token, target_type, itemID);
  1164. if (!succOld) {
  1165. print(t('fetchAwardItemFailedRetry'));
  1166. continue;
  1167. }
  1168. GoldReactions = oldReactions;
  1169. const todoReactions = selectFitableReactions(pointsLeft, GoldReactions);
  1170. if (todoReactions.length === 0) {
  1171. print(`【${target_name}】${t('noAwardableObjectSkip')}`);
  1172. break;
  1173. }
  1174. coast = sumReactionsPoints(todoReactions);
  1175. print(`【${target_name}】${t('willAward')}: ${todoReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`)
  1176. const plist = [];
  1177. for (const id of todoReactions) {
  1178. plist.push(sendAwardReaction(token, target_type, itemID, id));
  1179. }
  1180. print(t('sendingAwards'));
  1181. const result = await Promise.all(plist);
  1182. const [succ, fail] = countSuccess(result);
  1183. print(`${t('requestsSummary')}: ${succ} / ${fail}`);
  1184. }
  1185. print(t('wait2Seconds'));
  1186. await asleep(2000);
  1187. //统计新的打赏列表,计算打赏点数
  1188. const [succNew, newReactions] = await getAwardRecords(token, target_type, itemID);
  1189. if (!succNew) {
  1190. print(t('fetchAwardItemFailedRetry'));
  1191. continue;
  1192. }
  1193. const diffReactions = filterDiffReactions(newReactions, GoldReactions);
  1194. coast = sumReactionsPoints(diffReactions);
  1195. pointsLeft -= coast;
  1196. addHistory(steamID, taskNick, coast);
  1197. print(`【${target_name}】${t('awardSuccess')}: ${diffReactions.length} ${t('itemAndTotal')}: ${coast.toLocaleString()} ${t('points')}`);
  1198. break;
  1199. }
  1200. GTask.points = pointsLeft;
  1201. if (pointsLeft <= 0) {
  1202. GTask.work = false;
  1203. }
  1204. GM_setValue('task', GTask);
  1205. process = genProgressBar((pointsGoal - pointsLeft) / pointsGoal * 100);
  1206.  
  1207. spaceLine(3);
  1208. print(`【${target_name}】${t('taskFinishedPointsSummary')}: ${pointsLeft.toLocaleString()} / ${pointsGoal.toLocaleString()} ${t('points')}`);
  1209. print(`${t('currentProcess')}: ${process}`);
  1210. spaceLine(3);
  1211.  
  1212. print(t('updateBotPointsBalance'));
  1213.  
  1214. await getPoints(bot, token)
  1215. .then((p) => {
  1216. GBots[bot].points = p;
  1217. GM_setValue('bots', GBots);
  1218. print(`${t('bot')}【${botNick}】${t('pointsBalanceUpdateSuccess')}: ${p.toLocaleString()} ${t('points')}`);
  1219. if (p < 300) {
  1220. print(`${t('bot')}【${botNick}】${t('lackOfPointsTaskEnd')}`);
  1221. GTask.work = false;
  1222. }
  1223. }).catch((r) => {
  1224. print(`${t('bot')}【${botNick}】${t('pointBalanceUpdateFailed')}: ${r}`);
  1225. });
  1226.  
  1227. if (!GTask.work) {
  1228. break;
  1229. }
  1230. }
  1231. }
  1232. }
  1233. if (workflow.length > 0) {
  1234. await aiosleep(1000);
  1235. }
  1236. }
  1237. } else {
  1238. delete GBots[bot];
  1239. GM_setValue('bots', GBots);
  1240. print(t('botDataError'));
  1241. showAlert(t('error'), t('botDataError'), false);
  1242. }
  1243. spaceLine(4);
  1244. if (pointsLeft <= 0) {
  1245. GTask = {};
  1246. print(t('awardTaskFinish'));
  1247. } else {
  1248. GTask.work = false;
  1249. print(t('awardTaskNotFinish'));
  1250. }
  1251. GM_setValue('task', GTask);
  1252. appllyTask();
  1253. showStatus(t('stop'), false);
  1254. flashHistoryList();
  1255. }
  1256. //====================================================================================
  1257. //显示提示
  1258. function showAlert(title, text, succ = true) {
  1259. ShowAlertDialog(`${succ ? '✅' : '❌'}${title}`, `<div>${text}</div>`);
  1260. }
  1261. //显示确认
  1262. function showConfirm(title, text, done = null, cancel = null) {
  1263. ShowConfirmDialog(`⚠️${title}`, `<div>${text}</div>`, t('confirm'), t('cancel'))
  1264. .done(() => {
  1265. if (done) { done(); }
  1266. })
  1267. .fail(() => {
  1268. if (cancel) { cancel(); }
  1269. })
  1270. }
  1271. //显示状态
  1272. function showStatus(text, run = true) {
  1273. const { awardStatus, workHide } = GObjs;
  1274. workHide.disabled = run;
  1275. awardStatus.textContent = `${run ? '🟩' : '🟥'} ${text}`;
  1276. }
  1277. //读取设置
  1278. function loadConf() {
  1279. const bots = GM_getValue('bots');
  1280. GBots = isEmptyObject(bots) ? {} : bots;
  1281. const hs = GM_getValue('history');
  1282. GHistory = isEmptyObject(hs) ? {} : hs;
  1283. const task = GM_getValue('task');
  1284. GTask = isEmptyObject(task) ? {} : task;
  1285. const panel = GM_getValue('panel');
  1286. GPanel = isEmptyObject(panel) ? {} : panel;
  1287. }
  1288. //保存设置
  1289. function saveConf() {
  1290. GM_setValue('bots', GBots);
  1291. GM_setValue('history', GHistory);
  1292. GM_setValue('task', GTask);
  1293. GM_setValue('panel', GPanel);
  1294. }
  1295. //是不是空对象
  1296. function isEmptyObject(obj) {
  1297. for (const _ in obj) { return false; }
  1298. return true;
  1299. }
  1300. //显示加载面板
  1301. function loadScreen(show = true, msg = t('operating')) {
  1302. const { busyPanel, busyMessage } = GObjs;
  1303. if (show) {
  1304. busyPanel.style.opacity = '1';
  1305. busyPanel.style.visibility = 'visible';
  1306. if (msg) {
  1307. busyMessage.textContent = msg;
  1308. }
  1309. } else {
  1310. busyPanel.style.opacity = '0';
  1311. busyPanel.style.visibility = 'hidden';
  1312. }
  1313. }
  1314. //显示日志面板
  1315. function workScreen(show = true) {
  1316. const { workPanel } = GObjs;
  1317. if (show) {
  1318. workPanel.style.opacity = '1';
  1319. workPanel.style.visibility = 'visible';
  1320. } else {
  1321. workPanel.style.opacity = '0';
  1322. workPanel.style.visibility = 'hidden';
  1323. }
  1324. }
  1325. //生成进度条
  1326. const BAR_STYLE = '⣀⣄⣤⣦⣶⣷⣿';
  1327. function genProgressBar(percent) {
  1328. const full_symbol = '⣿';
  1329. const none_symbol = '⣀';
  1330. const percentStr = ` ${percent.toFixed(2)}%`
  1331. if (percent >= 100) {
  1332. return full_symbol.repeat(40) + percentStr;
  1333. } else {
  1334. percent = percent / 100;
  1335. let full = Math.floor(percent * 40);
  1336. let rest = percent * 40 - full;
  1337. let middle = Math.floor(rest * 6);
  1338. if (percent !== 0 && full === 0 && middle === 0) { middle = 1; }
  1339. let d = Math.abs(percent - (full + middle / 6) / 40) * 100;
  1340. if (d < Number.POSITIVE_INFINITY) {
  1341. let m = BAR_STYLE[middle];
  1342. if (full === 40) { m = ""; }
  1343. return full_symbol.repeat(full) + m + BAR_STYLE[0].repeat(39 - full) + percentStr;
  1344. }
  1345. return none_symbol.repeat(40) + percentStr;
  1346. }
  1347. }
  1348. //日志时间
  1349. function formatTime() {
  1350. const date = new Date();
  1351. return `${date.toLocaleDateString()} ${date.toTimeString().substr(0, 8)}`;
  1352. }
  1353. //输出日志
  1354. function print(msg, level = 'I') {
  1355. const { workLog } = GObjs;
  1356. const time = formatTime();
  1357. workLog.value += `${time} - ${level} - ${msg}\n`;
  1358. workLog.scrollTop = workLog.scrollHeight;
  1359. console.log(`${time} - ${level} - ${msg}`);
  1360. }
  1361. //画分割线
  1362. function spaceLine(style = 1) {
  1363. switch (style) {
  1364. case 1:
  1365. print('#'.repeat(68));
  1366. return
  1367. case 2:
  1368. print('='.repeat(68));
  1369. return
  1370. case 3:
  1371. print('+'.repeat(68));
  1372. return
  1373. case 4:
  1374. print('~'.repeat(68));
  1375. return
  1376. }
  1377. }
  1378. //异步延时
  1379. function asleep(ms) {
  1380. return new Promise(resolve => setTimeout(resolve, ms));
  1381. }
  1382. //====================================================================================
  1383. //计算合适的打赏项目
  1384. function selectFitableReactions(goal, doneList) {
  1385. const fitableList = [];
  1386. const aviableList = [];
  1387. for (const id of reactionIDs) {
  1388. if (doneList.indexOf(id) === -1) {
  1389. aviableList.push(id);
  1390. }
  1391. }
  1392. aviableList.sort((a, b) => { return reactionsDict[a] - reactionsDict[b]; });
  1393. for (const id of aviableList) {
  1394. if (goal < 100) {
  1395. break;
  1396. }
  1397. const value = reactionsDict[id] / 3;
  1398. if (goal >= value) {
  1399. fitableList.push(id);
  1400. goal -= value;
  1401. }
  1402. }
  1403. return fitableList;
  1404. }
  1405. //获取新增打赏项目
  1406. function filterDiffReactions(newList, oldList) {
  1407. const diffList = [];
  1408. for (const id of newList) {
  1409. if (oldList.indexOf(id) === -1) {
  1410. diffList.push(id);
  1411. }
  1412. }
  1413. return diffList;
  1414. }
  1415. //计算打赏项目点数开销
  1416. function sumReactionsPoints(reactions) {
  1417. let points = 0;
  1418. for (const id of reactions) {
  1419. points += reactionsDict[id];
  1420. }
  1421. return points / 3;
  1422. }
  1423. //统计成功失败
  1424. function countSuccess(result) {
  1425. let succ = 0, fail = 0;
  1426. for (const r of result) {
  1427. if (r) {
  1428. succ++;
  1429. } else {
  1430. fail++;
  1431. }
  1432. }
  1433. return ([succ, fail]);
  1434. }
  1435. //异步延时
  1436. function aiosleep(ms) {
  1437. return new Promise(resolve => setTimeout(resolve, ms))
  1438. }
  1439. //====================================================================================
  1440. function getMySteamID() {
  1441. return new Promise((resolve, reject) => {
  1442. $http.getText('https://store.steampowered.com/account/?l=english')
  1443. .then((text) => {
  1444. let match1 = text.match(/pageheader">([\s\S]+)'s account/);
  1445. let match2 = text.match(/Steam ID: (\d+)/);
  1446.  
  1447. if (match1 && match2) {
  1448. resolve({ nick: match1[1], steamID: match2[1] });
  1449. } else {
  1450. reject(t('steamStoreNotLogin'));
  1451. }
  1452. })
  1453. .catch((reason) => {
  1454. reject(reason);
  1455. });
  1456. });
  1457. }
  1458. function getToken() {
  1459. return new Promise((resolve, reject) => {
  1460. $http.get('https://store.steampowered.com/pointssummary/ajaxgetasyncconfig')
  1461. .then(({ data }) => {
  1462. if (isEmptyObject(data)) {
  1463. reject(t('steamStoreNotLogin'));
  1464. }
  1465. resolve(data.webapi_token);
  1466. })
  1467. .catch((reason) => {
  1468. reject(reason);
  1469. });
  1470. });
  1471. }
  1472. function getPoints(steamID, token) {
  1473. return new Promise((resolve, reject) => {
  1474. $http.get(`https://api.steampowered.com/ILoyaltyRewardsService/GetSummary/v1/?access_token=${token}&steamid=${steamID}`)
  1475. .then(({ response }) => {
  1476. if (isEmptyObject(response)) {
  1477. reject(t('steamStoreNotLogin'));
  1478. }
  1479. try {
  1480. const points = parseInt(response.summary.points);
  1481. if (points === points) {
  1482. resolve(points);
  1483. } else {
  1484. reject(t('parseDataFailedMaybeNetworkError'));
  1485. }
  1486. } catch (e) {
  1487. reject(t('parseDataFailedMaybeNetworkError'));
  1488. }
  1489. })
  1490. .catch((reason) => {
  1491. reject(reason);
  1492. });
  1493. });
  1494. }
  1495. function getProfile(steamID) {
  1496. return new Promise((resolve, reject) => {
  1497. $http.getText(`https://steamcommunity.com/profiles/${steamID}/?xml=1`)
  1498. .then((text) => {
  1499. try {
  1500. const match = text.match(/<steamID><!\[CDATA\[([\s\S]*)\]\]><\/steamID>/) ||
  1501. text.match(/<steamID>([\s\S]*)<\/steamID>/);
  1502. if (match) {
  1503. resolve([true, match[1].substring()]);
  1504. } else {
  1505. resolve([false, null]);
  1506. }
  1507. } catch (e) {
  1508. reject(e);
  1509. }
  1510. })
  1511. .catch((reason) => {
  1512. reject(reason);
  1513. });
  1514. });
  1515. }
  1516. function getAwardCounts(steamID, type) {
  1517. let subPath, preg;
  1518. switch (type) {
  1519. case 'r':
  1520. subPath = 'recommended/?l=schinese';
  1521. preg = /共 (\d+) 项条目/;
  1522. break;
  1523. case 's':
  1524. subPath = 'screenshots/?l=schinese';
  1525. preg = /共 (\d+) 张/;
  1526. break;
  1527. case 'i':
  1528. subPath = 'images/?l=schinese';
  1529. preg = /共 (\d+) 张/;
  1530. break;
  1531. default:
  1532. throw 'type错误';
  1533. }
  1534. return new Promise((resolve, reject) => {
  1535. $http.getText(`https://steamcommunity.com/profiles/${steamID}/${subPath}`)
  1536. .then((text) => {
  1537. try {
  1538. const match = text.match(preg);
  1539. const count = match ? Number(match[1]) : 0;
  1540. resolve([type, true, count]);
  1541. } catch (e) {
  1542. resolve([type, false, 0]);
  1543. }
  1544. })
  1545. .catch((reason) => {
  1546. console.error(reason);
  1547. resolve([type, false, 0]);
  1548. });
  1549. });
  1550. }
  1551. function getAwardItems(steamID, type, p = 1) {
  1552. let subPath, preg;
  1553. switch (type) {
  1554. case 'r':
  1555. subPath = `recommended/?p=${p}&l=schinese`;
  1556. preg = /id="RecommendationVoteUpBtn(\d+)"/g;
  1557. break;
  1558. case 's':
  1559. subPath = `screenshots/?p=${p}&view=grid&l=schinese`;
  1560. preg = /id="imgWallHover(\d+)"/g;
  1561. break;
  1562. case 'i':
  1563. subPath = `images/?p=${p}&view=grid&l=schinese`;
  1564. preg = /id="imgWallHover(\d+)"/g;
  1565. break;
  1566. default:
  1567. throw t('typeError');
  1568. }
  1569. return new Promise((resolve, reject) => {
  1570. $http.getText(`https://steamcommunity.com/profiles/${steamID}/${subPath}`)
  1571. .then((text) => {
  1572. try {
  1573. const result = [];
  1574. const matches = text.matchAll(preg);
  1575. for (const match of matches) {
  1576. result.push(match[1]);
  1577. }
  1578. resolve([true, result]);
  1579. } catch (e) {
  1580. console.error(e);
  1581. resolve([false, e]);
  1582. }
  1583. })
  1584. .catch((reason) => {
  1585. console.error(reason);
  1586. resolve([false, reason]);
  1587. });
  1588. });
  1589. }
  1590. function getAwardRecords(token, targetType, targetID) {
  1591. return new Promise((resolve, reject) => {
  1592. const params = `access_token=${token}&target_type=${targetType}&targetid=${targetID}`;
  1593. $http.get('https://api.steampowered.com/ILoyaltyRewardsService/GetReactions/v1/?' + params)
  1594. .then(({ response }) => {
  1595. const { reactionids } = response;
  1596. resolve([true, reactionids || []]);
  1597. })
  1598. .catch((reason) => {
  1599. console.error(reason);
  1600. resolve([false, null]);
  1601. });
  1602. });
  1603. }
  1604. function sendAwardReaction(token, targetType, targetID, reactionID) {
  1605. return new Promise((resolve, reject) => {
  1606. const params = `access_token=${token}&target_type=${targetType}&targetid=${targetID}&reactionid=${reactionID}`;
  1607. $http.post('https://api.steampowered.com/ILoyaltyRewardsService/AddReaction/v1/?' + params)
  1608. .then((json) => {
  1609. console.log(json);
  1610. resolve(true);
  1611. })
  1612. .catch((reason) => {
  1613. console.error(reason);
  1614. resolve(false);
  1615. });
  1616. });
  1617. }
  1618. })();
  1619. //====================================================================================
  1620. class Request {
  1621. constructor(timeout = 3000) {
  1622. this.timeout = timeout;
  1623. }
  1624. get(url, opt = {}) {
  1625. return this.baseRequest(url, 'GET', opt, 'json');
  1626. }
  1627. getHtml(url, opt = {}) {
  1628. return this.baseRequest(url, 'GET', opt, '');
  1629. }
  1630. getText(url, opt = {}) {
  1631. return this.baseRequest(url, 'GET', opt, 'text');
  1632. }
  1633. post(url, data, opt = {}) {
  1634. opt.data = JSON.stringify(data);
  1635. return this.baseRequest(url, 'POST', opt, 'json');
  1636. }
  1637. baseRequest(url, method = 'GET', opt = {}, responseType = 'json') {
  1638. Object.assign(opt, {
  1639. url, method, responseType, timeout: this.timeout
  1640. });
  1641. return new Promise((resolve, reject) => {
  1642. opt.ontimeout = opt.onerror = reject;
  1643. opt.onload = ({ readyState, status, response, responseText }) => {
  1644. if (readyState === 4 && status === 200) {
  1645. if (responseType == 'json') {
  1646. resolve(response);
  1647. } else if (responseType == 'text') {
  1648. resolve(responseText);
  1649. }
  1650. } else {
  1651. console.error(t('networkError'));
  1652. console.log(readyState);
  1653. console.log(status);
  1654. console.log(response);
  1655. reject(t('parseError'));
  1656. }
  1657. }
  1658. GM_xmlhttpRequest(opt);
  1659. });
  1660. }
  1661. }
  1662. const $http = new Request();
  1663.  
  1664. //CSS表
  1665. GM_addStyle(`.aam_panel,
  1666. .aam_work {
  1667. padding: 10px;
  1668. display: flex;
  1669. }
  1670. .aam_work {
  1671. z-index: 500 !important;
  1672. }
  1673. .aam_busy {
  1674. width: 100%;
  1675. height: 100%;
  1676. z-index: 700;
  1677. position: absolute;
  1678. top: 0;
  1679. left: 0;
  1680. background: rgba(0, 0, 0, 0.7);
  1681. display: table;
  1682. visibility: hidden;
  1683. opacity: 0;
  1684. transition: all 0.1s;
  1685. }
  1686. .aam_busy_content {
  1687. display: table-cell;
  1688. vertical-align: middle;
  1689. text-align: center;
  1690. }
  1691. .aam_left {
  1692. width: 61%;
  1693. padding-right: 10px;
  1694. }
  1695. .aam_award {
  1696. width: 39%;
  1697. }
  1698. .aam_list,
  1699. .aam_select,
  1700. .aam_input,
  1701. .aam_textarea {
  1702. background-color: #fff !important;
  1703. color: #000 !important;
  1704. border: none !important;
  1705. border-radius: 0 !important;
  1706. }
  1707. .aam_input {
  1708. width: 98% !important;
  1709. text-align: center;
  1710. }
  1711. .aam_list {
  1712. height: 230px;
  1713. }
  1714. .aam_textarea {
  1715. height: calc(100% - 85px);
  1716. width: calc(100% - 45px);
  1717. resize: none;
  1718. font-size: 12px;
  1719. }
  1720. .aam_left > div > *,
  1721. .aam_award:not(span, button) > * {
  1722. width: 100%;
  1723. margin-bottom: 5px;
  1724. }
  1725. .aam_btns > button:not(:last-child) {
  1726. margin-right: 4px;
  1727. }
  1728. .aam_award_btns {
  1729. z-index: 600;
  1730. bottom: 10px;
  1731. position: absolute;
  1732. }
  1733. .aam_work > * {
  1734. position: absolute;
  1735. }
  1736. .aam_work > span {
  1737. bottom: 12%;
  1738. left: 70px;
  1739. }
  1740. .aam_work > button {
  1741. bottom: 11%;
  1742. }
  1743. .aam_a {
  1744. margin-left: 110px;
  1745. }`);

QingJ © 2025

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