@honeybbq/teamspeak 1.0.0-beta.13 → 1.0.0-beta.14
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 +19 -1
- package/dist/client/api/files.d.ts +89 -0
- package/dist/client/api/files.d.ts.map +1 -0
- package/dist/client/api/files.js +125 -0
- package/dist/client/api/files.js.map +1 -0
- package/dist/client/client.d.ts +2 -2
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/core/api/channels.d.ts +53 -1
- package/dist/core/api/channels.d.ts.map +1 -1
- package/dist/core/api/channels.js +64 -1
- package/dist/core/api/channels.js.map +1 -1
- package/dist/core/api/clients.d.ts +87 -1
- package/dist/core/api/clients.d.ts.map +1 -1
- package/dist/core/api/clients.js +95 -1
- package/dist/core/api/clients.js.map +1 -1
- package/dist/core/api/command.d.ts +40 -1
- package/dist/core/api/command.d.ts.map +1 -1
- package/dist/core/api/command.js +26 -1
- package/dist/core/api/command.js.map +1 -1
- package/dist/core/api/files.d.ts +39 -1
- package/dist/core/api/files.d.ts.map +1 -1
- package/dist/core/api/files.js +46 -1
- package/dist/core/api/files.js.map +1 -1
- package/dist/core/api/groups.d.ts +28 -3
- package/dist/core/api/groups.d.ts.map +1 -1
- package/dist/core/api/groups.js +32 -3
- package/dist/core/api/groups.js.map +1 -1
- package/dist/core/api/moderation.d.ts +36 -2
- package/dist/core/api/moderation.d.ts.map +1 -1
- package/dist/core/api/moderation.js +36 -3
- package/dist/core/api/moderation.js.map +1 -1
- package/dist/core/api/server.d.ts +94 -1
- package/dist/core/api/server.d.ts.map +1 -1
- package/dist/core/api/server.js +105 -1
- package/dist/core/api/server.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/query/client.d.ts.map +1 -1
- package/dist/query/client.js +76 -5
- package/dist/query/client.js.map +1 -1
- package/dist/query/late-rows.d.ts +31 -0
- package/dist/query/late-rows.d.ts.map +1 -0
- package/dist/query/late-rows.js +49 -0
- package/dist/query/late-rows.js.map +1 -0
- package/package.json +1 -1
- package/src/client/api/files.ts +249 -0
- package/src/client/client.ts +2 -2
- package/src/client/index.ts +1 -0
- package/src/core/api/channels.ts +122 -1
- package/src/core/api/clients.ts +167 -1
- package/src/core/api/command.ts +74 -1
- package/src/core/api/files.ts +72 -1
- package/src/core/api/groups.ts +64 -3
- package/src/core/api/moderation.ts +52 -3
- package/src/core/api/server.ts +194 -1
- package/src/core/index.ts +8 -1
- package/src/query/client.ts +82 -4
- package/src/query/late-rows.ts +64 -0
package/src/core/api/channels.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
nonZero,
|
|
13
13
|
firstRow,
|
|
14
14
|
exec,
|
|
15
|
+
execRows,
|
|
16
|
+
continueOnErrorParams,
|
|
15
17
|
query,
|
|
16
18
|
list,
|
|
17
19
|
parseChannel,
|
|
@@ -22,7 +24,13 @@ import {
|
|
|
22
24
|
big,
|
|
23
25
|
str,
|
|
24
26
|
} from "./command.js";
|
|
25
|
-
import type {
|
|
27
|
+
import type {
|
|
28
|
+
BatchOptions,
|
|
29
|
+
Channel,
|
|
30
|
+
PermissionEntry,
|
|
31
|
+
PermissionValue,
|
|
32
|
+
WireNames,
|
|
33
|
+
} from "./command.js";
|
|
26
34
|
import type { CommandExecutor, CommandOptions } from "../command/request.js";
|
|
27
35
|
import type { ChannelID, ClientDBID } from "../command/ids.js";
|
|
28
36
|
import { asChannelID } from "../command/ids.js";
|
|
@@ -354,6 +362,71 @@ export async function addChannelPermission(
|
|
|
354
362
|
);
|
|
355
363
|
}
|
|
356
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Set several permissions on a channel, in one command: each permission is a
|
|
367
|
+
* row (`permsid=… permvalue=…|permsid=… permvalue=…`). With none, nothing is
|
|
368
|
+
* sent.
|
|
369
|
+
*
|
|
370
|
+
* The server checks every row before it applies any: a permission it does not
|
|
371
|
+
* know (2562) or a value it cannot read (1540) refuses the call, and none is
|
|
372
|
+
* set, with or without `continueOnError`. Measured on TeamSpeak 3 3.13.8 and
|
|
373
|
+
* TeamSpeak 6 beta13.1, over raw ServerQuery, SSH, WebQuery and the voice
|
|
374
|
+
* protocol alike. Resolves to nothing: the server never reports an answer per
|
|
375
|
+
* row.
|
|
376
|
+
*
|
|
377
|
+
* A permission the caller has too little modify power for (2570, or 2568 where
|
|
378
|
+
* the group power is short) is another matter: the rows are applied in order.
|
|
379
|
+
* Without `continueOnError` the first row that fails ends the call, so the rows
|
|
380
|
+
* before it are set and the rows after it are not; with it the rows that fail
|
|
381
|
+
* are skipped and the others set. The error is the failing row's, and which row
|
|
382
|
+
* is not reported; with several failing rows which error comes back was not
|
|
383
|
+
* measured. Measured on TeamSpeak 3 3.13.8 and TeamSpeak 6 beta13.1, over raw
|
|
384
|
+
* ServerQuery (TeamSpeak 3), SSH, WebQuery and the voice protocol, in both
|
|
385
|
+
* orders of a free and a locked permission.
|
|
386
|
+
*/
|
|
387
|
+
export async function addChannelPermissions(
|
|
388
|
+
client: CommandExecutor,
|
|
389
|
+
cid: ChannelID,
|
|
390
|
+
permissions: readonly PermissionValue[],
|
|
391
|
+
options: BatchOptions = {},
|
|
392
|
+
): Promise<void> {
|
|
393
|
+
await execRows(
|
|
394
|
+
client,
|
|
395
|
+
"channeladdperm",
|
|
396
|
+
[...continueOnErrorParams(options), ["cid", cid]],
|
|
397
|
+
permissions.map((entry) => [permissionParam(entry.permission), ["permvalue", entry.value]]),
|
|
398
|
+
options,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Remove several permissions from a channel, in one command: each permission is
|
|
404
|
+
* a row (`permsid=…|permsid=…`). With none, nothing is sent.
|
|
405
|
+
*
|
|
406
|
+
* The server checks every row first, and an unknown permission (2562) refuses
|
|
407
|
+
* the call with none removed. Removing a permission the channel does not have
|
|
408
|
+
* succeeds. A permission the caller has too little modify power for (2570) ends
|
|
409
|
+
* the call at that row: the rows before it are removed and the rows after it
|
|
410
|
+
* are not, measured on TeamSpeak 3 3.13.8 and TeamSpeak 6 beta13.1 over every
|
|
411
|
+
* query transport and the voice protocol. The server documents no
|
|
412
|
+
* `-continueonerror` for this command, so none is offered, and sent anyway it
|
|
413
|
+
* changed nothing.
|
|
414
|
+
*/
|
|
415
|
+
export async function deleteChannelPermissions(
|
|
416
|
+
client: CommandExecutor,
|
|
417
|
+
cid: ChannelID,
|
|
418
|
+
permissions: readonly Permission[],
|
|
419
|
+
options?: CommandOptions,
|
|
420
|
+
): Promise<void> {
|
|
421
|
+
await execRows(
|
|
422
|
+
client,
|
|
423
|
+
"channeldelperm",
|
|
424
|
+
[["cid", cid]],
|
|
425
|
+
permissions.map((permission) => [permissionParam(permission)]),
|
|
426
|
+
options,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
357
430
|
/** Remove a permission from a channel. */
|
|
358
431
|
export async function deleteChannelPermission(
|
|
359
432
|
client: CommandExecutor,
|
|
@@ -409,6 +482,54 @@ export async function addChannelClientPermission(
|
|
|
409
482
|
);
|
|
410
483
|
}
|
|
411
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Set several permissions on one client within one channel, in one command:
|
|
487
|
+
* each permission is a row. With none, nothing is sent. The server's answer
|
|
488
|
+
* is as for {@link addChannelPermissions}: an unknown permission or a value it
|
|
489
|
+
* cannot read refuses the call with none set, and a row the caller lacks the
|
|
490
|
+
* power for ends it there, or is skipped with `continueOnError`.
|
|
491
|
+
*/
|
|
492
|
+
export async function addChannelClientPermissions(
|
|
493
|
+
client: CommandExecutor,
|
|
494
|
+
cid: ChannelID,
|
|
495
|
+
cldbid: ClientDBID,
|
|
496
|
+
permissions: readonly PermissionValue[],
|
|
497
|
+
options: BatchOptions = {},
|
|
498
|
+
): Promise<void> {
|
|
499
|
+
await execRows(
|
|
500
|
+
client,
|
|
501
|
+
"channelclientaddperm",
|
|
502
|
+
[...continueOnErrorParams(options), ["cid", cid], ["cldbid", cldbid]],
|
|
503
|
+
permissions.map((entry) => [permissionParam(entry.permission), ["permvalue", entry.value]]),
|
|
504
|
+
options,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Remove several permissions from one client within one channel, in one
|
|
510
|
+
* command. With none, nothing is sent. The server's answer is as for
|
|
511
|
+
* {@link deleteChannelPermissions}, and it documents no `-continueonerror` for
|
|
512
|
+
* this command either.
|
|
513
|
+
*/
|
|
514
|
+
export async function deleteChannelClientPermissions(
|
|
515
|
+
client: CommandExecutor,
|
|
516
|
+
cid: ChannelID,
|
|
517
|
+
cldbid: ClientDBID,
|
|
518
|
+
permissions: readonly Permission[],
|
|
519
|
+
options?: CommandOptions,
|
|
520
|
+
): Promise<void> {
|
|
521
|
+
await execRows(
|
|
522
|
+
client,
|
|
523
|
+
"channelclientdelperm",
|
|
524
|
+
[
|
|
525
|
+
["cid", cid],
|
|
526
|
+
["cldbid", cldbid],
|
|
527
|
+
],
|
|
528
|
+
permissions.map((permission) => [permissionParam(permission)]),
|
|
529
|
+
options,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
412
533
|
/** Remove a permission from one client within one channel. */
|
|
413
534
|
export async function deleteChannelClientPermission(
|
|
414
535
|
client: CommandExecutor,
|
package/src/core/api/clients.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
U16,
|
|
13
13
|
firstRow,
|
|
14
14
|
exec,
|
|
15
|
+
execRows,
|
|
16
|
+
continueOnErrorParams,
|
|
15
17
|
query,
|
|
16
18
|
list,
|
|
17
19
|
pages,
|
|
@@ -25,7 +27,14 @@ import {
|
|
|
25
27
|
optNum,
|
|
26
28
|
date,
|
|
27
29
|
} from "./command.js";
|
|
28
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
BatchOptions,
|
|
32
|
+
PagingOptions,
|
|
33
|
+
Params,
|
|
34
|
+
PermissionEntry,
|
|
35
|
+
PermissionValue,
|
|
36
|
+
WireNames,
|
|
37
|
+
} from "./command.js";
|
|
29
38
|
import type { CommandExecutor, CommandOptions } from "../command/request.js";
|
|
30
39
|
import type {
|
|
31
40
|
ChannelGroupID,
|
|
@@ -419,6 +428,43 @@ export async function moveClient(
|
|
|
419
428
|
);
|
|
420
429
|
}
|
|
421
430
|
|
|
431
|
+
/** How {@link moveClients} enters the channel. */
|
|
432
|
+
export interface ClientsMoveOptions extends ClientMoveOptions, BatchOptions {}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Move several clients to one channel, in one command, such as
|
|
436
|
+
* `clientmove clid=5|clid=6 cid=3`. With no ids nothing is sent.
|
|
437
|
+
*
|
|
438
|
+
* The server reports one answer for the whole call, never one per client, so
|
|
439
|
+
* this resolves to nothing and a failure says only what the server said.
|
|
440
|
+
* Measured on TeamSpeak 3 3.13.8 and TeamSpeak 6 beta13.1, over raw
|
|
441
|
+
* ServerQuery, SSH, WebQuery and the voice protocol alike:
|
|
442
|
+
*
|
|
443
|
+
* - Without `continueOnError`, the server checks every client first: one id it
|
|
444
|
+
* cannot move (no such client, error 512; a client already in the channel,
|
|
445
|
+
* 770) refuses the call, and no client is moved.
|
|
446
|
+
* - With it (`-continueonerror`), a client that cannot be moved is skipped, the
|
|
447
|
+
* others are moved, and the answer is success, even when none could be moved.
|
|
448
|
+
* Read the channel back, as `listClients` reports it, to know who moved.
|
|
449
|
+
*
|
|
450
|
+
* A channel that does not exist fails the call either way (768).
|
|
451
|
+
*/
|
|
452
|
+
export async function moveClients(
|
|
453
|
+
client: CommandExecutor,
|
|
454
|
+
clientIDs: readonly ClientID[],
|
|
455
|
+
channelID: ChannelID,
|
|
456
|
+
options: ClientsMoveOptions = {},
|
|
457
|
+
): Promise<void> {
|
|
458
|
+
await execRows(
|
|
459
|
+
client,
|
|
460
|
+
"clientmove",
|
|
461
|
+
[...continueOnErrorParams(options), ["cid", channelID], ["cpw", options.password]],
|
|
462
|
+
clientIDs.map((clid) => [["clid", clid]]),
|
|
463
|
+
options,
|
|
464
|
+
["cpw"],
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
422
468
|
/** The reasons a client can be kicked for. */
|
|
423
469
|
export type KickReason = typeof Reason.KickChannel | typeof Reason.KickServer;
|
|
424
470
|
|
|
@@ -456,6 +502,51 @@ export async function kickClient(
|
|
|
456
502
|
);
|
|
457
503
|
}
|
|
458
504
|
|
|
505
|
+
/** What {@link kickClients} tells the kicked clients, and whether it goes on past a failure. */
|
|
506
|
+
export interface ClientsKickOptions extends ClientKickOptions, BatchOptions {}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Kick several clients for one reason, in one command, such as
|
|
510
|
+
* `clientkick reasonid=4 clid=5|clid=6`. The reason is as for
|
|
511
|
+
* {@link kickClient}; with no ids nothing is sent.
|
|
512
|
+
*
|
|
513
|
+
* The server reports one answer for the whole call, never one per client, so
|
|
514
|
+
* this resolves to nothing and a failure says only what the server said.
|
|
515
|
+
* Measured on TeamSpeak 3 3.13.8 and TeamSpeak 6 beta13.1, over raw
|
|
516
|
+
* ServerQuery, SSH, WebQuery and the voice protocol alike:
|
|
517
|
+
*
|
|
518
|
+
* - Without `continueOnError`, the clients are kicked in the order given and
|
|
519
|
+
* the call stops at the first one that fails (an id no client has, 512):
|
|
520
|
+
* the clients before it are kicked, the ones after it are not, and the
|
|
521
|
+
* error is that client's.
|
|
522
|
+
* - With it (`-continueonerror`), every client is tried, a failing one is
|
|
523
|
+
* skipped, and the answer is success, even when none could be kicked. Read
|
|
524
|
+
* the clients back, as `listClients` reports them, to know who was kicked.
|
|
525
|
+
*/
|
|
526
|
+
export async function kickClients(
|
|
527
|
+
client: CommandExecutor,
|
|
528
|
+
clientIDs: readonly ClientID[],
|
|
529
|
+
reason: KickReason,
|
|
530
|
+
options: ClientsKickOptions = {},
|
|
531
|
+
): Promise<void> {
|
|
532
|
+
if (reason !== Reason.KickChannel && reason !== Reason.KickServer) {
|
|
533
|
+
throw new RangeError(
|
|
534
|
+
`a kick's reason is Reason.KickChannel or Reason.KickServer, not ${String(reason)}`,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
await execRows(
|
|
538
|
+
client,
|
|
539
|
+
"clientkick",
|
|
540
|
+
[
|
|
541
|
+
...continueOnErrorParams(options),
|
|
542
|
+
["reasonid", reason],
|
|
543
|
+
["reasonmsg", nonEmpty(options.reasonMessage ?? "")],
|
|
544
|
+
],
|
|
545
|
+
clientIDs.map((clid) => [["clid", clid]]),
|
|
546
|
+
options,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
459
550
|
/** Send a poke to a client. */
|
|
460
551
|
export async function pokeClient(
|
|
461
552
|
client: CommandExecutor,
|
|
@@ -1049,6 +1140,53 @@ export async function addClientPermission(
|
|
|
1049
1140
|
);
|
|
1050
1141
|
}
|
|
1051
1142
|
|
|
1143
|
+
/** One permission to set on a client, server-wide, as {@link addClientPermissions} takes it. */
|
|
1144
|
+
export interface ClientPermissionValue extends PermissionValue {
|
|
1145
|
+
/** Whether it skips channel and client overrides; false by default. Always sent (`permskip`). */
|
|
1146
|
+
readonly skip?: boolean | undefined;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Set several permissions on a client, server-wide, in one command: each
|
|
1151
|
+
* permission is a row (`permsid=… permvalue=… permskip=0|…`). With none,
|
|
1152
|
+
* nothing is sent.
|
|
1153
|
+
*
|
|
1154
|
+
* The server checks every row before it applies any: a permission it does not
|
|
1155
|
+
* know (2562) or a value it cannot read (1540) refuses the call, and none is
|
|
1156
|
+
* set, with or without `continueOnError`. Measured on TeamSpeak 3 3.13.8 and
|
|
1157
|
+
* TeamSpeak 6 beta13.1, over raw ServerQuery, SSH, WebQuery and the voice
|
|
1158
|
+
* protocol alike. Resolves to nothing: the server never reports one answer
|
|
1159
|
+
* per row.
|
|
1160
|
+
*
|
|
1161
|
+
* A permission the caller has too little modify power for (2570, or 2568 where
|
|
1162
|
+
* the group power is short) is another matter: the rows are applied in order.
|
|
1163
|
+
* Without `continueOnError` the first row that fails ends the call, so the rows
|
|
1164
|
+
* before it are set and the rows after it are not; with it the rows that fail
|
|
1165
|
+
* are skipped and the others set. The error is the failing row's, and which row
|
|
1166
|
+
* is not reported; with several failing rows which error comes back was not
|
|
1167
|
+
* measured. Measured on TeamSpeak 3 3.13.8 and TeamSpeak 6 beta13.1, over raw
|
|
1168
|
+
* ServerQuery (TeamSpeak 3), SSH, WebQuery and the voice protocol, in both
|
|
1169
|
+
* orders of a free and a locked permission.
|
|
1170
|
+
*/
|
|
1171
|
+
export async function addClientPermissions(
|
|
1172
|
+
client: CommandExecutor,
|
|
1173
|
+
cldbid: ClientDBID,
|
|
1174
|
+
permissions: readonly ClientPermissionValue[],
|
|
1175
|
+
options: BatchOptions = {},
|
|
1176
|
+
): Promise<void> {
|
|
1177
|
+
await execRows(
|
|
1178
|
+
client,
|
|
1179
|
+
"clientaddperm",
|
|
1180
|
+
[...continueOnErrorParams(options), ["cldbid", cldbid]],
|
|
1181
|
+
permissions.map((entry) => [
|
|
1182
|
+
permissionParam(entry.permission),
|
|
1183
|
+
["permvalue", entry.value],
|
|
1184
|
+
["permskip", entry.skip ?? false],
|
|
1185
|
+
]),
|
|
1186
|
+
options,
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1052
1190
|
/** Remove a permission from a client. */
|
|
1053
1191
|
export async function deleteClientPermission(
|
|
1054
1192
|
client: CommandExecutor,
|
|
@@ -1059,6 +1197,34 @@ export async function deleteClientPermission(
|
|
|
1059
1197
|
await exec(client, "clientdelperm", [["cldbid", cldbid], permissionParam(permission)], options);
|
|
1060
1198
|
}
|
|
1061
1199
|
|
|
1200
|
+
/**
|
|
1201
|
+
* Remove several permissions from a client, in one command: each permission is
|
|
1202
|
+
* a row (`permsid=…|permsid=…`). With none, nothing is sent.
|
|
1203
|
+
*
|
|
1204
|
+
* The server checks every row before it removes any: one permission it does
|
|
1205
|
+
* not know (2562) refuses the call and none is removed. Removing a permission
|
|
1206
|
+
* the client does not have succeeds. A permission the caller has too little
|
|
1207
|
+
* modify power for (2570) ends the call at that row: the rows before it are
|
|
1208
|
+
* removed and the rows after it are not, measured on TeamSpeak 3 3.13.8 and
|
|
1209
|
+
* TeamSpeak 6 beta13.1 over every query transport and the voice protocol. The
|
|
1210
|
+
* server documents no `-continueonerror` for this command, so none is offered,
|
|
1211
|
+
* and sent anyway it changed nothing.
|
|
1212
|
+
*/
|
|
1213
|
+
export async function deleteClientPermissions(
|
|
1214
|
+
client: CommandExecutor,
|
|
1215
|
+
cldbid: ClientDBID,
|
|
1216
|
+
permissions: readonly Permission[],
|
|
1217
|
+
options?: CommandOptions,
|
|
1218
|
+
): Promise<void> {
|
|
1219
|
+
await execRows(
|
|
1220
|
+
client,
|
|
1221
|
+
"clientdelperm",
|
|
1222
|
+
[["cldbid", cldbid]],
|
|
1223
|
+
permissions.map((permission) => [permissionParam(permission)]),
|
|
1224
|
+
options,
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1062
1228
|
// ---- Custom properties --------------------------------------------------------
|
|
1063
1229
|
|
|
1064
1230
|
/** One custom property of a client: a key and a value, both free text. */
|
package/src/core/api/command.ts
CHANGED
|
@@ -54,6 +54,47 @@ function commandOptions(options: CommandOptions | undefined): CommandOptions | u
|
|
|
54
54
|
return { signal: options.signal, timeoutMs: options.timeoutMs };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Send a command that takes a list and produces no data: `params` are the
|
|
59
|
+
* parameters of the first row, and every entry of `rows` is one more row of
|
|
60
|
+
* the list (`a=1 b=2|b=3`). With no rows, nothing is sent.
|
|
61
|
+
*/
|
|
62
|
+
async function execRows(
|
|
63
|
+
client: CommandExecutor,
|
|
64
|
+
command: string,
|
|
65
|
+
params: Params,
|
|
66
|
+
rows: readonly Params[],
|
|
67
|
+
options?: CommandOptions,
|
|
68
|
+
passwords: readonly string[] = [],
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
if (rows.length === 0) return;
|
|
71
|
+
await client.send({ command, params, rows, passwords }, commandOptions(options));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Send a list command that answers with rows, as {@link execRows} sends one.
|
|
76
|
+
* With no rows nothing is sent, and the answer is empty.
|
|
77
|
+
*/
|
|
78
|
+
async function queryRows(
|
|
79
|
+
client: CommandExecutor,
|
|
80
|
+
command: string,
|
|
81
|
+
params: Params,
|
|
82
|
+
rows: readonly Params[],
|
|
83
|
+
options?: CommandOptions,
|
|
84
|
+
): Promise<Array<Record<string, string>>> {
|
|
85
|
+
if (rows.length === 0) return [];
|
|
86
|
+
return client.send({ command, params, rows, passwords: [] }, commandOptions(options));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The `-continueonerror` switch, as a command's first parameter: the server
|
|
91
|
+
* reads it from the first row only, so a switch written after the last row
|
|
92
|
+
* does nothing.
|
|
93
|
+
*/
|
|
94
|
+
const continueOnErrorParams = (options: {
|
|
95
|
+
readonly continueOnError?: boolean | undefined;
|
|
96
|
+
}): Params => (options.continueOnError === true ? [["-continueonerror", true]] : []);
|
|
97
|
+
|
|
57
98
|
/** Send a command and return its response rows. */
|
|
58
99
|
async function query(
|
|
59
100
|
client: CommandExecutor,
|
|
@@ -187,7 +228,17 @@ function firstRow(
|
|
|
187
228
|
return row;
|
|
188
229
|
}
|
|
189
230
|
|
|
190
|
-
export {
|
|
231
|
+
export {
|
|
232
|
+
exec,
|
|
233
|
+
execRows,
|
|
234
|
+
query,
|
|
235
|
+
queryRows,
|
|
236
|
+
list,
|
|
237
|
+
pages,
|
|
238
|
+
commandOptions,
|
|
239
|
+
continueOnErrorParams,
|
|
240
|
+
firstRow,
|
|
241
|
+
};
|
|
191
242
|
export type { ParamValue };
|
|
192
243
|
|
|
193
244
|
// ---- Field tables -----------------------------------------------------------
|
|
@@ -722,6 +773,28 @@ function parseChannelChanges(row: Record<string, string>): ChannelChanges {
|
|
|
722
773
|
|
|
723
774
|
export { parseChannel, parseChannelChanges };
|
|
724
775
|
|
|
776
|
+
/**
|
|
777
|
+
* What a command that takes a list of options carries beside the usual signal
|
|
778
|
+
* and timeout: whether the server keeps going past an item that fails.
|
|
779
|
+
*/
|
|
780
|
+
export interface BatchOptions extends CommandOptions {
|
|
781
|
+
/**
|
|
782
|
+
* Send `-continueonerror`, which the server documents for this command: it
|
|
783
|
+
* processes every item and skips the ones that fail, instead of stopping at
|
|
784
|
+
* the first. What it then answers differs by command, and each function says.
|
|
785
|
+
* False by default.
|
|
786
|
+
*/
|
|
787
|
+
readonly continueOnError?: boolean | undefined;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** One permission to set, with its value: an entry of a batch that adds permissions. */
|
|
791
|
+
export interface PermissionValue {
|
|
792
|
+
/** The permission to set. */
|
|
793
|
+
readonly permission: Permission;
|
|
794
|
+
/** Its value (`permvalue`). */
|
|
795
|
+
readonly value: number;
|
|
796
|
+
}
|
|
797
|
+
|
|
725
798
|
/** One entry of a permission list. */
|
|
726
799
|
export interface PermissionEntry {
|
|
727
800
|
/**
|
package/src/core/api/files.ts
CHANGED
|
@@ -96,6 +96,73 @@ export async function listFiles(
|
|
|
96
96
|
return rows.map(parseFileEntry);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** One entry of a recursive listing: what {@link listFiles} reports, with where it is. */
|
|
100
|
+
export interface FileTreeEntry extends FileEntry {
|
|
101
|
+
/**
|
|
102
|
+
* The entry's full path in the channel's file area, from its root: `/a/b.txt`
|
|
103
|
+
* for the file `b.txt` in the directory `a`. A directory's path has no
|
|
104
|
+
* trailing slash.
|
|
105
|
+
*/
|
|
106
|
+
readonly path: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** How {@link listFilesRecursive} walks the file area. */
|
|
110
|
+
export interface FileTreeOptions extends FileOptions {
|
|
111
|
+
/**
|
|
112
|
+
* How many directory levels to go down from the starting path: 0 lists the
|
|
113
|
+
* starting directory only, as {@link listFiles} does; 1 also lists the
|
|
114
|
+
* directories in it; and so on. 16 by default. A non-negative integer.
|
|
115
|
+
*/
|
|
116
|
+
readonly maxDepth?: number | undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A path without the slashes at its end; "/" becomes "". */
|
|
120
|
+
function withoutTrailingSlashes(path: string): string {
|
|
121
|
+
let end = path.length;
|
|
122
|
+
while (end > 0 && path.charCodeAt(end - 1) === 0x2f) end--;
|
|
123
|
+
return path.slice(0, end);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** How far {@link listFilesRecursive} goes down unless told otherwise. */
|
|
127
|
+
const DEFAULT_MAX_DEPTH = 16;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Every file and directory under a path in a channel's file area, in one list:
|
|
131
|
+
* the entries of the directory in the order {@link listFiles} gives them, each
|
|
132
|
+
* directory followed by what it holds.
|
|
133
|
+
*
|
|
134
|
+
* It lists one directory at a time, one command each, so a large tree costs
|
|
135
|
+
* that many round trips. `maxDepth` bounds how far it goes down; a directory
|
|
136
|
+
* at the bound is listed, but not entered. The options (password, signal,
|
|
137
|
+
* timeoutMs) reach every command, and the first error ends the walk. An empty
|
|
138
|
+
* directory has no entries, and a path that is not a directory fails as
|
|
139
|
+
* {@link listFiles} does: TS3 3.13.8 answers 2052 and TS6 beta13.1 2051 for a
|
|
140
|
+
* path that does not exist.
|
|
141
|
+
*
|
|
142
|
+
* File commands are not served over WebQuery (error 5120).
|
|
143
|
+
*/
|
|
144
|
+
export async function listFilesRecursive(
|
|
145
|
+
client: CommandExecutor,
|
|
146
|
+
cid: ChannelID,
|
|
147
|
+
path: string,
|
|
148
|
+
options: FileTreeOptions = {},
|
|
149
|
+
): Promise<FileTreeEntry[]> {
|
|
150
|
+
const { maxDepth = DEFAULT_MAX_DEPTH, ...listOptions } = options;
|
|
151
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 0) {
|
|
152
|
+
throw new RangeError(`maxDepth is a non-negative integer, not ${String(maxDepth)}`);
|
|
153
|
+
}
|
|
154
|
+
const found: FileTreeEntry[] = [];
|
|
155
|
+
const walk = async (directory: string, depth: number): Promise<void> => {
|
|
156
|
+
for (const entry of await listFiles(client, cid, directory, listOptions)) {
|
|
157
|
+
const entryPath = `${withoutTrailingSlashes(directory)}/${entry.name}`;
|
|
158
|
+
found.push({ ...entry, path: entryPath });
|
|
159
|
+
if (entry.isDirectory && depth < maxDepth) await walk(entryPath, depth + 1);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
await walk(asPath(path), 0);
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
|
|
99
166
|
/** Size and timestamp of one file. */
|
|
100
167
|
export async function getFileInfo(
|
|
101
168
|
client: CommandExecutor,
|
|
@@ -144,7 +211,11 @@ export async function deleteFile(
|
|
|
144
211
|
);
|
|
145
212
|
}
|
|
146
213
|
|
|
147
|
-
/**
|
|
214
|
+
/**
|
|
215
|
+
* Create a directory. Its parent has to exist: measured on TS3 3.13.8 and TS6
|
|
216
|
+
* beta13.1, a path whose parent is missing is refused with 2052 (file
|
|
217
|
+
* input/output error), so nested directories are made one level at a time.
|
|
218
|
+
*/
|
|
148
219
|
export async function createDirectory(
|
|
149
220
|
client: CommandExecutor,
|
|
150
221
|
cid: ChannelID,
|
package/src/core/api/groups.ts
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
U32,
|
|
9
9
|
firstRow,
|
|
10
10
|
exec,
|
|
11
|
+
execRows,
|
|
12
|
+
continueOnErrorParams,
|
|
11
13
|
query,
|
|
12
14
|
list,
|
|
13
15
|
encodeFields,
|
|
@@ -21,7 +23,13 @@ import {
|
|
|
21
23
|
bool,
|
|
22
24
|
date,
|
|
23
25
|
} from "./command.js";
|
|
24
|
-
import type {
|
|
26
|
+
import type {
|
|
27
|
+
BatchOptions,
|
|
28
|
+
Params,
|
|
29
|
+
PermissionEntry,
|
|
30
|
+
PermissionValue,
|
|
31
|
+
WireNames,
|
|
32
|
+
} from "./command.js";
|
|
25
33
|
import type { CommandExecutor, CommandOptions } from "../command/request.js";
|
|
26
34
|
import type {
|
|
27
35
|
ChannelGroupID,
|
|
@@ -267,6 +275,55 @@ export async function addChannelGroupPermission(
|
|
|
267
275
|
);
|
|
268
276
|
}
|
|
269
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Set several permissions on a channel group, in one command: each permission
|
|
280
|
+
* is a row. With none, nothing is sent. The server's answer is as for
|
|
281
|
+
* {@link addChannelPermissions}: an unknown permission or a value it cannot read
|
|
282
|
+
* refuses the call with none set, with or without `continueOnError`, and a row
|
|
283
|
+
* the caller lacks the power for ends it there, or is skipped with
|
|
284
|
+
* `continueOnError`.
|
|
285
|
+
*/
|
|
286
|
+
export async function addChannelGroupPermissions(
|
|
287
|
+
client: CommandExecutor,
|
|
288
|
+
cgid: ChannelGroupID,
|
|
289
|
+
permissions: readonly PermissionValue[],
|
|
290
|
+
options: BatchOptions = {},
|
|
291
|
+
): Promise<void> {
|
|
292
|
+
await execRows(
|
|
293
|
+
client,
|
|
294
|
+
"channelgroupaddperm",
|
|
295
|
+
[...continueOnErrorParams(options), ["cgid", cgid]],
|
|
296
|
+
permissions.map((entry) => [permissionParam(entry.permission), ["permvalue", entry.value]]),
|
|
297
|
+
options,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Remove several permissions from a channel group, in one command: each
|
|
303
|
+
* permission is a row. With none, nothing is sent. An unknown permission (2562)
|
|
304
|
+
* is found first and leaves none removed, with or without `continueOnError`.
|
|
305
|
+
* Removing a permission the group does not have succeeds. A permission the
|
|
306
|
+
* caller has too little modify power for (2570) ends the call at that row, so
|
|
307
|
+
* the rows before it are removed and the rows after it are not; with
|
|
308
|
+
* `continueOnError` that row is skipped and the others removed. Measured on
|
|
309
|
+
* TeamSpeak 3 3.13.8 and TeamSpeak 6 beta13.1 over every query transport and
|
|
310
|
+
* the voice protocol.
|
|
311
|
+
*/
|
|
312
|
+
export async function deleteChannelGroupPermissions(
|
|
313
|
+
client: CommandExecutor,
|
|
314
|
+
cgid: ChannelGroupID,
|
|
315
|
+
permissions: readonly Permission[],
|
|
316
|
+
options: BatchOptions = {},
|
|
317
|
+
): Promise<void> {
|
|
318
|
+
await execRows(
|
|
319
|
+
client,
|
|
320
|
+
"channelgroupdelperm",
|
|
321
|
+
[...continueOnErrorParams(options), ["cgid", cgid]],
|
|
322
|
+
permissions.map((permission) => [permissionParam(permission)]),
|
|
323
|
+
options,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
270
327
|
/** Remove a permission from a channel group. */
|
|
271
328
|
export async function deleteChannelGroupPermission(
|
|
272
329
|
client: CommandExecutor,
|
|
@@ -314,8 +371,12 @@ export async function listPermissions(
|
|
|
314
371
|
}
|
|
315
372
|
|
|
316
373
|
/**
|
|
317
|
-
* Resolve a permission name to its numeric id
|
|
318
|
-
*
|
|
374
|
+
* Resolve a permission name to its numeric id.
|
|
375
|
+
*
|
|
376
|
+
* A name the server does not know is not an `undefined` result: both TS3 3.13.8
|
|
377
|
+
* and TS6 beta13.1 refuse it with a ServerError 2562 (invalid permission ID),
|
|
378
|
+
* which is thrown. `undefined` is only for a server that acknowledges the
|
|
379
|
+
* command with no row, which neither measured server does.
|
|
319
380
|
*
|
|
320
381
|
* Needs an instance-level permission (`b_serverinstance_*`), which not even the
|
|
321
382
|
* Server Admin group holds by default — expect error 2568 until it is granted.
|