您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Join roblox job ids, games and private servers easily
当前为
// ==UserScript== // @name EasyJoin // @namespace https://mstudio45.com/ // @version 1.2 // @description Join roblox job ids, games and private servers easily // @author mstudio45 // @license MPL-2.0 // @match https://www.roblox.com/* // @match https://web.roblox.com/* // @match https://roblox.com/* // @icon https://roblox.com/favicon.ico // @grant none // ==/UserScript== // DO NOT CHANGE ANYTHING BELOW // const Roblox = globalThis.Roblox // this is useless but i want to remove stupid warnings const jQuery = globalThis.jQuery // this is useless but i want to remove stupid warnings const DataVersion = "v1" const VERSION = "v1.1" const pageUrl = window.location.href; const pageInfo = { url: pageUrl, getPageName: function() { if (pageUrl.indexOf("roblox.com/games/") !== -1 || pageUrl.indexOf("roblox.com/private-server/") !== -1) return "games" if (pageUrl.indexOf("roblox.com/home")) return "home"; if (pageUrl.indexOf("roblox.com/catalog")) return "catalog"; return pageUrl.split("roblox.com/")[1]; } } const menu = [document.getElementsByTagName('ul')[0], document.querySelector("#header > div > ul.nav.rbx-navbar.hidden-md.hidden-lg.col-xs-12")] const html = ` <style> .privservercontainer { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr; } .privservercontainer::after, .privservercontainer::before { display:none !important; } .contentfix .modal-content { width: max-content; } .mstudio45JoinerMenuBody > li::hover { background: #191b1d; } </style> <li class='cursor-pointer bottomTooltip'> <a id="mstudio45JoinerMenuButton" style="color: green;" style="display: block;" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.open()'>EasyJoin</a> </li> <body> <div id="mstudio45JoinerMenuBody" style="display: none; margin: 0 auto; min-width: 200px; z-index: 10; background: #393b3d; border: solid #111214;"> <!-- Only visible on the games page. ↓ --> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"><a style="color:gray;" class='font-header-2 nav-menu-title text-header'>Place:</a></li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(1)'>Job ID</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(99)'>Most Players</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(98)'>Least Players</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"><a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header'></a></li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"><a style="color:gray;" class='font-header-2 nav-menu-title text-header'>Private Servers:</a></li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(2)'>Code</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(3)'>Share URL Code</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(4)'>By User ID</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(5)'>By Username</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(6)'>By Display Name</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"><a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header'></a></li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a id="mstudio45JoinerDropdownMenuButton" style="color:yellow;" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(101)'>Reload private servers</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a id="mstudio45JoinerDropdownMenuButton" style="color:yellow;" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.cache.clear("all")'>Clear Cache</a> </li> <li class="cursor-pointer only-gamespage" style="margin: 0 auto;"> <a id="mstudio45JoinerDropdownMenuButton" style="color:yellow;" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.cache.clear("servers")'>Clear Saved Servers</a> </li> <!-- Visible all the time excpet on the games page. ↓ --> <li class="cursor-pointer except-gamespage" style="margin: 0 auto;"> <a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.setup(7)'>Join a Friend</a> </li> <!-- Visible all the time. ↓ --> <li class="cursor-pointer" style="margin: 0 auto;"><a style="/*color:green;*/" class='font-header-2 nav-menu-title text-header'></a></li> <li class="cursor-pointer" style="margin: 0 auto;"> <a id="mstudio45JoinerDropdownMenuButton" style="color:red;" class='font-header-2 nav-menu-title text-header' onclick='window.mstudio45.toggle()'>Close</a> </li> </div> </body> ` // Required Functions // function randomString(length) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; let counter = 0; while (counter < length) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); counter += 1; } return result; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Roblox Functions // function generateProtocol(placeId = null, jobId = null, privServerId = null) { if (!placeId) return null; let protocol = "roblox://placeID=" + String(placeId); if (jobId && privServerId) { protocol += "&linkCode=" + String(privServerId); } else if (jobId && !privServerId) { protocol += "&gameInstanceId=" + jobId; } return protocol; } function join(placeId = null, jobId = null, privServerId = null, followUserId = null) { if (followUserId && !jobId && !privServerId) { // only works when placeId and followUserId is provided (placeId is not required either) window.mstudio45.close(); Roblox.Dialog.close(); Roblox.GameLauncher.followPlayerIntoGame(followUserId); return; } if (!placeId) return null; if (privServerId) { window.mstudio45.close(); Roblox.Dialog.close(); Roblox.GameLauncher.joinPrivateGame(placeId, privServerId, null); return; } if (jobId) { window.mstudio45.close(); Roblox.Dialog.close(); Roblox.GameLauncher.joinGameInstance(placeId, jobId); return; } Roblox.GameLauncher.joinGameInstance(placeId, null); } async function getAvatars(userIds) { const avatars = window.mstudio45.cache.get("avatars", true) || {} userIds = userIds.filter((id) => id in avatars ? avatars[id] == "placeholder" : true); try { for (let i = 0; i < userIds.length; i += 99) { const chunk = userIds.slice(i, i + 99); const img = await fetch("https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=" + chunk.join(",") + "&size=150x150&format=png", { method: 'GET', headers: { 'Content-Type': 'application/json' } }).then(r => r.json()).then((json) => { return json.data }) img.forEach((x) => { avatars["" + x.targetId.toString()] = x.imageUrl || "placeholder" }); } } catch (e) { window.mstudio45.console.log(e) userIds.forEach((x) => { avatars["" + x.toString()] = "placeholder" }); } window.mstudio45.cache.set("avatars", avatars, true) } async function getServer(placeId, ownerId, type = 1, cursor = "") { // 1 = id, 2 = name, 3 = display name window.mstudio45.close(); window.mstudio45.modals.loading("Trying to find the Private server(s)..."); ownerId = ownerId == null ? "" : ownerId; const servers = window.mstudio45.cache.get("servers", true, true) || {}, privateServerData = window.mstudio45.cache.get("privateServer", true, true) || {}, serverNameForInfo = DataVersion + "-" + ownerId.toString() + "-" + type.toString() + "-" + placeId.toString(), privateServerNameForInfo = DataVersion + "-" + placeId.toString(); let finished = false, doLoop = false, reloadServerInfo = false; if (type == true && ownerId == "") { delete privateServerData[privateServerNameForInfo]; delete servers[serverNameForInfo]; doLoop = true; reloadServerInfo = true; } // Get Cache if (privateServerNameForInfo in privateServerData ? privateServerData[privateServerNameForInfo].length >= 1 : false) { window.mstudio45.console.log("Found '" + privateServerNameForInfo + "' private servers in cache!") } else { doLoop = true; reloadServerInfo = true; privateServerData[privateServerNameForInfo] = [] } if (serverNameForInfo in servers ? servers[serverNameForInfo].length >= 1 : false) { window.mstudio45.console.log("Found '" + serverNameForInfo + "' in cache!") } else { reloadServerInfo = true; servers[serverNameForInfo] = []; } // Main function loop(placeId, ownerId, type, cursor) { window.mstudio45.modals.loading("Loading Private servers for this place... (" + privateServerData[privateServerNameForInfo].length + ")"); fetch("https://games.roblox.com/v1/games/" + placeId + "/private-servers?cursor=" + cursor + "&sortOrder=Desc&excludeFullGames=false", { method: 'GET', mode: "cors", credentials: "include", headers: { 'Content-Type': 'application/json', "Cookie": document.cookie, } }).then(r => r.json()).then((json) => { json.data.forEach((x) => privateServerData[privateServerNameForInfo].push(x)); if (json.nextPageCursor == null) { finished = true; } else { setTimeout(function() { loop(placeId, ownerId, type, json.nextPageCursor) }, 1500) } }); } try { if (doLoop) { loop(placeId, ownerId, type, "") while (finished == false) await sleep(500); } if (reloadServerInfo) { // Find Private Server(s) privateServerData[privateServerNameForInfo].forEach((x) => { let check = x.owner.id; switch (type) { case 3: check = x.owner.displayName; break; case 2: check = x.owner.name; break; case 1: default: check = x.owner.id; break } if (type == 1 ? check == ownerId : check.toString().toLowerCase().indexOf(ownerId.toString().toLowerCase()) !== -1) { servers[serverNameForInfo].push({ callbackName: x.vipServerId.toString(), name: x.owner.name, displayName: x.owner.displayName, id: x.owner.id, accessCode: x.accessCode, placeId: placeId }); } }) } if (type == true && ownerId == "") { setTimeout(function() { window.mstudio45.modals.info("Private Server successfully reloaded for this place.") }, 250) } else { // Load Modal if (servers[serverNameForInfo].length <= 0) { delete servers[serverNameForInfo]; setTimeout(function() { window.mstudio45.modals.error("Couldn't find any Private Server for this place.") }, 250) } else if (servers[serverNameForInfo].length >= 1) { await getAvatars(servers[serverNameForInfo].map((x) => x.id)); window.mstudio45.modals.servers("Found Private servers. (" + servers[serverNameForInfo].length + "/" + privateServerData[privateServerNameForInfo].length + ")", servers[serverNameForInfo]) } } window.mstudio45.cache.set("servers", servers, true); window.mstudio45.cache.set("privateServer", privateServerData, true); } catch (e) { window.mstudio45.modals.error("Couldn't find any Private Server for this place or something has failed."); window.mstudio45.console.warn("[EasyJoin]", e); } } // Main Code // window.mstudio45 = { // Data servers: {}, toggled: false, cache: { get: function(name, isJson, clearOldVersionData) { let data = localStorage.getItem("EJ_" + name); if (data) { if (isJson) { data = JSON.parse(data); if (clearOldVersionData) { // only works if the index starts with the version (V1-...) for (var i in data) { if (!i.toString().startsWith(DataVersion + "-")) { delete data[i]; } } } } else { if (!data.startsWith(DataVersion + "-")) { data = null; } } } return data ? data : null; }, set: function(name, data, isJson) { localStorage.setItem("EJ_" + name, isJson ? JSON.stringify(data) : data); }, clear: function(name = "all") { var keys = Object.keys(localStorage), i = 0, key; for (; key = keys[i]; i++) { if (name == "all" ? key.startsWith("EJ_") : key == "EJ_" + name) localStorage.removeItem(key); } window.mstudio45.modals.info("The specified cache has been cleared successfully.") }, }, // Modals modals: { info: function(message) { window.mstudio45.console.log(message) Roblox.Dialog.open({ titleText: "EasyJoin", bodyContent: '<p class="text-center">' + message + '</p>', acceptText: "Ok", showDecline: false, xToCancel: true, allowHtmlContentInBody: true }); }, loading: function(message) { window.mstudio45.console.log("Loading:", message) Roblox.Dialog.open({ titleText: "EasyJoin", bodyContent: '<span class="spinner spinner-default"></span><p class="text-center">' + message + '</p>', showAccept: false, showDecline: false, xToCancel: true, allowHtmlContentInBody: true }); }, error: function(message) { window.mstudio45.console.warn("Error:", message) Roblox.Dialog.open({ titleText: "EasyJoin", bodyContent: '<p class="text-error" style="font-size: 16px;">' + message + '</p>', acceptText: "Ok", showDecline: false, xToCancel: true, allowHtmlContentInBody: true }); }, input: function(placeholder, footer, btnText, callback) { const modalName = randomString(10); Roblox.Dialog.close(); setTimeout(function() { Roblox.Dialog.open({ titleText: "EasyJoin", bodyContent: "<input type='text' class='form-control input-field' placeholder='" + placeholder + "' id='" + modalName + "-text'/>", footerText: footer, acceptText: btnText, showDecline: false, xToCancel: true, allowHtmlContentInBody: true, fieldValidationRequired: true, onAccept: function() { var validationText = jQuery("#" + modalName + "-text"); if (validationText && validationText.val()) { callback(true, validationText.val()) } else { callback(false, null) } }, onDecline: function() { callback(false, null) }, onCancel: function() { callback(false, null) } }); }, 10); }, servers: async function(message, servers) { const avatars = window.mstudio45.cache.get("avatars", true) || {} Roblox.Dialog.close(); await sleep(10); Roblox.Dialog.open({ titleText: "EasyJoin", bodyContent: '<p class="text-center">' + message + '</p><br></br><ul class="hlist game-cards privservercontainer">' + servers.map((item) => { if (item.name && item.id && item.callbackName) { window.mstudio45.servers[item.callbackName] = function() { Roblox.Dialog.close(); join(item.placeId, null, item.accessCode); } const img = avatars["" + item.id]; const html = `<li class="list-item game-card game-tile"> <div class="game-card-container"> <div class="game-card-link"> <a href="https://www.roblox.com/users/${item.id}/profile" class="game-card-link" alt="${item.displayName} (${item.name})" title="${item.displayName} (${item.name})"> <div class="game-card-thumb-container"> ${img == "placeholder" ? '<span class="avatar-card-image icon-placeholder-avatar-headshot"></span>' : '<span class="avatar-card-image"><img class="" src="' + img + '" alt="" title=""></span>'} </div> <div class="game-card-name game-name-title" ng-non-bindable="">${item.displayName} (${item.name})</div> </a> <div class="game-card-info" style="position: absolute;bottom: 0;height: 15%;"> <button type="button" class="info-label btn-common-play-game-lg btn-primary-md btn-full-width" style=" height: 100%; display: flex; align-items: center; justify-content: center;" onclick='window.mstudio45.servers[${item.callbackName}]()'> <span class="icon-common-play" style="text-align: center; transform: scale(0.6);"></span> </button> </div> </div> </div> </li>` return html; } return ""; }).join('') + '</ul>', showAccept: false, showDecline: false, xToCancel: true, allowHtmlContentInBody: true, cssClass: "contentfix" }); while (document.querySelector(".contentfix") == null) await sleep(150); const contentfix = document.querySelector(".contentfix"); // i love roblox contentfix.parentElement.style.overflow = "" contentfix.parentElement.parentElement.style = "z-index: 1942; position: absolute; transform: translate(-60%, 35%);" window.dispatchEvent(new Event('resize')); contentfix.parentElement.parentElement.style.transform = "translate(-60%, 50%);" window.dispatchEvent(new Event('resize')); } }, // Console console: { log: function() { console.log("[EasyJoin]", ...arguments) }, warn: function() { console.warn("[EasyJoin]", ...arguments) }, error: function() { console.error("[EasyJoin]", ...arguments) }, }, // Custom functions update: function() { document.querySelectorAll("#mstudio45JoinerMenuButton").forEach(x => { x.style.display = window.mstudio45.toggled === false ? "block" : "none"; }) document.querySelectorAll("#mstudio45JoinerMenuBody").forEach(x => { x.style.display = window.mstudio45.toggled === false ? "none" : "block"; }) }, toggle: function() { window.mstudio45.toggled = !window.mstudio45.toggled window.mstudio45.update(); }, close: function() { window.mstudio45.toggled = false window.mstudio45.update(); }, open: function() { window.mstudio45.toggled = true window.mstudio45.update(); }, setup: async function(type = 1, customPlaceId = false) { // 1 = jobid, 2 = priv, 3 = least plrs, 4 = most plrs let placeId = pageInfo.getPageName() == "games" ? window.location.pathname.match(/\/(\d+)\/.+?$/)[1] : -1; if (placeId == -1 && customPlaceId) { window.mstudio45.modals.input("Input the Place ID here.", "", "Ok", async function(suc, text) { if (suc && text) { if (!parseInt(text)) { Roblox.Dialog.close(); await sleep(10); window.mstudio45.modals.error("Invalid Place ID.") return; } placeId = parseInt(text); } }); while (placeId == -1) await sleep(150); if (placeId <= -1) return window.mstudio45.console.warn("Action stopped"); } if (type == 1) { window.mstudio45.modals.input("Input the Job ID here.", "", "Ok", function(suc, text) { if (suc && text) join(placeId, text); }); return; } // Private Server if (type == 2) { window.mstudio45.modals.input("Input the Private Server Code here.", "", "Ok", function(suc, text) { if (suc && text) join(placeId, null, text); }); return; } if (type == 3) { window.mstudio45.modals.input("Input the Private Server Share Code here.", "", "Ok", function(suc, text) { if (suc && text) window.location.href = "https://roblox.com/share?code=" + text + "&type=Server"; }); return; } if (type == 4) { window.mstudio45.modals.input("Input the User ID here.", "", "Ok", async function(suc, text) { if (suc && text) { if (!parseInt(text)) { Roblox.Dialog.close(); await sleep(10); window.mstudio45.modals.error("Invalid User ID.") return; } await getServer(placeId, parseInt(text), 1) } }); return; } if (type == 5) { window.mstudio45.modals.input("Input the Username here.", "", "Ok", async function(suc, text) { if (suc && text) await getServer(placeId, text, 2) }); return; } if (type == 6) { window.mstudio45.modals.input("Input the Display Name here.", "", "Ok", async function(suc, text) { if (suc && text) await getServer(placeId, text, 3) }); return; } // Follow User if (type == 7) { window.mstudio45.modals.input("Input the User ID here.", "", "Ok", async function(suc, text) { if (suc && text) { if (!parseInt(text)) { await sleep(10); window.mstudio45.modals.error("Invalid User ID.") return; } await join(placeId, null, null, parseInt(text)) } }); return; } // API if (type == 98) { fetch("https://games.roblox.com/v1/games/" + placeId + "/servers/Public?sortOrder=Asc&excludeFullGames=true&limit=10&cursor=", { method: 'GET', headers: { 'Content-Type': 'application/json'} }).then(r => r.json()).then((json) =>{ join(placeId, json.data[0].id) }); return; } if (type == 99) { fetch("https://games.roblox.com/v1/games/" + placeId + "/servers/Public?sortOrder=Desc&excludeFullGames=true&limit=10&cursor=", { method: 'GET', headers: { 'Content-Type': 'application/json'} }).then(r => r.json()).then((json) =>{ join(placeId, json.data[0].id) }); return; } if (type == 101) { getServer(placeId, null, true) return; } }, } // Insert HTML menu.forEach(e => { e.innerHTML += html }); document.querySelectorAll("#mstudio45JoinerMenuBody").forEach((el) => { el.children.forEach((el) => { if (el.className.indexOf("only-gamespage") !== -1) el.style.display = pageInfo.getPageName() == "games" ? "" : "none"; else if (el.className.indexOf("except-gamespage") !== -1) el.style.display = pageInfo.getPageName() == "games" ? "none" : ""; }); }); window.mstudio45.console.log("Initialized! (" + VERSION + ") - Created by mstudio45");
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址