Skip to content

Commit a47d9b6

Browse files
committed
Add commands to add/remove and list sub-sessions
1 parent 2181279 commit a47d9b6

8 files changed

Lines changed: 240 additions & 11 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,16 @@ There is an egg for easy instance creation supplied for [Pterodactyl Panel](http
4343
5. Login to the account you want to use
4444
6. Follow the account on Xbox LIVE
4545
7. Check the friends tab ingame and you should see the server listed
46+
47+
48+
## Commands
49+
For the extension version prefix with `/mcxboxbroadcast`
50+
51+
| Command | Description |
52+
| --- | --- |
53+
| `exit` (Standalone Only) | Exits the program |
54+
| `restart` | Restarts the tool |
55+
| `dumpsession` | Dumps the current session data to files for debugging |
56+
| `accounts list` | Lists the accounts that are currently in use and their followers count |
57+
| `accounts add <sub-session-id>` | Adds an account to the list of accounts to use |
58+
| `accounts remove <sub-session-id>` | Removes an account from the list of accounts to use |

bootstrap/geyser/src/main/java/com/rtm516/mcxboxbroadcast/bootstrap/geyser/MCXboxBroadcastExtension.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.rtm516.mcxboxbroadcast.bootstrap.geyser;
22

3-
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
53
import com.rtm516.mcxboxbroadcast.core.Logger;
64
import com.rtm516.mcxboxbroadcast.core.SessionInfo;
75
import com.rtm516.mcxboxbroadcast.core.SessionManager;
@@ -68,6 +66,41 @@ public void onCommandDefine(GeyserDefineCommandsEvent event) {
6866
sessionManager.dumpSession();
6967
})
7068
.build());
69+
70+
event.register(Command.builder(this)
71+
.source(CommandSource.class)
72+
.name("accounts")
73+
.description("Manage sub-accounts.")
74+
.executor((source, command, args) -> {
75+
if (!source.isConsole()) {
76+
source.sendMessage("This command can only be ran from the console.");
77+
return;
78+
}
79+
80+
if (args.length < 3) {
81+
if (args.length == 2 && args[1].equalsIgnoreCase("list")) {
82+
sessionManager.listSessions();
83+
return;
84+
}
85+
86+
source.sendMessage("Usage:");
87+
source.sendMessage("accounts list");
88+
source.sendMessage("accounts add/remove <sub-session-id>");
89+
return;
90+
}
91+
92+
switch (args[1].toLowerCase()) {
93+
case "add":
94+
sessionManager.addSubSession(args[2]);
95+
break;
96+
case "remove":
97+
sessionManager.removeSubSession(args[2]);
98+
break;
99+
default:
100+
source.sendMessage("Unknown accounts command: " + args[1]);
101+
}
102+
})
103+
.build());
71104
}
72105

73106
@Subscribe

