@sonoransoftware/sonoran.js 1.0.36 → 1.0.38

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/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ PolyForm Noncommercial License 1.0.0
2
+
3
+ Copyright (c) [2025] [Sonoran Software Systems, LLC]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to
7
+ use, copy, modify, and distribute the Software, for any noncommercial purpose,
8
+ subject to the following conditions:
9
+
10
+ 1. Noncommercial Use Only. You may not use the Software for a commercial
11
+ purpose. “Commercial purpose” means the sale, lease, license, or other
12
+ commercial exploitation of the Software, or the use of the Software to
13
+ provide a service or product for which you receive compensation.
14
+
15
+ 2. Compliance and Attribution. You must ensure that anyone who uses the
16
+ Software complies with this license, and you must retain the above
17
+ copyright notice and this license in all copies or substantial portions
18
+ of the Software.
19
+
20
+ 3. No Warranty. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY
21
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+
24
+ See https://polyformproject.org/licenses/noncommercial/1.0.0/ for the full license text.
@@ -394,6 +394,17 @@ export interface CMSERLCAddNewRecordPromiseResult {
394
394
  reason?: string;
395
395
  logId?: string;
396
396
  }
397
+ export interface CMSERLCExecuteCommandPayload {
398
+ type: string;
399
+ args: unknown;
400
+ discordId: string | number;
401
+ includesPlayerNameOrId: boolean;
402
+ }
403
+ export interface CMSERLCExecuteCommandPromiseResult {
404
+ success: boolean;
405
+ reason?: string;
406
+ data?: unknown;
407
+ }
397
408
  export interface RadioChannelGroup {
398
409
  id: number;
399
410
  name: string;
@@ -442,6 +453,15 @@ export interface RadioSpeakerLocation {
442
453
  label: string;
443
454
  id: string;
444
455
  }
456
+ export type RadioTonePlayTargetType = 'channel' | 'group' | 'game';
457
+ export interface RadioTonePlayTarget {
458
+ label: string;
459
+ type: RadioTonePlayTargetType;
460
+ value: unknown;
461
+ group: number | null;
462
+ icon?: string;
463
+ color?: string;
464
+ }
445
465
  export interface RadioSetUserChannelsOptions {
446
466
  transmit?: number;
447
467
  scan?: number[];
@@ -504,3 +524,8 @@ export interface RadioSetInGameSpeakerLocationsPromiseResult {
504
524
  reason?: string;
505
525
  result?: string;
506
526
  }
527
+ export interface RadioPlayTonePromiseResult {
528
+ success: boolean;
529
+ reason?: string;
530
+ result?: string;
531
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './instance/Instance';
2
2
  export * from './builders';
3
3
  export * from './libs/rest/src';
4
- export { productEnums, CADNewDispatchBuilderOptions, CADSubscriptionVersionEnum, CMSSubscriptionVersionEnum, RadioSubscriptionLevel, RadioSetUserChannelsOptions, RadioChannel, RadioChannelGroup, RadioConnectedUser, RadioSpeakerLocation, RadioGetCommunityChannelsPromiseResult, RadioGetConnectedUsersPromiseResult, RadioGetConnectedUserPromiseResult, RadioSetUserChannelsPromiseResult, RadioSetUserDisplayNamePromiseResult, RadioGetServerSubscriptionFromIpPromiseResult, RadioSetServerIpPromiseResult, RadioSetInGameSpeakerLocationsPromiseResult, CADSetClockTimePromiseResult, CADJoinCommunityPromiseResult, CADLeaveCommunityPromiseResult, CADStandardResponse, CMSProfileField, CMSGetCurrentClockInPromiseResult, CMSAccountsPage, CMSAccountSummary, CMSGetAccountsPromiseResult, CMSGetProfileFieldsPromiseResult, CMSProfileFieldUpdate, CMSEditAccountProfileFieldsPromiseResult, CMSRsvpPromiseResult, CMSChangeFormStagePromiseResult, CMSSetGameServerStruct, CMSSetGameServersPromiseResult, CMSExecuteRankPromotionResult, CMSTriggerPromotionFlowPayload, CMSPromotionFlow, CMSTriggerPromotionFlowsPromiseResult, CMSGetPromotionFlowsPromiseResult, CMSGetFormSubmissionsPromiseResult } from './constants';
4
+ export { productEnums, CADNewDispatchBuilderOptions, CADSubscriptionVersionEnum, CMSSubscriptionVersionEnum, RadioSubscriptionLevel, RadioSetUserChannelsOptions, RadioChannel, RadioChannelGroup, RadioConnectedUser, RadioSpeakerLocation, RadioGetCommunityChannelsPromiseResult, RadioGetConnectedUsersPromiseResult, RadioGetConnectedUserPromiseResult, RadioSetUserChannelsPromiseResult, RadioSetUserDisplayNamePromiseResult, RadioGetServerSubscriptionFromIpPromiseResult, RadioSetServerIpPromiseResult, RadioSetInGameSpeakerLocationsPromiseResult, CADSetClockTimePromiseResult, CADJoinCommunityPromiseResult, CADLeaveCommunityPromiseResult, CADStandardResponse, CMSProfileField, CMSGetCurrentClockInPromiseResult, CMSAccountsPage, CMSAccountSummary, CMSGetAccountsPromiseResult, CMSGetProfileFieldsPromiseResult, CMSProfileFieldUpdate, CMSEditAccountProfileFieldsPromiseResult, CMSRsvpPromiseResult, CMSChangeFormStagePromiseResult, CMSSetGameServerStruct, CMSSetGameServersPromiseResult, CMSExecuteRankPromotionResult, CMSTriggerPromotionFlowPayload, CMSPromotionFlow, CMSTriggerPromotionFlowsPromiseResult, CMSGetPromotionFlowsPromiseResult, CMSGetFormSubmissionsPromiseResult, CMSERLCGetOnlinePlayersPromiseResult, CMSERLCGetPlayerQueuePromiseResult, CMSERLCAddNewRecordPromiseResult, CMSERLCExecuteCommandPayload, CMSERLCExecuteCommandPromiseResult } from './constants';
@@ -300,6 +300,13 @@ class REST extends events_1.EventEmitter {
300
300
  points: args[6],
301
301
  };
302
302
  }
303
+ case 'ERLC_EXECUTE_COMMAND': {
304
+ const payload = args[0];
305
+ if (!Array.isArray(payload)) {
306
+ throw new Error('ERLC_EXECUTE_COMMAND requires an array of command payloads.');
307
+ }
308
+ return payload;
309
+ }
303
310
  case 'RADIO_GET_COMMUNITY_CHANNELS':
304
311
  case 'RADIO_GET_CONNECTED_USERS':
305
312
  case 'RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP': {
@@ -334,6 +341,13 @@ class REST extends events_1.EventEmitter {
334
341
  token: args[1]
335
342
  };
336
343
  }
344
+ case 'PLAY_TONE': {
345
+ return {
346
+ roomId: args[0],
347
+ tones: args[1],
348
+ playTo: args[2]
349
+ };
350
+ }
337
351
  default: {
338
352
  return args;
339
353
  }
@@ -101,6 +101,10 @@ class RequestManager extends events_1.EventEmitter {
101
101
  apiData.data.data = [];
102
102
  break;
103
103
  }
104
+ case 'ERLC_EXECUTE_COMMAND': {
105
+ apiData.data.data = clonedData;
106
+ break;
107
+ }
104
108
  case 'SET_PENAL_CODES': {
105
109
  apiData.data.data = [clonedData[0]];
106
110
  break;
@@ -378,6 +382,35 @@ class RequestManager extends events_1.EventEmitter {
378
382
  path = apiType.path;
379
383
  break;
380
384
  }
385
+ case 'PLAY_TONE': {
386
+ const auth = ensureAuth();
387
+ const roomIdRaw = payload === null || payload === void 0 ? void 0 : payload.roomId;
388
+ if (roomIdRaw === undefined || roomIdRaw === null) {
389
+ throw new Error('roomId is required for PLAY_TONE requests.');
390
+ }
391
+ const roomIdNumber = typeof roomIdRaw === 'number' ? roomIdRaw : Number(roomIdRaw);
392
+ if (Number.isNaN(roomIdNumber)) {
393
+ throw new Error('roomId must be a number for PLAY_TONE requests.');
394
+ }
395
+ const tones = payload === null || payload === void 0 ? void 0 : payload.tones;
396
+ if (!Array.isArray(tones) || tones.length === 0) {
397
+ throw new Error('tones array is required for PLAY_TONE requests.');
398
+ }
399
+ const playTo = payload === null || payload === void 0 ? void 0 : payload.playTo;
400
+ if (!Array.isArray(playTo) || playTo.length === 0) {
401
+ throw new Error('playTo array is required for PLAY_TONE requests.');
402
+ }
403
+ method = 'POST';
404
+ body = {
405
+ id: auth.id,
406
+ key: auth.key,
407
+ roomId: roomIdNumber,
408
+ tones,
409
+ playTo
410
+ };
411
+ path = apiType.path;
412
+ break;
413
+ }
381
414
  default: {
382
415
  throw new Error(`Unsupported radio API type received: ${apiType.type}`);
383
416
  }
@@ -1,4 +1,4 @@
1
- import { productEnums, RadioSetUserChannelsOptions, RadioSpeakerLocation, CMSProfileFieldUpdate, CMSSetGameServerStruct, CMSTriggerPromotionFlowPayload } from '../../../../../constants';
1
+ import { productEnums, RadioSetUserChannelsOptions, RadioSpeakerLocation, RadioTonePlayTarget, CMSProfileFieldUpdate, CMSSetGameServerStruct, CMSTriggerPromotionFlowPayload, CMSERLCExecuteCommandPayload } from '../../../../../constants';
2
2
  import type { RESTOptions } from '../REST';
3
3
  export declare const DefaultUserAgent = "Sonoran.js NPM Module";
4
4
  export declare const DefaultCADRestOptions: Required<RESTOptions>;
@@ -38,7 +38,7 @@ export declare const CommunitiesCMSAPITypes: APITypeData[];
38
38
  export declare const ERLCMSAPITypes: APITypeData[];
39
39
  export declare const RadioAPITypes: APITypeData[];
40
40
  export declare const AllAPITypes: AllAPITypeData[];
41
- export type AllAPITypesType = 'GET_SERVERS' | 'SET_SERVERS' | 'GET_VERSION' | 'SET_PENAL_CODES' | 'SET_API_ID' | 'GET_TEMPLATES' | 'NEW_RECORD' | 'EDIT_RECORD' | 'REMOVE_RECORD' | 'LOOKUP_INT' | 'LOOKUP' | 'GET_ACCOUNT' | 'CHECK_APIID' | 'APPLY_PERMISSION_KEY' | 'SET_ACCOUNT_PERMISSIONS' | 'BAN_USER' | 'VERIFY_SECRET' | 'AUTH_STREETSIGNS' | 'SET_POSTALS' | 'SEND_PHOTO' | 'SET_CLOCK' | 'JOIN_COMMUNITY' | 'LEAVE_COMMUNITY' | 'GET_CHARACTERS' | 'NEW_CHARACTER' | 'EDIT_CHARACTER' | 'REMOVE_CHARACTER' | 'GET_IDENTIFIERS' | 'MODIFY_IDENTIFIER' | 'SET_IDENTIFIER' | 'UNIT_PANIC' | 'UNIT_STATUS' | 'GET_BLIPS' | 'ADD_BLIP' | 'MODIFY_BLIP' | 'REMOVE_BLIP' | '911_CALL' | 'REMOVE_911' | 'GET_CALLS' | 'GET_ACTIVE_UNITS' | 'KICK_UNIT' | 'NEW_DISPATCH' | 'ATTACH_UNIT' | 'DETACH_UNIT' | 'SET_CALL_POSTAL' | 'SET_CALL_PRIMARY' | 'ADD_CALL_NOTE' | 'CLOSE_CALL' | 'UNIT_LOCATION' | 'SET_STREETSIGN_CONFIG' | 'UPDATE_STREETSIGN' | 'GET_COM_ACCOUNT' | 'GET_DEPARTMENTS' | 'GET_SUB_VERSION' | 'GET_CURRENT_CLOCK_IN' | 'GET_ACCOUNTS' | 'GET_PROFILE_FIELDS' | 'CHECK_COM_APIID' | 'VERIFY_WHITELIST' | 'CLOCK_IN_OUT' | 'FULL_WHITELIST' | 'GET_ACCOUNT_RANKS' | 'SET_ACCOUNT_RANKS' | 'RSVP' | 'CHANGE_FORM_STAGE' | 'GET_FORM_TEMPLATE_SUBMISSIONS' | 'KICK_ACCOUNT' | 'BAN_ACCOUNT' | 'LOOKUP' | 'EDIT_ACC_PROFLIE_FIELDS' | 'SET_ACCOUNT_NAME' | 'FORCE_SYNC' | 'TRIGGER_PROMOTION_FLOWS' | 'GET_PROMOTION_FLOWS' | 'SET_GAME_SERVERS' | 'ERLC_GET_ONLINE_PLAYERS' | 'ERLC_GET_PLAYER_QUEUE' | 'ERLC_ADD_NEW_RECORD' | 'RADIO_GET_COMMUNITY_CHANNELS' | 'RADIO_GET_CONNECTED_USERS' | 'RADIO_GET_CONNECTED_USER' | 'RADIO_SET_USER_CHANNELS' | 'RADIO_SET_USER_DISPLAY_NAME' | 'RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP' | 'RADIO_SET_SERVER_IP' | 'RADIO_SET_IN_GAME_SPEAKER_LOCATIONS';
41
+ export type AllAPITypesType = 'GET_SERVERS' | 'SET_SERVERS' | 'GET_VERSION' | 'SET_PENAL_CODES' | 'SET_API_ID' | 'GET_TEMPLATES' | 'NEW_RECORD' | 'EDIT_RECORD' | 'REMOVE_RECORD' | 'LOOKUP_INT' | 'LOOKUP' | 'GET_ACCOUNT' | 'CHECK_APIID' | 'APPLY_PERMISSION_KEY' | 'SET_ACCOUNT_PERMISSIONS' | 'BAN_USER' | 'VERIFY_SECRET' | 'AUTH_STREETSIGNS' | 'SET_POSTALS' | 'SEND_PHOTO' | 'SET_CLOCK' | 'JOIN_COMMUNITY' | 'LEAVE_COMMUNITY' | 'GET_CHARACTERS' | 'NEW_CHARACTER' | 'EDIT_CHARACTER' | 'REMOVE_CHARACTER' | 'GET_IDENTIFIERS' | 'MODIFY_IDENTIFIER' | 'SET_IDENTIFIER' | 'UNIT_PANIC' | 'UNIT_STATUS' | 'GET_BLIPS' | 'ADD_BLIP' | 'MODIFY_BLIP' | 'REMOVE_BLIP' | '911_CALL' | 'REMOVE_911' | 'GET_CALLS' | 'GET_ACTIVE_UNITS' | 'KICK_UNIT' | 'NEW_DISPATCH' | 'ATTACH_UNIT' | 'DETACH_UNIT' | 'SET_CALL_POSTAL' | 'SET_CALL_PRIMARY' | 'ADD_CALL_NOTE' | 'CLOSE_CALL' | 'UNIT_LOCATION' | 'SET_STREETSIGN_CONFIG' | 'UPDATE_STREETSIGN' | 'GET_COM_ACCOUNT' | 'GET_DEPARTMENTS' | 'GET_SUB_VERSION' | 'GET_CURRENT_CLOCK_IN' | 'GET_ACCOUNTS' | 'GET_PROFILE_FIELDS' | 'CHECK_COM_APIID' | 'VERIFY_WHITELIST' | 'CLOCK_IN_OUT' | 'FULL_WHITELIST' | 'GET_ACCOUNT_RANKS' | 'SET_ACCOUNT_RANKS' | 'RSVP' | 'CHANGE_FORM_STAGE' | 'GET_FORM_TEMPLATE_SUBMISSIONS' | 'KICK_ACCOUNT' | 'BAN_ACCOUNT' | 'LOOKUP' | 'EDIT_ACC_PROFLIE_FIELDS' | 'SET_ACCOUNT_NAME' | 'FORCE_SYNC' | 'TRIGGER_PROMOTION_FLOWS' | 'GET_PROMOTION_FLOWS' | 'SET_GAME_SERVERS' | 'ERLC_GET_ONLINE_PLAYERS' | 'ERLC_GET_PLAYER_QUEUE' | 'ERLC_ADD_NEW_RECORD' | 'ERLC_EXECUTE_COMMAND' | 'RADIO_GET_COMMUNITY_CHANNELS' | 'RADIO_GET_CONNECTED_USERS' | 'RADIO_GET_CONNECTED_USER' | 'RADIO_SET_USER_CHANNELS' | 'RADIO_SET_USER_DISPLAY_NAME' | 'RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP' | 'RADIO_SET_SERVER_IP' | 'RADIO_SET_IN_GAME_SPEAKER_LOCATIONS' | 'PLAY_TONE';
42
42
  export interface CMSServerAPIStruct {
43
43
  id: number;
44
44
  name: string;
@@ -555,6 +555,9 @@ export interface RESTTypedAPIDataStructs {
555
555
  playerRobloxId?: string | number,
556
556
  points?: number
557
557
  ];
558
+ ERLC_EXECUTE_COMMAND: [
559
+ commands: CMSERLCExecuteCommandPayload[]
560
+ ];
558
561
  RADIO_GET_COMMUNITY_CHANNELS: [];
559
562
  RADIO_GET_CONNECTED_USERS: [];
560
563
  RADIO_GET_CONNECTED_USER: [
@@ -577,6 +580,11 @@ export interface RESTTypedAPIDataStructs {
577
580
  locations: RadioSpeakerLocation[],
578
581
  token?: string
579
582
  ];
583
+ PLAY_TONE: [
584
+ roomId: number,
585
+ tones: number[],
586
+ playTo: RadioTonePlayTarget[]
587
+ ];
580
588
  }
581
589
  export type PossibleRequestData = undefined | {
582
590
  data: CADPenalCodeStruct[] | CADSetAPIIDStruct | CADNewEditRecordOptionOneStruct | CADNewEditRecordOptionTwoStruct | CADLookupByIntStruct | CADLookupStruct | CADModifyAccountPermsStruct | CADKickBanUserStruct | CADSetPostalStruct[] | CADModifyIdentifierStruct | CADAddBlipStruct[] | CADModifyBlipStruct[] | CADGetCallsStruct | CADGetActiveUnitsStruct | CADNewDispatchStruct | CMSTriggerPromotionFlowPayload[];
@@ -511,6 +511,12 @@ exports.ERLCMSAPITypes = [
511
511
  path: "erlc/add_new_record",
512
512
  method: "POST",
513
513
  minVersion: 0
514
+ },
515
+ {
516
+ type: "ERLC_EXECUTE_COMMAND",
517
+ path: "erlc/execute_command",
518
+ method: "POST",
519
+ minVersion: 0
514
520
  }
515
521
  ];
516
522
  exports.RadioAPITypes = [
@@ -561,6 +567,12 @@ exports.RadioAPITypes = [
561
567
  path: 'radio/set-server-speakers',
562
568
  method: 'POST',
563
569
  minVersion: 0
570
+ },
571
+ {
572
+ type: 'PLAY_TONE',
573
+ path: 'api/play-tone',
574
+ method: 'POST',
575
+ minVersion: 0
564
576
  }
565
577
  ];
566
578
  function formatForAll(array, product) {
@@ -260,6 +260,10 @@ export declare class CMSManager extends BaseManager {
260
260
  * @returns {Promise} Promise object indicates success and includes the queue count when successful.
261
261
  */
262
262
  erlcGetPlayerQueue(robloxJoinCode: string): Promise<globalTypes.CMSERLCGetPlayerQueuePromiseResult>;
263
+ /**
264
+ * Sends an ERLC command payload to CMS.
265
+ */
266
+ erlcExecuteCommand(commands: globalTypes.CMSERLCExecuteCommandPayload[]): Promise<globalTypes.CMSERLCExecuteCommandPromiseResult>;
263
267
  /**
264
268
  * Adds a new ERLC record for a player.
265
269
  * @param {Object} data The object that contains critical data to add a new ERLC record.
@@ -627,6 +627,29 @@ class CMSManager extends BaseManager_1.BaseManager {
627
627
  }
628
628
  });
629
629
  }
630
+ /**
631
+ * Sends an ERLC command payload to CMS.
632
+ */
633
+ async erlcExecuteCommand(commands) {
634
+ if (!Array.isArray(commands) || commands.length === 0) {
635
+ throw new Error('ERLC execute command requires at least one command payload.');
636
+ }
637
+ return new Promise(async (resolve, reject) => {
638
+ var _a;
639
+ try {
640
+ const erlcExecuteCommandRequest = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('ERLC_EXECUTE_COMMAND', commands));
641
+ resolve({ success: true, data: erlcExecuteCommandRequest });
642
+ }
643
+ catch (err) {
644
+ if (err instanceof src_1.APIError) {
645
+ resolve({ success: false, reason: err.response });
646
+ }
647
+ else {
648
+ reject(err);
649
+ }
650
+ }
651
+ });
652
+ }
630
653
  /**
631
654
  * Adds a new ERLC record for a player.
632
655
  * @param {Object} data The object that contains critical data to add a new ERLC record.
@@ -52,4 +52,11 @@ export declare class RadioManager extends BaseManager {
52
52
  * @param {string} [token] Optional bearer token for authorization. Defaults to the community API key.
53
53
  */
54
54
  setInGameSpeakerLocations(locations: globalTypes.RadioSpeakerLocation[], token?: string): Promise<globalTypes.RadioSetInGameSpeakerLocationsPromiseResult>;
55
+ /**
56
+ * Plays one or more tones to radio channels, groups, or in-game speakers.
57
+ * @param {number} roomId Multi-server room id for tone playback.
58
+ * @param {number[]} tones Tone identifiers to play.
59
+ * @param {globalTypes.RadioTonePlayTarget[]} playTo Targets that should receive the tones.
60
+ */
61
+ playTone(roomId: number, tones: number[], playTo: globalTypes.RadioTonePlayTarget[]): Promise<globalTypes.RadioPlayTonePromiseResult>;
55
62
  }
@@ -220,5 +220,28 @@ class RadioManager extends BaseManager_1.BaseManager {
220
220
  }
221
221
  });
222
222
  }
223
+ /**
224
+ * Plays one or more tones to radio channels, groups, or in-game speakers.
225
+ * @param {number} roomId Multi-server room id for tone playback.
226
+ * @param {number[]} tones Tone identifiers to play.
227
+ * @param {globalTypes.RadioTonePlayTarget[]} playTo Targets that should receive the tones.
228
+ */
229
+ async playTone(roomId, tones, playTo) {
230
+ return new Promise(async (resolve, reject) => {
231
+ var _a, _b;
232
+ try {
233
+ const response = await ((_a = this.rest) === null || _a === void 0 ? void 0 : _a.request('PLAY_TONE', roomId, tones, playTo));
234
+ resolve({ success: true, result: (_b = response === null || response === void 0 ? void 0 : response.result) !== null && _b !== void 0 ? _b : response });
235
+ }
236
+ catch (err) {
237
+ if (err instanceof src_1.APIError) {
238
+ resolve({ success: false, reason: err.response });
239
+ }
240
+ else {
241
+ reject(err);
242
+ }
243
+ }
244
+ });
245
+ }
223
246
  }
