Epix Auto Friend

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

13.08.2024 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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.2
// @author      UpDownLeftDie
// @contributionURL https://www.patreon.com/camkitties
// @supportURL https://discord.gg/hWvWGUDf

// @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 = 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();

  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">
        <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 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 {
    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);
    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;
}

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('&');

(new MutationObserver(check)).observe(document, {childList: true, subtree: true});
function check(changes, observer) {
  if(document.querySelector('h1')) {
    observer.disconnect();
    main();
  }
}