Greasy Fork

RoSniper

(WORKING AS OF 9/5/24) The only working stream sniper script without using exploits.

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

// ==UserScript==
// @name         RoSniper
// @namespace    http://tampermonkey.net/
// @version      3.1.3
// @description  (WORKING AS OF 9/5/24) The only working stream sniper script without using exploits.
// @author       ryi3r
// @match        https://www.roblox.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

// Psst... if you encounter any issues, talk to me on Discord: `ryi3r`!

(function () {
  "use strict";

  const getJSON = (url, args = {}) => {
    args.headers = args.headers || {};
    return fetch(url, args)
      .then((r) => r.json())
      .catch((e) => console.log(e));
  };

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

  const search = async (placeId, name, setStatus, cb, setThumb) => {
    setStatus("Loading...");
    let userID = 0;
    try {
      userID = await getUserId(name);
    }
    catch (e) {
      console.log("error:", e);
      setStatus("Requested user doesn't exist.");
      return;
    }
    const thumbUrl = await getThumb(userID);
    //console.log(thumbUrl);
    setThumb(thumbUrl.replace("/noFilter", "/isCircular"));
    let cursor = null;
    let found = false;

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

    const nextThumbChunk = async (chunk, place) => {
      if (found || chunk.length === 0) return;

      //console.log("pre-mapping:", chunk);
      //console.log("place:", place);
      let data;
      while (true) {
        try {
          data = await fetchThumbs(chunk);
          break;
        } catch (e) {
          console.log("error:", e);
          await delay(1000);
        }
      }
      //console.log("thumbs:", data);
      let serverThumbs = data.data;
      if (!serverThumbs)
        setStatus("error: " + serverThumbs);
      else {
        //console.log(serverThumbs);
        for (let k = 0; k < serverThumbs.length; k++) {
          const thumb = serverThumbs[k];
          //console.log(thumb);
          if (thumb && thumb.imageUrl === thumbUrl) {
            found = true;

            //const thumbToken = thumb.requestId.split(":")[1];
            cb({
              found: true,
              place: place,
              serversStatus,
            });
            //console.log("found at index", k, "and got place", place);
            return;
          }
        }
      }
    };

    while (true) {
      const servers = await getServer(placeId, cursor);

      if (servers !== undefined && servers.data !== undefined) {
        //console.log(servers);
        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.)`;
            setStatus(serversStatus);
            await nextThumbChunk(place.playerTokens, place);
          } else {
            break;
          }
        }

        cursor = servers.nextPageCursor;
        if (!cursor || found)
          break;

      } else {
        let amount = 10;
        while (amount > 0) {
          let secDef = "seconds";
          if (amount === 1) {
            secDef = "second";
          }
          setStatus(`Waiting ${amount} ${secDef}...\n${serversStatus}`);
          amount -= 1;
          await delay(1000);
        }
        setStatus(`Waiting 0 seconds...\n${serversStatus}`);
      }
    }

    if (!found) {
      cb({ found: false, serversStatus });
    }

  };

  const getUserId = (name) =>
    fetch("https://www.roblox.com/users/profile?username=" + name).then((r) => {
      if (!r.ok) throw "User not found.";
      return r.url.match(/\d+/)[0];
    });

  const getThumb = (id) =>
    getJSON(
      `https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${id}&format=Png&size=150x150`
    ).then((d) => {
      //console.log(d);
      return d.data[0].imageUrl;
    });

  const getServer = (placeId, cursor) => {
    let url = `https://games.roblox.com/v1/games/${placeId}/servers/0?limit=100`;

    if (cursor) url += "&cursor=" + cursor;
    return getJSON(url).catch(() => null);
  };

  const fetchThumbs = async (tokens) => {
    let body = [];

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

    return getJSON("https://thumbnails.roblox.com/v1/batch", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(body),
    });
  };

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

    const headerText = document.createElement("h2");
    headerText.innerText = "RoSniper 3.1";
    containerHeader.appendChild(headerText);

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

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

    containerHeader.appendChild(thumbImage);
    form.appendChild(document.createElement("br"));

    const usernameInput = document.createElement("input");
    usernameInput.classList = "input-field";
    usernameInput.placeholder = "Username";
    form.appendChild(usernameInput);

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

    usernameInput.addEventListener("input", (e) => {
      submitButton.disabled = e.target.value.length === 0;
    });

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

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

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

    containerHeader.appendChild(form);
    containerHeader.appendChild(joinBtn);
    containerHeader.appendChild(copyBtn);
    instancesContainer.insertBefore(
      containerHeader,
      instancesContainer.firstChild
    );

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

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

      joinBtn.style.display = "none";
      copyBtn.style.display = "none";

      search(
        placeId,
        usernameInput.value,
        (txt) => {
          statusText.innerText = txt;
        },
        (place) => {
          console.log(place);
          if (!place.found) {
            statusText.innerText = "User not found in the servers.\n" + place.serversStatus;
            return;
          }
          statusText.innerText = `User found.\n${place.serversStatus}\n${place.place.playing} of ${place.place.maxPlayers} people max`;

          joinBtn.style.display = "";
          joinBtn.onclick = () => {
            window.Roblox.GameLauncher.joinGameInstance(
              placeId,
              place.place.id
            );
          };
          copyBtn.style.display = "";
          copyBtn.onclick = () => {
            copyBtn.innerText = "Copied!";
            navigator.clipboard.writeText(`https://www.roblox.com/games/start?placeId=${placeId}&gameId=${place.place.id}`).finally(() => {
              delay(1000).then(() => {
                copyBtn.innerText = "Copy Join Link";
              });
            });
          };
        },
        (src) => {
           thumbImage.src = src;
           thumbImage.display = "";
        }
      );
    });
  }
})();