chapterjs 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/dist/assets/asset.d.ts +46 -0
  2. package/dist/assets/asset.js +86 -0
  3. package/dist/assets/public.d.ts +19 -0
  4. package/dist/assets/public.js +120 -0
  5. package/dist/cache/cache.d.ts +62 -0
  6. package/dist/cache/cache.js +80 -0
  7. package/dist/cache/store.d.ts +45 -0
  8. package/dist/cache/store.js +53 -0
  9. package/dist/cli/build.d.ts +41 -0
  10. package/dist/cli/build.js +206 -0
  11. package/dist/cli/cli.js +108 -25
  12. package/dist/cli/cluster.d.ts +70 -0
  13. package/dist/cli/cluster.js +213 -0
  14. package/dist/cli/dev.d.ts +18 -1
  15. package/dist/cli/dev.js +252 -198
  16. package/dist/cli/env.d.ts +31 -0
  17. package/dist/cli/env.js +93 -0
  18. package/dist/cli/log.d.ts +13 -0
  19. package/dist/cli/log.js +17 -0
  20. package/dist/cli/node-version.d.ts +8 -0
  21. package/dist/cli/node-version.js +13 -0
  22. package/dist/cli/preflight.d.ts +36 -0
  23. package/dist/cli/preflight.js +122 -0
  24. package/dist/cli/project.d.ts +122 -0
  25. package/dist/cli/project.js +452 -0
  26. package/dist/cli/start.d.ts +25 -0
  27. package/dist/cli/start.js +225 -0
  28. package/dist/commands/autocomplete.d.ts +13 -0
  29. package/dist/commands/autocomplete.js +159 -0
  30. package/dist/commands/command.d.ts +346 -0
  31. package/dist/commands/command.js +28 -0
  32. package/dist/commands/convention.d.ts +64 -0
  33. package/dist/commands/convention.js +464 -0
  34. package/dist/commands/register.d.ts +18 -0
  35. package/dist/commands/register.js +42 -0
  36. package/dist/commands/router.d.ts +20 -0
  37. package/dist/commands/router.js +228 -0
  38. package/dist/commands/tree.d.ts +43 -0
  39. package/dist/commands/tree.js +206 -0
  40. package/dist/commands/twins.d.ts +15 -0
  41. package/dist/commands/twins.js +78 -0
  42. package/dist/components/button.d.ts +79 -0
  43. package/dist/components/button.js +108 -0
  44. package/dist/components/component.d.ts +140 -0
  45. package/dist/components/component.js +39 -0
  46. package/dist/components/convention.d.ts +35 -0
  47. package/dist/components/convention.js +339 -0
  48. package/dist/components/custom-id.d.ts +72 -0
  49. package/dist/components/custom-id.js +128 -0
  50. package/dist/components/embed.d.ts +28 -0
  51. package/dist/components/embed.js +11 -0
  52. package/dist/components/file.d.ts +30 -0
  53. package/dist/components/file.js +52 -0
  54. package/dist/components/instance.d.ts +100 -0
  55. package/dist/components/instance.js +86 -0
  56. package/dist/components/layout.d.ts +103 -0
  57. package/dist/components/layout.js +279 -0
  58. package/dist/components/modal.d.ts +244 -0
  59. package/dist/components/modal.js +399 -0
  60. package/dist/components/options.d.ts +41 -0
  61. package/dist/components/options.js +101 -0
  62. package/dist/components/render.d.ts +12 -0
  63. package/dist/components/render.js +58 -0
  64. package/dist/components/router.d.ts +18 -0
  65. package/dist/components/router.js +256 -0
  66. package/dist/components/select.d.ts +144 -0
  67. package/dist/components/select.js +179 -0
  68. package/dist/core/bot.d.ts +99 -0
  69. package/dist/core/bot.js +174 -0
  70. package/dist/core/memory.d.ts +66 -0
  71. package/dist/core/memory.js +115 -0
  72. package/dist/discord/api.d.ts +112 -0
  73. package/dist/discord/api.js +115 -0
  74. package/dist/discord/cdn.d.ts +67 -0
  75. package/dist/discord/cdn.js +100 -0
  76. package/dist/discord/codes.d.ts +171 -0
  77. package/dist/discord/codes.js +421 -0
  78. package/dist/discord/endpoint.d.ts +56 -0
  79. package/dist/discord/endpoint.js +53 -0
  80. package/dist/discord/endpoints.d.ts +1529 -0
  81. package/dist/discord/endpoints.js +1139 -0
  82. package/dist/discord/formatting.d.ts +67 -0
  83. package/dist/discord/formatting.js +87 -0
  84. package/dist/discord/index.d.ts +10 -0
  85. package/dist/discord/index.js +13 -0
  86. package/dist/discord/intents.d.ts +48 -0
  87. package/dist/discord/intents.js +128 -0
  88. package/dist/discord/permissions.d.ts +94 -0
  89. package/dist/discord/permissions.js +185 -0
  90. package/dist/discord/snowflake.d.ts +21 -0
  91. package/dist/discord/snowflake.js +36 -0
  92. package/dist/discord/types/application-command.d.ts +332 -0
  93. package/dist/discord/types/application-command.js +48 -0
  94. package/dist/discord/types/application-role-connection-metadata.d.ts +42 -0
  95. package/dist/discord/types/application-role-connection-metadata.js +22 -0
  96. package/dist/discord/types/application.d.ts +208 -0
  97. package/dist/discord/types/application.js +48 -0
  98. package/dist/discord/types/audit-log.d.ts +255 -0
  99. package/dist/discord/types/audit-log.js +144 -0
  100. package/dist/discord/types/auto-moderation.d.ts +167 -0
  101. package/dist/discord/types/auto-moderation.js +52 -0
  102. package/dist/discord/types/channel.d.ts +557 -0
  103. package/dist/discord/types/channel.js +88 -0
  104. package/dist/discord/types/common.d.ts +54 -0
  105. package/dist/discord/types/common.js +38 -0
  106. package/dist/discord/types/component.d.ts +775 -0
  107. package/dist/discord/types/component.js +76 -0
  108. package/dist/discord/types/emoji.d.ts +74 -0
  109. package/dist/discord/types/emoji.js +1 -0
  110. package/dist/discord/types/entitlement.d.ts +84 -0
  111. package/dist/discord/types/entitlement.js +22 -0
  112. package/dist/discord/types/gateway-events.d.ts +1148 -0
  113. package/dist/discord/types/gateway-events.js +54 -0
  114. package/dist/discord/types/gateway.d.ts +38 -0
  115. package/dist/discord/types/gateway.js +1 -0
  116. package/dist/discord/types/guild-scheduled-event.d.ts +257 -0
  117. package/dist/discord/types/guild-scheduled-event.js +68 -0
  118. package/dist/discord/types/guild-template.d.ts +51 -0
  119. package/dist/discord/types/guild-template.js +1 -0
  120. package/dist/discord/types/guild.d.ts +910 -0
  121. package/dist/discord/types/guild.js +153 -0
  122. package/dist/discord/types/index.d.ts +31 -0
  123. package/dist/discord/types/index.js +31 -0
  124. package/dist/discord/types/interaction.d.ts +321 -0
  125. package/dist/discord/types/interaction.js +47 -0
  126. package/dist/discord/types/invite.d.ts +158 -0
  127. package/dist/discord/types/invite.js +39 -0
  128. package/dist/discord/types/message.d.ts +834 -0
  129. package/dist/discord/types/message.js +159 -0
  130. package/dist/discord/types/oauth2.d.ts +22 -0
  131. package/dist/discord/types/oauth2.js +1 -0
  132. package/dist/discord/types/permissions.d.ts +183 -0
  133. package/dist/discord/types/permissions.js +118 -0
  134. package/dist/discord/types/poll.d.ts +106 -0
  135. package/dist/discord/types/poll.js +8 -0
  136. package/dist/discord/types/sku.d.ts +47 -0
  137. package/dist/discord/types/sku.js +26 -0
  138. package/dist/discord/types/soundboard.d.ts +71 -0
  139. package/dist/discord/types/soundboard.js +1 -0
  140. package/dist/discord/types/stage-instance.d.ts +58 -0
  141. package/dist/discord/types/stage-instance.js +10 -0
  142. package/dist/discord/types/sticker.d.ts +115 -0
  143. package/dist/discord/types/sticker.js +20 -0
  144. package/dist/discord/types/subscription.d.ts +54 -0
  145. package/dist/discord/types/subscription.js +12 -0
  146. package/dist/discord/types/team.d.ts +55 -0
  147. package/dist/discord/types/team.js +21 -0
  148. package/dist/discord/types/user.d.ts +273 -0
  149. package/dist/discord/types/user.js +83 -0
  150. package/dist/discord/types/voice.d.ts +72 -0
  151. package/dist/discord/types/voice.js +1 -0
  152. package/dist/discord/types/webhook.d.ts +184 -0
  153. package/dist/discord/types/webhook.js +12 -0
  154. package/dist/events/convention.d.ts +25 -0
  155. package/dist/events/convention.js +97 -0
  156. package/dist/events/event.d.ts +8 -0
  157. package/dist/events/event.js +20 -0
  158. package/dist/events/registry.d.ts +706 -0
  159. package/dist/events/registry.js +839 -0
  160. package/dist/events/router.d.ts +45 -0
  161. package/dist/events/router.js +133 -0
  162. package/dist/events/types.d.ts +7 -0
  163. package/dist/events/types.js +48 -0
  164. package/dist/gateway/errors.d.ts +18 -0
  165. package/dist/gateway/errors.js +47 -0
  166. package/dist/gateway/gateway.d.ts +64 -0
  167. package/dist/gateway/gateway.js +117 -0
  168. package/dist/gateway/identify-queue.d.ts +25 -0
  169. package/dist/gateway/identify-queue.js +29 -0
  170. package/dist/gateway/shard.d.ts +94 -0
  171. package/dist/gateway/shard.js +414 -0
  172. package/dist/gateway/state.d.ts +11 -0
  173. package/dist/gateway/state.js +184 -0
  174. package/dist/index.d.ts +55 -18
  175. package/dist/index.js +38 -12
  176. package/dist/interactions/cooldown.d.ts +33 -0
  177. package/dist/interactions/cooldown.js +74 -0
  178. package/dist/interactions/dispatch.d.ts +91 -0
  179. package/dist/interactions/dispatch.js +110 -0
  180. package/dist/loader/generated.d.ts +37 -0
  181. package/dist/loader/generated.js +114 -0
  182. package/dist/loader/hot.d.ts +13 -0
  183. package/dist/loader/hot.js +85 -0
  184. package/dist/loader/loader.d.ts +72 -0
  185. package/dist/loader/loader.js +135 -0
  186. package/dist/loader/locate.d.ts +10 -0
  187. package/dist/loader/locate.js +36 -0
  188. package/dist/loader/watch.d.ts +17 -0
  189. package/dist/loader/watch.js +66 -0
  190. package/dist/main.d.ts +3 -0
  191. package/dist/main.js +6 -0
  192. package/dist/messages/commands.d.ts +23 -0
  193. package/dist/messages/commands.js +123 -0
  194. package/dist/messages/convention.d.ts +89 -0
  195. package/dist/messages/convention.js +347 -0
  196. package/dist/messages/messages.d.ts +219 -0
  197. package/dist/messages/messages.js +30 -0
  198. package/dist/messages/phrases.d.ts +23 -0
  199. package/dist/messages/phrases.js +44 -0
  200. package/dist/messages/translate.d.ts +100 -0
  201. package/dist/messages/translate.js +249 -0
  202. package/dist/presence/convention.d.ts +26 -0
  203. package/dist/presence/convention.js +133 -0
  204. package/dist/presence/presence.d.ts +57 -0
  205. package/dist/presence/presence.js +27 -0
  206. package/dist/rest/errors.d.ts +55 -0
  207. package/dist/rest/errors.js +92 -0
  208. package/dist/rest/rate-limiter.d.ts +46 -0
  209. package/dist/rest/rate-limiter.js +153 -0
  210. package/dist/rest/rest.d.ts +63 -0
  211. package/dist/rest/rest.js +194 -0
  212. package/dist/structures/base.d.ts +48 -0
  213. package/dist/structures/base.js +84 -0
  214. package/dist/structures/channel.d.ts +380 -0
  215. package/dist/structures/channel.js +627 -0
  216. package/dist/structures/context.d.ts +30 -0
  217. package/dist/structures/context.js +7 -0
  218. package/dist/structures/emoji.d.ts +46 -0
  219. package/dist/structures/emoji.js +65 -0
  220. package/dist/structures/entities.d.ts +62 -0
  221. package/dist/structures/entities.js +226 -0
  222. package/dist/structures/guild.d.ts +348 -0
  223. package/dist/structures/guild.js +466 -0
  224. package/dist/structures/interaction.d.ts +197 -0
  225. package/dist/structures/interaction.js +396 -0
  226. package/dist/structures/invite.d.ts +38 -0
  227. package/dist/structures/invite.js +57 -0
  228. package/dist/structures/known.d.ts +28 -0
  229. package/dist/structures/known.js +58 -0
  230. package/dist/structures/member.d.ts +140 -0
  231. package/dist/structures/member.js +260 -0
  232. package/dist/structures/message.d.ts +197 -0
  233. package/dist/structures/message.js +268 -0
  234. package/dist/structures/payload.d.ts +91 -0
  235. package/dist/structures/payload.js +184 -0
  236. package/dist/structures/role.d.ts +69 -0
  237. package/dist/structures/role.js +112 -0
  238. package/dist/structures/user.d.ts +59 -0
  239. package/dist/structures/user.js +94 -0
  240. package/dist/structures/webhook.d.ts +46 -0
  241. package/dist/structures/webhook.js +79 -0
  242. package/dist/tasks/convention.d.ts +26 -0
  243. package/dist/tasks/convention.js +78 -0
  244. package/dist/tasks/schedule.d.ts +54 -0
  245. package/dist/tasks/schedule.js +236 -0
  246. package/dist/tasks/scheduler.d.ts +33 -0
  247. package/dist/tasks/scheduler.js +107 -0
  248. package/dist/tasks/task.d.ts +76 -0
  249. package/dist/tasks/task.js +28 -0
  250. package/dist/util/case.d.ts +13 -0
  251. package/dist/util/case.js +54 -0
  252. package/dist/util/duration.d.ts +8 -0
  253. package/dist/util/duration.js +29 -0
  254. package/dist/util/websocket.d.ts +9 -0
  255. package/dist/util/websocket.js +31 -0
  256. package/dist/voice/connection.d.ts +90 -0
  257. package/dist/voice/connection.js +494 -0
  258. package/dist/voice/dave.d.ts +109 -0
  259. package/dist/voice/dave.js +245 -0
  260. package/dist/voice/opus.d.ts +19 -0
  261. package/dist/voice/opus.js +273 -0
  262. package/dist/voice/session.d.ts +55 -0
  263. package/dist/voice/session.js +405 -0
  264. package/dist/voice/source.d.ts +22 -0
  265. package/dist/voice/source.js +195 -0
  266. package/dist/voice/transport.d.ts +43 -0
  267. package/dist/voice/transport.js +140 -0
  268. package/dist/voice/usage.d.ts +4 -0
  269. package/dist/voice/usage.js +33 -0
  270. package/package.json +26 -7
  271. package/vendor/dave/LICENSE +231 -0
  272. package/vendor/dave/libdave.d.mts +99 -0
  273. package/vendor/dave/libdave.mjs +2 -0
  274. package/vendor/dave/libdave.wasm +0 -0
  275. package/dist/components.d.ts +0 -97
  276. package/dist/components.js +0 -260
  277. package/dist/context.d.ts +0 -72
  278. package/dist/context.js +0 -273
  279. package/dist/discord/client.d.ts +0 -35
  280. package/dist/discord/client.js +0 -66
  281. package/dist/discord/constants.d.ts +0 -49
  282. package/dist/discord/constants.js +0 -61
  283. package/dist/discord/events.d.ts +0 -129
  284. package/dist/discord/events.js +0 -102
  285. package/dist/discord/gateway.d.ts +0 -61
  286. package/dist/discord/gateway.js +0 -320
  287. package/dist/discord/payloads.d.ts +0 -275
  288. package/dist/discord/payloads.js +0 -3
  289. package/dist/discord/presence.d.ts +0 -42
  290. package/dist/discord/presence.js +0 -51
  291. package/dist/discord/rest.d.ts +0 -49
  292. package/dist/discord/rest.js +0 -271
  293. package/dist/discord/types.d.ts +0 -334
  294. package/dist/discord/types.js +0 -4
  295. package/dist/events.d.ts +0 -94
  296. package/dist/events.js +0 -255
  297. package/dist/presence.d.ts +0 -92
  298. package/dist/presence.js +0 -250
  299. package/dist/structures/Channel.d.ts +0 -110
  300. package/dist/structures/Channel.js +0 -171
  301. package/dist/structures/Guild.d.ts +0 -70
  302. package/dist/structures/Guild.js +0 -105
  303. package/dist/structures/Interaction.d.ts +0 -113
  304. package/dist/structures/Interaction.js +0 -274
  305. package/dist/structures/Member.d.ts +0 -70
  306. package/dist/structures/Member.js +0 -124
  307. package/dist/structures/Message.d.ts +0 -87
  308. package/dist/structures/Message.js +0 -182
  309. package/dist/structures/Reaction.d.ts +0 -38
  310. package/dist/structures/Reaction.js +0 -64
  311. package/dist/structures/Role.d.ts +0 -43
  312. package/dist/structures/Role.js +0 -77
  313. package/dist/structures/User.d.ts +0 -41
  314. package/dist/structures/User.js +0 -79
  315. package/dist/structures/utils.d.ts +0 -72
  316. package/dist/structures/utils.js +0 -59
  317. package/dist/utils/errors.d.ts +0 -5
  318. package/dist/utils/errors.js +0 -28
  319. package/dist/utils/modules.d.ts +0 -39
  320. package/dist/utils/modules.js +0 -156
