bedrock-portal 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,6 +63,7 @@ Automatically adds the account's followers as friends and invites them to the ga
63
63
 
64
64
  Options:
65
65
  - **inviteOnAdd**: boolean - Automatically invites recently added friends to the game (default: false)
66
+ - **conditionToMeet**: (player: RawPlayer) => boolean - A function that returns a boolean. If the function returns true, followers will be added as a friend and the friends that don't will be removed (default: () => true)
66
67
 
67
68
  ```js
68
69
  const { BedrockPortal, Modules } = require('bedrock-portal');
@@ -75,6 +76,7 @@ portal.use(Modules.autoFriendAdd);
75
76
 
76
77
  portal.use(Modules.autoFriendAdd, {
77
78
  inviteOnAdd: true,
79
+ conditionToMeet: (player) => player.presenceState === 'Online', // Only add friends that are online and remove friends that are offline
78
80
  });
79
81
  ```
80
82
 
@@ -143,6 +145,12 @@ Emitted when a player is added as a friend. This event is only emitted when the
143
145
  **Parameters**
144
146
  - player - [Player](#player) object
145
147
 
148
+ ### portal.on('friendRemoved', (player) => {})
149
+ Emitted when a player is removed as a friend. This event is only emitted when the `autoFriendAdd` module is enabled.
150
+
151
+ **Parameters**
152
+ - player - [Player](#player) object
153
+
146
154
  ## Objects
147
155
 
148
156
  ### Player
@@ -258,6 +266,83 @@ Emitted when a player is added as a friend. This event is only emitted when the
258
266
  }
259
267
  ```
260
268
 
269
+ ### RawPlayer
270
+
271
+ ```ts
272
+ {
273
+ xuid: string;
274
+ isFavorite: boolean;
275
+ isFollowingCaller: boolean;
276
+ isFollowedByCaller: boolean;
277
+ isIdentityShared: boolean;
278
+ addedDateTimeUtc: string;
279
+ displayName: string;
280
+ realName: string;
281
+ displayPicRaw: string;
282
+ showUserAsAvatar: string;
283
+ gamertag: string;
284
+ gamerScore: string;
285
+ modernGamertag: string;
286
+ modernGamertagSuffix: string;
287
+ uniqueModernGamertag: string;
288
+ xboxOneRep: string;
289
+ presenceState: string;
290
+ presenceText: string;
291
+ presenceDevices: null | any[];
292
+ isBroadcasting: boolean;
293
+ isCloaked: null | boolean;
294
+ isQuarantined: boolean;
295
+ isXbox360Gamerpic: boolean;
296
+ lastSeenDateTimeUtc: string;
297
+ suggestion: null;
298
+ recommendation: null;
299
+ search: null;
300
+ titleHistory: null;
301
+ multiplayerSummary: null;
302
+ recentPlayer: null;
303
+ follower: {
304
+ text: string;
305
+ followedDateTime: string;
306
+ };
307
+ preferredColor: {
308
+ primaryColor: string;
309
+ secondaryColor: string;
310
+ tertiaryColor: string;
311
+ };
312
+ presenceDetails: null;
313
+ titlePresence: null;
314
+ titleSummaries: null;
315
+ presenceTitleIds: null;
316
+ detail: {
317
+ accountTier: string;
318
+ bio: string;
319
+ isVerified: boolean;
320
+ location: string;
321
+ tenure: string;
322
+ watermarks: any[];
323
+ blocked: boolean;
324
+ mute: boolean;
325
+ followerCount: number;
326
+ followingCount: number;
327
+ hasGamePass: boolean;
328
+ };
329
+ communityManagerTitles: null;
330
+ socialManager: null;
331
+ broadcast: null;
332
+ avatar: null;
333
+ linkedAccounts: {
334
+ networkName: string;
335
+ displayName: string;
336
+ showOnProfile: boolean;
337
+ isFamilyFriendly: boolean;
338
+ deeplink: null;
339
+ }[];
340
+ colorTheme: string;
341
+ preferredFlag: string;
342
+ preferredPlatforms: string[];
343
+ }
344
+ ```
345
+
261
346
 
262
347
  ## Debugging
263
348
 
@@ -1,4 +1,6 @@
1
1
  /* eslint-disable no-multi-spaces */
2
+ // process.env.DEBUG = 'bedrock-portal*';
3
+
2
4
  const { BedrockPortal, Modules, Joinability } = require('bedrock-portal');
3
5
  const { Authflow, Titles } = require('prismarine-auth');
4
6
 
@@ -21,11 +23,10 @@ const main = async () => {
21
23
 
22
24
  portal.use(Modules.autoFriendAdd, { // Automatically adds friends to the session
23
25
  inviteOnAdd: true,
26
+ conditionToMeet: (player) => player.presenceState === 'Online', // Only add friends that are online and remove friends that are offline
24
27
  });
25
28
 
26
- await portal.start(); // Starts the session
27
-
28
- await portal.invitePlayer('p3'); // gamertag or xuid to invite
29
+ // Put your event listeners before portal.start() to ensure you don't miss any events
29
30
 
30
31
  portal.on('rtaEvent', (event) => { // Emits when an RTA event is received
31
32
  console.log('RTA Event: ', event);
@@ -51,6 +52,14 @@ const main = async () => {
51
52
  console.log('Friend Added: ', player);
52
53
  });
53
54
 
55
+ portal.on('friendRemoved', (player) => { // Emits when a friend is removed. Only emitted if autoFriendAdd module is used
56
+ console.log('Friend Removed: ', player);
57
+ });
58
+
59
+ await portal.start(); // Starts the session
60
+
61
+ await portal.invitePlayer('p3'); // gamertag or xuid to invite
62
+
54
63
  };
55
64
 
56
65
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bedrock-portal",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "description": "Creates a joinable session in Minecraft Bedrock Edition that can route players to their a specified host",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -7,7 +7,10 @@ class Module {
7
7
  this.debug = require('debug')(`bedrock-portal:${this.name}`);
8
8
  }
9
9
  applyOptions(options) {
10
- this.options = options;
10
+ this.options = {
11
+ ...this.options,
12
+ ...options,
13
+ };
11
14
  }
12
15
  stop() {
13
16
  this.stopped = true;
@@ -1,9 +1,9 @@
1
1
  class Player {
2
- constructor(profileData = {}, sessionData = {}) {
2
+ constructor(profileData, sessionData) {
3
3
  this.profile = {};
4
4
  this.session = {};
5
- this.setXboxProfile(profileData);
6
- this.setSessionMember(sessionData);
5
+ if (profileData) this.setXboxProfile(profileData);
6
+ if (sessionData) this.setSessionMember(sessionData);
7
7
  }
8
8
 
9
9
  static fromSessionMember(data) {
@@ -7,7 +7,7 @@ const SessionConfig = {
7
7
  MinecraftTitleID: '896928775',
8
8
  MinecraftSCID: '4fc10100-5f7a-4470-899b-280835760c07',
9
9
  MinecraftTemplateName: 'MinecraftLobby',
10
- MiencraftProtocolVersion: 575,
10
+ MiencraftProtocolVersion: 589,
11
11
  };
12
12
 
13
13
  const Joinability = {
package/src/index.js CHANGED
@@ -94,7 +94,11 @@ module.exports = class BedrockPortal extends EventEmitter {
94
94
  async invitePlayer(identifier) {
95
95
  debug(`Inviting player, identifier: ${identifier}`);
96
96
 
97
- const profile = await this.#rest.getXboxProfile(identifier);
97
+ const profile = await this.#rest.getXboxProfile(identifier)
98
+ .catch(() => { throw new Error(`Failed to get profile for identifier: ${identifier}`); });
99
+
100
+ debug(`Inviting player, Got profile, xuid: ${profile.xuid}`);
101
+
98
102
  const invitePayload = {
99
103
  invitedXuid: String(profile.xuid),
100
104
  inviteAttributes: { titleId: SessionConfig.MinecraftTitleID },
@@ -220,11 +224,15 @@ module.exports = class BedrockPortal extends EventEmitter {
220
224
  });
221
225
 
222
226
  if (this.modules) {
223
- for (const mod of Object.values(this.modules)) {
224
- mod.run(this, { rest: this.#rest, rta: this.#rta })
225
- .then(() => debug(`Module ${mod.name} has run`))
226
- .catch(e => debug(`Module ${mod.name} failed to run`, e));
227
- }
227
+ Object.values(this.modules).forEach(async mod => {
228
+ try {
229
+ mod.run(this, { rest: this.#rest, rta: this.#rta });
230
+ debug(`Module ${mod.name} has run`);
231
+ }
232
+ catch (e) {
233
+ debug(`Module ${mod.name} failed to run`, e);
234
+ }
235
+ });
228
236
  }
229
237
 
230
238
  }
@@ -7,6 +7,7 @@ class AutoFriendAdd extends Module {
7
7
  super('autoFriendAdd', 'Automatically adds followers as friends');
8
8
  this.options = {
9
9
  inviteOnAdd: false,
10
+ conditionToMeet: () => true,
10
11
  };
11
12
  }
12
13
 
@@ -17,20 +18,12 @@ class AutoFriendAdd extends Module {
17
18
 
18
19
  this.debug('Checking for followers to add');
19
20
 
20
- const friends = await rest.getXboxFriends()
21
- .then(people => people.map(e => e.xuid))
22
- .catch(() => []);
23
-
24
21
  const followers = await rest.getXboxFollowers()
25
22
  .catch(() => []);
26
23
 
27
- const needsAdding = followers.filter(res => !friends.includes(res.xuid));
24
+ this.debug(`Found ${followers.length} follower(s)`);
28
25
 
29
- if (!needsAdding.length) {
30
- this.debug('No followers to add');
31
- await new Promise(resolve => setTimeout(resolve, 30000));
32
- continue;
33
- }
26
+ const needsAdding = followers.filter(res => !res.isFollowedByCaller && this.options.conditionToMeet(res));
34
27
 
35
28
  this.debug(`Adding ${needsAdding.length} account(s) [${needsAdding.map(res => res.gamertag).join(', ')}]`);
36
29
 
@@ -49,8 +42,32 @@ class AutoFriendAdd extends Module {
49
42
 
50
43
  this.debug(`Added & invited ${account.gamertag}`);
51
44
 
52
- await new Promise(resolve => setTimeout(resolve, 10000));
45
+ await new Promise(resolve => setTimeout(resolve, 2000));
46
+ }
47
+
48
+ this.debug('Checking for friends to remove');
49
+
50
+ const friends = await rest.getXboxFriends()
51
+ .catch(() => []);
52
+
53
+ this.debug(`Found ${friends.length} friend(s)`);
54
+
55
+ const needsRemoving = friends.filter(res => !res.isFollowingCaller || !this.options.conditionToMeet(res));
56
+
57
+ this.debug(`Removing ${needsRemoving.length} account(s) [${needsRemoving.map(res => res.gamertag).join(', ')}]`);
58
+
59
+ for (const account of needsRemoving) {
60
+ await rest.removeXboxFriend(account.xuid).catch(err => {
61
+ throw Error(`Failed to remove ${account.gamertag}`, { cause: err });
62
+ });
63
+
64
+ portal.emit('friendRemoved', Player.fromXboxProfile(account));
65
+
66
+ this.debug(`Removed ${account.gamertag}`);
67
+
68
+ await new Promise(resolve => setTimeout(resolve, 2000));
53
69
  }
70
+
54
71
  }
55
72
  catch (error) {
56
73
  this.debug(`Error: ${error.message}`, error);
package/src/rest.js CHANGED
@@ -114,19 +114,24 @@ module.exports = class Rest {
114
114
  }
115
115
 
116
116
  async getXboxFriends() {
117
- return await this.get('https://peoplehub.xboxlive.com/users/me/people/social/decoration/details,preferredColor', { contractVersion: 5 }).then(res => res.people.map(e => this.#parseXboxProfile(e)));
117
+ return await this.get('https://peoplehub.xboxlive.com/users/me/people/social/decoration/detail,preferredColor,follower', { contractVersion: 5 }).then(res => res.people.map(e => this.#parseXboxProfile(e)));
118
118
  }
119
119
 
120
120
  async getXboxFollowers() {
121
- return await this.get('https://peoplehub.xboxlive.com/users/me/people/followers/decoration/details,preferredColor', { contractVersion: 5 }).then(res => res.people.map(e => this.#parseXboxProfile(e)));
121
+ return await this.get('https://peoplehub.xboxlive.com/users/me/people/followers/decoration/detail,preferredColor,follower', { contractVersion: 5 }).then(res => res.people.map(e => this.#parseXboxProfile(e)));
122
122
  }
123
123
 
124
124
  async addXboxFriend(xuid) {
125
125
  return await this.put(`https://social.xboxlive.com/users/me/people/xuid(${xuid})`, { contractVersion: 2 });
126
126
  }
127
127
 
128
+ async removeXboxFriend(xuid) {
129
+ return await this.delete(`https://social.xboxlive.com/users/me/people/xuid(${xuid})`, { contractVersion: 2 });
130
+ }
131
+
128
132
  #parseXboxProfile(profile) {
129
133
  return {
134
+ ...profile,
130
135
  xuid: profile.xuid,
131
136
  avatar: profile.displayPicRaw,
132
137
  gamerscore: profile.gamerScore,