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