// ==UserScript==
// @name Epix Auto Friend
// @namespace Violentmonkey Scripts
// @match https://discord.com/channels/574865170694799400/1259933715409145966*
// @inject-into content
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @version 1.1
// @author UpDownLeftDie
// @license MIT
// @description Automatically adds all the Epix friends links from the gamescom discord server
// ==/UserScript==
let EPIX_IDS = [];
let DISCORD_TOKEN = '';
let EPIX_FRIENDS = [];
let EPIX_BUTTON;
function main() {
EPIX_FRIENDS = [...new Set(GM_getValue('epixFriends') || [])];
EPIX_IDS = [...new Set(GM_getValue('epixIds') || [])];
DISCORD_TOKEN = localStorage.getItem("token")?.replaceAll('\"', '');
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();
EPIX_BUTTON.setAttribute('disabled', true);
EPIX_BUTTON.innerHTML = 'Running...';
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);
let json = await responses[0].json();
let status = json?.data?.status.toUpperCase();
if (!responses[0].ok) {
if (json?.message.toLowerCase() === "user not found") {
status = "USER_NOT_FOUND";
} else {
EPIX_BUTTON.innerHTML = "ERROR"
EPIX_BUTTON.setAttribute('disabled', true);
throw resp;
}
}
updateFriends(status, codes[i]);
}
if (messages.length > 0) {
GM_setValue('discordMinId', messages[messages.length - 1][0].id);
}
} while(messages.length >= 25);
EPIX_BUTTON.innerHTML = '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">
<input id="epixIds" placeholder="b5629b160f555ab4b08ef8e49568b7dd, a49f9b160f555vd4b08ef8e49568b7a2" value="${inputValue}" />
<button id="epixIdAddButton">START</button>
</form>
`;
document.body.appendChild(dialog);
document.getElementById("epixIdAddButton").addEventListener("click", ()=> {
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;
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 match = message.content.match(/epix-connect=([\w\d]{7})/i);
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);
}
}
//--- Style our newly added elements using CSS.
GM_addStyle (`
#epixButton {
margin-left: 10px;
}
#epixIdsDialog {
margin-top: 5rem;
}
#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);
EPIX_BUTTON.innerHTML = "ERROR"
EPIX_BUTTON.setAttribute('disabled', true);
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());
EPIX_BUTTON.innerHTML = "ERROR"
EPIX_BUTTON.setAttribute('disabled', true);
throw resp;
}
}
const data = await resp.json();
return data.messages;
}
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('&');
(new MutationObserver(check)).observe(document, {childList: true, subtree: true});
function check(changes, observer) {
if(document.querySelector('h1')) {
observer.disconnect();
main();
}
}