chapterjs 0.1.2 → 0.2.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/dist/assets/asset.d.ts +46 -0
- package/dist/assets/asset.js +86 -0
- package/dist/assets/public.d.ts +19 -0
- package/dist/assets/public.js +120 -0
- package/dist/cache/cache.d.ts +62 -0
- package/dist/cache/cache.js +80 -0
- package/dist/cache/store.d.ts +45 -0
- package/dist/cache/store.js +53 -0
- package/dist/cli/build.d.ts +41 -0
- package/dist/cli/build.js +205 -0
- package/dist/cli/cli.js +108 -25
- package/dist/cli/cluster.d.ts +70 -0
- package/dist/cli/cluster.js +213 -0
- package/dist/cli/dev.d.ts +18 -1
- package/dist/cli/dev.js +253 -198
- package/dist/cli/env.d.ts +31 -0
- package/dist/cli/env.js +93 -0
- package/dist/cli/log.d.ts +13 -0
- package/dist/cli/log.js +17 -0
- package/dist/cli/node-version.d.ts +8 -0
- package/dist/cli/node-version.js +13 -0
- package/dist/cli/preflight.d.ts +36 -0
- package/dist/cli/preflight.js +122 -0
- package/dist/cli/project.d.ts +135 -0
- package/dist/cli/project.js +517 -0
- package/dist/cli/start.d.ts +25 -0
- package/dist/cli/start.js +225 -0
- package/dist/commands/autocomplete.d.ts +13 -0
- package/dist/commands/autocomplete.js +159 -0
- package/dist/commands/command.d.ts +360 -0
- package/dist/commands/command.js +28 -0
- package/dist/commands/declaration.d.ts +77 -0
- package/dist/commands/declaration.js +492 -0
- package/dist/commands/register.d.ts +18 -0
- package/dist/commands/register.js +42 -0
- package/dist/commands/router.d.ts +20 -0
- package/dist/commands/router.js +228 -0
- package/dist/commands/tree.d.ts +47 -0
- package/dist/commands/tree.js +216 -0
- package/dist/commands/twins.d.ts +15 -0
- package/dist/commands/twins.js +78 -0
- package/dist/components/button.d.ts +79 -0
- package/dist/components/button.js +106 -0
- package/dist/components/component.d.ts +140 -0
- package/dist/components/component.js +39 -0
- package/dist/components/custom-id.d.ts +72 -0
- package/dist/components/custom-id.js +128 -0
- package/dist/components/declaration.d.ts +42 -0
- package/dist/components/declaration.js +342 -0
- package/dist/components/declared.d.ts +29 -0
- package/dist/components/declared.js +49 -0
- package/dist/components/embed.d.ts +29 -0
- package/dist/components/embed.js +11 -0
- package/dist/components/instance.d.ts +100 -0
- package/dist/components/instance.js +86 -0
- package/dist/components/layout.d.ts +103 -0
- package/dist/components/layout.js +279 -0
- package/dist/components/modal.d.ts +245 -0
- package/dist/components/modal.js +398 -0
- package/dist/components/options.d.ts +41 -0
- package/dist/components/options.js +101 -0
- package/dist/components/render.d.ts +12 -0
- package/dist/components/render.js +58 -0
- package/dist/components/router.d.ts +19 -0
- package/dist/components/router.js +251 -0
- package/dist/components/select.d.ts +146 -0
- package/dist/components/select.js +179 -0
- package/dist/core/bot.d.ts +99 -0
- package/dist/core/bot.js +174 -0
- package/dist/core/memory.d.ts +66 -0
- package/dist/core/memory.js +115 -0
- package/dist/discord/api.d.ts +112 -0
- package/dist/discord/api.js +115 -0
- package/dist/discord/cdn.d.ts +67 -0
- package/dist/discord/cdn.js +100 -0
- package/dist/discord/codes.d.ts +171 -0
- package/dist/discord/codes.js +421 -0
- package/dist/discord/endpoint.d.ts +56 -0
- package/dist/discord/endpoint.js +53 -0
- package/dist/discord/endpoints.d.ts +1529 -0
- package/dist/discord/endpoints.js +1139 -0
- package/dist/discord/formatting.d.ts +67 -0
- package/dist/discord/formatting.js +87 -0
- package/dist/discord/index.d.ts +10 -0
- package/dist/discord/index.js +13 -0
- package/dist/discord/intents.d.ts +48 -0
- package/dist/discord/intents.js +128 -0
- package/dist/discord/permissions.d.ts +94 -0
- package/dist/discord/permissions.js +185 -0
- package/dist/discord/snowflake.d.ts +21 -0
- package/dist/discord/snowflake.js +36 -0
- package/dist/discord/types/application-command.d.ts +332 -0
- package/dist/discord/types/application-command.js +48 -0
- package/dist/discord/types/application-role-connection-metadata.d.ts +42 -0
- package/dist/discord/types/application-role-connection-metadata.js +22 -0
- package/dist/discord/types/application.d.ts +208 -0
- package/dist/discord/types/application.js +48 -0
- package/dist/discord/types/audit-log.d.ts +255 -0
- package/dist/discord/types/audit-log.js +144 -0
- package/dist/discord/types/auto-moderation.d.ts +167 -0
- package/dist/discord/types/auto-moderation.js +52 -0
- package/dist/discord/types/channel.d.ts +557 -0
- package/dist/discord/types/channel.js +88 -0
- package/dist/discord/types/common.d.ts +54 -0
- package/dist/discord/types/common.js +38 -0
- package/dist/discord/types/component.d.ts +775 -0
- package/dist/discord/types/component.js +76 -0
- package/dist/discord/types/emoji.d.ts +74 -0
- package/dist/discord/types/emoji.js +1 -0
- package/dist/discord/types/entitlement.d.ts +84 -0
- package/dist/discord/types/entitlement.js +22 -0
- package/dist/discord/types/gateway-events.d.ts +1148 -0
- package/dist/discord/types/gateway-events.js +54 -0
- package/dist/discord/types/gateway.d.ts +38 -0
- package/dist/discord/types/gateway.js +1 -0
- package/dist/discord/types/guild-scheduled-event.d.ts +257 -0
- package/dist/discord/types/guild-scheduled-event.js +68 -0
- package/dist/discord/types/guild-template.d.ts +51 -0
- package/dist/discord/types/guild-template.js +1 -0
- package/dist/discord/types/guild.d.ts +910 -0
- package/dist/discord/types/guild.js +153 -0
- package/dist/discord/types/index.d.ts +31 -0
- package/dist/discord/types/index.js +31 -0
- package/dist/discord/types/interaction.d.ts +321 -0
- package/dist/discord/types/interaction.js +47 -0
- package/dist/discord/types/invite.d.ts +158 -0
- package/dist/discord/types/invite.js +39 -0
- package/dist/discord/types/message.d.ts +834 -0
- package/dist/discord/types/message.js +159 -0
- package/dist/discord/types/oauth2.d.ts +22 -0
- package/dist/discord/types/oauth2.js +1 -0
- package/dist/discord/types/permissions.d.ts +183 -0
- package/dist/discord/types/permissions.js +118 -0
- package/dist/discord/types/poll.d.ts +106 -0
- package/dist/discord/types/poll.js +8 -0
- package/dist/discord/types/sku.d.ts +47 -0
- package/dist/discord/types/sku.js +26 -0
- package/dist/discord/types/soundboard.d.ts +71 -0
- package/dist/discord/types/soundboard.js +1 -0
- package/dist/discord/types/stage-instance.d.ts +58 -0
- package/dist/discord/types/stage-instance.js +10 -0
- package/dist/discord/types/sticker.d.ts +115 -0
- package/dist/discord/types/sticker.js +20 -0
- package/dist/discord/types/subscription.d.ts +54 -0
- package/dist/discord/types/subscription.js +12 -0
- package/dist/discord/types/team.d.ts +55 -0
- package/dist/discord/types/team.js +21 -0
- package/dist/discord/types/user.d.ts +273 -0
- package/dist/discord/types/user.js +83 -0
- package/dist/discord/types/voice.d.ts +72 -0
- package/dist/discord/types/voice.js +1 -0
- package/dist/discord/types/webhook.d.ts +184 -0
- package/dist/discord/types/webhook.js +12 -0
- package/dist/events/declaration.d.ts +16 -0
- package/dist/events/declaration.js +80 -0
- package/dist/events/event.d.ts +45 -0
- package/dist/events/event.js +34 -0
- package/dist/events/registry.d.ts +697 -0
- package/dist/events/registry.js +839 -0
- package/dist/events/router.d.ts +46 -0
- package/dist/events/router.js +133 -0
- package/dist/gateway/errors.d.ts +18 -0
- package/dist/gateway/errors.js +47 -0
- package/dist/gateway/gateway.d.ts +64 -0
- package/dist/gateway/gateway.js +117 -0
- package/dist/gateway/identify-queue.d.ts +25 -0
- package/dist/gateway/identify-queue.js +29 -0
- package/dist/gateway/shard.d.ts +94 -0
- package/dist/gateway/shard.js +414 -0
- package/dist/gateway/state.d.ts +11 -0
- package/dist/gateway/state.js +184 -0
- package/dist/index.d.ts +57 -18
- package/dist/index.js +40 -12
- package/dist/interactions/cooldown.d.ts +33 -0
- package/dist/interactions/cooldown.js +74 -0
- package/dist/interactions/dispatch.d.ts +91 -0
- package/dist/interactions/dispatch.js +110 -0
- package/dist/loader/generated.d.ts +10 -0
- package/dist/loader/generated.js +80 -0
- package/dist/loader/hot.d.ts +13 -0
- package/dist/loader/hot.js +85 -0
- package/dist/loader/loader.d.ts +85 -0
- package/dist/loader/loader.js +119 -0
- package/dist/loader/locate.d.ts +10 -0
- package/dist/loader/locate.js +36 -0
- package/dist/loader/watch.d.ts +17 -0
- package/dist/loader/watch.js +66 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +5 -0
- package/dist/messages/commands.d.ts +23 -0
- package/dist/messages/commands.js +142 -0
- package/dist/messages/declaration.d.ts +89 -0
- package/dist/messages/declaration.js +455 -0
- package/dist/messages/messages.d.ts +241 -0
- package/dist/messages/messages.js +31 -0
- package/dist/messages/phrases.d.ts +23 -0
- package/dist/messages/phrases.js +44 -0
- package/dist/messages/translate.d.ts +100 -0
- package/dist/messages/translate.js +249 -0
- package/dist/presence/declaration.d.ts +19 -0
- package/dist/presence/declaration.js +118 -0
- package/dist/presence/presence.d.ts +57 -0
- package/dist/presence/presence.js +27 -0
- package/dist/rest/errors.d.ts +55 -0
- package/dist/rest/errors.js +92 -0
- package/dist/rest/rate-limiter.d.ts +46 -0
- package/dist/rest/rate-limiter.js +153 -0
- package/dist/rest/rest.d.ts +63 -0
- package/dist/rest/rest.js +194 -0
- package/dist/structures/base.d.ts +48 -0
- package/dist/structures/base.js +84 -0
- package/dist/structures/channel.d.ts +380 -0
- package/dist/structures/channel.js +627 -0
- package/dist/structures/context.d.ts +30 -0
- package/dist/structures/context.js +7 -0
- package/dist/structures/emoji.d.ts +46 -0
- package/dist/structures/emoji.js +65 -0
- package/dist/structures/entities.d.ts +62 -0
- package/dist/structures/entities.js +226 -0
- package/dist/structures/guild.d.ts +348 -0
- package/dist/structures/guild.js +466 -0
- package/dist/structures/interaction.d.ts +197 -0
- package/dist/structures/interaction.js +396 -0
- package/dist/structures/invite.d.ts +38 -0
- package/dist/structures/invite.js +57 -0
- package/dist/structures/known.d.ts +28 -0
- package/dist/structures/known.js +58 -0
- package/dist/structures/member.d.ts +140 -0
- package/dist/structures/member.js +260 -0
- package/dist/structures/message.d.ts +197 -0
- package/dist/structures/message.js +268 -0
- package/dist/structures/payload.d.ts +91 -0
- package/dist/structures/payload.js +184 -0
- package/dist/structures/role.d.ts +69 -0
- package/dist/structures/role.js +112 -0
- package/dist/structures/user.d.ts +59 -0
- package/dist/structures/user.js +94 -0
- package/dist/structures/webhook.d.ts +46 -0
- package/dist/structures/webhook.js +79 -0
- package/dist/tasks/declaration.d.ts +16 -0
- package/dist/tasks/declaration.js +56 -0
- package/dist/tasks/schedule.d.ts +54 -0
- package/dist/tasks/schedule.js +236 -0
- package/dist/tasks/scheduler.d.ts +35 -0
- package/dist/tasks/scheduler.js +107 -0
- package/dist/tasks/task.d.ts +75 -0
- package/dist/tasks/task.js +27 -0
- package/dist/util/case.d.ts +13 -0
- package/dist/util/case.js +54 -0
- package/dist/util/duration.d.ts +8 -0
- package/dist/util/duration.js +29 -0
- package/dist/util/websocket.d.ts +9 -0
- package/dist/util/websocket.js +31 -0
- package/dist/voice/connection.d.ts +90 -0
- package/dist/voice/connection.js +494 -0
- package/dist/voice/dave.d.ts +109 -0
- package/dist/voice/dave.js +245 -0
- package/dist/voice/opus.d.ts +19 -0
- package/dist/voice/opus.js +273 -0
- package/dist/voice/session.d.ts +55 -0
- package/dist/voice/session.js +405 -0
- package/dist/voice/source.d.ts +22 -0
- package/dist/voice/source.js +195 -0
- package/dist/voice/transport.d.ts +43 -0
- package/dist/voice/transport.js +140 -0
- package/dist/voice/usage.d.ts +4 -0
- package/dist/voice/usage.js +33 -0
- package/package.json +26 -7
- package/vendor/dave/LICENSE +231 -0
- package/vendor/dave/libdave.d.mts +99 -0
- package/vendor/dave/libdave.mjs +2 -0
- package/vendor/dave/libdave.wasm +0 -0
- package/dist/components.d.ts +0 -97
- package/dist/components.js +0 -260
- package/dist/context.d.ts +0 -72
- package/dist/context.js +0 -273
- package/dist/discord/client.d.ts +0 -35
- package/dist/discord/client.js +0 -66
- package/dist/discord/constants.d.ts +0 -49
- package/dist/discord/constants.js +0 -61
- package/dist/discord/events.d.ts +0 -129
- package/dist/discord/events.js +0 -102
- package/dist/discord/gateway.d.ts +0 -61
- package/dist/discord/gateway.js +0 -320
- package/dist/discord/payloads.d.ts +0 -275
- package/dist/discord/payloads.js +0 -3
- package/dist/discord/presence.d.ts +0 -42
- package/dist/discord/presence.js +0 -51
- package/dist/discord/rest.d.ts +0 -49
- package/dist/discord/rest.js +0 -271
- package/dist/discord/types.d.ts +0 -334
- package/dist/discord/types.js +0 -4
- package/dist/events.d.ts +0 -94
- package/dist/events.js +0 -255
- package/dist/presence.d.ts +0 -92
- package/dist/presence.js +0 -250
- package/dist/structures/Channel.d.ts +0 -110
- package/dist/structures/Channel.js +0 -171
- package/dist/structures/Guild.d.ts +0 -70
- package/dist/structures/Guild.js +0 -105
- package/dist/structures/Interaction.d.ts +0 -113
- package/dist/structures/Interaction.js +0 -274
- package/dist/structures/Member.d.ts +0 -70
- package/dist/structures/Member.js +0 -124
- package/dist/structures/Message.d.ts +0 -87
- package/dist/structures/Message.js +0 -182
- package/dist/structures/Reaction.d.ts +0 -38
- package/dist/structures/Reaction.js +0 -64
- package/dist/structures/Role.d.ts +0 -43
- package/dist/structures/Role.js +0 -77
- package/dist/structures/User.d.ts +0 -41
- package/dist/structures/User.js +0 -79
- package/dist/structures/utils.d.ts +0 -72
- package/dist/structures/utils.js +0 -59
- package/dist/utils/errors.d.ts +0 -5
- package/dist/utils/errors.js +0 -28
- package/dist/utils/modules.d.ts +0 -39
- package/dist/utils/modules.js +0 -156
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// End-to-end encryption of voice (DAVE), on Discord's own libdave compiled
|
|
2
|
+
// to WebAssembly (vendor/dave, built by scripts/build-dave.sh). libdave
|
|
3
|
+
// keeps the MLS group and encrypts frames; this file follows the voice
|
|
4
|
+
// gateway through the protocol: key packages, proposals, commits, welcomes
|
|
5
|
+
// and transitions.
|
|
6
|
+
// https://docs.discord.com/developers/topics/voice-connections#end-to-end-encryption-dave-protocol
|
|
7
|
+
// https://daveprotocol.com/#voice-gateway-opcodes
|
|
8
|
+
import { VoiceOpcode } from '../discord/codes.js';
|
|
9
|
+
let loading = null;
|
|
10
|
+
/** libdave, loaded the first time a voice connection needs it. */
|
|
11
|
+
export function loadDave() {
|
|
12
|
+
loading ??= (async () => {
|
|
13
|
+
const url = new URL('../../vendor/dave/libdave.mjs', import.meta.url);
|
|
14
|
+
const { default: factory } = (await import(url.href));
|
|
15
|
+
// libdave logs every step, MLS messages included: not for the
|
|
16
|
+
// terminal of the developer. What fails reaches the session callback.
|
|
17
|
+
return factory({ print: () => { }, printErr: () => { } });
|
|
18
|
+
})();
|
|
19
|
+
return loading;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The DAVE state of one voice connection: the MLS group of the call and
|
|
23
|
+
* the encryptor of what the bot sends.
|
|
24
|
+
*/
|
|
25
|
+
export class DaveSession {
|
|
26
|
+
#dave;
|
|
27
|
+
#transport;
|
|
28
|
+
#userId;
|
|
29
|
+
#channelId;
|
|
30
|
+
#warn;
|
|
31
|
+
#keys;
|
|
32
|
+
#session;
|
|
33
|
+
#encryptor;
|
|
34
|
+
/** Who the voice gateway says is in the call: the members it may add. */
|
|
35
|
+
#connected = new Set();
|
|
36
|
+
/** The protocol version each announced transition moves to. */
|
|
37
|
+
#transitions = new Map();
|
|
38
|
+
#version = 0;
|
|
39
|
+
/** Whether frames can be sent: no E2EE, or the bot has its key. */
|
|
40
|
+
#ready = true;
|
|
41
|
+
#waiting = [];
|
|
42
|
+
#ssrc = null;
|
|
43
|
+
constructor(dave, options) {
|
|
44
|
+
this.#dave = dave;
|
|
45
|
+
this.#transport = options.transport;
|
|
46
|
+
this.#userId = options.userId;
|
|
47
|
+
this.#channelId = options.channelId;
|
|
48
|
+
this.#warn = options.warn;
|
|
49
|
+
this.#keys = new dave.TransientKeys();
|
|
50
|
+
// libdave also reports what is normal in a call (a commit that arrives
|
|
51
|
+
// while the group starts over): only a recovery is worth saying.
|
|
52
|
+
this.#session = new dave.Session('', '', () => { });
|
|
53
|
+
this.#encryptor = new dave.Encryptor();
|
|
54
|
+
this.#encryptor.SetPassthroughMode(true);
|
|
55
|
+
this.#connected.add(this.#userId);
|
|
56
|
+
}
|
|
57
|
+
/** The highest DAVE version the bot speaks, for Identify. */
|
|
58
|
+
get maxVersion() {
|
|
59
|
+
return this.#dave.MaxSupportedProtocolVersion();
|
|
60
|
+
}
|
|
61
|
+
/** The protocol version of the call; 0 without E2EE. */
|
|
62
|
+
get version() {
|
|
63
|
+
return this.#version;
|
|
64
|
+
}
|
|
65
|
+
/** Whether frames can be sent now. */
|
|
66
|
+
get canSend() {
|
|
67
|
+
return this.#ready;
|
|
68
|
+
}
|
|
69
|
+
/** Resolves once frames can be sent. */
|
|
70
|
+
ready() {
|
|
71
|
+
if (this.#ready)
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
return new Promise(resolve => this.#waiting.push(resolve));
|
|
74
|
+
}
|
|
75
|
+
/** The SSRC of the bot, which the encryptor files its frames under. */
|
|
76
|
+
setSsrc(ssrc) {
|
|
77
|
+
this.#ssrc = ssrc;
|
|
78
|
+
this.#encryptor.AssignSsrcToCodec(ssrc, this.#dave.Codec.Opus);
|
|
79
|
+
}
|
|
80
|
+
/** Session Description (4): the version the call starts with. */
|
|
81
|
+
start(version) {
|
|
82
|
+
this.#version = version;
|
|
83
|
+
if (version > 0) {
|
|
84
|
+
this.#setReady(false);
|
|
85
|
+
this.#reinit(version);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Clients Connect (11) and Client Disconnect (13). */
|
|
89
|
+
clientsConnect(userIds) {
|
|
90
|
+
for (const id of userIds)
|
|
91
|
+
this.#connected.add(id);
|
|
92
|
+
}
|
|
93
|
+
clientDisconnect(userId) {
|
|
94
|
+
this.#connected.delete(userId);
|
|
95
|
+
}
|
|
96
|
+
/** DAVE Prepare Transition (21): a move to another version, 0 included. */
|
|
97
|
+
prepareTransition(transitionId, version) {
|
|
98
|
+
this.#transitions.set(transitionId, version);
|
|
99
|
+
if (transitionId === 0)
|
|
100
|
+
this.executeTransition(0);
|
|
101
|
+
else {
|
|
102
|
+
this.#transport.json(VoiceOpcode.DaveTransitionReady, {
|
|
103
|
+
transition_id: transitionId,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** DAVE Execute Transition (22): senders switch now. */
|
|
108
|
+
executeTransition(transitionId) {
|
|
109
|
+
const version = this.#transitions.get(transitionId);
|
|
110
|
+
if (version === undefined)
|
|
111
|
+
return;
|
|
112
|
+
this.#transitions.delete(transitionId);
|
|
113
|
+
const previous = this.#version;
|
|
114
|
+
this.#version = version;
|
|
115
|
+
if (version === 0) {
|
|
116
|
+
this.#encryptor.SetPassthroughMode(true);
|
|
117
|
+
this.#setReady(true);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (previous === 0 && transitionId !== 0) {
|
|
121
|
+
// An upgrade: the group of the new version was made before this.
|
|
122
|
+
this.#session.SetProtocolVersion(version);
|
|
123
|
+
}
|
|
124
|
+
this.#useOwnKey();
|
|
125
|
+
}
|
|
126
|
+
/** DAVE Prepare Epoch (24): a new group when `epoch` is 1. */
|
|
127
|
+
prepareEpoch(epoch, version) {
|
|
128
|
+
if (epoch === 1) {
|
|
129
|
+
this.#version = version;
|
|
130
|
+
this.#setReady(false);
|
|
131
|
+
this.#reinit(version);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** DAVE MLS External Sender (25). */
|
|
135
|
+
externalSender(payload) {
|
|
136
|
+
this.#session.SetExternalSender(payload);
|
|
137
|
+
}
|
|
138
|
+
/** DAVE MLS Proposals (27): answered with a commit when there is one. */
|
|
139
|
+
proposals(payload) {
|
|
140
|
+
const commit = this.#session.ProcessProposals(payload, [
|
|
141
|
+
...this.#connected,
|
|
142
|
+
]);
|
|
143
|
+
if (commit) {
|
|
144
|
+
this.#transport.binary(VoiceOpcode.DaveMlsCommitWelcome, Uint8Array.from(commit));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** DAVE MLS Announce Commit Transition (29). */
|
|
148
|
+
announceCommit(transitionId, commit) {
|
|
149
|
+
let result;
|
|
150
|
+
try {
|
|
151
|
+
result = this.#session.ProcessCommit(commit);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
result = { failed: true, ignored: false };
|
|
155
|
+
}
|
|
156
|
+
if (result.ignored)
|
|
157
|
+
return;
|
|
158
|
+
if (result.failed)
|
|
159
|
+
return this.#recover(transitionId);
|
|
160
|
+
this.#prepared(transitionId);
|
|
161
|
+
}
|
|
162
|
+
/** DAVE MLS Welcome (30): the bot joins the group. */
|
|
163
|
+
welcome(transitionId, welcome) {
|
|
164
|
+
let roster;
|
|
165
|
+
try {
|
|
166
|
+
roster = this.#session.ProcessWelcome(welcome, [...this.#connected]);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
roster = null;
|
|
170
|
+
}
|
|
171
|
+
if (!roster)
|
|
172
|
+
return this.#recover(transitionId);
|
|
173
|
+
this.#prepared(transitionId);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* One Opus frame as it travels: end-to-end encrypted when the call is,
|
|
177
|
+
* unchanged otherwise.
|
|
178
|
+
*/
|
|
179
|
+
encrypt(frame) {
|
|
180
|
+
if (this.#version === 0 || this.#ssrc === null)
|
|
181
|
+
return frame;
|
|
182
|
+
const dave = this.#dave;
|
|
183
|
+
const capacity = this.#encryptor.GetMaxCiphertextByteSize(dave.MediaType.Audio, frame.length);
|
|
184
|
+
const pointer = dave._malloc(capacity);
|
|
185
|
+
try {
|
|
186
|
+
dave.HEAPU8.set(frame, pointer);
|
|
187
|
+
const written = this.#encryptor.Encrypt(dave.MediaType.Audio, this.#ssrc, pointer, frame.length, capacity);
|
|
188
|
+
if (written === 0) {
|
|
189
|
+
throw new Error('libdave could not encrypt a voice frame.');
|
|
190
|
+
}
|
|
191
|
+
return dave.HEAPU8.slice(pointer, pointer + written);
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
dave._free(pointer);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** Frees what libdave holds for this connection. */
|
|
198
|
+
close() {
|
|
199
|
+
this.#setReady(true);
|
|
200
|
+
this.#encryptor.delete();
|
|
201
|
+
this.#session.delete();
|
|
202
|
+
this.#keys.delete();
|
|
203
|
+
}
|
|
204
|
+
/** A new local group for `version`, and a key package to join it. */
|
|
205
|
+
#reinit(version) {
|
|
206
|
+
this.#session.Init(version, BigInt(this.#channelId), this.#userId, this.#keys.GetTransientPrivateKey(version));
|
|
207
|
+
this.#transport.binary(VoiceOpcode.DaveMlsKeyPackage, Uint8Array.from(this.#session.GetMarshalledKeyPackage()));
|
|
208
|
+
}
|
|
209
|
+
/** The bot processed a commit or a welcome: ready for the transition. */
|
|
210
|
+
#prepared(transitionId) {
|
|
211
|
+
this.#transitions.set(transitionId, this.#session.GetProtocolVersion());
|
|
212
|
+
if (transitionId === 0)
|
|
213
|
+
this.executeTransition(0);
|
|
214
|
+
else {
|
|
215
|
+
this.#transport.json(VoiceOpcode.DaveTransitionReady, {
|
|
216
|
+
transition_id: transitionId,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** A commit or welcome that could not be processed: start over. */
|
|
221
|
+
#recover(transitionId) {
|
|
222
|
+
this.#warn(`the end-to-end encryption of the call could not be updated (transition ${transitionId}): joining it again.`);
|
|
223
|
+
this.#transport.json(VoiceOpcode.DaveMlsInvalidCommitWelcome, {
|
|
224
|
+
transition_id: transitionId,
|
|
225
|
+
});
|
|
226
|
+
this.#reinit(this.#version || this.#session.GetProtocolVersion());
|
|
227
|
+
}
|
|
228
|
+
#useOwnKey() {
|
|
229
|
+
const ratchet = this.#session.GetKeyRatchet(this.#userId);
|
|
230
|
+
if (!ratchet)
|
|
231
|
+
return;
|
|
232
|
+
this.#encryptor.SetKeyRatchet(ratchet);
|
|
233
|
+
this.#encryptor.SetPassthroughMode(false);
|
|
234
|
+
this.#setReady(true);
|
|
235
|
+
}
|
|
236
|
+
#setReady(ready) {
|
|
237
|
+
this.#ready = ready;
|
|
238
|
+
if (!ready)
|
|
239
|
+
return;
|
|
240
|
+
const waiting = this.#waiting;
|
|
241
|
+
this.#waiting = [];
|
|
242
|
+
for (const resolve of waiting)
|
|
243
|
+
resolve();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A file that holds no Opus audio this module can read. */
|
|
2
|
+
export declare class NotOpusError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* How many samples (at 48 kHz) an Opus packet plays, from its TOC byte.
|
|
6
|
+
* https://www.rfc-editor.org/rfc/rfc6716#section-3.1
|
|
7
|
+
*/
|
|
8
|
+
export declare function opusSamples(packet: Uint8Array): number;
|
|
9
|
+
/**
|
|
10
|
+
* The Opus packets of an Ogg stream, in order, without its two header
|
|
11
|
+
* packets (OpusHead, OpusTags). Only the first logical stream is read.
|
|
12
|
+
* https://www.rfc-editor.org/rfc/rfc3533 https://www.rfc-editor.org/rfc/rfc7845
|
|
13
|
+
*/
|
|
14
|
+
export declare function oggOpusPackets(source: AsyncIterable<Uint8Array>): AsyncGenerator<Buffer>;
|
|
15
|
+
/**
|
|
16
|
+
* The Opus packets of a WebM/Matroska stream, from its first Opus track.
|
|
17
|
+
* https://www.matroska.org/technical/elements.html
|
|
18
|
+
*/
|
|
19
|
+
export declare function webmOpusPackets(source: AsyncIterable<Uint8Array>): AsyncGenerator<Buffer>;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// Opus packets out of the files that already hold them: Ogg (`.ogg`,
|
|
2
|
+
// `.opus`) and WebM/Matroska (`.webm`, `.mka`). Discord takes Opus as is,
|
|
3
|
+
// so nothing is decoded: packets are only taken out of their container.
|
|
4
|
+
/** A file that holds no Opus audio this module can read. */
|
|
5
|
+
export class NotOpusError extends Error {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* How many samples (at 48 kHz) an Opus packet plays, from its TOC byte.
|
|
9
|
+
* https://www.rfc-editor.org/rfc/rfc6716#section-3.1
|
|
10
|
+
*/
|
|
11
|
+
export function opusSamples(packet) {
|
|
12
|
+
const toc = packet[0];
|
|
13
|
+
if (toc === undefined)
|
|
14
|
+
return 0;
|
|
15
|
+
const config = toc >> 3;
|
|
16
|
+
// Frame size in 1/400 s (2.5 ms), per configuration group.
|
|
17
|
+
const tenths = config < 12
|
|
18
|
+
? [4, 8, 16, 24][config % 4]
|
|
19
|
+
: config < 16
|
|
20
|
+
? [4, 8][config % 2]
|
|
21
|
+
: [1, 2, 4, 8][config % 4];
|
|
22
|
+
const code = toc & 3;
|
|
23
|
+
const frames = code === 0 ? 1 : code === 3 ? (packet[1] ?? 0) & 0x3f : 2;
|
|
24
|
+
return tenths * 120 * frames;
|
|
25
|
+
}
|
|
26
|
+
/** Bytes coming in pieces, read as they are needed. */
|
|
27
|
+
class Reader {
|
|
28
|
+
#chunks = [];
|
|
29
|
+
#length = 0;
|
|
30
|
+
#source;
|
|
31
|
+
#done = false;
|
|
32
|
+
constructor(source) {
|
|
33
|
+
this.#source = source[Symbol.asyncIterator]();
|
|
34
|
+
}
|
|
35
|
+
/** Makes at least `size` bytes available; `false` at the end. */
|
|
36
|
+
async need(size) {
|
|
37
|
+
while (this.#length < size && !this.#done) {
|
|
38
|
+
const next = await this.#source.next();
|
|
39
|
+
if (next.done)
|
|
40
|
+
this.#done = true;
|
|
41
|
+
else if (next.value.length > 0) {
|
|
42
|
+
this.#chunks.push(next.value);
|
|
43
|
+
this.#length += next.value.length;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return this.#length >= size;
|
|
47
|
+
}
|
|
48
|
+
/** The first `size` bytes, without consuming them. */
|
|
49
|
+
peek(size) {
|
|
50
|
+
if (this.#chunks.length !== 1) {
|
|
51
|
+
const all = Buffer.concat(this.#chunks);
|
|
52
|
+
this.#chunks = [all];
|
|
53
|
+
}
|
|
54
|
+
return Buffer.from(this.#chunks[0].subarray(0, size));
|
|
55
|
+
}
|
|
56
|
+
async take(size) {
|
|
57
|
+
if (!(await this.need(size)))
|
|
58
|
+
return null;
|
|
59
|
+
const out = this.peek(size);
|
|
60
|
+
const first = this.#chunks[0].subarray(size);
|
|
61
|
+
this.#chunks = first.length > 0 ? [first] : [];
|
|
62
|
+
this.#length -= size;
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
async skip(size) {
|
|
66
|
+
let left = size;
|
|
67
|
+
while (left > 0) {
|
|
68
|
+
if (!(await this.need(Math.min(left, 65536))))
|
|
69
|
+
return false;
|
|
70
|
+
const step = Math.min(left, this.#length);
|
|
71
|
+
await this.take(step);
|
|
72
|
+
left -= step;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
get ended() {
|
|
77
|
+
return this.#done && this.#length === 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The Opus packets of an Ogg stream, in order, without its two header
|
|
82
|
+
* packets (OpusHead, OpusTags). Only the first logical stream is read.
|
|
83
|
+
* https://www.rfc-editor.org/rfc/rfc3533 https://www.rfc-editor.org/rfc/rfc7845
|
|
84
|
+
*/
|
|
85
|
+
export async function* oggOpusPackets(source) {
|
|
86
|
+
const reader = new Reader(source);
|
|
87
|
+
let serial = null;
|
|
88
|
+
let index = 0;
|
|
89
|
+
let pending = [];
|
|
90
|
+
for (;;) {
|
|
91
|
+
const head = await reader.take(27);
|
|
92
|
+
if (!head)
|
|
93
|
+
return;
|
|
94
|
+
if (head.toString('latin1', 0, 4) !== 'OggS') {
|
|
95
|
+
throw new NotOpusError('This is not an Ogg file.');
|
|
96
|
+
}
|
|
97
|
+
const segments = head[26];
|
|
98
|
+
const lacing = await reader.take(segments);
|
|
99
|
+
if (!lacing)
|
|
100
|
+
return;
|
|
101
|
+
const size = lacing.reduce((sum, value) => sum + value, 0);
|
|
102
|
+
const body = await reader.take(size);
|
|
103
|
+
if (!body)
|
|
104
|
+
return;
|
|
105
|
+
const pageSerial = head.readUInt32LE(14);
|
|
106
|
+
if (serial === null)
|
|
107
|
+
serial = pageSerial;
|
|
108
|
+
if (pageSerial !== serial)
|
|
109
|
+
continue;
|
|
110
|
+
let offset = 0;
|
|
111
|
+
for (const value of lacing) {
|
|
112
|
+
pending.push(body.subarray(offset, offset + value));
|
|
113
|
+
offset += value;
|
|
114
|
+
if (value === 255)
|
|
115
|
+
continue;
|
|
116
|
+
const packet = Buffer.concat(pending);
|
|
117
|
+
pending = [];
|
|
118
|
+
if (index === 0 && packet.toString('latin1', 0, 8) !== 'OpusHead') {
|
|
119
|
+
throw new NotOpusError('This Ogg file holds no Opus audio.');
|
|
120
|
+
}
|
|
121
|
+
if (index >= 2 && packet.length > 0)
|
|
122
|
+
yield packet;
|
|
123
|
+
index++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const EBML = 0x1a45dfa3;
|
|
128
|
+
const SEGMENT = 0x18538067;
|
|
129
|
+
const TRACKS = 0x1654ae6b;
|
|
130
|
+
const TRACK_ENTRY = 0xae;
|
|
131
|
+
const TRACK_NUMBER = 0xd7;
|
|
132
|
+
const CODEC_ID = 0x86;
|
|
133
|
+
const CLUSTER = 0x1f43b675;
|
|
134
|
+
const BLOCK_GROUP = 0xa0;
|
|
135
|
+
const BLOCK = 0xa1;
|
|
136
|
+
const SIMPLE_BLOCK = 0xa3;
|
|
137
|
+
/** Elements whose children are read; everything else is skipped. */
|
|
138
|
+
const MASTERS = new Set([SEGMENT, TRACKS, TRACK_ENTRY, CLUSTER, BLOCK_GROUP]);
|
|
139
|
+
/** The length of an EBML variable-size integer, from its first byte. */
|
|
140
|
+
const vintLength = (first) => {
|
|
141
|
+
for (let length = 1; length <= 8; length++) {
|
|
142
|
+
if (first & (0x80 >> (length - 1)))
|
|
143
|
+
return length;
|
|
144
|
+
}
|
|
145
|
+
return 0;
|
|
146
|
+
};
|
|
147
|
+
/** A variable-size integer, without its marker; `-1` for "unknown". */
|
|
148
|
+
function readVint(bytes, offset = 0) {
|
|
149
|
+
const length = vintLength(bytes[offset]);
|
|
150
|
+
if (length === 0)
|
|
151
|
+
throw new NotOpusError('This WebM file is damaged.');
|
|
152
|
+
let value = bytes[offset] & (0xff >> length);
|
|
153
|
+
let unknown = value === 0xff >> length;
|
|
154
|
+
for (let i = 1; i < length; i++) {
|
|
155
|
+
const byte = bytes[offset + i];
|
|
156
|
+
if (byte !== 0xff)
|
|
157
|
+
unknown = false;
|
|
158
|
+
value = value * 256 + byte;
|
|
159
|
+
}
|
|
160
|
+
return { value: unknown ? -1 : value, length };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The Opus packets of a WebM/Matroska stream, from its first Opus track.
|
|
164
|
+
* https://www.matroska.org/technical/elements.html
|
|
165
|
+
*/
|
|
166
|
+
export async function* webmOpusPackets(source) {
|
|
167
|
+
const reader = new Reader(source);
|
|
168
|
+
let track = null;
|
|
169
|
+
let entry = null;
|
|
170
|
+
let first = true;
|
|
171
|
+
while (await reader.need(1)) {
|
|
172
|
+
// An element: its id (marker kept), then its size.
|
|
173
|
+
const idLength = vintLength(reader.peek(1)[0]);
|
|
174
|
+
if (idLength === 0 || idLength > 4 || !(await reader.need(idLength + 1))) {
|
|
175
|
+
throw new NotOpusError('This WebM file is damaged.');
|
|
176
|
+
}
|
|
177
|
+
const id = reader.peek(idLength).readUIntBE(0, idLength);
|
|
178
|
+
const sizeLength = vintLength(reader.peek(idLength + 1)[idLength]);
|
|
179
|
+
if (!(await reader.need(idLength + sizeLength)))
|
|
180
|
+
return;
|
|
181
|
+
const { value: size } = readVint(reader.peek(idLength + sizeLength), idLength);
|
|
182
|
+
await reader.take(idLength + sizeLength);
|
|
183
|
+
if (first) {
|
|
184
|
+
if (id !== EBML)
|
|
185
|
+
throw new NotOpusError('This is not a WebM file.');
|
|
186
|
+
first = false;
|
|
187
|
+
}
|
|
188
|
+
if (MASTERS.has(id)) {
|
|
189
|
+
if (id === TRACK_ENTRY)
|
|
190
|
+
entry = { number: null, codec: null };
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (size < 0)
|
|
194
|
+
throw new NotOpusError('This WebM file is damaged.');
|
|
195
|
+
if (id === TRACK_NUMBER || id === CODEC_ID) {
|
|
196
|
+
const data = await reader.take(size);
|
|
197
|
+
if (!data || !entry)
|
|
198
|
+
continue;
|
|
199
|
+
if (id === TRACK_NUMBER)
|
|
200
|
+
entry.number = data.readUIntBE(0, size);
|
|
201
|
+
else
|
|
202
|
+
entry.codec = data.toString('latin1').replace(/\0+$/, '');
|
|
203
|
+
if (track === null && entry.codec === 'A_OPUS' && entry.number !== null) {
|
|
204
|
+
track = entry.number;
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (id === SIMPLE_BLOCK || id === BLOCK) {
|
|
209
|
+
const data = await reader.take(size);
|
|
210
|
+
if (!data)
|
|
211
|
+
return;
|
|
212
|
+
if (track === null) {
|
|
213
|
+
throw new NotOpusError('This WebM file holds no Opus audio.');
|
|
214
|
+
}
|
|
215
|
+
const number = readVint(data);
|
|
216
|
+
if (number.value !== track)
|
|
217
|
+
continue;
|
|
218
|
+
yield* laced(data, number.length + 3);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!(await reader.skip(size)))
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (track === null) {
|
|
225
|
+
throw new NotOpusError('This WebM file holds no Opus audio.');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** The frames of a block, after its header, whatever its lacing. */
|
|
229
|
+
function* laced(block, start) {
|
|
230
|
+
const lacing = (block[start - 1] >> 1) & 3;
|
|
231
|
+
if (lacing === 0) {
|
|
232
|
+
yield block.subarray(start);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const count = block[start] + 1;
|
|
236
|
+
let offset = start + 1;
|
|
237
|
+
const sizes = [];
|
|
238
|
+
if (lacing === 1) {
|
|
239
|
+
// Xiph: each size is a run of 255s and a last byte.
|
|
240
|
+
for (let i = 0; i < count - 1; i++) {
|
|
241
|
+
let size = 0;
|
|
242
|
+
let byte;
|
|
243
|
+
do {
|
|
244
|
+
byte = block[offset++];
|
|
245
|
+
size += byte;
|
|
246
|
+
} while (byte === 255);
|
|
247
|
+
sizes.push(size);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else if (lacing === 3) {
|
|
251
|
+
// EBML: the first size, then signed differences.
|
|
252
|
+
const firstSize = readVint(block, offset);
|
|
253
|
+
offset += firstSize.length;
|
|
254
|
+
sizes.push(firstSize.value);
|
|
255
|
+
for (let i = 1; i < count - 1; i++) {
|
|
256
|
+
const delta = readVint(block, offset);
|
|
257
|
+
offset += delta.length;
|
|
258
|
+
const bias = 2 ** (7 * delta.length - 1) - 1;
|
|
259
|
+
sizes.push(sizes[i - 1] + delta.value - bias);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// Fixed: every frame has the same size.
|
|
264
|
+
const each = (block.length - offset) / count;
|
|
265
|
+
for (let i = 0; i < count - 1; i++)
|
|
266
|
+
sizes.push(each);
|
|
267
|
+
}
|
|
268
|
+
for (const size of sizes) {
|
|
269
|
+
yield block.subarray(offset, offset + size);
|
|
270
|
+
offset += size;
|
|
271
|
+
}
|
|
272
|
+
yield block.subarray(offset);
|
|
273
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Snowflake } from '../discord/types/common.js';
|
|
2
|
+
import { DaveSession, type loadDave } from './dave.js';
|
|
3
|
+
export declare const VOICE_GATEWAY_VERSION = 8;
|
|
4
|
+
/** What a voice server needs to know who the bot is. */
|
|
5
|
+
export interface VoiceServer {
|
|
6
|
+
endpoint: string;
|
|
7
|
+
token: string;
|
|
8
|
+
guildId: Snowflake;
|
|
9
|
+
channelId: Snowflake;
|
|
10
|
+
userId: Snowflake;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
}
|
|
13
|
+
/** How a session ended, when it did not end on purpose. */
|
|
14
|
+
export type SessionEnd =
|
|
15
|
+
/** Discord ended it (the bot was disconnected, the call ended...). */
|
|
16
|
+
{
|
|
17
|
+
type: 'ended';
|
|
18
|
+
code: number;
|
|
19
|
+
}
|
|
20
|
+
/** The session can't be resumed: ask the gateway for a new one. */
|
|
21
|
+
| {
|
|
22
|
+
type: 'lost';
|
|
23
|
+
code: number;
|
|
24
|
+
}
|
|
25
|
+
/** Discord refused the session: something to fix. */
|
|
26
|
+
| {
|
|
27
|
+
type: 'refused';
|
|
28
|
+
code: number;
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
export interface SessionOptions {
|
|
32
|
+
dave: Awaited<ReturnType<typeof loadDave>>;
|
|
33
|
+
/** The session can no longer send; the connection decides what next. */
|
|
34
|
+
onEnd: (end: SessionEnd) => void;
|
|
35
|
+
warn: (message: string) => void;
|
|
36
|
+
/** Only tests change these. */
|
|
37
|
+
secure?: boolean;
|
|
38
|
+
timeout?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare class VoiceSession {
|
|
41
|
+
#private;
|
|
42
|
+
readonly server: VoiceServer;
|
|
43
|
+
readonly dave: DaveSession;
|
|
44
|
+
constructor(server: VoiceServer, options: SessionOptions);
|
|
45
|
+
/** Resolves once the session can send audio. */
|
|
46
|
+
connect(): Promise<void>;
|
|
47
|
+
/** Sends one Opus packet that plays `samples` samples. */
|
|
48
|
+
sendOpus(packet: Uint8Array, samples: number): void;
|
|
49
|
+
/** The five frames of silence Discord asks for before a pause. */
|
|
50
|
+
sendSilence(): void;
|
|
51
|
+
/** Speaking (5): needed before audio, and shown in Discord. */
|
|
52
|
+
setSpeaking(speaking: boolean): void;
|
|
53
|
+
/** Closes the session for good. */
|
|
54
|
+
close(): void;
|
|
55
|
+
}
|