Revert "refactor: vendor content as regular files (remove git submodules) so CI builds don't need GitHub auth"

This reverts commit 134674d7dd.
This commit is contained in:
asepharyana
2026-07-02 03:54:44 +07:00
parent 24c738dd19
commit 586627319f
452 changed files with 4 additions and 75011 deletions
Vendored Submodule
+1
Submodule vendor/better-sqlite3 added at 7fa2365432
-18
View File
@@ -1,18 +0,0 @@
name: Biome Lint & Format Check
on:
push:
pull_request:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: 2.3.8
- name: Run Biome
run: biome ci .
@@ -1,30 +0,0 @@
name: Publish pkg.pr.new
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
- name: Publish
run: pnpx pkg-pr-new publish
-6
View File
@@ -1,6 +0,0 @@
node_modules
dist
package-lock.json
#example/src/config.json
examples/*/node_modules
examples/*/dist
-88
View File
@@ -1,88 +0,0 @@
# Performance related tweaks
## `ultrafast` shouldn't be used for x264/5
In our testing, the `ultrafast` preset produces a lot of bitrate spikes, causing the stream to stutter. `superfast` and below seems to keep it under control pretty well. Do not use `ultrafast`. Previous versions of the library has `ultrafast` as the default, which has been changed after the testing.
## Transport encryption methods
> [!NOTE]
> This is no longer accurate as of [#195](https://github.com/Discord-RE/Discord-video-stream/pull/195), which replaces the custom UDP connection with standard WebRTC. This is kept here for historical purposes only.
On CPUs without AES acceleration (very old x86 CPUs, certain ARM SoCs on single board computers, certain VMs that don't expose AES acceleration capability), the default encryption method (AES-256-GCM) might not be fast enough to handle high frame-rate + high bitrate streams.
In such cases, you can enable the `forceChacha20Encryption` option on the `Streamer` instance (`streamer.opts.forceChacha20Encryption = true`) before starting a stream, to force the use of the faster Chacha20-Poly1305 encryption method. For even higher performance, also install the optional [`sodium-native`](https://www.npmjs.com/package/sodium-native) package to use the faster native version instead of the WASM version.
Below are some benchmark results of the two encryption methods in various circumstances, for reference purposes only. All benchmarks are performed on a Ryzen 5 5600H.
<details>
<summary>AES-256-GCM, with AES acceleration</summary>
```
PS C:\> openssl speed -elapsed -aead -evp aes-256-gcm
You have chosen to measure elapsed time instead of user CPU time.
Doing AES-256-GCM ops for 3s on 2 size blocks: 19046296 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 31 size blocks: 15299030 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 136 size blocks: 13580376 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 1024 size blocks: 7691855 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 8192 size blocks: 1648811 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 16384 size blocks: 863115 AES-256-GCM ops in 3.00s
version: 3.4.0
built on: Tue Oct 22 23:27:41 2024 UTC
options: bn(64,64)
compiler: cl /Z7 /Fdossl_static.pdb /Gs0 /GF /Gy /MD /W3 /wd4090 /nologo /O2 -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"NDEBUG" -D_WINSOCK_DEPRECATED_NO_WARNINGS -D_WIN32_WINNT=0x0502
CPUINFO: OPENSSL_ia32cap=0xfed8320b078bffff:0x400684219c97a9
The 'numbers' are in 1000s of bytes per second processed.
type 2 bytes 31 bytes 136 bytes 1024 bytes 8192 bytes 16384 bytes
AES-256-GCM 12693.30k 158089.98k 615233.56k 2625486.51k 4500852.95k 4712187.99k
```
</details>
<details>
<summary>AES-256-GCM, without AES acceleration</summary>
```
PS C:\> openssl speed -elapsed -aead -evp aes-256-gcm
You have chosen to measure elapsed time instead of user CPU time.
Doing AES-256-GCM ops for 3s on 2 size blocks: 6947831 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 31 size blocks: 4875037 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 136 size blocks: 3132696 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 1024 size blocks: 821006 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 8192 size blocks: 113769 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 16384 size blocks: 57074 AES-256-GCM ops in 3.00s
version: 3.4.0
built on: Tue Oct 22 23:27:41 2024 UTC
options: bn(64,64)
compiler: cl /Z7 /Fdossl_static.pdb /Gs0 /GF /Gy /MD /W3 /wd4090 /nologo /O2 -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"NDEBUG" -D_WINSOCK_DEPRECATED_NO_WARNINGS -D_WIN32_WINNT=0x0502
CPUINFO: OPENSSL_ia32cap=0xfcd83209078bffff:0x0 env:~0x200000200000000
The 'numbers' are in 1000s of bytes per second processed.
type 2 bytes 31 bytes 136 bytes 1024 bytes 8192 bytes 16384 bytes
AES-256-GCM 4630.34k 50358.60k 142015.55k 280143.33k 310561.70k 311596.27k
```
</details>
<details>
<summary>Chacha20-Poly1305</summary>
```
PS C:\> openssl speed -elapsed -aead -evp chacha20-poly1305
You have chosen to measure elapsed time instead of user CPU time.
Doing ChaCha20-Poly1305 ops for 3s on 2 size blocks: 8312139 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 31 size blocks: 7801222 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 136 size blocks: 5436377 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 1024 size blocks: 4182141 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 8192 size blocks: 903567 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 16384 size blocks: 472556 ChaCha20-Poly1305 ops in 3.00s
version: 3.4.0
built on: Tue Oct 22 23:27:41 2024 UTC
options: bn(64,64)
compiler: cl /Z7 /Fdossl_static.pdb /Gs0 /GF /Gy /MD /W3 /wd4090 /nologo /O2 -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"NDEBUG" -D_WINSOCK_DEPRECATED_NO_WARNINGS -D_WIN32_WINNT=0x0502
CPUINFO: OPENSSL_ia32cap=0xfed8320b078bffff:0x400684219c97a9
The 'numbers' are in 1000s of bytes per second processed.
type 2 bytes 31 bytes 136 bytes 1024 bytes 8192 bytes 16384 bytes
ChaCha20-Poly1305 5539.58k 80585.77k 246284.90k 1427504.13k 2465696.49k 2580785.83k
```
</details>
-296
View File
@@ -1,296 +0,0 @@
# Discord self-bot video
[![pkg.pr.new](https://pkg.pr.new/badge/Discord-RE/Discord-video-stream)](https://pkg.pr.new/~/Discord-RE/Discord-video-stream)
Fork: [Discord-video-experiment](https://github.com/mrjvs/Discord-video-experiment)
> [!CAUTION]
> Using any kind of automation programs on your account can result in your account getting permanently banned by Discord. Use at your own risk
## Features
- Playing video & audio in a voice channel (`Go Live`, or webcam video)
## Implementation
What I implemented and what I did not.
### Video codecs
- [ ] VP8 (once supported, removed for maintainability)
- [ ] VP9
- [X] H.264
- [X] H.265
- [ ] AV1
### Packet types
- [X] RTP (sending of realtime data)
- [ ] RTX (retransmission)
### Connection types
- [X] Regular Voice Connection
- [X] Go Live
### Encryption
- [X] Transport Encryption
- [X] [End-to-end Encryption](https://github.com/dank074/Discord-video-stream/issues/102)
### Extras
- [X] Figure out RTP header extensions (discord specific) (discord seems to use [one-byte RTP header extension](https://www.rfc-editor.org/rfc/rfc8285.html#section-4.2))
Extensions supported by Discord (taken from the webrtc sdp exchange)
```
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level"
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid"
"a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"
"a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type"
"a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing"
"a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space"
"a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"
"a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"
"a=extmap:13 urn:3gpp:video-orientation"
"a=extmap:14 urn:ietf:params:rtp-hdrext:toffset"
```
## Requirements
For full functionality, this library requires an FFmpeg build with `libzmq` enabled. Here is our recommendation:
- Windows & Linux: [BtbN's FFmpeg Builds](https://github.com/BtbN/FFmpeg-Builds)
- macOS (Intel): [evermeet.cx](https://evermeet.cx/ffmpeg/)
- macOS (Apple Silicon): Install from Homebrew
## Usage
Install the package, alongside its peer-dependency discord.js-selfbot-v13:
```
npm install @dank074/discord-video-stream@latest
npm install discord.js-selfbot-v13@latest
```
> [!IMPORTANT]
> This library makes use of native dependencies (`node-av` and `node-datachannel`). If you use package managers that don't run install scripts by default (`pnpm`, `bun`, etc.), you'll need to allow running install scripts for `node-av` and `node-datachannel` for proper operation.
Create a new Streamer, and pass it a selfbot Client
```typescript
import { Client } from "discord.js-selfbot-v13";
import { Streamer } from '@dank074/discord-video-stream';
const streamer = new Streamer(new Client());
await streamer.client.login('TOKEN HERE');
```
Make client join a voice channel
```typescript
await streamer.joinVoice("GUILD ID HERE", "CHANNEL ID HERE");
```
Start sending media
```typescript
import { prepareStream, playStream, Utils, Encoders } from "@dank074/discord-video-stream"
try {
// NVENC is also available, change Encoders.software to Encoders.nvenc and
// adapt the settings
let encoder = Encoders.software({
x264: {
preset: "superfast"
},
x265: {
preset: "superfast"
}
});
const { command, output } = prepareStream("DIRECT VIDEO URL OR READABLE STREAM HERE", {
encoder,
// Specify either width or height for aspect ratio aware scaling
// Specify both for stretched output
height: 1080,
// Force frame rate, or leave blank to use source frame rate
frameRate: 30,
bitrateVideo: 5000,
bitrateVideoMax: 7500,
videoCodec: Utils.normalizeVideoCodec("H264" /* or H265 */),
});
command.on("error", (err, stdout, stderr) => {
// Handle ffmpeg errors here
});
await playStream(output, streamer, {
type: "go-live" // use "camera" for camera stream
});
console.log("Finished playing video");
} catch (e) {
console.log(e);
}
```
## Encoder options available
```typescript
/**
* A function returning encoder settings for a specific avg and max bitrate
* You can define your own, or use the pre-made functions in the library
*/
encoder: EncoderSettingsGetter;
/**
* Disable transcoding of the video stream. If specified, all video related
* options have no effects
*
* Only use this if your video stream is Discord streaming friendly, otherwise
* you'll get a glitchy output
*/
noTranscoding?: boolean;
/**
* Video output width
*/
width?: number;
/**
* Video output height
*/
height?: number;
/**
* Video output frames per second
*/
fps?: number;
/**
* Video average bitrate in kbps
*/
bitrateVideo?: number;
/**
* Video max bitrate in kbps
*/
bitrateVideoMax?: number;
/**
* Audio bitrate in kbps
*/
bitrateAudio?: number;
/**
* Enable audio output
*/
includeAudio?: boolean;
/**
* Enables hardware accelerated video decoding. Enabling this option might result in an exception
* being thrown by Ffmpeg process if your system does not support hardware acceleration
*/
hardwareAcceleratedDecoding?: boolean;
/**
* Output video codec. **Only** supports H264, H265, and VP8 currently
*/
videoCodec?: SupportedVideoCodec;
/**
* Adds ffmpeg params to minimize latency and start outputting video as fast as possible.
* Might create lag in video output in some rare cases
*/
minimizeLatency?: boolean;
/**
* Custom headers for HTTP requests
*/
customHeaders?: Record<string, string>;
/**
* Custom input options to pass directly to ffmpeg
* These will be added to the command *before* other options
*/
customInputOptions?: string[];
/**
* Custom ffmpeg flags/options to pass directly to ffmpeg
* These will be added to the command *after* other options
*/
customFfmpegFlags?: string[];
```
## `playStream` options available
```typescript
/**
* Set stream type as "Go Live" or camera stream
*/
type?: "go-live" | "camera",
/**
* Override video width sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
width?: number,
/**
* Override video height sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
height?: number,
/**
* Override video frame rate sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
frameRate?: number,
/**
* Same as ffmpeg's `readrate_initial_burst` command line flag
*
* See https://ffmpeg.org/ffmpeg.html#:~:text=%2Dreadrate_initial_burst
*/
readrateInitialBurst?: number,
```
## Performance tips
See [this page](./PERFORMANCE.md) for some tips on improving performance
## Running example
`examples/basic/src/config.json`:
```json
"token": "SELF TOKEN HERE",
"acceptedAuthors": ["USER_ID_HERE"],
```
1. Configure your `config.json` with your accepted authors ids, and your self token
2. Generate js files with ```npm run build```
3. Start program with: ```npm run start```
4. Join a voice channel
5. Start streaming with commands:
for go-live
```
$play-live <Direct video link>
```
or for cam
```
$play-cam <Direct video link>
```
for example:
```
$play-live http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
```
## FAQs
- Can I stream on existing voice connection (CAM) and in a go-live connection simultaneously?
Yes, just send the media packets over both connections. The voice gateway expects you to signal when a user turns on their camera, so make sure you signal using `client.signalVideo(guildId, channelId, true)` before you start sending cam media packets.
- Does this library work with bot tokens?
No, Discord blocks video from bots which is why this library uses a selfbot library as peer dependency. You must use a user token
-38
View File
@@ -1,38 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"includes": ["./src/**/*"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"assist": {
"actions": {
"source": {
"organizeImports": "off"
}
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noNonNullAssertion": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}
-3
View File
@@ -1,3 +0,0 @@
# basic example
This example shows how to stream a video, both using the existing voice connection or with a Go Live connection, using the new API introduced in v4.1.3
-23
View File
@@ -1,23 +0,0 @@
{
"name": "@dank074/discord-video-stream-example",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"dependencies": {
"@dank074/discord-video-stream": "^5.0.0",
"discord.js-selfbot-v13": "^3.5.1"
},
"devDependencies": {
"@types/node": "^22.10.1",
"typescript": "^5.7.2"
},
"scripts": {
"build": "tsc",
"start": "node ./dist/index.js",
"yeet": "npm run build && npm run start"
},
"author": "",
"license": "ISC"
}
@@ -1,13 +0,0 @@
{
"token": "SELF TOKEN HERE",
"acceptedAuthors": ["USER_ID_HERE"],
"streamOpts": {
"width": 1280,
"height": 720,
"fps": 30,
"bitrateKbps": 1000,
"maxBitrateKbps": 2500,
"hardware_acceleration": false,
"videoCodec": "H264"
}
}
-112
View File
@@ -1,112 +0,0 @@
import { Client, StageChannel } from "discord.js-selfbot-v13";
import { Streamer, Utils, prepareStream, playStream } from "@dank074/discord-video-stream";
import config from "./config.json" with {type: "json"};
const streamer = new Streamer(new Client());
// ready event
streamer.client.on("ready", () => {
console.log(`--- ${streamer.client.user?.tag} is ready ---`);
});
let controller: AbortController;
// message event
streamer.client.on("messageCreate", async (msg) => {
if (msg.author.bot) return;
if (!config.acceptedAuthors.includes(msg.author.id)) return;
if (!msg.content) return;
if (msg.content.startsWith("$play-live")) {
const args = parseArgs(msg.content)
if (!args) return;
const channel = msg.author.voice?.channel;
if(!channel) return;
console.log(`Attempting to join voice channel ${msg.guildId}/${channel.id}`);
await streamer.joinVoice(msg.guildId!, channel.id);
if (channel instanceof StageChannel)
{
await streamer.client.user?.voice?.setSuppressed(false);
}
controller?.abort();
controller = new AbortController();
const { command, output } = prepareStream(args.url, {
width: config.streamOpts.width,
height: config.streamOpts.height,
frameRate: config.streamOpts.fps,
bitrateVideo: config.streamOpts.bitrateKbps,
bitrateVideoMax: config.streamOpts.maxBitrateKbps,
hardwareAcceleratedDecoding: config.streamOpts.hardware_acceleration,
videoCodec: Utils.normalizeVideoCodec(config.streamOpts.videoCodec)
}, controller.signal);
command.on("error", (err) => {
console.log("An error happened with ffmpeg");
console.log(err);
});
await playStream(output, streamer, undefined, controller.signal)
.catch(() => controller.abort());
} else if (msg.content.startsWith("$play-cam")) {
const args = parseArgs(msg.content);
if (!args) return;
const channel = msg.author.voice?.channel;
if (!channel) return;
console.log(`Attempting to join voice channel ${msg.guildId}/${channel.id}`);
const vc = await streamer.joinVoice(msg.guildId!, channel.id);
if (channel instanceof StageChannel)
{
await streamer.client.user?.voice?.setSuppressed(false);
}
controller?.abort();
controller = new AbortController();
const { command, output } = prepareStream(args.url, {
width: config.streamOpts.width,
height: config.streamOpts.height,
frameRate: config.streamOpts.fps,
bitrateVideo: config.streamOpts.bitrateKbps,
bitrateVideoMax: config.streamOpts.maxBitrateKbps,
hardwareAcceleratedDecoding: config.streamOpts.hardware_acceleration,
videoCodec: Utils.normalizeVideoCodec(config.streamOpts.videoCodec)
}, controller.signal)
command.on("error", (err) => {
console.log("An error happened with ffmpeg");
console.log(err);
});
await playStream(output, streamer, undefined, controller.signal)
.catch(() => controller.abort());
} else if (msg.content.startsWith("$disconnect")) {
controller?.abort();
streamer.leaveVoice();
} else if(msg.content.startsWith("$stop-stream")) {
controller?.abort();
}
});
// login
streamer.client.login(config.token);
function parseArgs(message: string): Args | undefined {
const args = message.split(" ");
if (args.length < 2) return;
const url = args[1];
return { url }
}
type Args = {
url: string;
}
-106
View File
@@ -1,106 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "NodeNext", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/**/*"
]
}
@@ -1,3 +0,0 @@
# puppeteer stream example
This example shows how to use puppeteer stream to stream a browser window
@@ -1,25 +0,0 @@
{
"name": "@dank074/discord-video-stream-example-puppeteer",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"dependencies": {
"@dank074/discord-video-stream": "^5.0.0",
"discord.js-selfbot-v13": "^3.5.1",
"puppeteer": "^24.1.1",
"puppeteer-stream": "^3.0.19"
},
"devDependencies": {
"@types/node": "^22.13.1",
"typescript": "^5.7.3"
},
"scripts": {
"build": "tsc",
"start": "node ./dist/index.js",
"yeet": "npm run build && npm run start"
},
"author": "",
"license": "ISC"
}
@@ -1,13 +0,0 @@
{
"token": "SELF TOKEN HERE",
"acceptedAuthors": ["USER_ID_HERE"],
"streamOpts": {
"width": 1280,
"height": 720,
"fps": 30,
"bitrateKbps": 1000,
"maxBitrateKbps": 2500,
"hardware_acceleration": false,
"videoCodec": "H264"
}
}
@@ -1,104 +0,0 @@
import { Client, StageChannel } from 'discord.js-selfbot-v13';
import { Streamer, Utils, prepareStream, playStream } from "@dank074/discord-video-stream";
import { executablePath } from 'puppeteer';
import { launch, getStream } from 'puppeteer-stream';
import config from "./config.json" with {type: "json"};
type BrowserOptions = {
width: number,
height: number
}
const streamer = new Streamer(new Client());
let browser: Awaited<ReturnType<typeof launch>>;
// ready event
streamer.client.on("ready", () => {
console.log(`--- ${streamer.client.user?.tag} is ready ---`);
});
let controller: AbortController;
// message event
streamer.client.on("messageCreate", async (msg) => {
if (msg.author.bot) return;
if (!config.acceptedAuthors.includes(msg.author.id)) return;
if (!msg.content) return;
if (msg.content.startsWith("$play-screen")) {
const args = msg.content.split(" ");
if (args.length < 2) return;
const url = args[1];
if (!url) return;
const channel = msg.author.voice?.channel;
if (!channel) return;
console.log(`Attempting to join voice channel ${msg.guildId}/${channel.id}`);
await streamer.joinVoice(msg.guildId!, channel.id);
if (channel instanceof StageChannel)
{
await streamer.client.user?.voice?.setSuppressed(false);
}
controller?.abort();
controller = new AbortController();
await streamPuppeteer(url, streamer, {
width: config.streamOpts.width,
height: config.streamOpts.height
}, controller.signal);
} else if (msg.content.startsWith("$disconnect")) {
controller?.abort();
streamer.leaveVoice();
}
})
// login
streamer.client.login(config.token);
async function streamPuppeteer(url: string, streamer: Streamer, opts: BrowserOptions, cancelSignal?: AbortSignal) {
cancelSignal?.throwIfAborted();
cancelSignal?.addEventListener("abort", () => {
browser.close();
}, { once: true });
browser = await launch({
defaultViewport: {
width: opts.width,
height: opts.height,
},
executablePath: executablePath()
});
const page = await browser.newPage();
await page.goto(url);
const stream = await getStream(page, { audio: true, video: true, mimeType: "video/webm;codecs=vp8,opus" });
try {
const { command, output } = prepareStream(stream, {
frameRate: config.streamOpts.fps,
bitrateVideo: config.streamOpts.bitrateKbps,
bitrateVideoMax: config.streamOpts.maxBitrateKbps,
hardwareAcceleratedDecoding: config.streamOpts.hardware_acceleration,
videoCodec: Utils.normalizeVideoCodec(config.streamOpts.videoCodec)
}, cancelSignal);
command.on("error", (err, stdout, stderr) => {
console.log("An error occurred with ffmpeg");
console.log(err)
});
await playStream(output, streamer, {
// Use this to catch up with ffmpeg
readrateInitialBurst: 10
}, cancelSignal);
console.log("Finished playing video");
} catch (e) {
console.log(e);
}
}
@@ -1,106 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "NodeNext", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/**/*"
]
}
-70
View File
@@ -1,70 +0,0 @@
{
"name": "@dank074/discord-video-stream",
"version": "6.0.0",
"description": "Experiment for making video streaming work for discord selfbots",
"exports": "./dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"files": [
"dist",
"src"
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"@lng2004/node-datachannel": "0.32.0-20260202",
"@snazzah/davey": "^0.1.8",
"debug-level": "^4.1.1",
"fluent-ffmpeg-simplified": "^0.1.0",
"node-av": "^5.2.2",
"p-debounce": "^5.1.0",
"sharp": "^0.34.5",
"zeromq": "^6.5.0"
},
"devDependencies": {
"@biomejs/biome": "2.3.8",
"@types/fluent-ffmpeg": "^2.1.28",
"@types/node": "^25.0.1",
"discord.js-selfbot-v13": "workspace:*",
"pkg-pr-new": "^0.0.62",
"typescript": "^5.9.3"
},
"peerDependencies": {
"discord.js-selfbot-v13": "^3.6.0"
},
"engines": {
"node": ">=22.4.0"
},
"scripts": {
"build": "tsc",
"lint": "biome lint --error-on-warnings .",
"lint:fix": "biome lint --write .",
"check": "biome check .",
"check:fix": "biome check --write ."
},
"keywords": [
"discord",
"video",
"voice",
"stream",
"go-live"
],
"repository": {
"type": "git",
"url": "git+https://github.com/dank074/Discord-video-stream.git"
},
"contributors": [
"Long Nguyen <nguyen.long.908132@gmail.com>",
"s074 <torresefrain10@gmail.com>",
"mrjvs <jellevs@gmail.com>",
"Elysia <71698422+aiko-chan-ai@users.noreply.github.com>",
"Fede14 <fede.ferri2001@gmail.com>",
"Malthe Morsing Larsen <57196060+malthemorsing@users.noreply.github.com>"
],
"license": "ISC",
"bugs": {
"url": "https://github.com/dank074/Discord-video-stream/issues"
},
"homepage": "https://github.com/dank074/Discord-video-stream#readme"
}
-2689
View File
File diff suppressed because it is too large Load Diff
-48
View File
@@ -1,48 +0,0 @@
type GatewayEventGeneric<Type extends string = string, Data = unknown> = {
t: Type;
d: Data;
};
export namespace GatewayEvent {
export type VoiceStateUpdate = GatewayEventGeneric<
"VOICE_STATE_UPDATE",
{
user_id: string;
session_id: string;
}
>;
export type VoiceServerUpdate = GatewayEventGeneric<
"VOICE_SERVER_UPDATE",
{
guild_id: string;
channel_id?: string;
endpoint: string;
token: string;
}
>;
export type StreamCreate = GatewayEventGeneric<
"STREAM_CREATE",
{
stream_key: string;
rtc_server_id: string;
}
>;
export type StreamServerUpdate = GatewayEventGeneric<
"STREAM_SERVER_UPDATE",
{
stream_key: string;
endpoint: string;
token: string;
}
>;
}
export type GatewayEvent =
| GatewayEvent.VoiceStateUpdate
| GatewayEvent.VoiceServerUpdate
| GatewayEvent.StreamCreate
| GatewayEvent.StreamServerUpdate;
export type GatewayEventMap = {
[E in GatewayEvent as E["t"]]: [E["d"]];
};
@@ -1,40 +0,0 @@
export enum GatewayOpCodes {
DISPATCH = 0,
HEARTBEAT = 1,
IDENTIFY = 2,
PRESENCE_UPDATE = 3,
VOICE_STATE_UPDATE = 4,
VOICE_SERVER_PING = 5,
RESUME = 6,
RECONNECT = 7,
REQUEST_GUILD_MEMBERS = 8,
INVALID_SESSION = 9,
HELLO = 10,
HEARTBEAT_ACK = 11,
CALL_CONNECT = 13,
GUILD_SUBSCRIPTIONS = 14,
LOBBY_CONNECT = 15,
LOBBY_DISCONNECT = 16,
LOBBY_VOICE_STATES_UPDATE = 17,
STREAM_CREATE = 18,
STREAM_DELETE = 19,
STREAM_WATCH = 20,
STREAM_PING = 21,
STREAM_SET_PAUSED = 22,
REQUEST_GUILD_APPLICATION_COMMANDS = 24,
EMBEDDED_ACTIVITY_LAUNCH = 25,
EMBEDDED_ACTIVITY_CLOSE = 26,
EMBEDDED_ACTIVITY_UPDATE = 27,
REQUEST_FORUM_UNREADS = 28,
REMOTE_COMMAND = 29,
GET_DELETED_ENTITY_IDS_NOT_MATCHING_HASH = 30,
REQUEST_SOUNDBOARD_SOUNDS = 31,
SPEED_TEST_CREATE = 32,
SPEED_TEST_DELETE = 33,
REQUEST_LAST_MESSAGES = 34,
SEARCH_RECENT_MEMBERS = 35,
REQUEST_CHANNEL_STATUSES = 36,
GUILD_SUBSCRIPTIONS_BULK = 37,
GUILD_CHANNELS_RESYNC = 38,
REQUEST_CHANNEL_MEMBER_COUNT = 39,
}
-259
View File
@@ -1,259 +0,0 @@
import { EventEmitter } from "node:events";
import { VoiceConnection } from "./voice/VoiceConnection.js";
import { StreamConnection } from "./voice/StreamConnection.js";
import { GatewayOpCodes } from "./GatewayOpCodes.js";
import type {
Client,
DMChannel,
GroupDMChannel,
VoiceBasedChannel,
} from "discord.js-selfbot-v13";
import type { GatewayEvent, GatewayEventMap } from "./GatewayEvents.js";
import type { WebRtcConnWrapper } from "./voice/WebRtcWrapper.js";
import { generateStreamKey, parseStreamKey } from "../utils.js";
export class Streamer {
private _voiceConnection?: VoiceConnection;
private _client: Client;
private _gatewayEmitter = new EventEmitter<GatewayEventMap>();
constructor(client: Client) {
this._client = client;
//listen for messages
this.client.on("raw", (packet: GatewayEvent) => {
// @ts-expect-error I don't know how to make this work with TypeScript, so whatever
this._gatewayEmitter.emit(packet.t, packet.d);
});
}
public get client(): Client {
return this._client;
}
public get opts() {
return {};
}
public get voiceConnection(): VoiceConnection | undefined {
return this._voiceConnection;
}
public sendOpcode(code: number, data: unknown): void {
this.client.ws.broadcast({
op: code,
d: data,
});
}
public joinVoiceChannel(
channel: DMChannel | GroupDMChannel | VoiceBasedChannel,
): Promise<WebRtcConnWrapper> {
let guildId: string | null = null;
if (
channel.type === "GUILD_STAGE_VOICE" ||
channel.type === "GUILD_VOICE"
) {
guildId = channel.guildId;
}
return this.joinVoice(guildId, channel.id);
}
/**
* Joins a voice channel and returns a WebRtcConnWrapper object.
* @param guild_id the guild id of the voice channel. If null, it will join a DM voice channel.
* @param channel_id the channel id of the voice channel
* @returns the WebRtcConnWrapper object
* @throws Error if the client is not logged in
*/
public joinVoice(
guild_id: string | null,
channel_id: string,
): Promise<WebRtcConnWrapper> {
return new Promise<WebRtcConnWrapper>((resolve, reject) => {
if (!this.client.user) {
reject("Client not logged in");
return;
}
const user_id = this.client.user.id;
const voiceConn = new VoiceConnection(
this,
guild_id,
user_id,
channel_id,
(conn) => {
resolve(conn);
},
);
this._voiceConnection = voiceConn;
this._gatewayEmitter.on("VOICE_STATE_UPDATE", (d) => {
if (user_id !== d.user_id) return;
voiceConn.setSession(d.session_id);
});
this._gatewayEmitter.on("VOICE_SERVER_UPDATE", (d) => {
if (guild_id !== d.guild_id) return;
// channel_id is not set for guild voice calls
if (d.channel_id && channel_id !== d.channel_id) return;
voiceConn.setTokens(d.endpoint, d.token);
});
this.signalVideo(false);
});
}
public createStream(): Promise<WebRtcConnWrapper> {
return new Promise<WebRtcConnWrapper>((resolve, reject) => {
if (!this.client.user) {
reject("Client not logged in");
return;
}
if (!this.voiceConnection) {
reject("cannot start stream without first joining voice channel");
return;
}
this.signalStream();
const {
guildId: clientGuildId,
channelId: clientChannelId,
session_id,
} = this.voiceConnection;
const { id: clientUserId } = this.client.user;
if (!session_id) throw new Error("Session doesn't exist yet");
const streamConn = new StreamConnection(
this,
clientGuildId,
clientUserId,
clientChannelId,
(conn) => {
resolve(conn);
},
);
this.voiceConnection.streamConnection = streamConn;
this._gatewayEmitter.on("STREAM_CREATE", (d) => {
const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
if (
clientGuildId !== guildId ||
clientChannelId !== channelId ||
clientUserId !== userId
)
return;
streamConn.serverId = d.rtc_server_id;
streamConn.streamKey = d.stream_key;
streamConn.setSession(session_id);
});
this._gatewayEmitter.on("STREAM_SERVER_UPDATE", (d) => {
const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
if (
clientGuildId !== guildId ||
clientChannelId !== channelId ||
clientUserId !== userId
)
return;
streamConn.setTokens(d.endpoint, d.token);
});
});
}
public async setStreamPreview(image: Buffer): Promise<void> {
if (!this.client.token) throw new Error("Please login :)");
if (!this.voiceConnection?.streamConnection?.guildId) return;
const data = `data:image/jpeg;base64,${image.toString("base64")}`;
const { guildId } = this.voiceConnection.streamConnection;
const server = await this.client.guilds.fetch(guildId);
await server.members.me?.voice.postPreview(data);
}
public stopStream(): void {
const stream = this.voiceConnection?.streamConnection;
if (!stream) return;
stream.stop();
this.signalStopStream();
this.voiceConnection.streamConnection = undefined;
this._gatewayEmitter.removeAllListeners("STREAM_CREATE");
this._gatewayEmitter.removeAllListeners("STREAM_SERVER_UPDATE");
}
public leaveVoice(): void {
this.voiceConnection?.stop();
this.signalLeaveVoice();
this._voiceConnection = undefined;
this._gatewayEmitter.removeAllListeners("VOICE_STATE_UPDATE");
this._gatewayEmitter.removeAllListeners("VOICE_SERVER_UPDATE");
}
public signalVideo(video_enabled: boolean): void {
if (!this.voiceConnection) return;
const { guildId: guild_id, channelId: channel_id } = this.voiceConnection;
this.sendOpcode(GatewayOpCodes.VOICE_STATE_UPDATE, {
guild_id: guild_id,
channel_id,
self_mute: false,
self_deaf: true,
self_video: video_enabled,
});
}
public signalStream(): void {
if (!this.voiceConnection) return;
const {
type,
guildId: guild_id,
channelId: channel_id,
botId: user_id,
} = this.voiceConnection;
const streamKey = generateStreamKey(type, guild_id, channel_id, user_id);
this.sendOpcode(GatewayOpCodes.STREAM_CREATE, {
type,
guild_id,
channel_id,
preferred_region: null,
});
this.sendOpcode(GatewayOpCodes.STREAM_SET_PAUSED, {
stream_key: streamKey,
paused: false,
});
}
public signalStopStream(): void {
if (!this.voiceConnection) return;
const {
type,
guildId: guild_id,
channelId: channel_id,
botId: user_id,
} = this.voiceConnection;
const streamKey = generateStreamKey(type, guild_id, channel_id, user_id);
this.sendOpcode(GatewayOpCodes.STREAM_DELETE, {
stream_key: streamKey,
});
}
public signalLeaveVoice(): void {
this.sendOpcode(GatewayOpCodes.VOICE_STATE_UPDATE, {
guild_id: null,
channel_id: null,
self_mute: true,
self_deaf: false,
self_video: false,
});
}
}
-3
View File
@@ -1,3 +0,0 @@
export * from "./voice/index.js";
export * from "./GatewayOpCodes.js";
export * from "./Streamer.js";
@@ -1,148 +0,0 @@
export class AnnexBBitstreamReader {
private _buffer: Buffer;
private _byteOffset = 0;
private _bitOffset = 0;
constructor(buffer: Buffer) {
this._buffer = buffer;
}
public readBits(count: number) {
if (count === 0) return 0;
let result = 0;
while (count > 0) {
if (this._byteOffset >= this._buffer.length)
throw new Error("Bad byte offset");
if (
this._bitOffset === 0 &&
this._byteOffset >= 2 &&
this._buffer[this._byteOffset - 2] === 0 &&
this._buffer[this._byteOffset - 1] === 0 &&
this._buffer[this._byteOffset] === 3
) {
// Skip over emulation prevention
this._byteOffset++;
}
if (this._bitOffset === 0 && count >= 8) {
// We're byte aligned, read whole bytes and push in
result = (result << 8) | this._buffer[this._byteOffset++];
count -= 8;
} else {
// Read just enough to get us to the next byte
const numBitsToRead = Math.min(count, 8 - this._bitOffset);
const mask = (1 << numBitsToRead) - 1;
const newBits =
(this._buffer[this._byteOffset] >>
(8 - this._bitOffset - numBitsToRead)) &
mask;
result = (result << numBitsToRead) | newBits;
count -= numBitsToRead;
this._bitOffset += numBitsToRead;
if (this._bitOffset === 8) {
this._bitOffset = 0;
this._byteOffset++;
}
}
}
return result;
}
public readUnsigned(bits: number) {
return this.readBits(bits);
}
public readSigned(bits: number) {
const unsigned = this.readUnsigned(bits);
if (unsigned & (1 << (bits - 1))) return unsigned - (1 << bits);
return unsigned;
}
public readUnsignedExpGolomb() {
let leading0 = 0;
while (this.readBits(1) === 0) leading0++;
return (1 << leading0) + this.readBits(leading0) - 1;
}
public readSignedExpGolomb() {
// Mapping: x <= 0 => -2x, x > 0 => 2x - 1
const unsigned = this.readUnsignedExpGolomb();
if (unsigned % 2 === 0) return unsigned / -2;
return (unsigned + 1) / 2;
}
}
export class AnnexBBitstreamWriter {
private _arr: number[] = [];
private _pendingByte = 0;
private _bitOffset = 0;
public toBuffer() {
return Buffer.from(this._arr);
}
public flush() {
// Write the pending byte into the array and reset, taking care of emulation prevention
if (
this._pendingByte <= 3 &&
this._arr.at(-1) === 0 &&
this._arr.at(-2) === 0
)
this._arr.push(3);
this._arr.push(this._pendingByte);
this._pendingByte = 0;
this._bitOffset = 0;
}
public writeBits(bits: number, count: number) {
while (count > 0) {
if (this._bitOffset === 0) {
if (count >= 8) {
// We're byte aligned and has more than 1 byte left to write, write a whole byte
this._pendingByte = (bits >> (count - 8)) & 0xff;
count -= 8;
this.flush();
} else {
// We have less than 1 byte, write the rest in
const mask = (1 << count) - 1;
this._pendingByte |= (bits & mask) << (8 - count);
this._bitOffset = count;
count = 0;
}
} else {
// Write the minimum number of bits to get us byte aligned again
const numBitsToWrite = Math.min(8 - this._bitOffset, count);
const bitsToWrite =
(bits >> (count - numBitsToWrite)) & ((1 << numBitsToWrite) - 1);
this._pendingByte |=
bitsToWrite << (8 - this._bitOffset - numBitsToWrite);
count -= numBitsToWrite;
this._bitOffset += numBitsToWrite;
if (this._bitOffset === 8) {
this._bitOffset = 0;
this.flush();
}
}
}
}
public writeUnsigned(num: number, count: number) {
if (num < 0) throw new Error("Expected a non-negative number");
this.writeBits(num, count);
}
public writeSigned(num: number, count: number) {
if (count <= 0) return;
if (count > 32) throw new Error("writeSigned supports up to 32 bits");
// Build mask for `count` bits. Handle 32-bit as a special case.
const mask =
count === 32 ? 0xffffffff >>> 0 : (((1 << count) >>> 0) - 1) >>> 0;
// Convert to two's-complement unsigned representation and write
const unsigned = (num & mask) >>> 0;
this.writeBits(unsigned, count);
}
public writeUnsignedExpGolomb(num: number) {
if (num < 0) throw new Error("Expected a non-negative number");
num++;
const bitCount = 32 - Math.clz32(num >>> 0);
this.writeBits(0, bitCount - 1);
this.writeBits(num, bitCount);
}
public writeSignedExpGolomb(num: number) {
if (num < 0) this.writeUnsignedExpGolomb(-2 * num);
else this.writeUnsignedExpGolomb(2 * num - 1);
}
}
@@ -1,134 +0,0 @@
export enum H264NalUnitTypes {
Unspecified = 0,
CodedSliceNonIDR = 1,
CodedSlicePartitionA = 2,
CodedSlicePartitionB = 3,
CodedSlicePartitionC = 4,
CodedSliceIdr = 5,
SEI = 6,
SPS = 7,
PPS = 8,
AccessUnitDelimiter = 9,
EndOfSequence = 10,
EndOfStream = 11,
FillerData = 12,
SEIExtenstion = 13,
PrefixNalUnit = 14,
SubsetSPS = 15,
}
export enum H265NalUnitTypes {
TRAIL_N = 0,
TRAIL_R = 1,
TSA_N = 2,
TSA_R = 3,
STSA_N = 4,
STSA_R = 5,
RADL_N = 6,
RADL_R = 7,
RASL_N = 8,
RASL_R = 9,
RSV_VCL_N10 = 10,
RSV_VCL_R11 = 11,
RSV_VCL_N12 = 12,
RSV_VCL_R13 = 13,
RSV_VCL_N14 = 14,
RSV_VCL_R15 = 15,
BLA_W_LP = 16,
BLA_W_RADL = 17,
BLA_N_LP = 18,
IDR_W_RADL = 19,
IDR_N_LP = 20,
CRA_NUT = 21,
RSV_IRAP_VCL22 = 22,
RSV_IRAP_VCL23 = 23,
RSV_VCL24 = 24,
RSV_VCL25 = 25,
RSV_VCL26 = 26,
RSV_VCL27 = 27,
RSV_VCL28 = 28,
RSV_VCL29 = 29,
RSV_VCL30 = 30,
RSV_VCL31 = 31,
VPS_NUT = 32,
SPS_NUT = 33,
PPS_NUT = 34,
AUD_NUT = 35,
EOS_NUT = 36,
EOB_NUT = 37,
FD_NUT = 38,
PREFIX_SEI_NUT = 39,
SUFFIX_SEI_NUT = 40,
RSV_NVCL41 = 41,
RSV_NVCL42 = 42,
RSV_NVCL43 = 43,
RSV_NVCL44 = 44,
RSV_NVCL45 = 45,
RSV_NVCL46 = 46,
RSV_NVCL47 = 47,
UNSPEC48 = 48,
UNSPEC49 = 49,
UNSPEC50 = 50,
UNSPEC51 = 51,
UNSPEC52 = 52,
UNSPEC53 = 53,
UNSPEC54 = 54,
UNSPEC55 = 55,
UNSPEC56 = 56,
UNSPEC57 = 57,
UNSPEC58 = 58,
UNSPEC59 = 59,
UNSPEC60 = 60,
UNSPEC61 = 61,
UNSPEC62 = 62,
UNSPEC63 = 63,
}
export interface AnnexBHelpers {
getUnitType(frame: Buffer): number;
splitHeader(frame: Buffer): [Buffer, Buffer];
isAUD(unitType: number): boolean;
}
export const H264Helpers: AnnexBHelpers = {
getUnitType(frame) {
return frame[0] & 0x1f;
},
splitHeader(frame) {
return [frame.subarray(0, 1), frame.subarray(1)];
},
isAUD(unitType) {
return unitType === H264NalUnitTypes.AccessUnitDelimiter;
},
};
export const H265Helpers: AnnexBHelpers = {
getUnitType(frame) {
return (frame[0] >> 1) & 0x3f;
},
splitHeader(frame) {
return [frame.subarray(0, 2), frame.subarray(2)];
},
isAUD(unitType) {
return unitType === H265NalUnitTypes.AUD_NUT;
},
};
export const startCode3 = Buffer.from([0, 0, 1]);
export function splitNalu(buf: Buffer) {
let temp: Buffer | null = buf;
const nalus: Buffer[] = [];
while (temp?.byteLength) {
let pos: number = temp.indexOf(startCode3);
let length = 3;
if (pos > 0 && temp[pos - 1] === 0) {
pos--;
length++;
}
const nalu = pos === -1 ? temp : temp.subarray(0, pos);
temp = pos === -1 ? null : temp.subarray(pos + length);
if (nalu.byteLength) nalus.push(nalu);
}
return nalus;
}
@@ -1,332 +0,0 @@
import {
AnnexBBitstreamReader,
AnnexBBitstreamWriter,
} from "./AnnexBBitstreamReaderWriter.js";
export function rewriteSPSVUI(buffer: Buffer) {
const reader = new AnnexBBitstreamReader(buffer.subarray(1));
const writer = new AnnexBBitstreamWriter();
const readBit = (n = 1) => reader.readBits(n);
const writeBit = (v: number, n = 1) => writer.writeBits(v, n);
const readU = (n: number) => reader.readUnsigned(n);
const writeU = (v: number, n: number) => writer.writeUnsigned(v, n);
const readUE = () => reader.readUnsignedExpGolomb();
const writeUE = (v: number) => writer.writeUnsignedExpGolomb(v);
const readSE = () => reader.readSignedExpGolomb();
const writeSE = (v: number) => writer.writeSignedExpGolomb(v);
// Rewrite the NAL header
writeU(buffer[0], 8);
const profile_idc = readU(8);
writeU(profile_idc, 8);
const constraint_flags = readU(8);
writeU(constraint_flags, 8);
const level_idc = readU(8);
writeU(level_idc, 8);
const seq_parameter_set_id = readUE();
writeUE(seq_parameter_set_id);
// If profile in high profiles, additional fields
const highProfiles = new Set([
100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 144,
]);
if (highProfiles.has(profile_idc)) {
const chroma_format_idc = readUE();
writeUE(chroma_format_idc);
if (chroma_format_idc === 3) {
const separate_colour_plane_flag = readBit(1);
writeBit(separate_colour_plane_flag, 1);
}
const bit_depth_luma_minus8 = readUE();
writeUE(bit_depth_luma_minus8);
const bit_depth_chroma_minus8 = readUE();
writeUE(bit_depth_chroma_minus8);
const qpprime_y_zero_transform_bypass_flag = readBit(1);
writeBit(qpprime_y_zero_transform_bypass_flag, 1);
const seq_scaling_matrix_present_flag = readBit(1);
writeBit(seq_scaling_matrix_present_flag, 1);
if (seq_scaling_matrix_present_flag) {
const scalingCount = chroma_format_idc !== 3 ? 8 : 12;
for (let i = 0; i < scalingCount; i++) {
const seq_scaling_list_present_flag = readBit(1);
writeBit(seq_scaling_list_present_flag, 1);
if (seq_scaling_list_present_flag) {
const size = i < 6 ? 16 : 64;
// scaling_list(size)
let lastScale = 8;
let nextScale = 8;
for (let j = 0; j < size; j++) {
const delta = readSE();
writeSE(delta);
nextScale = (lastScale + delta + 256) % 256;
if (nextScale !== 0) lastScale = nextScale;
}
}
}
}
}
const log2_max_frame_num_minus4 = readUE();
writeUE(log2_max_frame_num_minus4);
const pic_order_cnt_type = readUE();
writeUE(pic_order_cnt_type);
if (pic_order_cnt_type === 0) {
const log2_max_pic_order_cnt_lsb_minus4 = readUE();
writeUE(log2_max_pic_order_cnt_lsb_minus4);
} else if (pic_order_cnt_type === 1) {
const delta_pic_order_always_zero_flag = readBit(1);
writeBit(delta_pic_order_always_zero_flag, 1);
const offset_for_non_ref_pic = readSE();
writeSE(offset_for_non_ref_pic);
const offset_for_top_to_bottom_field = readSE();
writeSE(offset_for_top_to_bottom_field);
const num_ref_frames_in_pic_order_cnt_cycle = readUE();
writeUE(num_ref_frames_in_pic_order_cnt_cycle);
for (let i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) {
const offset_for_ref_frame = readSE();
writeSE(offset_for_ref_frame);
}
}
const max_num_ref_frames = readUE();
writeUE(max_num_ref_frames);
const gaps_in_frame_num_value_allowed_flag = readBit(1);
writeBit(gaps_in_frame_num_value_allowed_flag, 1);
const pic_width_in_mbs_minus1 = readUE();
writeUE(pic_width_in_mbs_minus1);
const pic_height_in_map_units_minus1 = readUE();
writeUE(pic_height_in_map_units_minus1);
const frame_mbs_only_flag = readBit(1);
writeBit(frame_mbs_only_flag, 1);
if (frame_mbs_only_flag === 0) {
const mb_adaptive_frame_field_flag = readBit(1);
writeBit(mb_adaptive_frame_field_flag, 1);
}
const direct_8x8_inference_flag = readBit(1);
writeBit(direct_8x8_inference_flag, 1);
const frame_cropping_flag = readBit(1);
writeBit(frame_cropping_flag, 1);
if (frame_cropping_flag) {
const frame_crop_left_offset = readUE();
writeUE(frame_crop_left_offset);
const frame_crop_right_offset = readUE();
writeUE(frame_crop_right_offset);
const frame_crop_top_offset = readUE();
writeUE(frame_crop_top_offset);
const frame_crop_bottom_offset = readUE();
writeUE(frame_crop_bottom_offset);
}
// https://webrtc.googlesource.com/src/+/5f2c9278f35e47ff72eb191669d473b7400c9f3e/common_video/h264/sps_vui_rewriter.cc#283
function addBitstreamRestriction() {
// motion_vectors_over_pic_boundaries_flag: u(1)
// Default is 1 when not present.
writeBit(1, 1);
// max_bytes_per_pic_denom: ue(v)
// Default is 2 when not present.
writeUE(2);
// max_bits_per_mb_denom: ue(v)
// Default is 1 when not present.
writeUE(1);
// log2_max_mv_length_horizontal: ue(v)
// log2_max_mv_length_vertical: ue(v)
// Both default to 16 when not present.
writeUE(16);
writeUE(16);
// ********* IMPORTANT! **********
// max_num_reorder_frames: ue(v)
writeUE(0);
// max_dec_frame_buffering: ue(v)
writeUE(max_num_ref_frames);
}
const vui_parameters_present_flag = readBit(1);
writeBit(1, 1);
// If no VUI exists, write one
if (!vui_parameters_present_flag) {
// aspect_ratio_info_present_flag, overscan_info_present_flag. Both u(1).
writeBit(0, 2);
// video_signal_type_present_flag, u(1).
// Just write 0 here because I'm not gonna bother myself with color space and whatnot
writeBit(0, 1);
// chroma_loc_info_present_flag, timing_info_present_flag,
// nal_hrd_parameters_present_flag, vcl_hrd_parameters_present_flag,
// pic_struct_present_flag, All u(1)
writeBit(0, 5);
// bitstream_restriction_flag: u(1)
writeBit(1, 1);
addBitstreamRestriction();
} else {
// VUI parsing and copying
const aspect_ratio_info_present_flag = readBit(1);
writeBit(aspect_ratio_info_present_flag, 1);
if (aspect_ratio_info_present_flag) {
const aspect_ratio_idc = readU(8);
writeU(aspect_ratio_idc, 8);
if (aspect_ratio_idc === 255) {
// Extended_SAR
const sar_width = readU(16);
writeU(sar_width, 16);
const sar_height = readU(16);
writeU(sar_height, 16);
}
}
const overscan_info_present_flag = readBit(1);
writeBit(overscan_info_present_flag, 1);
if (overscan_info_present_flag) {
const overscan_appropriate_flag = readBit(1);
writeBit(overscan_appropriate_flag, 1);
}
// Read the video signal type, but don't copy it
const video_signal_type_present_flag = readBit(1);
writeBit(0, 1);
if (video_signal_type_present_flag) {
const _video_format = readBit(3);
// writeBit(video_format, 3);
const _video_full_range_flag = readBit(1);
// writeBit(video_full_range_flag, 1);
const colour_description_present_flag = readBit(1);
// writeBit(colour_description_present_flag, 1);
if (colour_description_present_flag) {
const _colour_primaries = readU(8);
// writeU(colour_primaries, 8);
const _transfer_characteristics = readU(8);
// writeU(transfer_characteristics, 8);
const _matrix_coeffs = readU(8);
// writeU(matrix_coeffs, 8);
}
}
const chroma_loc_info_present_flag = readBit(1);
writeBit(chroma_loc_info_present_flag, 1);
if (chroma_loc_info_present_flag) {
const chroma_sample_loc_type_top_field = readUE();
writeUE(chroma_sample_loc_type_top_field);
const chroma_sample_loc_type_bottom_field = readUE();
writeUE(chroma_sample_loc_type_bottom_field);
}
const timing_info_present_flag = readBit(1);
writeBit(timing_info_present_flag, 1);
if (timing_info_present_flag) {
const num_units_in_tick = readU(32);
writeU(num_units_in_tick, 32);
const time_scale = readU(32);
writeU(time_scale, 32);
const fixed_frame_rate_flag = readBit(1);
writeBit(fixed_frame_rate_flag, 1);
}
const nal_hrd_parameters_present_flag = readBit(1);
writeBit(nal_hrd_parameters_present_flag, 1);
if (nal_hrd_parameters_present_flag) {
// hrd_parameters()
const cpb_cnt_minus1 = readUE();
writeUE(cpb_cnt_minus1);
const bit_rate_scale = readBit(4);
writeBit(bit_rate_scale, 4);
const cpb_size_scale = readBit(4);
writeBit(cpb_size_scale, 4);
for (let i = 0; i <= cpb_cnt_minus1; i++) {
const bit_rate_value_minus1 = readUE();
writeUE(bit_rate_value_minus1);
const cpb_size_value_minus1 = readUE();
writeUE(cpb_size_value_minus1);
const cbr_flag = readBit(1);
writeBit(cbr_flag, 1);
}
const initial_cpb_removal_delay_length_minus1 = readBit(5);
writeBit(initial_cpb_removal_delay_length_minus1, 5);
const cpb_removal_delay_length_minus1 = readBit(5);
writeBit(cpb_removal_delay_length_minus1, 5);
const dpb_output_delay_length_minus1 = readBit(5);
writeBit(dpb_output_delay_length_minus1, 5);
const time_offset_length = readBit(5);
writeBit(time_offset_length, 5);
}
const vcl_hrd_parameters_present_flag = readBit(1);
writeBit(vcl_hrd_parameters_present_flag, 1);
if (vcl_hrd_parameters_present_flag) {
// hrd_parameters()
const cpb_cnt_minus1 = readUE();
writeUE(cpb_cnt_minus1);
const bit_rate_scale = readBit(4);
writeBit(bit_rate_scale, 4);
const cpb_size_scale = readBit(4);
writeBit(cpb_size_scale, 4);
for (let i = 0; i <= cpb_cnt_minus1; i++) {
const bit_rate_value_minus1 = readUE();
writeUE(bit_rate_value_minus1);
const cpb_size_value_minus1 = readUE();
writeUE(cpb_size_value_minus1);
const cbr_flag = readBit(1);
writeBit(cbr_flag, 1);
}
const initial_cpb_removal_delay_length_minus1 = readBit(5);
writeBit(initial_cpb_removal_delay_length_minus1, 5);
const cpb_removal_delay_length_minus1 = readBit(5);
writeBit(cpb_removal_delay_length_minus1, 5);
const dpb_output_delay_length_minus1 = readBit(5);
writeBit(dpb_output_delay_length_minus1, 5);
const time_offset_length = readBit(5);
writeBit(time_offset_length, 5);
}
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
const low_delay_hrd_flag = readBit(1);
writeBit(low_delay_hrd_flag, 1);
}
const pic_struct_present_flag = readBit(1);
writeBit(pic_struct_present_flag, 1);
const bitstream_restriction_flag = readBit(1);
writeBit(1, 1);
if (!bitstream_restriction_flag) {
addBitstreamRestriction();
} else {
const motion_vectors_over_pic_boundaries_flag = readBit(1);
writeBit(motion_vectors_over_pic_boundaries_flag, 1);
const max_bytes_per_pic_denom = readUE();
writeUE(max_bytes_per_pic_denom);
const max_bits_per_mb_denom = readUE();
writeUE(max_bits_per_mb_denom);
const log2_max_mv_length_horizontal = readUE();
writeUE(log2_max_mv_length_horizontal);
const log2_max_mv_length_vertical = readUE();
writeUE(log2_max_mv_length_vertical);
const _num_reorder_frames = readUE();
writeUE(0);
const _max_dec_frame_buffering = readUE();
writeUE(max_num_ref_frames);
}
}
writeBit(1, 1); // rbsp_stop_one_bit
writer.flush();
// return the rewritten RBSP as a buffer
return writer.toBuffer();
}
@@ -1,640 +0,0 @@
import Davey from "@snazzah/davey";
import EventEmitter from "node:events";
import { Log } from "debug-level";
import { randomUUID } from "node:crypto";
import { CodecPayloadType } from "./CodecPayloadType.js";
import { WebRtcConnWrapper } from "./WebRtcWrapper.js";
import { VoiceOpCodes, VoiceOpCodesBinary } from "./VoiceOpCodes.js";
import {
STREAMS_SIMULCAST,
type SupportedEncryptionModes,
} from "../../utils.js";
import type {
Message,
GatewayRequest,
GatewayResponse,
} from "./VoiceMessageTypes.js";
import type { Streamer } from "../Streamer.js";
type VoiceConnectionStatus = {
hasSession: boolean;
hasToken: boolean;
started: boolean;
resuming: boolean;
};
type WebRtcParameters = {
address: string;
port: number;
audioSsrc: number;
videoSsrc: number;
rtxSsrc: number;
supportedEncryptionModes: SupportedEncryptionModes[];
};
type ValueOf<T> = T extends (infer U)[]
? U
: T extends Record<string, infer U>
? U
: never;
export type VideoAttributes = {
width: number;
height: number;
fps: number;
};
export abstract class BaseMediaConnection extends EventEmitter {
private interval: NodeJS.Timeout | null = null;
public guildId: string | null = null;
public channelId: string;
public botId: string;
public ws: WebSocket | null = null;
public status: VoiceConnectionStatus;
public server: string | null = null; //websocket url
public token: string | null = null;
public session_id: string | null = null;
private _webRtcWrapper;
private _webRtcParams: WebRtcParameters | null = null;
private _closed = false;
public ready: (conn: WebRtcConnWrapper) => void;
private _streamer: Streamer;
private _sequenceNumber = -1;
private _daveSession: Davey.DaveSession | undefined;
private _connectedUsers = new Set<string>();
private _daveProtocolVersion = 0;
private _davePendingTransitions = new Map<number, number>();
private _daveDowngraded = false;
private _logger = new Log("conn");
private _loggerDave = new Log("conn:dave");
constructor(
streamer: Streamer,
guildId: string | null,
botId: string,
channelId: string,
callback: (conn: WebRtcConnWrapper) => void,
) {
super();
this._streamer = streamer;
this.status = {
hasSession: false,
hasToken: false,
started: false,
resuming: false,
};
this.guildId = guildId;
this.channelId = channelId;
this.botId = botId;
this.ready = callback;
this._webRtcWrapper = new WebRtcConnWrapper(this);
}
public abstract get serverId(): string | null;
public get type(): "guild" | "call" {
return this.guildId ? "guild" : "call";
}
public get webRtcConn() {
return this._webRtcWrapper;
}
public get webRtcParams() {
return this._webRtcParams;
}
public get streamer() {
return this._streamer;
}
public abstract get daveChannelId(): string;
stop(): void {
this._closed = true;
this._webRtcWrapper.close();
this.ws?.close();
}
setSession(session_id: string): void {
this.session_id = session_id;
this.status.hasSession = true;
this.start();
}
setTokens(server: string, token: string): void {
this.token = token;
this.server = server;
this.status.hasToken = true;
this.start();
}
start(): void {
/*
** Connection can only start once both
** session description and tokens have been gathered
*/
if (this.status.hasSession && this.status.hasToken) {
if (this.status.started) return;
this.status.started = true;
this.ws = new WebSocket(`wss://${this.server}/?v=8`);
this.ws.binaryType = "arraybuffer";
this.ws.addEventListener("open", () => {
if (this.status.resuming) {
this.status.resuming = false;
this.resume();
} else {
this.identify();
}
});
this.ws.addEventListener("error", (err) => {
console.error(err);
});
this.ws.addEventListener("close", (e) => {
const wasStarted = this.status.started;
this.interval && clearInterval(this.interval);
this.status.started = false;
const canResume = e.code === 4_015 || e.code < 4_000;
if (canResume && wasStarted) {
this.status.resuming = true;
this.start();
} else {
this._closed = true;
this._webRtcWrapper?.close();
}
});
this.setupEvents();
}
}
handleReady(d: Message.Ready): void {
// we hardcoded the STREAMS_SIMULCAST, which will always be array of 1
const stream = d.streams[0];
this._webRtcParams = {
address: d.ip,
port: d.port,
audioSsrc: d.ssrc,
videoSsrc: stream.ssrc,
rtxSsrc: stream.rtx_ssrc,
supportedEncryptionModes: d.modes,
};
}
async handleProtocolAck(d: Message.SelectProtocolAck) {
if (!("sdp" in d)) throw new Error("Only WebRTC connections are allowed");
this._daveProtocolVersion = d.dave_protocol_version;
this.initDave();
// Discord's SDP is absolute garbage...Generate one ourselves
let ip = "",
port = "",
iceUsername = "",
icePassword = "",
fingerprint = "",
candidate = "";
for (const line of d.sdp.split("\n")) {
if (line.startsWith("c=")) ip = line;
else if (line.startsWith("a=rtcp")) port = line.split(":")[1];
else if (line.startsWith("a=ice-ufrag")) iceUsername = line;
else if (line.startsWith("a=ice-pwd")) icePassword = line;
else if (line.startsWith("a=fingerprint")) fingerprint = line;
else if (line.startsWith("a=candidate")) candidate = line;
}
const audioPayloadType = CodecPayloadType.opus.payload_type;
const audioSection = `
m=audio ${port} UDP/TLS/RTP/SAVPF ${audioPayloadType}
${ip}
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=setup:passive
a=mid:0
a=maxptime:60
a=inactive
${iceUsername}
${icePassword}
${fingerprint}
${candidate}
a=rtcp-mux
a=rtpmap:${audioPayloadType} opus/48000/2
a=fmtp:${audioPayloadType} minptime=10;useinbandfec=1;usedtx=1
a=rtcp-fb:${audioPayloadType} transport-cc
a=rtcp-fb:${audioPayloadType} nack
a=ice-lite
`.trim();
const videoPayloads = Object.values(CodecPayloadType).filter(
(el) => el.type === "video",
);
const videoPayloadTypes = videoPayloads.flatMap((el) => [
el.payload_type,
el.rtx_payload_type,
]);
const videoSection = `
m=video ${port} UDP/TLS/RTP/SAVPF ${videoPayloadTypes.join(" ")}
${ip}
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:13 urn:3gpp:video-orientation
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=setup:passive
a=mid:1
a=inactive
${iceUsername}
${icePassword}
${fingerprint}
${candidate}
a=rtcp-mux
a=ice-lite
`.trim();
const videoRtpMap = videoPayloads
.flatMap((el) => [
`a=rtpmap:${el.payload_type} ${el.name}/90000`,
`a=rtpmap:${el.rtx_payload_type} rtx/90000`,
`a=fmtp:${el.rtx_payload_type} apt=${el.payload_type}`,
`a=rtcp-fb:${el.payload_type} ccm fir`,
`a=rtcp-fb:${el.payload_type} nack`,
`a=rtcp-fb:${el.payload_type} nack pli`,
`a=rtcp-fb:${el.payload_type} goog-remb`,
`a=rtcp-fb:${el.payload_type} transport-cc`,
])
.join("\n");
this._webRtcWrapper.webRtcConn?.setRemoteDescription(
[audioSection, videoSection, videoRtpMap].join("\n"),
"answer",
);
this.emit("select_protocol_ack");
}
initDave() {
if (this._daveProtocolVersion) {
if (this._daveSession) {
this._daveSession.reinit(
this._daveProtocolVersion,
this.botId,
this.daveChannelId,
);
this._loggerDave.debug(`Reinitialized DAVE`, {
user_id: this.botId,
channel_id: this.daveChannelId,
});
} else {
this._daveSession = new Davey.DAVESession(
this._daveProtocolVersion,
this.botId,
this.daveChannelId,
);
this._loggerDave.debug(`Initialized DAVE`, {
user_id: this.botId,
channel_id: this.daveChannelId,
});
}
this.sendOpcodeBinary(
VoiceOpCodesBinary.MLS_KEY_PACKAGE,
this._daveSession.getSerializedKeyPackage(),
);
} else if (this._daveSession) {
this._daveSession.reset();
this._daveSession.setPassthroughMode(true, 10);
}
}
processInvalidCommit(transitionId: number) {
this._loggerDave.debug("Invalid commit received, reinitializing DAVE", {
transitionId,
});
this.sendOpcode(VoiceOpCodes.MLS_INVALID_COMMIT_WELCOME, {
transition_id: transitionId,
});
this.initDave();
}
executePendingTransition(transitionId: number) {
const newVersion = this._davePendingTransitions.get(transitionId);
if (newVersion === undefined) {
this._loggerDave.error("Unrecognized transition ID", { transitionId });
return;
}
const oldVersion = this._daveProtocolVersion;
this._daveProtocolVersion = newVersion;
if (oldVersion !== newVersion && newVersion === 0) {
// Downgraded
this._daveDowngraded = true;
this._loggerDave.debug("Downgraded to non-E2E voice call");
} else if (transitionId > 0 && this._daveDowngraded) {
this._daveDowngraded = false;
this._daveSession?.setPassthroughMode(true, 10);
this._loggerDave.debug("Upgraded to E2E voice call");
}
this._davePendingTransitions.delete(transitionId);
this._loggerDave.debug(`Pending transition ID ${transitionId} executed`, {
transitionId,
});
}
setupEvents(): void {
this.ws?.addEventListener("message", async (e) => {
if (e.data instanceof ArrayBuffer) {
this.handleBinaryMessages(Buffer.from(e.data));
return;
}
const { op, d, seq } = JSON.parse(e.data as string) as GatewayResponse;
if (seq) this._sequenceNumber = seq;
if (op === VoiceOpCodes.READY) {
// ready
this.handleReady(d);
this.setProtocols().then(() => this.ready(this._webRtcWrapper));
this.setVideoAttributes(false);
} else if (op >= 4000) {
console.error(`Error ${this.constructor.name} connection`, d);
} else if (op === VoiceOpCodes.HELLO) {
this.setupHeartbeat(d.heartbeat_interval);
} else if (op === VoiceOpCodes.SELECT_PROTOCOL_ACK) {
// session description
this.handleProtocolAck(d);
} else if (op === VoiceOpCodes.SPEAKING) {
// ignore speaking updates
} else if (op === VoiceOpCodes.HEARTBEAT_ACK) {
// ignore heartbeat acknowledgements
} else if (op === VoiceOpCodes.RESUMED) {
this.status.started = true;
} else if (op === VoiceOpCodes.CLIENTS_CONNECT) {
d.user_ids.forEach((id) => {
this._connectedUsers.add(id);
});
} else if (op === VoiceOpCodes.CLIENT_DISCONNECT) {
this._connectedUsers.delete(d.user_id);
} else if (op === VoiceOpCodes.DAVE_PREPARE_TRANSITION) {
this._loggerDave.debug("Preparing for DAVE transition", d);
this._davePendingTransitions.set(d.transition_id, d.protocol_version);
if (d.transition_id === 0) {
this.executePendingTransition(d.transition_id);
} else {
if (d.protocol_version === 0)
this._daveSession?.setPassthroughMode(true, 120);
this.sendOpcode(VoiceOpCodes.DAVE_TRANSITION_READY, {
transition_id: d.transition_id,
});
}
} else if (op === VoiceOpCodes.DAVE_EXECUTE_TRANSITION) {
this.executePendingTransition(d.transition_id);
} else if (op === VoiceOpCodes.DAVE_PREPARE_EPOCH) {
this._loggerDave.debug("Preparing for DAVE epoch", d);
if (d.epoch === 1) {
this._daveProtocolVersion = d.protocol_version;
this.initDave();
}
} else {
//console.log("unhandled voice event", {op, d});
}
});
}
handleBinaryMessages(msg: Buffer) {
this._sequenceNumber = msg.readUint16BE(0);
const op = msg.readUint8(2);
this._logger.trace(`Handling binary message with op ${op}`, { op });
switch (op) {
case VoiceOpCodesBinary.MLS_EXTERNAL_SENDER: {
this._daveSession?.setExternalSender(msg.subarray(3));
this._loggerDave.debug("Set MLS external sender");
break;
}
case VoiceOpCodesBinary.MLS_PROPOSALS: {
const optype = msg.readUint8(3);
const { commit, welcome } = this._daveSession!.processProposals(
optype,
msg.subarray(4),
[...this._connectedUsers],
);
if (commit) {
this.sendOpcodeBinary(
VoiceOpCodesBinary.MLS_COMMIT_WELCOME,
welcome ? Buffer.concat([commit, welcome]) : commit,
);
}
this._loggerDave.debug("Processed MLS proposal");
break;
}
case VoiceOpCodesBinary.MLS_ANNOUNCE_COMMIT_TRANSITION: {
const transitionId = msg.readUInt16BE(3);
try {
this._daveSession?.processCommit(msg.subarray(5));
if (transitionId) {
this._davePendingTransitions.set(
transitionId,
this._daveProtocolVersion,
);
this.sendOpcode(VoiceOpCodes.DAVE_TRANSITION_READY, {
transition_id: transitionId,
});
}
this._loggerDave.debug("MLS commit processed", { transitionId });
} catch (e) {
this._loggerDave.debug("MLS commit errored", e);
this.processInvalidCommit(transitionId);
}
break;
}
case VoiceOpCodesBinary.MLS_WELCOME: {
const transitionId = msg.readUInt16BE(3);
try {
this._daveSession?.processWelcome(msg.subarray(5));
if (transitionId) {
this._davePendingTransitions.set(
transitionId,
this._daveProtocolVersion,
);
this.sendOpcode(VoiceOpCodes.DAVE_TRANSITION_READY, {
transition_id: transitionId,
});
}
this._loggerDave.debug("MLS welcome processed", { transitionId });
} catch (e) {
this._loggerDave.debug("MLS welcome errored", e);
this.processInvalidCommit(transitionId);
}
break;
}
}
}
public get daveReady() {
return this._daveProtocolVersion && this._daveSession?.ready;
}
public get daveSession() {
return this._daveSession;
}
setupHeartbeat(interval: number): void {
if (this.interval) {
clearInterval(this.interval);
}
this.interval = setInterval(() => {
try {
this.sendOpcode(VoiceOpCodes.HEARTBEAT, {
t: Date.now(),
seq_ack: this._sequenceNumber,
});
} catch {}
}, interval);
}
sendOpcode<T extends GatewayRequest>(code: T["op"], data: T["d"]): void {
if (this.ws?.readyState !== WebSocket.OPEN) return;
this.ws.send(
JSON.stringify({
op: code,
d: data,
}),
);
}
sendOpcodeBinary(code: VoiceOpCodesBinary, data: Buffer) {
if (this.ws?.readyState !== WebSocket.OPEN) return;
const buf = Buffer.allocUnsafe(data.length + 1);
buf.writeUInt8(code);
data.copy(buf, 1);
this.ws.send(buf);
}
/*
** identifies with media server with credentials
*/
identify(): void {
if (!this.serverId) throw new Error("Server ID is null or empty");
if (!this.session_id) throw new Error("Session ID is null or empty");
if (!this.token) throw new Error("Token is null or empty");
this.sendOpcode(VoiceOpCodes.IDENTIFY, {
server_id: this.serverId,
user_id: this.botId,
session_id: this.session_id,
token: this.token,
video: true,
streams: STREAMS_SIMULCAST,
max_dave_protocol_version: Davey.DAVE_PROTOCOL_VERSION ?? 0,
});
}
resume(): void {
if (!this.serverId) throw new Error("Server ID is null or empty");
if (!this.session_id) throw new Error("Session ID is null or empty");
if (!this.token) throw new Error("Token is null or empty");
this.sendOpcode(VoiceOpCodes.RESUME, {
server_id: this.serverId,
session_id: this.session_id,
token: this.token,
seq_ack: this._sequenceNumber,
});
}
/*
** Sets protocols and ip data used for video and audio.
** Uses vp8 for video
** Uses opus for audio
*/
public async setProtocols(): Promise<void> {
if (!this._webRtcParams) throw new Error("WebRTC parameters not set");
// if (
// this._webRtcParams.supportedEncryptionModes.includes(SupportedEncryptionModes.AES256) &&
// !this._streamer.opts.forceChacha20Encryption
// ) {
// encryptionMode = SupportedEncryptionModes.AES256
// } else {
// encryptionMode = SupportedEncryptionModes.XCHACHA20
// }
const reconnect = () => {
const webRtcConn = this._webRtcWrapper.initWebRtc();
webRtcConn.onStateChange((state) => {
if (state === "closed" && !this._closed) reconnect();
});
webRtcConn.onLocalDescription((sdp) => {
const rtc_connection_id = randomUUID();
this.sendOpcode(VoiceOpCodes.SELECT_PROTOCOL, {
protocol: "webrtc",
codecs: Object.values(CodecPayloadType) as ValueOf<
typeof CodecPayloadType
>[],
data: sdp,
sdp: sdp,
rtc_connection_id,
});
});
webRtcConn.setLocalDescription();
};
reconnect();
return new Promise((resolve) => {
this.once("select_protocol_ack", () => resolve());
});
}
/*
* Sets video attributes (width, height, frame rate).
* enabled -> video on or off
* attr -> video attributes
* video and rtx sources are set to ssrc + 1 and ssrc + 2
*/
public setVideoAttributes(enabled: false): void;
public setVideoAttributes(enabled: true, attr: VideoAttributes): void;
public setVideoAttributes(enabled: boolean, attr?: VideoAttributes): void {
if (!this._webRtcParams) throw new Error("WebRTC parameters not set");
const { audioSsrc, videoSsrc, rtxSsrc } = this._webRtcParams;
if (!enabled) {
this.sendOpcode(VoiceOpCodes.VIDEO, {
audio_ssrc: audioSsrc,
video_ssrc: 0,
rtx_ssrc: 0,
streams: [],
});
} else {
if (!attr) throw new Error("Need to specify video attributes");
this.sendOpcode(VoiceOpCodes.VIDEO, {
audio_ssrc: audioSsrc,
video_ssrc: videoSsrc,
rtx_ssrc: rtxSsrc,
streams: [
{
type: "video",
rid: "100",
ssrc: videoSsrc,
active: true,
quality: 100,
rtx_ssrc: rtxSsrc,
// hardcode the max bitrate because we don't really know anyway
max_bitrate: 10000 * 1000,
max_framerate: enabled ? attr.fps : 0,
max_resolution: {
type: "fixed",
width: attr.width,
height: attr.height,
},
},
],
});
}
}
/*
** Set speaking status
** speaking -> speaking status on or off
*/
public setSpeaking(speaking: boolean): void {
if (!this._webRtcParams) throw new Error("WebRTC connection not ready");
this.sendOpcode(VoiceOpCodes.SPEAKING, {
delay: 0,
speaking: speaking ? 1 : 0,
ssrc: this._webRtcParams.audioSsrc,
});
}
}
@@ -1,59 +0,0 @@
export const CodecPayloadType = {
opus: {
name: "opus",
type: "audio",
clockRate: 48000,
priority: 1000,
payload_type: 120,
},
H264: {
name: "H264",
type: "video",
clockRate: 90000,
priority: 1000,
payload_type: 101,
rtx_payload_type: 102,
encode: true,
decode: true,
},
H265: {
name: "H265",
type: "video",
clockRate: 90000,
priority: 1000,
payload_type: 103,
rtx_payload_type: 104,
encode: true,
decode: true,
},
VP8: {
name: "VP8",
type: "video",
clockRate: 90000,
priority: 1000,
payload_type: 105,
rtx_payload_type: 106,
encode: true,
decode: true,
},
VP9: {
name: "VP9",
type: "video",
clockRate: 90000,
priority: 1000,
payload_type: 107,
rtx_payload_type: 108,
encode: true,
decode: true,
},
AV1: {
name: "AV1",
type: "video",
clockRate: 90000,
priority: 1000,
payload_type: 109,
rtx_payload_type: 110,
encode: true,
decode: true,
},
} as const;
@@ -1,171 +0,0 @@
import udpCon from 'node:dgram';
import { isIP } from 'node:net';
import { AudioPacketizer } from '../packet/AudioPacketizer.js';
import {
VideoPacketizerH264,
VideoPacketizerH265
} from '../packet/VideoPacketizerAnnexB.js';
import { VideoPacketizerVP8 } from '../packet/VideoPacketizerVP8.js';
import { normalizeVideoCodec } from '../../utils.js';
import type { BaseMediaPacketizer } from '../packet/BaseMediaPacketizer.js';
import type { BaseMediaConnection } from './BaseMediaConnection.js';
// credit to discord.js
function parseLocalPacket(message: Buffer) {
const packet = Buffer.from(message);
const ip = packet.subarray(8, packet.indexOf(0, 8)).toString('utf8');
if (!isIP(ip)) {
throw new Error('Malformed IP address');
}
const port = packet.readUInt16BE(packet.length - 2);
return { ip, port };
}
export class MediaUdp {
private _mediaConnection: BaseMediaConnection;
private _socket: udpCon.Socket | null = null;
private _ready = false;
private _audioPacketizer?: BaseMediaPacketizer;
private _videoPacketizer?: BaseMediaPacketizer;
private _ip?: string;
private _port?: number;
constructor(voiceConnection: BaseMediaConnection) {
this._mediaConnection = voiceConnection;
}
public get audioPacketizer(): BaseMediaPacketizer | undefined {
return this._audioPacketizer;
}
public get videoPacketizer(): BaseMediaPacketizer | undefined {
// This will never be undefined anyway, so it's safe
return this._videoPacketizer;
}
public get mediaConnection(): BaseMediaConnection {
return this._mediaConnection;
}
public get ip()
{
return this._ip;
}
public get port()
{
return this._port;
}
public async sendAudioFrame(frame: Buffer, frametime: number): Promise<void> {
if(!this.ready) return;
await this.audioPacketizer?.sendFrame(frame, frametime);
}
public async sendVideoFrame(frame: Buffer, frametime: number): Promise<void> {
if(!this.ready) return;
await this.videoPacketizer?.sendFrame(frame, frametime);
}
public setPacketizer(videoCodec: string): void {
if (!this.mediaConnection.webRtcParams)
throw new Error("WebRTC connection not ready");
const { audioSsrc, videoSsrc } = this.mediaConnection.webRtcParams;
this._audioPacketizer = new AudioPacketizer(this, audioSsrc);
switch (normalizeVideoCodec(videoCodec))
{
case "H264":
this._videoPacketizer = new VideoPacketizerH264(this, videoSsrc);
break;
case "H265":
this._videoPacketizer = new VideoPacketizerH265(this, videoSsrc);
break;
case "VP8":
this._videoPacketizer = new VideoPacketizerVP8(this, videoSsrc);
break;
default:
throw new Error(`Packetizer not implemented for ${videoCodec}`)
}
}
public sendPacket(packet: Buffer): Promise<void> {
if (!this.mediaConnection.webRtcParams)
throw new Error("WebRTC connection not ready");
const { address, port } = this.mediaConnection.webRtcParams;
return new Promise<void>((resolve, reject) => {
try {
this._socket?.send(packet, 0, packet.length, port, address, (error, bytes) => {
if (error) {
console.log("ERROR", error);
reject(error);
}
resolve();
});
} catch(e) {reject(e)}
});
}
handleIncoming(buf: unknown): void {
//console.log("RECEIVED PACKET", buf);
}
public get ready(): boolean {
return this._ready;
}
public set ready(val: boolean) {
this._ready = val;
}
public stop(): void {
try {
this.ready = false;
this._socket?.disconnect();
}catch(e) {}
}
public createUdp(): Promise<void> {
if (!this.mediaConnection.webRtcParams)
throw new Error("WebRTC connection not ready");
const { audioSsrc, address, port } = this.mediaConnection.webRtcParams;
return new Promise<void>((resolve, reject) => {
this._socket = udpCon.createSocket('udp4');
this._socket.on('error', (error: Error) => {
console.error("Error connecting to media udp server", error);
reject(error);
});
this._socket.once('message', (message) => {
if (message.readUInt16BE(0) !== 2) {
reject('wrong handshake packet for udp')
}
try {
const packet = parseLocalPacket(message);
this._ip = packet.ip;
this._port = packet.port;
this._ready = true;
} catch(e) { reject(e) }
resolve();
this._socket?.on('message', this.handleIncoming);
});
const blank = Buffer.alloc(74);
blank.writeUInt16BE(1, 0);
blank.writeUInt16BE(70, 2);
blank.writeUInt32BE(audioSsrc, 4);
this._socket.send(blank, 0, blank.length, port, address, (error, bytes) => {
if (error) {
reject(error)
}
});
});
}
}
@@ -1,38 +0,0 @@
import { VoiceOpCodes } from "../voice/VoiceOpCodes.js";
import { BaseMediaConnection } from "./BaseMediaConnection.js";
export class StreamConnection extends BaseMediaConnection {
private _streamKey: string | null = null;
private _serverId: string | null = null;
public override setSpeaking(speaking: boolean): void {
if (!this.webRtcParams) throw new Error("WebRTC connection not ready");
this.sendOpcode(VoiceOpCodes.SPEAKING, {
delay: 0,
speaking: speaking ? 2 : 0,
ssrc: this.webRtcParams.audioSsrc,
});
}
public override get daveChannelId() {
if (this._serverId === null)
throw new Error("Server ID not set (this shouldn't happen)");
const channelId = BigInt(this._serverId) - 1n;
return channelId.toString();
}
public override get serverId(): string | null {
return this._serverId;
}
public set serverId(id: string) {
this._serverId = id;
}
public get streamKey(): string | null {
return this._streamKey;
}
public set streamKey(value: string) {
this._streamKey = value;
}
}
@@ -1,19 +0,0 @@
import { BaseMediaConnection } from "./BaseMediaConnection.js";
import type { StreamConnection } from "./StreamConnection.js";
export class VoiceConnection extends BaseMediaConnection {
public streamConnection?: StreamConnection;
public override get daveChannelId() {
return this.channelId;
}
public override get serverId(): string {
return this.guildId ?? this.channelId; // for guild vc it is the guild id, for dm voice it is the channel id
}
public override stop(): void {
super.stop();
this.streamConnection?.stop();
}
}
@@ -1,263 +0,0 @@
import type { VoiceOpCodes } from "./VoiceOpCodes.js";
import type { SupportedEncryptionModes } from "../../utils.js";
type StreamInfo = {
active: boolean;
quality: number;
rid: string;
ssrc: number;
rtx_ssrc: number;
/**
* always "video" from what I observed
*/
type: string;
};
type SimulcastInfo = {
type: string;
rid: string;
quality: number;
};
type CodecPayloadType =
| {
name: string;
type: "audio";
priority: number;
payload_type: number;
}
| {
name: string;
type: "video";
priority: number;
payload_type: number;
rtx_payload_type: number;
encode: boolean;
decode: boolean;
};
export namespace Message {
// Request messages
export type Identify = {
server_id: string;
user_id: string;
session_id: string;
token: string;
video: boolean;
streams: SimulcastInfo[];
max_dave_protocol_version?: number;
};
export type Resume = {
server_id: string;
session_id: string;
token: string;
seq_ack: number;
};
export type Heartbeat = {
t: number;
seq_ack?: number;
};
export type SelectProtocol =
| {
protocol: "udp";
codecs: CodecPayloadType[];
data: {
address: string;
port: number;
mode: SupportedEncryptionModes;
};
}
| {
protocol: "webrtc";
codecs: CodecPayloadType[];
data: string;
sdp: string;
rtc_connection_id: string;
};
export type Video = {
audio_ssrc: number;
video_ssrc: number;
rtx_ssrc: number;
streams: {
type: "video";
rid: string;
ssrc: number;
active: boolean;
quality: number;
rtx_ssrc: number;
max_bitrate: number;
max_framerate: number;
max_resolution: {
type: "fixed";
width: number;
height: number;
};
}[];
};
// Response messages
export type Hello = {
heartbeat_interval: number;
};
export type Ready = {
ssrc: number;
ip: string;
port: number;
modes: SupportedEncryptionModes[];
experiments: string[];
streams: StreamInfo[];
};
export type Speaking = {
speaking: 0 | 1 | 2;
delay: number;
ssrc: number;
};
export type SelectProtocolAck = {
audio_codec: string;
video_codec: string;
dave_protocol_version: number;
} & (
| {
secret_key: number[];
mode: string;
}
| {
media_session_id: number;
sdp: string;
}
);
export type HeartbeatAck = {
t: number;
};
export type ClientsConnect = {
user_ids: string[];
};
export type ClientDisconnect = {
user_id: string;
};
export type DavePrepareTransition = {
transition_id: number;
protocol_version: number;
};
export type DaveExecuteTransition = {
transition_id: number;
};
export type DaveTransitionReady = {
transition_id: number;
};
export type DavePrepareEpoch = {
epoch: number;
protocol_version: number;
};
export type MlsInvalidCommitWelcome = {
transition_id: number;
};
}
export namespace GatewayResponse {
type Generic<
Op extends VoiceOpCodes,
T extends Record<string, unknown> | null,
> = {
op: Op;
d: T;
seq?: number;
};
export type Hello = Generic<VoiceOpCodes.HELLO, Message.Hello>;
export type Ready = Generic<VoiceOpCodes.READY, Message.Ready>;
export type Resumed = Generic<VoiceOpCodes.RESUMED, null>;
export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>;
export type SelectProtocolAck = Generic<
VoiceOpCodes.SELECT_PROTOCOL_ACK,
Message.SelectProtocolAck
>;
export type HeartbeatAck = Generic<
VoiceOpCodes.HEARTBEAT_ACK,
Message.HeartbeatAck
>;
export type ClientsConnect = Generic<
VoiceOpCodes.CLIENTS_CONNECT,
Message.ClientsConnect
>;
export type ClientDisconnect = Generic<
VoiceOpCodes.CLIENT_DISCONNECT,
Message.ClientDisconnect
>;
export type DavePrepareTransition = Generic<
VoiceOpCodes.DAVE_PREPARE_TRANSITION,
Message.DavePrepareTransition
>;
export type DaveExecuteTransition = Generic<
VoiceOpCodes.DAVE_EXECUTE_TRANSITION,
Message.DaveExecuteTransition
>;
export type DavePrepareEpoch = Generic<
VoiceOpCodes.DAVE_PREPARE_EPOCH,
Message.DavePrepareEpoch
>;
}
export type GatewayResponse =
| GatewayResponse.Hello
| GatewayResponse.Ready
| GatewayResponse.Resumed
| GatewayResponse.Speaking
| GatewayResponse.SelectProtocolAck
| GatewayResponse.HeartbeatAck
| GatewayResponse.ClientsConnect
| GatewayResponse.ClientDisconnect
| GatewayResponse.DavePrepareTransition
| GatewayResponse.DaveExecuteTransition
| GatewayResponse.DavePrepareEpoch;
export namespace GatewayRequest {
type Generic<
Op extends VoiceOpCodes,
T extends Record<string, unknown> | null,
> = {
op: Op;
d: T;
};
export type Identify = Generic<VoiceOpCodes.IDENTIFY, Message.Identify>;
export type Resume = Generic<VoiceOpCodes.RESUME, Message.Resume>;
export type Heartbeat = Generic<VoiceOpCodes.HEARTBEAT, Message.Heartbeat>;
export type SelectProtocol = Generic<
VoiceOpCodes.SELECT_PROTOCOL,
Message.SelectProtocol
>;
export type Video = Generic<VoiceOpCodes.VIDEO, Message.Video>;
export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>;
export type DaveTransitionReady = Generic<
VoiceOpCodes.DAVE_TRANSITION_READY,
Message.DaveTransitionReady
>;
export type MlsInvalidCommitWelcome = Generic<
VoiceOpCodes.MLS_INVALID_COMMIT_WELCOME,
Message.MlsInvalidCommitWelcome
>;
}
export type GatewayRequest =
| GatewayRequest.Identify
| GatewayRequest.Resume
| GatewayRequest.Heartbeat
| GatewayRequest.SelectProtocol
| GatewayRequest.Video
| GatewayRequest.Speaking
| GatewayRequest.DaveTransitionReady
| GatewayRequest.MlsInvalidCommitWelcome;
@@ -1,36 +0,0 @@
export enum VoiceOpCodes {
IDENTIFY = 0,
SELECT_PROTOCOL = 1,
READY = 2,
HEARTBEAT = 3,
SELECT_PROTOCOL_ACK = 4,
SPEAKING = 5,
HEARTBEAT_ACK = 6,
RESUME = 7,
HELLO = 8,
RESUMED = 9,
CLIENTS_CONNECT = 11,
VIDEO = 12,
CLIENT_DISCONNECT = 13,
SESSION_UPDATE = 14,
MEDIA_SINK_WANTS = 15,
VOICE_BACKEND_VERSION = 16,
CHANNEL_OPTIONS_UPDATE = 17,
FLAGS = 18,
SPEED_TEST = 19,
PLATFORM = 20,
DAVE_PREPARE_TRANSITION = 21,
DAVE_EXECUTE_TRANSITION = 22,
DAVE_TRANSITION_READY = 23,
DAVE_PREPARE_EPOCH = 24,
MLS_INVALID_COMMIT_WELCOME = 31,
}
export enum VoiceOpCodesBinary {
MLS_EXTERNAL_SENDER = 25,
MLS_KEY_PACKAGE = 26,
MLS_PROPOSALS = 27,
MLS_COMMIT_WELCOME = 28,
MLS_ANNOUNCE_COMMIT_TRANSITION = 29,
MLS_WELCOME = 30,
}
@@ -1,213 +0,0 @@
import {
PeerConnection,
Audio,
Video,
PacingHandler,
RtpPacketizer,
H264RtpPacketizer,
H265RtpPacketizer,
AV1RtpPacketizer,
RtpPacketizationConfig,
RtcpNackResponder,
RtcpSrReporter,
type Track,
} from "@lng2004/node-datachannel";
import { Codec, MediaType } from "@snazzah/davey";
import { CodecPayloadType } from "./CodecPayloadType.js";
import { normalizeVideoCodec, type SupportedVideoCodec } from "../../utils.js";
import {
splitNalu,
H264Helpers,
H264NalUnitTypes,
startCode3,
} from "../processing/AnnexBHelper.js";
import { rewriteSPSVUI } from "../processing/SPSVUIRewriter.js";
import type { BaseMediaConnection } from "./BaseMediaConnection.js";
export class WebRtcConnWrapper {
private _mediaConn: BaseMediaConnection;
private _webRtcConn?: PeerConnection;
private _audioDef: Audio;
private _videoDef: Video;
private _audioTrack?: Track;
private _videoTrack?: Track;
private _audioPacketizer?: RtpPacketizer;
private _videoPacketizer?: RtpPacketizer;
private _videoCodec?: SupportedVideoCodec;
constructor(mediaConn: BaseMediaConnection) {
this._mediaConn = mediaConn;
this._audioDef = new Audio("0", "SendRecv");
this._videoDef = new Video("1", "SendRecv");
this._audioDef.addOpusCodec(CodecPayloadType.opus.payload_type);
for (const {
name,
payload_type,
rtx_payload_type,
clockRate,
} of Object.values(CodecPayloadType).filter((el) => el.type === "video")) {
switch (name) {
case "H264":
this._videoDef.addH264Codec(payload_type);
break;
case "H265":
this._videoDef.addH265Codec(payload_type);
break;
case "VP8":
this._videoDef.addVP8Codec(payload_type);
break;
case "VP9":
this._videoDef.addVP9Codec(payload_type);
break;
case "AV1":
this._videoDef.addAV1Codec(payload_type);
break;
}
this._videoDef.addRTXCodec(rtx_payload_type, payload_type, clockRate);
}
}
public initWebRtc() {
this._webRtcConn = new PeerConnection("", {
iceServers: ["stun:stun.l.google.com:19302"],
});
this._audioTrack = this._webRtcConn.addTrack(this._audioDef);
this._videoTrack = this._webRtcConn.addTrack(this._videoDef);
this._setMediaHandler();
return this._webRtcConn;
}
private _setMediaHandler() {
if (this._audioPacketizer)
this._audioTrack?.setMediaHandler(this._audioPacketizer);
if (this._videoPacketizer)
this._videoTrack?.setMediaHandler(this._videoPacketizer);
}
public close() {
this._webRtcConn?.close();
}
public get webRtcConn() {
return this._webRtcConn;
}
public get ready() {
return this._webRtcConn?.state() === "connected";
}
public get mediaConnection() {
return this._mediaConn;
}
public sendAudioFrame(frame: Buffer, frametime: number) {
if (!this.ready) return;
if (!this._audioPacketizer) return;
const { rtpConfig } = this._audioPacketizer;
const { clockRate } = rtpConfig;
if (this.mediaConnection.daveReady)
frame = this.mediaConnection.daveSession!.encryptOpus(frame);
this._audioTrack?.sendMessageBinary(frame);
rtpConfig.timestamp += Math.round((frametime * clockRate) / 1000);
}
public sendVideoFrame(frame: Buffer, frametime: number) {
if (!this.ready) return;
if (!this._videoPacketizer) return;
const { rtpConfig } = this._videoPacketizer;
const { clockRate } = rtpConfig;
if (this._videoCodec === "H264") {
let spsRewritten = false;
const nalus = splitNalu(frame).map((el) => {
if (H264Helpers.getUnitType(el) === H264NalUnitTypes.SPS) {
spsRewritten = true;
return rewriteSPSVUI(el);
}
return el;
});
if (spsRewritten)
frame = Buffer.concat(nalus.flatMap((el) => [startCode3, el]));
}
if (this.mediaConnection.daveReady) {
let daveCodec = Codec.UNKNOWN;
switch (this._videoCodec) {
case "H264":
daveCodec = Codec.H264;
break;
case "H265":
daveCodec = Codec.H265;
break;
case "VP8":
daveCodec = Codec.VP8;
break;
case "VP9":
daveCodec = Codec.VP9;
break;
case "AV1":
daveCodec = Codec.AV1;
break;
}
frame = this.mediaConnection.daveSession!.encrypt(
MediaType.VIDEO,
daveCodec,
frame,
);
}
this._videoTrack?.sendMessageBinary(frame);
rtpConfig.timestamp += Math.round((frametime * clockRate) / 1000);
}
public setPacketizer(videoCodec: string): void {
if (!this.mediaConnection.webRtcParams)
throw new Error("WebRTC connection not ready");
const { audioSsrc, videoSsrc } = this.mediaConnection.webRtcParams;
const rtpConfigAudio = new RtpPacketizationConfig(
audioSsrc,
"",
CodecPayloadType.opus.payload_type,
CodecPayloadType.opus.clockRate,
);
rtpConfigAudio.playoutDelayId = 5;
rtpConfigAudio.playoutDelayMin = 0;
rtpConfigAudio.playoutDelayMax = 1;
this._audioPacketizer = new RtpPacketizer(rtpConfigAudio);
this._audioPacketizer.addToChain(new RtcpSrReporter(rtpConfigAudio));
this._audioPacketizer.addToChain(new RtcpNackResponder());
this._videoCodec = normalizeVideoCodec(videoCodec);
const rtpConfigVideo = new RtpPacketizationConfig(
videoSsrc,
"",
CodecPayloadType[this._videoCodec].payload_type,
CodecPayloadType[this._videoCodec].clockRate,
);
rtpConfigVideo.playoutDelayId = 5;
rtpConfigVideo.playoutDelayMin = 0;
rtpConfigVideo.playoutDelayMax = 10;
switch (this._videoCodec) {
case "H264":
this._videoPacketizer = new H264RtpPacketizer(
"StartSequence",
rtpConfigVideo,
);
break;
case "H265":
this._videoPacketizer = new H265RtpPacketizer(
"StartSequence",
rtpConfigVideo,
);
break;
case "AV1":
this._videoPacketizer = new AV1RtpPacketizer("Obu", rtpConfigVideo);
break;
default:
throw new Error(`Packetizer not implemented for ${this._videoCodec}`);
}
this._videoPacketizer.addToChain(new RtcpSrReporter(rtpConfigVideo));
this._videoPacketizer.addToChain(new RtcpNackResponder());
this._videoPacketizer.addToChain(new PacingHandler(25 * 1000 * 1000, 1));
this._setMediaHandler();
}
}
-5
View File
@@ -1,5 +0,0 @@
export * from "./VoiceConnection.js";
export * from "./VoiceOpCodes.js";
// export * from './MediaUdp.js';
export * from "./StreamConnection.js";
export * from "./BaseMediaConnection.js";
-3
View File
@@ -1,3 +0,0 @@
export * from "./client/index.js";
export * from "./media/index.js";
export * as Utils from "./utils.js";
-18
View File
@@ -1,18 +0,0 @@
import { BaseMediaStream } from "./BaseMediaStream.js";
import type { WebRtcConnWrapper } from "../client/voice/WebRtcWrapper.js";
export class AudioStream extends BaseMediaStream {
private _conn: WebRtcConnWrapper;
constructor(conn: WebRtcConnWrapper, noSleep = false) {
super("audio", noSleep);
this._conn = conn;
}
protected override async _sendFrame(
frame: Buffer,
frametime: number,
): Promise<void> {
this._conn.sendAudioFrame(frame, frametime);
}
}
-198
View File
@@ -1,198 +0,0 @@
import { Log } from "debug-level";
import { setTimeout } from "node:timers/promises";
import { Writable } from "node:stream";
import type { Packet } from "node-av";
export class BaseMediaStream extends Writable {
private _pts?: number;
private _syncTolerance = 20;
private _loggerSend: Log;
private _loggerSync: Log;
private _loggerSleep: Log;
private _noSleep: boolean;
private _startTime?: number;
private _startPts?: number;
private _sync = true;
private _syncStream?: BaseMediaStream;
constructor(type: string, noSleep = false) {
super({ objectMode: true, highWaterMark: 0 });
this._loggerSend = new Log(`stream:${type}:send`);
this._loggerSync = new Log(`stream:${type}:sync`);
this._loggerSleep = new Log(`stream:${type}:sleep`);
this._noSleep = noSleep;
}
get sync(): boolean {
return this._sync;
}
set sync(val: boolean) {
this._sync = val;
if (val) this._loggerSync.debug("Sync enabled");
else this._loggerSync.debug("Sync disabled");
}
get syncStream() {
return this._syncStream;
}
set syncStream(stream: BaseMediaStream | undefined) {
if (stream !== undefined && this === stream.syncStream)
throw new Error("Cannot sync 2 streams with eachother");
this._syncStream = stream;
}
get noSleep(): boolean {
return this._noSleep;
}
set noSleep(val: boolean) {
this._noSleep = val;
if (!val) this.resetTimingCompensation();
}
get pts(): number | undefined {
return this._pts;
}
get syncTolerance() {
return this._syncTolerance;
}
set syncTolerance(n: number) {
if (n < 0) return;
this._syncTolerance = n;
}
protected async _sendFrame(
_frame: Buffer,
_frametime: number,
): Promise<void> {
throw new Error("Not implemented");
}
private ptsDelta() {
if (this.pts !== undefined && this.syncStream?.pts !== undefined)
return this.pts - this.syncStream.pts;
return undefined;
}
private isAhead() {
const delta = this.ptsDelta();
return (
this.syncStream?.writableEnded === false &&
delta !== undefined &&
delta > this.syncTolerance
);
}
private isBehind() {
const delta = this.ptsDelta();
return (
this.syncStream?.writableEnded === false &&
delta !== undefined &&
delta < -this.syncTolerance
);
}
private resetTimingCompensation() {
this._startTime = this._startPts = undefined;
}
async _write(
frame: Packet,
_: BufferEncoding,
callback: (error?: Error | null) => void,
) {
const { data, pts, duration, timeBase } = frame;
if (!data) {
frame.free();
callback();
return;
}
const frametime = (Number(duration) / timeBase.den) * timeBase.num * 1000;
const start_sendFrame = performance.now();
await this._sendFrame(Buffer.from(data), frametime);
const end_sendFrame = performance.now();
this._pts = (Number(pts) / timeBase.den) * timeBase.num * 1000;
this.emit("pts", this._pts);
const sendTime = end_sendFrame - start_sendFrame;
const ratio = sendTime / frametime;
this._loggerSend.debug(
{
stats: {
pts: this._pts,
frame_size: data.length,
duration: sendTime,
frametime,
},
},
`Frame sent in ${sendTime.toFixed(2)}ms (${(ratio * 100).toFixed(2)}% frametime)`,
);
if (ratio > 1) {
this._loggerSend.warn(
{
frame_size: data.length,
duration: sendTime,
frametime,
},
`Frame takes too long to send (${(ratio * 100).toFixed(2)}% frametime)`,
);
}
this._startTime ??= start_sendFrame;
this._startPts ??= this._pts;
const sleep = Math.max(
0,
this._pts -
this._startPts +
frametime -
(end_sendFrame - this._startTime),
);
if (this._noSleep || sleep === 0) {
callback(null);
} else if (this.sync && this.isBehind()) {
this._loggerSync.debug(
{
stats: {
pts: this.pts,
pts_other: this.syncStream?.pts,
},
},
"Stream is behind. Not sleeping for this frame",
);
this.resetTimingCompensation();
callback(null);
} else if (this.sync && this.isAhead()) {
do {
this._loggerSync.debug(
{
stats: {
pts: this.pts,
pts_other: this.syncStream?.pts,
frametime,
},
},
`Stream is ahead. Waiting for ${frametime}ms`,
);
await setTimeout(frametime);
} while (this.sync && this.isAhead());
this.resetTimingCompensation();
callback(null);
} else {
this._loggerSleep.debug(
{
stats: {
pts: this._pts,
startPts: this._startPts,
time: end_sendFrame,
startTime: this._startTime,
frametime,
},
},
`Sleeping for ${sleep}ms`,
);
setTimeout(sleep).then(() => callback(null));
}
frame.free();
}
_destroy(
error: Error | null,
callback: (error?: Error | null) => void,
): void {
super._destroy(error, callback);
this.syncStream = undefined;
}
}
-564
View File
@@ -1,564 +0,0 @@
// https://ffmpeg.org/doxygen/7.0/codec__id_8h_source.html
export enum AVCodecID {
AV_CODEC_ID_NONE,
/* video codecs */
AV_CODEC_ID_MPEG1VIDEO,
AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
AV_CODEC_ID_H261,
AV_CODEC_ID_H263,
AV_CODEC_ID_RV10,
AV_CODEC_ID_RV20,
AV_CODEC_ID_MJPEG,
AV_CODEC_ID_MJPEGB,
AV_CODEC_ID_LJPEG,
AV_CODEC_ID_SP5X,
AV_CODEC_ID_JPEGLS,
AV_CODEC_ID_MPEG4,
AV_CODEC_ID_RAWVIDEO,
AV_CODEC_ID_MSMPEG4V1,
AV_CODEC_ID_MSMPEG4V2,
AV_CODEC_ID_MSMPEG4V3,
AV_CODEC_ID_WMV1,
AV_CODEC_ID_WMV2,
AV_CODEC_ID_H263P,
AV_CODEC_ID_H263I,
AV_CODEC_ID_FLV1,
AV_CODEC_ID_SVQ1,
AV_CODEC_ID_SVQ3,
AV_CODEC_ID_DVVIDEO,
AV_CODEC_ID_HUFFYUV,
AV_CODEC_ID_CYUV,
AV_CODEC_ID_H264,
AV_CODEC_ID_INDEO3,
AV_CODEC_ID_VP3,
AV_CODEC_ID_THEORA,
AV_CODEC_ID_ASV1,
AV_CODEC_ID_ASV2,
AV_CODEC_ID_FFV1,
AV_CODEC_ID_4XM,
AV_CODEC_ID_VCR1,
AV_CODEC_ID_CLJR,
AV_CODEC_ID_MDEC,
AV_CODEC_ID_ROQ,
AV_CODEC_ID_INTERPLAY_VIDEO,
AV_CODEC_ID_XAN_WC3,
AV_CODEC_ID_XAN_WC4,
AV_CODEC_ID_RPZA,
AV_CODEC_ID_CINEPAK,
AV_CODEC_ID_WS_VQA,
AV_CODEC_ID_MSRLE,
AV_CODEC_ID_MSVIDEO1,
AV_CODEC_ID_IDCIN,
AV_CODEC_ID_8BPS,
AV_CODEC_ID_SMC,
AV_CODEC_ID_FLIC,
AV_CODEC_ID_TRUEMOTION1,
AV_CODEC_ID_VMDVIDEO,
AV_CODEC_ID_MSZH,
AV_CODEC_ID_ZLIB,
AV_CODEC_ID_QTRLE,
AV_CODEC_ID_TSCC,
AV_CODEC_ID_ULTI,
AV_CODEC_ID_QDRAW,
AV_CODEC_ID_VIXL,
AV_CODEC_ID_QPEG,
AV_CODEC_ID_PNG,
AV_CODEC_ID_PPM,
AV_CODEC_ID_PBM,
AV_CODEC_ID_PGM,
AV_CODEC_ID_PGMYUV,
AV_CODEC_ID_PAM,
AV_CODEC_ID_FFVHUFF,
AV_CODEC_ID_RV30,
AV_CODEC_ID_RV40,
AV_CODEC_ID_VC1,
AV_CODEC_ID_WMV3,
AV_CODEC_ID_LOCO,
AV_CODEC_ID_WNV1,
AV_CODEC_ID_AASC,
AV_CODEC_ID_INDEO2,
AV_CODEC_ID_FRAPS,
AV_CODEC_ID_TRUEMOTION2,
AV_CODEC_ID_BMP,
AV_CODEC_ID_CSCD,
AV_CODEC_ID_MMVIDEO,
AV_CODEC_ID_ZMBV,
AV_CODEC_ID_AVS,
AV_CODEC_ID_SMACKVIDEO,
AV_CODEC_ID_NUV,
AV_CODEC_ID_KMVC,
AV_CODEC_ID_FLASHSV,
AV_CODEC_ID_CAVS,
AV_CODEC_ID_JPEG2000,
AV_CODEC_ID_VMNC,
AV_CODEC_ID_VP5,
AV_CODEC_ID_VP6,
AV_CODEC_ID_VP6F,
AV_CODEC_ID_TARGA,
AV_CODEC_ID_DSICINVIDEO,
AV_CODEC_ID_TIERTEXSEQVIDEO,
AV_CODEC_ID_TIFF,
AV_CODEC_ID_GIF,
AV_CODEC_ID_DXA,
AV_CODEC_ID_DNXHD,
AV_CODEC_ID_THP,
AV_CODEC_ID_SGI,
AV_CODEC_ID_C93,
AV_CODEC_ID_BETHSOFTVID,
AV_CODEC_ID_PTX,
AV_CODEC_ID_TXD,
AV_CODEC_ID_VP6A,
AV_CODEC_ID_AMV,
AV_CODEC_ID_VB,
AV_CODEC_ID_PCX,
AV_CODEC_ID_SUNRAST,
AV_CODEC_ID_INDEO4,
AV_CODEC_ID_INDEO5,
AV_CODEC_ID_MIMIC,
AV_CODEC_ID_RL2,
AV_CODEC_ID_ESCAPE124,
AV_CODEC_ID_DIRAC,
AV_CODEC_ID_BFI,
AV_CODEC_ID_CMV,
AV_CODEC_ID_MOTIONPIXELS,
AV_CODEC_ID_TGV,
AV_CODEC_ID_TGQ,
AV_CODEC_ID_TQI,
AV_CODEC_ID_AURA,
AV_CODEC_ID_AURA2,
AV_CODEC_ID_V210X,
AV_CODEC_ID_TMV,
AV_CODEC_ID_V210,
AV_CODEC_ID_DPX,
AV_CODEC_ID_MAD,
AV_CODEC_ID_FRWU,
AV_CODEC_ID_FLASHSV2,
AV_CODEC_ID_CDGRAPHICS,
AV_CODEC_ID_R210,
AV_CODEC_ID_ANM,
AV_CODEC_ID_BINKVIDEO,
AV_CODEC_ID_IFF_ILBM,
AV_CODEC_ID_IFF_BYTERUN1 = AV_CODEC_ID_IFF_ILBM,
AV_CODEC_ID_KGV1,
AV_CODEC_ID_YOP,
AV_CODEC_ID_VP8,
AV_CODEC_ID_PICTOR,
AV_CODEC_ID_ANSI,
AV_CODEC_ID_A64_MULTI,
AV_CODEC_ID_A64_MULTI5,
AV_CODEC_ID_R10K,
AV_CODEC_ID_MXPEG,
AV_CODEC_ID_LAGARITH,
AV_CODEC_ID_PRORES,
AV_CODEC_ID_JV,
AV_CODEC_ID_DFA,
AV_CODEC_ID_WMV3IMAGE,
AV_CODEC_ID_VC1IMAGE,
AV_CODEC_ID_UTVIDEO,
AV_CODEC_ID_BMV_VIDEO,
AV_CODEC_ID_VBLE,
AV_CODEC_ID_DXTORY,
AV_CODEC_ID_V410,
AV_CODEC_ID_XWD,
AV_CODEC_ID_CDXL,
AV_CODEC_ID_XBM,
AV_CODEC_ID_ZEROCODEC,
AV_CODEC_ID_MSS1,
AV_CODEC_ID_MSA1,
AV_CODEC_ID_TSCC2,
AV_CODEC_ID_MTS2,
AV_CODEC_ID_CLLC,
AV_CODEC_ID_MSS2,
AV_CODEC_ID_VP9,
AV_CODEC_ID_AIC,
AV_CODEC_ID_ESCAPE130,
AV_CODEC_ID_G2M,
AV_CODEC_ID_WEBP,
AV_CODEC_ID_HNM4_VIDEO,
AV_CODEC_ID_HEVC,
AV_CODEC_ID_H265 = AV_CODEC_ID_HEVC,
AV_CODEC_ID_FIC,
AV_CODEC_ID_ALIAS_PIX,
AV_CODEC_ID_BRENDER_PIX,
AV_CODEC_ID_PAF_VIDEO,
AV_CODEC_ID_EXR,
AV_CODEC_ID_VP7,
AV_CODEC_ID_SANM,
AV_CODEC_ID_SGIRLE,
AV_CODEC_ID_MVC1,
AV_CODEC_ID_MVC2,
AV_CODEC_ID_HQX,
AV_CODEC_ID_TDSC,
AV_CODEC_ID_HQ_HQA,
AV_CODEC_ID_HAP,
AV_CODEC_ID_DDS,
AV_CODEC_ID_DXV,
AV_CODEC_ID_SCREENPRESSO,
AV_CODEC_ID_RSCC,
AV_CODEC_ID_AVS2,
AV_CODEC_ID_PGX,
AV_CODEC_ID_AVS3,
AV_CODEC_ID_MSP2,
AV_CODEC_ID_VVC,
AV_CODEC_ID_H266 = AV_CODEC_ID_VVC,
AV_CODEC_ID_Y41P,
AV_CODEC_ID_AVRP,
AV_CODEC_ID_012V,
AV_CODEC_ID_AVUI,
AV_CODEC_ID_TARGA_Y216,
AV_CODEC_ID_V308,
AV_CODEC_ID_V408,
AV_CODEC_ID_YUV4,
AV_CODEC_ID_AVRN,
AV_CODEC_ID_CPIA,
AV_CODEC_ID_XFACE,
AV_CODEC_ID_SNOW,
AV_CODEC_ID_SMVJPEG,
AV_CODEC_ID_APNG,
AV_CODEC_ID_DAALA,
AV_CODEC_ID_CFHD,
AV_CODEC_ID_TRUEMOTION2RT,
AV_CODEC_ID_M101,
AV_CODEC_ID_MAGICYUV,
AV_CODEC_ID_SHEERVIDEO,
AV_CODEC_ID_YLC,
AV_CODEC_ID_PSD,
AV_CODEC_ID_PIXLET,
AV_CODEC_ID_SPEEDHQ,
AV_CODEC_ID_FMVC,
AV_CODEC_ID_SCPR,
AV_CODEC_ID_CLEARVIDEO,
AV_CODEC_ID_XPM,
AV_CODEC_ID_AV1,
AV_CODEC_ID_BITPACKED,
AV_CODEC_ID_MSCC,
AV_CODEC_ID_SRGC,
AV_CODEC_ID_SVG,
AV_CODEC_ID_GDV,
AV_CODEC_ID_FITS,
AV_CODEC_ID_IMM4,
AV_CODEC_ID_PROSUMER,
AV_CODEC_ID_MWSC,
AV_CODEC_ID_WCMV,
AV_CODEC_ID_RASC,
AV_CODEC_ID_HYMT,
AV_CODEC_ID_ARBC,
AV_CODEC_ID_AGM,
AV_CODEC_ID_LSCR,
AV_CODEC_ID_VP4,
AV_CODEC_ID_IMM5,
AV_CODEC_ID_MVDV,
AV_CODEC_ID_MVHA,
AV_CODEC_ID_CDTOONS,
AV_CODEC_ID_MV30,
AV_CODEC_ID_NOTCHLC,
AV_CODEC_ID_PFM,
AV_CODEC_ID_MOBICLIP,
AV_CODEC_ID_PHOTOCD,
AV_CODEC_ID_IPU,
AV_CODEC_ID_ARGO,
AV_CODEC_ID_CRI,
AV_CODEC_ID_SIMBIOSIS_IMX,
AV_CODEC_ID_SGA_VIDEO,
AV_CODEC_ID_GEM,
AV_CODEC_ID_VBN,
AV_CODEC_ID_JPEGXL,
AV_CODEC_ID_QOI,
AV_CODEC_ID_PHM,
AV_CODEC_ID_RADIANCE_HDR,
AV_CODEC_ID_WBMP,
AV_CODEC_ID_MEDIA100,
AV_CODEC_ID_VQC,
AV_CODEC_ID_PDV,
AV_CODEC_ID_EVC,
AV_CODEC_ID_RTV1,
AV_CODEC_ID_VMIX,
AV_CODEC_ID_LEAD,
/* various PCM "codecs" */
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
AV_CODEC_ID_PCM_S16LE = 0x10000,
AV_CODEC_ID_PCM_S16BE,
AV_CODEC_ID_PCM_U16LE,
AV_CODEC_ID_PCM_U16BE,
AV_CODEC_ID_PCM_S8,
AV_CODEC_ID_PCM_U8,
AV_CODEC_ID_PCM_MULAW,
AV_CODEC_ID_PCM_ALAW,
AV_CODEC_ID_PCM_S32LE,
AV_CODEC_ID_PCM_S32BE,
AV_CODEC_ID_PCM_U32LE,
AV_CODEC_ID_PCM_U32BE,
AV_CODEC_ID_PCM_S24LE,
AV_CODEC_ID_PCM_S24BE,
AV_CODEC_ID_PCM_U24LE,
AV_CODEC_ID_PCM_U24BE,
AV_CODEC_ID_PCM_S24DAUD,
AV_CODEC_ID_PCM_ZORK,
AV_CODEC_ID_PCM_S16LE_PLANAR,
AV_CODEC_ID_PCM_DVD,
AV_CODEC_ID_PCM_F32BE,
AV_CODEC_ID_PCM_F32LE,
AV_CODEC_ID_PCM_F64BE,
AV_CODEC_ID_PCM_F64LE,
AV_CODEC_ID_PCM_BLURAY,
AV_CODEC_ID_PCM_LXF,
AV_CODEC_ID_S302M,
AV_CODEC_ID_PCM_S8_PLANAR,
AV_CODEC_ID_PCM_S24LE_PLANAR,
AV_CODEC_ID_PCM_S32LE_PLANAR,
AV_CODEC_ID_PCM_S16BE_PLANAR,
AV_CODEC_ID_PCM_S64LE,
AV_CODEC_ID_PCM_S64BE,
AV_CODEC_ID_PCM_F16LE,
AV_CODEC_ID_PCM_F24LE,
AV_CODEC_ID_PCM_VIDC,
AV_CODEC_ID_PCM_SGA,
/* various ADPCM codecs */
AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
AV_CODEC_ID_ADPCM_IMA_WAV,
AV_CODEC_ID_ADPCM_IMA_DK3,
AV_CODEC_ID_ADPCM_IMA_DK4,
AV_CODEC_ID_ADPCM_IMA_WS,
AV_CODEC_ID_ADPCM_IMA_SMJPEG,
AV_CODEC_ID_ADPCM_MS,
AV_CODEC_ID_ADPCM_4XM,
AV_CODEC_ID_ADPCM_XA,
AV_CODEC_ID_ADPCM_ADX,
AV_CODEC_ID_ADPCM_EA,
AV_CODEC_ID_ADPCM_G726,
AV_CODEC_ID_ADPCM_CT,
AV_CODEC_ID_ADPCM_SWF,
AV_CODEC_ID_ADPCM_YAMAHA,
AV_CODEC_ID_ADPCM_SBPRO_4,
AV_CODEC_ID_ADPCM_SBPRO_3,
AV_CODEC_ID_ADPCM_SBPRO_2,
AV_CODEC_ID_ADPCM_THP,
AV_CODEC_ID_ADPCM_IMA_AMV,
AV_CODEC_ID_ADPCM_EA_R1,
AV_CODEC_ID_ADPCM_EA_R3,
AV_CODEC_ID_ADPCM_EA_R2,
AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
AV_CODEC_ID_ADPCM_IMA_EA_EACS,
AV_CODEC_ID_ADPCM_EA_XAS,
AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
AV_CODEC_ID_ADPCM_IMA_ISS,
AV_CODEC_ID_ADPCM_G722,
AV_CODEC_ID_ADPCM_IMA_APC,
AV_CODEC_ID_ADPCM_VIMA,
AV_CODEC_ID_ADPCM_AFC,
AV_CODEC_ID_ADPCM_IMA_OKI,
AV_CODEC_ID_ADPCM_DTK,
AV_CODEC_ID_ADPCM_IMA_RAD,
AV_CODEC_ID_ADPCM_G726LE,
AV_CODEC_ID_ADPCM_THP_LE,
AV_CODEC_ID_ADPCM_PSX,
AV_CODEC_ID_ADPCM_AICA,
AV_CODEC_ID_ADPCM_IMA_DAT4,
AV_CODEC_ID_ADPCM_MTAF,
AV_CODEC_ID_ADPCM_AGM,
AV_CODEC_ID_ADPCM_ARGO,
AV_CODEC_ID_ADPCM_IMA_SSI,
AV_CODEC_ID_ADPCM_ZORK,
AV_CODEC_ID_ADPCM_IMA_APM,
AV_CODEC_ID_ADPCM_IMA_ALP,
AV_CODEC_ID_ADPCM_IMA_MTF,
AV_CODEC_ID_ADPCM_IMA_CUNNING,
AV_CODEC_ID_ADPCM_IMA_MOFLEX,
AV_CODEC_ID_ADPCM_IMA_ACORN,
AV_CODEC_ID_ADPCM_XMD,
/* AMR */
AV_CODEC_ID_AMR_NB = 0x12000,
AV_CODEC_ID_AMR_WB,
/* RealAudio codecs*/
AV_CODEC_ID_RA_144 = 0x13000,
AV_CODEC_ID_RA_288,
/* various DPCM codecs */
AV_CODEC_ID_ROQ_DPCM = 0x14000,
AV_CODEC_ID_INTERPLAY_DPCM,
AV_CODEC_ID_XAN_DPCM,
AV_CODEC_ID_SOL_DPCM,
AV_CODEC_ID_SDX2_DPCM,
AV_CODEC_ID_GREMLIN_DPCM,
AV_CODEC_ID_DERF_DPCM,
AV_CODEC_ID_WADY_DPCM,
AV_CODEC_ID_CBD2_DPCM,
/* audio codecs */
AV_CODEC_ID_MP2 = 0x15000,
AV_CODEC_ID_MP3, ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
AV_CODEC_ID_AAC,
AV_CODEC_ID_AC3,
AV_CODEC_ID_DTS,
AV_CODEC_ID_VORBIS,
AV_CODEC_ID_DVAUDIO,
AV_CODEC_ID_WMAV1,
AV_CODEC_ID_WMAV2,
AV_CODEC_ID_MACE3,
AV_CODEC_ID_MACE6,
AV_CODEC_ID_VMDAUDIO,
AV_CODEC_ID_FLAC,
AV_CODEC_ID_MP3ADU,
AV_CODEC_ID_MP3ON4,
AV_CODEC_ID_SHORTEN,
AV_CODEC_ID_ALAC,
AV_CODEC_ID_WESTWOOD_SND1,
AV_CODEC_ID_GSM, ///< as in Berlin toast format
AV_CODEC_ID_QDM2,
AV_CODEC_ID_COOK,
AV_CODEC_ID_TRUESPEECH,
AV_CODEC_ID_TTA,
AV_CODEC_ID_SMACKAUDIO,
AV_CODEC_ID_QCELP,
AV_CODEC_ID_WAVPACK,
AV_CODEC_ID_DSICINAUDIO,
AV_CODEC_ID_IMC,
AV_CODEC_ID_MUSEPACK7,
AV_CODEC_ID_MLP,
AV_CODEC_ID_GSM_MS /* as found in WAV */,
AV_CODEC_ID_ATRAC3,
AV_CODEC_ID_APE,
AV_CODEC_ID_NELLYMOSER,
AV_CODEC_ID_MUSEPACK8,
AV_CODEC_ID_SPEEX,
AV_CODEC_ID_WMAVOICE,
AV_CODEC_ID_WMAPRO,
AV_CODEC_ID_WMALOSSLESS,
AV_CODEC_ID_ATRAC3P,
AV_CODEC_ID_EAC3,
AV_CODEC_ID_SIPR,
AV_CODEC_ID_MP1,
AV_CODEC_ID_TWINVQ,
AV_CODEC_ID_TRUEHD,
AV_CODEC_ID_MP4ALS,
AV_CODEC_ID_ATRAC1,
AV_CODEC_ID_BINKAUDIO_RDFT,
AV_CODEC_ID_BINKAUDIO_DCT,
AV_CODEC_ID_AAC_LATM,
AV_CODEC_ID_QDMC,
AV_CODEC_ID_CELT,
AV_CODEC_ID_G723_1,
AV_CODEC_ID_G729,
AV_CODEC_ID_8SVX_EXP,
AV_CODEC_ID_8SVX_FIB,
AV_CODEC_ID_BMV_AUDIO,
AV_CODEC_ID_RALF,
AV_CODEC_ID_IAC,
AV_CODEC_ID_ILBC,
AV_CODEC_ID_OPUS,
AV_CODEC_ID_COMFORT_NOISE,
AV_CODEC_ID_TAK,
AV_CODEC_ID_METASOUND,
AV_CODEC_ID_PAF_AUDIO,
AV_CODEC_ID_ON2AVC,
AV_CODEC_ID_DSS_SP,
AV_CODEC_ID_CODEC2,
AV_CODEC_ID_FFWAVESYNTH,
AV_CODEC_ID_SONIC,
AV_CODEC_ID_SONIC_LS,
AV_CODEC_ID_EVRC,
AV_CODEC_ID_SMV,
AV_CODEC_ID_DSD_LSBF,
AV_CODEC_ID_DSD_MSBF,
AV_CODEC_ID_DSD_LSBF_PLANAR,
AV_CODEC_ID_DSD_MSBF_PLANAR,
AV_CODEC_ID_4GV,
AV_CODEC_ID_INTERPLAY_ACM,
AV_CODEC_ID_XMA1,
AV_CODEC_ID_XMA2,
AV_CODEC_ID_DST,
AV_CODEC_ID_ATRAC3AL,
AV_CODEC_ID_ATRAC3PAL,
AV_CODEC_ID_DOLBY_E,
AV_CODEC_ID_APTX,
AV_CODEC_ID_APTX_HD,
AV_CODEC_ID_SBC,
AV_CODEC_ID_ATRAC9,
AV_CODEC_ID_HCOM,
AV_CODEC_ID_ACELP_KELVIN,
AV_CODEC_ID_MPEGH_3D_AUDIO,
AV_CODEC_ID_SIREN,
AV_CODEC_ID_HCA,
AV_CODEC_ID_FASTAUDIO,
AV_CODEC_ID_MSNSIREN,
AV_CODEC_ID_DFPWM,
AV_CODEC_ID_BONK,
AV_CODEC_ID_MISC4,
AV_CODEC_ID_APAC,
AV_CODEC_ID_FTR,
AV_CODEC_ID_WAVARC,
AV_CODEC_ID_RKA,
AV_CODEC_ID_AC4,
AV_CODEC_ID_OSQ,
AV_CODEC_ID_QOA,
/* subtitle codecs */
AV_CODEC_ID_FIRST_SUBTITLE = 0x17000, ///< A dummy ID pointing at the start of subtitle codecs.
AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
AV_CODEC_ID_DVB_SUBTITLE,
AV_CODEC_ID_TEXT, ///< raw UTF-8 text
AV_CODEC_ID_XSUB,
AV_CODEC_ID_SSA,
AV_CODEC_ID_MOV_TEXT,
AV_CODEC_ID_HDMV_PGS_SUBTITLE,
AV_CODEC_ID_DVB_TELETEXT,
AV_CODEC_ID_SRT,
AV_CODEC_ID_MICRODVD,
AV_CODEC_ID_EIA_608,
AV_CODEC_ID_JACOSUB,
AV_CODEC_ID_SAMI,
AV_CODEC_ID_REALTEXT,
AV_CODEC_ID_STL,
AV_CODEC_ID_SUBVIEWER1,
AV_CODEC_ID_SUBVIEWER,
AV_CODEC_ID_SUBRIP,
AV_CODEC_ID_WEBVTT,
AV_CODEC_ID_MPL2,
AV_CODEC_ID_VPLAYER,
AV_CODEC_ID_PJS,
AV_CODEC_ID_ASS,
AV_CODEC_ID_HDMV_TEXT_SUBTITLE,
AV_CODEC_ID_TTML,
AV_CODEC_ID_ARIB_CAPTION,
/* other specific kind of codecs (generally used for attachments) */
AV_CODEC_ID_FIRST_UNKNOWN = 0x18000, ///< A dummy ID pointing at the start of various fake codecs.
AV_CODEC_ID_TTF = 0x18000,
AV_CODEC_ID_SCTE_35, ///< Contain timestamp estimated through PCR of program stream.
AV_CODEC_ID_EPG,
AV_CODEC_ID_BINTEXT,
AV_CODEC_ID_XBIN,
AV_CODEC_ID_IDF,
AV_CODEC_ID_OTF,
AV_CODEC_ID_SMPTE_KLV,
AV_CODEC_ID_DVD_NAV,
AV_CODEC_ID_TIMED_ID3,
AV_CODEC_ID_BIN_DATA,
AV_CODEC_ID_SMPTE_2038,
AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf should attempt to identify it
AV_CODEC_ID_MPEG2TS = 0x20000 /**< _FAKE_ codec to indicate a raw MPEG-2 TS
* stream (only used by libavformat) */,
AV_CODEC_ID_MPEG4SYSTEMS = 0x20001 /**< _FAKE_ codec to indicate a MPEG-4 Systems
* stream (only used by libavformat) */,
AV_CODEC_ID_FFMETADATA = 0x21000, ///< Dummy codec for streams containing only metadata information.
AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, ///< Passthrough codec, AVFrames wrapped in AVPacket
/**
* Dummy null video codec, useful mainly for development and debugging.
* Null encoder/decoder discard all input and never return any output.
*/
AV_CODEC_ID_VNULL,
/**
* Dummy null audio codec, useful mainly for development and debugging.
* Null encoder/decoder discard all input and never return any output.
*/
AV_CODEC_ID_ANULL,
}
-47
View File
@@ -1,47 +0,0 @@
import {
Decoder,
FilterAPI,
type Frame,
type Packet,
type Stream,
} from "node-av";
export async function createDecoder(stream: Stream) {
const decoder = await Decoder.create(stream);
let freed = false;
let serializer: Promise<unknown> | null = null;
const serialize = <T>(f: () => Promise<T>) => {
let p: Promise<T>;
if (serializer) {
p = serializer.catch(() => {}).then(() => f());
} else {
p = f();
}
serializer = p = p.finally(() => {
if (serializer === p) serializer = null;
});
return p;
};
const filter = FilterAPI.create("format=pix_fmts=rgba");
return {
decode: async (packets: Packet) => {
if (freed) return [];
return serialize(async () => {
const frames = await decoder.decodeAll(packets);
let filtered: Frame[] = [];
for (const frame of frames) {
filtered = [...filtered, ...(await filter.processAll(frame))];
}
return filtered;
});
},
free: () => {
freed = true;
return serialize(async () => {
decoder.close();
filter.close();
});
},
};
}
-291
View File
@@ -1,291 +0,0 @@
import pDebounce from "p-debounce";
import {
BitStreamFilterAPI,
Demuxer,
avGetCodecName,
type Stream,
} from "node-av";
import { Log } from "debug-level";
import { randomUUID } from "node:crypto";
import { AVCodecID } from "./LibavCodecId.js";
import { PassThrough } from "node:stream";
import type { CodecParameters, Packet } from "node-av";
import type { Readable } from "node:stream";
type MediaStreamInfoCommon = {
index: number;
codec: AVCodecID;
codecpar: CodecParameters;
avStream: Stream;
};
export type VideoStreamInfo = MediaStreamInfoCommon & {
width: number;
height: number;
framerate_num: number;
framerate_den: number;
};
export type AudioStreamInfo = MediaStreamInfoCommon & {
sample_rate: number;
};
const allowedVideoCodec = new Set([
AVCodecID.AV_CODEC_ID_H264,
AVCodecID.AV_CODEC_ID_H265,
AVCodecID.AV_CODEC_ID_VP8,
AVCodecID.AV_CODEC_ID_VP9,
AVCodecID.AV_CODEC_ID_AV1,
]);
const allowedAudioCodec = new Set([AVCodecID.AV_CODEC_ID_OPUS]);
function parseOpusPacketDuration(frame: Uint8Array) {
// https://datatracker.ietf.org/doc/html/rfc6716#section-3.1
const frameSizes = [
// SILK only, narrow band
10, 20, 40, 60,
// SILK only, medium band
10, 20, 40, 60,
// SILK only, wide band
10, 20, 40, 60,
// Hybrid, super wide band
10, 20,
// Hybrid, full band
10, 20,
// CELT only, narrow band
2.5, 5, 10, 20,
// CELT only, wide band
2.5, 5, 10, 20,
// CELT only, super wide band
2.5, 5, 10, 20,
// CELT only, full band
2.5, 5, 10, 20,
];
const frameSize = (48000 / 1000) * frameSizes[frame[0] >> 3];
let frameCount = 0;
const c = frame[0] & 0b11;
switch (c) {
case 0:
frameCount = 1;
break;
case 1:
case 2:
frameCount = 2;
break;
case 3:
frameCount = frame[1] & 0b111111;
break;
}
return frameSize * frameCount;
}
type DemuxerOptions = {
format: "matroska" | "nut";
};
export async function demux(input: Readable, { format }: DemuxerOptions) {
const loggerFormat = new Log("demux:format");
const loggerFrameCommon = new Log("demux:frame:common");
const loggerFrameVideo = new Log("demux:frame:video");
const loggerFrameAudio = new Log("demux:frame:audio");
const filename = randomUUID();
const demuxer = await Demuxer.open(input, {
options: {
fflags: "nobuffer",
},
format,
bufferSize: 8192,
});
const cleanup = () => {
input.destroy();
demuxer.close();
vPipe.off("drain", readFrame);
aPipe.off("drain", readFrame);
vPipe.end();
aPipe.end();
vbsf.forEach((e) => {
e.close();
});
};
const vStream = demuxer.video();
const aStream = demuxer.audio();
let vInfo: VideoStreamInfo | undefined;
let aInfo: AudioStreamInfo | undefined;
const vPipe = new PassThrough({
objectMode: true,
writableHighWaterMark: 128,
});
const aPipe = new PassThrough({
objectMode: true,
writableHighWaterMark: 128,
});
const vbsf: BitStreamFilterAPI[] = [];
if (vStream) {
const codecId = vStream.codecpar.codecId;
if (!allowedVideoCodec.has(codecId)) {
const codecName = avGetCodecName(codecId);
cleanup();
throw new Error(`Video codec ${codecName} is not allowed`);
}
try {
switch (codecId) {
case AVCodecID.AV_CODEC_ID_H264:
vbsf.push(BitStreamFilterAPI.create("h264_mp4toannexb", vStream));
vbsf.push(
BitStreamFilterAPI.create("h264_metadata", vStream, {
options: {
aud: "remove",
},
}),
);
vbsf.push(BitStreamFilterAPI.create("dump_extra", vStream));
break;
case AVCodecID.AV_CODEC_ID_HEVC:
vbsf.push(BitStreamFilterAPI.create("hevc_mp4toannexb", vStream));
vbsf.push(
BitStreamFilterAPI.create("hevc_metadata", vStream, {
options: {
aud: "remove",
},
}),
);
vbsf.push(BitStreamFilterAPI.create("dump_extra", vStream));
break;
default:
vbsf.push(BitStreamFilterAPI.create("null", vStream));
break;
}
} catch (e) {
cleanup();
throw new Error(`Failed to construct bitstream filterchain`, {
cause: (e as Error).cause,
});
}
const codecpar = vbsf.at(-1)?.outputCodecParameters ?? vStream.codecpar;
vInfo = {
index: vStream.index,
codec: codecId,
codecpar,
width: codecpar.width ?? 0,
height: codecpar.height ?? 0,
framerate_num: codecpar.frameRate.num,
framerate_den: codecpar.frameRate.den,
avStream: vStream,
};
loggerFormat.info(
{
info: vInfo,
},
`Found video stream in input ${filename}`,
);
}
if (aStream) {
const codecId = aStream.codecpar.codecId;
if (!allowedAudioCodec.has(codecId)) {
const codecName = avGetCodecName(codecId);
cleanup();
throw new Error(`Audio codec ${codecName} is not allowed`);
}
aInfo = {
index: aStream.index,
codec: codecId,
codecpar: aStream.codecpar,
sample_rate: aStream.codecpar.sampleRate || 0,
avStream: aStream,
};
loggerFormat.info(
{
info: aInfo,
},
`Found audio stream in input ${filename}`,
);
}
const packetIterator = demuxer.packets();
const applyBitStreamFilters = async (
input: Packet | null,
filters: BitStreamFilterAPI[],
) => {
let packets = [input];
for (const filter of filters) {
let newPackets: (Packet | null)[] = [];
for (const packet of packets) {
newPackets = [...newPackets, ...(await filter.filterAll(packet))];
packet?.free();
}
if (!input) newPackets.push(null);
packets = newPackets;
}
return packets;
};
const readFrame = pDebounce.promise(async () => {
let resume = true;
while (resume) {
try {
const { value: inPacket, done } = await packetIterator.next();
if (done) {
loggerFrameCommon.info("Reached end of stream. Stopping");
const packets = await applyBitStreamFilters(null, vbsf);
for (const packet of packets) {
if (packet) vPipe.write(packet);
}
cleanup();
return;
} else if (inPacket) {
const streamIndex = inPacket.streamIndex;
if (vInfo && vInfo.index === streamIndex) {
loggerFrameVideo.trace("Received a video packet");
const packets = await applyBitStreamFilters(inPacket.clone(), vbsf);
for (const packet of packets) {
if (packet) resume &&= vPipe.write(packet);
}
} else if (aInfo && aInfo.index === streamIndex) {
const packet = inPacket.clone()!;
packet.duration ||= BigInt(parseOpusPacketDuration(packet.data!));
resume &&= aPipe.write(packet);
}
inPacket.free();
}
} catch (e) {
loggerFrameCommon.info(
{ error: e },
"Received an error during frame extraction. Stopping",
);
cleanup();
return;
}
}
});
vPipe.on("drain", () => {
loggerFrameVideo.trace("Video pipe drained");
readFrame();
});
aPipe.on("drain", () => {
loggerFrameAudio.trace("Audio pipe drained");
readFrame();
});
readFrame();
return {
video: vInfo ? { ...vInfo, stream: vPipe as Readable } : undefined,
audio: aInfo ? { ...aInfo, stream: aPipe as Readable } : undefined,
};
}
-17
View File
@@ -1,17 +0,0 @@
import { BaseMediaStream } from "./BaseMediaStream.js";
import type { WebRtcConnWrapper } from "../client/voice/WebRtcWrapper.js";
export class VideoStream extends BaseMediaStream {
private _conn: WebRtcConnWrapper;
constructor(conn: WebRtcConnWrapper, noSleep = false) {
super("video", noSleep);
this._conn = conn;
}
protected override async _sendFrame(
frame: Buffer,
frametime: number,
): Promise<void> {
this._conn.sendVideoFrame(frame, frametime);
}
}
-27
View File
@@ -1,27 +0,0 @@
import type { SupportedVideoCodec } from "../../utils.js";
export type EncoderSettings = {
name: string;
options: string[];
globalOptions?: string[];
outFilters?: string[];
};
export type EncoderSettingsGetter = (
bitrate: number,
bitrateMax: number,
) => Partial<Record<SupportedVideoCodec, EncoderSettings>>;
import { software } from "./software.js";
import { nvenc } from "./nvenc.js";
import { vaapi } from "./vaapi.js";
import { merge } from "./merge.js";
const Encoders = {
software,
nvenc,
vaapi,
merge,
};
export { Encoders };
-14
View File
@@ -1,14 +0,0 @@
import type { EncoderSettingsGetter } from "./index.js";
import type { SupportedVideoCodec } from "../../utils.js";
export const merge = (
encoder: Partial<Record<SupportedVideoCodec, EncoderSettingsGetter>>,
) => {
return ((bitrate, bitrateMax) => ({
H264: encoder.H264?.(bitrate, bitrateMax),
H265: encoder.H265?.(bitrate, bitrateMax),
VP8: encoder.VP8?.(bitrate, bitrateMax),
VP9: encoder.VP9?.(bitrate, bitrateMax),
AV1: encoder.AV1?.(bitrate, bitrateMax),
})) as EncoderSettingsGetter;
};
-38
View File
@@ -1,38 +0,0 @@
import type { EncoderSettingsGetter } from "./index.js";
type NvencPreset = "p1" | "p2" | "p3" | "p4" | "p5" | "p6" | "p7";
type NvencSettings = {
preset: NvencPreset;
spatialAq: boolean;
temporalAq: boolean;
gpu: number;
};
export function nvenc({
preset = "p4",
spatialAq = false,
temporalAq = false,
gpu,
}: Partial<NvencSettings> = {}) {
const options = [
`-preset ${preset}`,
`-spatial-aq ${spatialAq}`,
`-temporal-aq ${temporalAq}`,
...(gpu !== undefined ? [`-gpu ${gpu}`] : []),
];
return (() => ({
H264: {
name: "h264_nvenc",
options,
},
H265: {
name: "hevc_nvenc",
options,
},
AV1: {
name: "av1_nvenc",
options,
},
})) as EncoderSettingsGetter;
}
@@ -1,76 +0,0 @@
import type { EncoderSettingsGetter } from "./index.js";
type DeepPartial<T> = T extends unknown[]
? T
: { [P in keyof T]?: DeepPartial<T[P]> };
type x26xPreset =
| "ultrafast"
| "superfast"
| "veryfast"
| "faster"
| "fast"
| "medium"
| "slow"
| "slower"
| "veryslow"
| "placebo";
export type SoftwareEncoderSettings = {
x264: {
preset: x26xPreset;
tune:
| "film"
| "animation"
| "grain"
| "stillimage"
| "fastdecode"
| "zerolatency"
| "psnr"
| "ssim";
};
x265: {
preset: x26xPreset;
tune:
| "psnr"
| "ssim"
| "grain"
| "fastdecode"
| "zerolatency"
| "animation";
};
};
export const software = ({
x264,
x265,
}: DeepPartial<SoftwareEncoderSettings> = {}) => {
const { preset: x264Preset = "superfast", tune: x264Tune = "film" } =
x264 ?? {};
const { preset: x265Preset = "superfast", tune: x265Tune } = x265 ?? {};
return (() => ({
H264: {
name: "libx264",
options: ["-forced-idr 1", `-tune ${x264Tune}`, `-preset ${x264Preset}`],
},
H265: {
name: "libx265",
options: [
"-forced-idr 1",
...(x265Tune ? [`-tune ${x265Tune}`] : []),
`-preset ${x265Preset}`,
],
},
VP8: {
name: "libvpx",
options: ["-deadline 20000"],
},
VP9: {
name: "libvpx-vp9",
options: ["-deadline 20000"],
},
AV1: {
name: "libsvtav1",
options: [],
},
})) as EncoderSettingsGetter;
};
-29
View File
@@ -1,29 +0,0 @@
import type { EncoderSettingsGetter } from "./index.js";
type VaapiSettings = {
device?: string;
};
export function vaapi({
device = "/dev/dri/renderD128",
}: Partial<VaapiSettings> = {}) {
const props = {
options: [],
globalOptions: ["-vaapi_device", device],
outFilters: ["format=nv12|vaapi", "hwupload"],
};
return (() => ({
H264: {
name: "h264_vaapi",
...props,
},
H265: {
name: "hevc_vaapi",
...props,
},
AV1: {
name: "av1_vaapi",
...props,
},
})) as EncoderSettingsGetter;
}
-4
View File
@@ -1,4 +0,0 @@
export * from "./LibavDemuxer.js";
export * from "./newApi.js";
export * as NewApi from "./newApi.js";
export * from "./encoders/index.js";
-653
View File
@@ -1,653 +0,0 @@
import pDebounce from "p-debounce";
import sharp from "sharp";
import Log from "debug-level";
import { FFmpegCommand } from "fluent-ffmpeg-simplified";
import { type Packet, AV_PKT_FLAG_KEY } from "node-av";
import { PassThrough, type Readable } from "node:stream";
import { demux } from "./LibavDemuxer.js";
import { VideoStream } from "./VideoStream.js";
import { AudioStream } from "./AudioStream.js";
import { isBun, isDeno, isFiniteNonZero } from "../utils.js";
import { AVCodecID } from "./LibavCodecId.js";
import { createDecoder } from "./LibavDecoder.js";
import { Encoders } from "./encoders/index.js";
import type { Request } from "zeromq";
import type { SupportedVideoCodec } from "../utils.js";
import type { Streamer } from "../client/index.js";
import type { EncoderSettingsGetter } from "./encoders/index.js";
import type { VideoStreamInfo } from "./LibavDemuxer.js";
import type { WebRtcConnWrapper } from "../client/voice/WebRtcWrapper.js";
export type PrepareStreamOptions = {
/**
* Disable video transcoding
* If enabled, all video related settings have no effects, and the input
* video stream is used as-is.
*
* You need to ensure that the video stream has the right properties
* (keyframe every 1s, B-frames disabled). Failure to do so will result in
* a glitchy stream, or degraded performance
*/
noTranscoding: boolean;
/**
* Video width
*/
width: number;
/**
* Video height
*/
height: number;
/**
* Video frame rate
*/
frameRate?: number;
/**
* Video codec
*/
videoCodec: SupportedVideoCodec;
/**
* Video average bitrate in kbps
*/
bitrateVideo: number;
/**
* Video max bitrate in kbps
*/
bitrateVideoMax: number;
/**
* Audio bitrate in kbps
*/
bitrateAudio: number;
/**
* Enable audio output
*/
includeAudio: boolean;
/**
* Functions to get encoder settings
* This function will receive the average and max bitrate as the input, and
* returns an object containing encoder settings for the supported codecs
*/
encoder: EncoderSettingsGetter;
/**
* Enable hardware accelerated decoding
*/
hardwareAcceleratedDecoding: boolean;
/**
* Add some options to minimize latency
*/
minimizeLatency: boolean;
/**
* Custom headers for HTTP requests
*/
customHeaders: Record<string, string>;
/**
* Custom input options to pass directly to ffmpeg
* These will be added to the command before other options
*/
customInputOptions: string[];
/**
* Custom ffmpeg flags/options to pass directly to ffmpeg
* These will be added to the command after other options
*/
customFfmpegFlags: string[];
/**
* FFmpeg log level
*/
logLevel:
| "quiet"
| "panic"
| "fatal"
| "error"
| "warning"
| "info"
| "verbose"
| "debug"
| "trace";
};
export type Controller = {
volume: number;
setVolume(newVolume: number): Promise<boolean>;
};
export function prepareStream(
input: string | Readable,
options: Partial<PrepareStreamOptions> = {},
cancelSignal?: AbortSignal,
) {
cancelSignal?.throwIfAborted();
const logger = new Log("prepareStream");
const loggerFFmpeg = new Log("prepareStream:ffmpeg");
const defaultOptions = {
noTranscoding: false,
// negative values = resize by aspect ratio, see https://trac.ffmpeg.org/wiki/Scaling
width: -2,
height: -2,
frameRate: undefined,
videoCodec: "H264",
bitrateVideo: 5000,
bitrateVideoMax: 7000,
bitrateAudio: 128,
includeAudio: true,
encoder: Encoders.software(),
hardwareAcceleratedDecoding: false,
minimizeLatency: false,
customHeaders: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.3",
Connection: "keep-alive",
},
customInputOptions: [],
customFfmpegFlags: [],
logLevel: "verbose",
} satisfies PrepareStreamOptions;
function mergeOptions(opts: Partial<PrepareStreamOptions>) {
return {
noTranscoding: opts.noTranscoding ?? defaultOptions.noTranscoding,
width: isFiniteNonZero(opts.width)
? Math.round(opts.width)
: defaultOptions.width,
height: isFiniteNonZero(opts.height)
? Math.round(opts.height)
: defaultOptions.height,
frameRate:
isFiniteNonZero(opts.frameRate) && opts.frameRate > 0
? opts.frameRate
: defaultOptions.frameRate,
videoCodec: opts.videoCodec ?? defaultOptions.videoCodec,
bitrateVideo:
isFiniteNonZero(opts.bitrateVideo) && opts.bitrateVideo > 0
? Math.round(opts.bitrateVideo)
: defaultOptions.bitrateVideo,
bitrateVideoMax:
isFiniteNonZero(opts.bitrateVideoMax) && opts.bitrateVideoMax > 0
? Math.round(opts.bitrateVideoMax)
: defaultOptions.bitrateVideoMax,
bitrateAudio:
isFiniteNonZero(opts.bitrateAudio) && opts.bitrateAudio > 0
? Math.round(opts.bitrateAudio)
: defaultOptions.bitrateAudio,
encoder: opts.encoder ?? defaultOptions.encoder,
includeAudio: opts.includeAudio ?? defaultOptions.includeAudio,
hardwareAcceleratedDecoding:
opts.hardwareAcceleratedDecoding ??
defaultOptions.hardwareAcceleratedDecoding,
minimizeLatency: opts.minimizeLatency ?? defaultOptions.minimizeLatency,
customHeaders: {
...defaultOptions.customHeaders,
...opts.customHeaders,
},
customInputOptions:
opts.customInputOptions ?? defaultOptions.customInputOptions,
customFfmpegFlags:
opts.customFfmpegFlags ?? defaultOptions.customFfmpegFlags,
logLevel: opts.logLevel ?? defaultOptions.logLevel,
} satisfies PrepareStreamOptions;
}
const mergedOptions = mergeOptions(options);
let isHttpUrl = false;
let isHls = false;
let isSrt = false;
if (typeof input === "string") {
isHttpUrl = input.startsWith("http") || input.startsWith("https");
isHls = input.includes("m3u");
isSrt = input.startsWith("srt://");
}
const output = new PassThrough();
// command creation
const command = new FFmpegCommand();
command.on("stderr", (line) => {
loggerFFmpeg.debug(line);
});
command.input(input);
command.inputOptions("-y", "-loglevel", mergedOptions.logLevel, "-nostats");
// input options
if (
mergedOptions.customInputOptions &&
mergedOptions.customInputOptions.length > 0
) {
command.inputOptions(mergedOptions.customInputOptions);
}
const { hardwareAcceleratedDecoding, minimizeLatency, customHeaders } =
mergedOptions;
if (hardwareAcceleratedDecoding) command.inputOptions("-hwaccel", "auto");
if (minimizeLatency) {
command.inputOptions(
"-fflags nobuffer",
"-flags lowdelay",
"-flush_packets 1",
"-max_delay 100000",
);
}
if (isHttpUrl) {
const headersString = Object.entries(customHeaders)
.map(([k, v]) => `${k}: ${v}`)
.join("\r\n");
command.inputOptions(`-headers "${headersString}"`);
if (!isHls) {
command.inputOptions([
"-reconnect 1",
"-reconnect_at_eof 1",
"-reconnect_streamed 1",
"-reconnect_delay_max 4294",
]);
}
}
if (isSrt) {
command.inputOptions("-scan_all_pmts 0");
}
// general output options
command.output(output).format("nut");
// video setup
const {
noTranscoding,
width,
height,
frameRate,
bitrateVideo,
bitrateVideoMax,
videoCodec,
encoder,
} = mergedOptions;
command.outputOptions("-map 0:v");
if (noTranscoding) {
command.videoCodec("copy");
} else {
command.videoFilters(`scale=${width}:${height}`);
if (frameRate) command.fps(frameRate);
command.outputOptions([
"-b:v",
`${bitrateVideo}k`,
"-maxrate:v",
`${bitrateVideoMax}k`,
"-bufsize:v",
`${Math.round(bitrateVideo / 2)}k`,
"-bf",
"0",
"-pix_fmt",
"yuv420p",
"-force_key_frames",
"expr:gte(t,n_forced*1)",
]);
const encoderSettings = encoder(bitrateVideo, bitrateVideoMax)[videoCodec];
if (!encoderSettings)
throw new Error(`Encoder settings not specified for ${videoCodec}`);
command
.videoCodec(encoderSettings.name)
.videoFilters(encoderSettings.outFilters ?? [])
.outputOptions(encoderSettings.options)
.outputOptions(encoderSettings.globalOptions ?? []);
}
// audio setup
const { includeAudio, bitrateAudio } = mergedOptions;
if (includeAudio)
command
.outputOptions("-map 0:a:0?")
.audioChannels(2)
/*
* I don't have much surround sound material to test this with,
* if you do and you have better settings for this, feel free to
* contribute!
*/
.outputOptions("-lfe_mix_level 1")
.audioFrequency(48000)
.audioCodec("libopus")
.audioBitrate(`${bitrateAudio}k`)
.audioFilters("volume@internal_lib=1.0");
// Add custom ffmpeg flags
if (
mergedOptions.customFfmpegFlags &&
mergedOptions.customFfmpegFlags.length > 0
) {
command.outputOptions(mergedOptions.customFfmpegFlags);
}
// realtime control mechanism
let currentVolume = 1;
let zmqClientPromise: Promise<Request> | undefined;
if (includeAudio && !isBun() && !isDeno()) {
function randomInclusive(start: number, end: number) {
return Math.floor(Math.random() * (end - start + 1)) + start;
}
// Last octet is from 2 to 254 to avoid WSL2 shenanigans
const loopbackIp = [
127,
randomInclusive(0, 255),
randomInclusive(0, 255),
randomInclusive(2, 254),
].join(".");
const zmqEndpoint = `tcp://${loopbackIp}:42069`;
command.audioFilters(`azmq=b=${zmqEndpoint.replaceAll(":", "\\\\:")}`);
zmqClientPromise = import("zeromq").then((zmq) => {
const client = new zmq.Request({
sendTimeout: 5000,
receiveTimeout: 5000,
});
client.connect(zmqEndpoint);
promise.catch(() => {}).finally(() => client.disconnect(zmqEndpoint));
return client;
});
}
command.once("start", (cmdline) => {
logger.debug(`Starting ffmpeg: ${cmdline}`);
});
const promise = command.run(cancelSignal);
return {
command,
output,
promise: promise as Promise<unknown>,
controller: {
get volume() {
return currentVolume;
},
async setVolume(newVolume: number) {
if (newVolume < 0) return false;
try {
if (!zmqClientPromise) return false;
const client = await zmqClientPromise;
await client.send(`volume@internal_lib volume ${newVolume}`);
const [res] = await client.receive();
if (res.toString("utf-8").split(" ")[0] !== "0") return false;
currentVolume = newVolume;
return true;
} catch {
return false;
}
},
} satisfies Controller,
};
}
export type PlayStreamOptions = {
/**
* Set stream type as "Go Live" or camera stream
*/
type: "go-live" | "camera";
/**
* Set format of the stream
*/
format: "matroska" | "nut";
/**
* Override video width sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
width: number | ((v: VideoStreamInfo) => number);
/**
* Override video height sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
height: number | ((v: VideoStreamInfo) => number);
/**
* Override video frame rate sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
frameRate: number | ((v: VideoStreamInfo) => number);
/**
* Same as ffmpeg's `readrate_initial_burst` command line flag
*
* See https://ffmpeg.org/ffmpeg.html#:~:text=%2Dreadrate_initial_burst
*/
readrateInitialBurst: number | undefined;
/**
* Enable stream preview from input stream (experimental)
*/
streamPreview: boolean;
};
export async function playStream(
input: Readable,
streamer: Streamer,
options: Partial<PlayStreamOptions> = {},
cancelSignal?: AbortSignal,
) {
const logger = new Log("playStream");
cancelSignal?.throwIfAborted();
if (!streamer.voiceConnection)
throw new Error("Bot is not connected to a voice channel");
const defaultOptions = {
type: "go-live",
format: "nut",
width: (video) => video.width,
height: (video) => video.height,
frameRate: (video) => video.framerate_num / video.framerate_den,
readrateInitialBurst: undefined,
streamPreview: false,
} satisfies PlayStreamOptions;
function mergeOptions(opts: Partial<PlayStreamOptions>) {
return {
type: opts.type ?? defaultOptions.type,
format: opts.format ?? defaultOptions.format,
width:
typeof opts.width === "function" ||
(isFiniteNonZero(opts.width) && opts.width > 0)
? opts.width
: defaultOptions.width,
height:
typeof opts.height === "function" ||
(isFiniteNonZero(opts.height) && opts.height > 0)
? opts.height
: defaultOptions.height,
frameRate:
typeof opts.frameRate === "function" ||
(isFiniteNonZero(opts.frameRate) && opts.frameRate > 0)
? opts.frameRate
: defaultOptions.frameRate,
readrateInitialBurst:
isFiniteNonZero(opts.readrateInitialBurst) &&
opts.readrateInitialBurst > 0
? opts.readrateInitialBurst
: defaultOptions.readrateInitialBurst,
streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
} satisfies PlayStreamOptions;
}
const mergedOptions = mergeOptions(options);
logger.debug({ options: mergedOptions }, "Merged options");
logger.debug("Initializing demuxer");
const { video, audio } = await demux(input, {
format: mergedOptions.format,
});
cancelSignal?.throwIfAborted();
if (!video) throw new Error("No video stream in media");
const cleanupFuncs: (() => unknown)[] = [];
const videoCodecMap: Record<number, SupportedVideoCodec> = {
[AVCodecID.AV_CODEC_ID_H264]: "H264",
[AVCodecID.AV_CODEC_ID_H265]: "H265",
[AVCodecID.AV_CODEC_ID_VP8]: "VP8",
[AVCodecID.AV_CODEC_ID_VP9]: "VP9",
[AVCodecID.AV_CODEC_ID_AV1]: "AV1",
};
let conn: WebRtcConnWrapper;
let stopStream: () => unknown;
if (mergedOptions.type === "go-live") {
conn = await streamer.createStream();
stopStream = () => streamer.stopStream();
} else {
conn = streamer.voiceConnection.webRtcConn;
streamer.signalVideo(true);
stopStream = () => streamer.signalVideo(false);
}
conn.setPacketizer(videoCodecMap[video.codec]);
conn.mediaConnection.setSpeaking(true);
const { width, height, frameRate } = mergedOptions;
conn.mediaConnection.setVideoAttributes(true, {
width: Math.round(typeof width === "function" ? width(video) : width),
height: Math.round(typeof height === "function" ? height(video) : height),
fps: Math.round(
typeof frameRate === "function" ? frameRate(video) : frameRate,
),
});
const vStream = new VideoStream(conn);
video.stream.pipe(vStream);
if (audio) {
const aStream = new AudioStream(conn);
audio.stream.pipe(aStream);
vStream.syncStream = aStream;
const burstTime = mergedOptions.readrateInitialBurst;
if (typeof burstTime === "number") {
vStream.sync = false;
vStream.noSleep = aStream.noSleep = true;
const stopBurst = (pts: number) => {
if (pts < burstTime * 1000) return;
vStream.sync = true;
vStream.noSleep = aStream.noSleep = false;
vStream.off("pts", stopBurst);
};
vStream.on("pts", stopBurst);
}
}
if (mergedOptions.streamPreview && mergedOptions.type === "go-live") {
(async () => {
const logger = new Log("playStream:preview");
logger.debug("Initializing decoder for stream preview");
const decoder = await createDecoder(video.avStream);
if (!decoder) {
logger.warn(
"Failed to initialize decoder. Stream preview will be disabled",
);
return;
}
cleanupFuncs.push(() => {
logger.debug("Freeing decoder");
decoder.free();
});
const updatePreview = pDebounce.promise(async (packet: Packet) => {
if (!(packet.flags !== undefined && packet.flags & AV_PKT_FLAG_KEY))
return;
const decodeStart = performance.now();
const frames = await decoder.decode(packet).catch((e) => {
logger.error(e, "Failed to decode the frame");
return [];
});
if (!frames.length) return;
const decodeEnd = performance.now();
logger.debug(`Decoding a frame took ${decodeEnd - decodeStart}ms`);
const frame = frames[0];
return sharp(frame.toBuffer(), {
raw: {
width: frame.width ?? 0,
height: frame.height ?? 0,
channels: 4,
},
})
.resize(1024, 576, { fit: "inside" })
.jpeg()
.toBuffer()
.then((image) => streamer.setStreamPreview(image))
.catch(() => {})
.finally(() => {
frames.forEach((frame) => {
frame.free();
});
});
});
video.stream.on("data", updatePreview);
cleanupFuncs.push(() => video.stream.off("data", updatePreview));
})();
}
const promise = new Promise<void>((resolve, reject) => {
cleanupFuncs.push(() => {
stopStream();
conn.mediaConnection.setSpeaking(false);
conn.mediaConnection.setVideoAttributes(false);
});
let cleanedUp = false;
const cleanup = () => {
if (cleanedUp) return;
cleanedUp = true;
for (const f of cleanupFuncs) f();
};
cancelSignal?.addEventListener(
"abort",
() => {
cleanup();
reject(cancelSignal.reason);
},
{ once: true },
);
vStream.once("finish", () => {
if (cancelSignal?.aborted) return;
cleanup();
resolve();
});
});
promise.catch(() => {});
return promise;
}
-101
View File
@@ -1,101 +0,0 @@
import type {
AnyChannel,
DMChannel,
GroupDMChannel,
VoiceBasedChannel,
} from "discord.js-selfbot-v13";
export function normalizeVideoCodec(
codec: string,
): "H264" | "H265" | "VP8" | "VP9" | "AV1" {
if (/H\.?264|AVC/i.test(codec)) return "H264";
if (/H\.?265|HEVC/i.test(codec)) return "H265";
if (/VP(8|9)/i.test(codec)) return codec.toUpperCase() as "VP8" | "VP9";
if (/AV1/i.test(codec)) return "AV1";
throw new Error(`Unknown codec: ${codec}`);
}
// The available video streams are sent by client on connection to voice gateway using OpCode Identify (0)
// The server then replies with the ssrc and rtxssrc for each available stream using OpCode Ready (2)
// RID is used specifically to distinguish between different simulcast streams of the same video source,
// but we don't really care about sending multiple quality streams, so we hardcode a single one
export const STREAMS_SIMULCAST = [{ type: "screen", rid: "100", quality: 100 }];
export enum SupportedEncryptionModes {
AES256 = "aead_aes256_gcm_rtpsize",
XCHACHA20 = "aead_xchacha20_poly1305_rtpsize",
}
export type SupportedVideoCodec = "H264" | "H265" | "VP8" | "VP9" | "AV1";
export const max_int16bit = 2 ** 16;
export const max_int32bit = 2 ** 32;
export function isFiniteNonZero(n: unknown): n is number {
return !!n && Number.isFinite(n);
}
export function parseStreamKey(streamKey: string): {
type: "guild" | "call";
channelId: string;
guildId: string | null;
userId: string;
} {
const streamKeyArray = streamKey.split(":");
const type = streamKeyArray.shift();
if (type !== "guild" && type !== "call") {
throw new Error(`Invalid stream key type: ${type}`);
}
if (
(type === "guild" && streamKeyArray.length < 3) ||
(type === "call" && streamKey.length < 2)
)
throw new Error(`Invalid stream key: ${streamKey}`); // invalid stream key
let guildId: string | null = null;
if (type === "guild") {
guildId = streamKeyArray.shift() ?? null;
}
const channelId = streamKeyArray.shift();
const userId = streamKeyArray.shift();
if (!channelId || !userId) {
throw new Error(`Invalid stream key: ${streamKey}`);
}
return { type, channelId, guildId, userId };
}
export function generateStreamKey(
type: "guild" | "call",
guildId: string | null,
channelId: string,
userId: string,
): string {
const streamKey = `${type}${type === "guild" ? `:${guildId}` : ""}:${channelId}:${userId}`;
return streamKey;
}
export function isVoiceChannel(
channel: AnyChannel,
): channel is DMChannel | GroupDMChannel | VoiceBasedChannel {
return (
channel.type === "DM" ||
channel.type === "GROUP_DM" ||
channel.type === "GUILD_STAGE_VOICE" ||
channel.type === "GUILD_VOICE"
);
}
export function isDeno() {
// @ts-expect-error
return typeof Deno !== "undefined";
}
export function isBun() {
// @ts-expect-error
return typeof Bun !== "undefined";
}
-106
View File
@@ -1,106 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "Node16", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "Node16", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/**/*"
]
}
-3
View File
@@ -1,3 +0,0 @@
{
"ignorePatterns": ["node_modules/*"]
}
@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
-1
View File
@@ -1 +0,0 @@
github: [aiko-chan-ai]
@@ -1,91 +0,0 @@
name: Bug report
description: Report incorrect or unexpected behavior of a package
labels: [bug, need repro]
body:
- type: dropdown
id: package
attributes:
label: Which package has the bugs?
options:
- The core library
- The documentation
- WebEmbed x Shorten API
validations:
required: true
- type: textarea
id: description
attributes:
label: Issue description
description: |
Describe the issue in as much detail as possible.
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files into it.
placeholder: |
Steps to reproduce with below code sample:
1. do thing
2. do thing in Discord client
3. observe behavior
4. see error logs below
validations:
required: true
- type: textarea
id: codesample
attributes:
label: Code sample
description: Include a reproducible, minimal code sample. This will be automatically formatted into code, so no need for backticks.
render: typescript
placeholder: |
Your code sample should be...
... Minimal - Use as little code as possible that still produces the same problem (and is understandable)
... Complete - Provide all parts someone else needs to reproduce your problem
... Reproducible - Test the code you're about to provide to make sure it reproduces the problem
- type: input
id: djs-version
attributes:
label: Package version
description: Which version of are you using? Run `npm list <package>` in your project directory and paste the output.
placeholder: Older versions are not supported.
validations:
required: true
- type: input
id: node-version
attributes:
label: Node.js version
description: |
Which version of Node.js are you using? Run `node --version` in your project directory and paste the output.
If you are using TypeScript, please include its version (`npm list typescript`) as well.
placeholder: Node.js version 16.9+ is required for version 14.0.0+
validations:
required: true
- type: input
id: os
attributes:
label: Operating system
description: Which OS does your application run on?
- type: dropdown
id: priority
attributes:
label: Priority this issue should have
description: Please be realistic. If you need to elaborate on your reasoning, please use the Issue description field above.
options:
- Low (slightly annoying)
- Medium (should be fixed soon)
- High (immediate attention needed)
validations:
required: true
- type: checkboxes
attributes:
label: Checklist
description: >
Let's make sure this issue is valid!
options:
- label: I have searched the open issues for duplicates.
required: true
- label: I have shared the entire traceback.
required: true
- label: I am using a user token (and it isn't visible in the code).
required: true
- type: textarea
attributes:
label: Additional Information
description: Put any extra context, weird configurations, or other important info here.
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Discord.js Repo
url: https://github.com/discordjs/discord.js
about: A powerful JavaScript library for interacting with the Discord API [Bot].
@@ -1,44 +0,0 @@
name: Feature request
description: Request a new feature
labels: [Feature]
body:
- type: markdown
attributes:
value: |
We can only implement features that Discord publishes, documents and merges into the Discord API documentation.
For unreleased API features, you need more documents (Unofficial Discord API) or things you know.
- type: dropdown
id: package
attributes:
label: Which package is the feature request for?
options:
- The core library
- The documentation
- WebEmbed x Shorten API
validations:
required: true
- type: textarea
id: description
attributes:
label: Feature
description: A clear and concise description of what the problem is, or what feature you want to be implemented.
placeholder: I'm always frustrated when..., Discord has recently released..., A good addition would be...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Ideal solution or implementation
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternative solutions or implementations
description: A clear and concise description of any alternative solutions or features you have considered.
- type: textarea
id: additional-context
attributes:
label: Other context
description: Any other context, screenshots, or file uploads that help us understand your feature request.
-8
View File
@@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
time: "12:00"
open-pull-requests-limit: 15
@@ -1,26 +0,0 @@
name: Lint
on:
push:
branches:
- '*'
pull_request:
branches:
- '*'
jobs:
eslint:
name: ESLint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Node.js v24
uses: actions/setup-node@v4
with:
node-version: 24
- run: npm install --verbose
- run: npx patch-package
- run: npm run test
@@ -1,49 +0,0 @@
name: Release
on:
push:
tags:
- '3*' # Trigger on version 3.x.x tags
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Node.js v22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Configure npm for publishing
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
- name: Get current published version (if any)
id: current_version
run: |
version=$(npm view discord.js-selfbot-v13 version || echo "none")
echo "version=$version" >> $GITHUB_OUTPUT
- name: Deprecate previous version
if: steps.current_version.outputs.version != 'none'
run: |
echo "Deprecating version ${{ steps.current_version.outputs.version }}"
npm deprecate discord.js-selfbot-v13@${{ steps.current_version.outputs.version }} "Deprecated: Please use the latest version."
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to NPM
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
-85
View File
@@ -1,85 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Packages
node_modules/
djs/
# Log files
logs/
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Env
.env
test/auth.json
test/auth.js
docs/deploy/deploy_key
docs/deploy/deploy_key.pub
deploy/deploy_key
deploy/deploy_key.pub
# Dist
dist/
docs/docs.json
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Env
.env
test/
docs/deploy/deploy_key
docs/deploy/deploy_key.pub
deploy/deploy_key
deploy/deploy_key.pub
# Miscellaneous
.tmp/
.idea/
.DS_Store
# Custom
data/
update.mjs
yarn.lock
package-lock.json
-674
View File
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2022 aiko-chan-ai and discordjs
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
-124
View File
@@ -1,124 +0,0 @@
> [!IMPORTANT]
> ## Project Archival
>
> **This project is no longer actively maintained and this repository has been archived.**
>
> You can read the full announcement [here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/discussions/1743)
<div align="center">
<br />
<p>
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
</p>
</div>
> [!CAUTION]
> **The use of this module under a different name on NPM (or another source besides this Github) is not associated with this library.**
> **When using these libraries, you accept the risk of exposing your Discord Token.**
## About
<strong>Welcome to `discord.js-selfbot-v13@v3.7`, based on `discord.js@13.17` and backport `discord.js@14.21.0`</strong>
- discord.js-selfbot-v13 is a [Node.js](https://nodejs.org) module that allows user accounts to interact with the Discord API v9.
<div align="center">
<p>
<a href="https://www.npmjs.com/package/discord.js-selfbot-v13"><img src="https://img.shields.io/npm/v/discord.js-selfbot-v13.svg" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/discord.js-selfbot-v13"><img src="https://img.shields.io/npm/dt/discord.js-selfbot-v13.svg" alt="npm downloads" /></a>
<a href="https://github.com/aiko-chan-ai/discord.js-selfbot-v13/actions"><img src="https://github.com/aiko-chan-ai/discord.js-selfbot-v13/actions/workflows/lint.yml/badge.svg" alt="Tests status" /></a>
</p>
</div>
> [!WARNING]
> **I don't take any responsibility for blocked Discord accounts that used this module.**
> [!CAUTION]
> **Using this on a user account is prohibited by the [Discord TOS](https://discord.com/terms) and can lead to the account block.**
### <strong>[Document Website](https://discordjs-self-v13.netlify.app/)</strong>
### <strong>[Example Code](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/tree/main/examples)</strong>
## Features (User)
- [x] Message
- [x] ClientUser: Status, Activity, RemoteAuth, etc.
- [X] Guild: Fetch Members, Join / Leave, Top emojis, etc.
- [X] Interactions: Slash Commands, Buttons, Menu, Modal.
- [X] Captcha & TOTP Handler
- [X] Documentation
- [x] Voice & Video
- [ ] Everything
## Installation
> [!NOTE]
> **Node.js 20.18.0 or newer is required**
```sh-session
npm install discord.js-selfbot-v13@latest
```
## Example
```js
const { Client } = require('discord.js-selfbot-v13');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
})
client.login('token');
```
## Get Token ?
- Based: [findByProps](https://discord.com/channels/603970300668805120/1085682686607249478/1085682686607249478)
<strong>Run code (Discord Console - [Ctrl + Shift + I])</strong>
```js
window.webpackChunkdiscord_app.push([
[Symbol()],
{},
req => {
if (!req.c) return;
for (let m of Object.values(req.c)) {
try {
if (!m.exports || m.exports === window) continue;
if (m.exports?.getToken) return copy(m.exports.getToken());
for (let ex in m.exports) {
if (m.exports?.[ex]?.getToken && m.exports[ex][Symbol.toStringTag] !== 'IntlMessagesProxy') return copy(m.exports[ex].getToken());
}
} catch {}
}
},
]);
window.webpackChunkdiscord_app.pop();
console.log('%cWorked!', 'font-size: 50px');
console.log(`%cYou now have your token in the clipboard!`, 'font-size: 16px');
```
## Contributing
- Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discordjs-self-v13.netlify.app/).
- See [the contribution guide](https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
## Need help?
Github Discussion: [Here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/discussions)
## Credits
- [Discord.js](https://github.com/discordjs/discord.js)
## <strong>Other project(s)
- 📘 [***aiko-chan-ai/DiscordBotClient***](https://github.com/aiko-chan-ai/DiscordBotClient) <br/>
A patched version of discord, with bot login support
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=aiko-chan-ai/discord.js-selfbot-v13&type=Date)](https://star-history.com/#aiko-chan-ai/discord.js-selfbot-v13&Date)
-29
View File
@@ -1,29 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
"files": {
"includes": ["src/**/*.js", "typings/**/*.ts", "*.json"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": false,
"style": {
"useNodejsImportProtocol": "warn"
},
"suspicious": {
"noExplicitAny": "warn"
}
}
}
}
-5
View File
@@ -1,5 +0,0 @@
- name: General
files:
- name: Welcome
id: welcome
path: ../../README.md
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="100%" width="100%" viewBox="0 0 6111.4378 1102.9827">
<g transform="translate(2539.6 -107.66)">
<g id="logo-discord" fill="#3d3f42" transform="translate(-44.194 1175.6)">
<path d="m-2495.4-1051.4v453.6 453.6l145.75-.37695c127.36-.3288 147.71-.58582 161.25-2.041 45.045-4.8398 76.353-11.233 111.79-22.826 44.217-14.465 83.672-35.567 118.71-63.49 13.615-10.851 40.444-37.567 50.889-50.674 37.186-46.665 61.816-98.191 78.01-163.2 23.57-94.614 23.154-219.66-1.0469-313.5-41.72-161.77-155.27-260-329.35-284.92-38.756-5.5479-34.464-5.4161-190.75-5.8086l-145.25-.3652zm161 130.09 41.75.0156c55.334.0205 78.397 1.6295 108.25 7.5566 105.75 20.995 171.57 87.554 196.39 198.59 12.878 57.6 14.716 139.6 4.5469 202.81-7.3952 45.963-21.469 87.286-40.711 119.53-12.041 20.179-33.82 45.681-51 59.719-38.627 31.563-87.98 50.255-148.73 56.326-9.5463.9541-32.361 1.7291-62.75 2.1328l-47.75.63477v-323.66-323.66z"/>
<path d="m-1631.4-597.85v-453.5h80.5 80.5v453.5 453.5h-80.5-80.5v-453.5z"/>
<path d="m-1008.4-128.41c-96.325-5.9603-189.36-41.918-264.54-102.25-15.565-12.49-33-28.526-33-30.352 0-.7224 20.622-25.63 45.826-55.351l45.826-54.038 3.8214 3.2697c17.83 15.256 22.538 19.151 29.616 24.501 48.673 36.79 103.35 61.169 158.92 70.862 18.387 3.2073 54.666 4.419 74.088 2.4745 41.751-4.1802 74.798-17.199 96.864-38.16 10.213-9.7012 15.896-17.429 21.626-29.408 17.4-36.376 13.152-81.77-10.39-111-16.357-20.31-45.054-37.907-98.696-60.521-41.654-17.56-164.15-71.537-176.19-77.638-85.541-43.335-134.63-104.27-148.9-184.84-2.6851-15.162-3.7276-49.931-1.9989-66.666 7.4631-72.25 48.261-136.63 113.09-178.46 41.81-26.976 88.546-43.103 144.99-50.03 20.52-2.5182 67.722-2.5268 88-.016 74.352 9.2063 141.74 36.296 199 79.999 18.772 14.327 37.632 31.435 36.864 33.44-.2001.52235-18.812 23.693-41.361 51.49l-40.997 50.54-3.503-2.9264c-1.9267-1.6095-9.4625-7.4505-16.746-12.98-44.158-33.522-88.429-52.307-140.26-59.513-17.665-2.4562-54.274-2.4782-70-.042-35.82 5.5488-61.303 16.869-80.113 35.588-17.506 17.422-26.238 37.587-27.528 63.576-1.3118 26.419 6.521 48.306 24.066 67.249 17.834 19.254 45.314 35.115 99.448 57.398 32.211 13.259 137.3 57.517 151.65 63.864 47.003 20.795 80.577 42.726 108.49 70.87 43.959 44.316 64.938 98.562 65.021 168.13.053 44.646-7.8058 78.816-26.734 116.23-12.46 24.632-27.741 45.114-49.45 66.28-51.458 50.172-122.59 79.937-208.86 87.392-17.502 1.5126-51.786 2.0335-67.962 1.0326z"/>
<path d="m-155.84-128.44c-100.7-5.7557-190.26-44.562-257.1-111.4-58.171-58.171-98.098-136.72-116.41-229.01-13.522-68.153-15.549-148.4-5.5195-218.5 13.11-91.624 47.506-173.73 99.29-237 11.342-13.858 35.64-38.591 49.282-50.164 54.726-46.425 120.9-76.546 193.88-88.256 25.873-4.1511 37.999-5.0552 67.977-5.0681 28.858-.013 38.31.6981 60.5 4.5485 70.566 12.245 140.29 49.396 192.89 102.78l6.8911 6.9936-2.8911 3.4607c-1.59 1.9034-21.52 24.408-44.288 50.011l-41.397 46.551-10.103-9.0797c-40.998-36.846-79.308-56.146-125.89-63.421-13.826-2.1591-48.594-2.4422-62.711-.51067-51.945 7.1074-94.856 27.696-131.17 62.933-64.806 62.887-97.854 165.12-92.829 287.16 2.697 65.505 14.091 119.1 35.16 165.38 30.027 65.96 77.365 110.94 138.03 131.16 24.572 8.1885 46.583 11.525 76.026 11.525 45.839 0 83.431-9.665 120.81-31.062 19.559-11.195 45.837-32.314 63.267-50.848 3.7379-3.9745 7.1554-7.0833 7.5942-6.9085 1.3142.5236 88.109 97.158 88.109 98.098 0 2.0843-41.684 42.322-54 52.126-73.043 58.146-157.48 84.1-255.41 78.503z"/>
<path d="m610.07-1067.8c-34.898-.056-47.464.862-75.232 5.4922-188.34 31.405-308.9 182.45-325.21 407.46-2.8044 38.675-2.2536 84.125 1.4941 123.38 9.2582 96.975 39.751 184.31 87.494 250.58 57.015 79.142 139.29 130.29 236.46 147 14.533 2.4988 40.496 5.3373 53.5 5.8496 147.12 5.7956 267.7-55.193 342.98-173.48 10.897-17.122 28.991-52.974 36.758-72.828 27.4-70.046 39.498-139.21 39.617-226.5.062-45.479-1.9339-73.343-7.9121-110.4-31.164-193.18-145.75-321-314.25-350.53-27.838-4.8789-41.445-5.9606-75.699-6.0156zm-1.4395 139.59c2.8062.0114 5.6199.0752 8.4395.19336 49.33 2.0671 91.449 18.361 127.46 49.305 12.954 11.133 20.363 19.102 31.482 33.861 40.99 54.409 62.709 125.93 66.582 219.25 4.5628 109.93-19.826 208.09-67.676 272.39-33.936 45.599-76.643 72.514-130.84 82.459-10.577 1.9408-50.92 2.8029-62 1.3242-74.694-9.9681-131.62-54.014-168.58-130.43-24.356-50.365-36.989-106.85-39.92-178.5-5.9652-145.81 37.791-262.31 118.61-315.79 33.933-22.452 74.357-34.245 116.45-34.074z"/>
<path d="m1187.6-1051.4v453.54 453.54h80.5 80.5v-177.51-177.51l68.717.25585 68.719.25782 97.531 177.22 97.533 177.22 90.285.0273c85.686.0268 90.237-.0599 89.336-1.7207-.5222-.9625-49.147-86.08-108.05-189.15-58.906-103.07-106.98-187.52-106.83-187.67.1497-.14971 5.5455-2.31 11.99-4.8008 92.947-35.923 149.28-103.8 164.7-198.43 3.4973-21.47 4.3763-36.845 3.7539-65.688-.8444-39.124-4.5518-62.293-14.883-93.008-29.696-88.286-106.44-143.03-224.91-160.44-38.597-5.6719-28.81-5.4157-221.14-5.7871l-177.75-.3438zm161 128.95 84.25.37695c91.298.40795 95.375.61732 123.75 6.3809 23.495 4.7723 45.38 13.215 61 23.533 15.167 10.019 29.716 27.182 37.475 44.207 14.573 31.978 16.395 82.735 4.3301 120.62-6.6274 20.814-16.172 36.615-31.18 51.625-27.567 27.57-66.814 42.804-121.93 47.324-7.3903.60617-43.437 1.0508-85.25 1.0508h-72.445v-147.56-147.56z"/>
<path d="m2014.6-1051.4v453.6 453.6l145.75-.37695c156.69-.4046 153.13-.29648 191.25-5.8008 38.321-5.5332 77.017-15.82 109.08-28.998 17.362-7.137 22.208-9.743 21.508-11.566-.3206-.8355-1.452-4.9721-2.5156-9.1914-3.4865-13.831-4.3718-23.482-3.7617-41.053.63-18.145 2.2913-27.3 7.7285-42.617 17.594-49.562 60.836-85.599 112.95-94.131 16.457-2.6941 38.955-1.8474 57.701 2.1719 3.6928.79178 3.1565 1.7476 11.26-20.041 27.066-72.775 38.169-169.68 30.476-265.97-14.239-178.25-95.276-299.81-236.97-355.47-33.122-13.01-69.539-22.404-108.45-27.975-38.756-5.5479-34.464-5.4161-190.75-5.8086l-145.25-.3652zm161 130.09 41.75.0156c55.334.0205 78.397 1.6295 108.25 7.5566 105.75 20.995 171.57 87.554 196.39 198.59 12.878 57.6 14.716 139.6 4.5469 202.81-7.3952 45.963-21.469 87.286-40.711 119.53-12.041 20.179-33.82 45.681-51 59.719-38.627 31.563-87.98 50.255-148.73 56.326-9.5463.9541-32.361 1.7291-62.75 2.1328l-47.75.63477v-323.66-323.66z"/>
</g>
<circle id="logo-dot" cx="2575.3" cy="939.96" r="125.4" fill="#499a6c"/>
<g id="logo-js" fill="#33b5e5" transform="translate(-44.194 1175.6)">
<path d="m2602.1 34.57c-57.094-4.6075-113.49-28.558-158.26-67.213-27.741-23.949-51.228-55.235-63.883-85.094-5.4804-12.93-5.926-15.992-2.3882-16.406 8.1404-.953 38.073-7.05 53.318-10.86 20.337-5.0831 29.827-8.2686 48.112-16.15 12.138-5.2318 12.996-5.46 14-3.7198 14.778 25.613 36.757 46.236 62.906 59.024 21.609 10.567 39.696 14.761 63.664 14.761 23.073 0 41.694-4.1466 61.73-13.746 36.584-17.528 62.542-46.884 75.844-85.772 2.3995-7.0151 7.5664-31.714 9.361-44.747 2.8753-20.881 3.0454-40.134 3.0555-345.75l.01-314.25h78 78v318.25c0 209.58-.3574 323.03-1.0389 332.25-4.4405 60.076-22.061 115.17-51.016 159.5-11.306 17.311-21.135 29.375-35.857 44.012-44.122 43.866-101.51 69.204-169.58 74.876-17.815 1.4842-53.463 2.0433-65.964 1.0344z"/>
<path d="m3256.6 33.535c-103.92-8.2588-202.14-50.771-278.59-120.57l-11.459-10.464 4.7737-5.6963c2.6255-3.133 23.371-27.615 46.101-54.405l41.327-48.709 11.068 9.6086c54.856 47.624 120.13 79.074 185.78 89.508 19.275 3.0634 60.816 3.3389 79 .5237 56.007-8.6707 91.978-30.946 109.48-67.793 5.7814-12.174 8.6772-25.17 9.2639-41.574 1.8511-51.755-20.009-81.836-81.241-111.79-10.45-5.1123-25.75-12.128-34-15.591-32.568-13.67-168.23-73.282-178.56-78.459-84.895-42.577-136.19-105.76-149.34-183.97-24.654-146.62 80.068-271.29 246.91-293.93 39.105-5.3065 82.999-4.2183 122.48 3.0365 76.174 13.996 145.21 48.561 201.87 101.07l7.367 6.8275-39.699 49c-21.834 26.95-40.537 49.863-41.563 50.918-1.8327 1.8856-1.9536 1.8424-7.1685-2.562-25.013-21.126-59.394-41.952-87.804-53.188-33.742-13.345-63.677-18.968-101.5-19.066-28.062-.0727-45.321 2.2-65.5 8.6248-40.117 12.773-65.445 37.309-74.612 72.282-3.4331 13.097-3.8978 33.664-1.0368 45.883 7.6067 32.488 29.949 55.7 75.674 78.622 15.123 7.5809 24.021 11.522 52.974 23.46 125.45 51.728 173.58 73.274 198.67 88.935 70.314 43.888 106.41 97.76 116.97 174.59 2.1563 15.683 2.4444 55.002.5056 69-7.9359 57.297-31.186 104.9-70.626 144.6-53.439 53.792-126.37 84.242-218.91 91.402-14.98 1.1588-53.385 1.0944-68.605-.1152z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.2 KiB

File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
# [View the documentation here.](https://discordjs-self-v13.netlify.app/#/docs)
@@ -1,15 +0,0 @@
const { Client } = require('../src/index');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
const channel = client.channels.cache.get('id');
channel.send({
activity: {
type: 3, // MessageActivityType.Listen
partyId: `spotify:${client.user.id}`,
},
});
});
client.login('token');
-35
View File
@@ -1,35 +0,0 @@
'use strict';
// No longer using 2captcha since the website no longer supports hCaptcha, which Discord uses.
const Captcha = require('2captcha');
const Discord = require('../src/index');
const solver = new Captcha.Solver('<2captcha key>');
const client = new Discord.Client({
captchaSolver: function (captcha, UA) {
return solver
.hcaptcha(captcha.captcha_sitekey, 'discord.com', {
invisible: 1,
userAgent: UA,
data: captcha.captcha_rqdata,
})
.then(res => res.data);
},
TOTPKey: '<string>',
});
client.on('ready', async () => {
console.log('Ready!', client.user.tag);
// Note
// You need to include `guild_id` to invite the bot
// These two fields can appear either in the URL or in the options.
await client.authorizeURL(
`https://discord.com/api/oauth2/authorize?client_id=289066747443675143&permissions=414501424448&scope=bot%20applications.commands`,
{
guild_id: 'guild id',
},
);
});
client.login('token');
@@ -1,12 +0,0 @@
'use strict';
const Discord = require('../src/index');
const client = new Discord.Client();
client.on('ready', async () => {
console.log('Ready!', client.user.tag);
await client.installUserApps('936929561302675456'); // Midjourney
});
client.login('token');
-14
View File
@@ -1,14 +0,0 @@
const { Client } = require('../src/index');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
});
client.on("messageCreate", message => {
if (message.content == 'ping') {
message.reply('pong');
}
});
client.login('token');
@@ -1,37 +0,0 @@
const { Client } = require('../src/index');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
const channel = client.channels.cache.get('channel id');
const message = await channel.send({
poll: {
question: {
text: 'What is your favorite color?',
},
answers: [{ text: 'Red', emoji: '🍎' }, { text: 'Green', emoji: '🥗' }, { text: 'Blue', emoji: '💙' }, { text: 'Yellow', emoji: '🟡' }],
duration: 8,
allowMultiselect: true,
},
});
console.log(message.poll);
// Multi select
await message.vote(1, 3);
});
client.on('messagePollVoteAdd', (answer, userId) => {
console.log(`User ${userId} voted for answer ${answer.id}`);
});
client.on('messagePollVoteRemove', (answer, userId) => {
console.log(`User ${userId} removed their vote for answer ${answer.id}`);
});
client.on('messageUpdate', async (_oldMessage, newMessage) => {
if (!newMessage.poll) return;
console.log('Poll was updated', newMessage.poll);
});
client.login('token');
-41
View File
@@ -1,41 +0,0 @@
const { Client, WebEmbed } = require('../src/index');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
});
client.on('messageCreate', message => {
if (message.content == 'embed_hidden_url') {
const embed = new WebEmbed()
.setAuthor({ name: 'hello', url: 'https://google.com' })
.setColor('RED')
.setDescription('description uh')
.setProvider({ name: 'provider', url: 'https://google.com' })
.setTitle('This is Title')
.setURL('https://google.com')
.setImage('https://i.ytimg.com/vi/iBP8HambzpY/maxresdefault.jpg')
.setRedirect('https://www.youtube.com/watch?v=iBP8HambzpY')
.setVideo('http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4');
message.channel.send({
content: `Hello world ${WebEmbed.hiddenEmbed}${embed}`,
});
}
if (message.content == 'embed') {
const embed = new WebEmbed()
.setAuthor({ name: 'hello', url: 'https://google.com' })
.setColor('RED')
.setDescription('description uh')
.setProvider({ name: 'provider', url: 'https://google.com' })
.setTitle('This is Title')
.setURL('https://google.com')
.setImage('https://i.ytimg.com/vi/iBP8HambzpY/maxresdefault.jpg')
.setRedirect('https://www.youtube.com/watch?v=iBP8HambzpY')
.setVideo('http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4');
message.channel.send({
content: `${embed}`,
});
}
});
client.login('token');
-27
View File
@@ -1,27 +0,0 @@
'use strict';
// No longer using 2captcha since the website no longer supports hCaptcha, which Discord uses.
const Captcha = require('2captcha');
const Discord = require('../src/index');
const solver = new Captcha.Solver('<2captcha key>');
const client = new Discord.Client({
captchaSolver: function (captcha, UA) {
return solver
.hcaptcha(captcha.captcha_sitekey, 'discord.com', {
invisible: 1,
userAgent: UA,
data: captcha.captcha_rqdata,
})
.then(res => res.data);
},
captchaRetryLimit: 3,
});
client.on('ready', async () => {
console.log('Ready!', client.user.tag);
await client.acceptInvite('mdmc');
});
client.login('token');
-33
View File
@@ -1,33 +0,0 @@
'use strict';
const Discord = require('../src/index');
const { ProxyAgent } = require('proxy-agent');
const proxy = new ProxyAgent({
getProxyForUrl: function () {
return '<any proxy>';
},
});
const client = new Discord.Client({
ws: {
agent: proxy, // WebSocket Proxy
// Do not use the `proxy` option if you don't need to use the WebSocket Proxy
},
http: {
// API Proxy
// Read more: https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md
// agent: ProxyAgentOptions
agent: 'my.proxy.server',
// or new URL('my.proxy.server')
// or { uri: 'my.proxy.server' }
},
});
// So if you only need to use the API Proxy (for the purpose of saving data), you don't need to install `proxy-agent`.
client.on('ready', async () => {
console.log('Ready!', client.user.tag);
});
client.login('token');
-47
View File
@@ -1,47 +0,0 @@
const { Client, RichPresence, CustomStatus, SpotifyRPC } = require('discord.js-selfbot-v13');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
const getExtendURL = await RichPresence.getExternal(
client,
'367827983903490050',
'https://assets.ppy.sh/beatmaps/1550633/covers/list.jpg', // Required if the image you use is not in Discord
);
const status = new RichPresence(client)
.setApplicationId('367827983903490050')
.setType('PLAYING')
.setURL('https://www.youtube.com/watch?v=5icFcPkVzMg') // If you set a URL, it will automatically change to STREAMING type
.setState('Arcade Game')
.setName('osu!')
.setDetails('MariannE - Yooh')
.setParty({
max: 8,
current: 1,
})
.setStartTimestamp(Date.now())
.setAssetsLargeImage(getExtendURL[0].external_asset_path) // https://assets.ppy.sh/beatmaps/1550633/covers/list.jpg
.setAssetsLargeText('Idle')
.setAssetsSmallImage('373370493127884800') // https://discord.com/api/v9/oauth2/applications/367827983903490050/assets
.setAssetsSmallText('click the circles')
.setPlatform('desktop')
.addButton('Beatmap', 'https://osu.ppy.sh/beatmapsets/1391659#osu/2873429');
// Custom Status
const custom = new CustomStatus(client).setEmoji('😋').setState('yum');
// Spotify
const spotify = new SpotifyRPC(client)
.setAssetsLargeImage('spotify:ab67616d00001e02768629f8bc5b39b68797d1bb') // Image ID
.setAssetsSmallImage('spotify:ab6761610000f178049d8aeae802c96c8208f3b7') // Image ID
.setAssetsLargeText('未来茶屋 (vol.1)') // Album Name
.setState('Yunomi; Kizuna AI') // Artists
.setDetails('ロボットハート') // Song name
.setStartTimestamp(Date.now())
.setEndTimestamp(Date.now() + 1_000 * (2 * 60 + 56)) // Song length = 2m56s
.setSongId('667eE4CFfNtJloC6Lvmgrx') // Song ID
.setAlbumId('6AAmvxoPoDbJAwbatKwMb9') // Album ID
.setArtistIds('2j00CVYTPx6q9ANbmB2keb', '2nKGmC5Mc13ct02xAY8ccS'); // Artist IDs
client.user.setPresence({ activities: [status, custom, spotify] });
});
client.login('token');
-17
View File
@@ -1,17 +0,0 @@
const { Client } = require('../src/index');
const client = new Client();
client.on('ready', async () => {
client.user.setSamsungActivity('com.YostarJP.BlueArchive', 'START');
setTimeout(() => {
client.user.setSamsungActivity('com.miHoYo.bh3oversea', 'UPDATE');
}, 30_000);
setTimeout(() => {
client.user.setSamsungActivity('com.miHoYo.GenshinImpact', 'STOP');
}, 60_000);
});
client.login('token');
-129
View File
@@ -1,129 +0,0 @@
# Slash command
```js
TextBasedChannel.sendSlash(
user: BotId (Snowflake) | User (User.bot === true),
commandName: 'command_name [sub_group] [sub]',
...args: (string|number|boolean|FileLike|undefined)[],
): Promise<Message<true> | Modal>
```
## Basic
### Demo
![image](https://user-images.githubusercontent.com/71698422/173344527-86520c60-64cd-459c-ba3b-d35f14279f93.png)
### Code
```js
await channel.sendSlash('bot_id', 'aiko')
```
## Sub Command / Sub Group
### Demo
![image](https://user-images.githubusercontent.com/71698422/173346438-678009a1-870c-49a2-97fe-8ceed4f1ab64.png)
### Code test
```js
await channel.sendSlash('450323683840491530', 'animal chat', 'bye')
```
## Attachment
### Demo
![image](https://user-images.githubusercontent.com/71698422/173346964-0c44f91f-e5bf-43d4-8401-914fc3e92073.png)
### Code test
```js
const { MessageAttachment } = require('discord.js-selfbot-v13')
const fs = require('fs')
const a = new MessageAttachment(fs.readFileSync('./wallpaper.jpg') , 'test.jpg')
await message.channel.sendSlash('718642000898818048', 'sauce', a)
```
### Result
![image](https://user-images.githubusercontent.com/71698422/173347075-5c8a1347-3845-489e-956b-63975911b6e0.png)
## Skip options
### Demo Command
![image](https://github.com/user-attachments/assets/e7b8fc6c-4816-49df-a400-6a4eed7a9a88)
![image](https://github.com/user-attachments/assets/3452f388-639b-4626-a826-56ec3683ee32)
![image](https://github.com/user-attachments/assets/4a1e92d7-402d-4087-afa7-5794ce8ba6eb)
![image](https://github.com/user-attachments/assets/85b029f4-27f7-4e20-b3a7-a4d0597b4a98)
### Code
```js
const channel = client.channels.cache.get('channel_id');
const response = await channel.sendSlash(
'bot_id',
'image make',
'MeinaMix - v11',
'Phone (9:16) [576x1024 | 810x1440]',
'2', // String choices, not number
undefined, // VAE
undefined, // sdxl_refiner
undefined, // sampling_method,
30,
);
// Submit Modal
if (!response.isMessage) { // Modal
response.components[0].components[0].setValue(
'1girl, brown hair, green eyes, colorful, autumn, cumulonimbus clouds',
);
response.components[1].components[0].setValue(
'(worst quality:1.4), (low quality:1.4), (normal quality:1.4), (ugly:1.4), (bad anatomy:1.4), (extra limbs:1.2), (text, error, signature, watermark:1.2), (bad legs, incomplete legs), (bad feet), (bad arms), (bad hands, too many hands, mutated hands), (zombie, sketch, interlocked fingers, comic, morbid), cropped, long neck, lowres, missing fingers, missing arms, missing legs, extra fingers, extra digit, fewer digits, jpeg artifacts',
);
await response.reply();
}
```
### Receive messages after bot has replied `{botname} is thinking...`
> [aiko-chan-ai/discord.js-selfbot-v13#1055 (comment)](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues/1055#issuecomment-1949653100)
![image](https://github.com/user-attachments/assets/0a1d253a-7751-4f63-a750-58b50d055928)
```js
const channel = client.channels.cache.get('id');
channel
.sendSlash('289066747443675143', 'osu', 'Accolibed')
.then(async (message) => {
if (message.flags.has('LOADING')) { // owo is thinking...
return new Promise((resolve, reject) => {
let done = false;
const timeout = setTimeout(() => {
if (!done) {
done = true;
client.off('messageUpdate', onUpdate);
reject('timeout');
}
}, 15 * 60 * 1000); // 15m (DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE)
function onUpdate(_, m) {
if (_.id === message.id) {
if (!done) {
done = true;
clearTimeout(timeout);
client.off('messageUpdate', onUpdate);
resolve(m);
}
}
}
client.on('messageUpdate', onUpdate);
});
} else {
return Promise.resolve(message);
}
})
.then(console.log);
```
@@ -1,30 +0,0 @@
// Join a voice channel and do nothing
/*
Install:
- An Opus library: @discordjs/opus or opusscript
- An encryption packages:
+ sodium (best performance)
+ libsodium-wrappers
+ @stablelib/xchacha20poly1305
- ffmpeg (install and add to your system environment)
*/
const { Client } = require('../../src/index');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
const channel = client.channels.cache.get('voice_channel');
const connection = await client.voice.joinChannel(channel, {
selfMute: true,
selfDeaf: true,
selfVideo: false,
});
// Leave voice
setTimeout(() => {
connection.disconnect();
}, 5_000);
});
client.login('token');
@@ -1,58 +0,0 @@
// Join a channel and play music, like a Discord bot.
/*
Install:
- An Opus library: @discordjs/opus or opusscript
- An encryption packages:
+ sodium (best performance)
+ libsodium-wrappers
+ @stablelib/xchacha20poly1305
- ffmpeg (install and add to your system environment)
*/
const { Client } = require('../../src/index');
const ytdl = require('@distube/ytdl-core'); // better than ytdl-core
const client = new Client();
client.on('ready', async client => {
console.log(`${client.user.username} is ready!`);
const channel = client.channels.cache.get('voice_id');
const connection = await client.voice.joinChannel(channel, {
selfMute: true,
selfDeaf: true,
selfVideo: false,
});
const dispatcher = connection.playAudio(
ytdl('https://www.youtube.com/watch?v=3KadWjpqDXs', {
quality: 'highestaudio',
}),
);
dispatcher.on('start', () => {
console.log('audio is now playing!');
// pause
console.log('paused');
dispatcher.pause();
// resume
setTimeout(() => {
console.log('resumed');
dispatcher.resume();
}, 5_000);
// Set volume
dispatcher.setVolume(0.5);
console.log('50% volume');
});
dispatcher.on('finish', () => {
console.log('audio has finished playing!');
});
dispatcher.on('error', console.error);
// Leave voice
setTimeout(() => {
console.log('disconnected');
connection.disconnect();
}, 30_000);
});
client.login('token');
@@ -1,72 +0,0 @@
/*
Credit: https://github.com/dank074/Discord-video-stream
The use of video streaming in this library is an incomplete implementation with many bugs, primarily aimed at lazy users.
The video streaming features in this library are sourced from https://github.com/dank074/Discord-video-stream.
Please use the @dank074/discord-video-stream library to access all advanced and professional features,
along with comprehensive support. I will not actively fix bugs related to streaming and encourage everyone to
use https://github.com/dank074/Discord-video-stream for stable and smooth streaming.
To reiterate: This is an incomplete implementation of the library https://github.com/dank074/Discord-video-stream.
Thanks to dank074 and longnguyen2004 for implementing new codecs (H264, H265).
Thanks to mrjvs for discovering how Discord transmits data and the VP8 codec.
Please use the @dank074/discord-video-stream library for the best support.
*/
/*
Install:
- An Opus library: @discordjs/opus or opusscript
- An encryption packages:
+ sodium (best performance)
+ libsodium-wrappers
+ @stablelib/xchacha20poly1305
- ffmpeg (install and add to your system environment)
*/
const { Client } = require('../../src/index');
const client = new Client();
client.on('ready', async client => {
console.log(`${client.user.username} is ready!`);
const channel = client.channels.cache.get('voice_channel');
const connection = await client.voice.joinChannel(channel, {
selfMute: true,
selfDeaf: true,
selfVideo: false, // Turn on the camera? If you turn on the camera, use the connection similar to
// PlayAudio.js, and it will stream video through the camera. This is an implementation of screen sharing.
videoCodec: 'H264',
});
const stream = await connection.createStreamConnection();
// You can also access it by using `connection.streamConnection` (only after it has been initialized by the `createStreamConnection` function).
// Split it into two separate streams (audio / video)
// Or with a combined stream that will be automatically processed by FFmpeg.
const input = 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
// Play
const dispatcher = stream.playVideo(input, {
fps: 60,
bitrate: 4000,
});
const dispatcher2 = stream.playAudio(input);
dispatcher.on('start', () => {
console.log('video is now playing!');
});
dispatcher.on('finish', () => {
console.log('video has finished playing!');
});
dispatcher.on('error', console.error);
dispatcher2.on('start', () => {
console.log('audio is now playing!');
});
dispatcher2.on('finish', () => {
console.log('audio has finished playing!');
});
dispatcher2.on('error', console.error);
// Of course, you can also pause the stream using the `pause` function, but remember to pause both video and audio.
});
client.login('token');
@@ -1,54 +0,0 @@
// https://v12.discordjs.guide/voice/receiving-audio.html#basic-usage
/*
Install:
- An Opus library: @discordjs/opus or opusscript
- An encryption packages:
+ sodium (best performance)
+ libsodium-wrappers
+ @stablelib/xchacha20poly1305
- ffmpeg (install and add to your system environment)
*/
const { Client } = require('../../src/index');
const client = new Client();
const fs = require('fs');
const Speaker = require('speaker');
client.on('ready', async client => {
console.log(`${client.user.username} is ready!`);
const speaker = new Speaker({
channels: 2, // 2 channels
bitDepth: 16, // 16-bit samples
sampleRate: 48000, // 48000 Hz sample rate
});
const channel = client.channels.cache.get('voice_id');
const connection = await client.voice.joinChannel(channel, {
selfMute: true,
selfDeaf: true,
selfVideo: false,
});
const audio = connection.receiver.createStream('user_id', {
mode: 'pcm',
end: 'manual',
paddingSilence: true,
});
audio.pipe(fs.createWriteStream('test.pcm'));
// After 15s
setTimeout(() => {
console.log('Stop recording');
audio.destroy();
// Play this record...
fs.createReadStream('test.pcm').pipe(speaker);
}, 15_000);
});
client.login('token');
@@ -1,45 +0,0 @@
// https://v12.discordjs.guide/voice/receiving-audio.html#basic-usage
/*
Install:
- An Opus library: @discordjs/opus or opusscript
- An encryption packages:
+ sodium (best performance)
+ libsodium-wrappers
+ @stablelib/xchacha20poly1305
- ffmpeg (install and add to your system environment)
*/
const { Client } = require('../../src/index');
const client = new Client();
const fs = require('fs');
client.on('ready', async client => {
console.log(`${client.user.username} is ready!`);
const channel = client.channels.cache.get('voice_id');
const connection = await client.voice.joinChannel(channel, {
selfMute: true,
selfDeaf: true,
selfVideo: false,
});
const connectionStream = await connection.joinStreamConnection('user_id');
const video = connectionStream.receiver.createVideoStream('user_id', fs.createWriteStream('video.mkv')); // Output file using matroska container
video.on('ready', () => {
console.log('FFmpeg process ready!');
video.stream.stderr.on('data', data => {
console.log(`FFmpeg: ${data}`);
});
});
// After 15s
setTimeout(() => {
video.destroy();
}, 15_000);
});
client.login('token');
-21
View File
@@ -1,21 +0,0 @@
const { Client, MessageAttachment } = require('../src/index');
const client = new Client();
client.on('ready', async () => {
console.log(`${client.user.username} is ready!`);
const channel = client.channels.cache.get('channel_id');
const attachment = new MessageAttachment(
'./test.mp3', // path file
'random_file_name.ogg', // must be .ogg
{
waveform: 'AAAAAAAAAAAA',
duration_secs: 1, // any number you want
},
);
channel.send({
files: [attachment],
flags: 'IS_VOICE_MESSAGE',
});
});
client.login('token');
-79
View File
@@ -1,79 +0,0 @@
{
"name": "discord.js-selfbot-v13",
"version": "3.7.1",
"description": "An unofficial discord.js fork for creating selfbots",
"main": "./src/index.js",
"types": "./typings/index.d.ts",
"scripts": {
"all": "npm run build && npm publish",
"test": "npm run lint && npm run test:typescript && npm run docs:test",
"fix:all": "npm run format",
"test:typescript": "tsc --noEmit && tsd",
"lint": "biome check . --diagnostic-level=error",
"format": "biome format --write .",
"docs": "docgen --source src --custom docs/index.yml --output docs/main.json",
"docs:test": "docgen --source src --custom docs/index.yml",
"build": "npm run format && npm run docs"
},
"files": [
"src",
"typings"
],
"directories": {
"lib": "src",
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/aiko-chan-ai/discord.js-selfbot-v13.git"
},
"keywords": [
"discord.js",
"discord.js v13",
"selfbot",
"selfbot v13",
"djs",
"api",
"bot",
"node",
"discord",
"client",
"discordapp"
],
"author": "aiko-chan-ai",
"license": "GNU General Public License v3.0",
"bugs": {
"url": "https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues"
},
"homepage": "https://github.com/aiko-chan-ai/discord.js-selfbot-v13#readme",
"dependencies": {
"@discordjs/builders": "^1.13.0",
"@discordjs/collection": "^2.1.1",
"@sapphire/async-queue": "^1.5.5",
"@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "^0.38.38",
"fetch-cookie": "^3.1.0",
"find-process": "^2.0.0",
"otplib": "^12.0.1",
"prism-media": "^2.0.0-alpha.0",
"qrcode": "^1.5.4",
"tough-cookie": "^5.1.2",
"tree-kill": "^1.2.2",
"undici": "^7.16.0",
"werift-rtp": "^0.8.4",
"ws": "^8.20.0"
},
"engines": {
"node": ">=20.18"
},
"devDependencies": {
"@biomejs/biome": "latest",
"@discordjs/docgen": "^0.11.1",
"@types/debug": "^4.1.12",
"@types/node": "^25.8.0",
"@types/ws": "^8.18.1",
"patch-package": "^8.0.1",
"tsd": "^0.33.0",
"typescript": "^5.9.3"
}
}
@@ -1,21 +0,0 @@
diff --git a/node_modules/jsdoc/lib/jsdoc/util/dumper.js b/node_modules/jsdoc/lib/jsdoc/util/dumper.js
index 515c972..975757b 100644
--- a/node_modules/jsdoc/lib/jsdoc/util/dumper.js
+++ b/node_modules/jsdoc/lib/jsdoc/util/dumper.js
@@ -95,13 +95,13 @@ class ObjectWalker {
return newArray;
});
}
- else if ( util.isRegExp(o) ) {
+ else if ( util.types.isRegExp(o) ) {
result = `<RegExp ${o}>`;
}
- else if ( util.isDate(o) ) {
+ else if ( util.types.isDate(o) ) {
result = `<Date ${o.toUTCString()}>`;
}
- else if ( util.isError(o) ) {
+ else if ( Object.prototype.toString.call(o) === '[object Error]' ) {
result = { message: o.message };
}
else if ( this.isFunction(o) ) {
-40
View File
@@ -1,40 +0,0 @@
'use strict';
let erlpack;
const { Buffer } = require('node:buffer');
try {
erlpack = require('erlpack');
if (!erlpack.pack) erlpack = null;
} catch {} // eslint-disable-line no-empty
exports.WebSocket = require('ws');
const ab = new TextDecoder();
exports.encoding = erlpack ? 'etf' : 'json';
exports.pack = erlpack ? erlpack.pack : JSON.stringify;
exports.unpack = (data, type) => {
if (exports.encoding === 'json' || type === 'json') {
if (typeof data !== 'string') {
data = ab.decode(data);
}
return JSON.parse(data);
}
if (!Buffer.isBuffer(data)) data = Buffer.from(new Uint8Array(data));
return erlpack.unpack(data);
};
exports.create = (gateway, query = {}, ...args) => {
const [g, q] = gateway.split('?');
query.encoding = exports.encoding;
query = new URLSearchParams(query);
if (q) new URLSearchParams(q).forEach((v, k) => query.set(k, v));
const ws = new exports.WebSocket(`${g}?${query}`, ...args);
return ws;
};
for (const state of ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'])
exports[state] = exports.WebSocket[state];
-86
View File
@@ -1,86 +0,0 @@
'use strict';
const EventEmitter = require('node:events');
const process = require('node:process');
const RESTManager = require('../rest/RESTManager');
const Options = require('../util/Options');
const Util = require('../util/Util');
/**
* The base class for all clients.
* @extends {EventEmitter}
*/
class BaseClient extends EventEmitter {
constructor(options = {}) {
super({ captureRejections: true });
if (options.intents) {
process.emitWarning('Intents is not available.', 'DeprecationWarning');
}
/**
* The options the client was instantiated with
* @type {ClientOptions}
*/
this.options = Util.mergeDefault(Options.createDefault(), options);
/**
* The REST manager of the client
* @type {RESTManager}
* @private
*/
this.rest = new RESTManager(this);
}
/**
* API shortcut
* @type {Object}
* @readonly
* @private
*/
get api() {
return this.rest.api;
}
/**
* Destroys all assets used by the base client.
* @returns {void}
*/
destroy() {
if (this.rest.sweepInterval) clearInterval(this.rest.sweepInterval);
}
/**
* Increments max listeners by one, if they are not zero.
* @private
*/
incrementMaxListeners() {
const maxListeners = this.getMaxListeners();
if (maxListeners !== 0) {
this.setMaxListeners(maxListeners + 1);
}
}
/**
* Decrements max listeners by one, if they are not zero.
* @private
*/
decrementMaxListeners() {
const maxListeners = this.getMaxListeners();
if (maxListeners !== 0) {
this.setMaxListeners(maxListeners - 1);
}
}
toJSON(...props) {
return Util.flatten(this, { domain: false }, ...props);
}
}
module.exports = BaseClient;
/**
* Emitted for general debugging information.
* @event BaseClient#debug
* @param {string} info The debug information
*/
File diff suppressed because it is too large Load Diff
@@ -1,65 +0,0 @@
'use strict';
const BaseClient = require('./BaseClient');
const { Error } = require('../errors');
const Webhook = require('../structures/Webhook');
/**
* The webhook client.
* @implements {Webhook}
* @extends {BaseClient}
*/
class WebhookClient extends BaseClient {
/**
* The data for the webhook client containing either an id and token or just a URL
* @typedef {Object} WebhookClientData
* @property {Snowflake} [id] The id of the webhook
* @property {string} [token] The token of the webhook
* @property {string} [url] The full URL for the webhook client
*/
/**
* @param {WebhookClientData} data The data of the webhook
* @param {ClientOptions} [options] Options for the client
*/
constructor(data, options) {
super(options);
Object.defineProperty(this, 'client', { value: this });
let { id, token } = data;
if ('url' in data) {
const url = data.url.match(
// eslint-disable-next-line no-useless-escape
/^https?:\/\/(?:canary|ptb)?\.?discord\.com\/api\/webhooks(?:\/v[0-9]\d*)?\/([^\/]+)\/([^\/]+)/i,
);
if (!url || url.length <= 1) throw new Error('WEBHOOK_URL_INVALID');
[, id, token] = url;
}
this.id = id;
Object.defineProperty(this, 'token', {
value: token,
writable: true,
configurable: true,
});
}
// These are here only for documentation purposes - they are implemented by Webhook
/* eslint-disable no-empty-function */
send() {}
sendSlackMessage() {}
fetchMessage() {}
edit() {}
editMessage() {}
delete() {}
deleteMessage() {}
get createdTimestamp() {}
get createdAt() {}
get url() {}
}
Webhook.applyToClass(WebhookClient);
module.exports = WebhookClient;
@@ -1,130 +0,0 @@
'use strict';
const { PartialTypes } = require('../../util/Constants');
/*
ABOUT ACTIONS
Actions are similar to WebSocket Packet Handlers, but since introducing
the REST API methods, in order to prevent rewriting code to handle data,
"actions" have been introduced. They're basically what Packet Handlers
used to be but they're strictly for manipulating data and making sure
that WebSocket events don't clash with REST methods.
*/
class GenericAction {
constructor(client) {
this.client = client;
}
handle(data) {
return data;
}
getPayload(data, manager, id, partialType, cache) {
const existing = manager.cache.get(id);
if (!existing && this.client.options.partials.includes(partialType)) {
return manager._add(data, cache);
}
return existing;
}
getChannel(data) {
const payloadData = {};
const id = data.channel_id ?? data.id;
if (!('recipients' in data)) {
// Try to resolve the recipient, but do not add the client user.
const recipient = data.author ?? data.user ?? { id: data.user_id };
if (recipient.id !== this.client.user.id)
payloadData.recipients = [recipient];
}
if (id !== undefined) payloadData.id = id;
return (
data[this.client.actions.injectedChannel] ??
this.getPayload(
{ ...data, ...payloadData },
this.client.channels,
id,
PartialTypes.CHANNEL,
)
);
}
getMessage(data, channel, cache) {
const id = data.message_id ?? data.id;
return (
data[this.client.actions.injectedMessage] ??
this.getPayload(
{
id,
channel_id: channel.id,
guild_id: data.guild_id ?? channel.guild?.id,
},
channel.messages,
id,
PartialTypes.MESSAGE,
cache,
)
);
}
getReaction(data, message, user) {
const id = data.emoji.id ?? decodeURIComponent(data.emoji.name);
return this.getPayload(
{
emoji: data.emoji,
count: message.partial ? null : 0,
me: user?.id === this.client.user.id,
},
message.reactions,
id,
PartialTypes.REACTION,
);
}
getMember(data, guild) {
return this.getPayload(
data,
guild.members,
data.user.id,
PartialTypes.GUILD_MEMBER,
);
}
getUser(data) {
const id = data.user_id;
return (
data[this.client.actions.injectedUser] ??
this.getPayload({ id }, this.client.users, id, PartialTypes.USER)
);
}
getUserFromMember(data) {
if (data.guild_id && data.member?.user) {
const guild = this.client.guilds.cache.get(data.guild_id);
if (guild) {
return guild.members._add(data.member).user;
} else {
return this.client.users._add(data.member.user);
}
}
return this.getUser(data);
}
getScheduledEvent(data, guild) {
const id = data.guild_scheduled_event_id ?? data.id;
return this.getPayload(
{ id, guild_id: data.guild_id ?? guild.id },
guild.scheduledEvents,
id,
PartialTypes.GUILD_SCHEDULED_EVENT,
);
}
}
module.exports = GenericAction;
@@ -1,80 +0,0 @@
'use strict';
class ActionsManager {
constructor(client) {
this.client = client;
// These symbols represent fully built data that we inject at times when calling actions manually.
// Action#getUser for example, will return the injected data (which is assumed to be a built structure)
// instead of trying to make it from provided data
this.injectedUser = Symbol('djs.actions.injectedUser');
this.injectedChannel = Symbol('djs.actions.injectedChannel');
this.injectedMessage = Symbol('djs.actions.injectedMessage');
this.register(require('./ApplicationCommandPermissionsUpdate'));
this.register(require('./AutoModerationActionExecution'));
this.register(require('./AutoModerationRuleCreate'));
this.register(require('./AutoModerationRuleDelete'));
this.register(require('./AutoModerationRuleUpdate'));
this.register(require('./ChannelCreate'));
this.register(require('./ChannelDelete'));
this.register(require('./ChannelUpdate'));
this.register(require('./GuildAuditLogEntryCreate'));
this.register(require('./GuildBanAdd'));
this.register(require('./GuildBanRemove'));
this.register(require('./GuildChannelsPositionUpdate'));
this.register(require('./GuildDelete'));
this.register(require('./GuildEmojiCreate'));
this.register(require('./GuildEmojiDelete'));
this.register(require('./GuildEmojiUpdate'));
this.register(require('./GuildEmojisUpdate'));
this.register(require('./GuildIntegrationsUpdate'));
this.register(require('./GuildMemberRemove'));
this.register(require('./GuildMemberUpdate'));
this.register(require('./GuildRoleCreate'));
this.register(require('./GuildRoleDelete'));
this.register(require('./GuildRoleUpdate'));
this.register(require('./GuildRolesPositionUpdate'));
this.register(require('./GuildScheduledEventCreate'));
this.register(require('./GuildScheduledEventDelete'));
this.register(require('./GuildScheduledEventUpdate'));
this.register(require('./GuildScheduledEventUserAdd'));
this.register(require('./GuildScheduledEventUserRemove'));
this.register(require('./GuildStickerCreate'));
this.register(require('./GuildStickerDelete'));
this.register(require('./GuildStickerUpdate'));
this.register(require('./GuildStickersUpdate'));
this.register(require('./GuildUpdate'));
this.register(require('./InviteCreate'));
this.register(require('./InviteDelete'));
this.register(require('./MessageCreate'));
this.register(require('./MessageDelete'));
this.register(require('./MessageDeleteBulk'));
this.register(require('./MessagePollVoteAdd'));
this.register(require('./MessagePollVoteRemove'));
this.register(require('./MessageReactionAdd'));
this.register(require('./MessageReactionRemove'));
this.register(require('./MessageReactionRemoveAll'));
this.register(require('./MessageReactionRemoveEmoji'));
this.register(require('./MessageUpdate'));
this.register(require('./PresenceUpdate'));
this.register(require('./StageInstanceCreate'));
this.register(require('./StageInstanceDelete'));
this.register(require('./StageInstanceUpdate'));
this.register(require('./ThreadCreate'));
this.register(require('./ThreadDelete'));
this.register(require('./ThreadListSync'));
this.register(require('./ThreadMemberUpdate'));
this.register(require('./ThreadMembersUpdate'));
this.register(require('./TypingStart'));
this.register(require('./UserUpdate'));
this.register(require('./VoiceStateUpdate'));
this.register(require('./WebhooksUpdate'));
}
register(Action) {
this[Action.name.replace(/Action$/, '')] = new Action(this.client);
}
}
module.exports = ActionsManager;
@@ -1,34 +0,0 @@
'use strict';
const Action = require('./Action');
const { Events } = require('../../util/Constants');
/**
* The data received in the {@link Client#event:applicationCommandPermissionsUpdate} event
* @typedef {Object} ApplicationCommandPermissionsUpdateData
* @property {Snowflake} id The id of the command or global entity that was updated
* @property {Snowflake} guildId The id of the guild in which permissions were updated
* @property {Snowflake} applicationId The id of the application that owns the command or entity being updated
* @property {ApplicationCommandPermissions[]} permissions The updated permissions
*/
class ApplicationCommandPermissionsUpdateAction extends Action {
handle(data) {
const client = this.client;
/**
* Emitted whenever permissions for an application command in a guild were updated.
* <warn>This includes permission updates for other applications in addition to the logged in client,
* check `data.applicationId` to verify which application the update is for</warn>
* @event Client#applicationCommandPermissionsUpdate
* @param {ApplicationCommandPermissionsUpdateData} data The updated permissions
*/
client.emit(Events.APPLICATION_COMMAND_PERMISSIONS_UPDATE, {
permissions: data.permissions,
id: data.id,
guildId: data.guild_id,
applicationId: data.application_id,
});
}
}
module.exports = ApplicationCommandPermissionsUpdateAction;
@@ -1,30 +0,0 @@
'use strict';
const Action = require('./Action');
const AutoModerationActionExecution = require('../../structures/AutoModerationActionExecution');
const { Events } = require('../../util/Constants');
class AutoModerationActionExecutionAction extends Action {
handle(data) {
const { client } = this;
const guild = client.guilds.cache.get(data.guild_id);
if (guild) {
/**
* Emitted whenever an auto moderation rule is triggered.
* <info>This event requires the {@link Permissions.FLAGS.MANAGE_GUILD} permission.</info>
* @event Client#autoModerationActionExecution
* @param {AutoModerationActionExecution} autoModerationActionExecution The data of the execution
* @deprecated This event is not received by user accounts.
*/
client.emit(
Events.AUTO_MODERATION_ACTION_EXECUTION,
new AutoModerationActionExecution(data, guild),
);
}
return {};
}
}
module.exports = AutoModerationActionExecutionAction;
@@ -1,28 +0,0 @@
'use strict';
const Action = require('./Action');
const { Events } = require('../../util/Constants');
class AutoModerationRuleCreateAction extends Action {
handle(data) {
const { client } = this;
const guild = client.guilds.cache.get(data.guild_id);
if (guild) {
const autoModerationRule = guild.autoModerationRules._add(data);
/**
* Emitted whenever an auto moderation rule is created.
* <info>This event requires the {@link Permissions.FLAGS.MANAGE_GUILD} permission.</info>
* @event Client#autoModerationRuleCreate
* @param {AutoModerationRule} autoModerationRule The created auto moderation rule
* @deprecated This event is not received by user accounts.
*/
client.emit(Events.AUTO_MODERATION_RULE_CREATE, autoModerationRule);
}
return {};
}
}
module.exports = AutoModerationRuleCreateAction;
@@ -1,32 +0,0 @@
'use strict';
const Action = require('./Action');
const { Events } = require('../../util/Constants');
class AutoModerationRuleDeleteAction extends Action {
handle(data) {
const { client } = this;
const guild = client.guilds.cache.get(data.guild_id);
if (guild) {
const autoModerationRule = guild.autoModerationRules.cache.get(data.id);
if (autoModerationRule) {
guild.autoModerationRules.cache.delete(autoModerationRule.id);
/**
* Emitted whenever an auto moderation rule is deleted.
* <info>This event requires the {@link Permissions.FLAGS.MANAGE_GUILD} permission.</info>
* @event Client#autoModerationRuleDelete
* @param {AutoModerationRule} autoModerationRule The deleted auto moderation rule
* @deprecated This event is not received by user accounts.
*/
client.emit(Events.AUTO_MODERATION_RULE_DELETE, autoModerationRule);
}
}
return {};
}
}
module.exports = AutoModerationRuleDeleteAction;
@@ -1,35 +0,0 @@
'use strict';
const Action = require('./Action');
const { Events } = require('../../util/Constants');
class AutoModerationRuleUpdateAction extends Action {
handle(data) {
const { client } = this;
const guild = client.guilds.cache.get(data.guild_id);
if (guild) {
const oldAutoModerationRule =
guild.autoModerationRules.cache.get(data.id)?._clone() ?? null;
const newAutoModerationRule = guild.autoModerationRules._add(data);
/**
* Emitted whenever an auto moderation rule gets updated.
* <info>This event requires the {@link Permissions.FLAGS.MANAGE_GUILD} permission.</info>
* @event Client#autoModerationRuleUpdate
* @param {?AutoModerationRule} oldAutoModerationRule The auto moderation rule before the update
* @param {AutoModerationRule} newAutoModerationRule The auto moderation rule after the update
* @deprecated This event is not received by user accounts.
*/
client.emit(
Events.AUTO_MODERATION_RULE_UPDATE,
oldAutoModerationRule,
newAutoModerationRule,
);
}
return {};
}
}
module.exports = AutoModerationRuleUpdateAction;

Some files were not shown because too many files have changed in this diff Show More