Epix Auto Friend

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

目前为 2024-08-13 提交的版本。查看 最新版本

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