lunibee 0.3.3 → 0.4.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/README.md +1 -1
- package/dist/builders/commands.d.ts +1 -1
- package/dist/builders/components.d.ts +9 -11
- package/dist/builders/embed.d.ts +1 -1
- package/dist/builders/index.d.ts +1 -1
- package/dist/builders/index.js +87 -1298
- package/dist/builders/index.js.map +3 -11
- package/dist/chunks/index-0wn81eng.js +2492 -0
- package/dist/chunks/index-0wn81eng.js.map +17 -0
- package/dist/chunks/index-2en489j6.js +40 -0
- package/dist/chunks/index-2en489j6.js.map +10 -0
- package/dist/chunks/index-3hyps48a.js +203 -0
- package/dist/chunks/index-3hyps48a.js.map +11 -0
- package/dist/chunks/index-57c5n3ms.js +2274 -0
- package/dist/chunks/index-57c5n3ms.js.map +19 -0
- package/dist/chunks/index-5yx92e36.js +1170 -0
- package/dist/chunks/index-5yx92e36.js.map +14 -0
- package/dist/chunks/index-690bp093.js +95 -0
- package/dist/chunks/index-690bp093.js.map +10 -0
- package/dist/chunks/index-7fp2hvmg.js +2155 -0
- package/dist/chunks/index-7fp2hvmg.js.map +24 -0
- package/dist/chunks/index-cnmsajvk.js +1119 -0
- package/dist/chunks/index-cnmsajvk.js.map +12 -0
- package/dist/chunks/index-fdvgk2sw.js +1310 -0
- package/dist/chunks/index-fdvgk2sw.js.map +21 -0
- package/dist/chunks/index-gyvps8b9.js +214 -0
- package/dist/chunks/index-gyvps8b9.js.map +12 -0
- package/dist/chunks/index-jzg4bh99.js +1533 -0
- package/dist/chunks/index-jzg4bh99.js.map +21 -0
- package/dist/chunks/index-mgw2yvrm.js +3547 -0
- package/dist/chunks/index-mgw2yvrm.js.map +25 -0
- package/dist/chunks/index-yh76b55h.js +3449 -0
- package/dist/chunks/index-yh76b55h.js.map +29 -0
- package/dist/collection/index.js +9 -1118
- package/dist/collection/index.js.map +3 -6
- package/dist/core/api.d.ts +3 -3
- package/dist/core/channel-create.d.ts +2 -2
- package/dist/core/commands.d.ts +3 -3
- package/dist/core/events.d.ts +4 -4
- package/dist/core/handles.d.ts +4 -4
- package/dist/core/index.d.ts +25 -20
- package/dist/core/index.js +68 -10130
- package/dist/core/index.js.map +3 -65
- package/dist/core/permissions.d.ts +1 -1
- package/dist/core/redis-cache.d.ts +1 -1
- package/dist/formatters/index.js +41 -110
- package/dist/formatters/index.js.map +3 -4
- package/dist/handlers/index.js +5 -37
- package/dist/handlers/index.js.map +3 -4
- package/dist/index.d.ts +21 -21
- package/dist/index.js +641 -16325
- package/dist/index.js.map +3 -92
- package/dist/managers/advanced.d.ts +3 -3
- package/dist/managers/application.d.ts +2 -2
- package/dist/managers/base.d.ts +1 -1
- package/dist/managers/emoji.d.ts +2 -2
- package/dist/managers/guild-resources.d.ts +6 -6
- package/dist/managers/guild.d.ts +3 -3
- package/dist/managers/index.d.ts +4 -8
- package/dist/managers/index.js +51 -4060
- package/dist/managers/index.js.map +3 -29
- package/dist/managers/member.d.ts +2 -2
- package/dist/managers/message.d.ts +14 -6
- package/dist/managers/role.d.ts +2 -2
- package/dist/managers/thread.d.ts +3 -3
- package/dist/managers/user.d.ts +7 -4
- package/dist/rest/index.d.ts +10 -35
- package/dist/rest/index.js +57 -2184
- package/dist/rest/index.js.map +3 -18
- package/dist/rest/limiter.d.ts +9 -1
- package/dist/rest/webhook.d.ts +2 -8
- package/dist/sharding/bus.d.ts +2 -2
- package/dist/sharding/cluster.d.ts +45 -5
- package/dist/sharding/context.d.ts +45 -3
- package/dist/sharding/gateway-bot.d.ts +2 -1
- package/dist/sharding/health.d.ts +1 -1
- package/dist/sharding/identify.d.ts +12 -0
- package/dist/sharding/index.d.ts +44 -11
- package/dist/sharding/index.js +60 -4620
- package/dist/sharding/index.js.map +3 -33
- package/dist/sharding/supervisor.d.ts +4 -2
- package/dist/structures/audit-log.d.ts +1 -1
- package/dist/structures/base.d.ts +6 -6
- package/dist/structures/channels.d.ts +1 -1
- package/dist/structures/embed.d.ts +1 -1
- package/dist/structures/index.d.ts +14 -14
- package/dist/structures/index.js +109 -2382
- package/dist/structures/index.js.map +3 -16
- package/dist/structures/interactions.d.ts +13 -9
- package/dist/structures/options.d.ts +1 -1
- package/dist/structures/permissions.d.ts +2 -2
- package/dist/structures/resources.d.ts +10 -10
- package/dist/structures/sticker.d.ts +1 -1
- package/dist/types/gateway.d.ts +2 -79
- package/dist/types/index.d.ts +0 -6
- package/dist/types/index.js +41 -272
- package/dist/types/index.js.map +3 -6
- package/dist/utils/index.js +23 -209
- package/dist/utils/index.js.map +3 -5
- package/dist/voice/connection.d.ts +200 -0
- package/dist/voice/crypto.d.ts +50 -0
- package/dist/voice/dave.d.ts +85 -0
- package/dist/voice/effects.d.ts +31 -0
- package/dist/voice/emitter.d.ts +14 -0
- package/dist/voice/gateway.d.ts +59 -0
- package/dist/voice/index.d.ts +25 -228
- package/dist/voice/index.js +111 -442
- package/dist/voice/index.js.map +3 -4
- package/dist/voice/libopus.d.ts +88 -0
- package/dist/voice/networking.d.ts +80 -0
- package/dist/voice/ogg.d.ts +24 -0
- package/dist/voice/opus.d.ts +18 -0
- package/dist/voice/player.d.ts +79 -0
- package/dist/voice/protocol.d.ts +76 -0
- package/dist/voice/receiver.d.ts +59 -0
- package/dist/voice/resource.d.ts +102 -0
- package/dist/voice/udp.d.ts +40 -0
- package/dist/ws/close-codes.d.ts +1 -1
- package/dist/ws/index.d.ts +25 -4
- package/dist/ws/index.js +47 -1336
- package/dist/ws/index.js.map +3 -17
- package/dist/ws/protocol.d.ts +1 -1
- package/dist/ws/send-budget.d.ts +0 -2
- package/dist/ws/state.d.ts +0 -10
- package/package.json +19 -16
|
@@ -0,0 +1,3449 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
GatewayHeartbeat2
|
|
4
|
+
} from "./index-fdvgk2sw.js";
|
|
5
|
+
|
|
6
|
+
// packages/voice/src/crypto.ts
|
|
7
|
+
import { createCipheriv, createDecipheriv, getCiphers } from "crypto";
|
|
8
|
+
|
|
9
|
+
// node_modules/.bun/@noble+ciphers@1.3.0/node_modules/@noble/ciphers/esm/utils.js
|
|
10
|
+
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
|
11
|
+
function isBytes(a) {
|
|
12
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
13
|
+
}
|
|
14
|
+
function abool(b) {
|
|
15
|
+
if (typeof b !== "boolean")
|
|
16
|
+
throw new Error(`boolean expected, not ${b}`);
|
|
17
|
+
}
|
|
18
|
+
function anumber(n) {
|
|
19
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
20
|
+
throw new Error("positive integer expected, got " + n);
|
|
21
|
+
}
|
|
22
|
+
function abytes(b, ...lengths) {
|
|
23
|
+
if (!isBytes(b))
|
|
24
|
+
throw new Error("Uint8Array expected");
|
|
25
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
26
|
+
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
27
|
+
}
|
|
28
|
+
function aexists(instance, checkFinished = true) {
|
|
29
|
+
if (instance.destroyed)
|
|
30
|
+
throw new Error("Hash instance has been destroyed");
|
|
31
|
+
if (checkFinished && instance.finished)
|
|
32
|
+
throw new Error("Hash#digest() has already been called");
|
|
33
|
+
}
|
|
34
|
+
function aoutput(out, instance) {
|
|
35
|
+
abytes(out);
|
|
36
|
+
const min = instance.outputLen;
|
|
37
|
+
if (out.length < min) {
|
|
38
|
+
throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function u32(arr) {
|
|
42
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
43
|
+
}
|
|
44
|
+
function clean(...arrays) {
|
|
45
|
+
for (let i = 0;i < arrays.length; i++) {
|
|
46
|
+
arrays[i].fill(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function createView(arr) {
|
|
50
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
51
|
+
}
|
|
52
|
+
var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
|
53
|
+
function utf8ToBytes(str) {
|
|
54
|
+
if (typeof str !== "string")
|
|
55
|
+
throw new Error("string expected");
|
|
56
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
57
|
+
}
|
|
58
|
+
function toBytes(data) {
|
|
59
|
+
if (typeof data === "string")
|
|
60
|
+
data = utf8ToBytes(data);
|
|
61
|
+
else if (isBytes(data))
|
|
62
|
+
data = copyBytes(data);
|
|
63
|
+
else
|
|
64
|
+
throw new Error("Uint8Array expected, got " + typeof data);
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
function checkOpts(defaults, opts) {
|
|
68
|
+
if (opts == null || typeof opts !== "object")
|
|
69
|
+
throw new Error("options must be defined");
|
|
70
|
+
const merged = Object.assign(defaults, opts);
|
|
71
|
+
return merged;
|
|
72
|
+
}
|
|
73
|
+
function equalBytes(a, b) {
|
|
74
|
+
if (a.length !== b.length)
|
|
75
|
+
return false;
|
|
76
|
+
let diff = 0;
|
|
77
|
+
for (let i = 0;i < a.length; i++)
|
|
78
|
+
diff |= a[i] ^ b[i];
|
|
79
|
+
return diff === 0;
|
|
80
|
+
}
|
|
81
|
+
var wrapCipher = (params, constructor) => {
|
|
82
|
+
function wrappedCipher(key, ...args) {
|
|
83
|
+
abytes(key);
|
|
84
|
+
if (!isLE)
|
|
85
|
+
throw new Error("Non little-endian hardware is not yet supported");
|
|
86
|
+
if (params.nonceLength !== undefined) {
|
|
87
|
+
const nonce = args[0];
|
|
88
|
+
if (!nonce)
|
|
89
|
+
throw new Error("nonce / iv required");
|
|
90
|
+
if (params.varSizeNonce)
|
|
91
|
+
abytes(nonce);
|
|
92
|
+
else
|
|
93
|
+
abytes(nonce, params.nonceLength);
|
|
94
|
+
}
|
|
95
|
+
const tagl = params.tagLength;
|
|
96
|
+
if (tagl && args[1] !== undefined) {
|
|
97
|
+
abytes(args[1]);
|
|
98
|
+
}
|
|
99
|
+
const cipher = constructor(key, ...args);
|
|
100
|
+
const checkOutput = (fnLength, output) => {
|
|
101
|
+
if (output !== undefined) {
|
|
102
|
+
if (fnLength !== 2)
|
|
103
|
+
throw new Error("cipher output not supported");
|
|
104
|
+
abytes(output);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
let called = false;
|
|
108
|
+
const wrCipher = {
|
|
109
|
+
encrypt(data, output) {
|
|
110
|
+
if (called)
|
|
111
|
+
throw new Error("cannot encrypt() twice with same key + nonce");
|
|
112
|
+
called = true;
|
|
113
|
+
abytes(data);
|
|
114
|
+
checkOutput(cipher.encrypt.length, output);
|
|
115
|
+
return cipher.encrypt(data, output);
|
|
116
|
+
},
|
|
117
|
+
decrypt(data, output) {
|
|
118
|
+
abytes(data);
|
|
119
|
+
if (tagl && data.length < tagl)
|
|
120
|
+
throw new Error("invalid ciphertext length: smaller than tagLength=" + tagl);
|
|
121
|
+
checkOutput(cipher.decrypt.length, output);
|
|
122
|
+
return cipher.decrypt(data, output);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
return wrCipher;
|
|
126
|
+
}
|
|
127
|
+
Object.assign(wrappedCipher, params);
|
|
128
|
+
return wrappedCipher;
|
|
129
|
+
};
|
|
130
|
+
function getOutput(expectedLength, out, onlyAligned = true) {
|
|
131
|
+
if (out === undefined)
|
|
132
|
+
return new Uint8Array(expectedLength);
|
|
133
|
+
if (out.length !== expectedLength)
|
|
134
|
+
throw new Error("invalid output length, expected " + expectedLength + ", got: " + out.length);
|
|
135
|
+
if (onlyAligned && !isAligned32(out))
|
|
136
|
+
throw new Error("invalid output, must be aligned");
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function setBigUint64(view, byteOffset, value, isLE) {
|
|
140
|
+
if (typeof view.setBigUint64 === "function")
|
|
141
|
+
return view.setBigUint64(byteOffset, value, isLE);
|
|
142
|
+
const _32n = BigInt(32);
|
|
143
|
+
const _u32_max = BigInt(4294967295);
|
|
144
|
+
const wh = Number(value >> _32n & _u32_max);
|
|
145
|
+
const wl = Number(value & _u32_max);
|
|
146
|
+
const h = isLE ? 4 : 0;
|
|
147
|
+
const l = isLE ? 0 : 4;
|
|
148
|
+
view.setUint32(byteOffset + h, wh, isLE);
|
|
149
|
+
view.setUint32(byteOffset + l, wl, isLE);
|
|
150
|
+
}
|
|
151
|
+
function u64Lengths(dataLength, aadLength, isLE) {
|
|
152
|
+
abool(isLE);
|
|
153
|
+
const num = new Uint8Array(16);
|
|
154
|
+
const view = createView(num);
|
|
155
|
+
setBigUint64(view, 0, BigInt(aadLength), isLE);
|
|
156
|
+
setBigUint64(view, 8, BigInt(dataLength), isLE);
|
|
157
|
+
return num;
|
|
158
|
+
}
|
|
159
|
+
function isAligned32(bytes) {
|
|
160
|
+
return bytes.byteOffset % 4 === 0;
|
|
161
|
+
}
|
|
162
|
+
function copyBytes(bytes) {
|
|
163
|
+
return Uint8Array.from(bytes);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// node_modules/.bun/@noble+ciphers@1.3.0/node_modules/@noble/ciphers/esm/_arx.js
|
|
167
|
+
var _utf8ToBytes = (str) => Uint8Array.from(str.split("").map((c) => c.charCodeAt(0)));
|
|
168
|
+
var sigma16 = _utf8ToBytes("expand 16-byte k");
|
|
169
|
+
var sigma32 = _utf8ToBytes("expand 32-byte k");
|
|
170
|
+
var sigma16_32 = u32(sigma16);
|
|
171
|
+
var sigma32_32 = u32(sigma32);
|
|
172
|
+
function rotl(a, b) {
|
|
173
|
+
return a << b | a >>> 32 - b;
|
|
174
|
+
}
|
|
175
|
+
function isAligned322(b) {
|
|
176
|
+
return b.byteOffset % 4 === 0;
|
|
177
|
+
}
|
|
178
|
+
var BLOCK_LEN = 64;
|
|
179
|
+
var BLOCK_LEN32 = 16;
|
|
180
|
+
var MAX_COUNTER = 2 ** 32 - 1;
|
|
181
|
+
var U32_EMPTY = new Uint32Array;
|
|
182
|
+
function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
|
|
183
|
+
const len = data.length;
|
|
184
|
+
const block = new Uint8Array(BLOCK_LEN);
|
|
185
|
+
const b32 = u32(block);
|
|
186
|
+
const isAligned = isAligned322(data) && isAligned322(output);
|
|
187
|
+
const d32 = isAligned ? u32(data) : U32_EMPTY;
|
|
188
|
+
const o32 = isAligned ? u32(output) : U32_EMPTY;
|
|
189
|
+
for (let pos = 0;pos < len; counter++) {
|
|
190
|
+
core(sigma, key, nonce, b32, counter, rounds);
|
|
191
|
+
if (counter >= MAX_COUNTER)
|
|
192
|
+
throw new Error("arx: counter overflow");
|
|
193
|
+
const take = Math.min(BLOCK_LEN, len - pos);
|
|
194
|
+
if (isAligned && take === BLOCK_LEN) {
|
|
195
|
+
const pos32 = pos / 4;
|
|
196
|
+
if (pos % 4 !== 0)
|
|
197
|
+
throw new Error("arx: invalid block position");
|
|
198
|
+
for (let j = 0, posj;j < BLOCK_LEN32; j++) {
|
|
199
|
+
posj = pos32 + j;
|
|
200
|
+
o32[posj] = d32[posj] ^ b32[j];
|
|
201
|
+
}
|
|
202
|
+
pos += BLOCK_LEN;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
for (let j = 0, posj;j < take; j++) {
|
|
206
|
+
posj = pos + j;
|
|
207
|
+
output[posj] = data[posj] ^ block[j];
|
|
208
|
+
}
|
|
209
|
+
pos += take;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function createCipher(core, opts) {
|
|
213
|
+
const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts);
|
|
214
|
+
if (typeof core !== "function")
|
|
215
|
+
throw new Error("core must be a function");
|
|
216
|
+
anumber(counterLength);
|
|
217
|
+
anumber(rounds);
|
|
218
|
+
abool(counterRight);
|
|
219
|
+
abool(allowShortKeys);
|
|
220
|
+
return (key, nonce, data, output, counter = 0) => {
|
|
221
|
+
abytes(key);
|
|
222
|
+
abytes(nonce);
|
|
223
|
+
abytes(data);
|
|
224
|
+
const len = data.length;
|
|
225
|
+
if (output === undefined)
|
|
226
|
+
output = new Uint8Array(len);
|
|
227
|
+
abytes(output);
|
|
228
|
+
anumber(counter);
|
|
229
|
+
if (counter < 0 || counter >= MAX_COUNTER)
|
|
230
|
+
throw new Error("arx: counter overflow");
|
|
231
|
+
if (output.length < len)
|
|
232
|
+
throw new Error(`arx: output (${output.length}) is shorter than data (${len})`);
|
|
233
|
+
const toClean = [];
|
|
234
|
+
let l = key.length;
|
|
235
|
+
let k;
|
|
236
|
+
let sigma;
|
|
237
|
+
if (l === 32) {
|
|
238
|
+
toClean.push(k = copyBytes(key));
|
|
239
|
+
sigma = sigma32_32;
|
|
240
|
+
} else if (l === 16 && allowShortKeys) {
|
|
241
|
+
k = new Uint8Array(32);
|
|
242
|
+
k.set(key);
|
|
243
|
+
k.set(key, 16);
|
|
244
|
+
sigma = sigma16_32;
|
|
245
|
+
toClean.push(k);
|
|
246
|
+
} else {
|
|
247
|
+
throw new Error(`arx: invalid 32-byte key, got length=${l}`);
|
|
248
|
+
}
|
|
249
|
+
if (!isAligned322(nonce))
|
|
250
|
+
toClean.push(nonce = copyBytes(nonce));
|
|
251
|
+
const k32 = u32(k);
|
|
252
|
+
if (extendNonceFn) {
|
|
253
|
+
if (nonce.length !== 24)
|
|
254
|
+
throw new Error(`arx: extended nonce must be 24 bytes`);
|
|
255
|
+
extendNonceFn(sigma, k32, u32(nonce.subarray(0, 16)), k32);
|
|
256
|
+
nonce = nonce.subarray(16);
|
|
257
|
+
}
|
|
258
|
+
const nonceNcLen = 16 - counterLength;
|
|
259
|
+
if (nonceNcLen !== nonce.length)
|
|
260
|
+
throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`);
|
|
261
|
+
if (nonceNcLen !== 12) {
|
|
262
|
+
const nc = new Uint8Array(12);
|
|
263
|
+
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
|
|
264
|
+
nonce = nc;
|
|
265
|
+
toClean.push(nonce);
|
|
266
|
+
}
|
|
267
|
+
const n32 = u32(nonce);
|
|
268
|
+
runCipher(core, sigma, k32, n32, data, output, counter, rounds);
|
|
269
|
+
clean(...toClean);
|
|
270
|
+
return output;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// node_modules/.bun/@noble+ciphers@1.3.0/node_modules/@noble/ciphers/esm/_poly1305.js
|
|
275
|
+
var u8to16 = (a, i) => a[i++] & 255 | (a[i++] & 255) << 8;
|
|
276
|
+
|
|
277
|
+
class Poly1305 {
|
|
278
|
+
constructor(key) {
|
|
279
|
+
this.blockLen = 16;
|
|
280
|
+
this.outputLen = 16;
|
|
281
|
+
this.buffer = new Uint8Array(16);
|
|
282
|
+
this.r = new Uint16Array(10);
|
|
283
|
+
this.h = new Uint16Array(10);
|
|
284
|
+
this.pad = new Uint16Array(8);
|
|
285
|
+
this.pos = 0;
|
|
286
|
+
this.finished = false;
|
|
287
|
+
key = toBytes(key);
|
|
288
|
+
abytes(key, 32);
|
|
289
|
+
const t0 = u8to16(key, 0);
|
|
290
|
+
const t1 = u8to16(key, 2);
|
|
291
|
+
const t2 = u8to16(key, 4);
|
|
292
|
+
const t3 = u8to16(key, 6);
|
|
293
|
+
const t4 = u8to16(key, 8);
|
|
294
|
+
const t5 = u8to16(key, 10);
|
|
295
|
+
const t6 = u8to16(key, 12);
|
|
296
|
+
const t7 = u8to16(key, 14);
|
|
297
|
+
this.r[0] = t0 & 8191;
|
|
298
|
+
this.r[1] = (t0 >>> 13 | t1 << 3) & 8191;
|
|
299
|
+
this.r[2] = (t1 >>> 10 | t2 << 6) & 7939;
|
|
300
|
+
this.r[3] = (t2 >>> 7 | t3 << 9) & 8191;
|
|
301
|
+
this.r[4] = (t3 >>> 4 | t4 << 12) & 255;
|
|
302
|
+
this.r[5] = t4 >>> 1 & 8190;
|
|
303
|
+
this.r[6] = (t4 >>> 14 | t5 << 2) & 8191;
|
|
304
|
+
this.r[7] = (t5 >>> 11 | t6 << 5) & 8065;
|
|
305
|
+
this.r[8] = (t6 >>> 8 | t7 << 8) & 8191;
|
|
306
|
+
this.r[9] = t7 >>> 5 & 127;
|
|
307
|
+
for (let i = 0;i < 8; i++)
|
|
308
|
+
this.pad[i] = u8to16(key, 16 + 2 * i);
|
|
309
|
+
}
|
|
310
|
+
process(data, offset, isLast = false) {
|
|
311
|
+
const hibit = isLast ? 0 : 1 << 11;
|
|
312
|
+
const { h, r } = this;
|
|
313
|
+
const r0 = r[0];
|
|
314
|
+
const r1 = r[1];
|
|
315
|
+
const r2 = r[2];
|
|
316
|
+
const r3 = r[3];
|
|
317
|
+
const r4 = r[4];
|
|
318
|
+
const r5 = r[5];
|
|
319
|
+
const r6 = r[6];
|
|
320
|
+
const r7 = r[7];
|
|
321
|
+
const r8 = r[8];
|
|
322
|
+
const r9 = r[9];
|
|
323
|
+
const t0 = u8to16(data, offset + 0);
|
|
324
|
+
const t1 = u8to16(data, offset + 2);
|
|
325
|
+
const t2 = u8to16(data, offset + 4);
|
|
326
|
+
const t3 = u8to16(data, offset + 6);
|
|
327
|
+
const t4 = u8to16(data, offset + 8);
|
|
328
|
+
const t5 = u8to16(data, offset + 10);
|
|
329
|
+
const t6 = u8to16(data, offset + 12);
|
|
330
|
+
const t7 = u8to16(data, offset + 14);
|
|
331
|
+
let h0 = h[0] + (t0 & 8191);
|
|
332
|
+
let h1 = h[1] + ((t0 >>> 13 | t1 << 3) & 8191);
|
|
333
|
+
let h2 = h[2] + ((t1 >>> 10 | t2 << 6) & 8191);
|
|
334
|
+
let h3 = h[3] + ((t2 >>> 7 | t3 << 9) & 8191);
|
|
335
|
+
let h4 = h[4] + ((t3 >>> 4 | t4 << 12) & 8191);
|
|
336
|
+
let h5 = h[5] + (t4 >>> 1 & 8191);
|
|
337
|
+
let h6 = h[6] + ((t4 >>> 14 | t5 << 2) & 8191);
|
|
338
|
+
let h7 = h[7] + ((t5 >>> 11 | t6 << 5) & 8191);
|
|
339
|
+
let h8 = h[8] + ((t6 >>> 8 | t7 << 8) & 8191);
|
|
340
|
+
let h9 = h[9] + (t7 >>> 5 | hibit);
|
|
341
|
+
let c = 0;
|
|
342
|
+
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
|
|
343
|
+
c = d0 >>> 13;
|
|
344
|
+
d0 &= 8191;
|
|
345
|
+
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
|
|
346
|
+
c += d0 >>> 13;
|
|
347
|
+
d0 &= 8191;
|
|
348
|
+
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
|
|
349
|
+
c = d1 >>> 13;
|
|
350
|
+
d1 &= 8191;
|
|
351
|
+
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
|
|
352
|
+
c += d1 >>> 13;
|
|
353
|
+
d1 &= 8191;
|
|
354
|
+
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
|
|
355
|
+
c = d2 >>> 13;
|
|
356
|
+
d2 &= 8191;
|
|
357
|
+
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
|
|
358
|
+
c += d2 >>> 13;
|
|
359
|
+
d2 &= 8191;
|
|
360
|
+
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
|
|
361
|
+
c = d3 >>> 13;
|
|
362
|
+
d3 &= 8191;
|
|
363
|
+
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
|
|
364
|
+
c += d3 >>> 13;
|
|
365
|
+
d3 &= 8191;
|
|
366
|
+
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
|
|
367
|
+
c = d4 >>> 13;
|
|
368
|
+
d4 &= 8191;
|
|
369
|
+
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
|
|
370
|
+
c += d4 >>> 13;
|
|
371
|
+
d4 &= 8191;
|
|
372
|
+
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
|
|
373
|
+
c = d5 >>> 13;
|
|
374
|
+
d5 &= 8191;
|
|
375
|
+
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
|
|
376
|
+
c += d5 >>> 13;
|
|
377
|
+
d5 &= 8191;
|
|
378
|
+
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
|
|
379
|
+
c = d6 >>> 13;
|
|
380
|
+
d6 &= 8191;
|
|
381
|
+
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
|
|
382
|
+
c += d6 >>> 13;
|
|
383
|
+
d6 &= 8191;
|
|
384
|
+
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
|
|
385
|
+
c = d7 >>> 13;
|
|
386
|
+
d7 &= 8191;
|
|
387
|
+
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
|
|
388
|
+
c += d7 >>> 13;
|
|
389
|
+
d7 &= 8191;
|
|
390
|
+
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
|
|
391
|
+
c = d8 >>> 13;
|
|
392
|
+
d8 &= 8191;
|
|
393
|
+
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
|
|
394
|
+
c += d8 >>> 13;
|
|
395
|
+
d8 &= 8191;
|
|
396
|
+
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
|
|
397
|
+
c = d9 >>> 13;
|
|
398
|
+
d9 &= 8191;
|
|
399
|
+
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
|
|
400
|
+
c += d9 >>> 13;
|
|
401
|
+
d9 &= 8191;
|
|
402
|
+
c = (c << 2) + c | 0;
|
|
403
|
+
c = c + d0 | 0;
|
|
404
|
+
d0 = c & 8191;
|
|
405
|
+
c = c >>> 13;
|
|
406
|
+
d1 += c;
|
|
407
|
+
h[0] = d0;
|
|
408
|
+
h[1] = d1;
|
|
409
|
+
h[2] = d2;
|
|
410
|
+
h[3] = d3;
|
|
411
|
+
h[4] = d4;
|
|
412
|
+
h[5] = d5;
|
|
413
|
+
h[6] = d6;
|
|
414
|
+
h[7] = d7;
|
|
415
|
+
h[8] = d8;
|
|
416
|
+
h[9] = d9;
|
|
417
|
+
}
|
|
418
|
+
finalize() {
|
|
419
|
+
const { h, pad } = this;
|
|
420
|
+
const g = new Uint16Array(10);
|
|
421
|
+
let c = h[1] >>> 13;
|
|
422
|
+
h[1] &= 8191;
|
|
423
|
+
for (let i = 2;i < 10; i++) {
|
|
424
|
+
h[i] += c;
|
|
425
|
+
c = h[i] >>> 13;
|
|
426
|
+
h[i] &= 8191;
|
|
427
|
+
}
|
|
428
|
+
h[0] += c * 5;
|
|
429
|
+
c = h[0] >>> 13;
|
|
430
|
+
h[0] &= 8191;
|
|
431
|
+
h[1] += c;
|
|
432
|
+
c = h[1] >>> 13;
|
|
433
|
+
h[1] &= 8191;
|
|
434
|
+
h[2] += c;
|
|
435
|
+
g[0] = h[0] + 5;
|
|
436
|
+
c = g[0] >>> 13;
|
|
437
|
+
g[0] &= 8191;
|
|
438
|
+
for (let i = 1;i < 10; i++) {
|
|
439
|
+
g[i] = h[i] + c;
|
|
440
|
+
c = g[i] >>> 13;
|
|
441
|
+
g[i] &= 8191;
|
|
442
|
+
}
|
|
443
|
+
g[9] -= 1 << 13;
|
|
444
|
+
let mask = (c ^ 1) - 1;
|
|
445
|
+
for (let i = 0;i < 10; i++)
|
|
446
|
+
g[i] &= mask;
|
|
447
|
+
mask = ~mask;
|
|
448
|
+
for (let i = 0;i < 10; i++)
|
|
449
|
+
h[i] = h[i] & mask | g[i];
|
|
450
|
+
h[0] = (h[0] | h[1] << 13) & 65535;
|
|
451
|
+
h[1] = (h[1] >>> 3 | h[2] << 10) & 65535;
|
|
452
|
+
h[2] = (h[2] >>> 6 | h[3] << 7) & 65535;
|
|
453
|
+
h[3] = (h[3] >>> 9 | h[4] << 4) & 65535;
|
|
454
|
+
h[4] = (h[4] >>> 12 | h[5] << 1 | h[6] << 14) & 65535;
|
|
455
|
+
h[5] = (h[6] >>> 2 | h[7] << 11) & 65535;
|
|
456
|
+
h[6] = (h[7] >>> 5 | h[8] << 8) & 65535;
|
|
457
|
+
h[7] = (h[8] >>> 8 | h[9] << 5) & 65535;
|
|
458
|
+
let f = h[0] + pad[0];
|
|
459
|
+
h[0] = f & 65535;
|
|
460
|
+
for (let i = 1;i < 8; i++) {
|
|
461
|
+
f = (h[i] + pad[i] | 0) + (f >>> 16) | 0;
|
|
462
|
+
h[i] = f & 65535;
|
|
463
|
+
}
|
|
464
|
+
clean(g);
|
|
465
|
+
}
|
|
466
|
+
update(data) {
|
|
467
|
+
aexists(this);
|
|
468
|
+
data = toBytes(data);
|
|
469
|
+
abytes(data);
|
|
470
|
+
const { buffer, blockLen } = this;
|
|
471
|
+
const len = data.length;
|
|
472
|
+
for (let pos = 0;pos < len; ) {
|
|
473
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
474
|
+
if (take === blockLen) {
|
|
475
|
+
for (;blockLen <= len - pos; pos += blockLen)
|
|
476
|
+
this.process(data, pos);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
480
|
+
this.pos += take;
|
|
481
|
+
pos += take;
|
|
482
|
+
if (this.pos === blockLen) {
|
|
483
|
+
this.process(buffer, 0, false);
|
|
484
|
+
this.pos = 0;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return this;
|
|
488
|
+
}
|
|
489
|
+
destroy() {
|
|
490
|
+
clean(this.h, this.r, this.buffer, this.pad);
|
|
491
|
+
}
|
|
492
|
+
digestInto(out) {
|
|
493
|
+
aexists(this);
|
|
494
|
+
aoutput(out, this);
|
|
495
|
+
this.finished = true;
|
|
496
|
+
const { buffer, h } = this;
|
|
497
|
+
let { pos } = this;
|
|
498
|
+
if (pos) {
|
|
499
|
+
buffer[pos++] = 1;
|
|
500
|
+
for (;pos < 16; pos++)
|
|
501
|
+
buffer[pos] = 0;
|
|
502
|
+
this.process(buffer, 0, true);
|
|
503
|
+
}
|
|
504
|
+
this.finalize();
|
|
505
|
+
let opos = 0;
|
|
506
|
+
for (let i = 0;i < 8; i++) {
|
|
507
|
+
out[opos++] = h[i] >>> 0;
|
|
508
|
+
out[opos++] = h[i] >>> 8;
|
|
509
|
+
}
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
512
|
+
digest() {
|
|
513
|
+
const { buffer, outputLen } = this;
|
|
514
|
+
this.digestInto(buffer);
|
|
515
|
+
const res = buffer.slice(0, outputLen);
|
|
516
|
+
this.destroy();
|
|
517
|
+
return res;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function wrapConstructorWithKey(hashCons) {
|
|
521
|
+
const hashC = (msg, key) => hashCons(key).update(toBytes(msg)).digest();
|
|
522
|
+
const tmp = hashCons(new Uint8Array(32));
|
|
523
|
+
hashC.outputLen = tmp.outputLen;
|
|
524
|
+
hashC.blockLen = tmp.blockLen;
|
|
525
|
+
hashC.create = (key) => hashCons(key);
|
|
526
|
+
return hashC;
|
|
527
|
+
}
|
|
528
|
+
var poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
|
|
529
|
+
|
|
530
|
+
// node_modules/.bun/@noble+ciphers@1.3.0/node_modules/@noble/ciphers/esm/chacha.js
|
|
531
|
+
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
|
532
|
+
let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2];
|
|
533
|
+
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
|
|
534
|
+
for (let r = 0;r < rounds; r += 2) {
|
|
535
|
+
x00 = x00 + x04 | 0;
|
|
536
|
+
x12 = rotl(x12 ^ x00, 16);
|
|
537
|
+
x08 = x08 + x12 | 0;
|
|
538
|
+
x04 = rotl(x04 ^ x08, 12);
|
|
539
|
+
x00 = x00 + x04 | 0;
|
|
540
|
+
x12 = rotl(x12 ^ x00, 8);
|
|
541
|
+
x08 = x08 + x12 | 0;
|
|
542
|
+
x04 = rotl(x04 ^ x08, 7);
|
|
543
|
+
x01 = x01 + x05 | 0;
|
|
544
|
+
x13 = rotl(x13 ^ x01, 16);
|
|
545
|
+
x09 = x09 + x13 | 0;
|
|
546
|
+
x05 = rotl(x05 ^ x09, 12);
|
|
547
|
+
x01 = x01 + x05 | 0;
|
|
548
|
+
x13 = rotl(x13 ^ x01, 8);
|
|
549
|
+
x09 = x09 + x13 | 0;
|
|
550
|
+
x05 = rotl(x05 ^ x09, 7);
|
|
551
|
+
x02 = x02 + x06 | 0;
|
|
552
|
+
x14 = rotl(x14 ^ x02, 16);
|
|
553
|
+
x10 = x10 + x14 | 0;
|
|
554
|
+
x06 = rotl(x06 ^ x10, 12);
|
|
555
|
+
x02 = x02 + x06 | 0;
|
|
556
|
+
x14 = rotl(x14 ^ x02, 8);
|
|
557
|
+
x10 = x10 + x14 | 0;
|
|
558
|
+
x06 = rotl(x06 ^ x10, 7);
|
|
559
|
+
x03 = x03 + x07 | 0;
|
|
560
|
+
x15 = rotl(x15 ^ x03, 16);
|
|
561
|
+
x11 = x11 + x15 | 0;
|
|
562
|
+
x07 = rotl(x07 ^ x11, 12);
|
|
563
|
+
x03 = x03 + x07 | 0;
|
|
564
|
+
x15 = rotl(x15 ^ x03, 8);
|
|
565
|
+
x11 = x11 + x15 | 0;
|
|
566
|
+
x07 = rotl(x07 ^ x11, 7);
|
|
567
|
+
x00 = x00 + x05 | 0;
|
|
568
|
+
x15 = rotl(x15 ^ x00, 16);
|
|
569
|
+
x10 = x10 + x15 | 0;
|
|
570
|
+
x05 = rotl(x05 ^ x10, 12);
|
|
571
|
+
x00 = x00 + x05 | 0;
|
|
572
|
+
x15 = rotl(x15 ^ x00, 8);
|
|
573
|
+
x10 = x10 + x15 | 0;
|
|
574
|
+
x05 = rotl(x05 ^ x10, 7);
|
|
575
|
+
x01 = x01 + x06 | 0;
|
|
576
|
+
x12 = rotl(x12 ^ x01, 16);
|
|
577
|
+
x11 = x11 + x12 | 0;
|
|
578
|
+
x06 = rotl(x06 ^ x11, 12);
|
|
579
|
+
x01 = x01 + x06 | 0;
|
|
580
|
+
x12 = rotl(x12 ^ x01, 8);
|
|
581
|
+
x11 = x11 + x12 | 0;
|
|
582
|
+
x06 = rotl(x06 ^ x11, 7);
|
|
583
|
+
x02 = x02 + x07 | 0;
|
|
584
|
+
x13 = rotl(x13 ^ x02, 16);
|
|
585
|
+
x08 = x08 + x13 | 0;
|
|
586
|
+
x07 = rotl(x07 ^ x08, 12);
|
|
587
|
+
x02 = x02 + x07 | 0;
|
|
588
|
+
x13 = rotl(x13 ^ x02, 8);
|
|
589
|
+
x08 = x08 + x13 | 0;
|
|
590
|
+
x07 = rotl(x07 ^ x08, 7);
|
|
591
|
+
x03 = x03 + x04 | 0;
|
|
592
|
+
x14 = rotl(x14 ^ x03, 16);
|
|
593
|
+
x09 = x09 + x14 | 0;
|
|
594
|
+
x04 = rotl(x04 ^ x09, 12);
|
|
595
|
+
x03 = x03 + x04 | 0;
|
|
596
|
+
x14 = rotl(x14 ^ x03, 8);
|
|
597
|
+
x09 = x09 + x14 | 0;
|
|
598
|
+
x04 = rotl(x04 ^ x09, 7);
|
|
599
|
+
}
|
|
600
|
+
let oi = 0;
|
|
601
|
+
out[oi++] = y00 + x00 | 0;
|
|
602
|
+
out[oi++] = y01 + x01 | 0;
|
|
603
|
+
out[oi++] = y02 + x02 | 0;
|
|
604
|
+
out[oi++] = y03 + x03 | 0;
|
|
605
|
+
out[oi++] = y04 + x04 | 0;
|
|
606
|
+
out[oi++] = y05 + x05 | 0;
|
|
607
|
+
out[oi++] = y06 + x06 | 0;
|
|
608
|
+
out[oi++] = y07 + x07 | 0;
|
|
609
|
+
out[oi++] = y08 + x08 | 0;
|
|
610
|
+
out[oi++] = y09 + x09 | 0;
|
|
611
|
+
out[oi++] = y10 + x10 | 0;
|
|
612
|
+
out[oi++] = y11 + x11 | 0;
|
|
613
|
+
out[oi++] = y12 + x12 | 0;
|
|
614
|
+
out[oi++] = y13 + x13 | 0;
|
|
615
|
+
out[oi++] = y14 + x14 | 0;
|
|
616
|
+
out[oi++] = y15 + x15 | 0;
|
|
617
|
+
}
|
|
618
|
+
function hchacha(s, k, i, o32) {
|
|
619
|
+
let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3], x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3], x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7], x12 = i[0], x13 = i[1], x14 = i[2], x15 = i[3];
|
|
620
|
+
for (let r = 0;r < 20; r += 2) {
|
|
621
|
+
x00 = x00 + x04 | 0;
|
|
622
|
+
x12 = rotl(x12 ^ x00, 16);
|
|
623
|
+
x08 = x08 + x12 | 0;
|
|
624
|
+
x04 = rotl(x04 ^ x08, 12);
|
|
625
|
+
x00 = x00 + x04 | 0;
|
|
626
|
+
x12 = rotl(x12 ^ x00, 8);
|
|
627
|
+
x08 = x08 + x12 | 0;
|
|
628
|
+
x04 = rotl(x04 ^ x08, 7);
|
|
629
|
+
x01 = x01 + x05 | 0;
|
|
630
|
+
x13 = rotl(x13 ^ x01, 16);
|
|
631
|
+
x09 = x09 + x13 | 0;
|
|
632
|
+
x05 = rotl(x05 ^ x09, 12);
|
|
633
|
+
x01 = x01 + x05 | 0;
|
|
634
|
+
x13 = rotl(x13 ^ x01, 8);
|
|
635
|
+
x09 = x09 + x13 | 0;
|
|
636
|
+
x05 = rotl(x05 ^ x09, 7);
|
|
637
|
+
x02 = x02 + x06 | 0;
|
|
638
|
+
x14 = rotl(x14 ^ x02, 16);
|
|
639
|
+
x10 = x10 + x14 | 0;
|
|
640
|
+
x06 = rotl(x06 ^ x10, 12);
|
|
641
|
+
x02 = x02 + x06 | 0;
|
|
642
|
+
x14 = rotl(x14 ^ x02, 8);
|
|
643
|
+
x10 = x10 + x14 | 0;
|
|
644
|
+
x06 = rotl(x06 ^ x10, 7);
|
|
645
|
+
x03 = x03 + x07 | 0;
|
|
646
|
+
x15 = rotl(x15 ^ x03, 16);
|
|
647
|
+
x11 = x11 + x15 | 0;
|
|
648
|
+
x07 = rotl(x07 ^ x11, 12);
|
|
649
|
+
x03 = x03 + x07 | 0;
|
|
650
|
+
x15 = rotl(x15 ^ x03, 8);
|
|
651
|
+
x11 = x11 + x15 | 0;
|
|
652
|
+
x07 = rotl(x07 ^ x11, 7);
|
|
653
|
+
x00 = x00 + x05 | 0;
|
|
654
|
+
x15 = rotl(x15 ^ x00, 16);
|
|
655
|
+
x10 = x10 + x15 | 0;
|
|
656
|
+
x05 = rotl(x05 ^ x10, 12);
|
|
657
|
+
x00 = x00 + x05 | 0;
|
|
658
|
+
x15 = rotl(x15 ^ x00, 8);
|
|
659
|
+
x10 = x10 + x15 | 0;
|
|
660
|
+
x05 = rotl(x05 ^ x10, 7);
|
|
661
|
+
x01 = x01 + x06 | 0;
|
|
662
|
+
x12 = rotl(x12 ^ x01, 16);
|
|
663
|
+
x11 = x11 + x12 | 0;
|
|
664
|
+
x06 = rotl(x06 ^ x11, 12);
|
|
665
|
+
x01 = x01 + x06 | 0;
|
|
666
|
+
x12 = rotl(x12 ^ x01, 8);
|
|
667
|
+
x11 = x11 + x12 | 0;
|
|
668
|
+
x06 = rotl(x06 ^ x11, 7);
|
|
669
|
+
x02 = x02 + x07 | 0;
|
|
670
|
+
x13 = rotl(x13 ^ x02, 16);
|
|
671
|
+
x08 = x08 + x13 | 0;
|
|
672
|
+
x07 = rotl(x07 ^ x08, 12);
|
|
673
|
+
x02 = x02 + x07 | 0;
|
|
674
|
+
x13 = rotl(x13 ^ x02, 8);
|
|
675
|
+
x08 = x08 + x13 | 0;
|
|
676
|
+
x07 = rotl(x07 ^ x08, 7);
|
|
677
|
+
x03 = x03 + x04 | 0;
|
|
678
|
+
x14 = rotl(x14 ^ x03, 16);
|
|
679
|
+
x09 = x09 + x14 | 0;
|
|
680
|
+
x04 = rotl(x04 ^ x09, 12);
|
|
681
|
+
x03 = x03 + x04 | 0;
|
|
682
|
+
x14 = rotl(x14 ^ x03, 8);
|
|
683
|
+
x09 = x09 + x14 | 0;
|
|
684
|
+
x04 = rotl(x04 ^ x09, 7);
|
|
685
|
+
}
|
|
686
|
+
let oi = 0;
|
|
687
|
+
o32[oi++] = x00;
|
|
688
|
+
o32[oi++] = x01;
|
|
689
|
+
o32[oi++] = x02;
|
|
690
|
+
o32[oi++] = x03;
|
|
691
|
+
o32[oi++] = x12;
|
|
692
|
+
o32[oi++] = x13;
|
|
693
|
+
o32[oi++] = x14;
|
|
694
|
+
o32[oi++] = x15;
|
|
695
|
+
}
|
|
696
|
+
var chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
697
|
+
counterRight: false,
|
|
698
|
+
counterLength: 4,
|
|
699
|
+
allowShortKeys: false
|
|
700
|
+
});
|
|
701
|
+
var xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
702
|
+
counterRight: false,
|
|
703
|
+
counterLength: 8,
|
|
704
|
+
extendNonceFn: hchacha,
|
|
705
|
+
allowShortKeys: false
|
|
706
|
+
});
|
|
707
|
+
var ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
|
708
|
+
var updatePadded = (h, msg) => {
|
|
709
|
+
h.update(msg);
|
|
710
|
+
const left = msg.length % 16;
|
|
711
|
+
if (left)
|
|
712
|
+
h.update(ZEROS16.subarray(left));
|
|
713
|
+
};
|
|
714
|
+
var ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
|
|
715
|
+
function computeTag(fn, key, nonce, data, AAD) {
|
|
716
|
+
const authKey = fn(key, nonce, ZEROS32);
|
|
717
|
+
const h = poly1305.create(authKey);
|
|
718
|
+
if (AAD)
|
|
719
|
+
updatePadded(h, AAD);
|
|
720
|
+
updatePadded(h, data);
|
|
721
|
+
const num = u64Lengths(data.length, AAD ? AAD.length : 0, true);
|
|
722
|
+
h.update(num);
|
|
723
|
+
const res = h.digest();
|
|
724
|
+
clean(authKey, num);
|
|
725
|
+
return res;
|
|
726
|
+
}
|
|
727
|
+
var _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
|
728
|
+
const tagLength = 16;
|
|
729
|
+
return {
|
|
730
|
+
encrypt(plaintext, output) {
|
|
731
|
+
const plength = plaintext.length;
|
|
732
|
+
output = getOutput(plength + tagLength, output, false);
|
|
733
|
+
output.set(plaintext);
|
|
734
|
+
const oPlain = output.subarray(0, -tagLength);
|
|
735
|
+
xorStream(key, nonce, oPlain, oPlain, 1);
|
|
736
|
+
const tag = computeTag(xorStream, key, nonce, oPlain, AAD);
|
|
737
|
+
output.set(tag, plength);
|
|
738
|
+
clean(tag);
|
|
739
|
+
return output;
|
|
740
|
+
},
|
|
741
|
+
decrypt(ciphertext, output) {
|
|
742
|
+
output = getOutput(ciphertext.length - tagLength, output, false);
|
|
743
|
+
const data = ciphertext.subarray(0, -tagLength);
|
|
744
|
+
const passedTag = ciphertext.subarray(-tagLength);
|
|
745
|
+
const tag = computeTag(xorStream, key, nonce, data, AAD);
|
|
746
|
+
if (!equalBytes(passedTag, tag))
|
|
747
|
+
throw new Error("invalid tag");
|
|
748
|
+
output.set(ciphertext.subarray(0, -tagLength));
|
|
749
|
+
xorStream(key, nonce, output, output, 1);
|
|
750
|
+
clean(tag);
|
|
751
|
+
return output;
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
};
|
|
755
|
+
var chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16 }, _poly1305_aead(chacha20));
|
|
756
|
+
var xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, _poly1305_aead(xchacha20));
|
|
757
|
+
|
|
758
|
+
// packages/voice/src/protocol.ts
|
|
759
|
+
var VOICE_GATEWAY_VERSION2 = 8;
|
|
760
|
+
var VoiceOpcodes2 = {
|
|
761
|
+
Identify: 0,
|
|
762
|
+
SelectProtocol: 1,
|
|
763
|
+
Ready: 2,
|
|
764
|
+
Heartbeat: 3,
|
|
765
|
+
SessionDescription: 4,
|
|
766
|
+
Speaking: 5,
|
|
767
|
+
HeartbeatAck: 6,
|
|
768
|
+
Resume: 7,
|
|
769
|
+
Hello: 8,
|
|
770
|
+
Resumed: 9,
|
|
771
|
+
ClientsConnect: 11,
|
|
772
|
+
ClientDisconnect: 13,
|
|
773
|
+
DavePrepareTransition: 21,
|
|
774
|
+
DaveExecuteTransition: 22,
|
|
775
|
+
DaveTransitionReady: 23,
|
|
776
|
+
DavePrepareEpoch: 24,
|
|
777
|
+
DaveMlsExternalSender: 25,
|
|
778
|
+
DaveMlsKeyPackage: 26,
|
|
779
|
+
DaveMlsProposals: 27,
|
|
780
|
+
DaveMlsCommitWelcome: 28,
|
|
781
|
+
DaveMlsAnnounceCommitTransition: 29,
|
|
782
|
+
DaveMlsWelcome: 30,
|
|
783
|
+
DaveMlsInvalidCommitWelcome: 31
|
|
784
|
+
};
|
|
785
|
+
var VoiceCloseCodes2 = {
|
|
786
|
+
UnknownOpcode: 4001,
|
|
787
|
+
FailedToDecode: 4002,
|
|
788
|
+
NotAuthenticated: 4003,
|
|
789
|
+
AuthenticationFailed: 4004,
|
|
790
|
+
AlreadyAuthenticated: 4005,
|
|
791
|
+
SessionNoLongerValid: 4006,
|
|
792
|
+
SessionTimeout: 4009,
|
|
793
|
+
ServerNotFound: 4011,
|
|
794
|
+
UnknownProtocol: 4012,
|
|
795
|
+
Disconnected: 4014,
|
|
796
|
+
VoiceServerCrashed: 4015,
|
|
797
|
+
UnknownEncryptionMode: 4016,
|
|
798
|
+
DaveProtocolRequired: 4017,
|
|
799
|
+
BadRequest: 4020,
|
|
800
|
+
RateLimited: 4021,
|
|
801
|
+
CallTerminated: 4022
|
|
802
|
+
};
|
|
803
|
+
function canResumeVoice2(code) {
|
|
804
|
+
return code < 4000 || code === VoiceCloseCodes2.VoiceServerCrashed;
|
|
805
|
+
}
|
|
806
|
+
var RTP_OPUS_PAYLOAD_TYPE2 = 120;
|
|
807
|
+
var FRAME_DURATION_MS2 = 20;
|
|
808
|
+
var FRAME_SAMPLES2 = 960;
|
|
809
|
+
var PCM_FRAME_BYTES2 = FRAME_SAMPLES2 * 2 * 2;
|
|
810
|
+
var SILENCE_FRAME2 = Uint8Array.of(248, 255, 254);
|
|
811
|
+
var SpeakingFlags2 = {
|
|
812
|
+
Microphone: 1,
|
|
813
|
+
Soundshare: 2,
|
|
814
|
+
Priority: 4
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
class VoiceError2 extends Error {
|
|
818
|
+
constructor(message, options) {
|
|
819
|
+
super(message, options);
|
|
820
|
+
this.name = "VoiceError";
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// packages/voice/src/crypto.ts
|
|
825
|
+
var TAG_BYTES = 16;
|
|
826
|
+
var NONCE_SUFFIX_BYTES = 4;
|
|
827
|
+
var SUPPORTED_ENCRYPTION_MODES2 = getCiphers().includes("aes-256-gcm") ? ["aead_aes256_gcm_rtpsize", "aead_xchacha20_poly1305_rtpsize"] : ["aead_xchacha20_poly1305_rtpsize"];
|
|
828
|
+
function chooseEncryptionMode2(offered) {
|
|
829
|
+
const mode = SUPPORTED_ENCRYPTION_MODES2.find((m) => offered.includes(m));
|
|
830
|
+
if (!mode)
|
|
831
|
+
throw new VoiceError2(`No supported voice encryption mode offered (got: ${offered.join(", ") || "none"}).`);
|
|
832
|
+
return mode;
|
|
833
|
+
}
|
|
834
|
+
function rtpHeader2(sequence, timestamp, ssrc) {
|
|
835
|
+
const header = new Uint8Array(12);
|
|
836
|
+
const view = new DataView(header.buffer);
|
|
837
|
+
header[0] = 128;
|
|
838
|
+
header[1] = RTP_OPUS_PAYLOAD_TYPE2;
|
|
839
|
+
view.setUint16(2, sequence & 65535);
|
|
840
|
+
view.setUint32(4, timestamp >>> 0);
|
|
841
|
+
view.setUint32(8, ssrc >>> 0);
|
|
842
|
+
return header;
|
|
843
|
+
}
|
|
844
|
+
function parseRtpHeader2(packet) {
|
|
845
|
+
if (packet.length < 12)
|
|
846
|
+
return;
|
|
847
|
+
const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength);
|
|
848
|
+
const first = packet[0];
|
|
849
|
+
const version = first >> 6;
|
|
850
|
+
if (version !== 2)
|
|
851
|
+
return;
|
|
852
|
+
const extension = (first & 16) !== 0;
|
|
853
|
+
let headerSize = 12 + (first & 15) * 4;
|
|
854
|
+
let extensionWords = 0;
|
|
855
|
+
if (extension) {
|
|
856
|
+
if (packet.length < headerSize + 4)
|
|
857
|
+
return;
|
|
858
|
+
extensionWords = view.getUint16(headerSize + 2);
|
|
859
|
+
headerSize += 4;
|
|
860
|
+
}
|
|
861
|
+
if (packet.length < headerSize)
|
|
862
|
+
return;
|
|
863
|
+
return {
|
|
864
|
+
version,
|
|
865
|
+
padding: (first & 32) !== 0,
|
|
866
|
+
extension,
|
|
867
|
+
payloadType: packet[1] & 127,
|
|
868
|
+
sequence: view.getUint16(2),
|
|
869
|
+
timestamp: view.getUint32(4),
|
|
870
|
+
ssrc: view.getUint32(8),
|
|
871
|
+
headerSize,
|
|
872
|
+
extensionWords
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
class PacketCipher2 {
|
|
877
|
+
mode;
|
|
878
|
+
#key;
|
|
879
|
+
#counter = 0;
|
|
880
|
+
constructor(mode, key, counter = 0) {
|
|
881
|
+
if (key.length !== 32)
|
|
882
|
+
throw new VoiceError2("The voice secret key must be 32 bytes.");
|
|
883
|
+
if (!SUPPORTED_ENCRYPTION_MODES2.includes(mode))
|
|
884
|
+
throw new VoiceError2(`Unsupported voice encryption mode: ${mode}`);
|
|
885
|
+
this.mode = mode;
|
|
886
|
+
this.#key = Uint8Array.from(key);
|
|
887
|
+
this.#counter = counter >>> 0;
|
|
888
|
+
}
|
|
889
|
+
get counter() {
|
|
890
|
+
return this.#counter;
|
|
891
|
+
}
|
|
892
|
+
#nonce(suffix) {
|
|
893
|
+
const nonce = new Uint8Array(this.mode === "aead_aes256_gcm_rtpsize" ? 12 : 24);
|
|
894
|
+
nonce.set(suffix, 0);
|
|
895
|
+
return nonce;
|
|
896
|
+
}
|
|
897
|
+
seal(header, payload) {
|
|
898
|
+
const suffix = new Uint8Array(NONCE_SUFFIX_BYTES);
|
|
899
|
+
new DataView(suffix.buffer).setUint32(0, this.#counter);
|
|
900
|
+
this.#counter = this.#counter + 1 >>> 0;
|
|
901
|
+
const nonce = this.#nonce(suffix);
|
|
902
|
+
let sealed;
|
|
903
|
+
if (this.mode === "aead_aes256_gcm_rtpsize") {
|
|
904
|
+
const cipher = createCipheriv("aes-256-gcm", this.#key, nonce);
|
|
905
|
+
cipher.setAAD(header);
|
|
906
|
+
const body = Buffer.concat([
|
|
907
|
+
cipher.update(payload),
|
|
908
|
+
cipher.final()
|
|
909
|
+
]);
|
|
910
|
+
sealed = Buffer.concat([body, cipher.getAuthTag()]);
|
|
911
|
+
} else
|
|
912
|
+
sealed = xchacha20poly1305(this.#key, nonce, header).encrypt(payload);
|
|
913
|
+
const out = new Uint8Array(header.length + sealed.length + NONCE_SUFFIX_BYTES);
|
|
914
|
+
out.set(header, 0);
|
|
915
|
+
out.set(sealed, header.length);
|
|
916
|
+
out.set(suffix, header.length + sealed.length);
|
|
917
|
+
return out;
|
|
918
|
+
}
|
|
919
|
+
open(packet) {
|
|
920
|
+
const header = parseRtpHeader2(packet);
|
|
921
|
+
if (!header)
|
|
922
|
+
return;
|
|
923
|
+
const bodyEnd = packet.length - NONCE_SUFFIX_BYTES;
|
|
924
|
+
const tagStart = bodyEnd - TAG_BYTES;
|
|
925
|
+
if (tagStart < header.headerSize)
|
|
926
|
+
return;
|
|
927
|
+
const aad = packet.subarray(0, header.headerSize);
|
|
928
|
+
const nonce = this.#nonce(packet.subarray(bodyEnd));
|
|
929
|
+
let plain;
|
|
930
|
+
try {
|
|
931
|
+
if (this.mode === "aead_aes256_gcm_rtpsize") {
|
|
932
|
+
const decipher = createDecipheriv("aes-256-gcm", this.#key, nonce);
|
|
933
|
+
decipher.setAAD(aad);
|
|
934
|
+
decipher.setAuthTag(packet.subarray(tagStart, bodyEnd));
|
|
935
|
+
plain = Buffer.concat([
|
|
936
|
+
decipher.update(packet.subarray(header.headerSize, tagStart)),
|
|
937
|
+
decipher.final()
|
|
938
|
+
]);
|
|
939
|
+
} else
|
|
940
|
+
plain = xchacha20poly1305(this.#key, nonce, aad).decrypt(packet.subarray(header.headerSize, bodyEnd));
|
|
941
|
+
} catch {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (header.padding && plain.length > 0) {
|
|
945
|
+
const pad = plain[plain.length - 1];
|
|
946
|
+
if (pad > plain.length)
|
|
947
|
+
return;
|
|
948
|
+
plain = plain.subarray(0, plain.length - pad);
|
|
949
|
+
}
|
|
950
|
+
const skip = header.extensionWords * 4;
|
|
951
|
+
if (skip > plain.length)
|
|
952
|
+
return;
|
|
953
|
+
return { header, payload: plain.subarray(skip) };
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// packages/voice/src/dave.ts
|
|
958
|
+
var davey;
|
|
959
|
+
async function loadDave2() {
|
|
960
|
+
if (davey !== undefined)
|
|
961
|
+
return davey;
|
|
962
|
+
try {
|
|
963
|
+
const name = "@snazzah/davey";
|
|
964
|
+
const mod = await import(name);
|
|
965
|
+
davey = mod.default?.DAVESession ? mod.default : mod;
|
|
966
|
+
} catch {
|
|
967
|
+
davey = null;
|
|
968
|
+
}
|
|
969
|
+
return davey;
|
|
970
|
+
}
|
|
971
|
+
async function maxDaveProtocolVersion2() {
|
|
972
|
+
return (await loadDave2())?.DAVE_PROTOCOL_VERSION ?? 0;
|
|
973
|
+
}
|
|
974
|
+
var TRANSITION_EXPIRY_S = 10;
|
|
975
|
+
var TRANSITION_EXPIRY_DOWNGRADE_S = 24;
|
|
976
|
+
var DECRYPTION_FAILURE_TOLERANCE = 36;
|
|
977
|
+
var AUDIO = 0;
|
|
978
|
+
function equalBytes2(a, b) {
|
|
979
|
+
return a.length === b.length && a.every((byte, i) => byte === b[i]);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
class DaveSession2 {
|
|
983
|
+
protocolVersion;
|
|
984
|
+
#module;
|
|
985
|
+
#userId;
|
|
986
|
+
#channelId;
|
|
987
|
+
#hooks;
|
|
988
|
+
#session;
|
|
989
|
+
#pending = new Map;
|
|
990
|
+
#lastTransition;
|
|
991
|
+
#downgraded = false;
|
|
992
|
+
#reinitializing = false;
|
|
993
|
+
#upgrading = false;
|
|
994
|
+
#failures = 0;
|
|
995
|
+
constructor(module, protocolVersion, userId, channelId, hooks) {
|
|
996
|
+
this.#module = module;
|
|
997
|
+
this.protocolVersion = protocolVersion;
|
|
998
|
+
this.#userId = userId;
|
|
999
|
+
this.#channelId = channelId;
|
|
1000
|
+
this.#hooks = hooks;
|
|
1001
|
+
}
|
|
1002
|
+
get active() {
|
|
1003
|
+
return this.protocolVersion > 0 && Boolean(this.#session?.ready);
|
|
1004
|
+
}
|
|
1005
|
+
get voicePrivacyCode() {
|
|
1006
|
+
return this.protocolVersion > 0 && this.#session?.voicePrivacyCode ? this.#session.voicePrivacyCode : null;
|
|
1007
|
+
}
|
|
1008
|
+
reinit() {
|
|
1009
|
+
if (this.protocolVersion > 0) {
|
|
1010
|
+
if (this.#session)
|
|
1011
|
+
this.#session.reinit(this.protocolVersion, this.#userId, this.#channelId);
|
|
1012
|
+
else
|
|
1013
|
+
this.#session = new this.#module.DAVESession(this.protocolVersion, this.#userId, this.#channelId);
|
|
1014
|
+
this.#hooks.keyPackage(this.#session.getSerializedKeyPackage());
|
|
1015
|
+
} else if (this.#session) {
|
|
1016
|
+
this.#session.reset();
|
|
1017
|
+
this.#session.setPassthroughMode(true, TRANSITION_EXPIRY_S);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
setExternalSender(payload) {
|
|
1021
|
+
this.#native().setExternalSender(Buffer.from(payload));
|
|
1022
|
+
}
|
|
1023
|
+
prepareTransition(transitionId, protocolVersion) {
|
|
1024
|
+
this.#pending.set(transitionId, protocolVersion);
|
|
1025
|
+
if (transitionId === 0) {
|
|
1026
|
+
this.executeTransition(0);
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
if (protocolVersion === 0)
|
|
1030
|
+
this.#session?.setPassthroughMode(true, TRANSITION_EXPIRY_DOWNGRADE_S);
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
executeTransition(transitionId) {
|
|
1034
|
+
const next = this.#pending.get(transitionId);
|
|
1035
|
+
if (next === undefined)
|
|
1036
|
+
return false;
|
|
1037
|
+
const previous = this.protocolVersion;
|
|
1038
|
+
this.protocolVersion = next;
|
|
1039
|
+
if (previous !== next && next === 0)
|
|
1040
|
+
this.#downgraded = true;
|
|
1041
|
+
else if (transitionId > 0 && this.#downgraded) {
|
|
1042
|
+
this.#downgraded = false;
|
|
1043
|
+
this.#session?.setPassthroughMode(true, TRANSITION_EXPIRY_S);
|
|
1044
|
+
}
|
|
1045
|
+
this.#reinitializing = false;
|
|
1046
|
+
this.#upgrading = false;
|
|
1047
|
+
this.#lastTransition = transitionId;
|
|
1048
|
+
this.#pending.delete(transitionId);
|
|
1049
|
+
this.#hooks.debug?.(`DAVE transition ${transitionId}: v${previous} -> v${next}`);
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
prepareEpoch(epoch, protocolVersion) {
|
|
1053
|
+
if (epoch === 1) {
|
|
1054
|
+
if (this.protocolVersion === 0 && protocolVersion > 0)
|
|
1055
|
+
this.#upgrading = true;
|
|
1056
|
+
this.protocolVersion = protocolVersion;
|
|
1057
|
+
this.reinit();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
processProposals(payload, connectedUserIds) {
|
|
1061
|
+
if (payload.length < 1)
|
|
1062
|
+
return;
|
|
1063
|
+
const { commit, welcome } = this.#native().processProposals(payload[0], Buffer.from(payload.subarray(1)), [...connectedUserIds]);
|
|
1064
|
+
if (!commit)
|
|
1065
|
+
return;
|
|
1066
|
+
return welcome ? Buffer.concat([commit, welcome]) : commit;
|
|
1067
|
+
}
|
|
1068
|
+
process(kind, payload) {
|
|
1069
|
+
const transitionId = payload.length >= 2 ? payload[0] << 8 | payload[1] : 0;
|
|
1070
|
+
try {
|
|
1071
|
+
const body = Buffer.from(payload.subarray(2));
|
|
1072
|
+
if (kind === "commit")
|
|
1073
|
+
this.#native().processCommit(body);
|
|
1074
|
+
else
|
|
1075
|
+
this.#native().processWelcome(body);
|
|
1076
|
+
if (transitionId === 0) {
|
|
1077
|
+
this.#reinitializing = false;
|
|
1078
|
+
this.#lastTransition = 0;
|
|
1079
|
+
} else
|
|
1080
|
+
this.#pending.set(transitionId, this.protocolVersion);
|
|
1081
|
+
return { transitionId, ok: true };
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
this.#hooks.debug?.(`DAVE ${kind} failed: ${String(error)}`);
|
|
1084
|
+
this.#recover(transitionId);
|
|
1085
|
+
return { transitionId, ok: false };
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
#recover(transitionId) {
|
|
1089
|
+
if (this.#reinitializing)
|
|
1090
|
+
return;
|
|
1091
|
+
this.#reinitializing = true;
|
|
1092
|
+
this.#failures = 0;
|
|
1093
|
+
this.#hooks.invalidTransition(transitionId);
|
|
1094
|
+
this.reinit();
|
|
1095
|
+
}
|
|
1096
|
+
encrypt(frame) {
|
|
1097
|
+
if (this.protocolVersion === 0 || equalBytes2(frame, SILENCE_FRAME2))
|
|
1098
|
+
return frame;
|
|
1099
|
+
if (!this.#session?.ready)
|
|
1100
|
+
return this.#upgrading ? frame : null;
|
|
1101
|
+
return this.#session.encryptOpus(Buffer.from(frame));
|
|
1102
|
+
}
|
|
1103
|
+
decrypt(frame, userId) {
|
|
1104
|
+
const session = this.#session;
|
|
1105
|
+
const canDecrypt = session?.ready && (this.protocolVersion !== 0 || session.canPassthrough(userId));
|
|
1106
|
+
if (!session || !canDecrypt || equalBytes2(frame, SILENCE_FRAME2))
|
|
1107
|
+
return frame;
|
|
1108
|
+
try {
|
|
1109
|
+
const out = session.decrypt(userId, AUDIO, Buffer.from(frame));
|
|
1110
|
+
this.#failures = 0;
|
|
1111
|
+
return out;
|
|
1112
|
+
} catch {
|
|
1113
|
+
if (!this.#reinitializing && this.#pending.size === 0) {
|
|
1114
|
+
this.#failures++;
|
|
1115
|
+
if (this.#failures > DECRYPTION_FAILURE_TOLERANCE && this.#lastTransition !== undefined)
|
|
1116
|
+
this.#recover(this.#lastTransition);
|
|
1117
|
+
}
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
#native() {
|
|
1122
|
+
if (!this.#session)
|
|
1123
|
+
throw new VoiceError2("No DAVE session.");
|
|
1124
|
+
return this.#session;
|
|
1125
|
+
}
|
|
1126
|
+
destroy() {
|
|
1127
|
+
try {
|
|
1128
|
+
this.#session?.reset();
|
|
1129
|
+
} catch {}
|
|
1130
|
+
this.#session = undefined;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// packages/voice/src/gateway.ts
|
|
1135
|
+
var globalVoiceSocket = (url) => new WebSocket(url);
|
|
1136
|
+
var HEARTBEAT_TIMEOUT_CLOSE2 = 3001;
|
|
1137
|
+
function voiceGatewayURL2(endpoint) {
|
|
1138
|
+
const host = endpoint.replace(/^wss?:\/\//, "").replace(/\/.*$/, "");
|
|
1139
|
+
return `wss://${host}/?v=${VOICE_GATEWAY_VERSION2}`;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
class VoiceGateway2 {
|
|
1143
|
+
sequence;
|
|
1144
|
+
#socket;
|
|
1145
|
+
#handlers;
|
|
1146
|
+
#heartbeat;
|
|
1147
|
+
#closed = false;
|
|
1148
|
+
constructor(url, handlers, options = {}) {
|
|
1149
|
+
this.sequence = options.sequence ?? -1;
|
|
1150
|
+
this.#handlers = handlers;
|
|
1151
|
+
this.#socket = (options.factory ?? globalVoiceSocket)(url);
|
|
1152
|
+
this.#socket.binaryType = "arraybuffer";
|
|
1153
|
+
this.#heartbeat = new GatewayHeartbeat2({
|
|
1154
|
+
ackTimeout: 15000,
|
|
1155
|
+
zombieTimeout: 60000,
|
|
1156
|
+
sequence: () => this.sequence,
|
|
1157
|
+
isConnected: () => this.#socket.readyState === 1,
|
|
1158
|
+
send: () => this.sendJSON({
|
|
1159
|
+
op: VoiceOpcodes2.Heartbeat,
|
|
1160
|
+
d: { t: Date.now(), seq_ack: this.sequence }
|
|
1161
|
+
}),
|
|
1162
|
+
onTimeout: () => this.close(HEARTBEAT_TIMEOUT_CLOSE2, "Heartbeat timed out."),
|
|
1163
|
+
onError: (error) => this.#handlers.error(error)
|
|
1164
|
+
});
|
|
1165
|
+
this.#socket.addEventListener("open", () => {
|
|
1166
|
+
this.#heartbeat.watch();
|
|
1167
|
+
this.#handlers.open();
|
|
1168
|
+
});
|
|
1169
|
+
this.#socket.addEventListener("message", (event) => this.#message(event.data));
|
|
1170
|
+
this.#socket.addEventListener("error", () => {});
|
|
1171
|
+
this.#socket.addEventListener("close", (event) => {
|
|
1172
|
+
this.#heartbeat.stop();
|
|
1173
|
+
if (this.#closed)
|
|
1174
|
+
return;
|
|
1175
|
+
this.#closed = true;
|
|
1176
|
+
this.#handlers.close(event.code, event.reason ?? "");
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
get ping() {
|
|
1180
|
+
return this.#heartbeat.latency;
|
|
1181
|
+
}
|
|
1182
|
+
#message(data) {
|
|
1183
|
+
this.#heartbeat.receivedMessage();
|
|
1184
|
+
if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
|
|
1185
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
1186
|
+
if (bytes.length < 3)
|
|
1187
|
+
return;
|
|
1188
|
+
const seq = bytes[0] << 8 | bytes[1];
|
|
1189
|
+
this.sequence = seq;
|
|
1190
|
+
this.#handlers.binary({
|
|
1191
|
+
seq,
|
|
1192
|
+
op: bytes[2],
|
|
1193
|
+
payload: bytes.subarray(3)
|
|
1194
|
+
});
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (typeof data !== "string")
|
|
1198
|
+
return;
|
|
1199
|
+
let packet;
|
|
1200
|
+
try {
|
|
1201
|
+
packet = JSON.parse(data);
|
|
1202
|
+
} catch {
|
|
1203
|
+
this.#handlers.error(new Error("The voice gateway sent invalid JSON."));
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (!packet || typeof packet.op !== "number")
|
|
1207
|
+
return;
|
|
1208
|
+
if (typeof packet.seq === "number")
|
|
1209
|
+
this.sequence = packet.seq;
|
|
1210
|
+
if (packet.op === VoiceOpcodes2.Hello) {
|
|
1211
|
+
const interval = packet.d?.heartbeat_interval;
|
|
1212
|
+
if (typeof interval === "number" && interval > 0)
|
|
1213
|
+
this.#heartbeat.start(interval);
|
|
1214
|
+
} else if (packet.op === VoiceOpcodes2.HeartbeatAck)
|
|
1215
|
+
this.#heartbeat.acknowledge();
|
|
1216
|
+
this.#handlers.packet(packet);
|
|
1217
|
+
}
|
|
1218
|
+
sendJSON(packet) {
|
|
1219
|
+
if (this.#socket.readyState !== 1)
|
|
1220
|
+
return false;
|
|
1221
|
+
try {
|
|
1222
|
+
this.#socket.send(JSON.stringify(packet));
|
|
1223
|
+
return true;
|
|
1224
|
+
} catch {
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
sendBinary(op, payload) {
|
|
1229
|
+
if (this.#socket.readyState !== 1)
|
|
1230
|
+
return false;
|
|
1231
|
+
const frame = new Uint8Array(payload.length + 1);
|
|
1232
|
+
frame[0] = op;
|
|
1233
|
+
frame.set(payload, 1);
|
|
1234
|
+
try {
|
|
1235
|
+
this.#socket.send(frame);
|
|
1236
|
+
return true;
|
|
1237
|
+
} catch {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
close(code = 1000, reason = "") {
|
|
1242
|
+
this.#heartbeat.stop();
|
|
1243
|
+
const reported = this.#closed;
|
|
1244
|
+
this.#closed = true;
|
|
1245
|
+
try {
|
|
1246
|
+
this.#socket.close(code, reason);
|
|
1247
|
+
} catch {}
|
|
1248
|
+
if (!reported && code !== 1000)
|
|
1249
|
+
this.#handlers.close(code, reason);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// packages/voice/src/udp.ts
|
|
1254
|
+
var bunUdpSocket2 = async (onData) => await Bun.udpSocket({
|
|
1255
|
+
socket: {
|
|
1256
|
+
data: (_socket, data) => onData(new Uint8Array(data)),
|
|
1257
|
+
error: () => {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
var KEEPALIVE_MS = 5000;
|
|
1263
|
+
var DISCOVERY_TIMEOUT_MS = 5000;
|
|
1264
|
+
function discoveryRequest2(ssrc) {
|
|
1265
|
+
const packet = new Uint8Array(74);
|
|
1266
|
+
const view = new DataView(packet.buffer);
|
|
1267
|
+
view.setUint16(0, 1);
|
|
1268
|
+
view.setUint16(2, 70);
|
|
1269
|
+
view.setUint32(4, ssrc >>> 0);
|
|
1270
|
+
return packet;
|
|
1271
|
+
}
|
|
1272
|
+
function parseDiscoveryResponse2(packet) {
|
|
1273
|
+
if (packet.length !== 74)
|
|
1274
|
+
return;
|
|
1275
|
+
const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength);
|
|
1276
|
+
if (view.getUint16(0) !== 2 || view.getUint16(2) !== 70)
|
|
1277
|
+
return;
|
|
1278
|
+
const end = packet.indexOf(0, 8);
|
|
1279
|
+
const ip = new TextDecoder().decode(packet.subarray(8, end === -1 || end > 72 ? 72 : end));
|
|
1280
|
+
if (!/^\d{1,3}(?:\.\d{1,3}){3}$/.test(ip))
|
|
1281
|
+
return;
|
|
1282
|
+
return { ip, port: view.getUint16(72) };
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
class VoiceUdp2 {
|
|
1286
|
+
#socket;
|
|
1287
|
+
#ip;
|
|
1288
|
+
#port;
|
|
1289
|
+
#keepalive;
|
|
1290
|
+
#listeners = new Set;
|
|
1291
|
+
#discoveries = new Set;
|
|
1292
|
+
#counter = 0;
|
|
1293
|
+
#closed = false;
|
|
1294
|
+
constructor(socket, ip, port) {
|
|
1295
|
+
this.#socket = socket;
|
|
1296
|
+
this.#ip = ip;
|
|
1297
|
+
this.#port = port;
|
|
1298
|
+
this.#keepalive = setInterval(() => this.#sendKeepalive(), KEEPALIVE_MS);
|
|
1299
|
+
this.#keepalive.unref?.();
|
|
1300
|
+
}
|
|
1301
|
+
static async open(ip, port, factory = bunUdpSocket2) {
|
|
1302
|
+
let udp;
|
|
1303
|
+
const socket = await factory((data) => {
|
|
1304
|
+
if (udp)
|
|
1305
|
+
udp.#receive(data);
|
|
1306
|
+
});
|
|
1307
|
+
udp = new VoiceUdp2(socket, ip, port);
|
|
1308
|
+
return udp;
|
|
1309
|
+
}
|
|
1310
|
+
get closed() {
|
|
1311
|
+
return this.#closed;
|
|
1312
|
+
}
|
|
1313
|
+
onPacket(listener) {
|
|
1314
|
+
this.#listeners.add(listener);
|
|
1315
|
+
return () => this.#listeners.delete(listener);
|
|
1316
|
+
}
|
|
1317
|
+
send(packet) {
|
|
1318
|
+
if (this.#closed)
|
|
1319
|
+
return false;
|
|
1320
|
+
try {
|
|
1321
|
+
return this.#socket.send(packet, this.#port, this.#ip);
|
|
1322
|
+
} catch {
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
discover(ssrc, timeoutMs = DISCOVERY_TIMEOUT_MS) {
|
|
1327
|
+
return new Promise((resolve, reject) => {
|
|
1328
|
+
const settle = (error, found) => {
|
|
1329
|
+
clearTimeout(timer);
|
|
1330
|
+
remove();
|
|
1331
|
+
this.#discoveries.delete(abort);
|
|
1332
|
+
if (error)
|
|
1333
|
+
reject(error);
|
|
1334
|
+
else
|
|
1335
|
+
resolve(found);
|
|
1336
|
+
};
|
|
1337
|
+
const abort = () => settle(new VoiceError2("The voice UDP socket is closed."));
|
|
1338
|
+
const timer = setTimeout(() => settle(new VoiceError2("IP discovery timed out.")), timeoutMs);
|
|
1339
|
+
const remove = this.onPacket((packet) => {
|
|
1340
|
+
const found = parseDiscoveryResponse2(packet);
|
|
1341
|
+
if (found)
|
|
1342
|
+
settle(undefined, found);
|
|
1343
|
+
});
|
|
1344
|
+
this.#discoveries.add(abort);
|
|
1345
|
+
if (!this.send(discoveryRequest2(ssrc)))
|
|
1346
|
+
abort();
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
#sendKeepalive() {
|
|
1350
|
+
const packet = new Uint8Array(8);
|
|
1351
|
+
new DataView(packet.buffer).setUint32(0, this.#counter, true);
|
|
1352
|
+
this.#counter = this.#counter + 1 >>> 0;
|
|
1353
|
+
this.send(packet);
|
|
1354
|
+
}
|
|
1355
|
+
#receive(data) {
|
|
1356
|
+
if (this.#closed)
|
|
1357
|
+
return;
|
|
1358
|
+
for (const listener of [...this.#listeners])
|
|
1359
|
+
try {
|
|
1360
|
+
listener(data);
|
|
1361
|
+
} catch {}
|
|
1362
|
+
}
|
|
1363
|
+
close() {
|
|
1364
|
+
if (this.#closed)
|
|
1365
|
+
return;
|
|
1366
|
+
this.#closed = true;
|
|
1367
|
+
clearInterval(this.#keepalive);
|
|
1368
|
+
for (const abort of [...this.#discoveries])
|
|
1369
|
+
abort();
|
|
1370
|
+
this.#listeners.clear();
|
|
1371
|
+
try {
|
|
1372
|
+
this.#socket.close();
|
|
1373
|
+
} catch {}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// packages/voice/src/networking.ts
|
|
1378
|
+
var RESUME_ATTEMPTS = 5;
|
|
1379
|
+
var RESUME_DELAY_MS = 250;
|
|
1380
|
+
var RESUME_MAX_DELAY_MS = 1e4;
|
|
1381
|
+
|
|
1382
|
+
class VoiceNetworking2 {
|
|
1383
|
+
state = "opening";
|
|
1384
|
+
ssrc = 0;
|
|
1385
|
+
mode;
|
|
1386
|
+
#info;
|
|
1387
|
+
#options;
|
|
1388
|
+
#handlers;
|
|
1389
|
+
#gateway;
|
|
1390
|
+
#udp;
|
|
1391
|
+
#cipher;
|
|
1392
|
+
#dave;
|
|
1393
|
+
#maxDave = 0;
|
|
1394
|
+
#daveModule = null;
|
|
1395
|
+
#sequence = Math.floor(Math.random() * 65536);
|
|
1396
|
+
#timestamp = Math.floor(Math.random() * 4294967296);
|
|
1397
|
+
#speaking = false;
|
|
1398
|
+
#resumeAttempts = 0;
|
|
1399
|
+
#resumeTimer;
|
|
1400
|
+
#encryptFailing = false;
|
|
1401
|
+
#users = new Map;
|
|
1402
|
+
#connected = new Set;
|
|
1403
|
+
constructor(info, handlers = {}, options = {}) {
|
|
1404
|
+
this.#info = info;
|
|
1405
|
+
this.#handlers = handlers;
|
|
1406
|
+
this.#options = options;
|
|
1407
|
+
this.#gateway = this.#openGateway();
|
|
1408
|
+
}
|
|
1409
|
+
get ping() {
|
|
1410
|
+
return this.#gateway.ping;
|
|
1411
|
+
}
|
|
1412
|
+
get dave() {
|
|
1413
|
+
return this.#dave;
|
|
1414
|
+
}
|
|
1415
|
+
get connectedUsers() {
|
|
1416
|
+
return this.#connected;
|
|
1417
|
+
}
|
|
1418
|
+
#debug(message) {
|
|
1419
|
+
this.#options.debug?.(message);
|
|
1420
|
+
}
|
|
1421
|
+
#setState(next) {
|
|
1422
|
+
if (this.state === next)
|
|
1423
|
+
return;
|
|
1424
|
+
const previous = this.state;
|
|
1425
|
+
this.state = next;
|
|
1426
|
+
this.#handlers.state?.(next, previous);
|
|
1427
|
+
}
|
|
1428
|
+
#error(error) {
|
|
1429
|
+
this.#handlers.error?.(error instanceof Error ? error : new VoiceError2(String(error)));
|
|
1430
|
+
}
|
|
1431
|
+
#openGateway(sequence) {
|
|
1432
|
+
return new VoiceGateway2(typeof this.#options.url === "function" ? this.#options.url(this.#info.endpoint) : this.#options.url ?? voiceGatewayURL2(this.#info.endpoint), {
|
|
1433
|
+
open: () => void this.#onOpen(),
|
|
1434
|
+
packet: (packet) => this.#onPacket(packet),
|
|
1435
|
+
binary: (packet) => this.#onBinary(packet),
|
|
1436
|
+
close: (code, reason) => this.#onClose(code, reason),
|
|
1437
|
+
error: (error) => this.#error(error)
|
|
1438
|
+
}, { factory: this.#options.socketFactory, sequence });
|
|
1439
|
+
}
|
|
1440
|
+
async#onOpen() {
|
|
1441
|
+
const { guildId, userId, sessionId, token } = this.#info;
|
|
1442
|
+
if (this.state === "resuming") {
|
|
1443
|
+
this.#gateway.sendJSON({
|
|
1444
|
+
op: VoiceOpcodes2.Resume,
|
|
1445
|
+
d: {
|
|
1446
|
+
server_id: guildId,
|
|
1447
|
+
session_id: sessionId,
|
|
1448
|
+
token,
|
|
1449
|
+
seq_ack: this.#gateway.sequence
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
if (this.state !== "opening")
|
|
1455
|
+
return;
|
|
1456
|
+
this.#daveModule = this.#options.dave === false ? null : this.#options.daveModule ?? await loadDave2();
|
|
1457
|
+
this.#maxDave = this.#daveModule?.DAVE_PROTOCOL_VERSION ?? 0;
|
|
1458
|
+
if (this.state !== "opening")
|
|
1459
|
+
return;
|
|
1460
|
+
this.#gateway.sendJSON({
|
|
1461
|
+
op: VoiceOpcodes2.Identify,
|
|
1462
|
+
d: {
|
|
1463
|
+
server_id: guildId,
|
|
1464
|
+
user_id: userId,
|
|
1465
|
+
session_id: sessionId,
|
|
1466
|
+
token,
|
|
1467
|
+
max_dave_protocol_version: this.#maxDave
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
this.#setState("identifying");
|
|
1471
|
+
}
|
|
1472
|
+
#onPacket(packet) {
|
|
1473
|
+
const d = packet.d ?? {};
|
|
1474
|
+
switch (packet.op) {
|
|
1475
|
+
case VoiceOpcodes2.Ready:
|
|
1476
|
+
if (this.state === "identifying")
|
|
1477
|
+
this.#onReady(d);
|
|
1478
|
+
return;
|
|
1479
|
+
case VoiceOpcodes2.SessionDescription:
|
|
1480
|
+
if (this.state === "selecting")
|
|
1481
|
+
this.#onSession(d);
|
|
1482
|
+
return;
|
|
1483
|
+
case VoiceOpcodes2.Resumed:
|
|
1484
|
+
if (this.state === "resuming") {
|
|
1485
|
+
this.#speaking = false;
|
|
1486
|
+
this.#resumeAttempts = 0;
|
|
1487
|
+
this.#setState("ready");
|
|
1488
|
+
}
|
|
1489
|
+
return;
|
|
1490
|
+
case VoiceOpcodes2.Speaking:
|
|
1491
|
+
if (typeof d.user_id === "string" && typeof d.ssrc === "number") {
|
|
1492
|
+
this.#users.set(d.ssrc, d.user_id);
|
|
1493
|
+
this.#handlers.speaking?.(d.user_id, d.ssrc);
|
|
1494
|
+
}
|
|
1495
|
+
return;
|
|
1496
|
+
case VoiceOpcodes2.ClientsConnect:
|
|
1497
|
+
if (Array.isArray(d.user_ids)) {
|
|
1498
|
+
for (const id of d.user_ids)
|
|
1499
|
+
if (typeof id === "string")
|
|
1500
|
+
this.#connected.add(id);
|
|
1501
|
+
}
|
|
1502
|
+
return;
|
|
1503
|
+
case VoiceOpcodes2.ClientDisconnect:
|
|
1504
|
+
if (typeof d.user_id === "string") {
|
|
1505
|
+
this.#connected.delete(d.user_id);
|
|
1506
|
+
for (const [ssrc, user] of this.#users)
|
|
1507
|
+
if (user === d.user_id)
|
|
1508
|
+
this.#users.delete(ssrc);
|
|
1509
|
+
this.#handlers.clientDisconnect?.(d.user_id);
|
|
1510
|
+
}
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const dave = this.#dave;
|
|
1514
|
+
if (!dave || this.state !== "ready" && this.state !== "resuming")
|
|
1515
|
+
return;
|
|
1516
|
+
const transitionId = Number(d.transition_id ?? 0);
|
|
1517
|
+
if (packet.op === VoiceOpcodes2.DavePrepareTransition) {
|
|
1518
|
+
if (dave.prepareTransition(transitionId, Number(d.protocol_version ?? 0)))
|
|
1519
|
+
this.#gateway.sendJSON({
|
|
1520
|
+
op: VoiceOpcodes2.DaveTransitionReady,
|
|
1521
|
+
d: { transition_id: transitionId }
|
|
1522
|
+
});
|
|
1523
|
+
} else if (packet.op === VoiceOpcodes2.DaveExecuteTransition)
|
|
1524
|
+
dave.executeTransition(transitionId);
|
|
1525
|
+
else if (packet.op === VoiceOpcodes2.DavePrepareEpoch)
|
|
1526
|
+
dave.prepareEpoch(Number(d.epoch ?? 0), Number(d.protocol_version ?? 0));
|
|
1527
|
+
}
|
|
1528
|
+
#onBinary(packet) {
|
|
1529
|
+
const dave = this.#dave;
|
|
1530
|
+
if (!dave || this.state !== "ready")
|
|
1531
|
+
return;
|
|
1532
|
+
try {
|
|
1533
|
+
switch (packet.op) {
|
|
1534
|
+
case VoiceOpcodes2.DaveMlsExternalSender:
|
|
1535
|
+
dave.setExternalSender(packet.payload);
|
|
1536
|
+
return;
|
|
1537
|
+
case VoiceOpcodes2.DaveMlsProposals: {
|
|
1538
|
+
const reply = dave.processProposals(packet.payload, this.#connected);
|
|
1539
|
+
if (reply)
|
|
1540
|
+
this.#gateway.sendBinary(VoiceOpcodes2.DaveMlsCommitWelcome, reply);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
case VoiceOpcodes2.DaveMlsAnnounceCommitTransition:
|
|
1544
|
+
case VoiceOpcodes2.DaveMlsWelcome: {
|
|
1545
|
+
const { transitionId, ok } = dave.process(packet.op === VoiceOpcodes2.DaveMlsWelcome ? "welcome" : "commit", packet.payload);
|
|
1546
|
+
if (ok && transitionId !== 0)
|
|
1547
|
+
this.#gateway.sendJSON({
|
|
1548
|
+
op: VoiceOpcodes2.DaveTransitionReady,
|
|
1549
|
+
d: { transition_id: transitionId }
|
|
1550
|
+
});
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
this.#error(error);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async#onReady(d) {
|
|
1559
|
+
const { ssrc, ip, port, modes } = d;
|
|
1560
|
+
this.ssrc = ssrc;
|
|
1561
|
+
this.#setState("discovering");
|
|
1562
|
+
try {
|
|
1563
|
+
const mode = chooseEncryptionMode2(Array.isArray(modes) ? modes : []);
|
|
1564
|
+
const udp = await VoiceUdp2.open(ip, port, this.#options.udpFactory);
|
|
1565
|
+
if (this.state !== "discovering")
|
|
1566
|
+
return void udp.close();
|
|
1567
|
+
this.#udp = udp;
|
|
1568
|
+
udp.onPacket((packet) => this.#receive(packet));
|
|
1569
|
+
const local = await udp.discover(ssrc);
|
|
1570
|
+
if (this.state !== "discovering")
|
|
1571
|
+
return;
|
|
1572
|
+
this.#gateway.sendJSON({
|
|
1573
|
+
op: VoiceOpcodes2.SelectProtocol,
|
|
1574
|
+
d: {
|
|
1575
|
+
protocol: "udp",
|
|
1576
|
+
data: { address: local.ip, port: local.port, mode }
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
this.#setState("selecting");
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
if (this.state === "closed")
|
|
1582
|
+
return;
|
|
1583
|
+
this.#error(error);
|
|
1584
|
+
this.destroy();
|
|
1585
|
+
this.#handlers.close?.(4000, error instanceof Error ? error.message : String(error));
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
#onSession(d) {
|
|
1589
|
+
const key = d.secret_key;
|
|
1590
|
+
if (!Array.isArray(key) || typeof d.mode !== "string")
|
|
1591
|
+
return;
|
|
1592
|
+
try {
|
|
1593
|
+
this.#cipher = new PacketCipher2(d.mode, Uint8Array.from(key));
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
this.#error(error);
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
this.mode = this.#cipher.mode;
|
|
1599
|
+
const daveVersion = Number(d.dave_protocol_version ?? 0);
|
|
1600
|
+
const module = this.#daveModule;
|
|
1601
|
+
if (module && this.#maxDave > 0) {
|
|
1602
|
+
this.#dave = new DaveSession2(module, daveVersion, this.#info.userId, this.#info.channelId, {
|
|
1603
|
+
keyPackage: (payload) => this.#gateway.sendBinary(VoiceOpcodes2.DaveMlsKeyPackage, payload),
|
|
1604
|
+
invalidTransition: (transitionId) => this.#gateway.sendJSON({
|
|
1605
|
+
op: VoiceOpcodes2.DaveMlsInvalidCommitWelcome,
|
|
1606
|
+
d: { transition_id: transitionId }
|
|
1607
|
+
}),
|
|
1608
|
+
debug: (message) => this.#debug(message)
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
this.#setState("ready");
|
|
1612
|
+
this.#dave?.reinit();
|
|
1613
|
+
}
|
|
1614
|
+
#onClose(code, reason) {
|
|
1615
|
+
if (this.state === "closed")
|
|
1616
|
+
return;
|
|
1617
|
+
if (canResumeVoice2(code) && (this.state === "ready" || this.state === "resuming")) {
|
|
1618
|
+
const resume = this.#options.resume ?? {};
|
|
1619
|
+
if (this.#resumeAttempts < (resume.attempts ?? RESUME_ATTEMPTS)) {
|
|
1620
|
+
const base = Math.min((resume.delay ?? RESUME_DELAY_MS) * 2 ** this.#resumeAttempts, resume.maxDelay ?? RESUME_MAX_DELAY_MS);
|
|
1621
|
+
const delay = Math.round(base / 2 + Math.random() * base / 2);
|
|
1622
|
+
this.#resumeAttempts++;
|
|
1623
|
+
this.#debug(`Voice gateway closed (${code}); resuming in ${delay} ms (attempt ${this.#resumeAttempts}).`);
|
|
1624
|
+
this.#setState("resuming");
|
|
1625
|
+
const sequence = this.#gateway.sequence;
|
|
1626
|
+
this.#resumeTimer = setTimeout(() => {
|
|
1627
|
+
this.#resumeTimer = undefined;
|
|
1628
|
+
if (this.state === "resuming")
|
|
1629
|
+
this.#gateway = this.#openGateway(sequence);
|
|
1630
|
+
}, delay);
|
|
1631
|
+
this.#resumeTimer.unref?.();
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
this.destroy();
|
|
1635
|
+
this.#handlers.close?.(code, `${reason ? `${reason} ` : ""}Could not resume the voice session after ${this.#resumeAttempts} attempts.`);
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
this.destroy();
|
|
1639
|
+
const hint = code === VoiceCloseCodes2.DaveProtocolRequired && this.#maxDave === 0 ? " Discord requires end-to-end encryption (DAVE): install @snazzah/davey." : "";
|
|
1640
|
+
this.#handlers.close?.(code, `${reason}${hint}`.trim());
|
|
1641
|
+
}
|
|
1642
|
+
#receive(packet) {
|
|
1643
|
+
const cipher = this.#cipher;
|
|
1644
|
+
if (!cipher)
|
|
1645
|
+
return;
|
|
1646
|
+
const header = parseRtpHeader2(packet);
|
|
1647
|
+
if (!header || header.payloadType !== RTP_OPUS_PAYLOAD_TYPE2)
|
|
1648
|
+
return;
|
|
1649
|
+
const userId = this.#users.get(header.ssrc);
|
|
1650
|
+
if (!userId)
|
|
1651
|
+
return;
|
|
1652
|
+
const opened = cipher.open(packet);
|
|
1653
|
+
if (!opened)
|
|
1654
|
+
return;
|
|
1655
|
+
const frame = this.#dave ? this.#dave.decrypt(opened.payload, userId) : opened.payload;
|
|
1656
|
+
if (frame)
|
|
1657
|
+
this.#handlers.audio?.(userId, frame);
|
|
1658
|
+
}
|
|
1659
|
+
setSpeaking(speaking, flags = 1) {
|
|
1660
|
+
if (this.state !== "ready" || this.#speaking === speaking)
|
|
1661
|
+
return;
|
|
1662
|
+
this.#speaking = speaking;
|
|
1663
|
+
this.#gateway.sendJSON({
|
|
1664
|
+
op: VoiceOpcodes2.Speaking,
|
|
1665
|
+
d: { speaking: speaking ? flags : 0, delay: 0, ssrc: this.ssrc }
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
sendFrame(frame) {
|
|
1669
|
+
const cipher = this.#cipher;
|
|
1670
|
+
const udp = this.#udp;
|
|
1671
|
+
if (this.state !== "ready" || !cipher || !udp)
|
|
1672
|
+
return false;
|
|
1673
|
+
this.setSpeaking(true);
|
|
1674
|
+
const header = rtpHeader2(this.#sequence, this.#timestamp, this.ssrc);
|
|
1675
|
+
this.#sequence = this.#sequence + 1 & 65535;
|
|
1676
|
+
this.#timestamp = this.#timestamp + FRAME_SAMPLES2 >>> 0;
|
|
1677
|
+
let payload = frame;
|
|
1678
|
+
if (this.#dave)
|
|
1679
|
+
try {
|
|
1680
|
+
payload = this.#dave.encrypt(frame);
|
|
1681
|
+
this.#encryptFailing = false;
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
if (!this.#encryptFailing)
|
|
1684
|
+
this.#error(new VoiceError2(`DAVE could not encrypt a frame; frames are dropped until it can: ${error instanceof Error ? error.message : String(error)}`, { cause: error }));
|
|
1685
|
+
this.#encryptFailing = true;
|
|
1686
|
+
return false;
|
|
1687
|
+
}
|
|
1688
|
+
if (!payload)
|
|
1689
|
+
return false;
|
|
1690
|
+
return udp.send(cipher.seal(header, payload));
|
|
1691
|
+
}
|
|
1692
|
+
destroy() {
|
|
1693
|
+
if (this.state === "closed")
|
|
1694
|
+
return;
|
|
1695
|
+
this.#setState("closed");
|
|
1696
|
+
if (this.#resumeTimer)
|
|
1697
|
+
clearTimeout(this.#resumeTimer);
|
|
1698
|
+
this.#resumeTimer = undefined;
|
|
1699
|
+
this.#gateway.close(1000, "Voice session ended.");
|
|
1700
|
+
this.#udp?.close();
|
|
1701
|
+
this.#udp = undefined;
|
|
1702
|
+
this.#dave?.destroy();
|
|
1703
|
+
this.#dave = undefined;
|
|
1704
|
+
this.#cipher = undefined;
|
|
1705
|
+
this.#users.clear();
|
|
1706
|
+
this.#connected.clear();
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// packages/voice/src/emitter.ts
|
|
1711
|
+
class Emitter {
|
|
1712
|
+
#listeners = new Map;
|
|
1713
|
+
on(event, listener) {
|
|
1714
|
+
if (typeof listener !== "function")
|
|
1715
|
+
throw new TypeError("Listener must be a function.");
|
|
1716
|
+
let set = this.#listeners.get(event);
|
|
1717
|
+
if (!set)
|
|
1718
|
+
this.#listeners.set(event, set = new Set);
|
|
1719
|
+
set.add(listener);
|
|
1720
|
+
return this;
|
|
1721
|
+
}
|
|
1722
|
+
off(event, listener) {
|
|
1723
|
+
this.#listeners.get(event)?.delete(listener);
|
|
1724
|
+
return this;
|
|
1725
|
+
}
|
|
1726
|
+
emit(event, ...args) {
|
|
1727
|
+
const set = this.#listeners.get(event);
|
|
1728
|
+
if (!set)
|
|
1729
|
+
return;
|
|
1730
|
+
for (const listener of [...set])
|
|
1731
|
+
try {
|
|
1732
|
+
listener(...args);
|
|
1733
|
+
} catch {}
|
|
1734
|
+
}
|
|
1735
|
+
removeAllListeners() {
|
|
1736
|
+
this.#listeners.clear();
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// packages/voice/src/ogg.ts
|
|
1741
|
+
var CAPTURE = [79, 103, 103, 83];
|
|
1742
|
+
var OPUS_HEAD = "OpusHead";
|
|
1743
|
+
var OPUS_TAGS = "OpusTags";
|
|
1744
|
+
var MAX_PAGE = 27 + 255 + 255 * 255;
|
|
1745
|
+
var MAX_PACKET = 64 * 1024;
|
|
1746
|
+
function startsWith(bytes, text) {
|
|
1747
|
+
if (bytes.length < text.length)
|
|
1748
|
+
return false;
|
|
1749
|
+
for (let i = 0;i < text.length; i++)
|
|
1750
|
+
if (bytes[i] !== text.charCodeAt(i))
|
|
1751
|
+
return false;
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
function findCapture(buffer, from) {
|
|
1755
|
+
for (let i = from;i + 4 <= buffer.length; i++)
|
|
1756
|
+
if (buffer[i] === CAPTURE[0] && buffer[i + 1] === CAPTURE[1] && buffer[i + 2] === CAPTURE[2] && buffer[i + 3] === CAPTURE[3])
|
|
1757
|
+
return i;
|
|
1758
|
+
return -1;
|
|
1759
|
+
}
|
|
1760
|
+
async function* demuxOggOpus2(chunks) {
|
|
1761
|
+
let buffer = new Uint8Array(0);
|
|
1762
|
+
let partial = [];
|
|
1763
|
+
let partialSize = 0;
|
|
1764
|
+
let sawHead = false;
|
|
1765
|
+
let skipped = 0;
|
|
1766
|
+
const take = (bytes) => {
|
|
1767
|
+
if (partial.length === 0)
|
|
1768
|
+
return bytes;
|
|
1769
|
+
partial.push(bytes);
|
|
1770
|
+
const out = new Uint8Array(partialSize + bytes.length);
|
|
1771
|
+
let offset = 0;
|
|
1772
|
+
for (const part of partial) {
|
|
1773
|
+
out.set(part, offset);
|
|
1774
|
+
offset += part.length;
|
|
1775
|
+
}
|
|
1776
|
+
partial = [];
|
|
1777
|
+
partialSize = 0;
|
|
1778
|
+
return out;
|
|
1779
|
+
};
|
|
1780
|
+
for await (const chunk of chunks) {
|
|
1781
|
+
const merged = new Uint8Array(buffer.length + chunk.length);
|
|
1782
|
+
merged.set(buffer, 0);
|
|
1783
|
+
merged.set(chunk, buffer.length);
|
|
1784
|
+
buffer = merged;
|
|
1785
|
+
let offset = 0;
|
|
1786
|
+
for (;; ) {
|
|
1787
|
+
const start = findCapture(buffer, offset);
|
|
1788
|
+
if (start === -1) {
|
|
1789
|
+
offset = Math.max(offset, buffer.length - 3);
|
|
1790
|
+
break;
|
|
1791
|
+
}
|
|
1792
|
+
if (buffer.length < start + 27) {
|
|
1793
|
+
offset = start;
|
|
1794
|
+
break;
|
|
1795
|
+
}
|
|
1796
|
+
const segments = buffer[start + 26];
|
|
1797
|
+
const dataStart = start + 27 + segments;
|
|
1798
|
+
if (buffer.length < dataStart) {
|
|
1799
|
+
offset = start;
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
let size = 0;
|
|
1803
|
+
for (let i = 0;i < segments; i++)
|
|
1804
|
+
size += buffer[start + 27 + i];
|
|
1805
|
+
if (buffer.length < dataStart + size) {
|
|
1806
|
+
offset = start;
|
|
1807
|
+
break;
|
|
1808
|
+
}
|
|
1809
|
+
let cursor = dataStart;
|
|
1810
|
+
for (let i = 0;i < segments; i++) {
|
|
1811
|
+
const length = buffer[start + 27 + i];
|
|
1812
|
+
const segment = buffer.slice(cursor, cursor + length);
|
|
1813
|
+
cursor += length;
|
|
1814
|
+
if (length === 255) {
|
|
1815
|
+
partial.push(segment);
|
|
1816
|
+
partialSize += length;
|
|
1817
|
+
if (partialSize > MAX_PACKET)
|
|
1818
|
+
throw new VoiceError2("An Ogg packet is too large to be Opus audio.");
|
|
1819
|
+
continue;
|
|
1820
|
+
}
|
|
1821
|
+
const packet = take(segment);
|
|
1822
|
+
if (!packet || packet.length === 0)
|
|
1823
|
+
continue;
|
|
1824
|
+
if (startsWith(packet, OPUS_HEAD)) {
|
|
1825
|
+
const channels = packet[9] ?? 0;
|
|
1826
|
+
if (channels < 1 || channels > 2)
|
|
1827
|
+
throw new VoiceError2(`Unsupported Opus channel count: ${channels}.`);
|
|
1828
|
+
sawHead = true;
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (startsWith(packet, OPUS_TAGS))
|
|
1832
|
+
continue;
|
|
1833
|
+
if (!sawHead)
|
|
1834
|
+
throw new VoiceError2("The Ogg stream does not carry Opus audio.");
|
|
1835
|
+
yield packet;
|
|
1836
|
+
}
|
|
1837
|
+
offset = cursor;
|
|
1838
|
+
}
|
|
1839
|
+
if (!sawHead && findCapture(buffer, 0) === -1)
|
|
1840
|
+
skipped += offset;
|
|
1841
|
+
buffer = buffer.slice(offset);
|
|
1842
|
+
if (buffer.length > MAX_PAGE || skipped > MAX_PAGE)
|
|
1843
|
+
throw new VoiceError2("The input is not an Ogg stream.");
|
|
1844
|
+
}
|
|
1845
|
+
if (!sawHead && (skipped > 0 || buffer.length > 0))
|
|
1846
|
+
throw new VoiceError2("The input is not an Ogg/Opus stream.");
|
|
1847
|
+
}
|
|
1848
|
+
var CRC_TABLE = (() => {
|
|
1849
|
+
const table = new Uint32Array(256);
|
|
1850
|
+
for (let i = 0;i < 256; i++) {
|
|
1851
|
+
let r = i << 24;
|
|
1852
|
+
for (let j = 0;j < 8; j++)
|
|
1853
|
+
r = r & 2147483648 ? r << 1 ^ 79764919 : r << 1;
|
|
1854
|
+
table[i] = r >>> 0;
|
|
1855
|
+
}
|
|
1856
|
+
return table;
|
|
1857
|
+
})();
|
|
1858
|
+
function oggCrc(bytes) {
|
|
1859
|
+
let crc = 0;
|
|
1860
|
+
for (const byte of bytes)
|
|
1861
|
+
crc = (crc << 8 ^ CRC_TABLE[(crc >>> 24 ^ byte) & 255]) >>> 0;
|
|
1862
|
+
return crc;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
class OggOpusWriter2 {
|
|
1866
|
+
#serial;
|
|
1867
|
+
#sequence = 0;
|
|
1868
|
+
#granule = 0n;
|
|
1869
|
+
#packets = [];
|
|
1870
|
+
#lacing = 0;
|
|
1871
|
+
#ended = false;
|
|
1872
|
+
constructor(serial = Math.random() * 4294967296 >>> 0) {
|
|
1873
|
+
this.#serial = serial;
|
|
1874
|
+
}
|
|
1875
|
+
header(vendor = "lunibee") {
|
|
1876
|
+
const head = new Uint8Array(19);
|
|
1877
|
+
head.set(new TextEncoder().encode(OPUS_HEAD));
|
|
1878
|
+
const view = new DataView(head.buffer);
|
|
1879
|
+
head[8] = 1;
|
|
1880
|
+
head[9] = 2;
|
|
1881
|
+
view.setUint16(10, 0, true);
|
|
1882
|
+
view.setUint32(12, 48000, true);
|
|
1883
|
+
const vendorBytes = new TextEncoder().encode(vendor);
|
|
1884
|
+
const tags = new Uint8Array(8 + 4 + vendorBytes.length + 4);
|
|
1885
|
+
tags.set(new TextEncoder().encode(OPUS_TAGS));
|
|
1886
|
+
const tagsView = new DataView(tags.buffer);
|
|
1887
|
+
tagsView.setUint32(8, vendorBytes.length, true);
|
|
1888
|
+
tags.set(vendorBytes, 12);
|
|
1889
|
+
return concat([
|
|
1890
|
+
this.#page([head], 2, 0n),
|
|
1891
|
+
this.#page([tags], 0, 0n)
|
|
1892
|
+
]);
|
|
1893
|
+
}
|
|
1894
|
+
write(packet) {
|
|
1895
|
+
if (this.#ended)
|
|
1896
|
+
throw new VoiceError2("The Ogg stream has ended.");
|
|
1897
|
+
const lacing = Math.floor(packet.length / 255) + 1;
|
|
1898
|
+
if (lacing > 255)
|
|
1899
|
+
throw new VoiceError2("An Opus packet is too large for one page.");
|
|
1900
|
+
const before = this.#lacing + lacing > 255 ? this.flush() : undefined;
|
|
1901
|
+
this.#packets.push(packet);
|
|
1902
|
+
this.#lacing += lacing;
|
|
1903
|
+
this.#granule += 960n;
|
|
1904
|
+
const after = this.#packets.length >= 50 ? this.flush() : new Uint8Array(0);
|
|
1905
|
+
return before ? concat([before, after]) : after;
|
|
1906
|
+
}
|
|
1907
|
+
flush() {
|
|
1908
|
+
if (this.#packets.length === 0)
|
|
1909
|
+
return new Uint8Array(0);
|
|
1910
|
+
const packets = this.#packets;
|
|
1911
|
+
this.#packets = [];
|
|
1912
|
+
this.#lacing = 0;
|
|
1913
|
+
return this.#page(packets, 0, this.#granule);
|
|
1914
|
+
}
|
|
1915
|
+
end() {
|
|
1916
|
+
if (this.#ended)
|
|
1917
|
+
return new Uint8Array(0);
|
|
1918
|
+
this.#ended = true;
|
|
1919
|
+
const packets = this.#packets;
|
|
1920
|
+
this.#packets = [];
|
|
1921
|
+
this.#lacing = 0;
|
|
1922
|
+
return this.#page(packets, 4, this.#granule);
|
|
1923
|
+
}
|
|
1924
|
+
#page(packets, flags, granule) {
|
|
1925
|
+
const lacing = [];
|
|
1926
|
+
for (const packet of packets) {
|
|
1927
|
+
let left = packet.length;
|
|
1928
|
+
while (left >= 255) {
|
|
1929
|
+
lacing.push(255);
|
|
1930
|
+
left -= 255;
|
|
1931
|
+
}
|
|
1932
|
+
lacing.push(left);
|
|
1933
|
+
}
|
|
1934
|
+
const body = concat(packets);
|
|
1935
|
+
const page = new Uint8Array(27 + lacing.length + body.length);
|
|
1936
|
+
const view = new DataView(page.buffer);
|
|
1937
|
+
page.set(CAPTURE);
|
|
1938
|
+
page[5] = flags;
|
|
1939
|
+
view.setBigUint64(6, granule, true);
|
|
1940
|
+
view.setUint32(14, this.#serial, true);
|
|
1941
|
+
view.setUint32(18, this.#sequence++, true);
|
|
1942
|
+
page[26] = lacing.length;
|
|
1943
|
+
page.set(lacing, 27);
|
|
1944
|
+
page.set(body, 27 + lacing.length);
|
|
1945
|
+
view.setUint32(22, oggCrc(page), true);
|
|
1946
|
+
return page;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function concat(parts) {
|
|
1950
|
+
let length = 0;
|
|
1951
|
+
for (const part of parts)
|
|
1952
|
+
length += part.length;
|
|
1953
|
+
const out = new Uint8Array(length);
|
|
1954
|
+
let offset = 0;
|
|
1955
|
+
for (const part of parts) {
|
|
1956
|
+
out.set(part, offset);
|
|
1957
|
+
offset += part.length;
|
|
1958
|
+
}
|
|
1959
|
+
return out;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// packages/voice/src/receiver.ts
|
|
1963
|
+
var GAP_MS = 60;
|
|
1964
|
+
var MAX_GAP_FRAMES = 3600000 / FRAME_DURATION_MS2;
|
|
1965
|
+
function isSilence(frame) {
|
|
1966
|
+
return frame.length === SILENCE_FRAME2.length && frame.every((byte, i) => byte === SILENCE_FRAME2[i]);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
class VoiceReceiver2 {
|
|
1970
|
+
#subscriptions = new Map;
|
|
1971
|
+
#ssrcs = new Map;
|
|
1972
|
+
#speaking = new Map;
|
|
1973
|
+
#listeners = new Map;
|
|
1974
|
+
ssrcOf(userId) {
|
|
1975
|
+
return this.#ssrcs.get(userId);
|
|
1976
|
+
}
|
|
1977
|
+
get speaking() {
|
|
1978
|
+
return new Set(this.#speaking.keys());
|
|
1979
|
+
}
|
|
1980
|
+
on(event, listener) {
|
|
1981
|
+
let set = this.#listeners.get(event);
|
|
1982
|
+
if (!set)
|
|
1983
|
+
this.#listeners.set(event, set = new Set);
|
|
1984
|
+
set.add(listener);
|
|
1985
|
+
return this;
|
|
1986
|
+
}
|
|
1987
|
+
off(event, listener) {
|
|
1988
|
+
this.#listeners.get(event)?.delete(listener);
|
|
1989
|
+
return this;
|
|
1990
|
+
}
|
|
1991
|
+
#emit(event, userId) {
|
|
1992
|
+
for (const listener of this.#listeners.get(event) ?? [])
|
|
1993
|
+
try {
|
|
1994
|
+
listener(userId);
|
|
1995
|
+
} catch {}
|
|
1996
|
+
}
|
|
1997
|
+
subscribe(userId, options = {}) {
|
|
1998
|
+
const end = options.end ?? { behavior: "silence" };
|
|
1999
|
+
let subscription;
|
|
2000
|
+
return new ReadableStream({
|
|
2001
|
+
start: (controller) => {
|
|
2002
|
+
subscription = {
|
|
2003
|
+
end,
|
|
2004
|
+
deliver: (frame) => {
|
|
2005
|
+
if ((controller.desiredSize ?? 1) > 0)
|
|
2006
|
+
controller.enqueue(frame);
|
|
2007
|
+
},
|
|
2008
|
+
close: () => controller.close()
|
|
2009
|
+
};
|
|
2010
|
+
this.#add(userId, subscription);
|
|
2011
|
+
},
|
|
2012
|
+
cancel: () => {
|
|
2013
|
+
if (subscription)
|
|
2014
|
+
this.#remove(userId, subscription);
|
|
2015
|
+
}
|
|
2016
|
+
}, { highWaterMark: options.highWaterMark ?? 50 });
|
|
2017
|
+
}
|
|
2018
|
+
record(userId, options = {}) {
|
|
2019
|
+
const end = options.end ?? { behavior: "manual" };
|
|
2020
|
+
const writer = new OggOpusWriter2;
|
|
2021
|
+
let subscription;
|
|
2022
|
+
return new ReadableStream({
|
|
2023
|
+
start: (controller) => {
|
|
2024
|
+
const emit = (bytes) => {
|
|
2025
|
+
if (bytes.length > 0)
|
|
2026
|
+
controller.enqueue(bytes);
|
|
2027
|
+
};
|
|
2028
|
+
emit(writer.header());
|
|
2029
|
+
let last = performance.now();
|
|
2030
|
+
subscription = {
|
|
2031
|
+
end,
|
|
2032
|
+
deliver: (frame) => {
|
|
2033
|
+
if ((controller.desiredSize ?? 1) <= 0)
|
|
2034
|
+
return;
|
|
2035
|
+
const now = performance.now();
|
|
2036
|
+
const gap = now - last;
|
|
2037
|
+
if (gap > GAP_MS) {
|
|
2038
|
+
const missing = Math.min(Math.round(gap / FRAME_DURATION_MS2) - 1, MAX_GAP_FRAMES);
|
|
2039
|
+
for (let i = 0;i < missing && (controller.desiredSize ?? 1) > 0; i++)
|
|
2040
|
+
emit(writer.write(SILENCE_FRAME2));
|
|
2041
|
+
}
|
|
2042
|
+
last = now;
|
|
2043
|
+
emit(writer.write(frame));
|
|
2044
|
+
},
|
|
2045
|
+
close: () => {
|
|
2046
|
+
emit(writer.end());
|
|
2047
|
+
controller.close();
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
this.#add(userId, subscription);
|
|
2051
|
+
},
|
|
2052
|
+
cancel: () => {
|
|
2053
|
+
if (subscription)
|
|
2054
|
+
this.#remove(userId, subscription);
|
|
2055
|
+
}
|
|
2056
|
+
}, new ByteLengthQueuingStrategy({
|
|
2057
|
+
highWaterMark: options.highWaterMark ?? 1024 * 1024
|
|
2058
|
+
}));
|
|
2059
|
+
}
|
|
2060
|
+
#add(userId, subscription) {
|
|
2061
|
+
let set = this.#subscriptions.get(userId);
|
|
2062
|
+
if (!set)
|
|
2063
|
+
this.#subscriptions.set(userId, set = new Set);
|
|
2064
|
+
set.add(subscription);
|
|
2065
|
+
this.#arm(userId, subscription);
|
|
2066
|
+
}
|
|
2067
|
+
unsubscribe(userId) {
|
|
2068
|
+
const set = this.#subscriptions.get(userId);
|
|
2069
|
+
if (!set)
|
|
2070
|
+
return false;
|
|
2071
|
+
for (const subscription of [...set])
|
|
2072
|
+
this.#close(userId, subscription);
|
|
2073
|
+
return true;
|
|
2074
|
+
}
|
|
2075
|
+
#arm(userId, subscription) {
|
|
2076
|
+
if (subscription.end.behavior !== "silence")
|
|
2077
|
+
return;
|
|
2078
|
+
if (subscription.timer)
|
|
2079
|
+
clearTimeout(subscription.timer);
|
|
2080
|
+
subscription.timer = setTimeout(() => this.#close(userId, subscription), subscription.end.duration ?? 1000);
|
|
2081
|
+
}
|
|
2082
|
+
#remove(userId, subscription) {
|
|
2083
|
+
if (subscription.timer)
|
|
2084
|
+
clearTimeout(subscription.timer);
|
|
2085
|
+
const set = this.#subscriptions.get(userId);
|
|
2086
|
+
set?.delete(subscription);
|
|
2087
|
+
if (set?.size === 0)
|
|
2088
|
+
this.#subscriptions.delete(userId);
|
|
2089
|
+
}
|
|
2090
|
+
#close(userId, subscription) {
|
|
2091
|
+
this.#remove(userId, subscription);
|
|
2092
|
+
try {
|
|
2093
|
+
subscription.close();
|
|
2094
|
+
} catch {}
|
|
2095
|
+
}
|
|
2096
|
+
mapUser(userId, ssrc) {
|
|
2097
|
+
this.#ssrcs.set(userId, ssrc);
|
|
2098
|
+
}
|
|
2099
|
+
forgetUser(userId) {
|
|
2100
|
+
this.#ssrcs.delete(userId);
|
|
2101
|
+
this.unsubscribe(userId);
|
|
2102
|
+
}
|
|
2103
|
+
push(userId, frame) {
|
|
2104
|
+
const silent = isSilence(frame);
|
|
2105
|
+
if (!silent)
|
|
2106
|
+
this.#markSpeaking(userId);
|
|
2107
|
+
const set = this.#subscriptions.get(userId);
|
|
2108
|
+
if (!set)
|
|
2109
|
+
return;
|
|
2110
|
+
for (const subscription of set) {
|
|
2111
|
+
if (!silent)
|
|
2112
|
+
this.#arm(userId, subscription);
|
|
2113
|
+
try {
|
|
2114
|
+
subscription.deliver(frame);
|
|
2115
|
+
} catch {}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
#markSpeaking(userId) {
|
|
2119
|
+
const timer = this.#speaking.get(userId);
|
|
2120
|
+
if (timer)
|
|
2121
|
+
clearTimeout(timer);
|
|
2122
|
+
else
|
|
2123
|
+
this.#emit("start", userId);
|
|
2124
|
+
this.#speaking.set(userId, setTimeout(() => {
|
|
2125
|
+
this.#speaking.delete(userId);
|
|
2126
|
+
this.#emit("end", userId);
|
|
2127
|
+
}, 100));
|
|
2128
|
+
}
|
|
2129
|
+
close() {
|
|
2130
|
+
for (const [userId, set] of [...this.#subscriptions])
|
|
2131
|
+
for (const subscription of [...set])
|
|
2132
|
+
this.#close(userId, subscription);
|
|
2133
|
+
for (const timer of this.#speaking.values())
|
|
2134
|
+
clearTimeout(timer);
|
|
2135
|
+
this.#speaking.clear();
|
|
2136
|
+
this.#ssrcs.clear();
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// packages/voice/src/connection.ts
|
|
2141
|
+
var VoiceConnectionStatus2 = {
|
|
2142
|
+
Signalling: "signalling",
|
|
2143
|
+
Connecting: "connecting",
|
|
2144
|
+
Ready: "ready",
|
|
2145
|
+
Disconnected: "disconnected",
|
|
2146
|
+
Destroyed: "destroyed"
|
|
2147
|
+
};
|
|
2148
|
+
|
|
2149
|
+
class VoiceConnection2 extends Emitter {
|
|
2150
|
+
status = "signalling";
|
|
2151
|
+
guildId;
|
|
2152
|
+
channelId;
|
|
2153
|
+
selfMute;
|
|
2154
|
+
selfDeaf;
|
|
2155
|
+
disconnectReason;
|
|
2156
|
+
receiver = new VoiceReceiver2;
|
|
2157
|
+
#options;
|
|
2158
|
+
#adapter;
|
|
2159
|
+
#networking;
|
|
2160
|
+
#sessionId;
|
|
2161
|
+
#sessionChannel;
|
|
2162
|
+
#userId;
|
|
2163
|
+
#token;
|
|
2164
|
+
#endpoint;
|
|
2165
|
+
#timer;
|
|
2166
|
+
#leaving = false;
|
|
2167
|
+
#subscription;
|
|
2168
|
+
#sink;
|
|
2169
|
+
constructor(options) {
|
|
2170
|
+
super();
|
|
2171
|
+
if (!options.guildId?.trim())
|
|
2172
|
+
throw new TypeError("A guild ID is required.");
|
|
2173
|
+
if (!options.channelId?.trim())
|
|
2174
|
+
throw new TypeError("A voice channel ID is required.");
|
|
2175
|
+
if (typeof options.adapterCreator !== "function")
|
|
2176
|
+
throw new TypeError("An adapterCreator is required.");
|
|
2177
|
+
this.#options = { ...options };
|
|
2178
|
+
this.guildId = options.guildId;
|
|
2179
|
+
this.channelId = options.channelId;
|
|
2180
|
+
this.selfMute = options.selfMute ?? false;
|
|
2181
|
+
this.selfDeaf = options.selfDeaf ?? false;
|
|
2182
|
+
const self = this;
|
|
2183
|
+
this.#sink = {
|
|
2184
|
+
get ready() {
|
|
2185
|
+
return self.status === "ready";
|
|
2186
|
+
},
|
|
2187
|
+
sendFrame: (frame) => this.#networking?.sendFrame(frame) ?? false,
|
|
2188
|
+
setSpeaking: (speaking) => this.#networking?.setSpeaking(speaking)
|
|
2189
|
+
};
|
|
2190
|
+
this.#adapter = options.adapterCreator({
|
|
2191
|
+
onVoiceStateUpdate: (data) => this.#onState(data),
|
|
2192
|
+
onVoiceServerUpdate: (data) => this.#onServer(data),
|
|
2193
|
+
destroy: () => this.destroy(false)
|
|
2194
|
+
});
|
|
2195
|
+
this.#signal();
|
|
2196
|
+
}
|
|
2197
|
+
get ping() {
|
|
2198
|
+
return this.#networking?.ping ?? -1;
|
|
2199
|
+
}
|
|
2200
|
+
get voicePrivacyCode() {
|
|
2201
|
+
return this.#networking?.dave?.voicePrivacyCode ?? null;
|
|
2202
|
+
}
|
|
2203
|
+
get endToEndEncrypted() {
|
|
2204
|
+
return this.#networking?.dave?.active ?? false;
|
|
2205
|
+
}
|
|
2206
|
+
get subscription() {
|
|
2207
|
+
return this.#subscription;
|
|
2208
|
+
}
|
|
2209
|
+
#debug(message) {
|
|
2210
|
+
this.#options.debug?.(message);
|
|
2211
|
+
this.emit("debug", message);
|
|
2212
|
+
}
|
|
2213
|
+
#setStatus(next, reason) {
|
|
2214
|
+
if (this.status === "destroyed")
|
|
2215
|
+
return;
|
|
2216
|
+
this.disconnectReason = next === "disconnected" ? reason : undefined;
|
|
2217
|
+
if (next === "ready" || next === "disconnected" || next === "destroyed")
|
|
2218
|
+
this.#clearTimer();
|
|
2219
|
+
if (this.status === next)
|
|
2220
|
+
return;
|
|
2221
|
+
const previous = this.status;
|
|
2222
|
+
this.status = next;
|
|
2223
|
+
this.emit("stateChange", next, previous);
|
|
2224
|
+
this.#subscription?.player.refresh();
|
|
2225
|
+
}
|
|
2226
|
+
#clearTimer() {
|
|
2227
|
+
if (this.#timer)
|
|
2228
|
+
clearTimeout(this.#timer);
|
|
2229
|
+
this.#timer = undefined;
|
|
2230
|
+
}
|
|
2231
|
+
#signal() {
|
|
2232
|
+
this.#leaving = false;
|
|
2233
|
+
this.#setStatus("signalling");
|
|
2234
|
+
this.#clearTimer();
|
|
2235
|
+
const timeout = this.#options.timeout ?? 15000;
|
|
2236
|
+
this.#timer = setTimeout(() => {
|
|
2237
|
+
this.#timer = undefined;
|
|
2238
|
+
if (this.status !== "signalling" && this.status !== "connecting")
|
|
2239
|
+
return;
|
|
2240
|
+
this.#networking?.destroy();
|
|
2241
|
+
this.#networking = undefined;
|
|
2242
|
+
this.#setStatus("disconnected", {
|
|
2243
|
+
reason: "timeout",
|
|
2244
|
+
message: `No voice connection within ${timeout} ms.`
|
|
2245
|
+
});
|
|
2246
|
+
this.emit("error", new VoiceError2(`Joining the voice channel timed out after ${timeout} ms.`));
|
|
2247
|
+
}, timeout);
|
|
2248
|
+
this.#timer.unref?.();
|
|
2249
|
+
if (this.#send(this.channelId))
|
|
2250
|
+
return true;
|
|
2251
|
+
this.#setStatus("disconnected", {
|
|
2252
|
+
reason: "unsent",
|
|
2253
|
+
message: "The voice state update could not be sent: the client is not connected, or the guild is on another shard."
|
|
2254
|
+
});
|
|
2255
|
+
return false;
|
|
2256
|
+
}
|
|
2257
|
+
#send(channelId) {
|
|
2258
|
+
try {
|
|
2259
|
+
return this.#adapter.sendPayload({
|
|
2260
|
+
op: 4,
|
|
2261
|
+
d: {
|
|
2262
|
+
guild_id: this.guildId,
|
|
2263
|
+
channel_id: channelId,
|
|
2264
|
+
self_mute: this.selfMute,
|
|
2265
|
+
self_deaf: this.selfDeaf
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
this.emit("error", error instanceof Error ? error : new VoiceError2(String(error)));
|
|
2270
|
+
return false;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
#onState(data) {
|
|
2274
|
+
if (this.status === "destroyed")
|
|
2275
|
+
return;
|
|
2276
|
+
if (data.channel_id === null) {
|
|
2277
|
+
this.#networking?.destroy();
|
|
2278
|
+
this.#networking = undefined;
|
|
2279
|
+
this.receiver.close();
|
|
2280
|
+
this.#sessionChannel = undefined;
|
|
2281
|
+
this.#setStatus("disconnected", {
|
|
2282
|
+
reason: this.#leaving ? "left" : "removed"
|
|
2283
|
+
});
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
const moved = data.channel_id !== this.#sessionChannel;
|
|
2287
|
+
const newSession = data.session_id !== this.#sessionId;
|
|
2288
|
+
this.#sessionId = data.session_id;
|
|
2289
|
+
this.#userId = data.user_id;
|
|
2290
|
+
this.channelId = data.channel_id;
|
|
2291
|
+
if (moved || newSession || !this.#networking)
|
|
2292
|
+
this.#connect();
|
|
2293
|
+
else if (this.status === "signalling" && this.#networking.state === "ready")
|
|
2294
|
+
this.#setStatus("ready");
|
|
2295
|
+
}
|
|
2296
|
+
#onServer(data) {
|
|
2297
|
+
if (this.status === "destroyed")
|
|
2298
|
+
return;
|
|
2299
|
+
if (data.endpoint === null) {
|
|
2300
|
+
this.#networking?.destroy();
|
|
2301
|
+
this.#networking = undefined;
|
|
2302
|
+
if (this.status === "ready")
|
|
2303
|
+
this.#setStatus("connecting");
|
|
2304
|
+
if (this.status === "connecting" && !this.#timer)
|
|
2305
|
+
this.#armReconnectTimeout();
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
this.#token = data.token;
|
|
2309
|
+
this.#endpoint = data.endpoint;
|
|
2310
|
+
this.#connect();
|
|
2311
|
+
}
|
|
2312
|
+
#connect() {
|
|
2313
|
+
const { channelId } = this;
|
|
2314
|
+
const sessionId = this.#sessionId;
|
|
2315
|
+
const userId = this.#userId;
|
|
2316
|
+
const token = this.#token;
|
|
2317
|
+
const endpoint = this.#endpoint;
|
|
2318
|
+
if (!sessionId || !userId || !token || !endpoint)
|
|
2319
|
+
return;
|
|
2320
|
+
this.#sessionChannel = channelId;
|
|
2321
|
+
this.#networking?.destroy();
|
|
2322
|
+
this.#setStatus("connecting");
|
|
2323
|
+
if (!this.#timer)
|
|
2324
|
+
this.#armReconnectTimeout();
|
|
2325
|
+
this.#debug(`Connecting to voice server ${endpoint}.`);
|
|
2326
|
+
const networking = new VoiceNetworking2({
|
|
2327
|
+
guildId: this.guildId,
|
|
2328
|
+
channelId,
|
|
2329
|
+
userId,
|
|
2330
|
+
sessionId,
|
|
2331
|
+
token,
|
|
2332
|
+
endpoint
|
|
2333
|
+
}, {
|
|
2334
|
+
state: (state) => {
|
|
2335
|
+
if (this.#networking !== networking)
|
|
2336
|
+
return;
|
|
2337
|
+
if (state === "ready")
|
|
2338
|
+
this.#setStatus("ready");
|
|
2339
|
+
else if (state === "resuming") {
|
|
2340
|
+
this.#setStatus("connecting");
|
|
2341
|
+
if (!this.#timer)
|
|
2342
|
+
this.#armReconnectTimeout();
|
|
2343
|
+
}
|
|
2344
|
+
},
|
|
2345
|
+
close: (code, reason) => {
|
|
2346
|
+
if (this.#networking !== networking)
|
|
2347
|
+
return;
|
|
2348
|
+
this.#networking = undefined;
|
|
2349
|
+
this.#setStatus("disconnected", {
|
|
2350
|
+
reason: "closed",
|
|
2351
|
+
code,
|
|
2352
|
+
message: reason
|
|
2353
|
+
});
|
|
2354
|
+
if (reason)
|
|
2355
|
+
this.emit("error", new VoiceError2(`Voice server closed the session (${code}): ${reason}`));
|
|
2356
|
+
},
|
|
2357
|
+
error: (error) => {
|
|
2358
|
+
if (this.#networking === networking)
|
|
2359
|
+
this.emit("error", error);
|
|
2360
|
+
},
|
|
2361
|
+
audio: (user, frame) => this.receiver.push(user, frame),
|
|
2362
|
+
speaking: (user, ssrc) => this.receiver.mapUser(user, ssrc),
|
|
2363
|
+
clientDisconnect: (user) => this.receiver.forgetUser(user)
|
|
2364
|
+
}, {
|
|
2365
|
+
dave: this.#options.dave,
|
|
2366
|
+
debug: (message) => this.#debug(message),
|
|
2367
|
+
...this.#options.networking
|
|
2368
|
+
});
|
|
2369
|
+
this.#networking = networking;
|
|
2370
|
+
}
|
|
2371
|
+
#armReconnectTimeout() {
|
|
2372
|
+
const timeout = this.#options.timeout ?? 15000;
|
|
2373
|
+
this.#timer = setTimeout(() => {
|
|
2374
|
+
this.#timer = undefined;
|
|
2375
|
+
if (this.status !== "connecting")
|
|
2376
|
+
return;
|
|
2377
|
+
this.#networking?.destroy();
|
|
2378
|
+
this.#networking = undefined;
|
|
2379
|
+
this.#setStatus("disconnected", {
|
|
2380
|
+
reason: "timeout",
|
|
2381
|
+
message: `No voice server connection within ${timeout} ms.`
|
|
2382
|
+
});
|
|
2383
|
+
}, timeout);
|
|
2384
|
+
this.#timer.unref?.();
|
|
2385
|
+
}
|
|
2386
|
+
rejoin(options = {}) {
|
|
2387
|
+
if (this.status === "destroyed")
|
|
2388
|
+
throw new VoiceError2("The voice connection was destroyed.");
|
|
2389
|
+
if (options.channelId !== undefined) {
|
|
2390
|
+
if (!options.channelId.trim())
|
|
2391
|
+
throw new TypeError("A voice channel ID is required.");
|
|
2392
|
+
this.channelId = options.channelId;
|
|
2393
|
+
}
|
|
2394
|
+
if (options.selfMute !== undefined)
|
|
2395
|
+
this.selfMute = options.selfMute;
|
|
2396
|
+
if (options.selfDeaf !== undefined)
|
|
2397
|
+
this.selfDeaf = options.selfDeaf;
|
|
2398
|
+
if (this.status === "ready" && (options.channelId === undefined || options.channelId === this.#sessionChannel))
|
|
2399
|
+
return this.#send(this.channelId);
|
|
2400
|
+
return this.#signal();
|
|
2401
|
+
}
|
|
2402
|
+
disconnect() {
|
|
2403
|
+
if (this.status === "destroyed")
|
|
2404
|
+
return false;
|
|
2405
|
+
this.#leaving = true;
|
|
2406
|
+
this.#clearTimer();
|
|
2407
|
+
const sent = this.#send(null);
|
|
2408
|
+
this.#networking?.destroy();
|
|
2409
|
+
this.#networking = undefined;
|
|
2410
|
+
this.receiver.close();
|
|
2411
|
+
this.#setStatus("disconnected", { reason: "left" });
|
|
2412
|
+
return sent;
|
|
2413
|
+
}
|
|
2414
|
+
destroy(leave = true) {
|
|
2415
|
+
if (this.status === "destroyed")
|
|
2416
|
+
return;
|
|
2417
|
+
this.#clearTimer();
|
|
2418
|
+
if (leave) {
|
|
2419
|
+
this.#leaving = true;
|
|
2420
|
+
this.#send(null);
|
|
2421
|
+
}
|
|
2422
|
+
this.#networking?.destroy();
|
|
2423
|
+
this.#networking = undefined;
|
|
2424
|
+
this.receiver.close();
|
|
2425
|
+
this.#subscription?.unsubscribe();
|
|
2426
|
+
const previous = this.status;
|
|
2427
|
+
this.status = "destroyed";
|
|
2428
|
+
this.disconnectReason = undefined;
|
|
2429
|
+
this.emit("stateChange", "destroyed", previous);
|
|
2430
|
+
try {
|
|
2431
|
+
this.#adapter.destroy();
|
|
2432
|
+
} catch {}
|
|
2433
|
+
this.removeAllListeners();
|
|
2434
|
+
}
|
|
2435
|
+
subscribe(player) {
|
|
2436
|
+
if (this.status === "destroyed")
|
|
2437
|
+
throw new VoiceError2("The voice connection was destroyed.");
|
|
2438
|
+
this.#subscription?.unsubscribe();
|
|
2439
|
+
const subscription = {
|
|
2440
|
+
connection: this,
|
|
2441
|
+
player,
|
|
2442
|
+
unsubscribe: () => {
|
|
2443
|
+
player.detach(this.#sink);
|
|
2444
|
+
if (this.#subscription === subscription)
|
|
2445
|
+
this.#subscription = undefined;
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
this.#subscription = subscription;
|
|
2449
|
+
player.attach(this.#sink);
|
|
2450
|
+
return subscription;
|
|
2451
|
+
}
|
|
2452
|
+
setSpeaking(speaking) {
|
|
2453
|
+
this.#networking?.setSpeaking(speaking);
|
|
2454
|
+
}
|
|
2455
|
+
playOpusPacket(frame) {
|
|
2456
|
+
return this.#networking?.sendFrame(frame) ?? false;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
var tracked = new Map;
|
|
2460
|
+
function joinVoiceChannel2(options) {
|
|
2461
|
+
const existing = tracked.get(options.guildId);
|
|
2462
|
+
if (existing && existing.status !== "destroyed") {
|
|
2463
|
+
existing.rejoin(options);
|
|
2464
|
+
return existing;
|
|
2465
|
+
}
|
|
2466
|
+
const connection = new VoiceConnection2(options);
|
|
2467
|
+
tracked.set(options.guildId, connection);
|
|
2468
|
+
connection.on("stateChange", (next) => {
|
|
2469
|
+
if (next === "destroyed" && tracked.get(connection.guildId) === connection)
|
|
2470
|
+
tracked.delete(connection.guildId);
|
|
2471
|
+
});
|
|
2472
|
+
return connection;
|
|
2473
|
+
}
|
|
2474
|
+
function getVoiceConnection2(guildId) {
|
|
2475
|
+
return tracked.get(guildId);
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
class VoiceManager2 {
|
|
2479
|
+
#client;
|
|
2480
|
+
#connections = new Map;
|
|
2481
|
+
#adapters = new Map;
|
|
2482
|
+
#defaults;
|
|
2483
|
+
constructor(client, defaults = {}) {
|
|
2484
|
+
this.#client = client;
|
|
2485
|
+
this.#defaults = defaults;
|
|
2486
|
+
}
|
|
2487
|
+
get connections() {
|
|
2488
|
+
return this.#connections;
|
|
2489
|
+
}
|
|
2490
|
+
get(guildId) {
|
|
2491
|
+
return this.#connections.get(guildId);
|
|
2492
|
+
}
|
|
2493
|
+
join(options) {
|
|
2494
|
+
const merged = { ...this.#defaults, ...options };
|
|
2495
|
+
if (this.#client.canSendVoiceState && !this.#client.canSendVoiceState(options.guildId))
|
|
2496
|
+
return Promise.reject(new VoiceError2("Cannot send the voice state update: the client is not connected, or the guild is on another shard."));
|
|
2497
|
+
let connection = this.#connections.get(options.guildId);
|
|
2498
|
+
if (connection && connection.status !== "destroyed")
|
|
2499
|
+
connection.rejoin(merged);
|
|
2500
|
+
else {
|
|
2501
|
+
connection = new VoiceConnection2({
|
|
2502
|
+
...merged,
|
|
2503
|
+
adapterCreator: (methods) => {
|
|
2504
|
+
this.#adapters.set(options.guildId, methods);
|
|
2505
|
+
return {
|
|
2506
|
+
sendPayload: (payload) => this.#client.sendVoiceState(payload.d.guild_id, payload.d.channel_id, {
|
|
2507
|
+
selfMute: payload.d.self_mute,
|
|
2508
|
+
selfDeaf: payload.d.self_deaf
|
|
2509
|
+
}),
|
|
2510
|
+
destroy: () => {
|
|
2511
|
+
if (this.#adapters.get(options.guildId) === methods)
|
|
2512
|
+
this.#adapters.delete(options.guildId);
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
});
|
|
2517
|
+
const created = connection;
|
|
2518
|
+
this.#connections.set(options.guildId, created);
|
|
2519
|
+
created.on("stateChange", (next) => {
|
|
2520
|
+
if (next === "destroyed" && this.#connections.get(created.guildId) === created)
|
|
2521
|
+
this.#connections.delete(created.guildId);
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
return waitReady(connection, merged.timeout ?? 15000);
|
|
2525
|
+
}
|
|
2526
|
+
leave(guildId) {
|
|
2527
|
+
const connection = this.#connections.get(guildId);
|
|
2528
|
+
if (!connection)
|
|
2529
|
+
return false;
|
|
2530
|
+
connection.destroy();
|
|
2531
|
+
return true;
|
|
2532
|
+
}
|
|
2533
|
+
handleVoiceState(data) {
|
|
2534
|
+
if (!data.guild_id || data.user_id !== this.#client.userId)
|
|
2535
|
+
return;
|
|
2536
|
+
this.#adapters.get(data.guild_id)?.onVoiceStateUpdate(data);
|
|
2537
|
+
}
|
|
2538
|
+
handleVoiceServer(data) {
|
|
2539
|
+
this.#adapters.get(data.guild_id)?.onVoiceServerUpdate(data);
|
|
2540
|
+
}
|
|
2541
|
+
handleGuildRemove(guildId) {
|
|
2542
|
+
this.#connections.get(guildId)?.destroy(false);
|
|
2543
|
+
}
|
|
2544
|
+
destroy(leave = true) {
|
|
2545
|
+
for (const connection of [...this.#connections.values()])
|
|
2546
|
+
connection.destroy(leave);
|
|
2547
|
+
this.#connections.clear();
|
|
2548
|
+
this.#adapters.clear();
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
function waitReady(connection, timeoutMs) {
|
|
2552
|
+
if (connection.status === "ready")
|
|
2553
|
+
return Promise.resolve(connection);
|
|
2554
|
+
return new Promise((resolve, reject) => {
|
|
2555
|
+
const done = (error) => {
|
|
2556
|
+
clearTimeout(timer);
|
|
2557
|
+
connection.off("stateChange", onChange);
|
|
2558
|
+
if (error)
|
|
2559
|
+
reject(error);
|
|
2560
|
+
else
|
|
2561
|
+
resolve(connection);
|
|
2562
|
+
};
|
|
2563
|
+
const onChange = (next) => {
|
|
2564
|
+
if (next === "ready")
|
|
2565
|
+
done();
|
|
2566
|
+
else if (next === "destroyed")
|
|
2567
|
+
done(new VoiceError2("The voice connection was destroyed."));
|
|
2568
|
+
else if (next === "disconnected")
|
|
2569
|
+
done(new VoiceError2(`Could not join the voice channel: ${connection.disconnectReason?.message ?? connection.disconnectReason?.reason ?? "disconnected"}.`));
|
|
2570
|
+
};
|
|
2571
|
+
const timer = setTimeout(() => done(new VoiceError2(`Joining the voice channel timed out after ${timeoutMs} ms.`)), Math.min(timeoutMs + 50, 2147483647));
|
|
2572
|
+
connection.on("stateChange", onChange);
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
// packages/voice/src/libopus.ts
|
|
2577
|
+
import { CString, dlopen, FFIType, ptr } from "bun:ffi";
|
|
2578
|
+
var OpusApplication2 = {
|
|
2579
|
+
Voip: 2048,
|
|
2580
|
+
Audio: 2049,
|
|
2581
|
+
RestrictedLowDelay: 2051
|
|
2582
|
+
};
|
|
2583
|
+
var SYMBOLS = {
|
|
2584
|
+
opus_encoder_create: {
|
|
2585
|
+
args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr],
|
|
2586
|
+
returns: FFIType.ptr
|
|
2587
|
+
},
|
|
2588
|
+
opus_encode: {
|
|
2589
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.ptr, FFIType.i32],
|
|
2590
|
+
returns: FFIType.i32
|
|
2591
|
+
},
|
|
2592
|
+
opus_encoder_destroy: { args: [FFIType.ptr], returns: FFIType.void },
|
|
2593
|
+
opus_decoder_create: {
|
|
2594
|
+
args: [FFIType.i32, FFIType.i32, FFIType.ptr],
|
|
2595
|
+
returns: FFIType.ptr
|
|
2596
|
+
},
|
|
2597
|
+
opus_decode: {
|
|
2598
|
+
args: [
|
|
2599
|
+
FFIType.ptr,
|
|
2600
|
+
FFIType.ptr,
|
|
2601
|
+
FFIType.i32,
|
|
2602
|
+
FFIType.ptr,
|
|
2603
|
+
FFIType.i32,
|
|
2604
|
+
FFIType.i32
|
|
2605
|
+
],
|
|
2606
|
+
returns: FFIType.i32
|
|
2607
|
+
},
|
|
2608
|
+
opus_decoder_destroy: { args: [FFIType.ptr], returns: FFIType.void },
|
|
2609
|
+
opus_strerror: { args: [FFIType.i32], returns: FFIType.ptr },
|
|
2610
|
+
opus_get_version_string: { args: [], returns: FFIType.ptr }
|
|
2611
|
+
};
|
|
2612
|
+
function candidates() {
|
|
2613
|
+
const own = process.env.LUNIBEE_OPUS_LIBRARY;
|
|
2614
|
+
if (own === "none")
|
|
2615
|
+
return [];
|
|
2616
|
+
const byPlatform = process.platform === "darwin" ? [
|
|
2617
|
+
"libopus.0.dylib",
|
|
2618
|
+
"libopus.dylib",
|
|
2619
|
+
"/opt/homebrew/lib/libopus.dylib",
|
|
2620
|
+
"/usr/local/lib/libopus.dylib"
|
|
2621
|
+
] : process.platform === "win32" ? ["opus.dll", "libopus-0.dll", "libopus.dll"] : ["libopus.so.0", "libopus.so"];
|
|
2622
|
+
return own ? [own, ...byPlatform] : byPlatform;
|
|
2623
|
+
}
|
|
2624
|
+
var loaded;
|
|
2625
|
+
function loadLibopus2(paths = candidates()) {
|
|
2626
|
+
for (const path of paths)
|
|
2627
|
+
try {
|
|
2628
|
+
const library = dlopen(path, SYMBOLS);
|
|
2629
|
+
return { symbols: library.symbols, path };
|
|
2630
|
+
} catch {}
|
|
2631
|
+
return null;
|
|
2632
|
+
}
|
|
2633
|
+
function hasLibopus2() {
|
|
2634
|
+
loaded ??= loadLibopus2();
|
|
2635
|
+
return loaded !== null;
|
|
2636
|
+
}
|
|
2637
|
+
function library() {
|
|
2638
|
+
loaded ??= loadLibopus2();
|
|
2639
|
+
if (!loaded)
|
|
2640
|
+
throw new VoiceError2("libopus is not installed (apt install libopus0, brew install opus), or set LUNIBEE_OPUS_LIBRARY to its path.");
|
|
2641
|
+
return loaded;
|
|
2642
|
+
}
|
|
2643
|
+
function libopusVersion2() {
|
|
2644
|
+
loaded ??= loadLibopus2();
|
|
2645
|
+
if (!loaded)
|
|
2646
|
+
return null;
|
|
2647
|
+
return new CString(loaded.symbols.opus_get_version_string()).toString();
|
|
2648
|
+
}
|
|
2649
|
+
function check(code, what) {
|
|
2650
|
+
if (code < 0)
|
|
2651
|
+
throw new VoiceError2(`${what} failed: ${new CString(library().symbols.opus_strerror(code)).toString()}`);
|
|
2652
|
+
return code;
|
|
2653
|
+
}
|
|
2654
|
+
var MAX_PACKET2 = 1275;
|
|
2655
|
+
|
|
2656
|
+
class Opus2 {
|
|
2657
|
+
#symbols;
|
|
2658
|
+
#encoder;
|
|
2659
|
+
#decoder;
|
|
2660
|
+
#packet = new Uint8Array(MAX_PACKET2);
|
|
2661
|
+
#pcm = new Int16Array(FRAME_SAMPLES2 * 2);
|
|
2662
|
+
static #registry = new FinalizationRegistry(({ symbols, encoder, decoder }) => {
|
|
2663
|
+
symbols.opus_encoder_destroy(encoder);
|
|
2664
|
+
symbols.opus_decoder_destroy(decoder);
|
|
2665
|
+
});
|
|
2666
|
+
constructor(application = OpusApplication2.Audio) {
|
|
2667
|
+
this.#symbols = library().symbols;
|
|
2668
|
+
const error = new Int32Array(1);
|
|
2669
|
+
this.#encoder = this.#symbols.opus_encoder_create(48000, 2, application, ptr(error));
|
|
2670
|
+
check(error[0], "Creating the Opus encoder");
|
|
2671
|
+
this.#decoder = this.#symbols.opus_decoder_create(48000, 2, ptr(error));
|
|
2672
|
+
if (error[0] < 0) {
|
|
2673
|
+
this.#symbols.opus_encoder_destroy(this.#encoder);
|
|
2674
|
+
check(error[0], "Creating the Opus decoder");
|
|
2675
|
+
}
|
|
2676
|
+
Opus2.#registry.register(this, {
|
|
2677
|
+
symbols: this.#symbols,
|
|
2678
|
+
encoder: this.#encoder,
|
|
2679
|
+
decoder: this.#decoder
|
|
2680
|
+
}, this);
|
|
2681
|
+
}
|
|
2682
|
+
static get libraryPath() {
|
|
2683
|
+
return library().path;
|
|
2684
|
+
}
|
|
2685
|
+
encode(pcm) {
|
|
2686
|
+
if (!this.#encoder)
|
|
2687
|
+
throw new VoiceError2("The Opus codec was destroyed.");
|
|
2688
|
+
if (pcm.byteLength !== PCM_FRAME_BYTES2)
|
|
2689
|
+
throw new VoiceError2(`Opus encodes one 20 ms frame of ${PCM_FRAME_BYTES2} bytes, got ${pcm.byteLength}.`);
|
|
2690
|
+
const samples = this.#pcm;
|
|
2691
|
+
const view = new DataView(pcm.buffer, pcm.byteOffset, pcm.byteLength);
|
|
2692
|
+
for (let i = 0;i < samples.length; i++)
|
|
2693
|
+
samples[i] = view.getInt16(i * 2, true);
|
|
2694
|
+
const length = check(this.#symbols.opus_encode(this.#encoder, ptr(samples), FRAME_SAMPLES2, ptr(this.#packet), MAX_PACKET2), "Opus encoding");
|
|
2695
|
+
return this.#packet.slice(0, length);
|
|
2696
|
+
}
|
|
2697
|
+
decode(packet) {
|
|
2698
|
+
if (!this.#decoder)
|
|
2699
|
+
throw new VoiceError2("The Opus codec was destroyed.");
|
|
2700
|
+
const frame = packet.byteLength ? 5760 : FRAME_SAMPLES2;
|
|
2701
|
+
const out = new Int16Array(frame * 2);
|
|
2702
|
+
const samples = check(this.#symbols.opus_decode(this.#decoder, packet.byteLength ? ptr(packet) : null, packet.byteLength, ptr(out), frame, 0), "Opus decoding");
|
|
2703
|
+
const pcm = new Uint8Array(samples * 4);
|
|
2704
|
+
const view = new DataView(pcm.buffer);
|
|
2705
|
+
for (let i = 0;i < samples * 2; i++)
|
|
2706
|
+
view.setInt16(i * 2, out[i], true);
|
|
2707
|
+
return pcm;
|
|
2708
|
+
}
|
|
2709
|
+
destroy() {
|
|
2710
|
+
if (!this.#encoder)
|
|
2711
|
+
return;
|
|
2712
|
+
Opus2.#registry.unregister(this);
|
|
2713
|
+
this.#symbols.opus_encoder_destroy(this.#encoder);
|
|
2714
|
+
this.#symbols.opus_decoder_destroy(this.#decoder);
|
|
2715
|
+
this.#encoder = null;
|
|
2716
|
+
this.#decoder = null;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
// packages/voice/src/opus.ts
|
|
2721
|
+
var native = () => {
|
|
2722
|
+
const codec = new Opus2;
|
|
2723
|
+
return {
|
|
2724
|
+
library: "libopus",
|
|
2725
|
+
encode: (pcm) => codec.encode(pcm),
|
|
2726
|
+
decode: (packet) => codec.decode(packet),
|
|
2727
|
+
destroy: () => codec.destroy()
|
|
2728
|
+
};
|
|
2729
|
+
};
|
|
2730
|
+
var wasm = async () => {
|
|
2731
|
+
const name = "opusscript";
|
|
2732
|
+
const mod = await import(name);
|
|
2733
|
+
const OpusScript = mod.default;
|
|
2734
|
+
const codec = new OpusScript(48000, 2, OpusScript.Application.AUDIO);
|
|
2735
|
+
let destroyed = false;
|
|
2736
|
+
return {
|
|
2737
|
+
library: "opusscript",
|
|
2738
|
+
encode: (pcm) => codec.encode(Buffer.from(pcm), FRAME_SAMPLES2),
|
|
2739
|
+
decode: (packet) => codec.decode(Buffer.from(packet)),
|
|
2740
|
+
destroy: () => {
|
|
2741
|
+
if (destroyed)
|
|
2742
|
+
return;
|
|
2743
|
+
destroyed = true;
|
|
2744
|
+
codec.delete();
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
};
|
|
2748
|
+
async function createOpusCodec2() {
|
|
2749
|
+
if (hasLibopus2())
|
|
2750
|
+
return native();
|
|
2751
|
+
try {
|
|
2752
|
+
return await wasm();
|
|
2753
|
+
} catch {
|
|
2754
|
+
throw new VoiceError2("Encoding or decoding Opus needs an Opus library: install libopus (apt install libopus0, brew install opus) or opusscript.");
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
// packages/voice/src/effects.ts
|
|
2758
|
+
var RATE = 48000;
|
|
2759
|
+
var CHANNELS = 2;
|
|
2760
|
+
function check2(name, value, min, max) {
|
|
2761
|
+
if (!Number.isFinite(value) || value < min || value > max)
|
|
2762
|
+
throw new RangeError(`${name} must be between ${min} and ${max}.`);
|
|
2763
|
+
}
|
|
2764
|
+
var clip = (value) => value > 32767 ? 32767 : value < -32768 ? -32768 : Math.round(value);
|
|
2765
|
+
function pitchShift2(semitones) {
|
|
2766
|
+
check2("semitones", semitones, -24, 24);
|
|
2767
|
+
const ratio = 2 ** (semitones / 12);
|
|
2768
|
+
const window = 2048;
|
|
2769
|
+
const size = 4096;
|
|
2770
|
+
const buffers = [new Float32Array(size), new Float32Array(size)];
|
|
2771
|
+
let write = 0;
|
|
2772
|
+
let phase = 0;
|
|
2773
|
+
const step = (1 - ratio) / window;
|
|
2774
|
+
const tap = (buffer, delay) => {
|
|
2775
|
+
const position = write - delay;
|
|
2776
|
+
const base = Math.floor(position);
|
|
2777
|
+
const frac = position - base;
|
|
2778
|
+
const a = buffer[base & size - 1];
|
|
2779
|
+
const b = buffer[base + 1 & size - 1];
|
|
2780
|
+
return a + (b - a) * frac;
|
|
2781
|
+
};
|
|
2782
|
+
return {
|
|
2783
|
+
process(samples) {
|
|
2784
|
+
if (semitones === 0)
|
|
2785
|
+
return;
|
|
2786
|
+
for (let i = 0;i < samples.length; i += CHANNELS) {
|
|
2787
|
+
const p2 = (phase + 0.5) % 1;
|
|
2788
|
+
const g1 = 1 - Math.abs(2 * phase - 1);
|
|
2789
|
+
const g2 = 1 - g1;
|
|
2790
|
+
for (let c = 0;c < CHANNELS; c++) {
|
|
2791
|
+
const buffer = buffers[c];
|
|
2792
|
+
buffer[write & size - 1] = samples[i + c];
|
|
2793
|
+
samples[i + c] = clip(g1 * tap(buffer, 1 + phase * window) + g2 * tap(buffer, 1 + p2 * window));
|
|
2794
|
+
}
|
|
2795
|
+
write = write + 1 & 1073741823;
|
|
2796
|
+
phase = ((phase + step) % 1 + 1) % 1;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
function robot2(frequency = 50) {
|
|
2802
|
+
check2("frequency", frequency, 1, 2000);
|
|
2803
|
+
const increment = 2 * Math.PI * frequency / RATE;
|
|
2804
|
+
let angle = 0;
|
|
2805
|
+
return {
|
|
2806
|
+
process(samples) {
|
|
2807
|
+
for (let i = 0;i < samples.length; i += CHANNELS) {
|
|
2808
|
+
const carrier = Math.sin(angle);
|
|
2809
|
+
for (let c = 0;c < CHANNELS; c++)
|
|
2810
|
+
samples[i + c] = clip(samples[i + c] * carrier);
|
|
2811
|
+
angle += increment;
|
|
2812
|
+
if (angle > 2 * Math.PI)
|
|
2813
|
+
angle -= 2 * Math.PI;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
function echo2(options = {}) {
|
|
2819
|
+
const { delay = 250, feedback = 0.4, mix = 0.5 } = options;
|
|
2820
|
+
check2("delay", delay, 1, 2000);
|
|
2821
|
+
check2("feedback", feedback, 0, 0.95);
|
|
2822
|
+
check2("mix", mix, 0, 1);
|
|
2823
|
+
const buffer = new Float32Array(Math.round(delay * RATE / 1000) * CHANNELS);
|
|
2824
|
+
let index = 0;
|
|
2825
|
+
return {
|
|
2826
|
+
process(samples) {
|
|
2827
|
+
for (let i = 0;i < samples.length; i++) {
|
|
2828
|
+
const delayed = buffer[index];
|
|
2829
|
+
const input = samples[i];
|
|
2830
|
+
buffer[index] = input + delayed * feedback;
|
|
2831
|
+
samples[i] = clip(input + delayed * mix);
|
|
2832
|
+
if (++index === buffer.length)
|
|
2833
|
+
index = 0;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
function gain2(factor) {
|
|
2839
|
+
check2("factor", factor, 0, 16);
|
|
2840
|
+
return {
|
|
2841
|
+
process(samples) {
|
|
2842
|
+
if (factor === 1)
|
|
2843
|
+
return;
|
|
2844
|
+
for (let i = 0;i < samples.length; i++)
|
|
2845
|
+
samples[i] = clip(samples[i] * factor);
|
|
2846
|
+
}
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
function applyEffects(effects, pcm) {
|
|
2850
|
+
if (effects.length === 0)
|
|
2851
|
+
return pcm;
|
|
2852
|
+
const view = new DataView(pcm.buffer, pcm.byteOffset, pcm.byteLength);
|
|
2853
|
+
const samples = new Int16Array(PCM_FRAME_BYTES2 >> 1);
|
|
2854
|
+
const count = Math.min(pcm.byteLength, PCM_FRAME_BYTES2) >> 1;
|
|
2855
|
+
for (let i = 0;i < count; i++)
|
|
2856
|
+
samples[i] = view.getInt16(i * 2, true);
|
|
2857
|
+
for (const effect of effects)
|
|
2858
|
+
effect.process(samples);
|
|
2859
|
+
const out = new Uint8Array(samples.length * 2);
|
|
2860
|
+
const outView = new DataView(out.buffer);
|
|
2861
|
+
for (let i = 0;i < samples.length; i++)
|
|
2862
|
+
outView.setInt16(i * 2, samples[i], true);
|
|
2863
|
+
return out;
|
|
2864
|
+
}
|
|
2865
|
+
// packages/voice/src/resource.ts
|
|
2866
|
+
var LIVE_BACKLOG = 5;
|
|
2867
|
+
|
|
2868
|
+
class VolumeControl2 {
|
|
2869
|
+
volume = 1;
|
|
2870
|
+
set(volume) {
|
|
2871
|
+
if (!Number.isFinite(volume) || volume < 0)
|
|
2872
|
+
throw new RangeError("Volume must be a finite number of at least 0.");
|
|
2873
|
+
this.volume = volume;
|
|
2874
|
+
}
|
|
2875
|
+
setDecibels(db) {
|
|
2876
|
+
this.set(10 ** (db / 20));
|
|
2877
|
+
}
|
|
2878
|
+
apply(pcm) {
|
|
2879
|
+
if (this.volume === 1)
|
|
2880
|
+
return pcm;
|
|
2881
|
+
const view = new DataView(pcm.buffer, pcm.byteOffset, pcm.byteLength);
|
|
2882
|
+
for (let i = 0;i + 1 < pcm.length; i += 2) {
|
|
2883
|
+
const scaled = Math.round(view.getInt16(i, true) * this.volume);
|
|
2884
|
+
view.setInt16(i, Math.max(-32768, Math.min(32767, scaled)), true);
|
|
2885
|
+
}
|
|
2886
|
+
return pcm;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
async function* chunksOf(input) {
|
|
2890
|
+
if (input instanceof Uint8Array) {
|
|
2891
|
+
yield input;
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
if (input instanceof ReadableStream) {
|
|
2895
|
+
const reader = input.getReader();
|
|
2896
|
+
try {
|
|
2897
|
+
for (;; ) {
|
|
2898
|
+
const { done, value } = await reader.read();
|
|
2899
|
+
if (done)
|
|
2900
|
+
return;
|
|
2901
|
+
yield value;
|
|
2902
|
+
}
|
|
2903
|
+
} finally {
|
|
2904
|
+
await reader.cancel().catch(() => {
|
|
2905
|
+
return;
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
yield* input;
|
|
2910
|
+
}
|
|
2911
|
+
async function* pcmFrames(chunks) {
|
|
2912
|
+
let pending = new Uint8Array(0);
|
|
2913
|
+
for await (const chunk of chunks) {
|
|
2914
|
+
const merged = new Uint8Array(pending.length + chunk.length);
|
|
2915
|
+
merged.set(pending, 0);
|
|
2916
|
+
merged.set(chunk, pending.length);
|
|
2917
|
+
let offset = 0;
|
|
2918
|
+
while (merged.length - offset >= PCM_FRAME_BYTES2) {
|
|
2919
|
+
yield merged.slice(offset, offset + PCM_FRAME_BYTES2);
|
|
2920
|
+
offset += PCM_FRAME_BYTES2;
|
|
2921
|
+
}
|
|
2922
|
+
pending = merged.slice(offset);
|
|
2923
|
+
}
|
|
2924
|
+
if (pending.length > 0) {
|
|
2925
|
+
const last = new Uint8Array(PCM_FRAME_BYTES2);
|
|
2926
|
+
last.set(pending, 0);
|
|
2927
|
+
yield last;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
function inferType(path) {
|
|
2931
|
+
const lower = path.toLowerCase();
|
|
2932
|
+
if (lower.endsWith(".ogg") || lower.endsWith(".opus"))
|
|
2933
|
+
return "ogg/opus";
|
|
2934
|
+
if (lower.endsWith(".pcm") || lower.endsWith(".raw"))
|
|
2935
|
+
return "raw";
|
|
2936
|
+
return "arbitrary";
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
class AudioResource2 {
|
|
2940
|
+
metadata;
|
|
2941
|
+
volume;
|
|
2942
|
+
effects;
|
|
2943
|
+
live;
|
|
2944
|
+
playbackDuration = 0;
|
|
2945
|
+
started = false;
|
|
2946
|
+
#frames;
|
|
2947
|
+
#queue = [];
|
|
2948
|
+
#prefetch;
|
|
2949
|
+
#cleanup = [];
|
|
2950
|
+
#filling = false;
|
|
2951
|
+
#done = false;
|
|
2952
|
+
#error;
|
|
2953
|
+
constructor(frames, options) {
|
|
2954
|
+
this.#frames = frames;
|
|
2955
|
+
this.metadata = options.metadata;
|
|
2956
|
+
this.volume = options.volume;
|
|
2957
|
+
this.effects = options.effects;
|
|
2958
|
+
this.live = options.live ?? false;
|
|
2959
|
+
this.#prefetch = this.live ? Math.max(1, options.prefetch ?? 1) : Math.max(1, options.prefetch ?? 10);
|
|
2960
|
+
if (options.cleanup)
|
|
2961
|
+
this.#cleanup.push(options.cleanup);
|
|
2962
|
+
this.#fill();
|
|
2963
|
+
}
|
|
2964
|
+
get ended() {
|
|
2965
|
+
return this.#done && this.#queue.length === 0;
|
|
2966
|
+
}
|
|
2967
|
+
get error() {
|
|
2968
|
+
return this.#error;
|
|
2969
|
+
}
|
|
2970
|
+
get readable() {
|
|
2971
|
+
return this.#queue.length > 0;
|
|
2972
|
+
}
|
|
2973
|
+
#fill() {
|
|
2974
|
+
if (this.#filling || this.#done)
|
|
2975
|
+
return;
|
|
2976
|
+
this.#filling = true;
|
|
2977
|
+
(async () => {
|
|
2978
|
+
try {
|
|
2979
|
+
while (!this.#done && (this.live || this.#queue.length < this.#prefetch)) {
|
|
2980
|
+
const next = await this.#frames.next();
|
|
2981
|
+
if (next.done) {
|
|
2982
|
+
this.#finish();
|
|
2983
|
+
break;
|
|
2984
|
+
}
|
|
2985
|
+
if (this.#done)
|
|
2986
|
+
break;
|
|
2987
|
+
this.#queue.push(next.value);
|
|
2988
|
+
if (this.live && this.#queue.length > LIVE_BACKLOG)
|
|
2989
|
+
this.#queue.shift();
|
|
2990
|
+
}
|
|
2991
|
+
} catch (error) {
|
|
2992
|
+
this.#error = error instanceof Error ? error : new VoiceError2(String(error));
|
|
2993
|
+
this.#finish();
|
|
2994
|
+
} finally {
|
|
2995
|
+
this.#filling = false;
|
|
2996
|
+
}
|
|
2997
|
+
})();
|
|
2998
|
+
}
|
|
2999
|
+
#finish() {
|
|
3000
|
+
if (this.#done)
|
|
3001
|
+
return;
|
|
3002
|
+
this.#done = true;
|
|
3003
|
+
for (const cleanup of this.#cleanup.splice(0))
|
|
3004
|
+
try {
|
|
3005
|
+
cleanup();
|
|
3006
|
+
} catch {}
|
|
3007
|
+
}
|
|
3008
|
+
read() {
|
|
3009
|
+
const frame = this.#queue.shift();
|
|
3010
|
+
if (frame) {
|
|
3011
|
+
this.started = true;
|
|
3012
|
+
this.#fill();
|
|
3013
|
+
return frame;
|
|
3014
|
+
}
|
|
3015
|
+
if (this.#done)
|
|
3016
|
+
return null;
|
|
3017
|
+
this.#fill();
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
destroy() {
|
|
3021
|
+
this.#queue.length = 0;
|
|
3022
|
+
this.#finish();
|
|
3023
|
+
this.#frames.return(undefined).catch(() => {
|
|
3024
|
+
return;
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
function spawnFFmpeg(input, path, output) {
|
|
3029
|
+
const fromURL = typeof input === "string" && /^https?:\/\//i.test(input);
|
|
3030
|
+
const args = [
|
|
3031
|
+
path,
|
|
3032
|
+
"-hide_banner",
|
|
3033
|
+
"-loglevel",
|
|
3034
|
+
"error",
|
|
3035
|
+
"-analyzeduration",
|
|
3036
|
+
"0",
|
|
3037
|
+
"-i",
|
|
3038
|
+
fromURL ? input : "pipe:0",
|
|
3039
|
+
"-vn",
|
|
3040
|
+
"-ar",
|
|
3041
|
+
"48000",
|
|
3042
|
+
"-ac",
|
|
3043
|
+
"2",
|
|
3044
|
+
...output === "raw" ? ["-f", "s16le"] : [
|
|
3045
|
+
"-c:a",
|
|
3046
|
+
"libopus",
|
|
3047
|
+
"-b:a",
|
|
3048
|
+
"128k",
|
|
3049
|
+
"-frame_duration",
|
|
3050
|
+
"20",
|
|
3051
|
+
"-f",
|
|
3052
|
+
"opus"
|
|
3053
|
+
],
|
|
3054
|
+
"pipe:1"
|
|
3055
|
+
];
|
|
3056
|
+
const proc = Bun.spawn(args, {
|
|
3057
|
+
stdin: fromURL ? "ignore" : "pipe",
|
|
3058
|
+
stdout: "pipe",
|
|
3059
|
+
stderr: "pipe"
|
|
3060
|
+
});
|
|
3061
|
+
let killed = false;
|
|
3062
|
+
const stderr = (async () => {
|
|
3063
|
+
let tail = "";
|
|
3064
|
+
const decoder = new TextDecoder;
|
|
3065
|
+
try {
|
|
3066
|
+
for await (const chunk of proc.stderr)
|
|
3067
|
+
tail = (tail + decoder.decode(chunk, { stream: true })).slice(-1000);
|
|
3068
|
+
} catch {}
|
|
3069
|
+
return tail.trim();
|
|
3070
|
+
})();
|
|
3071
|
+
if (!fromURL) {
|
|
3072
|
+
const sink = proc.stdin;
|
|
3073
|
+
const source = typeof input === "string" ? chunksOf(Bun.file(input).stream()) : chunksOf(input);
|
|
3074
|
+
(async () => {
|
|
3075
|
+
try {
|
|
3076
|
+
for await (const chunk of source)
|
|
3077
|
+
await sink.write(chunk);
|
|
3078
|
+
} catch {} finally {
|
|
3079
|
+
try {
|
|
3080
|
+
await sink.end();
|
|
3081
|
+
} catch {}
|
|
3082
|
+
}
|
|
3083
|
+
})();
|
|
3084
|
+
}
|
|
3085
|
+
return {
|
|
3086
|
+
stdout: proc.stdout,
|
|
3087
|
+
kill: () => {
|
|
3088
|
+
killed = true;
|
|
3089
|
+
try {
|
|
3090
|
+
proc.kill();
|
|
3091
|
+
} catch {}
|
|
3092
|
+
},
|
|
3093
|
+
exited: async () => {
|
|
3094
|
+
const code = await proc.exited;
|
|
3095
|
+
if (code === 0 || killed)
|
|
3096
|
+
return;
|
|
3097
|
+
const output = await stderr;
|
|
3098
|
+
throw new VoiceError2(`FFmpeg exited with code ${code}${output ? `: ${output}` : "."}`);
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
function createAudioResource2(input, options = {}) {
|
|
3103
|
+
let type = options.inputType ?? (typeof input === "string" ? inferType(input) : "arbitrary");
|
|
3104
|
+
const volume = options.inlineVolume ? new VolumeControl2 : undefined;
|
|
3105
|
+
const effects = options.effects ? [...options.effects] : undefined;
|
|
3106
|
+
const pcmOut = Boolean(volume || effects);
|
|
3107
|
+
const transform = (pcm) => {
|
|
3108
|
+
const out = effects ? applyEffects(effects, pcm) : pcm;
|
|
3109
|
+
return volume ? volume.apply(out) : out;
|
|
3110
|
+
};
|
|
3111
|
+
const cleanups = [];
|
|
3112
|
+
let chunks;
|
|
3113
|
+
if (type === "arbitrary") {
|
|
3114
|
+
const ffmpeg = spawnFFmpeg(input, options.ffmpegPath ?? "ffmpeg", pcmOut ? "raw" : "ogg/opus");
|
|
3115
|
+
cleanups.push(ffmpeg.kill);
|
|
3116
|
+
chunks = async function* () {
|
|
3117
|
+
yield* chunksOf(ffmpeg.stdout);
|
|
3118
|
+
await ffmpeg.exited();
|
|
3119
|
+
}();
|
|
3120
|
+
type = pcmOut ? "raw" : "ogg/opus";
|
|
3121
|
+
} else if (typeof input === "string") {
|
|
3122
|
+
if (/^https?:\/\//i.test(input))
|
|
3123
|
+
throw new VoiceError2(`A URL needs inputType "arbitrary" (FFmpeg).`);
|
|
3124
|
+
chunks = chunksOf(Bun.file(input).stream());
|
|
3125
|
+
} else
|
|
3126
|
+
chunks = chunksOf(input);
|
|
3127
|
+
let codec;
|
|
3128
|
+
let destroyed = false;
|
|
3129
|
+
const getCodec = async () => {
|
|
3130
|
+
if (!codec) {
|
|
3131
|
+
const created = await (options.codecFactory ?? createOpusCodec2)();
|
|
3132
|
+
if (destroyed) {
|
|
3133
|
+
created.destroy();
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
codec = created;
|
|
3137
|
+
cleanups.push(() => created.destroy());
|
|
3138
|
+
}
|
|
3139
|
+
return destroyed ? undefined : codec;
|
|
3140
|
+
};
|
|
3141
|
+
let frames;
|
|
3142
|
+
if (type === "raw") {
|
|
3143
|
+
const source = pcmFrames(chunks);
|
|
3144
|
+
frames = async function* () {
|
|
3145
|
+
for await (const pcm of source) {
|
|
3146
|
+
const c = await getCodec();
|
|
3147
|
+
if (!c)
|
|
3148
|
+
return;
|
|
3149
|
+
yield c.encode(transform(pcm));
|
|
3150
|
+
}
|
|
3151
|
+
}();
|
|
3152
|
+
} else {
|
|
3153
|
+
const packets = type === "ogg/opus" ? demuxOggOpus2(chunks) : chunks;
|
|
3154
|
+
frames = pcmOut ? async function* () {
|
|
3155
|
+
for await (const packet of packets) {
|
|
3156
|
+
const c = await getCodec();
|
|
3157
|
+
if (!c)
|
|
3158
|
+
return;
|
|
3159
|
+
yield c.encode(transform(c.decode(packet)));
|
|
3160
|
+
}
|
|
3161
|
+
}() : async function* () {
|
|
3162
|
+
yield* packets;
|
|
3163
|
+
}();
|
|
3164
|
+
}
|
|
3165
|
+
return new AudioResource2(frames, {
|
|
3166
|
+
metadata: options.metadata,
|
|
3167
|
+
volume,
|
|
3168
|
+
effects,
|
|
3169
|
+
live: options.live,
|
|
3170
|
+
prefetch: options.prefetch,
|
|
3171
|
+
cleanup: () => {
|
|
3172
|
+
destroyed = true;
|
|
3173
|
+
for (const cleanup of cleanups)
|
|
3174
|
+
cleanup();
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
// packages/voice/src/player.ts
|
|
3179
|
+
var AudioPlayerStatus2 = {
|
|
3180
|
+
Idle: "idle",
|
|
3181
|
+
Buffering: "buffering",
|
|
3182
|
+
Playing: "playing",
|
|
3183
|
+
Paused: "paused",
|
|
3184
|
+
AutoPaused: "autopaused"
|
|
3185
|
+
};
|
|
3186
|
+
var SILENCE_PADDING = 5;
|
|
3187
|
+
var MAX_LATENESS_MS = 100;
|
|
3188
|
+
|
|
3189
|
+
class AudioPlayer2 extends Emitter {
|
|
3190
|
+
#state = "idle";
|
|
3191
|
+
#resource;
|
|
3192
|
+
#sinks = new Set;
|
|
3193
|
+
#options;
|
|
3194
|
+
#timer;
|
|
3195
|
+
#next = 0;
|
|
3196
|
+
#silence = 0;
|
|
3197
|
+
#missed = 0;
|
|
3198
|
+
constructor(options = {}) {
|
|
3199
|
+
super();
|
|
3200
|
+
this.#options = {
|
|
3201
|
+
noSubscriber: options.noSubscriber ?? "pause",
|
|
3202
|
+
maxMissedFrames: options.maxMissedFrames ?? 250
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
get state() {
|
|
3206
|
+
return this.#state;
|
|
3207
|
+
}
|
|
3208
|
+
get resource() {
|
|
3209
|
+
return this.#resource;
|
|
3210
|
+
}
|
|
3211
|
+
get subscribers() {
|
|
3212
|
+
return this.#sinks;
|
|
3213
|
+
}
|
|
3214
|
+
get ticking() {
|
|
3215
|
+
return this.#timer !== undefined;
|
|
3216
|
+
}
|
|
3217
|
+
#transition(next) {
|
|
3218
|
+
if (this.#state === next)
|
|
3219
|
+
return;
|
|
3220
|
+
const from = this.#state;
|
|
3221
|
+
this.#state = next;
|
|
3222
|
+
this.emit("stateChange", from, next);
|
|
3223
|
+
}
|
|
3224
|
+
play(resource) {
|
|
3225
|
+
if (resource.ended || resource.started)
|
|
3226
|
+
throw new VoiceError2("This audio resource has already been played; create a new one.");
|
|
3227
|
+
if (this.#resource && this.#resource !== resource)
|
|
3228
|
+
this.#resource.destroy();
|
|
3229
|
+
this.#resource = resource;
|
|
3230
|
+
this.#missed = 0;
|
|
3231
|
+
this.#silence = 0;
|
|
3232
|
+
this.#transition(resource.readable ? "playing" : "buffering");
|
|
3233
|
+
this.#start();
|
|
3234
|
+
}
|
|
3235
|
+
pause(silence = true) {
|
|
3236
|
+
if (this.#state !== "playing" && this.#state !== "buffering")
|
|
3237
|
+
return false;
|
|
3238
|
+
this.#transition("paused");
|
|
3239
|
+
this.#silence = silence ? SILENCE_PADDING : 0;
|
|
3240
|
+
if (silence)
|
|
3241
|
+
this.#start();
|
|
3242
|
+
else
|
|
3243
|
+
this.#quiet();
|
|
3244
|
+
return true;
|
|
3245
|
+
}
|
|
3246
|
+
unpause() {
|
|
3247
|
+
if (this.#state !== "paused")
|
|
3248
|
+
return false;
|
|
3249
|
+
this.#silence = 0;
|
|
3250
|
+
this.#transition(this.#resource?.readable ? "playing" : "buffering");
|
|
3251
|
+
this.#start();
|
|
3252
|
+
return true;
|
|
3253
|
+
}
|
|
3254
|
+
resume() {
|
|
3255
|
+
return this.unpause();
|
|
3256
|
+
}
|
|
3257
|
+
stop(force = false) {
|
|
3258
|
+
if (this.#state === "idle")
|
|
3259
|
+
return false;
|
|
3260
|
+
this.#resource?.destroy();
|
|
3261
|
+
this.#resource = undefined;
|
|
3262
|
+
this.#transition("idle");
|
|
3263
|
+
this.#silence = force ? 0 : SILENCE_PADDING;
|
|
3264
|
+
if (force)
|
|
3265
|
+
this.#quiet();
|
|
3266
|
+
else
|
|
3267
|
+
this.#start();
|
|
3268
|
+
return true;
|
|
3269
|
+
}
|
|
3270
|
+
attach(sink) {
|
|
3271
|
+
this.#sinks.add(sink);
|
|
3272
|
+
this.refresh();
|
|
3273
|
+
}
|
|
3274
|
+
detach(sink) {
|
|
3275
|
+
if (!this.#sinks.delete(sink))
|
|
3276
|
+
return;
|
|
3277
|
+
try {
|
|
3278
|
+
sink.setSpeaking(false);
|
|
3279
|
+
} catch {}
|
|
3280
|
+
this.refresh();
|
|
3281
|
+
}
|
|
3282
|
+
refresh() {
|
|
3283
|
+
if (this.#state === "autopaused" && this.#hasReadySink()) {
|
|
3284
|
+
this.#transition(this.#resource?.readable ? "playing" : "buffering");
|
|
3285
|
+
this.#start();
|
|
3286
|
+
} else if (this.#state === "playing" || this.#state === "buffering")
|
|
3287
|
+
this.#start();
|
|
3288
|
+
}
|
|
3289
|
+
#hasReadySink() {
|
|
3290
|
+
for (const sink of this.#sinks)
|
|
3291
|
+
if (sink.ready)
|
|
3292
|
+
return true;
|
|
3293
|
+
return false;
|
|
3294
|
+
}
|
|
3295
|
+
#start() {
|
|
3296
|
+
if (this.#timer)
|
|
3297
|
+
return;
|
|
3298
|
+
this.#next = performance.now();
|
|
3299
|
+
this.#schedule();
|
|
3300
|
+
}
|
|
3301
|
+
#schedule() {
|
|
3302
|
+
this.#next += FRAME_DURATION_MS2;
|
|
3303
|
+
const now = performance.now();
|
|
3304
|
+
if (now - this.#next > MAX_LATENESS_MS)
|
|
3305
|
+
this.#next = now + FRAME_DURATION_MS2;
|
|
3306
|
+
this.#timer = setTimeout(() => {
|
|
3307
|
+
this.#timer = undefined;
|
|
3308
|
+
this.#tick();
|
|
3309
|
+
}, Math.max(0, this.#next - now));
|
|
3310
|
+
}
|
|
3311
|
+
#quiet() {
|
|
3312
|
+
if (this.#timer)
|
|
3313
|
+
clearTimeout(this.#timer);
|
|
3314
|
+
this.#timer = undefined;
|
|
3315
|
+
for (const sink of this.#sinks)
|
|
3316
|
+
try {
|
|
3317
|
+
sink.setSpeaking(false);
|
|
3318
|
+
} catch {}
|
|
3319
|
+
}
|
|
3320
|
+
#send(frame) {
|
|
3321
|
+
for (const sink of this.#sinks)
|
|
3322
|
+
if (sink.ready)
|
|
3323
|
+
try {
|
|
3324
|
+
sink.sendFrame(frame);
|
|
3325
|
+
} catch {}
|
|
3326
|
+
}
|
|
3327
|
+
#tick() {
|
|
3328
|
+
if (this.#state === "idle" || this.#state === "paused") {
|
|
3329
|
+
if (this.#silence > 0) {
|
|
3330
|
+
this.#silence--;
|
|
3331
|
+
this.#send(SILENCE_FRAME2);
|
|
3332
|
+
if (this.#silence > 0)
|
|
3333
|
+
return this.#schedule();
|
|
3334
|
+
}
|
|
3335
|
+
return this.#quiet();
|
|
3336
|
+
}
|
|
3337
|
+
if (this.#state === "autopaused")
|
|
3338
|
+
return this.#quiet();
|
|
3339
|
+
const resource = this.#resource;
|
|
3340
|
+
if (!resource)
|
|
3341
|
+
return this.#quiet();
|
|
3342
|
+
if (!this.#hasReadySink()) {
|
|
3343
|
+
if (this.#options.noSubscriber === "pause") {
|
|
3344
|
+
this.#transition("autopaused");
|
|
3345
|
+
return this.#quiet();
|
|
3346
|
+
}
|
|
3347
|
+
if (this.#options.noSubscriber === "stop") {
|
|
3348
|
+
this.stop(true);
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
const frame = resource.read();
|
|
3353
|
+
if (frame === null)
|
|
3354
|
+
return this.#end(resource);
|
|
3355
|
+
if (frame === undefined) {
|
|
3356
|
+
if (resource.live) {
|
|
3357
|
+
if (!resource.started)
|
|
3358
|
+
this.#transition("buffering");
|
|
3359
|
+
return this.#schedule();
|
|
3360
|
+
}
|
|
3361
|
+
this.#transition("buffering");
|
|
3362
|
+
if (++this.#missed > this.#options.maxMissedFrames) {
|
|
3363
|
+
this.#fail(resource, new VoiceError2("The audio resource stopped producing frames."));
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
return this.#schedule();
|
|
3367
|
+
}
|
|
3368
|
+
this.#missed = 0;
|
|
3369
|
+
this.#transition("playing");
|
|
3370
|
+
this.#send(frame);
|
|
3371
|
+
resource.playbackDuration += FRAME_DURATION_MS2;
|
|
3372
|
+
this.#schedule();
|
|
3373
|
+
}
|
|
3374
|
+
#end(resource) {
|
|
3375
|
+
if (resource.error)
|
|
3376
|
+
return this.#fail(resource, resource.error);
|
|
3377
|
+
this.#resource = undefined;
|
|
3378
|
+
this.#transition("idle");
|
|
3379
|
+
this.emit("finish", resource);
|
|
3380
|
+
this.#silence = SILENCE_PADDING;
|
|
3381
|
+
this.#tick();
|
|
3382
|
+
}
|
|
3383
|
+
#fail(resource, error) {
|
|
3384
|
+
resource.destroy();
|
|
3385
|
+
this.#resource = undefined;
|
|
3386
|
+
this.#transition("idle");
|
|
3387
|
+
this.emit("error", error, resource);
|
|
3388
|
+
this.#silence = SILENCE_PADDING;
|
|
3389
|
+
this.#tick();
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
function createAudioPlayer2(options) {
|
|
3393
|
+
return new AudioPlayer2(options);
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
// packages/voice/src/index.ts
|
|
3397
|
+
function entersState2(target, status, timeoutMs) {
|
|
3398
|
+
const current = target instanceof VoiceConnection2 ? target.status : target.state;
|
|
3399
|
+
if (current === status)
|
|
3400
|
+
return Promise.resolve(target);
|
|
3401
|
+
const emitter = target;
|
|
3402
|
+
const isConnection = target instanceof VoiceConnection2;
|
|
3403
|
+
return new Promise((resolve, reject) => {
|
|
3404
|
+
const onChange = (first, second) => {
|
|
3405
|
+
if ((isConnection ? first : second) !== status)
|
|
3406
|
+
return;
|
|
3407
|
+
clearTimeout(timer);
|
|
3408
|
+
emitter.off("stateChange", onChange);
|
|
3409
|
+
resolve(target);
|
|
3410
|
+
};
|
|
3411
|
+
const timer = setTimeout(() => {
|
|
3412
|
+
emitter.off("stateChange", onChange);
|
|
3413
|
+
reject(new VoiceError2(`Did not enter state "${String(status)}" within ${timeoutMs} ms.`));
|
|
3414
|
+
}, Math.min(timeoutMs, 2147483647));
|
|
3415
|
+
emitter.on("stateChange", onChange);
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
async function generateDependencyReport2() {
|
|
3419
|
+
const dave = await loadDave2();
|
|
3420
|
+
let opus = null;
|
|
3421
|
+
try {
|
|
3422
|
+
const codec = await createOpusCodec2();
|
|
3423
|
+
opus = codec.library;
|
|
3424
|
+
codec.destroy();
|
|
3425
|
+
} catch {
|
|
3426
|
+
opus = null;
|
|
3427
|
+
}
|
|
3428
|
+
let ffmpeg = null;
|
|
3429
|
+
try {
|
|
3430
|
+
const proc = Bun.spawnSync(["ffmpeg", "-version"], {
|
|
3431
|
+
stdout: "pipe",
|
|
3432
|
+
stderr: "ignore"
|
|
3433
|
+
});
|
|
3434
|
+
if (proc.exitCode === 0)
|
|
3435
|
+
ffmpeg = new TextDecoder().decode(proc.stdout).split(`
|
|
3436
|
+
`)[0] ?? "ffmpeg";
|
|
3437
|
+
} catch {
|
|
3438
|
+
ffmpeg = null;
|
|
3439
|
+
}
|
|
3440
|
+
return {
|
|
3441
|
+
dave: dave ? `@snazzah/davey (DAVE v${dave.DAVE_PROTOCOL_VERSION})` : null,
|
|
3442
|
+
opus,
|
|
3443
|
+
ffmpeg
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
export { VOICE_GATEWAY_VERSION2, VoiceOpcodes2, VoiceCloseCodes2, canResumeVoice2, RTP_OPUS_PAYLOAD_TYPE2, FRAME_DURATION_MS2, FRAME_SAMPLES2, PCM_FRAME_BYTES2, SILENCE_FRAME2, SpeakingFlags2, VoiceError2, SUPPORTED_ENCRYPTION_MODES2, chooseEncryptionMode2, rtpHeader2, parseRtpHeader2, PacketCipher2, loadDave2, maxDaveProtocolVersion2, DaveSession2, HEARTBEAT_TIMEOUT_CLOSE2, voiceGatewayURL2, VoiceGateway2, bunUdpSocket2, discoveryRequest2, parseDiscoveryResponse2, VoiceUdp2, VoiceNetworking2, demuxOggOpus2, OggOpusWriter2, VoiceReceiver2, VoiceConnectionStatus2, VoiceConnection2, joinVoiceChannel2, getVoiceConnection2, VoiceManager2, OpusApplication2, loadLibopus2, hasLibopus2, libopusVersion2, Opus2, createOpusCodec2, pitchShift2, robot2, echo2, gain2, VolumeControl2, AudioResource2, createAudioResource2, AudioPlayerStatus2, AudioPlayer2, createAudioPlayer2, entersState2, generateDependencyReport2 };
|
|
3448
|
+
|
|
3449
|
+
//# debugId=EA1F81AA6907139F64756E2164756E21
|