@@ -0,0 +1,90 @@
1
+ import { inspect } from 'node:util';
2
+ import type { Snowflake } from '../discord/types/common.js';
3
+ import type { GatewayDispatchEvents, RawGatewayVoiceStateUpdate } from '../discord/types/gateway-events.js';
4
+ import type { VoiceChannel } from '../structures/channel.js';
5
+ import type { Context } from '../structures/context.js';
6
+ import { type Guild } from '../structures/guild.js';
7
+ import { type AudioSource } from './source.js';
8
+ /** What `join()` takes. */
9
+ export interface JoinOptions {
10
+ /**
11
+ * Whether the bot is deafened: it hears nothing, which Discord then
12
+ * does not send it. Default: `true`, since the bot only plays.
13
+ */
14
+ deaf?: boolean;
15
+ /** Whether the bot shows as muted. Default: `false`. */
16
+ mute?: boolean;
17
+ }
18
+ /** What the bot sends to Discord's main gateway for voice. */
19
+ export interface VoiceGateway {
20
+ updateVoiceState(data: RawGatewayVoiceStateUpdate): Promise<void>;
21
+ /** Whether Discord sends the bot its own voice state (the intent). */
22
+ hasVoiceStates(): boolean;
23
+ }
24
+ /**
25
+ * The bot in a voice channel of a server: what it plays there, and how it
26
+ * leaves. `channel.join()` gives one; `guild.voice` is the one of a server.
27
+ */
28
+ export declare class VoiceConnection {
29
+ #private;
30
+ constructor(manager: VoiceManager, guildId: Snowflake, channelId: Snowflake);
31
+ /** The server of the voice channel. */
32
+ get guild(): Guild;
33
+ /** The voice channel the bot is in. */
34
+ get channel(): VoiceChannel;
35
+ /** Whether the bot is still in voice in this server. */
36
+ get connected(): boolean;
37
+ /** Whether something is playing, paused or not. */
38
+ get playing(): boolean;
39
+ /** Whether what is playing is paused. */
40
+ get paused(): boolean;
41
+ /**
42
+ * Plays a file of `public/` (`asset('music/intro.ogg')`), a file sent on
43
+ * Discord (the `attachment` option of a command) or a link. Opus
44
+ * (`.ogg`, `.webm`) is sent as is; anything else is converted with
45
+ * ffmpeg, which must then be installed. Stops what was playing.
46
+ * Resolves when it ends, is stopped, or another `play()` replaces it.
47
+ */
48
+ play(source: AudioSource): Promise<void>;
49
+ /** Pauses what is playing; `resume()` goes on from there. */
50
+ pause(): void;
51
+ /** Goes on with what was paused. */
52
+ resume(): void;
53
+ /** Stops what is playing: its `play()` resolves. */
54
+ stop(): void;
55
+ /** Leaves the voice channel. */
56
+ leave(): Promise<void>;
57
+ /**
58
+ * What `console.log` and `JSON.stringify` show: the server, the channel
59
+ * and what plays, never the session or its keys.
60
+ */
61
+ toJSON(): object;
62
+ [inspect.custom](): string;
63
+ }
64
+ /** Where the bot is in voice in each server, and how it gets there. */
65
+ export declare class VoiceManager {
66
+ #private;
67
+ constructor(ctx: Context, gateway: VoiceGateway, options: {
68
+ warn: (message: string) => void;
69
+ secure?: boolean;
70
+ joinTimeout?: number;
71
+ endedGrace?: number;
72
+ });
73
+ /** The connection of the bot in a server, if it is in voice there. */
74
+ connectionOf(guildId: Snowflake): VoiceConnection | null;
75
+ guild(guildId: Snowflake): Guild;
76
+ channel(guildId: Snowflake, channelId: Snowflake): VoiceChannel;
77
+ /**
78
+ * Joins a voice channel, or moves there from another channel of the
79
+ * same server. Resolves once audio can be sent.
80
+ */
81
+ join(channel: VoiceChannel, options?: JoinOptions): Promise<VoiceConnection>;
82
+ /** Leaves the voice channel of a server. */
83
+ leave(guildId: Snowflake): Promise<void>;
84
+ /** Closes every connection: the bot is going offline. */
85
+ closeAll(): void;
86
+ /** The bot's own Voice State Update. */
87
+ onVoiceState(data: GatewayDispatchEvents['VOICE_STATE_UPDATE']): void;
88
+ /** Voice Server Update: a voice server, and the token to use it. */
89
+ onVoiceServer(data: GatewayDispatchEvents['VOICE_SERVER_UPDATE']): void;
90
+ }
@@ -0,0 +1,494 @@
1
+ // The bot in a voice channel. A `VoiceConnection` is what user code holds:
2
+ // one per server, the same object while the bot stays in voice there, even
3
+ // when it moves or its session with the voice server is renewed. Joining,
4
+ // moving and leaving go through the main gateway (Voice State Update);
5
+ // what Discord answers is routed here by `VoiceManager`.
6
+ // https://docs.discord.com/developers/topics/voice-connections#connecting-to-voice
7
+ import { inspect } from 'node:util';
8
+ import { ModifyCurrentUserVoiceState } from '../discord/endpoints.js';
9
+ import { storesOf } from '../structures/guild.js';
10
+ import { loadDave } from './dave.js';
11
+ import { opusSamples } from './opus.js';
12
+ import { VoiceSession } from './session.js';
13
+ import { openAudio } from './source.js';
14
+ import { SAMPLE_RATE } from './transport.js';
15
+ /** How long to wait for Discord to let the bot in. */
16
+ const JOIN_TIMEOUT = 10_000;
17
+ /** How long playing waits for the end-to-end encryption of the call. */
18
+ const ENCRYPTION_WAIT = 5_000;
19
+ let internals;
20
+ /**
21
+ * The bot in a voice channel of a server: what it plays there, and how it
22
+ * leaves. `channel.join()` gives one; `guild.voice` is the one of a server.
23
+ */
24
+ export class VoiceConnection {
25
+ #manager;
26
+ #guildId;
27
+ #channelId;
28
+ #session = null;
29
+ #playing = null;
30
+ #left = false;
31
+ constructor(manager, guildId, channelId) {
32
+ this.#manager = manager;
33
+ this.#guildId = guildId;
34
+ this.#channelId = channelId;
35
+ }
36
+ /** The server of the voice channel. */
37
+ get guild() {
38
+ return this.#manager.guild(this.#guildId);
39
+ }
40
+ /** The voice channel the bot is in. */
41
+ get channel() {
42
+ return this.#manager.channel(this.#guildId, this.#channelId);
43
+ }
44
+ /** Whether the bot is still in voice in this server. */
45
+ get connected() {
46
+ return !this.#left;
47
+ }
48
+ /** Whether something is playing, paused or not. */
49
+ get playing() {
50
+ return this.#playing !== null;
51
+ }
52
+ /** Whether what is playing is paused. */
53
+ get paused() {
54
+ return this.#playing?.paused ?? false;
55
+ }
56
+ /**
57
+ * Plays a file of `public/` (`asset('music/intro.ogg')`), a file sent on
58
+ * Discord (the `attachment` option of a command) or a link. Opus
59
+ * (`.ogg`, `.webm`) is sent as is; anything else is converted with
60
+ * ffmpeg, which must then be installed. Stops what was playing.
61
+ * Resolves when it ends, is stopped, or another `play()` replaces it.
62
+ */
63
+ async play(source) {
64
+ this.#check();
65
+ const channel = this.channel;
66
+ const me = this.guild.me;
67
+ if (!me.permissionsIn(channel).has('Speak')) {
68
+ throw new Error(`The bot can't play in ${channel.name}: it needs the Speak permission there.`);
69
+ }
70
+ const stream = await openAudio(source);
71
+ this.stop();
72
+ if (this.#left) {
73
+ stream.close();
74
+ return;
75
+ }
76
+ const playing = {
77
+ stream,
78
+ paused: false,
79
+ stopped: false,
80
+ resume: null,
81
+ done: Promise.resolve(),
82
+ };
83
+ playing.done = this.#run(playing);
84
+ this.#playing = playing;
85
+ await playing.done;
86
+ }
87
+ /** Pauses what is playing; `resume()` goes on from there. */
88
+ pause() {
89
+ const playing = this.#playing;
90
+ if (playing)
91
+ playing.paused = true;
92
+ }
93
+ /** Goes on with what was paused. */
94
+ resume() {
95
+ const playing = this.#playing;
96
+ if (!playing?.paused)
97
+ return;
98
+ playing.paused = false;
99
+ playing.resume?.();
100
+ playing.resume = null;
101
+ }
102
+ /** Stops what is playing: its `play()` resolves. */
103
+ stop() {
104
+ const playing = this.#playing;
105
+ if (!playing)
106
+ return;
107
+ playing.stopped = true;
108
+ playing.paused = false;
109
+ playing.resume?.();
110
+ playing.resume = null;
111
+ playing.stream.close();
112
+ }
113
+ /** Leaves the voice channel. */
114
+ async leave() {
115
+ if (this.#left)
116
+ return;
117
+ this.#close();
118
+ await this.#manager.leave(this.#guildId);
119
+ }
120
+ /**
121
+ * What `console.log` and `JSON.stringify` show: the server, the channel
122
+ * and what plays, never the session or its keys.
123
+ */
124
+ toJSON() {
125
+ return {
126
+ guildId: this.#guildId,
127
+ channelId: this.#channelId,
128
+ connected: this.connected,
129
+ playing: this.playing,
130
+ paused: this.paused,
131
+ };
132
+ }
133
+ [inspect.custom]() {
134
+ return `VoiceConnection ${inspect(this.toJSON())}`;
135
+ }
136
+ static {
137
+ internals = connection => ({
138
+ attach(session, channelId) {
139
+ if (connection.#session !== session)
140
+ connection.#session?.close();
141
+ connection.#session = session;
142
+ connection.#channelId = channelId;
143
+ },
144
+ session: () => connection.#session,
145
+ channelId: () => connection.#channelId,
146
+ moved(channelId) {
147
+ connection.#channelId = channelId;
148
+ },
149
+ closed: () => connection.#close(),
150
+ });
151
+ }
152
+ #close() {
153
+ this.#left = true;
154
+ this.stop();
155
+ this.#session?.close();
156
+ this.#session = null;
157
+ }
158
+ #check() {
159
+ if (this.#left) {
160
+ throw new Error('The bot is no longer in this voice channel: join it again with channel.join().');
161
+ }
162
+ }
163
+ /** Sends the packets in time: each when the previous one has played. */
164
+ async #run(playing) {
165
+ const encrypted = this.#session?.dave.ready();
166
+ if (encrypted) {
167
+ await Promise.race([encrypted, sleep(ENCRYPTION_WAIT)]);
168
+ }
169
+ const packets = playing.stream.packets[Symbol.asyncIterator]();
170
+ let next = performance.now();
171
+ try {
172
+ while (!playing.stopped) {
173
+ if (playing.paused) {
174
+ this.#session?.sendSilence();
175
+ this.#session?.setSpeaking(false);
176
+ await new Promise(resolve => (playing.resume = resolve));
177
+ next = performance.now();
178
+ continue;
179
+ }
180
+ const { value, done } = await packets.next();
181
+ if (done || playing.stopped)
182
+ break;
183
+ const samples = opusSamples(value) || 960;
184
+ // While reconnecting there is no session: the audio goes on.
185
+ this.#session?.sendOpus(value, samples);
186
+ next += (samples * 1000) / SAMPLE_RATE;
187
+ const wait = next - performance.now();
188
+ if (wait > 0)
189
+ await sleep(wait);
190
+ // Far behind (the process was busy): start counting again.
191
+ else if (wait < -200)
192
+ next = performance.now();
193
+ }
194
+ }
195
+ finally {
196
+ playing.stream.close();
197
+ void packets.return?.();
198
+ if (this.#playing === playing)
199
+ this.#playing = null;
200
+ if (!this.#left) {
201
+ this.#session?.sendSilence();
202
+ this.#session?.setSpeaking(false);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
208
+ /** Where the bot is in voice in each server, and how it gets there. */
209
+ export class VoiceManager {
210
+ #ctx;
211
+ #gateway;
212
+ #warn;
213
+ #connections = new Map();
214
+ #pending = new Map();
215
+ /** The voice session id Discord gave the bot, per server. */
216
+ #sessionIds = new Map();
217
+ /** Only tests change these. */
218
+ #secure;
219
+ #joinTimeout;
220
+ #endedGrace;
221
+ constructor(ctx, gateway, options) {
222
+ this.#ctx = ctx;
223
+ this.#gateway = gateway;
224
+ this.#warn = options.warn;
225
+ this.#secure = options.secure ?? true;
226
+ this.#joinTimeout = options.joinTimeout ?? JOIN_TIMEOUT;
227
+ this.#endedGrace = options.endedGrace ?? 3000;
228
+ }
229
+ /** The connection of the bot in a server, if it is in voice there. */
230
+ connectionOf(guildId) {
231
+ return this.#connections.get(guildId) ?? null;
232
+ }
233
+ guild(guildId) {
234
+ const guild = this.#ctx.cache.guilds.get(guildId);
235
+ if (!guild) {
236
+ throw new Error(`The server ${guildId} is not one the bot is in any more.`);
237
+ }
238
+ return guild;
239
+ }
240
+ channel(guildId, channelId) {
241
+ const channel = this.guild(guildId).channels.get(channelId);
242
+ if (!channel || !('bitrate' in channel)) {
243
+ throw new Error(`The voice channel ${channelId} does not exist any more.`);
244
+ }
245
+ return channel;
246
+ }
247
+ /**
248
+ * Joins a voice channel, or moves there from another channel of the
249
+ * same server. Resolves once audio can be sent.
250
+ */
251
+ async join(channel, options = {}) {
252
+ const guild = channel.guild;
253
+ const me = guild.me;
254
+ const permissions = me.permissionsIn(channel);
255
+ if (!permissions.has('Connect')) {
256
+ throw new Error(`The bot can't join ${channel.name}: it needs the Connect permission there.`);
257
+ }
258
+ let inside = 0;
259
+ for (const [userId, entry] of storesOf(guild).voiceStates) {
260
+ if (entry.channel.id === channel.id && userId !== me.id)
261
+ inside++;
262
+ }
263
+ if (channel.userLimit > 0 &&
264
+ inside >= channel.userLimit &&
265
+ !permissions.has('MoveMembers')) {
266
+ throw new Error(`The bot can't join ${channel.name}: it is full (${channel.userLimit} people), and the bot does not have the Move Members permission that lets it in anyway.`);
267
+ }
268
+ const existing = this.#connections.get(guild.id);
269
+ if (existing?.connected && existing.channel.id === channel.id) {
270
+ return existing;
271
+ }
272
+ const connection = existing?.connected
273
+ ? existing
274
+ : new VoiceConnection(this, guild.id, channel.id);
275
+ this.#connections.set(guild.id, connection);
276
+ await this.#request(guild.id, channel, options);
277
+ if (channel.isStage)
278
+ await this.#speakOnStage(guild.id, channel);
279
+ return connection;
280
+ }
281
+ /** Leaves the voice channel of a server. */
282
+ async leave(guildId) {
283
+ const connection = this.#connections.get(guildId);
284
+ this.#connections.delete(guildId);
285
+ this.#pending
286
+ .get(guildId)
287
+ ?.settle(new Error('The bot left before it was in the channel.'));
288
+ if (connection)
289
+ internals(connection).closed();
290
+ await this.#gateway.updateVoiceState({
291
+ guild_id: guildId,
292
+ channel_id: null,
293
+ self_mute: false,
294
+ self_deaf: false,
295
+ });
296
+ }
297
+ /** Closes every connection: the bot is going offline. */
298
+ closeAll() {
299
+ for (const connection of this.#connections.values())
300
+ internals(connection).closed();
301
+ this.#connections.clear();
302
+ }
303
+ /** The bot's own Voice State Update. */
304
+ onVoiceState(data) {
305
+ const guildId = data.guild_id;
306
+ if (!guildId)
307
+ return;
308
+ this.#sessionIds.set(guildId, data.session_id);
309
+ const pending = this.#pending.get(guildId);
310
+ if (pending && data.channel_id === pending.channelId) {
311
+ pending.sessionId = data.session_id;
312
+ this.#tryConnect(guildId);
313
+ return;
314
+ }
315
+ const connection = this.#connections.get(guildId);
316
+ if (!connection)
317
+ return;
318
+ if (data.channel_id === null) {
319
+ // Disconnected by someone, or the channel was deleted.
320
+ this.#connections.delete(guildId);
321
+ internals(connection).closed();
322
+ }
323
+ else {
324
+ internals(connection).moved(data.channel_id);
325
+ }
326
+ }
327
+ /** Voice Server Update: a voice server, and the token to use it. */
328
+ onVoiceServer(data) {
329
+ // No endpoint: Discord is still choosing a server, another comes.
330
+ if (!data.endpoint)
331
+ return;
332
+ const guildId = data.guild_id;
333
+ const server = { token: data.token, endpoint: data.endpoint };
334
+ const pending = this.#pending.get(guildId);
335
+ if (pending) {
336
+ pending.server = server;
337
+ this.#tryConnect(guildId);
338
+ return;
339
+ }
340
+ // A new server for a connection that exists: moved, or renewed.
341
+ const connection = this.#connections.get(guildId);
342
+ const sessionId = this.#sessionIds.get(guildId);
343
+ if (!connection?.connected || !sessionId)
344
+ return;
345
+ void this.#open(connection, guildId, internals(connection).channelId(), {
346
+ ...server,
347
+ sessionId,
348
+ }).catch(error => {
349
+ this.#warn(`the bot lost its voice connection: ${error.message}`);
350
+ this.#connections.delete(guildId);
351
+ internals(connection).closed();
352
+ });
353
+ }
354
+ /** Asks Discord to put the bot in a channel, and waits for its answers. */
355
+ #request(guildId, channel, options) {
356
+ this.#pending
357
+ .get(guildId)
358
+ ?.settle(new Error(`The bot was asked to join ${channel.name} instead.`));
359
+ return new Promise((resolve, reject) => {
360
+ const timer = setTimeout(() => {
361
+ const pending = this.#pending.get(guildId);
362
+ const reason = pending?.server &&
363
+ !pending.sessionId &&
364
+ !this.#gateway.hasVoiceStates()
365
+ ? `Discord did not send the bot its voice state: it was connected without the GUILD_VOICE_STATES intent. ChapterJS asks for it when a file of your project calls join(); restart the bot so it is asked.`
366
+ : `Discord did not let the bot join ${channel.name} within ${this.#joinTimeout / 1000} s.`;
367
+ settle(new Error(reason));
368
+ }, this.#joinTimeout);
369
+ const settle = (error) => {
370
+ clearTimeout(timer);
371
+ if (this.#pending.get(guildId)?.settle === settle) {
372
+ this.#pending.delete(guildId);
373
+ }
374
+ if (error) {
375
+ const connection = this.#connections.get(guildId);
376
+ if (connection && !internals(connection).session()) {
377
+ this.#connections.delete(guildId);
378
+ internals(connection).closed();
379
+ }
380
+ reject(error);
381
+ }
382
+ else
383
+ resolve();
384
+ };
385
+ this.#pending.set(guildId, {
386
+ channelId: channel.id,
387
+ sessionId: null,
388
+ server: null,
389
+ settle,
390
+ });
391
+ this.#gateway
392
+ .updateVoiceState({
393
+ guild_id: guildId,
394
+ channel_id: channel.id,
395
+ self_mute: options.mute ?? false,
396
+ self_deaf: options.deaf ?? true,
397
+ })
398
+ .catch(settle);
399
+ });
400
+ }
401
+ #tryConnect(guildId) {
402
+ const pending = this.#pending.get(guildId);
403
+ const connection = this.#connections.get(guildId);
404
+ if (!pending?.sessionId || !pending.server || !connection)
405
+ return;
406
+ this.#open(connection, guildId, pending.channelId, {
407
+ ...pending.server,
408
+ sessionId: pending.sessionId,
409
+ }).then(() => pending.settle(), error => pending.settle(error));
410
+ }
411
+ /** A new session with a voice server, given to the connection. */
412
+ async #open(connection, guildId, channelId, server) {
413
+ const self = this.#ctx.self;
414
+ if (!self)
415
+ throw new Error('The bot is not connected to Discord yet.');
416
+ const dave = await loadDave();
417
+ const info = {
418
+ ...server,
419
+ guildId,
420
+ channelId,
421
+ userId: self.userId,
422
+ };
423
+ const session = new VoiceSession(info, {
424
+ dave,
425
+ secure: this.#secure,
426
+ warn: message => this.#warn(message),
427
+ onEnd: end => this.#ended(connection, session, end),
428
+ });
429
+ await session.connect();
430
+ internals(connection).attach(session, info.channelId);
431
+ }
432
+ /** A session ended without the bot leaving. */
433
+ #ended(connection, session, end) {
434
+ // An older session, replaced by a move: nothing to do.
435
+ if (internals(connection).session() !== session)
436
+ return;
437
+ const guildId = session.server.guildId;
438
+ // A move is under way: its new session replaces this one.
439
+ if (this.#pending.has(guildId))
440
+ return;
441
+ if (end.type === 'ended' && connection.connected) {
442
+ // Discord may close the old session of a bot it moved before the
443
+ // new server arrives: wait for it a moment.
444
+ setTimeout(() => {
445
+ if (internals(connection).session() === session &&
446
+ connection.connected) {
447
+ this.#connections.delete(guildId);
448
+ internals(connection).closed();
449
+ }
450
+ }, this.#endedGrace).unref();
451
+ return;
452
+ }
453
+ if (end.type === 'lost' && connection.connected) {
454
+ // Ask Discord again: a new session comes with a new token.
455
+ void this.#gateway
456
+ .updateVoiceState({
457
+ guild_id: guildId,
458
+ channel_id: internals(connection).channelId(),
459
+ self_mute: false,
460
+ self_deaf: true,
461
+ })
462
+ .catch(() => { });
463
+ return;
464
+ }
465
+ if (end.type === 'refused') {
466
+ this.#warn(`the voice server refused the bot (${end.code}): ${end.reason}.`);
467
+ }
468
+ this.#connections.delete(guildId);
469
+ internals(connection).closed();
470
+ }
471
+ /**
472
+ * On a stage, the bot is in the audience until it speaks: it asks to
473
+ * become a speaker, and to speak when it may not decide it.
474
+ * https://docs.discord.com/developers/resources/voice#modify-current-user-voice-state
475
+ */
476
+ async #speakOnStage(guildId, channel) {
477
+ try {
478
+ await this.#ctx.rest.request(ModifyCurrentUserVoiceState, [guildId], {
479
+ body: { channel_id: channel.id, suppress: false },
480
+ });
481
+ }
482
+ catch {
483
+ await this.#ctx.rest
484
+ .request(ModifyCurrentUserVoiceState, [guildId], {
485
+ body: {
486
+ channel_id: channel.id,
487
+ request_to_speak_timestamp: new Date().toISOString(),
488
+ },
489
+ })
490
+ .catch(() => { });
491
+ this.#warn(`the bot asked to speak on the stage ${channel.name}: a moderator must accept, or give it the Mute Members permission so it speaks by itself.`);
492
+ }
493
+ }
494
+ }
@@ -0,0 +1,109 @@
1
+ import { VoiceOpcode } from '../discord/codes.js';
2
+ import type { Snowflake } from '../discord/types/common.js';
3
+ /** What chapterjs uses of libdave's WebAssembly module. */
4
+ interface DaveModule {
5
+ HEAPU8: Uint8Array;
6
+ _malloc(size: number): number;
7
+ _free(pointer: number): void;
8
+ MaxSupportedProtocolVersion(): number;
9
+ MediaType: {
10
+ Audio: unknown;
11
+ };
12
+ Codec: {
13
+ Opus: unknown;
14
+ };
15
+ TransientKeys: new () => TransientKeys;
16
+ Session: new (context: string, authSessionId: string, onFailure: (source: string, reason: string) => void) => MlsSession;
17
+ Encryptor: new () => FrameEncryptor;
18
+ }
19
+ interface Deletable {
20
+ delete(): void;
21
+ }
22
+ interface TransientKeys extends Deletable {
23
+ GetTransientPrivateKey(version: number): Deletable;
24
+ }
25
+ interface KeyRatchet {
26
+ cipherSuite: number;
27
+ baseSecret: number[];
28
+ }
29
+ interface MlsSession extends Deletable {
30
+ Init(version: number, groupId: bigint, userId: string, key: Deletable): void;
31
+ Reset(): void;
32
+ SetProtocolVersion(version: number): void;
33
+ GetProtocolVersion(): number;
34
+ SetExternalSender(sender: Uint8Array): void;
35
+ ProcessProposals(proposals: Uint8Array, recognizedUserIds: string[]): number[] | null;
36
+ ProcessCommit(commit: Uint8Array): {
37
+ failed: boolean;
38
+ ignored: boolean;
39
+ };
40
+ ProcessWelcome(welcome: Uint8Array, recognizedUserIds: string[]): object | null;
41
+ GetMarshalledKeyPackage(): number[];
42
+ GetKeyRatchet(userId: string): KeyRatchet | null;
43
+ }
44
+ interface FrameEncryptor extends Deletable {
45
+ SetKeyRatchet(ratchet: KeyRatchet | null): void;
46
+ SetPassthroughMode(passthrough: boolean): void;
47
+ AssignSsrcToCodec(ssrc: number, codec: unknown): void;
48
+ GetMaxCiphertextByteSize(mediaType: unknown, size: number): number;
49
+ Encrypt(mediaType: unknown, ssrc: number, pointer: number, length: number, capacity: number): number;
50
+ }
51
+ /** libdave, loaded the first time a voice connection needs it. */
52
+ export declare function loadDave(): Promise<DaveModule>;
53
+ /** How a DAVE session talks back to the voice gateway. */
54
+ export interface DaveTransport {
55
+ json(op: VoiceOpcode, data: unknown): void;
56
+ binary(op: VoiceOpcode, payload: Uint8Array): void;
57
+ }
58
+ /** Something libdave refused: reported, and the session recovers by itself. */
59
+ export type DaveWarning = (message: string) => void;
60
+ /**
61
+ * The DAVE state of one voice connection: the MLS group of the call and
62
+ * the encryptor of what the bot sends.
63
+ */
64
+ export declare class DaveSession {
65
+ #private;
66
+ constructor(dave: DaveModule, options: {
67
+ userId: Snowflake;
68
+ channelId: Snowflake;
69
+ transport: DaveTransport;
70
+ warn: DaveWarning;
71
+ });
72
+ /** The highest DAVE version the bot speaks, for Identify. */
73
+ get maxVersion(): number;
74
+ /** The protocol version of the call; 0 without E2EE. */
75
+ get version(): number;
76
+ /** Whether frames can be sent now. */
77
+ get canSend(): boolean;
78
+ /** Resolves once frames can be sent. */
79
+ ready(): Promise<void>;
80
+ /** The SSRC of the bot, which the encryptor files its frames under. */
81
+ setSsrc(ssrc: number): void;
82
+ /** Session Description (4): the version the call starts with. */
83
+ start(version: number): void;
84
+ /** Clients Connect (11) and Client Disconnect (13). */
85
+ clientsConnect(userIds: readonly Snowflake[]): void;
86
+ clientDisconnect(userId: Snowflake): void;
87
+ /** DAVE Prepare Transition (21): a move to another version, 0 included. */
88
+ prepareTransition(transitionId: number, version: number): void;
89
+ /** DAVE Execute Transition (22): senders switch now. */
90
+ executeTransition(transitionId: number): void;
91
+ /** DAVE Prepare Epoch (24): a new group when `epoch` is 1. */
92
+ prepareEpoch(epoch: number, version: number): void;
93
+ /** DAVE MLS External Sender (25). */
94
+ externalSender(payload: Uint8Array): void;
95
+ /** DAVE MLS Proposals (27): answered with a commit when there is one. */
96
+ proposals(payload: Uint8Array): void;
97
+ /** DAVE MLS Announce Commit Transition (29). */
98
+ announceCommit(transitionId: number, commit: Uint8Array): void;
99
+ /** DAVE MLS Welcome (30): the bot joins the group. */
100
+ welcome(transitionId: number, welcome: Uint8Array): void;
101
+ /**
102
+ * One Opus frame as it travels: end-to-end encrypted when the call is,
103
+ * unchanged otherwise.
104
+ */
105
+ encrypt(frame: Uint8Array): Uint8Array;
106
+ /** Frees what libdave holds for this connection. */
107
+ close(): void;
108
+ }
109
+ export {};