AutoMudae

Automates the use of Mudae bot in Discord

  1. // ==UserScript==
  2. // @name AutoMudae
  3. // @description Automates the use of Mudae bot in Discord
  4. // @version 0.8.5
  5. // @author Nxve
  6. // @license GNU GPLv3
  7. // @namespace https://github.com/Nxve/AutoMudae
  8. // @supportURL https://github.com/Nxve/AutoMudae/issues
  9. // @match https://discord.com/channels/*
  10. // @exclude https://discord.com/channels/@me
  11. // @run-at document-start
  12. // @icon https://icons.duckduckgo.com/ip2/discord.com.ico
  13. // @grant GM_addStyle
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_info
  17. // ==/UserScript==
  18.  
  19. (function () {
  20. const window = unsafeWindow;
  21. const localStorage = window.localStorage;
  22.  
  23. //// Logger
  24. const _logger = {
  25. _preffix: '%c[AUTO MUDAE]',
  26. _symbols: { error: '[!]', info: '[i]', log: '[*]', plus: '[+]', debug: '[!]', warn: '[!]' },
  27. _color: { error: 'red', info: 'cyan', log: 'white', plus: 'lime', debug: 'cyan', warn: 'gold' },
  28. _history: [],
  29. _lastMessageHash: null,
  30. _hash: s => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0),
  31. _print: function (type, ...etc) {
  32. const hash = [...arguments].toString();
  33. // if (hash === this._lastMessageHash) return;
  34.  
  35. console.log(`${this._preffix}%c${this._symbols[type]}`, 'background: black; color: magenta;', `background: black; color: ${this._color[type]}`, ...etc);
  36.  
  37. if (type !== 'debug') this._history.push([type, [...etc]]);
  38. this._lastMessageHash = hash;
  39. },
  40. _reprompt: function () {
  41. this._history.forEach(log => this[log[0]](...log[1]));
  42. }
  43. };
  44.  
  45. const logger = {};
  46.  
  47. ['error', 'info', 'log', 'plus', 'debug', 'warn'].forEach(method => {
  48. logger[method] = function () { this._print(method, ...arguments) };
  49. });
  50.  
  51. /// I use prototype here to prevent exposing private properties in DevTools.
  52. Object.setPrototypeOf(logger, _logger);
  53.  
  54. window.logger = logger;
  55.  
  56. //// ENUM
  57. const E = {};
  58.  
  59. E.DISCORD_INFO = {
  60. CHANNEL_ID: 'channel_id',
  61. GUILD_ID: 'guild_id',
  62. SESSION_ID: 'session_id'
  63. };
  64. E.AUTOMUDAE_STATE = {
  65. INJECT: 'inject',
  66. SETUP: 'setup',
  67. ERROR: 'error',
  68. IDLE: 'idle',
  69. RUN: 'run',
  70. };
  71. E.MUDAE_INFO = {
  72. ROLLS_MAX: 'rolls_max',
  73. ROLLS_LEFT: 'rolls_left',
  74. POWER: 'power',
  75. CAN_RT: 'can_rt',
  76. CAN_MARRY: 'can_marry',
  77. CONSUMPTION: 'kakera_consumption'
  78. };
  79.  
  80. E.TOAST = {
  81. INFO: 'info',
  82. WARN: 'warn',
  83. CRITICAL: 'critical',
  84. KAKERA: 'kakera',
  85. CHARCLAIM: 'charclaim',
  86. SOULMATE: 'soulmate'
  87. };
  88. E.EMOJI = {
  89. '💓': '%F0%9F%92%93',
  90. '💕': '%F0%9F%92%95',
  91. '💖': '%F0%9F%92%96',
  92. '💗': '%F0%9F%92%97',
  93. '💘': '%F0%9F%92%98',
  94. '❤️': '%E2%9D%A4%EF%B8%8F',
  95. '❣️': '%E2%9D%A3%EF%B8%8F',
  96. '💞': '%F0%9F%92%9E',
  97. '♥️': '%E2%99%A5%EF%B8%8F'
  98. };
  99.  
  100. E.EMOJI_KAKERA = {
  101. kakeraP: 'kakeraP%3A609264156347990016',
  102. kakera: 'kakera%3A469791929106956298',
  103. kakeraT: 'kakeraT%3A609264180851376132',
  104. kakeraG: 'kakeraG%3A609264166381027329',
  105. kakeraY: 'kakeraY%3A605112931168026629',
  106. kakeraO: 'kakeraO%3A605112954391887888',
  107. kakeraR: 'kakeraR%3A605112980295647242',
  108. kakeraW: 'kakeraW%3A608192076286263297',
  109. kakeraL: 'kakeraL%3A815961697918779422',
  110. };
  111. E.KAKERA = {
  112. PURPLE: 'kakeraP',
  113. BLUE: 'kakera',
  114. CYAN: 'kakeraT',
  115. GREEN: 'kakeraG',
  116. YELLOW: 'kakeraY',
  117. ORANGE: 'kakeraO',
  118. RED: 'kakeraR',
  119. RAINBOW: 'kakeraW',
  120. LIGHT: 'kakeraL',
  121. };
  122.  
  123. E.GMVALUE = {
  124. PREFERENCES: 'preferences',
  125. VERSION: 'version',
  126. TOKENLIST: 'tokenlist'
  127. };
  128.  
  129. E.PREFERENCES = {
  130. KAKERA: 'kakera',
  131. MENTIONS: 'mentions',
  132. ROLL: 'roll',
  133. SOUND: 'sound',
  134. EXTRA: 'extra'
  135. };
  136.  
  137. E.INFO_FIELD = {
  138. KAKERA: 'kakera',
  139. COLLECTED_CHARACTERS: 'collected-characters',
  140. ROLLS_LEFT: 'rolls-left',
  141. ROLLS_MAX: 'rolls-max',
  142. POWER: 'power',
  143. POWER_CONSUMPTION: 'consumption',
  144. CAN_MARRY: 'marry',
  145. CAN_RT: 'rt'
  146. };
  147.  
  148. E.SLASH_COMMANDS = {
  149. "wx": { version: "832172261968314389", id: "832172261968314388" },
  150. "wa": { version: "832172151729422418", id: "832172151729422417" },
  151. "wg": { version: "832172216665374751", id: "832172216665374750" },
  152. "hx": { version: "832172373536669707", id: "832172373536669706" },
  153. "ha": { version: "832172457028747337", id: "832172457028747336" },
  154. "hg": { version: "832172416192872459", id: "832172416192872458" },
  155. };
  156.  
  157. //// SOUND
  158. const audioCtx = new AudioContext();
  159.  
  160. function beep(gain, hz, ms, times = 1){
  161. for (let i = 0; i < times; i++) {
  162. const v = audioCtx.createOscillator();
  163. const u = audioCtx.createGain();
  164. v.connect(u);
  165. v.frequency.value = hz;
  166. v.type = "square";
  167. u.connect(audioCtx.destination);
  168. u.gain.value = gain * 0.01;
  169. const durationInSeconds = ms * .001;
  170. v.start(audioCtx.currentTime + i * (durationInSeconds*1.5));
  171. v.stop(audioCtx.currentTime + durationInSeconds + i * (durationInSeconds*1.5));
  172. }
  173. };
  174.  
  175. const SOUND = {
  176. foundCharacter: () => {beep(5, 400, 100, 1)},
  177. marry: () => {beep(10, 600, 100, 1)},
  178. critical: () => {beep(15, 70, 80, 6)},
  179. lastResetNoRolls: () => {beep(10, 60, 250, 2)},
  180. newSoulmate: () => {beep(10, 600, 100, 2)}
  181. };
  182.  
  183. //// CSS
  184. const CSS = {};
  185.  
  186. CSS.decorators = `
  187. li[id^=chat-message]:is(.plus, .critical){
  188. position: relative;
  189. }
  190.  
  191. li[id^=chat-message]:is(.plus, .critical)::after{
  192. position: absolute;
  193. bottom: 0;
  194. width: 22px;
  195. height: 100%;
  196. display: flex;
  197. align-items: center;
  198. justify-content: center;
  199. }
  200.  
  201. li[id^=chat-message].plus{
  202. background-color: hsl(138deg 100% 50% / 10%);
  203. }
  204.  
  205. li[id^=chat-message].plus::after {
  206. content: '+';
  207. background-color: hsl(109deg 45% 18%);
  208. color: lime;
  209. }
  210.  
  211. li[id^=chat-message].critical{
  212. background-color: hsl(0deg 100% 50% / 10%);
  213. }
  214.  
  215. li[id^=chat-message].critical::after {
  216. content: '!';
  217. background-color: hsl(0deg 45% 18%);
  218. color: red;
  219. }
  220. `;
  221.  
  222. CSS.general = `
  223. ::-webkit-scrollbar {
  224. width: 2px;
  225. }
  226. ::-webkit-scrollbar-thumb {
  227. background-color: rgba(0, 0, 0, 0.8);
  228. }
  229.  
  230. .automudae-hide, .automudae-hide *, .automudae-hide::before, .automudae-hide::after {
  231. display: none !important;
  232. }
  233. `;
  234.  
  235. CSS.stateText = `
  236. #automudae-state {
  237. display: flex;
  238. gap: 10px;
  239. margin-right: 10px;
  240. color: var(--text-normal);
  241. }
  242. `;
  243.  
  244. CSS.runButton = `
  245. #automudae-run-button {
  246. display: flex;
  247. align-items: center;
  248. gap: 4px;
  249. padding: 1px 5px;
  250. margin-right: 20px;
  251. background-color: var(--button-outline-brand-background-active);
  252. cursor: pointer;
  253. transition: 200ms;
  254. }
  255. #automudae-run-button:hover {
  256. background-color: var(--button-outline-brand-background-hover);
  257. transform: scale(1.1);
  258. }
  259. #automudae-run-button::before {
  260. content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='15' fill='%23DCDDDE' viewBox='0 0 16 12'%3E%3Cpath d='m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z'/%3E%3C/svg%3E");
  261. }
  262. #automudae-run-button.running::before {
  263. content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23DCDDDE' viewBox='0 0 16 12'%3E%3Cpath d='M5.5 3.5A1.5 1.5 0 0 1 7 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5zm5 0A1.5 1.5 0 0 1 12 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5z'/%3E%3C/svg%3E");
  264. }
  265. #automudae-run-button::after {
  266. content: "Run";
  267. }
  268. #automudae-run-button.running::after {
  269. content: "Pause";
  270. }
  271. `;
  272.  
  273. CSS.injectionsAndError = `
  274. #automudae-injections-wrapper,
  275. #automudae-error {
  276. position: absolute;
  277. inset: auto 0;
  278. top: 8px;
  279. width: fit-content;
  280. margin-inline: auto;
  281. padding: 5px;
  282. z-index: 9999;
  283. }
  284.  
  285. #automudae-injections-wrapper {
  286. display: flex;
  287. gap: 10px;
  288. padding: 0;
  289. }
  290.  
  291. #automudae-injections-wrapper > div {
  292. padding: 5px;
  293. background-color: var(--button-outline-brand-background-active);
  294. color: var(--text-normal);
  295. font-weight: 500;
  296. cursor: pointer;
  297. transition: 200ms;
  298. }
  299.  
  300. #automudae-injections-wrapper > div:hover {
  301. background-color: var(--button-outline-brand-background-hover);
  302. transform: scale(1.1);
  303. }
  304.  
  305. #automudae-error {
  306. background-color: var(--button-danger-background);
  307. color: white;
  308. animation: popIn 150ms forwards;
  309. transform: scale(0);
  310. }
  311.  
  312. @keyframes popIn {
  313. to {
  314. top: 40px;
  315. transform: scale(1);
  316. }
  317. }
  318. `;
  319.  
  320. CSS.sidePanels = `
  321. [id^=automudae-panel] {
  322. background-color: var(--background-primary);
  323. font-weight: 500;
  324. display: flex;
  325. flex-direction: column;
  326. gap: 10px;
  327. transition: 500ms;
  328. overflow: hidden;
  329. height: fit-content;
  330. }
  331.  
  332. [id^=automudae-panel] > * {
  333. background-color: var(--interactive-muted);
  334. }
  335.  
  336. [id^=automudae-panel] :is(h1, h2) {
  337. background-color: var(--background-tertiary);
  338. color: var(--text-normal);
  339. display: flex;
  340. align-items: center;
  341. justify-content: center;
  342. }
  343.  
  344. [id^=automudae-panel] h1 {
  345. font-size: large;
  346. height: 1.5rem;
  347. background-color: var(--button-outline-brand-background-active);
  348. cursor: pointer;
  349. }
  350.  
  351. [id^=automudae-panel] h1:hover {
  352. background-color: var(--button-outline-brand-background-hover) !important;
  353. }
  354.  
  355. [id^=automudae-panel] h2 {
  356. font-size: medium;
  357. height: 1rem;
  358. }
  359.  
  360. [id^=automudae-panel] textarea {
  361. font-weight: 900;
  362. max-height: 100px;
  363. }
  364.  
  365. [id^=automudae-panel] span {
  366. font-size: small;
  367. color: var(--text-normal);
  368. }
  369.  
  370. [id^=automudae-panel] ul {
  371. width: 100%;
  372. font-size: small;
  373. color: var(--text-normal);
  374. max-height: 10rem;
  375. overflow-x: clip;
  376. overflow-y: auto;
  377. }
  378.  
  379. [id^=automudae-panel] li:nth-child(odd) {
  380. background-color: var(--background-primary);
  381. }
  382.  
  383. .automudae-section {
  384. margin-bottom: 5px;
  385. }
  386.  
  387. .automudae-section-body {
  388. display: flex;
  389. padding: 4px;
  390. flex-wrap: wrap;
  391. }
  392.  
  393. .automudae-section-body > div {
  394. display: flex;
  395. padding-inline: 3px;
  396. border-radius: 5px;
  397. align-items: center;
  398. }
  399.  
  400. .automudae-section-body > div:hover {
  401. background-color: var(--button-secondary-background-hover);
  402. }
  403.  
  404. #automudae-panel-info .automudae-section-body {
  405. flex-direction: column;
  406. gap: 8px;
  407. }
  408.  
  409. #automudae-section-kakera > div {
  410. justify-content: space-between;
  411. }
  412.  
  413. #automudae-section-kakera > div > div {
  414. flex-direction: column;
  415. padding: 0;
  416. }
  417.  
  418. #automudae-section-status .automudae-section-body {
  419. padding: 0;
  420. }
  421.  
  422. .automudae-row {
  423. display: flex;
  424. align-items: center;
  425. justify-content: space-between;
  426. gap: 10px;
  427. }
  428.  
  429. .automudae-row > div {
  430. display: flex;
  431. align-items: center;
  432. }
  433. .automudae-row-expandable {
  434. padding: 3px;
  435. display: block !important;
  436. }
  437.  
  438. .automudae-row-expandable > div:not(:first-child) {
  439. margin-top: 2px;
  440. background-color: var(--background-primary);
  441. max-height: 0px;
  442. overflow: hidden;
  443. transition: max-height 300ms linear;
  444. }
  445. .automudae-row-expandable:hover > div:not(:first-child) {
  446. max-height: 300px;
  447. }
  448. .automudae-row-expandable > div:not(:first-child) > .automudae-row:hover {
  449. background-color: var(--background-accent);
  450. }
  451.  
  452. [id^=automudae-panel] > div {
  453. max-height: 600px;
  454. transition: max-height 400ms cubic-bezier(0, 1, 1, 1);
  455. }
  456.  
  457. [id^=automudae-panel].collapsed {
  458. gap: 0px;
  459. }
  460. [id^=automudae-panel].collapsed > div {
  461. max-height: 0px;
  462. }
  463.  
  464. [data-requirerestart] {
  465. position: relative;
  466. }
  467. [data-requirerestart]::before {
  468. content: '*';
  469. color: yellow;
  470. position: absolute;
  471. left: 0px;
  472. }
  473. [data-requirerestart]:hover::before {
  474. content: '* Require restart to apply changes!';
  475. position: absolute;
  476. bottom: 20px;
  477. background-color: var(--background-tertiary);
  478. font-size: x-small;
  479. padding: 2px 10px;
  480. color: yellow;
  481. border-radius: 5px;
  482. pointer-events: none;
  483. }
  484. `;
  485.  
  486. CSS.toasts = `
  487. #automudae-toasts-wrapper {
  488. position: absolute;
  489. right: 15px;
  490. width: 37%;
  491. height: 97%;
  492. display: flex;
  493. flex-direction: column;
  494. justify-content: flex-end;
  495. align-items: flex-end;
  496. gap: 8px;
  497. z-index: 9;
  498. }
  499. .automudae-toast {
  500. background-color: white;
  501. padding: 5px;
  502. font-weight: 500;
  503. animation: slide-in-blurred-left 0.6s cubic-bezier(0.230, 1.000, 0.320, 1.000) both;
  504. }
  505. .automudae-toast.info {
  506. --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='20' fill='%235865f2' viewBox='0 0 16 16'%3E%3Cpath d='M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm8.93 4.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM8 5.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2z'/%3E%3C/svg%3E");
  507. background-color: var(--button-outline-brand-border);
  508. }
  509. .automudae-toast.kakera {
  510. --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='20' fill='%23dee0fc' viewBox='0 0 16 16'%3E%3Cpath d='M3.1.7a.5.5 0 0 1 .4-.2h9a.5.5 0 0 1 .4.2l2.976 3.974c.149.185.156.45.01.644L8.4 15.3a.5.5 0 0 1-.8 0L.1 5.3a.5.5 0 0 1 0-.6l3-4zm11.386 3.785-1.806-2.41-.776 2.413 2.582-.003zm-3.633.004.961-2.989H4.186l.963 2.995 5.704-.006zM5.47 5.495 8 13.366l2.532-7.876-5.062.005zm-1.371-.999-.78-2.422-1.818 2.425 2.598-.003zM1.499 5.5l5.113 6.817-2.192-6.82L1.5 5.5zm7.889 6.817 5.123-6.83-2.928.002-2.195 6.828z'/%3E%3C/svg%3E");
  511. background-color: var(--brand-experiment-200);
  512. }
  513. .automudae-toast.charclaim {
  514. --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='20' fill='%2346c46e' viewBox='0 0 16 16'%3E%3Cpath d='M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z'/%3E%3C/svg%3E");
  515. background-color: var(--text-positive);
  516. }
  517. .automudae-toast.soulmate {
  518. --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='20' fill='violet' viewBox='0 0 16 16'%3E%3Cpath d='M7.657 6.247c.11-.33.576-.33.686 0l.645 1.937a2.89 2.89 0 0 0 1.829 1.828l1.936.645c.33.11.33.576 0 .686l-1.937.645a2.89 2.89 0 0 0-1.828 1.829l-.645 1.936a.361.361 0 0 1-.686 0l-.645-1.937a2.89 2.89 0 0 0-1.828-1.828l-1.937-.645a.361.361 0 0 1 0-.686l1.937-.645a2.89 2.89 0 0 0 1.828-1.828l.645-1.937zM3.794 1.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387A1.734 1.734 0 0 0 4.593 5.69l-.387 1.162a.217.217 0 0 1-.412 0L3.407 5.69A1.734 1.734 0 0 0 2.31 4.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387A1.734 1.734 0 0 0 3.407 2.31l.387-1.162zM10.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732L9.1 2.137a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L10.863.1z'/%3E%3C/svg%3E");
  519. background-color: violet;
  520. }
  521. .automudae-toast.warn{
  522. --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='20' fill='%23faa81a' class='bi bi-exclamation-diamond-fill' viewBox='0 0 16 16'%3E%3Cpath d='M9.05.435c-.58-.58-1.52-.58-2.1 0L.436 6.95c-.58.58-.58 1.519 0 2.098l6.516 6.516c.58.58 1.519.58 2.098 0l6.516-6.516c.58-.58.58-1.519 0-2.098L9.05.435zM8 4c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995A.905.905 0 0 1 8 4zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z'/%3E%3C/svg%3E");
  523. background-color: var(--text-warning);
  524. }
  525. .automudae-toast.critical{
  526. --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='20' fill='%23ed4245' class='bi bi-exclamation-diamond-fill' viewBox='0 0 16 16'%3E%3Cpath d='M9.05.435c-.58-.58-1.52-.58-2.1 0L.436 6.95c-.58.58-.58 1.519 0 2.098l6.516 6.516c.58.58 1.519.58 2.098 0l6.516-6.516c.58-.58.58-1.519 0-2.098L9.05.435zM8 4c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995A.905.905 0 0 1 8 4zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z'/%3E%3C/svg%3E");
  527. background-color: var(--status-danger);
  528. }
  529.  
  530. .automudae-toast.link {
  531. cursor: alias;
  532. }
  533. .automudae-toast.missing {
  534. animation: wobble-hor-bottom 0.8s both;
  535. }
  536.  
  537. .automudae-toast:hover::before {
  538. --width: 18px;
  539. --height: 100;
  540. content: var(--svg);
  541. position: absolute;
  542. left: calc(calc(-1 * var(--width)) - 5px);
  543. top: calc(.5% * calc(100 - var(--height)));
  544. height: calc(1% * var(--height));
  545. width: var(--width);
  546. display: flex;
  547. align-items: center;
  548. justify-content: center;
  549. animation: flipHorz 1s cubic-bezier(0.455, 0.030, 0.515, 0.955) both;
  550. pointer-events: none;
  551. }
  552. .automudae-toast:nth-last-child(12) {
  553. opacity: 0.6;
  554. }
  555. .automudae-toast:nth-last-child(13) {
  556. opacity: 0.5;
  557. }
  558. .automudae-toast:nth-last-child(14) {
  559. opacity: 0.4;
  560. }
  561. .automudae-toast:nth-last-child(15) {
  562. opacity: 0.3;
  563. }
  564. .automudae-toast:nth-last-child(16) {
  565. opacity: 0.2;
  566. }
  567. .automudae-toast:nth-last-child(17) {
  568. opacity: 0.1;
  569. }
  570. .automudae-toast:nth-last-child(n+18) {
  571. opacity: 0;
  572. }
  573.  
  574. @keyframes flipHorz {
  575. 0% {
  576. transform: rotateY(0);
  577. }
  578. 100% {
  579. transform: rotateY(-360deg);
  580. }
  581. }
  582.  
  583. @keyframes slide-in-blurred-left {
  584. 0% {
  585. transform: translateX(-1000px) scaleX(2.5) scaleY(0.2);
  586. transform-origin: 100% 50%;
  587. filter: blur(40px);
  588. }
  589. 100% {
  590. transform: translateX(0) scaleY(1) scaleX(1);
  591. transform-origin: 50% 50%;
  592. filter: blur(0);
  593. }
  594. }
  595.  
  596. @keyframes wobble-hor-bottom {
  597. 0%,
  598. 100% {
  599. transform: translateX(0%);
  600. transform-origin: 50% 50%;
  601. }
  602. 15% {
  603. transform: translateX(-30px) rotate(-6deg);
  604. }
  605. 30% {
  606. transform: translateX(15px) rotate(6deg);
  607. }
  608. 45% {
  609. transform: translateX(-15px) rotate(-3.6deg);
  610. }
  611. 60% {
  612. transform: translateX(9px) rotate(2.4deg);
  613. }
  614. 75% {
  615. transform: translateX(-6px) rotate(-1.2deg);
  616. }
  617. }
  618. `;
  619.  
  620. CSS.tokenList = `
  621. #automudae-tokenlist-wrapper {
  622. position: absolute;
  623. width: 100%;
  624. height: 100%;
  625. z-index: 9999;
  626. display: flex;
  627. align-items: center;
  628. justify-content: center;
  629. }
  630.  
  631. #automudae-tokenlist {
  632. background-color: var(--background-tertiary);
  633. width: 400px;
  634. height: 80vh;
  635. display: flex;
  636. flex-direction: column;
  637. font-weight: 400;
  638. color: white;
  639. justify-content: space-between;
  640. align-items: center;
  641. padding: 5px;
  642. }
  643.  
  644. #automudae-tokenlist-accept {
  645. width: 100%;
  646. text-align: center;
  647. padding-block: 5px;
  648. background-color: var(--status-positive-background);
  649. transition: 200ms;
  650. cursor: pointer;
  651. }
  652.  
  653. #automudae-tokenlist h3 {
  654. font-size: x-large;
  655. position: relative;
  656. }
  657.  
  658. #automudae-tokenlist h3::after {
  659. content: '';
  660. position: absolute;
  661. left: 0px;
  662. bottom: -4px;
  663. height: 2px;
  664. width: 100%;
  665. background-color: var(--background-modifier-accent);
  666. }
  667.  
  668. #automudae-tokenlist ul {
  669. width: 400px;
  670. height: 60vh;
  671. background-color: var(--background-floating);
  672. display: flex;
  673. flex-direction: column;
  674. gap: 5px;
  675. overflow-y: overlay;
  676. }
  677.  
  678. #automudae-tokenlist-accept:hover {
  679. background-color: var(--status-positive);
  680. }
  681.  
  682. #automudae-tokenlist-controls {
  683. display: flex;
  684. flex-direction: row;
  685. justify-content: flex-end;
  686. gap: 1rem;
  687. padding-right: 10px;
  688. }
  689.  
  690. #automudae-tokenlist-controls > div {
  691. padding: 3px;
  692. font-size: small;
  693. cursor: pointer;
  694. transition: 200ms;
  695. }
  696.  
  697. #automudae-tokenlist-controls > div:hover {
  698. background-color: white;
  699. color: black;
  700. }
  701.  
  702. #automudae-tokenlist input {
  703. width: 99%;
  704. border: none;
  705. background: none;
  706. color: var(--text-normal);
  707. }
  708.  
  709. #automudae-tokenlist li:nth-child(odd) {
  710. background-color: var(--background-accent);
  711. }
  712.  
  713. #automudae-tokenlist li:nth-child(even) {
  714. background-color: var(--background-modifier-selected);
  715. }
  716.  
  717. #automudae-tokenlist li {
  718. position: relative;
  719. }
  720. #automudae-tokenlist li:hover:not(:focus-within) input {
  721. opacity: .1;
  722. }
  723. #automudae-tokenlist li:hover:not(:focus-within)::before {
  724. content: attr(data-username);
  725. position: absolute;
  726. height: 100%;
  727. display: flex;
  728. align-items: center;
  729. margin-left: 5px;
  730. }
  731. `;
  732.  
  733. GM_addStyle(Object.values(CSS).join(' '));
  734.  
  735. //// Utils
  736. const pickRandom = (arr) => arr[arr.length * Math.random() | 0];
  737. const getLast = (arr) => arr[arr.length - 1];
  738.  
  739. //// DOM Elements
  740. const DOM = {
  741. el_ChannelList: null,
  742. el_MemberList: null,
  743. el_Chat: null,
  744. el_ChatWrapper: null,
  745. el_InjectionsWrapper: null,
  746. el_RunButton: null,
  747. el_StateSpan: null,
  748. el_ToastsWrapper: null,
  749. el_ErrorPopup: null
  750. };
  751.  
  752. //// CONSTS
  753. const INTERVAL_SEND_MESSAGE = 1500;
  754. const INTERVAL_ROLL = 2000;
  755. const INTERVAL_THINK = 200;
  756. const MUDAE_USER_ID = '432610292342587392';
  757.  
  758. //// Discord Data & Utils
  759. const Discord = {
  760. info: new Map(), /// Map<E.DISCORD_INFO, >()
  761. lastMessageTime: 0,
  762. cdSendMessage: 0,
  763. nonce: Math.floor(Math.random() * 1000000),
  764.  
  765. Message: {
  766. getDate: (el_Message) => {
  767. const messageDate = new Date(this.el_Message.querySelector("time[id^='message-timestamp']")?.dateTime);
  768.  
  769. if (messageDate.toString() === "Invalid Date") {
  770. logger.error("Couldn't retrieve timestamp for this Discord message:", el_Message);
  771. return;
  772. }
  773.  
  774. return messageDate;
  775. },
  776.  
  777. getAuthorId: (el_Message) => {
  778. let el_TargetMessage = el_Message;
  779. let el_Avatar;
  780.  
  781. while (!el_Avatar) {
  782. el_Avatar = el_TargetMessage.querySelector(`img[class^='avatar']`);
  783. if (el_Avatar) break;
  784.  
  785. el_TargetMessage = el_TargetMessage.previousElementSibling;
  786.  
  787. while (el_TargetMessage && el_TargetMessage.tagName !== "LI") {
  788. el_TargetMessage = el_TargetMessage.previousElementSibling;
  789. }
  790.  
  791. if (!el_Avatar && !el_TargetMessage) return logger.error("Couldn't get avatar for this Discord message:", el_Message);
  792. }
  793.  
  794. const match = /avatars\/(\d+)\//.exec(el_Avatar.src);
  795.  
  796. if (match) return match[1];
  797. },
  798.  
  799. getId: (el_Message) => getLast(el_Message.id.split("-")),
  800.  
  801. isFromMudae: function (el_Message) {
  802. return this.getAuthorId(el_Message) === MUDAE_USER_ID
  803. },
  804.  
  805. isFromMe: function (el_Message) {
  806. return AutoMudae.users.find(user => user.id === this.getAuthorId(el_Message), this);
  807. }
  808. }
  809.  
  810. };
  811.  
  812. /// AutoMudae
  813. class MudaeUser {
  814. id
  815. username
  816. avatar
  817. token
  818. nick
  819. info
  820. sendTUTimer
  821.  
  822. constructor(token, id, username, avatar) {
  823. this.token = token;
  824. this.info = new Map();
  825.  
  826. return new Promise(async (resolve) => {
  827. if (id){
  828. this.id = id;
  829. this.username = username;
  830. this.avatar = avatar;
  831. await this.fetchNick();
  832. return resolve(this);
  833. }
  834.  
  835. fetch("https://discord.com/api/v9/users/@me", { "headers": { "authorization": token } })
  836. .then(response => response.json())
  837. .then(async (data) => {
  838. this.id = data.id;
  839. this.username = data.username;
  840. this.avatar = data.avatar;
  841.  
  842. await this.fetchNick();
  843. })
  844. .catch(err => logger.error(`Couldn't retrieve info for some user.`, err))
  845. .finally(() => resolve(this));
  846. });
  847. }
  848.  
  849. async fetchNick(){
  850. return new Promise(resolve => {
  851. const guildId = window.location.pathname.split("/")[2];
  852.  
  853. fetch(`https://discord.com/api/v9/users/${this.id}/profile?guild_id=${guildId}`, {
  854. "headers": {
  855. "authorization": this.token
  856. }
  857. })
  858. .then(response => response.json())
  859. .then(data => {
  860. const { guild_member: { nick } } = data;
  861. this.nick = nick;
  862. })
  863. .catch(err => logger.error(`Couldn't retrieve the nick for user [${this.username}]`, err))
  864. .finally(() => resolve());
  865. });
  866. }
  867.  
  868. hasNeededInfo() {
  869. return [E.MUDAE_INFO.ROLLS_MAX, E.MUDAE_INFO.ROLLS_LEFT, E.MUDAE_INFO.POWER, E.MUDAE_INFO.CAN_RT, E.MUDAE_INFO.CAN_MARRY, E.MUDAE_INFO.CONSUMPTION].every(info => this.info.has(info), this);
  870. }
  871.  
  872. send(content) {
  873. const now = performance.now();
  874.  
  875. if (now - Discord.cdSendMessage < INTERVAL_SEND_MESSAGE) return;
  876.  
  877. fetch(`https://discord.com/api/v9/channels/${Discord.info.get(E.DISCORD_INFO.CHANNEL_ID)}/messages`, {
  878. "method": "POST",
  879. "headers": {
  880. "authorization": this.token,
  881. "content-type": "application/json"
  882. },
  883. "body": `{"content":"${content || '?'}","nonce":"${++Discord.nonce}","tts":false}`
  884. });
  885.  
  886. Discord.cdSendMessage = now;
  887. }
  888.  
  889. react(el_Message, E_EMOJI = E.EMOJI["💓"]) {
  890. fetch(`https://discord.com/api/v9/channels/${Discord.info.get(E.DISCORD_INFO.CHANNEL_ID)}/messages/${Discord.Message.getId(el_Message)}/reactions/${E_EMOJI}/%40me`, {
  891. "method": "PUT",
  892. "headers": {
  893. "authorization": this.token,
  894. }
  895. });
  896. }
  897.  
  898. setTUTimer(ms) {
  899. if (this.sendTUTimer) clearTimeout(this.sendTUTimer);
  900.  
  901. this.sendTUTimer = setTimeout((user) => { user.send("$tu") }, ms, this);
  902. }
  903.  
  904. roll() {
  905. const rollPreferences = AutoMudae.preferences.get(E.PREFERENCES.ROLL);
  906.  
  907. const command = E.SLASH_COMMANDS[rollPreferences.type];
  908.  
  909. fetch("https://discord.com/api/v9/interactions", {
  910. "method": "POST",
  911. "headers": {
  912. "authorization": this.token,
  913. "content-type": "multipart/form-data; boundary=----BDR",
  914. },
  915. "body": `------BDR\r\nContent-Disposition: form-data; name="payload_json"\r\n\r\n{"type":2,"application_id":"${MUDAE_USER_ID}","guild_id":"${Discord.info.get(E.DISCORD_INFO.GUILD_ID)}","channel_id":"${Discord.info.get(E.DISCORD_INFO.CHANNEL_ID)}","session_id":"${Discord.info.get(E.DISCORD_INFO.SESSION_ID)}","data":{"version":"${command.version}","id":"${command.id}","name":"${rollPreferences.type}","type":1},"nonce":"${++Discord.nonce}"}\r\n------BDR--\r\n`
  916. });
  917. }
  918. }
  919.  
  920. const AutoMudae = {
  921. users: [], /// MudaeUser[]
  922. preferences: null, /// Map<string, any>
  923. state: E.AUTOMUDAE_STATE.INJECT,
  924. chatObserver: new MutationObserver(ms => ms.forEach(m => { if (m.addedNodes.length) { handleNewChatAppend(m.addedNodes) } })),
  925. cdGatherInfo: 0,
  926. cdRoll: 0,
  927. lastResetHash: '',
  928.  
  929. timers: {
  930. _t: new Map(),
  931. set(identifier, callback, ms, isInterval = false) {
  932. if (this._t.has(identifier)) clearTimeout(identifier);
  933. const timer = isInterval ? setInterval(callback, ms) : setTimeout(callback, ms);
  934. this._t.set(identifier, timer);
  935. },
  936. clear() { [...this._t.values()].forEach(t => { clearTimeout(t); clearInterval(t) }); this._t.clear(); }
  937. },
  938.  
  939. toasts: {
  940. add(E_TOAST, formattableText, el_SubjectMessage = null) {
  941. if (!DOM.el_ToastsWrapper) return;
  942.  
  943. const text = formattableText.replace(/\[(.+?)\]/g, "<strong>$1</strong>");
  944.  
  945. const el_Toast = document.createElement("div");
  946. el_Toast.classList.add("automudae-toast", E_TOAST);
  947. el_Toast.innerHTML = `<span>${text}</span>`;
  948.  
  949. if (el_SubjectMessage){
  950. el_Toast.classList.add("link");
  951.  
  952. el_Toast.onclick = function(){
  953. if (this.classList.contains("missing")){
  954. this.classList.remove("missing");
  955. void this.offsetWidth;
  956. this.classList.add("missing");
  957. return;
  958. }
  959.  
  960. if (!el_SubjectMessage) return this.classList.add("missing");
  961.  
  962. const loadedMessages = [...el_SubjectMessage.parentElement.children];
  963. const messageIndex = loadedMessages.indexOf(el_SubjectMessage);
  964. const distanceFromBottom = loadedMessages.length - messageIndex;
  965. const quantityMargin = 19;
  966.  
  967. if (messageIndex >= quantityMargin && distanceFromBottom <= quantityMargin){
  968. el_SubjectMessage.scrollIntoView();
  969. return;
  970. }
  971.  
  972. this.classList.add("missing");
  973. };
  974. }
  975.  
  976. DOM.el_ToastsWrapper.appendChild(el_Toast);
  977. },
  978. clear() {
  979. if (DOM.el_ToastsWrapper) DOM.el_ToastsWrapper.innerHTML = "";
  980. }
  981. },
  982.  
  983. /// Info
  984. hasNeededInfo() {
  985. return this.users.every(user => user.hasNeededInfo());
  986. },
  987.  
  988. isLastReset() {
  989. const now = new Date(), h = now.getHours(), m = now.getMinutes();
  990. return (h % 3 == 2 && m >= 36) || (h % 3 == 0 && m < 36)
  991. },
  992.  
  993. /// Utils
  994. mudaeTimeToMs(timeString) {
  995. if (!timeString.includes("h")) return Number(timeString) * 60 * 1000;
  996.  
  997. const match = /(\d+h)?\s?(\d+)?/.exec(timeString);
  998.  
  999. if (!match) return;
  1000.  
  1001. const h = match[1];
  1002. const m = match[2];
  1003. let totalMs = 0;
  1004.  
  1005. if (h) totalMs += Number(h.replace(/\D/g, '')) * 60 * 60 * 1000;
  1006. if (m) totalMs += Number(m) * 60 * 1000;
  1007.  
  1008. return totalMs;
  1009. },
  1010.  
  1011. getMarriageableUser(preferableNicknames) {
  1012. if (!preferableNicknames || preferableNicknames.length === 0){
  1013. return this.users.find(user => user.info.get(E.MUDAE_INFO.CAN_MARRY));
  1014. }
  1015.  
  1016. let marriageableUser;
  1017.  
  1018. for (let i = 0; i < this.users.length; i++) {
  1019. const user = this.users[i];
  1020. if (user.info.get(E.MUDAE_INFO.CAN_MARRY)){
  1021. marriageableUser = user;
  1022.  
  1023. if (preferableNicknames.includes(user.nick)) break;
  1024. }
  1025. }
  1026.  
  1027. return marriageableUser;
  1028. },
  1029.  
  1030. clearError(){
  1031. if (DOM.el_ErrorPopup) DOM.el_ErrorPopup = DOM.el_ErrorPopup.remove();
  1032. },
  1033.  
  1034. error(msg) {
  1035. this.clearError();
  1036. if (!msg) return;
  1037.  
  1038. const el_ErrorPopup = document.createElement("div");
  1039. el_ErrorPopup.id = "automudae-error";
  1040. el_ErrorPopup.innerHTML = `<span>${msg}</span>`;
  1041. document.body.appendChild(el_ErrorPopup);
  1042.  
  1043. DOM.el_ErrorPopup = el_ErrorPopup;
  1044. },
  1045.  
  1046. /// Workflow
  1047. renderTokenList(){
  1048. const isTokenValid = token => token && token.length >= 70 && token.length < 80 && /\w+\.\w+\.[-\w]+$/.test(token);
  1049.  
  1050. function handleTokenInput(){
  1051. if (!isTokenValid(this.value)) this.parentElement.remove();
  1052. }
  1053.  
  1054. const el_TokenListWrapper = document.createElement("div");
  1055. el_TokenListWrapper.id = "automudae-tokenlist-wrapper";
  1056. el_TokenListWrapper.innerHTML = `<div id="automudae-tokenlist"><h3>Token List</h3><div><ul></ul><div id="automudae-tokenlist-controls"><div id="automudae-tokenlist-add">Add</div><div id="automudae-tokenlist-clear">Clear</div></div></div><div id="automudae-tokenlist-accept">Accept</div></div>`;
  1057. document.body.appendChild(el_TokenListWrapper);
  1058.  
  1059. const el_TokenList = document.querySelector("#automudae-tokenlist ul");
  1060.  
  1061. const addInputField = (defaultValue) => {
  1062. if (el_TokenList.childElementCount < 20){
  1063. const el_TokenInput = document.createElement("input");
  1064.  
  1065. el_TokenInput.onblur = handleTokenInput;
  1066.  
  1067. if (defaultValue) el_TokenInput.value = defaultValue;
  1068.  
  1069. el_TokenList.appendChild(document.createElement("li").appendChild(el_TokenInput).parentElement);
  1070. }
  1071. };
  1072.  
  1073. document.getElementById("automudae-tokenlist-clear").onclick = () => el_TokenList.innerHTML = "";
  1074.  
  1075. document.getElementById("automudae-tokenlist-add").onclick = () => addInputField();
  1076.  
  1077. document.getElementById("automudae-tokenlist-accept").onclick = () => {
  1078. const tokenSet = new Set();
  1079.  
  1080. document.querySelectorAll("#automudae-tokenlist input").forEach(el_Input => {
  1081. const token = el_Input.value;
  1082. if (isTokenValid(token)) tokenSet.add(token);
  1083. });
  1084.  
  1085. if (tokenSet.size === 0){
  1086. AutoMudae.error("Please provide a valid token.");
  1087. return;
  1088. }
  1089.  
  1090. const tokenList = [...tokenSet];
  1091.  
  1092. GM_setValue(E.GMVALUE.TOKENLIST, tokenList.join(";"));
  1093.  
  1094. el_TokenListWrapper.remove();
  1095. AutoMudae.inject(tokenList);
  1096. };
  1097.  
  1098. GM_getValue(E.GMVALUE.TOKENLIST)?.split(";").forEach(token => addInputField(token));
  1099. },
  1100.  
  1101. toggleInjectionButtons(){
  1102. if (DOM.el_InjectionsWrapper){
  1103. DOM.el_InjectionsWrapper.classList.toggle("automudae-hide");
  1104. return;
  1105. }
  1106.  
  1107. const el_LoggedUsersButton = document.createElement("div");
  1108. el_LoggedUsersButton.id = "automudae-use-logged-button";
  1109. el_LoggedUsersButton.innerHTML = "<span>Use Logged Users</span>";
  1110.  
  1111. const el_TokenListButton = document.createElement("div");
  1112. el_TokenListButton.id = "automudae-use-tokenlist-button";
  1113. el_TokenListButton.innerHTML = "<span>Use Token List</span>";
  1114.  
  1115. el_LoggedUsersButton.onclick = (_e) => AutoMudae.inject(false);
  1116. el_TokenListButton.onclick = (_e) => AutoMudae.renderTokenList();
  1117.  
  1118. const el_InjectionsWrapper = document.createElement("div");
  1119. el_InjectionsWrapper.id = "automudae-injections-wrapper";
  1120.  
  1121. el_InjectionsWrapper.appendChild(el_LoggedUsersButton);
  1122. el_InjectionsWrapper.appendChild(el_TokenListButton);
  1123.  
  1124. DOM.el_InjectionsWrapper = el_InjectionsWrapper;
  1125.  
  1126. document.body.appendChild(el_InjectionsWrapper);
  1127. },
  1128.  
  1129. preRender() {
  1130. const el_DiscordToolBar = document.querySelector("[class^='toolbar']");
  1131. el_DiscordToolBar.innerHTML = "";
  1132.  
  1133. /// Run Button
  1134. const el_RunButton = document.createElement("div");
  1135. el_RunButton.id = "automudae-run-button";
  1136. el_RunButton.classList.add("automudae-hide");
  1137.  
  1138. el_DiscordToolBar.appendChild(el_RunButton);
  1139.  
  1140. DOM.el_RunButton = el_RunButton;
  1141.  
  1142. /// State Text
  1143. const el_StateWrapper = document.createElement("div");
  1144. el_StateWrapper.id = "automudae-state";
  1145. el_StateWrapper.innerHTML = "<b>AutoMudae:</b>";
  1146.  
  1147. const el_StateSpan = document.createElement("span");
  1148. el_StateSpan.appendChild(document.createTextNode("Idle"));
  1149.  
  1150. el_StateWrapper.appendChild(el_StateSpan);
  1151. el_DiscordToolBar.appendChild(el_StateWrapper);
  1152.  
  1153. DOM.el_StateSpan = el_StateSpan;
  1154.  
  1155. /// Injection Buttons
  1156. this.toggleInjectionButtons();
  1157. },
  1158.  
  1159. inject(tokenList) {
  1160. this.toggleInjectionButtons();
  1161.  
  1162. logger.info("Injecting...");
  1163.  
  1164. this.setState(E.AUTOMUDAE_STATE.SETUP);
  1165.  
  1166. AutoMudae.setup(tokenList)
  1167. .then(() => {
  1168. this.clearError();
  1169. const requirements = "Required:\n- All your accounts should have custom avatars\n- Arrange your $TU to expose all needed information: $ta claim rolls daily keys kakerareact kakerapower kakerainfo kakerastock rt dk rollsreset\n- Set your claim feedback to default: $rc none\n- Set your rolls left message to default: $rollsleft 0\nCan only roll with slash commands.\nDon't search for messages in Discord.\n- Don't scroll up the channel.";
  1170. const recommendations = "Recommended:\n- Use slash rolls.\n- Don't use non-slash rolls while the channel is in peak usage by other members.\n- Set your user order priorizing roll and kakera claiming.";
  1171. const exposeLogger = this.preferences.get(E.PREFERENCES.EXTRA).logger;
  1172. if (exposeLogger) {
  1173. const doNothing = () => { };
  1174. for (const method in logger) {
  1175. if (!Object.hasOwn(logger, method)) continue;
  1176. window.console[method] = doNothing;
  1177. }
  1178. console.clear();
  1179. window.logger = logger;
  1180. logger.debug("Turned off native console. Use logger instead. I recommend disabling network log, since Discord usualy prompt a lot of these.");
  1181. logger.debug(requirements);
  1182. logger.debug(recommendations);
  1183. logger._reprompt();
  1184. }
  1185. this.render();
  1186. this.tryEnable();
  1187. if (!exposeLogger) {
  1188. logger.info(requirements);
  1189. logger.info(recommendations);
  1190. }
  1191. })
  1192. .catch(err => {
  1193. logger.error(err);
  1194. this.error(err);
  1195. this.setState(E.AUTOMUDAE_STATE.INJECT);
  1196. this.toggleInjectionButtons();
  1197. });
  1198. },
  1199.  
  1200. async setup(tokenList){
  1201. return new Promise(async (resolve, reject) => {
  1202. const windowPathname = window.location?.pathname;
  1203. if (!windowPathname) {
  1204. reject("Couldn't retrieve current window URL.");
  1205. }
  1206. const [_, pathDiscriminator, guildId, channelId] = windowPathname.split("/");
  1207. if (pathDiscriminator !== "channels") {
  1208. reject("You must be viewing the desired channel.");
  1209. }
  1210. if (!guildId || !channelId) {
  1211. reject("Couldn't retrieve active guild or channel.");
  1212. }
  1213. DOM.el_ChannelList = document.querySelector("#channels > ul");
  1214. DOM.el_MemberList = document.querySelector("div[class^='members'] > div");
  1215. DOM.el_Chat = document.querySelector("ol[class^='scrollerInner']");
  1216. DOM.el_ChatWrapper = document.querySelector("main[class^='chatContent']");
  1217. if (!DOM.el_Chat || !DOM.el_MemberList || !DOM.el_ChannelList || !DOM.el_ChatWrapper) {
  1218. reject("Make sure you're viewing the desired channel and the page is fully loaded.");
  1219. }
  1220. if (!localStorage || !localStorage.MultiAccountStore || !localStorage.tokens) {
  1221. reject("Couldn't retrieve information from Discord.");
  1222. }
  1223. const users = [];
  1224. if (tokenList){
  1225. for (let i = 0; i < tokenList.length; i++) {
  1226. users.push(await new MudaeUser(tokenList[i]));
  1227. }
  1228. } else {
  1229. const storeUsers = JSON.parse(localStorage.MultiAccountStore)?._state.users;
  1230. const tokens = JSON.parse(localStorage.tokens);
  1231. if (!storeUsers || !tokens) {
  1232. return "Couldn't retrieve information about your accounts.";
  1233. }
  1234. for (let i = 0; i < storeUsers.length; i++) {
  1235. const { id, username, avatar } = storeUsers[i];
  1236. const token = tokens[id];
  1237. if (!token) {
  1238. return `Couldn't retrieve information about user [${username}]`;
  1239. }
  1240. users.push(await new MudaeUser(token, id, username, avatar));
  1241. }
  1242. }
  1243. this.users = users;
  1244. Discord.info.set(E.DISCORD_INFO.CHANNEL_ID, channelId);
  1245. Discord.info.set(E.DISCORD_INFO.GUILD_ID, guildId);
  1246. const defaultPreferences = `[
  1247. ["${E.PREFERENCES.KAKERA}", {"kakeraP": false, "kakera": false, "kakeraT": false, "kakeraG": false, "kakeraY": false, "kakeraO": false, "kakeraR": false, "kakeraW": false, "kakeraL": false}],
  1248. ["${E.PREFERENCES.MENTIONS}", ""],
  1249. ["${E.PREFERENCES.ROLL}", {"enabled":true,"type":"wx"}],
  1250. ["${E.PREFERENCES.SOUND}", {"foundcharacter":true,"marry":true,"cantmarry":true, "lastresetnorolls":true,"soulmate":true,"wishsteal":true}],
  1251. ["${E.PREFERENCES.EXTRA}", {"logger":true}]
  1252. ]`;
  1253. const savedVersion = GM_getValue(E.GMVALUE.VERSION, null);
  1254. const isPreferencesOutdated = !savedVersion || savedVersion !== GM_info.script.version;
  1255. const stringifiedPreferences = isPreferencesOutdated ? defaultPreferences : GM_getValue(E.GMVALUE.PREFERENCES, defaultPreferences);
  1256. this.preferences = new Map(JSON.parse(stringifiedPreferences));
  1257. GM_setValue(E.GMVALUE.VERSION, GM_info.script.version);
  1258.  
  1259. resolve();
  1260. });
  1261. },
  1262.  
  1263. render() {
  1264. logger.info("Rendering...");
  1265. const el_InfoPanel = document.createElement("div");
  1266. el_InfoPanel.id = "automudae-panel-info";
  1267. el_InfoPanel.innerHTML = `
  1268. <h1>Auto-Mudae Info</h1>
  1269. <div>
  1270. <div class="automudae-section">
  1271. <h2>Collected</h2>
  1272. <div class="automudae-section-body">
  1273. <div class="automudae-row">
  1274. <span>Kakera:</span>
  1275. <div><img class="emoji" src="https://cdn.discordapp.com/emojis/469835869059153940.webp?quality=lossless"><span id="automudae-field-${E.INFO_FIELD.KAKERA}">0</span></div>
  1276. </div>
  1277. <div class="automudae-row">
  1278. <span>Characters:</span>
  1279. </div>
  1280. <ul id="automudae-field-${E.INFO_FIELD.COLLECTED_CHARACTERS}"></ul>
  1281. </div>
  1282. </div>
  1283. <div class="automudae-section" id="automudae-section-status">
  1284. <h2>Status</h2>
  1285. <div class="automudae-section-body">
  1286. <div class="automudae-row-expandable">
  1287. <div class="automudae-row">
  1288. <span>Rolls:</span>
  1289. <div><span>(</span><span id="automudae-field-${E.INFO_FIELD.ROLLS_LEFT}">?</span><span>/</span><span id="automudae-field-${E.INFO_FIELD.ROLLS_MAX}">?</span><span>)</span></div>
  1290. </div>
  1291. <div>
  1292. ${this.users.map(user => `<div class="automudae-row"><span>${user.username}:</span><div><span>(</span><span id="automudae-field-${E.INFO_FIELD.ROLLS_LEFT}-${user.id}">?</span><span>/</span><span id="automudae-field-${E.INFO_FIELD.ROLLS_MAX}-${user.id}">?</span><span>)</span></div></div>`).join("")}
  1293. </div>
  1294. </div>
  1295. <div class="automudae-row-expandable">
  1296. <div class="automudae-row">
  1297. <span>Power:</span>
  1298. <div><span id="automudae-field-${E.INFO_FIELD.POWER}">?</span><span>%</span></div>
  1299. </div>
  1300. <div>
  1301. ${this.users.map(user => `<div class="automudae-row"><span>${user.username}:</span><div><div><span id="automudae-field-${E.INFO_FIELD.POWER}-${user.id}">?</span><span>%</span></div></div></div>`).join("")}
  1302. </div>
  1303. </div>
  1304. <div class="automudae-row-expandable">
  1305. <div class="automudae-row">
  1306. <span>Kakera Power Consumption:</span>
  1307. <div><span id="automudae-field-${E.INFO_FIELD.POWER_CONSUMPTION}">?</span><span>%</span></div>
  1308. </div>
  1309. <div>
  1310. ${this.users.map(user => `<div class="automudae-row"><span>${user.username}:</span><div><div><span id="automudae-field-${E.INFO_FIELD.POWER_CONSUMPTION}-${user.id}">?</span><span>%</span></div></div></div>`).join("")}
  1311. </div>
  1312. </div>
  1313. <div class="automudae-row-expandable">
  1314. <div class="automudae-row">
  1315. <span>Can Marry?</span>
  1316. <span id="automudae-field-${E.INFO_FIELD.CAN_MARRY}">?</span>
  1317. </div>
  1318. <div>
  1319. ${this.users.map(user => `<div class="automudae-row"><span>${user.username}:</span><div><span id="automudae-field-${E.INFO_FIELD.CAN_MARRY}-${user.id}">?</span></div></div>`).join("")}
  1320. </div>
  1321. </div>
  1322. <div class="automudae-row-expandable">
  1323. <div class="automudae-row">
  1324. <span>Can RT?</span>
  1325. <span id="automudae-field-${E.INFO_FIELD.CAN_RT}">?</span>
  1326. </div>
  1327. <div>
  1328. ${this.users.map(user => `<div class="automudae-row"><span>${user.username}:</span><div><span id="automudae-field-${E.INFO_FIELD.CAN_RT}-${user.id}">?</span></div></div>`).join("")}
  1329. </div>
  1330. </div>
  1331. </div>
  1332. </div>
  1333. </div>
  1334. `;
  1335.  
  1336. const el_ConfigPanel = document.createElement("div");
  1337. el_ConfigPanel.id = "automudae-panel-config";
  1338. el_ConfigPanel.innerHTML = `
  1339. <h1>Auto-Mudae Config</h1>
  1340. <div>
  1341. <div class="automudae-section" id="automudae-section-kakera">
  1342. <h2>Kakera to Collect</h2>
  1343. <div class="automudae-section-body">
  1344. <div><input type="checkbox" id="opt-kakera-kakeraP"><label for="opt-kakera-kakeraP"><img class="emoji" src="https://cdn.discordapp.com/emojis/609264156347990016.webp?quality=lossless"></label></div>
  1345. <div><input type="checkbox" id="opt-kakera-kakera"><label for="opt-kakera-kakera"><img class="emoji" src="https://cdn.discordapp.com/emojis/469835869059153940.webp?quality=lossless"></label></div>
  1346. <div><input type="checkbox" id="opt-kakera-kakeraT"><label for="opt-kakera-kakeraT"><img class="emoji" src="https://cdn.discordapp.com/emojis/609264180851376132.webp?quality=lossless"></label></div>
  1347. <div><input type="checkbox" id="opt-kakera-kakeraG"><label for="opt-kakera-kakeraG"><img class="emoji" src="https://cdn.discordapp.com/emojis/609264166381027329.webp?quality=lossless"></label></div>
  1348. <div><input type="checkbox" id="opt-kakera-kakeraY"><label for="opt-kakera-kakeraY"><img class="emoji" src="https://cdn.discordapp.com/emojis/605112931168026629.webp?quality=lossless"></label></div>
  1349. <div><input type="checkbox" id="opt-kakera-kakeraO"><label for="opt-kakera-kakeraO"><img class="emoji" src="https://cdn.discordapp.com/emojis/605112954391887888.webp?quality=lossless"></label></div>
  1350. <div><input type="checkbox" id="opt-kakera-kakeraR"><label for="opt-kakera-kakeraR"><img class="emoji" src="https://cdn.discordapp.com/emojis/605112980295647242.webp?quality=lossless"></label></div>
  1351. <div><input type="checkbox" id="opt-kakera-kakeraW"><label for="opt-kakera-kakeraW"><img class="emoji" src="https://cdn.discordapp.com/emojis/608192076286263297.webp?quality=lossless"></label></div>
  1352. <div><input type="checkbox" id="opt-kakera-kakeraL"><label for="opt-kakera-kakeraL"><img class="emoji" src="https://cdn.discordapp.com/emojis/815961697918779422.webp?quality=lossless"></label></div>
  1353. </div>
  1354. </div>
  1355. <div class="automudae-section">
  1356. <h2>Interesting Mentions</h2>
  1357. <div class="automudae-section-body">
  1358. <textarea spellcheck="false" id="opt-mentions"></textarea>
  1359. </div>
  1360. </div>
  1361. <div class="automudae-section">
  1362. <h2>Roll</h2>
  1363. <div class="automudae-section-body">
  1364. <div>
  1365. <input type="checkbox" id="opt-roll-enabled"><label for="opt-roll-enabled"><span>Enabled</span></label>
  1366. </div>
  1367. <div>
  1368. <select id="opt-roll-type">
  1369. <option value="wx">wx</option>
  1370. <option value="wa">wa</option>
  1371. <option value="wg">wg</option>
  1372. <option value="hx">hx</option>
  1373. <option value="ha">ha</option>
  1374. <option value="hg">hg</option>
  1375. </select>
  1376. </div>
  1377. </div>
  1378. </div>
  1379. <div class="automudae-section">
  1380. <h2>Sound</h2>
  1381. <div class="automudae-section-body">
  1382. <div>
  1383. <input type="checkbox" id="opt-sound-foundcharacter"><label for="opt-sound-foundcharacter"><span>Found character</span></label>
  1384. </div>
  1385. <div>
  1386. <input type="checkbox" id="opt-sound-marry"><label for="opt-sound-marry"><span>Marry</span></label>
  1387. </div>
  1388. <div>
  1389. <input type="checkbox" id="opt-sound-cantmarry"><label for="opt-sound-cantmarry"><span>Can't marry</span></label>
  1390. </div>
  1391. <div>
  1392. <input type="checkbox" id="opt-sound-lastresetnorolls"><label for="opt-sound-lastresetnorolls"><span>Can't roll in the last reset</span></label>
  1393. </div>
  1394. <div>
  1395. <input type="checkbox" id="opt-sound-soulmate"><label for="opt-sound-soulmate"><span>New soulmates</span></label>
  1396. </div>
  1397. <div>
  1398. <input type="checkbox" id="opt-sound-wishsteal"><label for="opt-sound-wishsteal"><span>Wish steals</span></label>
  1399. </div>
  1400. </div>
  1401. </div>
  1402. <div class="automudae-section">
  1403. <h2>Extra</h2>
  1404. <div class="automudae-section-body">
  1405. <div data-requirerestart>
  1406. <input type="checkbox" id="opt-extra-logger"><label for="opt-extra-logger"><span>Replace Console with Logger</span></label>
  1407. </div>
  1408. </div>
  1409. </div>
  1410. </div>
  1411. `;
  1412.  
  1413. const el_ToastsWrapper = document.createElement("div");
  1414. el_ToastsWrapper.id = "automudae-toasts-wrapper";
  1415.  
  1416. DOM.el_ChannelList.prepend(el_InfoPanel);
  1417. DOM.el_MemberList.prepend(el_ConfigPanel);
  1418. DOM.el_ChatWrapper.prepend(el_ToastsWrapper);
  1419.  
  1420. DOM.el_ToastsWrapper = el_ToastsWrapper;
  1421.  
  1422. document.querySelector("[class^='channelTextArea']").style.width = "60%";
  1423.  
  1424. /// Make side panels collapsable
  1425. function collapse() { this.parentElement.classList.toggle("collapsed") };
  1426.  
  1427. document.querySelectorAll("[id^='automudae-panel'] > h1").forEach(el_Header => el_Header.onclick = collapse);
  1428.  
  1429. /// Config Update & Functionality
  1430. function handleCheckboxPreference() {
  1431. const [_, category, key] = this.id.split("-");
  1432. const categoryPreferences = AutoMudae.preferences.get(category);
  1433.  
  1434. categoryPreferences[key] = this.checked;
  1435.  
  1436. AutoMudae.preferences.set(category, categoryPreferences);
  1437. AutoMudae.savePreferences();
  1438. };
  1439.  
  1440. document.querySelectorAll("input[type='checkbox'][id^='opt-']").forEach(el_OptCheckbox => {
  1441. const [_, category, key] = el_OptCheckbox.id.split("-");
  1442.  
  1443. el_OptCheckbox.checked = AutoMudae.preferences.get(category)[key];
  1444. el_OptCheckbox.onchange = handleCheckboxPreference;
  1445. });
  1446.  
  1447. const el_OptMentions = document.getElementById("opt-mentions");
  1448.  
  1449. el_OptMentions.value = this.preferences.get(E.PREFERENCES.MENTIONS);
  1450.  
  1451. el_OptMentions.onblur = function () {
  1452. AutoMudae.preferences.set(E.PREFERENCES.MENTIONS, this.value);
  1453. AutoMudae.savePreferences();
  1454. };
  1455.  
  1456. const el_OptRollType = document.getElementById("opt-roll-type");
  1457.  
  1458. el_OptRollType.value = this.preferences.get(E.PREFERENCES.ROLL).type;
  1459.  
  1460. el_OptRollType.onchange = function () {
  1461. const rollPreferences = AutoMudae.preferences.get(E.PREFERENCES.ROLL);
  1462.  
  1463. rollPreferences.type = this.value;
  1464.  
  1465. AutoMudae.preferences.set(E.PREFERENCES.ROLL, rollPreferences);
  1466. AutoMudae.savePreferences();
  1467. };
  1468. },
  1469.  
  1470. tryEnable() {
  1471. if (this.state !== E.AUTOMUDAE_STATE.SETUP) return;
  1472. if (!Object.values(E.DISCORD_INFO).every(info => Discord.info.has(info))) return;
  1473.  
  1474. this.setState(E.AUTOMUDAE_STATE.IDLE);
  1475. DOM.el_RunButton.onclick = (_e) => AutoMudae.toggle();
  1476. DOM.el_RunButton.classList.remove("automudae-hide");
  1477. logger.plus("Ready to go!");
  1478. },
  1479.  
  1480. toggle() {
  1481. if (this.state !== E.AUTOMUDAE_STATE.IDLE && this.state !== E.AUTOMUDAE_STATE.RUN) return;
  1482.  
  1483. if (this.state === E.AUTOMUDAE_STATE.IDLE) {
  1484. this.clearError();
  1485.  
  1486. let msToStartResetHandler = 1;
  1487. const now = new Date();
  1488.  
  1489. if (now.getMinutes() !== 37) {
  1490. const nextReset = new Date(now);
  1491. nextReset.setHours(now.getMinutes() > 37 ? now.getHours() + 1 : now.getHours(), 37);
  1492. msToStartResetHandler = nextReset - now;
  1493. }
  1494.  
  1495. this.timers.set("think", this.think, INTERVAL_THINK, true);
  1496. this.timers.set("initHourlyResetHandler", () => { AutoMudae.handleHourlyReset(); AutoMudae.timers.set("HandleHourlyReset", AutoMudae.handleHourlyReset, 1 * 60 * 60 * 1000, true) }, msToStartResetHandler);
  1497. this.chatObserver.observe(DOM.el_Chat, { childList: true });
  1498. this.setState(E.AUTOMUDAE_STATE.RUN);
  1499. logger.log("Running..");
  1500. return;
  1501. }
  1502.  
  1503. this.chatObserver.disconnect();
  1504. this.timers.clear();
  1505. this.users.forEach(user => {
  1506. if (user.sendTUTimer) clearTimeout(user.sendTUTimer);
  1507. user.info.clear();
  1508. });
  1509. this.setState(E.AUTOMUDAE_STATE.IDLE);
  1510. logger.log("Turned off.");
  1511. },
  1512.  
  1513. setState(E_STATE) {
  1514. this.state = E_STATE;
  1515.  
  1516. const stateTexts = {};
  1517. stateTexts[E.AUTOMUDAE_STATE.INJECT] = "Idle";
  1518. stateTexts[E.AUTOMUDAE_STATE.SETUP] = "Setting up...";
  1519. stateTexts[E.AUTOMUDAE_STATE.ERROR] = "Error!";
  1520. stateTexts[E.AUTOMUDAE_STATE.IDLE] = "Idle";
  1521. stateTexts[E.AUTOMUDAE_STATE.RUN] = "Running...";
  1522.  
  1523. DOM.el_StateSpan.innerText = stateTexts[E_STATE];
  1524.  
  1525. if ((E_STATE === E.AUTOMUDAE_STATE.RUN || E_STATE === E.AUTOMUDAE_STATE.IDLE) && DOM.el_RunButton){
  1526. const isRun = E_STATE === E.AUTOMUDAE_STATE.RUN;
  1527. DOM.el_RunButton.classList[isRun ? "add" : "remove"]("running");
  1528. }
  1529. },
  1530.  
  1531. think() {
  1532. const now = performance.now();
  1533. const dateNow = new Date(), h = dateNow.getHours(), m = dateNow.getMinutes();
  1534.  
  1535. if (!AutoMudae.hasNeededInfo()) {
  1536. if (now - AutoMudae.cdGatherInfo < 1000) return;
  1537.  
  1538. for (let i = 0; i < AutoMudae.users.length; i++) {
  1539. const user = AutoMudae.users[i];
  1540.  
  1541. if (!user.hasNeededInfo()) {
  1542. logger.log(`Gathering needed info for user [${user.username}]..`);
  1543.  
  1544. user.send("$tu");
  1545.  
  1546. break;
  1547. }
  1548.  
  1549. }
  1550.  
  1551. AutoMudae.cdGatherInfo = now;
  1552. return;
  1553. }
  1554.  
  1555. const userWithRolls = AutoMudae.users.find(user => user.info.get(E.MUDAE_INFO.ROLLS_LEFT) > 0);
  1556.  
  1557. if (AutoMudae.preferences.get(E.PREFERENCES.ROLL).enabled) {
  1558. if (userWithRolls && now - Discord.lastMessageTime > INTERVAL_ROLL && now - AutoMudae.cdRoll > (INTERVAL_ROLL * .5)) {
  1559. userWithRolls.roll();
  1560. AutoMudae.cdRoll = now;
  1561. }
  1562. }
  1563.  
  1564. if (!userWithRolls && m > 38 && AutoMudae.isLastReset() && AutoMudae.getMarriageableUser()) {
  1565. const currentResetHash = `${dateNow.toDateString()} ${h}`;
  1566.  
  1567. if (AutoMudae.lastResetHash !== currentResetHash) {
  1568. AutoMudae.lastResetHash = currentResetHash;
  1569.  
  1570. //# Add option to auto-use $us or $rolls
  1571.  
  1572. const warnMessage = "You have no more rolls, can still marry and it's the last reset. You could use $us or $rolls, then $tu.";
  1573.  
  1574. logger.warn(warnMessage);
  1575. AutoMudae.toasts.add(E.TOAST.WARN, warnMessage);
  1576. if (AutoMudae.preferences.get(E.PREFERENCES.SOUND).lastresetnorolls) SOUND.lastResetNoRolls();
  1577. }
  1578. }
  1579.  
  1580. },
  1581.  
  1582. savePreferences() {
  1583. GM_setValue(E.GMVALUE.PREFERENCES, JSON.stringify(this.preferences));
  1584. },
  1585.  
  1586. updateInfoPanel(E_INFO_FIELD, content, user) {
  1587. const el_OverallField = document.getElementById(`automudae-field-${E_INFO_FIELD}`);
  1588.  
  1589. if (E_INFO_FIELD === E.INFO_FIELD.KAKERA) {
  1590. const newKakera = Number(el_OverallField.innerText) + Number(content);
  1591.  
  1592. el_OverallField.innerText = newKakera;
  1593. return;
  1594. }
  1595.  
  1596. if (E_INFO_FIELD === E.INFO_FIELD.COLLECTED_CHARACTERS) {
  1597. const el_CharacterItem = document.createElement("li");
  1598. el_CharacterItem.appendChild(document.createTextNode((user ? `[${user.username}] ` : '') + content));
  1599. el_OverallField.appendChild(el_CharacterItem);
  1600.  
  1601. return;
  1602. }
  1603.  
  1604. const el_UserField = document.getElementById(`automudae-field-${E_INFO_FIELD}-${user.id}`);
  1605.  
  1606. el_UserField.innerText = content;
  1607.  
  1608. if (E_INFO_FIELD === E.INFO_FIELD.ROLLS_LEFT || E_INFO_FIELD === E.INFO_FIELD.ROLLS_MAX) {
  1609. const numeralFields = [...document.querySelectorAll(`[id^='automudae-field-${E_INFO_FIELD}-']`)].map(el_UserField => el_UserField.innerText).filter(text => /\d+/.test(text));
  1610.  
  1611. if (numeralFields.length > 0) el_OverallField.innerText = numeralFields.reduce((total, current) => Number(total) + Number(current));
  1612.  
  1613. return;
  1614. }
  1615.  
  1616. if (E_INFO_FIELD === E.INFO_FIELD.POWER) {
  1617. const highestPower = getLast([...document.querySelectorAll(`[id^='automudae-field-${E_INFO_FIELD}-']`)].map(el_UserField => el_UserField.innerText).filter(text => /\d+/.test(text)).sort((a, b) => Number(a) - Number(b)));
  1618.  
  1619. if (highestPower) el_OverallField.innerText = `↓ ${highestPower}`;
  1620.  
  1621. return;
  1622. }
  1623.  
  1624. if (E_INFO_FIELD === E.INFO_FIELD.POWER_CONSUMPTION) {
  1625. const lowestConsumption = [...document.querySelectorAll(`[id^='automudae-field-${E_INFO_FIELD}-']`)].map(el_UserField => el_UserField.innerText).filter(text => /\d+/.test(text)).sort((a, b) => Number(a) - Number(b))[0];
  1626.  
  1627. if (lowestConsumption) el_OverallField.innerText = `↑ ${lowestConsumption}`;
  1628.  
  1629. return;
  1630. }
  1631.  
  1632. if (E_INFO_FIELD === E.INFO_FIELD.CAN_MARRY || E_INFO_FIELD === E.INFO_FIELD.CAN_RT) {
  1633. const hasAny = [...document.querySelectorAll(`[id^='automudae-field-${E_INFO_FIELD}-']`)].some(el_UserField => el_UserField.innerText === "Yes");
  1634.  
  1635. el_OverallField.innerText = hasAny ? "Yes" : "No";
  1636.  
  1637. return;
  1638. }
  1639. },
  1640.  
  1641. handleHourlyReset() {
  1642. if (!AutoMudae.hasNeededInfo()) return;
  1643.  
  1644. logger.log("Hourly reset. Gathering updated status..");
  1645.  
  1646. AutoMudae.users.forEach(user => user.info.delete(E.MUDAE_INFO.ROLLS_LEFT));
  1647. }
  1648. };
  1649.  
  1650. //# Remove this exposure
  1651. window.Discord = Discord;
  1652. window.AutoMudae = AutoMudae;
  1653.  
  1654. function observeToReact(el_Message, userToReact) {
  1655. let runs = 0;
  1656.  
  1657. const observer = setInterval(() => {
  1658. if (!el_Message || runs++ >= 30) return clearInterval(observer);
  1659.  
  1660. const el_ReactionImg = el_Message.querySelector(`div[class^='reactionInner']${userToReact ? "" : "[aria-label^='kakera']"}[aria-label*='1 rea'] img`);
  1661.  
  1662. if (!el_ReactionImg) return;
  1663.  
  1664. clearInterval(observer);
  1665.  
  1666. if (userToReact) {
  1667. const emoji = E.EMOJI[el_ReactionImg.alt];
  1668.  
  1669. if (!emoji) {
  1670. const errMessage = `Couldn't find emoji code for [${el_ReactionImg.alt}]. Address this to AutoMudae's creator, please.`;
  1671. logger.error(errMessage);
  1672. AutoMudae.toasts.add(E.TOAST.CRITICAL, errMessage, el_Message);
  1673. return;
  1674. }
  1675.  
  1676. userToReact.react(el_Message, emoji);
  1677. return;
  1678. }
  1679.  
  1680. const kakeraCode = el_ReactionImg.alt;
  1681.  
  1682. if (!AutoMudae.preferences.get(E.PREFERENCES.KAKERA)[kakeraCode]) return;
  1683.  
  1684. const userWithEnoughPower = kakeraCode === E.KAKERA.PURPLE
  1685. ? AutoMudae.users[0]
  1686. : AutoMudae.users.find(user => user.info.get(E.MUDAE_INFO.POWER) >= user.info.get(E.MUDAE_INFO.CONSUMPTION));
  1687.  
  1688. if (userWithEnoughPower) userWithEnoughPower.react(el_Message, E.EMOJI_KAKERA[kakeraCode]);
  1689. }, 100);
  1690. };
  1691.  
  1692. function handleNewChatAppend(el_Children) {
  1693. document.querySelector("div[class^='scrollerSpacer']")?.scrollIntoView();
  1694.  
  1695. el_Children.forEach(el_Child => {
  1696. if (el_Child.tagName !== "LI") return;
  1697. Discord.lastMessageTime = performance.now();
  1698.  
  1699. const el_Message = el_Child;
  1700.  
  1701. if (!Discord.Message.isFromMudae(el_Message)) return;
  1702.  
  1703. const el_PreviousElement = el_Message.previousElementSibling
  1704. ? (el_Message.previousElementSibling.id === "---new-messages-bar" ? el_Message.previousElementSibling.previousElementSibling : el_Message.previousElementSibling)
  1705. : null;
  1706.  
  1707. /// Handle player commands
  1708. if (el_PreviousElement) {
  1709. const el_PreviousMessage = el_PreviousElement;
  1710.  
  1711. const user = Discord.Message.isFromMe(el_PreviousMessage);
  1712.  
  1713. if (user) {
  1714. const command = el_PreviousMessage.querySelector("div[id^='message-content']")?.innerText;
  1715. const mudaeResponse = el_Message.querySelector("div[id^='message-content']")?.innerText;
  1716.  
  1717. if (command && mudaeResponse && mudaeResponse.startsWith(`${user.username}, `)) {
  1718. if (command === "$tu") {
  1719. const matchRolls = /tem (\d+) rolls/.exec(mudaeResponse);
  1720. if (matchRolls) {
  1721. const rolls = Number(matchRolls[1]);
  1722.  
  1723. const hasRollsMax = user.info.has(E.MUDAE_INFO.ROLLS_MAX);
  1724.  
  1725. if (!hasRollsMax || user.info.get(E.MUDAE_INFO.ROLLS_MAX) < rolls) {
  1726. user.info.set(E.MUDAE_INFO.ROLLS_MAX, rolls);
  1727.  
  1728. AutoMudae.updateInfoPanel(E.INFO_FIELD.ROLLS_MAX, rolls, user);
  1729. }
  1730.  
  1731. user.info.set(E.MUDAE_INFO.ROLLS_LEFT, rolls);
  1732. AutoMudae.updateInfoPanel(E.INFO_FIELD.ROLLS_LEFT, rolls, user);
  1733. }
  1734.  
  1735. const matchPower = /Power: (\d+)%/.exec(mudaeResponse);
  1736. if (matchPower) {
  1737. const power = Number(matchPower[1]);
  1738.  
  1739. user.info.set(E.MUDAE_INFO.POWER, power);
  1740. AutoMudae.updateInfoPanel(E.INFO_FIELD.POWER, power, user);
  1741. }
  1742.  
  1743. if (/\$rt/.test(mudaeResponse)) {
  1744. const cooldownRTMatch = /: (.+) min. \(\$rtu\)/.exec(mudaeResponse);
  1745.  
  1746. user.info.set(E.MUDAE_INFO.CAN_RT, !cooldownRTMatch);
  1747.  
  1748. if (cooldownRTMatch) {
  1749. logger.log(`Scheduled a RT check for user [${user.username}]. [${cooldownRTMatch[1]}]`);
  1750.  
  1751. user.setTUTimer(AutoMudae.mudaeTimeToMs(cooldownRTMatch[1]) + 500);
  1752. }
  1753.  
  1754. const canRT = user.info.get(E.MUDAE_INFO.CAN_RT);
  1755. AutoMudae.updateInfoPanel(E.INFO_FIELD.CAN_RT, canRT ? "Yes" : "No", user);
  1756. } else {
  1757. user.info.set(E.MUDAE_INFO.CAN_RT, false);
  1758. AutoMudae.updateInfoPanel(E.INFO_FIELD.CAN_RT, "No", user);
  1759. }
  1760.  
  1761. if (/casar/.test(mudaeResponse)) {
  1762. const cantMarry = /se casar novamente (.+) min/.exec(mudaeResponse);
  1763.  
  1764. user.info.set(E.MUDAE_INFO.CAN_MARRY, !cantMarry);
  1765.  
  1766. AutoMudae.updateInfoPanel(E.INFO_FIELD.CAN_MARRY, cantMarry ? "No" : "Yes", user);
  1767. }
  1768.  
  1769. const matchKakeraConsumption = /kakera consume (\d+)%/.exec(mudaeResponse);
  1770. if (matchKakeraConsumption) {
  1771. const consumption = Number(matchKakeraConsumption[1]);
  1772.  
  1773. user.info.set(E.MUDAE_INFO.CONSUMPTION, consumption);
  1774.  
  1775. AutoMudae.updateInfoPanel(E.INFO_FIELD.POWER_CONSUMPTION, consumption, user);
  1776. }
  1777.  
  1778. if (!user.hasNeededInfo()) {
  1779. AutoMudae.toggle();
  1780.  
  1781. const errMsg = `Couldn't retrieve needed info for user [${user.username}]. Make sure your $tu configuration exposes every information.`;
  1782. logger.error(errMsg);
  1783. AutoMudae.error(errMsg);
  1784. return;
  1785. }
  1786.  
  1787. logger.info(`Got all needed info for user [${user.username}].`);
  1788. return;
  1789. };
  1790. }
  1791. }
  1792. }
  1793.  
  1794. if (!AutoMudae.hasNeededInfo()) return;
  1795.  
  1796. const el_MessageContent = el_Message.querySelector("div[id^='message-content']");
  1797.  
  1798. if (el_MessageContent) {
  1799. const messageContent = el_MessageContent.innerText;
  1800.  
  1801. /// Handle character claims & steals
  1802. const characterClaimMatch = /(.+) e (.+) agora são casados!/.exec(messageContent.trim());
  1803.  
  1804. if (characterClaimMatch || messageContent.includes("(Silver IV Bônus)")) {
  1805. let usernameThatClaimed, characterName;
  1806.  
  1807. if (characterClaimMatch) {
  1808. [_, usernameThatClaimed, characterName] = characterClaimMatch;
  1809. }
  1810.  
  1811. let user;
  1812.  
  1813. if (usernameThatClaimed) {
  1814. user = AutoMudae.users.find(user => user.username === usernameThatClaimed);
  1815. }
  1816.  
  1817. /// Claim
  1818. if (user) {
  1819. user.info.set(E.MUDAE_INFO.CAN_MARRY, false);
  1820.  
  1821. AutoMudae.updateInfoPanel(E.INFO_FIELD.CAN_MARRY, "No", user);
  1822. AutoMudae.updateInfoPanel(E.INFO_FIELD.COLLECTED_CHARACTERS, characterName, user);
  1823.  
  1824. if (AutoMudae.preferences.get(E.PREFERENCES.SOUND).marry) SOUND.marry();
  1825.  
  1826. const logMessage = `User [${usernameThatClaimed}] claimed character [${characterName}]!`;
  1827. logger.plus(logMessage);
  1828. AutoMudae.toasts.add(E.TOAST.CHARCLAIM, logMessage, el_Message);
  1829.  
  1830. el_Message.classList.add("plus");
  1831.  
  1832. document.querySelectorAll("[class^='embedAuthorName']").forEach(el_AuthorName => {
  1833. if (el_AuthorName.innerText === characterName) {
  1834. const el_ParentMessage = el_AuthorName.closest("li");
  1835. el_ParentMessage.classList.add("plus");
  1836. }
  1837. });
  1838. } else {
  1839. const el_Mentions = el_Message.querySelectorAll("span.mention");
  1840.  
  1841. let isIncludingMe = false;
  1842.  
  1843. for (let i = 0; i < el_Mentions.length; i++) {
  1844. const mentionedNick = el_Mentions[i].innerText.substr(1);
  1845.  
  1846. if (AutoMudae.users.some(user => user.nick === mentionedNick)) {
  1847. isIncludingMe = true;
  1848. break;
  1849. }
  1850. }
  1851.  
  1852. /// Steal
  1853. if (isIncludingMe) {
  1854. if (AutoMudae.preferences.get(E.PREFERENCES.SOUND).wishsteal) SOUND.critical();
  1855.  
  1856. el_Message.classList.add("critical");
  1857.  
  1858. if (characterName) {
  1859. document.querySelectorAll("[class^='embedAuthorName']").forEach(el_AuthorName => {
  1860. if (el_AuthorName.innerText === characterName) {
  1861. const el_ParentMessage = el_AuthorName.closest("li");
  1862. el_ParentMessage.classList.add("critical");
  1863. }
  1864. });
  1865. }
  1866.  
  1867. const stealWarn = characterClaimMatch
  1868. ? `User [${usernameThatClaimed}] claimed character [${characterName}] wished by you.`
  1869. : "A character wished by you was claimed by another user.";
  1870.  
  1871. logger.warn(stealWarn);
  1872. AutoMudae.toasts.add(E.TOAST.CRITICAL, stealWarn, el_Message);
  1873. }
  1874. }
  1875.  
  1876. return;
  1877. }
  1878.  
  1879. /// Handle "no more rolls" messages
  1880. const noMoreRollsMatch = /(.+), os rolls são limitado/.exec(messageContent);
  1881.  
  1882. if (noMoreRollsMatch) {
  1883. const user = AutoMudae.users.find(user => user.username === noMoreRollsMatch[1]);
  1884.  
  1885. return user && setTimeout(() => user.send("$tu"), 250);
  1886. }
  1887.  
  1888. const el_KakeraClaimStrong = el_Message.querySelector("div[id^='message-content'] span[class^='emojiContainer'] + strong");
  1889.  
  1890. /// Handle kakera claiming
  1891. if (el_KakeraClaimStrong) {
  1892. const kakeraClaimMatch = /^(.+)\s\+(\d+)$/.exec(el_KakeraClaimStrong.innerText);
  1893.  
  1894. if (kakeraClaimMatch) {
  1895. const [_, messageUsername, kakeraQuantity] = kakeraClaimMatch;
  1896.  
  1897. const user = AutoMudae.users.find(user => user.username === messageUsername);
  1898.  
  1899. if (user) {
  1900. const kakeraType = el_KakeraClaimStrong.previousElementSibling?.firstElementChild?.alt.replace(/:/g, '');
  1901.  
  1902. const powerCost = kakeraType === E.KAKERA.PURPLE ? 0 : user.info.get(E.MUDAE_INFO.CONSUMPTION);
  1903.  
  1904. if (powerCost > 0) {
  1905. const newPower = user.info.get(E.MUDAE_INFO.POWER) - powerCost;
  1906.  
  1907. user.info.set(E.MUDAE_INFO.POWER, newPower);
  1908. AutoMudae.updateInfoPanel(E.INFO_FIELD.POWER, newPower, user);
  1909. }
  1910.  
  1911. el_Message.classList.add("plus");
  1912. AutoMudae.updateInfoPanel(E.INFO_FIELD.KAKERA, kakeraQuantity);
  1913. logger.plus(`+${kakeraQuantity} kakera! [Remaining Power for user [${user.username}]: ${user.info.get(E.MUDAE_INFO.POWER)}%]`);
  1914. AutoMudae.toasts.add(E.TOAST.KAKERA, `+[${kakeraQuantity}] Kakera`, el_Message);
  1915. }
  1916.  
  1917. return;
  1918. }
  1919. }
  1920. }
  1921.  
  1922. const el_ImageWrapper = el_Message.querySelector("div[class^='embedDescription'] + div[class^='imageContent'] div[class^='imageWrapper']");
  1923.  
  1924. /// Handle character messages
  1925. if (el_ImageWrapper) {
  1926. const el_Footer = el_Message.querySelector("span[class^='embedFooterText']");
  1927.  
  1928. const isCharacterLookupMessage = (el_Footer && (/^\d+ \/ \d+$/.test(el_Footer.innerText) || /^Pertence a .+ ~~ \d+ \/ \d+$/.test(el_Footer.innerText)));
  1929.  
  1930. if (isCharacterLookupMessage) return;
  1931.  
  1932. const characterName = el_Message.querySelector("span[class^='embedAuthorName']").innerText;
  1933. const el_ReplyAvatar = el_Message.querySelector("img[class^='executedCommandAvatar']");
  1934. let replyUserId;
  1935.  
  1936. if (el_ReplyAvatar) {
  1937. replyUserId = /avatars\/(\d+)\//.exec(el_ReplyAvatar.src);
  1938.  
  1939. if (!replyUserId) return logger.error("Couldn't get reply user ID for", el_Message);
  1940.  
  1941. const user = AutoMudae.users.find(user => user.id === replyUserId[1]);
  1942.  
  1943. if (user) {
  1944. const rollsLeft = user.info.get(E.MUDAE_INFO.ROLLS_LEFT) - 1;
  1945. user.info.set(E.MUDAE_INFO.ROLLS_LEFT, rollsLeft);
  1946. AutoMudae.updateInfoPanel(E.INFO_FIELD.ROLLS_LEFT, rollsLeft, user);
  1947.  
  1948. if (el_Message.querySelector("div[class^='embedDescription']").innerText.includes("Sua nova ALMA")) {
  1949. if (AutoMudae.preferences.get(E.PREFERENCES.SOUND).soulmate) SOUND.newSoulmate();
  1950.  
  1951. const logMessage = `New soulmate: [${characterName}]!`;
  1952. logger.plus(logMessage);
  1953. AutoMudae.toasts.add(E.TOAST.SOULMATE, logMessage, el_Message);
  1954. }
  1955. }
  1956. }
  1957.  
  1958. if (!el_Footer || el_Footer.innerText.includes("2 ROLLS RESTANTES") && !el_Footer.innerText.includes("Pertence")) {
  1959. let el_InterestingCharacter, isWished;
  1960.  
  1961. const mentionedNicknames = [...el_Message.querySelectorAll("span.mention")].map(el_Mention => el_Mention.innerText.substr(1));
  1962.  
  1963. for (let i = 0; i < mentionedNicknames.length; i++) {
  1964. const mentionedNick = mentionedNicknames[i];
  1965.  
  1966. if (AutoMudae.users.some(user => user.nick === mentionedNick) || AutoMudae.preferences.get(E.PREFERENCES.MENTIONS).split(",").map(nick => nick.trim()).includes(mentionedNick)) {
  1967. el_InterestingCharacter = el_Message;
  1968. isWished = true;
  1969. break;
  1970. }
  1971. }
  1972.  
  1973. const marriageableUser = AutoMudae.getMarriageableUser(mentionedNicknames);
  1974.  
  1975. if (marriageableUser && !el_InterestingCharacter && AutoMudae.isLastReset()) {
  1976. //# Search in a database
  1977. if (characterName === "hmm") {
  1978. el_InterestingCharacter = el_Message;
  1979. };
  1980. }
  1981.  
  1982. if (el_InterestingCharacter) {
  1983. const logMessage = `Found character [${characterName}]`;
  1984. logger.info(logMessage);
  1985. AutoMudae.toasts.add(E.TOAST.INFO, logMessage, el_Message);
  1986.  
  1987. if (AutoMudae.preferences.get(E.PREFERENCES.SOUND).foundcharacter) SOUND.foundCharacter();
  1988.  
  1989. if (marriageableUser) {
  1990. //# Make it verify if marriageableUser can still marry after all delay calculations (In case of multiple marriageable characters at the same time)
  1991. if (!isWished) {
  1992. setTimeout(() => marriageableUser.react(el_Message, pickRandom(Object.values(E.EMOJI))), 8500);
  1993. return;
  1994. }
  1995.  
  1996. const isProtected = !!el_Message.querySelector("img[alt=':wishprotect:']");
  1997.  
  1998. if (!isProtected || isProtected && marriageableUser.id === replyUserId){
  1999. observeToReact(el_Message, marriageableUser);
  2000. return;
  2001. }
  2002.  
  2003. setTimeout(() => observeToReact(el_Message, marriageableUser), 2905);
  2004. return;
  2005. }
  2006.  
  2007. if (AutoMudae.preferences.get(E.PREFERENCES.SOUND).cantmarry) SOUND.critical();
  2008.  
  2009. const warnMessage = `Can't marry right now. You may lose character [${characterName}]`;
  2010. logger.warn(warnMessage);
  2011. AutoMudae.toasts.add(E.TOAST.WARN, warnMessage, el_Message);
  2012. }
  2013.  
  2014. return;
  2015. }
  2016.  
  2017. /// Owned characters
  2018. if (el_Footer.innerText.includes("Pertence")) {
  2019. /// Observe kakera reactions append
  2020. observeToReact(el_Message);
  2021. }
  2022.  
  2023. return;
  2024. }
  2025. });
  2026. }
  2027.  
  2028. //// SessionId Hook
  2029. window.console.info = function () {
  2030. for (const arg of arguments) {
  2031. const match = /\[READY\] (?:.+) as (.+)/.exec(arg) || /resuming session (.+),/.exec(arg);
  2032.  
  2033. if (match) {
  2034. window.console.info = console.info;
  2035. Discord.info.set(E.DISCORD_INFO.SESSION_ID, match[1]);
  2036. AutoMudae.tryEnable();
  2037. }
  2038. }
  2039. console.info(...arguments);
  2040. };
  2041.  
  2042. //// Main
  2043. window.addEventListener("load", main, false);
  2044.  
  2045. function main() {
  2046. const findToolbarTimer = setInterval(() => {
  2047. if (document.querySelector("[class^='toolbar']")){
  2048. clearInterval(findToolbarTimer);
  2049. AutoMudae.preRender();
  2050. }
  2051. }, 200);
  2052. };
  2053. })();
  2054.  

QingJ © 2025

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