Epix Auto Friend

Automatically adds all the Epix friends links from the gamescom discord server

当前为 2024-08-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Epix Auto Friend
  3. // @namespace Violentmonkey Scripts
  4. // @match https://discord.com/channels/574865170694799400/1259933715409145966*
  5. // @inject-into content
  6. // @grant GM_addStyle
  7. // @grant GM_getValue
  8. // @grant GM_setValue
  9. // @version 1.3
  10. // @author UpDownLeftDie
  11. // @contributionURL https://www.patreon.com/camkitties
  12. // @supportURL https://discord.gg/hWvWGUDf
  13.  
  14. // @license MIT
  15. // @description Automatically adds all the Epix friends links from the gamescom discord server
  16.  
  17. // ==/UserScript==
  18.  
  19. let EPIX_IDS = [];
  20. let DISCORD_TOKEN = '';
  21. let EPIX_FRIENDS = [];
  22. let EPIX_BUTTON;
  23.  
  24. function main() {
  25. EPIX_FRIENDS = [...new Set(GM_getValue('epixFriends') || [])];
  26. EPIX_IDS = [...new Set(GM_getValue('epixIds') || [])];
  27. DISCORD_TOKEN = getDiscordToken();
  28. console.log({EPIX_FRIENDS, EPIX_IDS, DISCORD_TOKEN: !!DISCORD_TOKEN});
  29.  
  30. EPIX_BUTTON = document.createElement('button');
  31. EPIX_BUTTON.setAttribute('id', 'epixButton');
  32. EPIX_BUTTON.setAttribute('type', 'button');
  33. EPIX_BUTTON.innerHTML = 'Run Epix Friend Adder';
  34. document.querySelector('h1').appendChild(EPIX_BUTTON);
  35.  
  36. document.getElementById("epixButton").addEventListener("click", handleButton, false);
  37. }
  38.  
  39. async function handleButton(event) {
  40. await getEpixIds();
  41.  
  42. let count = 0;
  43. disableButton('Running: added 0');
  44.  
  45. let messages = [];
  46. do {
  47. const minId = GM_getValue('discordMinId');
  48. console.log({minId});
  49. messages = await getDiscordMessages(minId);
  50. const codes = getEpixCodes(messages);
  51. console.log({messages, codes});
  52.  
  53. for(let i in codes) {
  54. const promises = EPIX_IDS.map(epixId => connectRequest(epixId, codes[i].code));
  55. let responses = await Promise.all(promises).catch(err => {
  56. disableButton("ERROR");
  57. throw err;
  58. })
  59. let json = await responses[0].json();
  60. let status = json?.data?.status.toUpperCase();
  61.  
  62. for(let i in responses) {
  63. const resp = responses[i];
  64. console.log({resp})
  65. if (!resp.ok) {
  66. if (json?.message.toLowerCase() === "user not found" || resp.status === 400) {
  67. status = "USER_NOT_FOUND";
  68. } else {
  69. disableButton("ERROR");
  70. throw resp;
  71. }
  72. }
  73. }
  74.  
  75. count++;
  76. disableButton(`Running: added ${count}`);
  77. updateFriends(status, codes[i]);
  78. }
  79. if (messages.length > 0) {
  80. GM_setValue('discordMinId', messages[messages.length - 1][0].id);
  81. }
  82. } while(messages.length >= 25);
  83.  
  84.  
  85. disableButton("Done!");
  86. }
  87.  
  88. async function getEpixIds() {
  89. return new Promise((resolve, reject) => {
  90. const inputValue = EPIX_IDS?.length ? EPIX_IDS.join(',') : '';
  91. const dialog = document.createElement('dialog');
  92. dialog.setAttribute('open', true);
  93. dialog.setAttribute('id', 'epixIdsDialog')
  94. dialog.innerHTML = `
  95. <p>Enter your Epix user id(s) (<strong>NOT the same as your invite id!</strong>)</p>
  96. To get this go to your <a href="https://www.gamescom.global/en/epix" target="_blank">profile</a>:
  97. <ol>
  98. <li>open dev tools</li>
  99. <li>refresh the page</li>
  100. <li>look at network requests for "user?userId=XXXXXXX"</li>
  101. </ol>
  102. <form method="dialog">
  103. <label for="epixIds">Epix Id(s):</label>
  104. <input required id="epixIds" placeholder="b5629b160f555ab4b08ef8e49568b7dd, a49f9b160f555vd4b08ef8e49568b7a2" value="${inputValue}" />
  105. <label for="discordToken">Discord token:</label>
  106. <span style="display:flex;">
  107. <input required id="discordToken" style="flex: 1 0 0;" placeholder="MTMkaJ9HshK2dAx.Fna4Jk.qsKrjSk42jKns9Js32-G3pnH_qcnIskQzy" value="${DISCORD_TOKEN ? DISCORD_TOKEN : ''}" />
  108. <button id="getDiscordToken" ${!!DISCORD_TOKEN ? "disabled" : null}>Get</button>
  109. </span>
  110. <button id="epixIdAddButton" type="submit">START</button>
  111. <span id="epixError" style="color: red; font-weight: bold;"></span>
  112. </form>
  113. `;
  114. document.body.appendChild(dialog);
  115.  
  116. document.getElementById("getDiscordToken").addEventListener("click", (e) => {
  117. e.preventDefault();
  118.  
  119. DISCORD_TOKEN = getDiscordToken();
  120. document.getElementById("discordToken").value = DISCORD_TOKEN;
  121. });
  122.  
  123. document.getElementById("epixIdAddButton").addEventListener("click", (e) => {
  124. e.preventDefault();
  125. const idStr = document.getElementById("epixIds").value;
  126. const ids = idStr.split(',').reduce((acc, curr) => {
  127. const id = curr.replace(/\W/gi, '');
  128. if (!id) return acc;
  129. acc.push(id);
  130. return acc;
  131. }, []);
  132. EPIX_IDS = ids;
  133.  
  134. if(!DISCORD_TOKEN || !EPIX_IDS?.length) {
  135. document.getElementById("epixError").innerHTML = "ERROR: missing Epix User ID(s) or Discord Token";
  136. return;
  137. }
  138.  
  139. GM_setValue('epixIds', EPIX_IDS);
  140. dialog.parentNode.removeChild(dialog);
  141. resolve();
  142. }, false);
  143. });
  144. }
  145.  
  146. function getEpixCodes(discordMessages) {
  147. const codes = discordMessages.reduce((acc, curr) => {
  148. const message = curr[0]
  149. const match = message.content.match(/epix-connect=([\w\d]{7})/i);
  150. if (match?.[1] && !EPIX_FRIENDS.includes(match[1])) {
  151. acc.push({code: match[1], messageId: message.id});
  152. }
  153. return acc;
  154. }, [])
  155.  
  156. return codes;
  157. }
  158.  
  159.  
  160. function updateFriends(status, code) {
  161. if (status === "CONNECTION_SUCCESSFUL" || status === "ALREADY_MATCHED" || status === "USER_NOT_FOUND") {
  162. EPIX_FRIENDS.push(code.code);
  163. GM_setValue('epixFriends', EPIX_FRIENDS);
  164. GM_setValue('discordMinId', code.messageId);
  165. }
  166.  
  167. }
  168.  
  169. /**
  170. * @param {string} [text] - Button text
  171. * @returns {null}
  172. */
  173. function disableButton(text = '') {
  174. EPIX_BUTTON.toggleAttribute('disabled', true);
  175. if (text) {
  176. updateButton(text);
  177. }
  178. }
  179.  
  180. /**
  181. * @param {string} [text] - Button text
  182. * @returns {null}
  183. */
  184. function enableButton(text = '') {
  185. EPIX_BUTTON.toggleAttribute('disabled', false);
  186. if (text) {
  187. updateButton(text);
  188. }
  189. }
  190.  
  191. /**
  192. * @param {string} [text] - Button text
  193. * @returns {null}
  194. */
  195. function updateButton(text = "Run Epix Friend Adder") {
  196. EPIX_BUTTON.innerHTML = text;
  197. }
  198.  
  199.  
  200. //--- Style our newly added elements using CSS.
  201. GM_addStyle (`
  202. #epixButton {
  203. margin-left: 10px;
  204. }
  205.  
  206. #epixIdsDialog {
  207. position: absolute;
  208. top: 5rem;
  209. z-index: 100;
  210. }
  211. #epixIdsDialog ol {
  212. list-style: auto;
  213. padding-left: 35px;
  214. margin-bottom: 10px;
  215. }
  216. #epixIdsDialog input {
  217. width: 100%;
  218. }
  219. #epixIdsDialog button {
  220. margin: 5px auto;
  221. display: block;
  222. font-size: large;
  223. background: greenyellow;
  224. padding: 2px 10px;
  225. }
  226. `);
  227.  
  228.  
  229. async function connectRequest(userId, profileId) {
  230. return fetch("https://wfppjum4x2.execute-api.eu-central-1.amazonaws.com/production/connection-request", {
  231. "referrer": "https://www.gamescom.global/",
  232. "referrerPolicy": "strict-origin-when-cross-origin",
  233. "body": `{"userId":"${userId}","profileId":"${profileId}"}`,
  234. "method": "POST",
  235. "mode": "cors",
  236. "credentials": "omit"
  237. });
  238. }
  239.  
  240.  
  241. // A lot of this function was adapted from: https://github.com/victornpb/undiscord/blob/master/deleteDiscordMessages.user.js#L652-L712
  242. async function getDiscordMessages(minId) {
  243. const params = queryString([
  244. ['limit', 25],
  245. ['channel_id', '1259933715409145966'],
  246. ['min_id', minId],
  247. ['sort_by', 'timestamp'],
  248. ['sort_order', 'asc'],
  249. ['has','link'],
  250. ]);
  251. let resp;
  252. try {
  253. resp = await fetch(`https://discord.com/api/v9/guilds/574865170694799400/messages/search?${params}`, {
  254. "headers": {
  255. "accept": "*/*",
  256. "authorization": DISCORD_TOKEN
  257. },
  258. "referrer": "https://discord.com/channels/574865170694799400/1259933715409145966",
  259. "referrerPolicy": "strict-origin-when-cross-origin",
  260. "method": "GET",
  261. "mode": "cors",
  262. "credentials": "include"
  263. });
  264. } catch (err) {
  265. this.state.running = false;
  266. console.error('Search request threw an error:', err);
  267. disableButton("ERROR");
  268. throw err;
  269. }
  270.  
  271. // not indexed yet
  272. if (resp.status === 202) {
  273. let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
  274. console.warn(`This channel isn't indexed yet. Waiting ${w}ms for discord to index it...`);
  275. await wait(w);
  276. return await getDiscordMessages(minId);
  277. }
  278.  
  279. if (!resp.ok) {
  280. // searching messages too fast
  281. if (resp.status === 429) {
  282. let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
  283. console.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`);
  284. console.warn(`Cooling down for ${w * 2}ms before retrying...`);
  285.  
  286. await wait(w * 2);
  287. return await getDiscordMessages(minId);
  288. } else {
  289. console.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json());
  290. disableButton("ERROR");
  291. throw resp;
  292. }
  293. }
  294.  
  295. const data = await resp.json();
  296. return data.messages;
  297. }
  298.  
  299. function getDiscordToken() {
  300. window.dispatchEvent(new Event('beforeunload'));
  301. const LS = document.body.appendChild(document.createElement('iframe')).contentWindow.localStorage;
  302. try {
  303. return JSON.parse(LS.token);
  304. } catch {
  305. console.info('Could not automatically detect Authorization Token in local storage!');
  306. console.info('Attempting to grab token using webpack');
  307. return (window.webpackChunkdiscord_app?.push([[''], {}, e => { window.m = []; for (let c in e.c) window.m.push(e.c[c]); }]), window.m)?.find(m => m?.exports?.default?.getToken !== void 0).exports.default.getToken();
  308. }
  309. }
  310.  
  311.  
  312. const wait = async ms => new Promise(done => setTimeout(done, ms));
  313. const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&');
  314.  
  315. (new MutationObserver(check)).observe(document, {childList: true, subtree: true});
  316. function check(changes, observer) {
  317. if(document.querySelector('h1')) {
  318. observer.disconnect();
  319. main();
  320. }
  321. }