friend-connect
Version:
**BEFORE YOU USE THIS TOOL, PLEASE READ THE FOLLOWING: WE _AS CONTRIBUTORS_ ARE NOT RESPONSIBLE FOR ANY DAMAGE OR LOSS CAUSED BY THIS APP. USE AN ALT ACCOUNT, JUST IN CASE THERE IS AN ISSUE WITH THIS METHOD.**
466 lines (465 loc) • 23.9 kB
JavaScript
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _Session_sessionInstance, _Session_friendXuids, _Session_accountXuids, _Session_accountsInitialized, _Session_accountsWithNoAchievements, _Session_ip, _Session_port;
import { EventEmitter } from "events";
import fs from "fs";
import { ping } from "bedrock-protocol";
import authPkg from "prismarine-auth";
const { Titles } = authPkg;
import { config } from "dotenv";
config();
const Constants = {
SERVICE_CONFIG_ID: "4fc10100-5f7a-4470-899b-280835760c07",
CLIENT_ID: "00000000441cc96b", // Nintendo Switch Title ID
};
const debug = (...args) => {
if (process.env.FRIEND_CONNECT_DEBUG) {
for (let i of args) {
try {
JSON.stringify(JSON.parse(args[i]), undefined, 4);
}
catch (e) {
console.log(i);
}
}
}
};
import { XboxClient, LinkCodeTokenProvider } from "./xbox";
import { RTAMultiplayerSession } from "./xbox/modules/rta";
//TODO add events to the social class the emits friendAdded on response so there is no duplicateEvents same for other events. also clean up the code for the xbox live client so it returns the values instead of a response
class Session extends EventEmitter {
constructor(options) {
super();
this.xboxAccounts = new Map();
_Session_sessionInstance.set(this, void 0);
_Session_friendXuids.set(this, new Set());
this.additionalOptions = {};
_Session_accountXuids.set(this, new Set());
this.fullOfFriends = new Set();
_Session_accountsInitialized.set(this, 0);
_Session_accountsWithNoAchievements.set(this, 0);
this.started = false;
_Session_ip.set(this, void 0);
_Session_port.set(this, void 0);
if (!options.accounts)
throw new Error("`accounts` is required in Session()");
if (!options.connectionType)
throw new Error("`connectionType` is required in Session()");
if (!options.ip)
throw new Error("`ip` is required in Session()");
if (!options.port)
throw new Error("`port` is required in Session()");
if (!options.tokenPath)
options.tokenPath = "./auth/";
if (!options.hostName)
options.hostName = "";
if (!options.worldName)
options.worldName = "";
if (options.log)
this.additionalOptions.log = true;
options.joinability = options.joinability || "joinable_by_friends";
if (options.pingServerForInfo)
this.additionalOptions.pingServerForInfo = true;
if (options.autoFriending)
this.additionalOptions.autoFriending = true;
this.additionalOptions.constants =
options.constants || {};
this.getAdvertisement();
if (!/.[\/]?[a-zA-Z0-9]+[\/]?/.test(options.tokenPath))
throw new Error("`tokenPath` is invalid, use ./auth/ for example");
this.initializeAccounts(options.accounts, options.tokenPath);
this.on("accountInitialized", () => {
if (__classPrivateFieldGet(this, _Session_accountsInitialized, "f") >= options.accounts.length)
this.checkAchievements(this.xboxAccounts.values());
});
this.on("achievementChecked", () => {
if (__classPrivateFieldGet(this, _Session_accountsWithNoAchievements, "f") >= options.accounts.length)
this.emit("accountsDoNotHaveAchievements");
//debug(this.accountsWithNoAchievements);
});
this.on("accountsDoNotHaveAchievements", () => {
this.duplicateFriendCheck(() => {
this.emit("gotFriends?");
});
});
this.on("gotFriends?", () => {
if (__classPrivateFieldGet(this, _Session_sessionInstance, "f"))
return void 0;
if (this.additionalOptions.autoFriending)
this.setFriendInterval(this.xboxAccounts.values());
if (this.xboxAccounts.size != 1) {
setInterval(() => {
if (!this.doingAutoFriendInterval)
try {
this.duplicateFriendCheck();
}
catch (error) {
this.errorHandling(error, undefined, "Friend Duplicate Checking");
}
}, 1800000);
}
for (const i of this.xboxAccounts.values()) {
if (!this.hostAccount)
this.hostAccount = i;
for (let j of this.xboxAccounts.values()) {
//debug(i.email, j.email);
if (i != j)
i.social.addFriend(j.token.userXuid).then(async (res) => {
debug(res.status);
__classPrivateFieldGet(this, _Session_friendXuids, "f").add(j.token.userXuid);
debug(await res.text());
});
}
}
const log = (...message) => {
if (this.additionalOptions.log)
console.log(`[FriendConnect ${this.hostAccount.xuid}]`, ...message, "\n");
};
log("Starting...");
//debug("accountsDoNotHaveAchievements");
this.minecraftLobbyCustomOptions =
this.createMinecraftLobbyCustomProperties(this.hostAccount, options);
setInterval(() => {
if (this.additionalOptions.pingServerForInfo)
this.getAdvertisement();
}, 15000);
//debug(request);
__classPrivateFieldSet(this, _Session_sessionInstance, new RTAMultiplayerSession(this.hostAccount,
//@ts-ignore
{
properties: {
system: {
joinRestriction: "followed",
readRestriction: "followed",
closed: false,
},
custom: this.minecraftLobbyCustomOptions,
},
}, Constants.SERVICE_CONFIG_ID, "MinecraftLobby", true, session => {
session.multiplayerSessionRequest.properties.custom =
this.minecraftLobbyCustomOptions;
}), "f");
__classPrivateFieldGet(this, _Session_sessionInstance, "f").on("message", (event) => {
log(event);
});
__classPrivateFieldGet(this, _Session_sessionInstance, "f").on("open", () => {
log("Connected to RTA Websocket");
this.started = true;
});
__classPrivateFieldGet(this, _Session_sessionInstance, "f").on("close", (event) => {
log(event.code);
log(event.reason);
log(event.wasClean);
log("Restarting...");
this.started = false;
});
__classPrivateFieldGet(this, _Session_sessionInstance, "f").on("error", (error) => {
this.errorHandling(error, this.hostAccount.xuid, "RTA Websocket");
log("Restarting...");
this.started = false;
});
let firstResponse = true;
__classPrivateFieldGet(this, _Session_sessionInstance, "f").on("sessionResponse", session => {
if (firstResponse) {
for (let i of this.xboxAccounts.values()) {
if (i == this.hostAccount)
continue;
__classPrivateFieldGet(this, _Session_sessionInstance, "f").join(i);
firstResponse = false;
}
}
if (Object.keys(session.members).length > 35) {
for (let i in session.members) {
let member = session.members[i];
console.log(session.members[i]);
if (__classPrivateFieldGet(this, _Session_accountXuids, "f").has(session.members[i].constants.system.xuid))
continue;
if (Date.now() + 300000 - Date.parse(member.joinTime) <
0)
this.hostAccount.sessionDirectory.removeMember(__classPrivateFieldGet(this, _Session_sessionInstance, "f").serviceConfigId, __classPrivateFieldGet(this, _Session_sessionInstance, "f").sessionTemplateName, __classPrivateFieldGet(this, _Session_sessionInstance, "f").sessionName, parseInt(i));
}
}
});
});
}
get sessionInstance() {
return __classPrivateFieldGet(this, _Session_sessionInstance, "f");
}
get friendXuids() {
return __classPrivateFieldGet(this, _Session_friendXuids, "f").values();
}
get accountXuids() {
return __classPrivateFieldGet(this, _Session_accountXuids, "f").values();
}
duplicateFriendCheck(callback) {
this.doingDuplicateFriendCheck = true;
let friendsGot = 0;
let friendXuids = new Map();
for (let account of this.xboxAccounts.values()) {
if (this.additionalOptions.log)
console.log(`[FriendConnect ${account.xuid}] Duplicate Friend Check Interval`);
__classPrivateFieldGet(this, _Session_accountXuids, "f").add(account.xuid);
account.social.getFriends().then(friendsList => {
friendXuids.set(account.xuid, new Set());
if (friendsList.totalCount == 1000) {
this.fullOfFriends.add(account.xuid);
return void 0;
}
else if (this.fullOfFriends.has(account.xuid)) {
this.fullOfFriends.delete(account.xuid);
}
for (let person of friendsList.people) {
if (!person.isFollowingCaller) {
account.social.removeFriend(person.xuid);
__classPrivateFieldGet(this, _Session_friendXuids, "f").delete(person.xuid);
if (this.additionalOptions.log)
console.log(`[FriendConnect ${account.xuid}] Removed Friend ${person.xuid}`);
this.emit("friendRemoved", {
account,
xuid: person.xuid,
});
}
else {
friendXuids.get(account.xuid).add(person.xuid);
__classPrivateFieldGet(this, _Session_friendXuids, "f").add(person.xuid);
}
}
friendsGot++;
this.emit("doFriendCheck");
});
} //xuid, Record<string,boolean>
this.on("doFriendCheck", () => {
let duplicateMap = new Map();
if (friendsGot == this.xboxAccounts.size) {
for (let email1 of friendXuids.keys()) {
let array1 = friendXuids.get(email1);
for (let email2 of friendXuids.keys()) {
let array2 = friendXuids.get(email2);
for (let xuid of array2) {
if (array1.has(xuid)) {
let dupEmailRecord = duplicateMap.get(xuid);
if (!dupEmailRecord)
dupEmailRecord = new Set();
dupEmailRecord.add(email1);
dupEmailRecord.add(email2);
}
}
}
}
for (let personXuid of duplicateMap.keys()) {
let emailSet = duplicateMap.get(personXuid);
let skipEmail = true;
for (let j of emailSet) {
if (skipEmail) {
skipEmail = false;
continue;
}
let account = this.xboxAccounts.get(j);
if (!__classPrivateFieldGet(this, _Session_accountXuids, "f").has(personXuid)) {
account.social.removeFriend(personXuid);
__classPrivateFieldGet(this, _Session_friendXuids, "f").delete(personXuid);
console.log(`[FriendConnect ${account.xuid}] Removed Friend ${personXuid}`);
this.emit("friendRemoved", {
account,
person: personXuid,
});
}
}
}
this.doingDuplicateFriendCheck = false;
if (callback)
callback();
}
});
}
initializeAccounts(accountEmails, tokenPath) {
__classPrivateFieldSet(this, _Session_accountsInitialized, 0, "f");
for (let email of accountEmails) {
//debug("Accounts initialized: " + this.accountsInitialized);
let tokenProvider = new LinkCodeTokenProvider(email, tokenPath, {
authTitle: Titles.MinecraftNintendoSwitch,
deviceType: "Nintendo",
});
if (this.additionalOptions.log)
console.log(`[FriendConnect ${email}] Initializing Account`);
tokenProvider.on("firstTokenAcquired", () => {
var _a;
let xbox = new XboxClient(tokenProvider);
this.xboxAccounts.set(xbox.xuid, xbox);
__classPrivateFieldSet(this, _Session_accountsInitialized, (_a = __classPrivateFieldGet(this, _Session_accountsInitialized, "f"), _a++, _a), "f");
if (this.additionalOptions.log)
console.log(`[FriendConnect ${xbox.xuid}] Account Initialized`);
this.emit("accountInitialized");
});
}
}
checkAchievements(accounts) {
for (let account of accounts) {
if (this.additionalOptions.log)
console.log(`[FriendConnect ${account.xuid}] Checking for Achievements`);
try {
account.achievements
.getAchievements(account.xuid)
.then(achievements => {
var _a;
if (achievements.length === 0) {
if (this.additionalOptions.log)
console.log(`[FriendConnect ${account.xuid}] Passed Achievement Check`);
__classPrivateFieldSet(this, _Session_accountsWithNoAchievements, (_a = __classPrivateFieldGet(this, _Session_accountsWithNoAchievements, "f"), _a++, _a), "f");
this.emit("achievementChecked");
}
else {
throw new Error(`This account "${account.xuid}" has achievements, please use an alt account without achievements to protect your account.`);
}
});
}
catch (error) {
this.errorHandling(error, account.xuid, "Checking for Achievements");
}
}
}
createMinecraftLobbyCustomProperties(xbox, options) {
__classPrivateFieldSet(this, _Session_ip, options.ip, "f");
__classPrivateFieldSet(this, _Session_port, options.port, "f");
if (this.additionalOptions.log)
console.log(`[FriendConnect ${xbox.xuid}] Creating Session Info`);
return {
BroadcastSetting: 3,
CrossPlayDisabled: false,
Joinability: options.joinability,
LanGame: true,
MaxMemberCount: options.maxConnectedPlayers,
MemberCount: options.connectedPlayers,
OnlineCrossPlatformGame: true,
SupportedConnections: [
{
ConnectionType: options.connectionType,
HostIpAddress: options.ip,
HostPort: options.port,
RakNetGUID: "",
},
],
TitleId: 0,
hostName: options.hostName,
ownerId: xbox.xuid,
rakNetGUID: "",
worldName: options.worldName,
worldType: "Survival",
protocol: options.protocol,
version: options.version,
levelId: "level",
TransportLayer: 0,
};
}
setFriendInterval(accounts) {
for (let account of accounts) {
setInterval(() => {
if (!this.fullOfFriends.has(account.xuid) && // maybe error
!this.doingDuplicateFriendCheck &&
!account.token.isRefreshing) {
this.doingAutoFriendInterval = true;
if (this.additionalOptions.log)
console.log(`[FriendConnect ${account.xuid}] AutoFriend Interval`);
let req = fetch("https://peoplehub.xboxlive.com/users/me/people/followers", {
method: "GET",
headers: {
Authorization: account.authorizationHeader,
"x-xbl-contract-version": "5",
"Accept-Language": "en-us",
},
})
.then(async (res) => {
//console.log(res.statusCode, res.statusMessage);
const data = await res.json();
try {
if (this.additionalOptions.log)
console.log(`[FriendConnect ${account.xuid}] ${data.people.length} profile(s) have this account friended.` //followed ${this.profileName}
);
for (let person of data.people) {
if (person.isFollowingCaller) {
if (!person.isFollowedByCaller &&
!__classPrivateFieldGet(this, _Session_friendXuids, "f").has(person.xuid)) {
account.social.addFriend(person.xuid);
if (this.additionalOptions.log)
console.log(`[FriendConnect ${account.xuid}] Added Friend ${person.gamertag}`);
__classPrivateFieldGet(this, _Session_friendXuids, "f").add(person.xuid);
this.emit("friendAdded", {
account,
person: person.xuid,
});
}
}
}
this.doingAutoFriendInterval = false;
}
catch (error) {
this.errorHandling(error, account.xuid, "AutoFriend Interval");
this.doingAutoFriendInterval = false;
}
})
.catch(error => {
this.errorHandling(error, account.xuid, "AutoFriend Interval");
this.doingAutoFriendInterval = false;
});
}
}, 15000);
}
}
async getAdvertisement() {
try {
let info = await ping({
host: __classPrivateFieldGet(this, _Session_ip, "f"),
port: parseInt(`${__classPrivateFieldGet(this, _Session_port, "f")}`),
});
if (!this.additionalOptions.constants.gamemode)
this.minecraftLobbyCustomOptions.worldType = info.gamemode;
if (!this.additionalOptions.constants.worldName)
//@ts-ignore
this.minecraftLobbyCustomOptions.worldName = info.levelName;
if (!this.additionalOptions.constants.hostName)
this.minecraftLobbyCustomOptions.hostName = info.motd;
if (!this.additionalOptions.constants.protocol)
this.minecraftLobbyCustomOptions.protocol = info.protocol;
if (!this.additionalOptions.constants.version)
this.minecraftLobbyCustomOptions.version = info.version;
if (!this.additionalOptions.constants.connectedPlayers)
this.minecraftLobbyCustomOptions.MemberCount =
//@ts-ignore
parseInt(info.playersOnline);
if (!this.additionalOptions.constants.maxConnectedPlayers)
this.minecraftLobbyCustomOptions.MaxMemberCount =
//@ts-ignore
parseInt(info.playersMax);
}
catch (e) {
this.errorHandling(e, "", "Server Advertisement");
}
}
errorHandling(error, xuid, source) {
if (this.additionalOptions.log) {
console.error(`[FriendConnect ${xuid}] ${source} Error`, "\nError Name: ", error.name, "\nError Message: ", error.message, "\nError Stack: ", error.stack);
if (!fs.existsSync("./friend-connect-error.log")) {
fs.writeFileSync("./friend-connect-error.log", "If restarting does not fix your error, submit this file in an github issue. https://github.com/minerj101/FriendConnect.\n-------------------------------------------------------\n", "utf8");
}
let message;
message = `\n[${Date.now()}] ${source} Error:\n
Name: ${error.name}\n
Message: ${error.message}\n
Stack: ${error.stack}\n
Error: ${error}\n
\n---------------------------------`;
fs.appendFileSync("./friend-connect-error.log", message, "utf8");
}
}
}
_Session_sessionInstance = new WeakMap(), _Session_friendXuids = new WeakMap(), _Session_accountXuids = new WeakMap(), _Session_accountsInitialized = new WeakMap(), _Session_accountsWithNoAchievements = new WeakMap(), _Session_ip = new WeakMap(), _Session_port = new WeakMap();
export { Session };