bootstrap/standalone/src/main/java/com/rtm516/mcxboxbroadcast/bootstrap/standalone/StandaloneLoggerImpl.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,26 @@ protected void runCommand(String command) {
7373
case "exit" -> System.exit(0);
7474
case "restart" -> StandaloneMain.restart();
7575
case "dumpsession" -> StandaloneMain.sessionManager.dumpSession();
76+
case "accounts" -> {
77+
String[] args = command.split(" ");
78+
if (args.length < 3) {
79+
if (args.length == 2 && args[1].equalsIgnoreCase("list")) {
80+
StandaloneMain.sessionManager.listSessions();
81+
return;
82+
}
83+
84+
warning("Usage:");
85+
warning("accounts list");
86+
warning("accounts add/remove <sub-session-id>");
87+
return;
88+
}
89+
90+
switch (args[1].toLowerCase()) {
91+
case "add" -> StandaloneMain.sessionManager.addSubSession(args[2]);
92+
case "remove" -> StandaloneMain.sessionManager.removeSubSession(args[2]);
93+
default -> warning("Unknown accounts command: " + args[1]);
94+
}
95+
}
7696
default -> warning("Unknown command: " + commandNode);
7797
}
7898
} catch (Exception e) {

core/src/main/java/com/rtm516/mcxboxbroadcast/core/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public class Constants {
2727
public static final String USER_PRESENCE = "https://userpresence.xboxlive.com/users/xuid(%s)/devices/current/titles/current";
2828
public static final URI FOLLOWERS = URI.create("https://peoplehub.xboxlive.com/users/me/people/followers");
2929
public static final URI SOCIAL = URI.create("https://peoplehub.xboxlive.com/users/me/people/social");
30+
public static final URI SOCIAL_SUMMARY = URI.create("https://social.xboxlive.com/users/me/summary");
31+
3032
/**
3133
* From the ConnectionType enum in the game
3234
* pre 1.19.10 UPNP was 7

core/src/main/java/com/rtm516/mcxboxbroadcast/core/SessionManager.java

Lines changed: 124 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,45 @@
11
package com.rtm516.mcxboxbroadcast.core;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
43
import com.rtm516.mcxboxbroadcast.core.exceptions.SessionCreationException;
54
import com.rtm516.mcxboxbroadcast.core.exceptions.SessionUpdateException;
65
import com.rtm516.mcxboxbroadcast.core.models.CreateSessionRequest;
7-
import com.rtm516.mcxboxbroadcast.core.models.JoinSessionRequest;
86
import org.java_websocket.util.NamedThreadFactory;
97

8+
import java.io.File;
109
import java.io.FileWriter;
1110
import java.io.IOException;
1211
import java.net.URI;
1312
import java.net.http.HttpRequest;
1413
import java.net.http.HttpResponse;
14+
import java.nio.file.Files;
15+
import java.nio.file.Path;
16+
import java.nio.file.Paths;
17+
import java.util.ArrayList;
18+
import java.util.Arrays;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
1522
import java.util.concurrent.Executors;
1623
import java.util.concurrent.ScheduledExecutorService;
24+
import java.util.stream.Stream;
1725

1826
/**
1927
* Simple manager to authenticate and create sessions on Xbox
2028
*/
2129
public class SessionManager extends SessionManagerCore {
2230
private final ScheduledExecutorService scheduledThreadPool;
31+
private final Map<String, SubSessionManager> subSessionManagers;
2332

2433
/**
2534
* Create an instance of SessionManager
2635
*
27-
* @param cache The directory to store the cached tokens in
36+
* @param cache The directory to store the cached tokens in
2837
* @param logger The logger to use for outputting messages
2938
*/
3039
public SessionManager(String cache, Logger logger) {
3140
super(cache, logger.prefixed("Primary Session"));
3241
this.scheduledThreadPool = Executors.newScheduledThreadPool(5, new NamedThreadFactory("MCXboxBroadcast Thread"));
42+
this.subSessionManagers = new HashMap<>();
3343
}
3444

3545
@Override
@@ -63,6 +73,19 @@ public void init(SessionInfo sessionInfo) throws SessionCreationException, Sessi
6373
this.sessionInfo = new ExpandedSessionInfo("", "", sessionInfo);
6474

6575
super.init();
76+
77+
// Load sub-sessions from cache
78+
List<String> subSessions = new ArrayList<>();
79+
try {
80+
subSessions = Arrays.asList(Constants.OBJECT_MAPPER.readValue(Paths.get(cache, "sub_sessions.json").toFile(), String[].class));
81+
} catch (IOException ignored) { }
82+
83+
// Create the sub-session manager for each sub-session
84+
for (String subSession : subSessions) {
85+
SubSessionManager subSessionManager = new SubSessionManager(subSession, this, Paths.get(cache, subSession).toString(), logger);
86+
subSessionManager.init();
87+
subSessionManagers.put(subSession, subSessionManager);
88+
}
6689
}
6790

6891
/**
@@ -88,8 +111,13 @@ protected void updateSession() throws SessionUpdateException {
88111
* Stop the current session and close the websocket
89112
*/
90113
public void shutdown() {
114+
// Shutdown all sub-sessions
115+
for (SubSessionManager subSessionManager : subSessionManagers.values()) {
116+
subSessionManager.shutdown();
117+
}
118+
119+
// Shutdown self
91120
super.shutdown();
92-
this.sessionInfo.setSessionId(null); // TODO I don't think this is needed
93121
scheduledThreadPool.shutdown();
94122
}
95123

@@ -126,4 +154,96 @@ public void dumpSession() {
126154

127155
logger.info("Dumped session responses to 'lastSessionResponse.json' and 'currentSessionResponse.json'");
128156
}
157+
158+
/**
159+
* Create a sub-session for the given ID
160+
*
161+
* @param id The ID of the sub-session to create
162+
*/
163+
public void addSubSession(String id) {
164+
// Make sure we don't already have that ID
165+
if (subSessionManagers.containsKey(id)) {
166+
coreLogger.error("Sub-session already exists with that ID");
167+
return;
168+
}
169+
170+
// Create the sub-session manager
171+
try {
172+
SubSessionManager subSessionManager = new SubSessionManager(id, this, Paths.get(cache, id).toString(), logger);
173+
subSessionManager.init();
174+
subSessionManagers.put(id, subSessionManager);
175+
} catch (SessionCreationException | SessionUpdateException e) {
176+
coreLogger.error("Failed to create sub-session", e);
177+
return;
178+
}
179+
180+
// Update the list of sub-sessions
181+
try {
182+
Files.write(Paths.get(cache, "sub_sessions.json"), Constants.OBJECT_MAPPER.writeValueAsBytes(subSessionManagers.keySet()));
183+
} catch (IOException e) {
184+
coreLogger.error("Failed to update sub-session list", e);
185+
}
186+
}
187+
188+
/**
189+
* Remove a sub-session for the given ID
190+
*
191+
* @param id The ID of the sub-session to remove
192+
*/
193+
public void removeSubSession(String id) {
194+
// Make sure we have that ID
195+
if (!subSessionManagers.containsKey(id)) {
196+
coreLogger.error("Sub-session does not exist with that ID");
197+
return;
198+
}
199+
200+
// Remove the sub-session manager
201+
subSessionManagers.get(id).shutdown();
202+
subSessionManagers.remove(id);
203+
204+
// Delete the sub-session cache folder and its contents
205+
try (Stream<Path> files = Files.walk(Paths.get(cache, id))) {
206+
files.map(Path::toFile)
207+
.forEach(File::delete);
208+
Paths.get(cache, id).toFile().delete();
209+
} catch (IOException e) {
210+
coreLogger.error("Failed to delete sub-session cache folder", e);
211+
}
212+
213+
// Update the list of sub-sessions
214+
try {
215+
Files.write(Paths.get(cache, "sub_sessions.json"), Constants.OBJECT_MAPPER.writeValueAsBytes(subSessionManagers.keySet()));
216+
} catch (IOException e) {
217+
coreLogger.error("Failed to update sub-session list", e);
218+
}
219+
220+
coreLogger.info("Removed sub-session with ID " + id);
221+
}
222+
223+
/**
224+
* List all sessions and their information
225+
*/
226+
public void listSessions() {
227+
List<String> messages = new ArrayList<>();
228+
coreLogger.info("Loading status of sessions...");
229+
230+
messages.add("Primary Session:");
231+
messages.add(" - Gamertag: " + getXboxToken().gamertag());
232+
messages.add(" Following: " + socialSummary().targetFollowingCount() + "/1000");
233+
234+
if (subSessionManagers.isEmpty()) {
235+
messages.add("Sub-sessions:");
236+
for (Map.Entry<String, SubSessionManager> subSession : subSessionManagers.entrySet()) {
237+
messages.add(" - ID: " + subSession.getKey());
238+
messages.add(" Gamertag: " + subSession.getValue().getXboxToken().gamertag());
239+
messages.add(" Following: " + subSession.getValue().socialSummary().targetFollowingCount() + "/1000");
240+
}
241+
} else {
242+
messages.add("No sub-sessions");
243+
}
244+
245+
for (String message : messages) {
246+
coreLogger.info(message);
247+
}
248+
}
129249
}

core/src/main/java/com/rtm516/mcxboxbroadcast/core/SessionManagerCore.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import com.rtm516.mcxboxbroadcast.core.exceptions.SessionUpdateException;
66
import com.rtm516.mcxboxbroadcast.core.models.CreateHandleRequest;
77
import com.rtm516.mcxboxbroadcast.core.models.CreateHandleResponse;
8-
import com.rtm516.mcxboxbroadcast.core.models.JoinSessionRequest;
98
import com.rtm516.mcxboxbroadcast.core.models.SISUAuthenticationResponse;
109
import com.rtm516.mcxboxbroadcast.core.models.SessionRef;
10+
import com.rtm516.mcxboxbroadcast.core.models.SocialSummaryResponse;
1111
import com.rtm516.mcxboxbroadcast.core.models.XboxTokenInfo;
1212

1313
import java.io.File;
@@ -32,6 +32,7 @@ public abstract class SessionManagerCore {
3232
private final FriendManager friendManager;
3333
protected final HttpClient httpClient;
3434
protected final Logger logger;
35+
protected final Logger coreLogger;
3536
protected final String cache;
3637

3738
protected RtaWebsocketClient rtaWebsocket;
@@ -53,6 +54,7 @@ public SessionManagerCore(String cache, Logger logger) {
5354
.build();
5455

5556
this.logger = logger;
57+
this.coreLogger = logger.prefixed("");
5658
this.cache = cache;
5759

5860
this.liveTokenManager = new LiveTokenManager(cache, httpClient, logger);
@@ -283,7 +285,7 @@ protected void updateSessionInternal(String url, Object data) throws SessionUpda
283285
}
284286

285287
if (createSessionResponse.statusCode() != 200 && createSessionResponse.statusCode() != 201) {
286-
logger.debug("Got join session response: " + createSessionResponse.body());
288+
logger.debug("Got update session response: " + createSessionResponse.body());
287289
throw new SessionUpdateException("Unable to update session information, got status " + createSessionResponse.statusCode() + " trying to update");
288290
}
289291
}
@@ -347,7 +349,9 @@ protected void setupWebsocket(String token) {
347349
* Stop the current session and close the websocket
348350
*/
349351
public void shutdown() {
350-
rtaWebsocket.close();
352+
if (rtaWebsocket != null) {
353+
rtaWebsocket.close();
354+
}
351355
}
352356

353357
/**
@@ -384,4 +388,25 @@ protected void updatePresence() {
384388
logger.debug("Presence update successful, scheduling presence update in " + heartbeatAfter + " seconds");
385389
scheduledThread().schedule(this::updatePresence, heartbeatAfter, TimeUnit.SECONDS);
386390
}
391+
392+
/**
393+
* Get the current follower count for the current user
394+
* @return The current follower count
395+
*/
396+
public SocialSummaryResponse socialSummary() {
397+
HttpRequest socialSummaryRequest = HttpRequest.newBuilder()
398+
.uri(Constants.SOCIAL_SUMMARY)
399+
.header("Authorization", getTokenHeader())
400+
.GET()
401+
.build();
402+
403+
404+
try {
405+
return Constants.OBJECT_MAPPER.readValue(httpClient.send(socialSummaryRequest, HttpResponse.BodyHandlers.ofString()).body(), SocialSummaryResponse.class);
406+
} catch (IOException | InterruptedException e) {
407+
logger.error("Unable to get current friend count", e);
408+
}
409+
410+
return new SocialSummaryResponse(-1, -1, false, false, false, false, "", -1, -1, "");
411+
}
387412
}

core/src/main/java/com/rtm516/mcxboxbroadcast/core/SubSessionManager.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ public class SubSessionManager extends SessionManagerCore {
1414
/**
1515
* Create a new session manager for a sub-session
1616
*
17+
* @param id The id of the sub-session
1718
* @param parent The parent session manager
1819
* @param cache The directory to store the cached tokens in
1920
* @param logger The logger to use for outputting messages
2021
*/
21-
public SubSessionManager(SessionManager parent, String cache, Logger logger) {
22-
super(cache, logger.prefixed("Sub-Session X"));
22+
public SubSessionManager(String id, SessionManager parent, String cache, Logger logger) {
23+
super(cache, logger.prefixed("Sub-Session " + id));
2324
this.parent = parent;
2425
}
2526

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.rtm516.mcxboxbroadcast.core.models;
2+
3+
public record SocialSummaryResponse(
4+
int targetFollowingCount,
5+
int targetFollowerCount,
6+
boolean isCallerFollowingTarget,
7+
boolean isTargetFollowingCaller,
8+
boolean hasCallerMarkedTargetAsFavorite,
9+
boolean hasCallerMarkedTargetAsKnown,
10+
String legacyFriendStatus,
11+
int availablePeopleSlots,
12+
int recentChangeCount,
13+
String watermark
14+
) {
15+
}

0 commit comments

Comments
 (0)