@sonoransoftware/sonoran.js 1.0.34 → 1.0.36

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.
Files changed (44) hide show
  1. package/.github/workflows/auto-pr-on-branch-push.yml +89 -0
  2. package/.github/workflows/codex_instructions.md +24 -0
  3. package/.github/workflows/push-pr-nudge-codex.yml +50 -0
  4. package/dist/constants.d.ts +242 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/index.d.ts +1 -1
  7. package/dist/instance/Instance.d.ts +6 -0
  8. package/dist/instance/Instance.js +27 -0
  9. package/dist/instance/instance.types.d.ts +3 -0
  10. package/dist/libs/rest/src/lib/REST.d.ts +2 -1
  11. package/dist/libs/rest/src/lib/REST.js +118 -0
  12. package/dist/libs/rest/src/lib/RequestManager.d.ts +2 -0
  13. package/dist/libs/rest/src/lib/RequestManager.js +209 -0
  14. package/dist/libs/rest/src/lib/errors/RateLimitError.js +19 -1
  15. package/dist/libs/rest/src/lib/utils/constants.d.ts +108 -24
  16. package/dist/libs/rest/src/lib/utils/constants.js +118 -2
  17. package/dist/managers/CADActiveUnitsManager.d.ts +3 -0
  18. package/dist/managers/CADActiveUnitsManager.js +16 -6
  19. package/dist/managers/CADManager.d.ts +223 -0
  20. package/dist/managers/CADManager.js +513 -4
  21. package/dist/managers/CADServerManager.d.ts +11 -0
  22. package/dist/managers/CADServerManager.js +56 -13
  23. package/dist/managers/CMSManager.d.ts +78 -0
  24. package/dist/managers/CMSManager.js +213 -3
  25. package/dist/managers/CMSServerManager.d.ts +8 -0
  26. package/dist/managers/CMSServerManager.js +61 -18
  27. package/dist/managers/RadioManager.d.ts +55 -0
  28. package/dist/managers/RadioManager.js +224 -0
  29. package/package.json +1 -1
  30. package/readme.md +294 -12
  31. package/src/constants.ts +281 -1
  32. package/src/index.ts +42 -1
  33. package/src/instance/Instance.ts +30 -1
  34. package/src/instance/instance.types.ts +4 -1
  35. package/src/libs/rest/src/lib/REST.ts +117 -1
  36. package/src/libs/rest/src/lib/RequestManager.ts +229 -10
  37. package/src/libs/rest/src/lib/errors/RateLimitError.ts +20 -2
  38. package/src/libs/rest/src/lib/utils/constants.ts +223 -26
  39. package/src/managers/CADActiveUnitsManager.ts +19 -6
  40. package/src/managers/CADManager.ts +574 -4
  41. package/src/managers/CADServerManager.ts +59 -15
  42. package/src/managers/CMSManager.ts +196 -2
  43. package/src/managers/CMSServerManager.ts +65 -21
  44. package/src/managers/RadioManager.ts +187 -0
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.RadioManager = void 0;
27
+ const src_1 = require("../libs/rest/src");
28
+ const BaseManager_1 = require("./BaseManager");
29
+ const globalTypes = __importStar(require("../constants"));
30
+ /**
31
+ * Manages all Sonoran Radio API interactions.
32
+ */
33
+ class RadioManager extends BaseManager_1.BaseManager {
34
+ constructor(instance) {
35
+ super(instance);
36
+ this.ready = false;
37
+ this.failReason = null;
38
+ this.rest = new src_1.REST(instance, this, globalTypes.productEnums.RADIO, src_1.DefaultRadioRestOptions);
39
+ void this.buildManager(instance);
40
+ }
41
+ async buildManager(instance) {
42
+ const mutableThis = this;
43
+ try {
44
+ mutableThis.ready = true;
45
+ instance.isRadioSuccessful = true;
46
+ instance.emit('RADIO_SETUP_SUCCESSFUL');
47
+ }
48
+ catch (err) {
49
+ mutableThis.failReason = err;
50
+ instance.emit('RADIO_SETUP_UNSUCCESSFUL', err);
51
+ throw err;
52
+ }
53
+ }
54
+ /**
55
+ * Retrieves the configured community channel groups and channels.
56
+ */
57
+ async getCommunityChannels() {
58
+ return new Promise(async (resolve, reject) => {
59
+ var _a;
60
+ try {
61
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_GET_COMMUNITY_CHANNELS'));
62
+ resolve({ success: true, data: response });
63
+ }
64
+ catch (err) {
65
+ if (err instanceof src_1.APIError) {
66
+ resolve({ success: false, reason: err.response });
67
+ }
68
+ else {
69
+ reject(err);
70
+ }
71
+ }
72
+ });
73
+ }
74
+ /**
75
+ * Retrieves all connected users for the community.
76
+ */
77
+ async getConnectedUsers() {
78
+ return new Promise(async (resolve, reject) => {
79
+ var _a;
80
+ try {
81
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_GET_CONNECTED_USERS'));
82
+ resolve({ success: true, data: response });
83
+ }
84
+ catch (err) {
85
+ if (err instanceof src_1.APIError) {
86
+ resolve({ success: false, reason: err.response });
87
+ }
88
+ else {
89
+ reject(err);
90
+ }
91
+ }
92
+ });
93
+ }
94
+ /**
95
+ * Retrieves a specific connected user by room id and identity.
96
+ * @param {number} roomId Multi-server room id.
97
+ * @param {string} identity User account UUID.
98
+ */
99
+ async getConnectedUser(roomId, identity) {
100
+ return new Promise(async (resolve, reject) => {
101
+ var _a;
102
+ try {
103
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_GET_CONNECTED_USER', roomId, identity));
104
+ resolve({ success: true, data: response });
105
+ }
106
+ catch (err) {
107
+ if (err instanceof src_1.APIError) {
108
+ resolve({ success: false, reason: err.response });
109
+ }
110
+ else {
111
+ reject(err);
112
+ }
113
+ }
114
+ });
115
+ }
116
+ /**
117
+ * Updates a user's transmit and scanned channel configuration.
118
+ * @param {string} identity The user's UUID.
119
+ * @param {RadioSetUserChannelsOptions} options Transmit and scan channel configuration.
120
+ */
121
+ async setUserChannels(identity, options = {}) {
122
+ return new Promise(async (resolve, reject) => {
123
+ var _a, _b;
124
+ try {
125
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_SET_USER_CHANNELS', identity, options));
126
+ resolve({ success: true, result: (_b = response === null || response === void 0 ? void 0 : response.result) !== null && _b !== void 0 ? _b : response });
127
+ }
128
+ catch (err) {
129
+ if (err instanceof src_1.APIError) {
130
+ resolve({ success: false, reason: err.response });
131
+ }
132
+ else {
133
+ reject(err);
134
+ }
135
+ }
136
+ });
137
+ }
138
+ /**
139
+ * Updates a user's display name.
140
+ * @param {string} accId The user's account UUID.
141
+ * @param {string} displayName The new display name.
142
+ */
143
+ async setUserDisplayName(accId, displayName) {
144
+ return new Promise(async (resolve, reject) => {
145
+ var _a, _b;
146
+ try {
147
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_SET_USER_DISPLAY_NAME', accId, displayName));
148
+ resolve({ success: true, result: (_b = response === null || response === void 0 ? void 0 : response.result) !== null && _b !== void 0 ? _b : response });
149
+ }
150
+ catch (err) {
151
+ if (err instanceof src_1.APIError) {
152
+ resolve({ success: false, reason: err.response });
153
+ }
154
+ else {
155
+ reject(err);
156
+ }
157
+ }
158
+ });
159
+ }
160
+ /**
161
+ * Retrieves the community subscription level determined by the calling server's IP.
162
+ */
163
+ async getServerSubscriptionFromIp() {
164
+ return new Promise(async (resolve, reject) => {
165
+ var _a;
166
+ try {
167
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP'));
168
+ resolve({ success: true, data: response });
169
+ }
170
+ catch (err) {
171
+ if (err instanceof src_1.APIError) {
172
+ resolve({ success: false, reason: err.response });
173
+ }
174
+ else {
175
+ reject(err);
176
+ }
177
+ }
178
+ });
179
+ }
180
+ /**
181
+ * Sets the push event URL for the community.
182
+ * @param {string} pushUrl The server push URL.
183
+ */
184
+ async setServerIp(pushUrl) {
185
+ return new Promise(async (resolve, reject) => {
186
+ var _a, _b;
187
+ try {
188
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_SET_SERVER_IP', pushUrl));
189
+ resolve({ success: true, result: (_b = response === null || response === void 0 ? void 0 : response.result) !== null && _b !== void 0 ? _b : response });
190
+ }
191
+ catch (err) {
192
+ if (err instanceof src_1.APIError) {
193
+ resolve({ success: false, reason: err.response });
194
+ }
195
+ else {
196
+ reject(err);
197
+ }
198
+ }
199
+ });
200
+ }
201
+ /**
202
+ * Sets the available in-game speaker locations for tone dispatching.
203
+ * @param {RadioSpeakerLocation[]} locations Collection of speaker locations.
204
+ * @param {string} [token] Optional bearer token for authorization. Defaults to the community API key.
205
+ */
206
+ async setInGameSpeakerLocations(locations, token) {
207
+ return new Promise(async (resolve, reject) => {
208
+ var _a, _b;
209
+ try {
210
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('RADIO_SET_IN_GAME_SPEAKER_LOCATIONS', locations, token));
211
+ resolve({ success: true, result: (_b = response === null || response === void 0 ? void 0 : response.result) !== null && _b !== void 0 ? _b : response });
212
+ }
213
+ catch (err) {
214
+ if (err instanceof src_1.APIError) {
215
+ resolve({ success: false, reason: err.response });
216
+ }
217
+ else {
218
+ reject(err);
219
+ }
220
+ }
221
+ });
222
+ }
223
+ }
224
+ exports.RadioManager = RadioManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonoransoftware/sonoran.js",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "Sonoran.js is a library that allows you to interact with the Sonoran CAD and Sonoran CMS API. Based off of and utilizes several Discord.js library techniques for ease of use.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -3,7 +3,7 @@ Sonoran.js is a library that allows you to interact with the [Sonoran CAD](https
3
3
 
4
4
  ## Example Instance Setup
5
5
 
6
- Utilizing both Sonoran CMS & Sonoran CAD
6
+ Utilizing Sonoran CMS, Sonoran CAD & Sonoran Radio
7
7
  ```js
8
8
  const Sonoran = require('sonoran.js');
9
9
  const instance = Sonoran.instance({
@@ -11,6 +11,8 @@ const instance = Sonoran.instance({
11
11
  cadApiKey: 'DF58F1E-FD8A-44C5-BA',
12
12
  cmsCommunityId: 'mycommunity',
13
13
  cmsApiKey: 'e6ba9d68-ca7a-4e59-a9e2-93e275b4e0bf'
14
+ radioCommunityId: 'mycommunity'
15
+ radioApiKey: 'e6ba9d68-ca7a-4e59-a9e2-93e275b4e0bf'
14
16
  });
15
17
  ```
16
18
 
@@ -51,20 +53,117 @@ instance.cms.verifyWhitelist({
51
53
  ```
52
54
 
53
55
  ## CAD Functions
54
- ### getAccount
55
- Returns the user's account object.
56
- #### Argument `params`
57
- ##### Type: `object` `{apiId?, username?}`
56
+ Most CAD manager helpers return a `CADStandardResponse<T>` of `{ success, data?, reason? }`. Legacy helpers (`getAccount`, `setClockTime`, `joinCommunity`, `leaveCommunity`) keep their original response shapes.
57
+
58
+ ### Account & Configuration
59
+ - **`getVersion()`** - resolves to the numeric CAD subscription level.
60
+ - **`getAccount({ apiId?, username? })`** - fetches account details.
61
+ - **`setClockTime({ serverId, currentUtc, currentGame, secondsPerHour })`** - synchronizes in-game time.
62
+ - **`joinCommunity(internalKey, accounts)`** / **`leaveCommunity(internalKey, accounts)`** - manages community membership (requires an internal key).
63
+ - **`setPenalCodes(codes)`** - replaces the penal code configuration.
64
+ - **`setAccountApiIds(data)`** - assigns API IDs to a username.
65
+ - **`checkApiId(apiId)`** - confirms whether an API ID exists.
66
+ - **`applyPermissionKey(apiId?, permissionKey)`** - applies a permission key to an account.
67
+ - **`setAccountPermissions(changes)`** - bulk add/remove CAD permissions.
68
+ - **`banUser(data)`** - kicks or bans an account via the CAD API.
69
+ - **`verifySecret(secret)`** - validates a configured secret.
70
+ - **`authorizeStreetSigns(serverId)`** - authorizes map street-sign updates.
71
+ - **`setPostals(entries)`** - overwrites the postal table.
72
+ - **`sendPhoto(apiId?, url)`** - attaches a photo to an account.
73
+
58
74
  ```js
59
- const params = {
60
- apiId: '',
61
- username: 'SomeUser',
62
- };
63
- // Get user account object
64
- const account = await instance.cad.getAccount(params);
75
+ const account = await instance.cad.getAccount({ apiId: '1234567890' });
76
+ const penalCodes = await instance.cad.setPenalCodes([
77
+ { code: '1A', type: 'Felony', title: 'Example', bondType: 'None', jailTime: '0', bondAmount: 0 }
78
+ ]);
79
+ await instance.cad.setAccountApiIds({ username: 'SomeUser', apiIds: ['1234567890'], pushNew: true });
80
+ const permissionUpdate = await instance.cad.setAccountPermissions({ apiId: '1234567890', add: ['admin'], remove: [] });
81
+ ```
82
+
83
+ ### Records & Lookups
84
+ - **`getRecordTemplates(recordTypeId?)`**
85
+ - **`createRecord(data)`** / **`updateRecord(data)`** / **`removeRecord(id)`**
86
+ - **`lookupByInt(criteria)`** - identifier-based lookup.
87
+ - **`lookupRecords(query)`** - plate/name-based lookup.
88
+
89
+ ```js
90
+ await instance.cad.createRecord({ user: '1234567890', useDictionary: true, recordTypeId: 2, replaceValues: { NAME: 'Jane Doe' } });
91
+ const lookup = await instance.cad.lookupRecords({ apiId: '1234567890', types: [2], first: 'Jane', last: 'Doe', mi: '', plate: '', partial: false });
92
+ ```
93
+
94
+ ### Civilian Tools
95
+ - **`getCharacters(apiId)`** - lists civilian characters for an API ID.
96
+ - **`createCharacter(data)`** / **`updateCharacter(data)`** / **`removeCharacter(id)`** - CRUD helpers for civilian profiles.
97
+
98
+ ### Identifiers & Units
99
+ - **`getIdentifiers(apiId)`**
100
+ - **`modifyIdentifier(change)`** / **`setIdentifier(apiId?, identId)`**
101
+ - **`setUnitPanic(apiId?, isPanic)`** / **`setUnitStatus(apiId?, status, serverId)`**
102
+ - **`getActiveUnits(options)`** - direct CAD fetch for active units.
103
+ - **`kickUnit(apiId?, reason, serverId)`**
104
+ - **`updateUnitLocations(locations)`**
105
+
106
+ ### Map & Streetsigns
107
+ - **`getBlips(serverId)`**
108
+ - **`addBlips(blips)`** / **`updateBlips(blips)`** / **`removeBlip(id)`**
109
+ - **`setStreetSignConfig(serverId, signConfig)`**
110
+ - **`updateStreetSign(serverId, signData)`**
111
+
112
+ ### Calls & Dispatch
113
+ - **`create911Call(details)`** / **`remove911Call(callId)`**
114
+ - **`getCalls(options)`**
115
+ - **`createDispatch(data)`**
116
+ - **`attachUnits(serverId, callId, units)`** / **`detachUnits(serverId, units)`**
117
+ - **`setCallPostal(serverId, callId, postal)`** / **`setCallPrimary(serverId, callId, primary, trackPrimary)`**
118
+ - **`addCallNote(serverId, callId, note)`**
119
+ - **`closeCall(serverId, callId)`**
120
+
121
+ ```js
122
+ const dispatch = await instance.cad.createDispatch({
123
+ serverId: 1,
124
+ origin: Sonoran.CADDispatchOriginEnums.Caller,
125
+ status: Sonoran.CADDispatchStatusEnums.Active,
126
+ priority: 1,
127
+ block: '123',
128
+ address: 'Main St',
129
+ postal: '100',
130
+ title: 'Traffic Stop',
131
+ code: 'TS',
132
+ primary: 42,
133
+ trackPrimary: true,
134
+ description: 'Blue sedan headed north',
135
+ metaData: {},
136
+ units: ['unit-1']
137
+ });
138
+ await instance.cad.attachUnits(1, 1001, ['unit-2']);
139
+ ```
140
+
141
+ ## CAD Server Functions
142
+ - **`getServers()`** - fetches configured CAD servers.
143
+ - **`setServers(servers, deployMap?)`** - updates server configuration and refreshes the cache.
144
+
145
+ ```js
146
+ const servers = await instance.cad.servers?.getServers();
147
+ await instance.cad.servers?.setServers(servers ?? [], false);
148
+ ```
149
+
150
+ ## CAD Active Unit Functions
151
+ `CADActiveUnitsManager#getActiveUnits(options?)` proxies the CAD endpoint and returns a `CADStandardResponse`.
152
+
153
+ ```js
154
+ const activeUnits = await cadActiveUnitsManager.getActiveUnits({ includeOffline: true, limit: 25 });
155
+ if (activeUnits.success) {
156
+ console.log(activeUnits.data);
157
+ }
65
158
  ```
66
159
 
67
160
  ## CMS Functions
161
+ ### getSubscriptionVersion()
162
+ Returns the community's CMS subscription version.
163
+ ```js
164
+ const version = await instance.cms.getSubscriptionVersion();
165
+ ```
166
+
68
167
  ### verifyWhitelist(obj)
69
168
  Verifies that a user is whitelisted in the specified server.
70
169
  #### Arugment `params`
@@ -158,7 +257,7 @@ const getDepts = await instance.cms.getDepartments();
158
257
  ```
159
258
 
160
259
  ### setAccountRanks(obj, apiId, accId, username, discord, uniqueId)
161
- Gets all department information for a CMS community
260
+ Updates the CMS account's ranks using the identifiers provided.
162
261
  #### Arugment `params`
163
262
  ##### Type `object` `{set?: string[]; add?: string[]; remove?: string[]}`
164
263
  #### Arguments `apiId`, `accId`, `username`, `discord`, `uniqueId`
@@ -176,5 +275,188 @@ const params = {
176
275
  const setRanks = await instance.cms.setAccountRanks(params, undefined, undefined, undefined, '12345678', undefined);
177
276
  ```
178
277
 
278
+ ### setAccountName(apiId, username, accId, discord, uniqueId, newName)
279
+ Sets the display name used in CMS for an account.
280
+ ```js
281
+ await instance.cms.setAccountName(undefined, undefined, 'account-uuid', undefined, undefined, 'New Display Name');
282
+ ```
283
+
284
+ ### cmsBanAccount(params)
285
+ Adds a ban flag to the targeted account.
286
+ ```js
287
+ await instance.cms.cmsBanAccount({ apiId: '1234' });
288
+ ```
289
+
290
+ ### cmsKickAccount(params)
291
+ Performs a CMS kick request for the targeted account.
292
+ ```js
293
+ await instance.cms.cmsKickAccount({ discord: '1234567890' });
294
+ ```
295
+
296
+ ### forceSync(params)
297
+ Manually triggers a CMS force-sync for the targeted identifiers.
298
+ ```js
299
+ await instance.cms.forceSync({ username: 'SomeUser' });
300
+ ```
301
+
302
+ ### getPromotionFlows()
303
+ Fetches the configured promotion flows.
304
+ ```js
305
+ const flows = await instance.cms.getPromotionFlows();
306
+ ```
307
+
308
+ ### triggerPromotionFlows(flows)
309
+ Executes promotion or demotion flows for one or more users.
310
+ ```js
311
+ await instance.cms.triggerPromotionFlows([{
312
+ userId: 'u-123',
313
+ flowId: 'flow-abc',
314
+ users: ['u-123', 'u-456'],
315
+ promote: true
316
+ }]);
317
+ ```
318
+
319
+ ### getCurrentClockIn(params)
320
+ Fetches the current clock-in entry for the account if one exists.
321
+ ```js
322
+ const currentEntry = await instance.cms.getCurrentClockIn({ apiId: '1234' });
323
+ ```
324
+
325
+ ### getAccounts(options)
326
+ Retrieves CMS accounts with optional pagination and status filters.
327
+ ```js
328
+ const accounts = await instance.cms.getAccounts({ take: 50, banned: false });
329
+ ```
330
+
331
+ ### getProfileFields()
332
+ Returns profile field definitions configured for the community.
333
+ ```js
334
+ const profileFields = await instance.cms.getProfileFields();
335
+ ```
336
+
337
+ ### rsvp(eventId, params)
338
+ Toggles RSVP for an event for the provided account identifiers.
339
+ ```js
340
+ await instance.cms.rsvp('event-id', { accId: 'account-uuid' });
341
+ ```
342
+
343
+ ### getFormSubmissions(templateId, options)
344
+ Retrieves form submissions with optional pagination.
345
+ ```js
346
+ const submissions = await instance.cms.getFormSubmissions(42, { skip: 0, take: 25 });
347
+ ```
348
+
349
+ ### changeFormStage(params)
350
+ Moves a form to the specified stage for an account.
351
+ ```js
352
+ await instance.cms.changeFormStage({
353
+ formId: 42,
354
+ newStageId: 'approved',
355
+ accId: 'account-uuid',
356
+ uniqueId: 1234
357
+ });
358
+ ```
359
+
360
+ ### editAccountProfileFields(params)
361
+ Updates profile fields for an account.
362
+ ```js
363
+ await instance.cms.editAccountProfileFields({
364
+ accId: 'account-uuid',
365
+ profileFields: [
366
+ { fieldId: 10, value: 'Value' }
367
+ ]
368
+ });
369
+ ```
370
+
371
+ ### erlcGetOnlinePlayers(robloxJoinCode)
372
+ Returns the current ERLC player list for the join code.
373
+ ```js
374
+ const players = await instance.cms.erlcGetOnlinePlayers('join-code');
375
+ ```
376
+
377
+ ### erlcGetPlayerQueue(robloxJoinCode)
378
+ Returns the current ERLC player queue count for the join code.
379
+ ```js
380
+ const queue = await instance.cms.erlcGetPlayerQueue('join-code');
381
+ ```
382
+
383
+ ### erlcAddNewRecord(data)
384
+ Adds a moderation record for a player in ERLC.
385
+ ```js
386
+ await instance.cms.erlcAddNewRecord({
387
+ robloxJoinCode: 'join-code',
388
+ executerDiscordId: '1234567890',
389
+ type: 'Warning',
390
+ reason: 'Reckless driving'
391
+ });
392
+ ```
393
+
394
+ ## CMS Server Functions
395
+ ### getGameServers()
396
+ Fetches the configured CMS game servers. Returns an array of server objects.
397
+ ```js
398
+ const cmsServers = await instance.cms.servers?.getGameServers();
399
+ ```
400
+
401
+ ### setGameServers(servers)
402
+ Replaces the configured CMS game servers and refreshes the cache with the response payload.
403
+ ```js
404
+ await instance.cms.servers?.setGameServers([
405
+ { name: 'Server 1', description: 'Primary server', allowedRanks: ['admin'] }
406
+ ]);
407
+ ```
408
+
409
+ ## Radio Functions
410
+ ### getCommunityChannels()
411
+ Retrieves configured community channel groups and channels.
412
+ ```js
413
+ const channels = await instance.radio.getCommunityChannels();
414
+ ```
415
+
416
+ ### getConnectedUsers()
417
+ Lists all connected radio users in the community.
418
+ ```js
419
+ const users = await instance.radio.getConnectedUsers();
420
+ ```
421
+
422
+ ### getConnectedUser(roomId, identity)
423
+ Fetches a specific connected radio user by room and identity.
424
+ ```js
425
+ const user = await instance.radio.getConnectedUser(1, 'account-uuid');
426
+ ```
427
+
428
+ ### setUserChannels(identity, options)
429
+ Updates a user's transmit or scan channels.
430
+ ```js
431
+ await instance.radio.setUserChannels('account-uuid', { transmit: 12, scan: [10, 11, 12] });
432
+ ```
433
+
434
+ ### setUserDisplayName(accId, displayName)
435
+ Sets the user's radio display name.
436
+ ```js
437
+ await instance.radio.setUserDisplayName('account-uuid', 'Dispatch 101');
438
+ ```
439
+
440
+ ### getServerSubscriptionFromIp()
441
+ Resolves the community's subscription level for the calling server IP.
442
+ ```js
443
+ const subscription = await instance.radio.getServerSubscriptionFromIp();
444
+ ```
445
+
446
+ ### setServerIp(pushUrl)
447
+ Registers the push event URL for radio webhooks.
448
+ ```js
449
+ await instance.radio.setServerIp('https://example.com/sonoran-radio');
450
+ ```
451
+
452
+ ### setInGameSpeakerLocations(locations, token?)
453
+ Publishes available in-game speaker locations for tone routing.
454
+ ```js
455
+ await instance.radio.setInGameSpeakerLocations(
456
+ [{ name: 'Station 1', x: 123.4, y: 567.8, z: 90.1 }],
457
+ 'optional-bearer-token'
458
+ );
459
+ ```
460
+
179
461
  ## Further Documentation
180
462
  More documentation for Sonoran CAD specific methods and usage can be found [here](/docs/CAD-Methods-and-Usage.md), Sonoran CMS specific methods and usage can be found [here](/docs/CMS-Methods-and-Usage.md), and usage information for the REST class [here](/docs/REST-Methods-and-Usage.md).