- // ==UserScript==
- // @name gamescom Epix Tools
- // @namespace Violentmonkey Scripts
- // @match https://discord.com/channels/574865170694799400/1259933715409145966*
- // @match https://www.gamescom.global/*/epix/cards
- // @inject-into content
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // @version 2.1
- // @author UpDownLeftDie
-
- // @license MIT
- // @description Automatically adds all the Epix friends links from the gamescom discord server
-
- // @contributionURL https://www.patreon.com/camkitties
- // @supportURL https://discord.gg/hWvWGUDf
-
- // @homepageURL https://greasyfork.org/en/scripts/503478%20gamescom%20epix%20tools
- // ==/UserScript==
-
- let EPIX_IDS = [];
- let DISCORD_TOKEN = '';
- let EPIX_FRIENDS = [];
- let EPIX_BUTTON;
-
-
- main();
- function main() {
- if(location.href.includes('discord.com')) {
- function check(changes, observer) {
- if(document.querySelector('h1')) {
- observer.disconnect();
- discordMain();
- }
- }
- } else {
- function check(changes, observer) {
- if(document.querySelector('.card-list--list-item')) {
- observer.disconnect();
- epixMain();
- }
- }
- }
- (new MutationObserver(check)).observe(document, {childList: true, subtree: true});
- }
-
-
- function epixMain() {
- const mainSection = document.querySelector('section > div');
- const cardsButton = document.createElement('button');
- cardsButton.setAttribute('id', 'loadCardsButton');
- cardsButton.setAttribute('type', 'button');
- cardsButton.textContent = 'Load have/need card lists';
-
- const screenshotButton = document.createElement('button');
- screenshotButton.setAttribute('id', 'screenshotButton');
- screenshotButton.setAttribute('type', 'button');
- screenshotButton.textContent = 'Show only extra cards';
-
- const buttonContainer = document.createElement('div');
- buttonContainer.setAttribute('style', 'margin-bottom: 20px; display: flex; justify-content: space-between;');
-
- buttonContainer.append(cardsButton, screenshotButton)
-
- mainSection.prepend(buttonContainer);
-
- document.getElementById("loadCardsButton").addEventListener("click", () => {
- cardsButton.remove();
- epixLoadCards();
- }, false);
-
- document.getElementById("screenshotButton").addEventListener("click", () => {
- cardsButton.remove();
- screenshotButton.remove();
- document.querySelectorAll('.card-list--list-item').forEach(node => {
- const text = node.textContent;
- if (text === "x1" || !text) {
- node.style.display = "none";
- }
- });
- }, false);
- }
-
- async function epixLoadCards() {
- window.scrollTo({
- top: document.body.scrollHeight,
- behavior:'instant',
- });
-
- const scrollPercent = 15
- // > -scrollPercent ensure we scroll all the way back to the top
- for(let p = 100; p > -scrollPercent; p -= scrollPercent) {
- await wait(200);
- if (p < 0) p = 0;
- window.scrollTo({
- top: document.body.scrollHeight * (p/100),
- behavior:'smooth',
- });
- }
-
- const lockedCardSrc = 'KdneecaZTKWwd7aCAkOT';
- let cardsHave = [];
- let cardsNeed = [];
- document.querySelectorAll('.card-list--list-item').forEach(node => {
- const img = node.querySelector('img');
- img.removeAttribute('loading');
- });
- await wait(500);
- document.querySelectorAll('.card-list--list-item').forEach(node => {
- const img = node.querySelector('img');
- const name = img.title;
- if (img.src.includes(lockedCardSrc)) {
- cardsNeed.push(name);
- } else {
- const count = Number(node.textContent.slice(1));
- cardsHave.push({name, count});
- }
- });
- cardsHave.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
- cardsNeed.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
-
- const mainSection = document.querySelector('section > div');
-
- const cardsLists = document.createElement('div');
- cardsLists.setAttribute('class', 'row gx-3 gy-4 g-sm-4');
- cardsLists.setAttribute('style', 'margin-bottom: 25px;')
- cardsLists.innerHTML = `
- <div class="col-6" role="button" tabindex="99">
- Extra Cards:<br />
- <div style="margin-left: 20px;">
- \`${cardsHave.filter(card => card.count > 1).map(card => `${card.name}\` - x${card.count - 1}`).join('<br />`')}
- <p> </p>
- </div>
- </div>
- <div class="col-6" role="button" tabindex="0">
- Cards Need:<br />
- <div style="margin-left: 20px;">
- \`${cardsNeed.join('`<br />`')}\`
- </div>
- </div>
- `;
-
- mainSection.prepend(cardsLists)
- }
-
- function discordMain() {
- EPIX_FRIENDS = [...new Set(GM_getValue('epixFriends') || [])];
- EPIX_IDS = [...new Set(GM_getValue('epixIds') || [])];
- DISCORD_TOKEN = getDiscordToken();
- console.log({EPIX_FRIENDS, EPIX_IDS, DISCORD_TOKEN: !!DISCORD_TOKEN});
-
- EPIX_BUTTON = document.createElement('button');
- EPIX_BUTTON.setAttribute('id', 'epixButton');
- EPIX_BUTTON.setAttribute('type', 'button');
- EPIX_BUTTON.innerHTML = 'Run Epix Friend Adder';
- document.querySelector('h1').appendChild(EPIX_BUTTON);
-
- document.getElementById("epixButton").addEventListener("click", handleButton, false);
- }
-
- async function handleButton(event) {
- await getEpixIds();
-
- let count = 0;
- disableButton('Running: added 0');
-
- let messages = [];
- do {
- const minId = GM_getValue('discordMinId');
- console.log({minId});
- messages = await getDiscordMessages(minId);
- const codes = getEpixCodes(messages);
- console.log({messages, codes});
-
- for(let i in codes) {
- const promises = EPIX_IDS.map(epixId => connectRequest(epixId, codes[i].code));
- let responses = await Promise.all(promises).catch(err => {
- disableButton("ERROR");
- throw err;
- })
- let json = await responses[0].json();
- let status = json?.data?.status.toUpperCase();
-
- for(let i in responses) {
- const resp = responses[i];
- console.log({resp})
- if (!resp.ok) {
- if (resp.status === 400 || json?.message?.toLowerCase() === "user not found") {
- status = "USER_NOT_FOUND";
- } else {
- disableButton("ERROR");
- throw resp;
- }
- }
- }
-
- count++;
- disableButton(`Running: added ${count}`);
- updateFriends(status, codes[i]);
- }
- if (messages.length > 0) {
- GM_setValue('discordMinId', messages[messages.length - 1][0].id);
- }
- } while(messages.length >= 25);
-
-
- disableButton("Done!");
- }
-
- async function getEpixIds() {
- return new Promise((resolve, reject) => {
- const inputValue = EPIX_IDS?.length ? EPIX_IDS.join(',') : '';
- const dialog = document.createElement('dialog');
- dialog.setAttribute('open', true);
- dialog.setAttribute('id', 'epixIdsDialog')
- dialog.innerHTML = `
- <p>Enter your Epix user id(s) (<strong>NOT the same as your invite id!</strong>)</p>
- To get this go to your <a href="https://www.gamescom.global/en/epix" target="_blank">profile</a>:
- <ol>
- <li>open dev tools</li>
- <li>refresh the page</li>
- <li>look at network requests for "user?userId=XXXXXXX"</li>
- </ol>
- <form method="dialog">
- <label for="epixIds">Epix Id(s):</label>
- <input required id="epixIds" placeholder="b5629b160f555ab4b08ef8e49568b7dd, a49f9b160f555vd4b08ef8e49568b7a2" value="${inputValue}" />
- <label for="discordToken">Discord token:</label>
- <span style="display:flex;">
- <input required id="discordToken" style="flex: 1 0 0;" placeholder="MTMkaJ9HshK2dAx.Fna4Jk.qsKrjSk42jKns9Js32-G3pnH_qcnIskQzy" value="${DISCORD_TOKEN ? DISCORD_TOKEN : ''}" />
- <button id="getDiscordToken" ${!!DISCORD_TOKEN ? "disabled" : null}>Get</button>
- </span>
- <button id="epixIdAddButton" type="submit">START</button>
- <span id="epixError" style="color: red; font-weight: bold;"></span>
- </form>
- `;
- document.body.appendChild(dialog);
-
- document.getElementById("getDiscordToken").addEventListener("click", (e) => {
- e.preventDefault();
-
- DISCORD_TOKEN = getDiscordToken();
- document.getElementById("discordToken").value = DISCORD_TOKEN;
- });
-
- document.getElementById("epixIdAddButton").addEventListener("click", (e) => {
- e.preventDefault();
- const idStr = document.getElementById("epixIds").value;
- const ids = idStr.split(',').reduce((acc, curr) => {
- const id = curr.replace(/\W/gi, '');
- if (!id) return acc;
- acc.push(id);
- return acc;
- }, []);
- EPIX_IDS = ids;
-
- if(!DISCORD_TOKEN || !EPIX_IDS?.length) {
- document.getElementById("epixError").innerHTML = "ERROR: missing Epix User ID(s) or Discord Token";
- return;
- }
-
- GM_setValue('epixIds', EPIX_IDS);
- dialog.parentNode.removeChild(dialog);
- resolve();
- }, false);
- });
- }
-
- function getEpixCodes(discordMessages) {
- const codes = discordMessages.reduce((acc, curr) => {
- const message = curr[0]
- const matches = message.content.matchAll(/epix-connect=([\w\d]{7})/ig);
-
- for (const match of matches) {
- if (match?.[1] && !EPIX_FRIENDS.includes(match[1])) {
- acc.push({code: match[1], messageId: message.id});
- }
- }
-
- return acc;
- }, [])
-
- return codes;
- }
-
-
- function updateFriends(status, code) {
- if (status === "CONNECTION_SUCCESSFUL" || status === "ALREADY_MATCHED" || status === "USER_NOT_FOUND") {
- EPIX_FRIENDS.push(code.code);
- GM_setValue('epixFriends', EPIX_FRIENDS);
- GM_setValue('discordMinId', code.messageId);
- }
-
- }
-
- /**
- * @param {string} [text] - Button text
- * @returns {null}
- */
- function disableButton(text = '') {
- EPIX_BUTTON.toggleAttribute('disabled', true);
- if (text) {
- updateButton(text);
- }
- }
-
- /**
- * @param {string} [text] - Button text
- * @returns {null}
- */
- function enableButton(text = '') {
- EPIX_BUTTON.toggleAttribute('disabled', false);
- if (text) {
- updateButton(text);
- }
- }
-
- /**
- * @param {string} [text] - Button text
- * @returns {null}
- */
- function updateButton(text = "Run Epix Friend Adder") {
- EPIX_BUTTON.innerHTML = text;
- }
-
-
- //--- Style our newly added elements using CSS.
- GM_addStyle (`
- #epixButton {
- margin-left: 10px;
- }
-
- #epixIdsDialog {
- position: absolute;
- top: 5rem;
- z-index: 100;
- }
- #epixIdsDialog ol {
- list-style: auto;
- padding-left: 35px;
- margin-bottom: 10px;
- }
- #epixIdsDialog input {
- width: 100%;
- }
- #epixIdsDialog button {
- margin: 5px auto;
- display: block;
- font-size: large;
- background: greenyellow;
- padding: 2px 10px;
- }
- `);
-
-
- async function connectRequest(userId, profileId) {
- return fetch("https://wfppjum4x2.execute-api.eu-central-1.amazonaws.com/production/connection-request", {
- "referrer": "https://www.gamescom.global/",
- "referrerPolicy": "strict-origin-when-cross-origin",
- "body": `{"userId":"${userId}","profileId":"${profileId}"}`,
- "method": "POST",
- "mode": "cors",
- "credentials": "omit"
- });
- }
-
-
- // A lot of this function was adapted from: https://github.com/victornpb/undiscord/blob/master/deleteDiscordMessages.user.js#L652-L712
- async function getDiscordMessages(minId) {
- const params = queryString([
- ['limit', 25],
- ['channel_id', '1259933715409145966'],
- ['min_id', minId],
- ['sort_by', 'timestamp'],
- ['sort_order', 'asc'],
- ['has','link'],
- ]);
- let resp;
- try {
- resp = await fetch(`https://discord.com/api/v9/guilds/574865170694799400/messages/search?${params}`, {
- "headers": {
- "accept": "*/*",
- "authorization": DISCORD_TOKEN
- },
- "referrer": "https://discord.com/channels/574865170694799400/1259933715409145966",
- "referrerPolicy": "strict-origin-when-cross-origin",
- "method": "GET",
- "mode": "cors",
- "credentials": "include"
- });
- } catch (err) {
- this.state.running = false;
- console.error('Search request threw an error:', err);
- disableButton("ERROR");
- throw err;
- }
-
- // not indexed yet
- if (resp.status === 202) {
- let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
- console.warn(`This channel isn't indexed yet. Waiting ${w}ms for discord to index it...`);
- await wait(w);
- return await getDiscordMessages(minId);
- }
-
- if (!resp.ok) {
- // searching messages too fast
- if (resp.status === 429) {
- let w = (await resp.json()).retry_after * 1000 || 30 * 1000;
- console.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`);
- console.warn(`Cooling down for ${w * 2}ms before retrying...`);
-
- await wait(w * 2);
- return await getDiscordMessages(minId);
- } else {
- console.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json());
- disableButton("ERROR");
- throw resp;
- }
- }
-
- const data = await resp.json();
- return data.messages;
- }
-
- function getDiscordToken() {
- window.dispatchEvent(new Event('beforeunload'));
- const LS = document.body.appendChild(document.createElement('iframe')).contentWindow.localStorage;
- try {
- return JSON.parse(LS.token);
- } catch {
- console.info('Could not automatically detect Authorization Token in local storage!');
- console.info('Attempting to grab token using webpack');
- 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();
- }
- }
-
-
- const wait = async ms => new Promise(done => setTimeout(done, ms));
- const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&');