Epix Auto Friend

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

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

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