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.1
  10. // @author UpDownLeftDie
  11. // @license MIT
  12. // @description Automatically adds all the Epix friends links from the gamescom discord server
  13.  
  14. // ==/UserScript==
  15.  
  16. let EPIX_IDS = [];
  17. let DISCORD_TOKEN = '';
  18. let EPIX_FRIENDS = [];
  19. let EPIX_BUTTON;
  20.  
  21. function main() {
  22. EPIX_FRIENDS = [...new Set(GM_getValue('epixFriends') || [])];
  23. EPIX_IDS = [...new Set(GM_getValue('epixIds') || [])];
  24. DISCORD_TOKEN = localStorage.getItem("token")?.replaceAll('\"', '');
  25. console.log({EPIX_FRIENDS, EPIX_IDS, DISCORD_TOKEN: !!DISCORD_TOKEN});
  26.  
  27. EPIX_BUTTON = document.createElement('button');
  28. EPIX_BUTTON.setAttribute('id', 'epixButton');
  29. EPIX_BUTTON.setAttribute('type', 'button');
  30. EPIX_BUTTON.innerHTML = 'Run Epix Friend Adder';
  31. document.querySelector('h1').appendChild(EPIX_BUTTON);
  32.  
  33. document.getElementById("epixButton").addEventListener("click", handleButton, false);
  34. }
  35.  
  36. async function handleButton(event) {
  37. await getEpixIds();
  38.  
  39. EPIX_BUTTON.setAttribute('disabled', true);
  40. EPIX_BUTTON.innerHTML = 'Running...';
  41.  
  42.  
  43. let messages = [];
  44. do {
  45. const minId = GM_getValue('discordMinId');
  46. console.log({minId});
  47. messages = await getDiscordMessages(minId);
  48. const codes = getEpixCodes(messages);
  49. console.log({messages, codes});
  50. for(let i in codes) {
  51. const promises = EPIX_IDS.map(epixId => connectRequest(epixId, codes[i].code));
  52. let responses = await Promise.all(promises);
  53. let json = await responses[0].json();
  54. let status = json?.data?.status.toUpperCase();
  55. if (!responses[0].ok) {
  56. if (json?.message.toLowerCase() === "user not found") {
  57. status = "USER_NOT_FOUND";
  58. } else {
  59. EPIX_BUTTON.innerHTML = "ERROR"
  60. EPIX_BUTTON.setAttribute('disabled', true);
  61. throw resp;
  62. }
  63. }
  64. updateFriends(status, codes[i]);
  65. }
  66. if (messages.length > 0) {
  67. GM_setValue('discordMinId', messages[messages.length - 1][0].id);
  68. }
  69. } while(messages.length >= 25);
  70.  
  71. EPIX_BUTTON.innerHTML = 'Done!';
  72. }
  73.  
  74. async function getEpixIds() {
  75. return new Promise((resolve, reject) => {
  76. const inputValue = EPIX_IDS?.length ? EPIX_IDS.join(',') : '';
  77. const dialog = document.createElement('dialog');
  78. dialog.setAttribute('open', true);
  79. dialog.setAttribute('id', 'epixIdsDialog')
  80. dialog.innerHTML = `
  81. <p>Enter your Epix user id(s) (<strong>NOT the same as your invite id!</strong>)</p>
  82. To get this go to your <a href="https://www.gamescom.global/en/epix" target="_blank">profile</a>:
  83. <ol>
  84. <li>open dev tools</li>
  85. <li>refresh the page</li>
  86. <li>look at network requests for "user?userId=XXXXXXX"</li>
  87. </ol>
  88. <form method="dialog">
  89. <input id="epixIds" placeholder="b5629b160f555ab4b08ef8e49568b7dd, a49f9b160f555vd4b08ef8e49568b7a2" value="${inputValue}" />
  90. <button id="epixIdAddButton">START</button>
  91. </form>
  92. `;
  93. document.body.appendChild(dialog);
  94. document.getElementById("epixIdAddButton").addEventListener("click", ()=> {
  95. const idStr = document.getElementById("epixIds").value;
  96. const ids = idStr.split(',').reduce((acc, curr) => {
  97. const id = curr.replace(/\W/gi, '');
  98. if (!id) return acc;
  99. acc.push(id);
  100. return acc;
  101. }, []);
  102. EPIX_IDS = ids;
  103. GM_setValue('epixIds', EPIX_IDS);
  104. dialog.parentNode.removeChild(dialog);
  105. resolve();
  106. }, false);
  107. });
  108. }
  109.  
  110. function getEpixCodes(discordMessages) {
  111. const codes = discordMessages.reduce((acc, curr) => {
  112. const message = curr[0]
  113. const match = message.content.match(/epix-connect=([\w\d]{7})/i);
  114. if (match?.[1] && !EPIX_FRIENDS.includes(match[1])) {
  115. acc.push({code: match[1], messageId: message.id});
  116. }
  117. return acc;
  118. }, [])
  119.  
  120. return codes;
  121. }
  122.  
  123.  
  124. function updateFriends(status, code) {
  125. if (status === "CONNECTION_SUCCESSFUL" || status === "ALREADY_MATCHED" || status === "USER_NOT_FOUND") {
  126. EPIX_FRIENDS.push(code.code);
  127. GM_setValue('epixFriends', EPIX_FRIENDS);
  128. GM_setValue('discordMinId', code.messageId);
  129. }
  130.  
  131. }
  132.  
  133.  
  134.  
  135. //--- Style our newly added elements using CSS.
  136. GM_addStyle (`
  137. #epixButton {
  138. margin-left: 10px;
  139. }
  140.  
  141. #epixIdsDialog {
  142. margin-top: 5rem;
  143. }
  144. #epixIdsDialog ol {
  145. list-style: auto;
  146. padding-left: 35px;
  147. margin-bottom: 10px;
  148. }
  149. #epixIdsDialog input {
  150. width: 100%;
  151. }
  152. #epixIdsDialog button {
  153. margin: 5px auto;
  154. display: block;
  155. font-size: large;
  156. background: greenyellow;
  157. padding: 2px 10px;
  158. }
  159. `);
  160.  
  161.  
  162. async function connectRequest(userId, profileId) {
  163. return fetch("https://wfppjum4x2.execute-api.eu-central-1.amazonaws.com/production/connection-request", {
  164. "referrer": "https://www.gamescom.global/",
  165. "referrerPolicy": "strict-origin-when-cross-origin",
  166. "body": `{"userId":"${userId}","profileId":"${profileId}"}`,
  167. "method": "POST",
  168. "mode": "cors",
  169. "credentials": "omit"
  170. });
  171. }
  172.  
  173.  
  174. // A lot of this function was adapted from: https://github.com/victornpb/undiscord/blob/master/deleteDiscordMessages.user.js#L652-L712
  175. async function getDiscordMessages(minId) {
  176. const params = queryString([
  177. ['limit', 25],
  178. ['channel_id', '1259933715409145966'],
  179. ['min_id', minId],
  180. ['sort_by', 'timestamp'],
  181. ['sort_order', 'asc'],
  182. ['has','link'],
  183. ]);
  184. let resp;
  185. try {
  186. resp = await fetch(`https://discord.com/api/v9/guilds/574865170694799400/messages/search?${params}`, {
  187. "headers": {
  188. "accept": "*/*",
  189. "authorization": DISCORD_TOKEN
  190. },
  191. "referrer": "https://discord.com/channels/574865170694799400/1259933715409145966",
  192. "referrerPolicy": "strict-origin-when-cross-origin",
  193. "method": "GET",
  194. "mode": "cors",
  195. "credentials": "include"
  196. });
  197. } catch (err) {
  198. this.state.running = false;
  199. console.error('Search request threw an error:', err);
  200. EPIX_BUTTON.innerHTML = "ERROR"
  201. EPIX_BUTTON.setAttribute('disabled', true);
  202. throw err;
  203. }
  204.  
  205. // not indexed yet
  206. if (resp.status === 202) {
  207. let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
  208. console.warn(`This channel isn't indexed yet. Waiting ${w}ms for discord to index it...`);
  209. await wait(w);
  210. return await getDiscordMessages(minId);
  211. }
  212.  
  213. if (!resp.ok) {
  214. // searching messages too fast
  215. if (resp.status === 429) {
  216. let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
  217. console.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`);
  218. console.warn(`Cooling down for ${w * 2}ms before retrying...`);
  219.  
  220. await wait(w * 2);
  221. return await getDiscordMessages(minId);
  222. } else {
  223. console.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json());
  224. EPIX_BUTTON.innerHTML = "ERROR"
  225. EPIX_BUTTON.setAttribute('disabled', true);
  226. throw resp;
  227. }
  228. }
  229.  
  230. const data = await resp.json();
  231. return data.messages;
  232. }
  233.  
  234.  
  235. const wait = async ms => new Promise(done => setTimeout(done, ms));
  236. const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&');
  237.  
  238. (new MutationObserver(check)).observe(document, {childList: true, subtree: true});
  239. function check(changes, observer) {
  240. if(document.querySelector('h1')) {
  241. observer.disconnect();
  242. main();
  243. }
  244. }