Epix Auto Friend

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

目前为 2024-08-17 提交的版本,查看 最新版本

  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.4
  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 (resp.status === 400 || json?.message?.toLowerCase() === "user not found") {
  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 matches = message.content.matchAll(/epix-connect=([\w\d]{7})/ig);
  150.  
  151. for (const match of matches) {
  152. if (match?.[1] && !EPIX_FRIENDS.includes(match[1])) {
  153. acc.push({code: match[1], messageId: message.id});
  154. }
  155. }
  156.  
  157. return acc;
  158. }, [])
  159.  
  160. return codes;
  161. }
  162.  
  163.  
  164. function updateFriends(status, code) {
  165. if (status === "CONNECTION_SUCCESSFUL" || status === "ALREADY_MATCHED" || status === "USER_NOT_FOUND") {
  166. EPIX_FRIENDS.push(code.code);
  167. GM_setValue('epixFriends', EPIX_FRIENDS);
  168. GM_setValue('discordMinId', code.messageId);
  169. }
  170.  
  171. }
  172.  
  173. /**
  174. * @param {string} [text] - Button text
  175. * @returns {null}
  176. */
  177. function disableButton(text = '') {
  178. EPIX_BUTTON.toggleAttribute('disabled', true);
  179. if (text) {
  180. updateButton(text);
  181. }
  182. }
  183.  
  184. /**
  185. * @param {string} [text] - Button text
  186. * @returns {null}
  187. */
  188. function enableButton(text = '') {
  189. EPIX_BUTTON.toggleAttribute('disabled', false);
  190. if (text) {
  191. updateButton(text);
  192. }
  193. }
  194.  
  195. /**
  196. * @param {string} [text] - Button text
  197. * @returns {null}
  198. */
  199. function updateButton(text = "Run Epix Friend Adder") {
  200. EPIX_BUTTON.innerHTML = text;
  201. }
  202.  
  203.  
  204. //--- Style our newly added elements using CSS.
  205. GM_addStyle (`
  206. #epixButton {
  207. margin-left: 10px;
  208. }
  209.  
  210. #epixIdsDialog {
  211. position: absolute;
  212. top: 5rem;
  213. z-index: 100;
  214. }
  215. #epixIdsDialog ol {
  216. list-style: auto;
  217. padding-left: 35px;
  218. margin-bottom: 10px;
  219. }
  220. #epixIdsDialog input {
  221. width: 100%;
  222. }
  223. #epixIdsDialog button {
  224. margin: 5px auto;
  225. display: block;
  226. font-size: large;
  227. background: greenyellow;
  228. padding: 2px 10px;
  229. }
  230. `);
  231.  
  232.  
  233. async function connectRequest(userId, profileId) {
  234. return fetch("https://wfppjum4x2.execute-api.eu-central-1.amazonaws.com/production/connection-request", {
  235. "referrer": "https://www.gamescom.global/",
  236. "referrerPolicy": "strict-origin-when-cross-origin",
  237. "body": `{"userId":"${userId}","profileId":"${profileId}"}`,
  238. "method": "POST",
  239. "mode": "cors",
  240. "credentials": "omit"
  241. });
  242. }
  243.  
  244.  
  245. // A lot of this function was adapted from: https://github.com/victornpb/undiscord/blob/master/deleteDiscordMessages.user.js#L652-L712
  246. async function getDiscordMessages(minId) {
  247. const params = queryString([
  248. ['limit', 25],
  249. ['channel_id', '1259933715409145966'],
  250. ['min_id', minId],
  251. ['sort_by', 'timestamp'],
  252. ['sort_order', 'asc'],
  253. ['has','link'],
  254. ]);
  255. let resp;
  256. try {
  257. resp = await fetch(`https://discord.com/api/v9/guilds/574865170694799400/messages/search?${params}`, {
  258. "headers": {
  259. "accept": "*/*",
  260. "authorization": DISCORD_TOKEN
  261. },
  262. "referrer": "https://discord.com/channels/574865170694799400/1259933715409145966",
  263. "referrerPolicy": "strict-origin-when-cross-origin",
  264. "method": "GET",
  265. "mode": "cors",
  266. "credentials": "include"
  267. });
  268. } catch (err) {
  269. this.state.running = false;
  270. console.error('Search request threw an error:', err);
  271. disableButton("ERROR");
  272. throw err;
  273. }
  274.  
  275. // not indexed yet
  276. if (resp.status === 202) {
  277. let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
  278. console.warn(`This channel isn't indexed yet. Waiting ${w}ms for discord to index it...`);
  279. await wait(w);
  280. return await getDiscordMessages(minId);
  281. }
  282.  
  283. if (!resp.ok) {
  284. // searching messages too fast
  285. if (resp.status === 429) {
  286. let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
  287. console.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`);
  288. console.warn(`Cooling down for ${w * 2}ms before retrying...`);
  289.  
  290. await wait(w * 2);
  291. return await getDiscordMessages(minId);
  292. } else {
  293. console.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json());
  294. disableButton("ERROR");
  295. throw resp;
  296. }
  297. }
  298.  
  299. const data = await resp.json();
  300. return data.messages;
  301. }
  302.  
  303. function getDiscordToken() {
  304. window.dispatchEvent(new Event('beforeunload'));
  305. const LS = document.body.appendChild(document.createElement('iframe')).contentWindow.localStorage;
  306. try {
  307. return JSON.parse(LS.token);
  308. } catch {
  309. console.info('Could not automatically detect Authorization Token in local storage!');
  310. console.info('Attempting to grab token using webpack');
  311. 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();
  312. }
  313. }
  314.  
  315.  
  316. const wait = async ms => new Promise(done => setTimeout(done, ms));
  317. const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&');
  318.  
  319. (new MutationObserver(check)).observe(document, {childList: true, subtree: true});
  320. function check(changes, observer) {
  321. if(document.querySelector('h1')) {
  322. observer.disconnect();
  323. main();
  324. }
  325. }