Greasy Fork

RoJoin

(WORKING AS OF 9/5/24) Allows you to join to any player in a Roblox game.

当前为 2024-09-08 提交的版本,查看 最新版本

// ==UserScript==
// @name         RoJoin
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  (WORKING AS OF 9/5/24) Allows you to join to any player in a Roblox game.
// @author       ryi3r
// @match        https://www.roblox.com/*
// @grant        none
// ==/UserScript==

"use strict";

const delay = ms => new Promise(res => setTimeout(res, ms));

const getJson = (url, args = {}) => {
    if (!args.headers)
        args.headers = {};
    return fetch(url, args)
        .then((r) => r.json())
        .catch((e) => console.log("error:", e));
};


const inst = document.getElementById("running-game-instances-container");
if (inst) {
    const div = document.createElement("div");
    div.classList = "section";

    const header = document.createElement("h2");
    header.innerText = "RsJoin 1.0";
    div.appendChild(header);

    const form = document.createElement("form");
    div.appendChild(form);

    const img = document.createElement("img");
    img.height = "40";
    img.display = "none";
    div.appendChild(img);

    const input = document.createElement("input");
    input.classList = "input-field";
    input.placeholder = "Username";
    input.addEventListener("input", (e) => {
        search.disabled = e.target.value.length === 0;
    });
    form.appendChild(input);

    const search = document.createElement("button");
    search.classList = "btn-primary-md";
    search.innerText = "Search";
    search.disabled = true;
    form.appendChild(search);


    const status = document.createElement("p");
    form.appendChild(status);

    const join = document.createElement("button");
    join.style.display = "none";
    join.innerText = "Join";
    join.classList = "btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width";
    div.appendChild(join);

    const copy = document.createElement("button");
    copy.style.display = "none";
    copy.innerText = "Copy Join Link";
    copy.classList = "btn-control-xs rg-btn btn-primary-md btn-min-width";
    div.appendChild(copy);

    inst.insertBefore(div, inst.firstChild);

    const placeID = location.href.match(/\d+/)[0];

    form.addEventListener("submit", async (event) => {
        event.preventDefault();

        join.style.display = "none";
        copy.style.display = "none";

        status.innerText = "Loading...";
        let userID;
        try {
            const profileData = await fetch(`https://www.roblox.com/users/profile?username=${input.value}`);
            if (!profileData.ok)
                throw `User named ${input.value} not found.`;
            userID = profileData.url.match(/\d+/)[0];
        }
        catch (e) {
            console.log("error:", e);
            status.innerText = "Requested user not found.";
            img.display = "none";
            return;
        }
        const userThumb = (await getJson(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userID}&format=Png&size=150x150`)).data[0].imageUrl;
        img.src = userThumb.split("/").slice(0, -1).join("/") + "/isCircular";
        img.display = "";

        let cursor = null;
        let found = false;

        let serversSearched = 0;
        let playersTotal = 0;
        let serversStatus = "";

        while (true) {
            let url = `https://games.roblox.com/v1/games/${placeID}/servers/0?limit=100`;
            if (cursor)
                url += "&cursor=" + cursor;
            const servers = await getJson(url).catch(() => null);

            if (servers !== undefined && servers.data !== undefined) {
                for (let i = 0; i < servers.data.length; i++) {
                    const place = servers.data[i];
                    if (!found) {
                        serversSearched++;
                        playersTotal += place.playerTokens.length;
                        serversStatus = `Servers searched: ${serversSearched} (through ${playersTotal} players.)`;
                        status.innerText = serversStatus;

                        if (place.playerTokens.length === 0)
                            return;

                        let data;
                        while (true) {
                            let body = [];

                            place.playerTokens.forEach((token) => {
                                body.push({
                                    requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
                                    type: "AvatarHeadShot",
                                    targetId: 0,
                                    token,
                                    format: "png",
                                    size: "150x150",
                                });
                            });
                            try {

                                data = await getJson("https://thumbnails.roblox.com/v1/batch", {
                                    method: "POST",
                                    headers: {
                                        "Content-Type": "application/json",
                                        Accept: "application/json",
                                    },
                                    body: JSON.stringify(body),
                                });
                                break;
                            } catch (e) {
                                console.log("Error:", e);
                                await delay(1000);
                            }
                        }
                        if (!data.data)
                            status.innerText = "Error: " + data;
                        else {
                            for (let k = 0; k < data.data.length; k++) {
                                const thumb = data.data[k];
                                if (thumb && thumb.imageUrl === userThumb) {
                                    found = true;

                                    status.innerText = `User found.\n${serversStatus}\n${place.playing} of ${place.maxPlayers} people max`;
                                    join.style.display = "";
                                    join.onclick = () => {
                                        window.Roblox.GameLauncher.joinGameInstance(
                                            placeID,
                                            place.id,
                                        );
                                    };
                                    copy.style.display = "";
                                    copy.onclick = () => {
                                        copy.innerText = "Copied!";
                                        navigator.clipboard.writeText(`https://www.roblox.com/games/start?placeId=${placeID}&gameId=${place.id}`).finally(() => {
                                            delay(1000).then(() => {
                                                copy.innerText = "Copy Join Link";
                                            });
                                        });
                                    };
                                    break;
                                }
                            }
                        }
                    } else {
                        break;
                    }
                }

                cursor = servers.nextPageCursor;
                if (!cursor || found)
                    break;
                else {
                    let amount = 10;
                    while (amount > 0) {
                        let secDef = "seconds";
                        if (amount === 1)
                            secDef = "second";
                        status.innerText = `Waiting ${amount} ${secDef}...\n${serversStatus}`;
                        amount--;
                        await delay(1000);
                    }
                    status.innerText = `Waiting 0 seconds...\n${serversStatus}`;
                }
            }
        }

        if (!found)
            status.innerText = `User not found on the checked servers.\n${serversStatus}`;
    });
}