224
247
  exports.RadioManager = RadioManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonoransoftware/sonoran.js",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
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
@@ -1,5 +1,10 @@
1
1
  # Sonoran.js
2
- Sonoran.js is a library that allows you to interact with the [Sonoran CAD](https://sonorancad.com/) and [Sonoran CMS](https://sonorancms.com/) API. Based off of and utilizes several Discord.js library techniques for ease of use.
2
+ Sonoran.js is a library that allows you to interact with the [Sonoran CAD](https://docs.sonoransoftware.com/cad), [Sonoran Radio](https://docs.sonoransoftware.com/radio), and [Sonoran CMS](https://docs.sonoransoftware.com/cms) API. Based off of and utilizes several Discord.js library techniques for ease of use.
3
+
4
+ ## Installation
5
+ ```js
6
+ npm i @sonoransoftware/sonoran.js
7
+ ```
3
8
 
4
9
  ## Example Instance Setup
5
10
 
@@ -458,5 +463,14 @@ await instance.radio.setInGameSpeakerLocations(
458
463
  );
459
464
  ```
460
465
 
466
+ ### playTone(roomId, tones, playTo)
467
+ Dispatches tones to channels, groups, or in-game speakers.
468
+ ```js
469
+ await instance.radio.playTone(1, [1001, 1002], [
470
+ { label: 'Primary Dispatch', type: 'channel', value: 10, group: 2 },
471
+ { label: 'Station Speakers', type: 'game', value: 'station-1', group: null }
472
+ ]);
473
+ ```
474
+
461
475
  ## Further Documentation
462
476
  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).
package/src/constants.ts CHANGED
@@ -443,6 +443,19 @@ export interface CMSERLCAddNewRecordPromiseResult {
443
443
  logId?: string;
444
444
  }
445
445
 
446
+ export interface CMSERLCExecuteCommandPayload {
447
+ type: string;
448
+ args: unknown;
449
+ discordId: string | number;
450
+ includesPlayerNameOrId: boolean;
451
+ }
452
+
453
+ export interface CMSERLCExecuteCommandPromiseResult {
454
+ success: boolean;
455
+ reason?: string;
456
+ data?: unknown;
457
+ }
458
+
446
459
  export interface RadioChannelGroup {
447
460
  id: number;
448
461
  name: string;
@@ -498,6 +511,17 @@ export interface RadioSpeakerLocation {
498
511
  id: string;
499
512
  }
500
513
 
514
+ export type RadioTonePlayTargetType = 'channel' | 'group' | 'game';
515
+
516
+ export interface RadioTonePlayTarget {
517
+ label: string;
518
+ type: RadioTonePlayTargetType;
519
+ value: unknown;
520
+ group: number | null;
521
+ icon?: string;
522
+ color?: string;
523
+ }
524
+
501
525
  export interface RadioSetUserChannelsOptions {
502
526
  transmit?: number;
503
527
  scan?: number[];
@@ -569,3 +593,9 @@ export interface RadioSetInGameSpeakerLocationsPromiseResult {
569
593
  reason?: string;
570
594
  result?: string;
571
595
  }
596
+
597
+ export interface RadioPlayTonePromiseResult {
598
+ success: boolean;
599
+ reason?: string;
600
+ result?: string;
601
+ }
package/src/index.ts CHANGED
@@ -41,5 +41,10 @@ export {
41
41
  CMSPromotionFlow,
42
42
  CMSTriggerPromotionFlowsPromiseResult,
43
43
  CMSGetPromotionFlowsPromiseResult,
44
- CMSGetFormSubmissionsPromiseResult
44
+ CMSGetFormSubmissionsPromiseResult,
45
+ CMSERLCGetOnlinePlayersPromiseResult,
46
+ CMSERLCGetPlayerQueuePromiseResult,
47
+ CMSERLCAddNewRecordPromiseResult,
48
+ CMSERLCExecuteCommandPayload,
49
+ CMSERLCExecuteCommandPromiseResult
45
50
  } from './constants';
@@ -404,6 +404,13 @@ export class REST extends EventEmitter {
404
404
  points: args[6],
405
405
  }
406
406
  }
407
+ case 'ERLC_EXECUTE_COMMAND': {
408
+ const payload = args[0];
409
+ if (!Array.isArray(payload)) {
410
+ throw new Error('ERLC_EXECUTE_COMMAND requires an array of command payloads.');
411
+ }
412
+ return payload;
413
+ }
407
414
  case 'RADIO_GET_COMMUNITY_CHANNELS':
408
415
  case 'RADIO_GET_CONNECTED_USERS':
409
416
  case 'RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP': {
@@ -438,6 +445,13 @@ export class REST extends EventEmitter {
438
445
  token: args[1]
439
446
  }
440
447
  }
448
+ case 'PLAY_TONE': {
449
+ return {
450
+ roomId: args[0],
451
+ tones: args[1],
452
+ playTo: args[2]
453
+ }
454
+ }
441
455
  default: {
442
456
  return args;
443
457
  }
@@ -174,6 +174,10 @@ export class RequestManager extends EventEmitter {
174
174
  apiData.data.data = [];
175
175
  break;
176
176
  }
177
+ case 'ERLC_EXECUTE_COMMAND': {
178
+ apiData.data.data = clonedData;
179
+ break;
180
+ }
177
181
  case 'SET_PENAL_CODES': {
178
182
  apiData.data.data = [clonedData[0]];
179
183
  break;
@@ -457,6 +461,35 @@ export class RequestManager extends EventEmitter {
457
461
  path = apiType.path;
458
462
  break;
459
463
  }
464
+ case 'PLAY_TONE': {
465
+ const auth = ensureAuth();
466
+ const roomIdRaw = payload?.roomId;
467
+ if (roomIdRaw === undefined || roomIdRaw === null) {
468
+ throw new Error('roomId is required for PLAY_TONE requests.');
469
+ }
470
+ const roomIdNumber = typeof roomIdRaw === 'number' ? roomIdRaw : Number(roomIdRaw);
471
+ if (Number.isNaN(roomIdNumber)) {
472
+ throw new Error('roomId must be a number for PLAY_TONE requests.');
473
+ }
474
+ const tones = payload?.tones;
475
+ if (!Array.isArray(tones) || tones.length === 0) {
476
+ throw new Error('tones array is required for PLAY_TONE requests.');
477
+ }
478
+ const playTo = payload?.playTo;
479
+ if (!Array.isArray(playTo) || playTo.length === 0) {
480
+ throw new Error('playTo array is required for PLAY_TONE requests.');
481
+ }
482
+ method = 'POST';
483
+ body = {
484
+ id: auth.id,
485
+ key: auth.key,
486
+ roomId: roomIdNumber,
487
+ tones,
488
+ playTo
489
+ };
490
+ path = apiType.path;
491
+ break;
492
+ }
460
493
  default: {
461
494
  throw new Error(`Unsupported radio API type received: ${apiType.type}`);
462
495
  }
@@ -1,4 +1,4 @@
1
- import { productEnums, RadioSetUserChannelsOptions, RadioSpeakerLocation, CMSProfileFieldUpdate, CMSSetGameServerStruct, CMSTriggerPromotionFlowPayload } from '../../../../../constants';
1
+ import { productEnums, RadioSetUserChannelsOptions, RadioSpeakerLocation, RadioTonePlayTarget, CMSProfileFieldUpdate, CMSSetGameServerStruct, CMSTriggerPromotionFlowPayload, CMSERLCExecuteCommandPayload } from '../../../../../constants';
2
2
  import type { RESTOptions } from '../REST';
3
3
 
4
4
  export const DefaultUserAgent = 'Sonoran.js NPM Module';
@@ -548,6 +548,12 @@ export const ERLCMSAPITypes: APITypeData[] = [
548
548
  path: "erlc/add_new_record",
549
549
  method: "POST",
550
550
  minVersion: 0
551
+ },
552
+ {
553
+ type: "ERLC_EXECUTE_COMMAND",
554
+ path: "erlc/execute_command",
555
+ method: "POST",
556
+ minVersion: 0
551
557
  }
552
558
  ];
553
559
 
@@ -599,6 +605,12 @@ export const RadioAPITypes: APITypeData[] = [
599
605
  path: 'radio/set-server-speakers',
600
606
  method: 'POST',
601
607
  minVersion: 0
608
+ },
609
+ {
610
+ type: 'PLAY_TONE',
611
+ path: 'api/play-tone',
612
+ method: 'POST',
613
+ minVersion: 0
602
614
  }
603
615
  ];
604
616
 
@@ -613,7 +625,7 @@ function formatForAll(array: APITypeData[], product: productEnums): AllAPITypeDa
613
625
 
614
626
  export const AllAPITypes: AllAPITypeData[] = [ ...formatForAll(GeneralCADAPITypes, productEnums.CAD), ...formatForAll(CivilianCADAPITypes, productEnums.CAD), ...formatForAll(EmergencyCADAPITypes, productEnums.CAD), ...formatForAll(GeneralCMSAPITypes, productEnums.CMS), ...formatForAll(ServersCMSAPITypes, productEnums.CMS), ...formatForAll(EventsCMSAPITypes, productEnums.CMS), ...formatForAll(FormsCMSAPITypes, productEnums.CMS), ...formatForAll(CommunitiesCMSAPITypes, productEnums.CMS), ...formatForAll(ERLCMSAPITypes, productEnums.CMS), ...formatForAll(RadioAPITypes, productEnums.RADIO) ];
615
627
 
616
- export type AllAPITypesType = 'GET_SERVERS' | 'SET_SERVERS' | 'GET_VERSION' | 'SET_PENAL_CODES' | 'SET_API_ID' | 'GET_TEMPLATES' | 'NEW_RECORD' | 'EDIT_RECORD' | 'REMOVE_RECORD' | 'LOOKUP_INT' | 'LOOKUP' | 'GET_ACCOUNT' | 'CHECK_APIID' | 'APPLY_PERMISSION_KEY' | 'SET_ACCOUNT_PERMISSIONS' | 'BAN_USER' | 'VERIFY_SECRET' | 'AUTH_STREETSIGNS' | 'SET_POSTALS' | 'SEND_PHOTO' | 'SET_CLOCK' | 'JOIN_COMMUNITY' | 'LEAVE_COMMUNITY' | 'GET_CHARACTERS' | 'NEW_CHARACTER' | 'EDIT_CHARACTER' | 'REMOVE_CHARACTER' | 'GET_IDENTIFIERS' | 'MODIFY_IDENTIFIER' | 'SET_IDENTIFIER' | 'UNIT_PANIC' | 'UNIT_STATUS' | 'GET_BLIPS' | 'ADD_BLIP' | 'MODIFY_BLIP' | 'REMOVE_BLIP' | '911_CALL' | 'REMOVE_911' | 'GET_CALLS' | 'GET_ACTIVE_UNITS' | 'KICK_UNIT' | 'NEW_DISPATCH' | 'ATTACH_UNIT' | 'DETACH_UNIT' | 'SET_CALL_POSTAL' | 'SET_CALL_PRIMARY' | 'ADD_CALL_NOTE' | 'CLOSE_CALL' | 'UNIT_LOCATION' | 'SET_STREETSIGN_CONFIG' | 'UPDATE_STREETSIGN' | 'GET_COM_ACCOUNT' | 'GET_DEPARTMENTS' | 'GET_SUB_VERSION' | 'GET_CURRENT_CLOCK_IN' | 'GET_ACCOUNTS' | 'GET_PROFILE_FIELDS' | 'CHECK_COM_APIID' | 'VERIFY_WHITELIST' | 'CLOCK_IN_OUT' | 'FULL_WHITELIST' | 'GET_ACCOUNT_RANKS' | 'SET_ACCOUNT_RANKS' | 'RSVP' | 'CHANGE_FORM_STAGE' | 'GET_FORM_TEMPLATE_SUBMISSIONS' | 'KICK_ACCOUNT' | 'BAN_ACCOUNT' | 'LOOKUP' | 'EDIT_ACC_PROFLIE_FIELDS' | 'SET_ACCOUNT_NAME' | 'FORCE_SYNC' | 'TRIGGER_PROMOTION_FLOWS' | 'GET_PROMOTION_FLOWS' | 'SET_GAME_SERVERS' | 'ERLC_GET_ONLINE_PLAYERS' | 'ERLC_GET_PLAYER_QUEUE' | 'ERLC_ADD_NEW_RECORD' | 'RADIO_GET_COMMUNITY_CHANNELS' | 'RADIO_GET_CONNECTED_USERS' | 'RADIO_GET_CONNECTED_USER' | 'RADIO_SET_USER_CHANNELS' | 'RADIO_SET_USER_DISPLAY_NAME' | 'RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP' | 'RADIO_SET_SERVER_IP' | 'RADIO_SET_IN_GAME_SPEAKER_LOCATIONS';
628
+ export type AllAPITypesType = 'GET_SERVERS' | 'SET_SERVERS' | 'GET_VERSION' | 'SET_PENAL_CODES' | 'SET_API_ID' | 'GET_TEMPLATES' | 'NEW_RECORD' | 'EDIT_RECORD' | 'REMOVE_RECORD' | 'LOOKUP_INT' | 'LOOKUP' | 'GET_ACCOUNT' | 'CHECK_APIID' | 'APPLY_PERMISSION_KEY' | 'SET_ACCOUNT_PERMISSIONS' | 'BAN_USER' | 'VERIFY_SECRET' | 'AUTH_STREETSIGNS' | 'SET_POSTALS' | 'SEND_PHOTO' | 'SET_CLOCK' | 'JOIN_COMMUNITY' | 'LEAVE_COMMUNITY' | 'GET_CHARACTERS' | 'NEW_CHARACTER' | 'EDIT_CHARACTER' | 'REMOVE_CHARACTER' | 'GET_IDENTIFIERS' | 'MODIFY_IDENTIFIER' | 'SET_IDENTIFIER' | 'UNIT_PANIC' | 'UNIT_STATUS' | 'GET_BLIPS' | 'ADD_BLIP' | 'MODIFY_BLIP' | 'REMOVE_BLIP' | '911_CALL' | 'REMOVE_911' | 'GET_CALLS' | 'GET_ACTIVE_UNITS' | 'KICK_UNIT' | 'NEW_DISPATCH' | 'ATTACH_UNIT' | 'DETACH_UNIT' | 'SET_CALL_POSTAL' | 'SET_CALL_PRIMARY' | 'ADD_CALL_NOTE' | 'CLOSE_CALL' | 'UNIT_LOCATION' | 'SET_STREETSIGN_CONFIG' | 'UPDATE_STREETSIGN' | 'GET_COM_ACCOUNT' | 'GET_DEPARTMENTS' | 'GET_SUB_VERSION' | 'GET_CURRENT_CLOCK_IN' | 'GET_ACCOUNTS' | 'GET_PROFILE_FIELDS' | 'CHECK_COM_APIID' | 'VERIFY_WHITELIST' | 'CLOCK_IN_OUT' | 'FULL_WHITELIST' | 'GET_ACCOUNT_RANKS' | 'SET_ACCOUNT_RANKS' | 'RSVP' | 'CHANGE_FORM_STAGE' | 'GET_FORM_TEMPLATE_SUBMISSIONS' | 'KICK_ACCOUNT' | 'BAN_ACCOUNT' | 'LOOKUP' | 'EDIT_ACC_PROFLIE_FIELDS' | 'SET_ACCOUNT_NAME' | 'FORCE_SYNC' | 'TRIGGER_PROMOTION_FLOWS' | 'GET_PROMOTION_FLOWS' | 'SET_GAME_SERVERS' | 'ERLC_GET_ONLINE_PLAYERS' | 'ERLC_GET_PLAYER_QUEUE' | 'ERLC_ADD_NEW_RECORD' | 'ERLC_EXECUTE_COMMAND' | 'RADIO_GET_COMMUNITY_CHANNELS' | 'RADIO_GET_CONNECTED_USERS' | 'RADIO_GET_CONNECTED_USER' | 'RADIO_SET_USER_CHANNELS' | 'RADIO_SET_USER_DISPLAY_NAME' | 'RADIO_GET_SERVER_SUBSCRIPTION_FROM_IP' | 'RADIO_SET_SERVER_IP' | 'RADIO_SET_IN_GAME_SPEAKER_LOCATIONS' | 'PLAY_TONE';
617
629
 
618
630
  export interface CMSServerAPIStruct {
619
631
  id: number;
@@ -1164,6 +1176,9 @@ export interface RESTTypedAPIDataStructs {
1164
1176
  playerRobloxId?: string | number,
1165
1177
  points?: number,
1166
1178
  ]
1179
+ ERLC_EXECUTE_COMMAND: [
1180
+ commands: CMSERLCExecuteCommandPayload[]
1181
+ ]
1167
1182
  // Radio
1168
1183
  RADIO_GET_COMMUNITY_CHANNELS: [];
1169
1184
  RADIO_GET_CONNECTED_USERS: [];
@@ -1187,6 +1202,11 @@ export interface RESTTypedAPIDataStructs {
1187
1202
  locations: RadioSpeakerLocation[],
1188
1203
  token?: string
1189
1204
  ];
1205
+ PLAY_TONE: [
1206
+ roomId: number,
1207
+ tones: number[],
1208
+ playTo: RadioTonePlayTarget[]
1209
+ ];
1190
1210
  }
1191
1211
 
1192
1212
  export type PossibleRequestData =
@@ -562,6 +562,28 @@ export class CMSManager extends BaseManager {
562
562
  });
563
563
  }
564
564
 
565
+ /**
566
+ * Sends an ERLC command payload to CMS.
567
+ */
568
+ public async erlcExecuteCommand(commands: globalTypes.CMSERLCExecuteCommandPayload[]): Promise<globalTypes.CMSERLCExecuteCommandPromiseResult> {
569
+ if (!Array.isArray(commands) || commands.length === 0) {
570
+ throw new Error('ERLC execute command requires at least one command payload.');
571
+ }
572
+
573
+ return new Promise(async (resolve, reject) => {
574
+ try {
575
+ const erlcExecuteCommandRequest: any = await this.rest?.request('ERLC_EXECUTE_COMMAND', commands);
576
+ resolve({ success: true, data: erlcExecuteCommandRequest });
577
+ } catch (err) {
578
+ if (err instanceof APIError) {
579
+ resolve({ success: false, reason: err.response });
580
+ } else {
581
+ reject(err);
582
+ }
583
+ }
584
+ });
585
+ }
586
+
565
587
  /**
566
588
  * Adds a new ERLC record for a player.
567
589
  * @param {Object} data The object that contains critical data to add a new ERLC record.
@@ -184,4 +184,25 @@ export class RadioManager extends BaseManager {
184
184
  }
185
185
  });
186
186
  }
187
+
188
+ /**
189
+ * Plays one or more tones to radio channels, groups, or in-game speakers.
190
+ * @param {number} roomId Multi-server room id for tone playback.
191
+ * @param {number[]} tones Tone identifiers to play.
192
+ * @param {globalTypes.RadioTonePlayTarget[]} playTo Targets that should receive the tones.
193
+ */
194
+ public async playTone(roomId: number, tones: number[], playTo: globalTypes.RadioTonePlayTarget[]): Promise<globalTypes.RadioPlayTonePromiseResult> {
195
+ return new Promise(async (resolve, reject) => {
196
+ try {
197
+ const response: any = await this.rest?.request('PLAY_TONE', roomId, tones, playTo);
198
+ resolve({ success: true, result: response?.result ?? response });
199
+ } catch (err) {
200
+ if (err instanceof APIError) {
201
+ resolve({ success: false, reason: err.response });
202
+ } else {
203
+ reject(err);
204
+ }
205
+ }
206
+ });
207
+ }
187
208
  }