Gigantic Update - A lot bug fixes and refactorings. #29

Merged
FrauJulian merged 2 commits from 20-gigantic-update into master 2025-12-16 15:05:25 +00:00
25 changed files with 11038 additions and 22118 deletions
Showing only changes of commit b0dc3ba52b - Show all commits
+9
View File
@@ -0,0 +1,9 @@
*.ts text eol=crlf linguist-language=TypeScript
*.tsx text eol=crlf linguist-language=TypeScript
*.mts text eol=crlf linguist-language=TypeScript
*.cts text eol=crlf linguist-language=TypeScript
*.js text eol=crlf linguist-language=JavaScript
*.jsx text eol=crlf linguist-language=JavaScript
*.mjs text eol=crlf linguist-language=JavaScript
*.cjs text eol=crlf linguist-language=JavaScript
copilot-pull-request-reviewer[bot] commented 2025-12-16 15:15:54 +00:00 (Migrated from github.com)
Review

Enforcing CRLF line endings for source files will cause issues for contributors on Unix-based systems (Linux/macOS) where LF is standard. Consider using eol=lf or removing the eol directive to let Git handle line endings automatically based on each contributor's system settings.

*.ts text linguist-language=TypeScript
*.tsx text linguist-language=TypeScript
*.mts text linguist-language=TypeScript
*.cts text linguist-language=TypeScript

*.js text linguist-language=JavaScript
*.jsx text linguist-language=JavaScript
*.mjs text linguist-language=JavaScript
*.cjs text linguist-language=JavaScript
Enforcing CRLF line endings for source files will cause issues for contributors on Unix-based systems (Linux/macOS) where LF is standard. Consider using `eol=lf` or removing the eol directive to let Git handle line endings automatically based on each contributor's system settings. ```suggestion *.ts text linguist-language=TypeScript *.tsx text linguist-language=TypeScript *.mts text linguist-language=TypeScript *.cts text linguist-language=TypeScript *.js text linguist-language=JavaScript *.jsx text linguist-language=JavaScript *.mjs text linguist-language=JavaScript *.cjs text linguist-language=JavaScript ```
+42
View File
@@ -0,0 +1,42 @@
name: 🐛 Bug Report
description: Report a bug or unexpected behavior
title: '🐛 [BUG]: '
body:
- type: textarea
id: information
attributes:
label: Information
description: Provide all information about the issue.
placeholder: 'What happened? What did you expect to happen?'
validations:
required: false
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: Step-by-step instructions to reproduce the issue.
placeholder: |
1. Go to ...
2. Click on ...
3. Observe ...
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs or Error Messages
description: Paste any relevant console output, stack traces, or screenshots.
placeholder: 'Console output, stacktrace, or screenshots here'
validations:
required: false
- type: input
id: occurrence
attributes:
label: Occurrence Version
description: Specify in which version the issue occurred
placeholder: 'v0.1.TTMMJJ-hhmm'
validations:
required: true
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+29
View File
@@ -0,0 +1,29 @@
name: 💡 Enhancement
description: Suggest a new feature or improvement
title: '💡 [ENHANCEMENT]: '
body:
- type: textarea
id: description
attributes:
label: Description
description: Describe the new feature or improvement!
placeholder: 'What problem does it solve or what value does it add?'
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed Solution
placeholder: 'Describe how the feature should work.'
validations:
required: false
- type: input
id: implementation
attributes:
label: Implementation Version
description: Specify in which version the enhancement should be released.
placeholder: 'v0.1'
validations:
required: true
+39
View File
@@ -0,0 +1,39 @@
name: ⚙️ Improvement
description: Suggest an improvement to existing functionality, code quality, or performance
title: '⚙️ [Improvement]: '
body:
- type: textarea
id: context
attributes:
label: Current Situation / Context
description: Describe the current behavior or implementation that could be improved.
placeholder: 'Currently, the system loads all users at once, which slows down response time.'
validations:
required: false
- type: textarea
id: proposal
attributes:
label: Proposed Improvement
description: Describe your proposed change or improvement in detail.
placeholder: 'Implement pagination for user loading to reduce memory usage and response time.'
validations:
required: false
- type: textarea
id: benefits
attributes:
label: Expected Benefits
description: Explain how this improvement will help (e.g., better performance, cleaner code, easier maintenance).
placeholder: 'Faster load times, improved scalability, and reduced database load.'
validations:
required: false
- type: input
id: implementation
attributes:
label: Implementation Version
description: Specify in which version the improvement should be released.
placeholder: 'v0.1'
validations:
required: true
+27 -27
View File
@@ -1,39 +1,39 @@
name: Build Rollout
name: Release and Deploy Build
on:
workflow_dispatch:
workflow_dispatch:
permissions:
id-token: write
contents: read
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: use NodeJS v22.12.0
uses: actions/setup-node@v4
with:
node-version: '22.12.0'
registry-url: 'https://registry.npmjs.org'
always-auth: true
- name: use NodeJS v22.12.0
uses: actions/setup-node@v4
with:
node-version: '22.12.0'
registry-url: 'https://registry.npmjs.org'
always-auth: true
- name: Create .npmrc
run: echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create .npmrc
run: echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Install Dependencies
run: npm install
- name: Install Dependencies
run: npm install
- name: run Rollout Build
run: npm run build
- name: run Rollout Build
run: npm run build
- name: Publish Library
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish Library
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+16 -15
View File
@@ -1,23 +1,24 @@
name: Build Validation
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: use NodeJS v22.16.0
uses: actions/setup-node@v4
with:
node-version: '22.16.0'
- name: use NodeJS v22.16.0
uses: actions/setup-node@v4
with:
node-version: '22.16.0'
- name: Install Dependencies
run: npm install
- name: Install Dependencies
run: npm install
- name: run Rollout Build
run: npm run build
- name: run Rollout Build
run: npm run build
+2 -3
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env sh
npm run format || exit 1
npm run lint:fix || exit 1
tsc || exit 1
npm run format:check || exit 1
npm run lint || exit 1
+22 -9
View File
@@ -1,16 +1,29 @@
.idea
.git
.github
.git
.idea
.vscode
dist
node_modules
.husky
src
set-version.js
README.md
SECURITY.md
CODEOWNERS
.gitignore
.npmignore
.husky
node_modules
src
README.md
.prettierignore
.prettierrc
.gitattributes
tsconfig.json
tsconfig.eslint.json
tsup.config.ts
eslint.config.js
.prettierrc
.vscode
eslint.config.typeaware.cjs
tsconfig.eslint.json
package-lock.json
+5
View File
@@ -0,0 +1,5 @@
node_modules
dist
.git
.idea
.vscode
+11 -11
View File
@@ -1,13 +1,13 @@
{
"printWidth": 140,
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"arrowParens": "always",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteProps": "as-needed",
"endOfLine": "crlf"
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"arrowParens": "always",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteProps": "as-needed",
"endOfLine": "crlf"
}
+1
View File
@@ -0,0 +1 @@
FrauJulian
+77 -68
View File
@@ -4,10 +4,23 @@
![GitHub package.json version](https://img.shields.io/github/package-json/v/FrauJulian/discord-audio-stream)
![GitHub Repo stars](https://img.shields.io/github/stars/FrauJulian/discord-audio-stream?style=social)
**This module is designed to work with [discord.js/voice](https://www.npmjs.com/package/@discordjs/voice) v0.18. This
**This module is designed to work with [discord.js/voice](https://www.npmjs.com/package/@discordjs/voice) v0.19. This
package doesn't support older
versions!**
> **Designed for 24/7 audio playing on discord.**
> Discord has many unwanted rate limits, especially in the audio area. This package does all the work and ensures that
> your music never stops playing due to ffmpeg or Discord, with as little effort as possible.
> **Recommended best Practise:**
> Create a global Map<GuildId, AudioManager> (Map<KEY, OBJ>) list. When the feature is used on a guild, add a new
copilot-pull-request-reviewer[bot] commented 2025-12-16 15:15:53 +00:00 (Migrated from github.com)
Review

Corrected spelling of 'Practise' to 'Practice'. In this context (noun), 'practice' is the correct spelling.

> **Recommended best Practice:**  
Corrected spelling of 'Practise' to 'Practice'. In this context (noun), 'practice' is the correct spelling. ```suggestion > **Recommended best Practice:** ```
> instance of AudioManager to the Map. When starting the connection or audio, overwrite the respective configuration with
> the override methods provided. To finally start, use the respective method. For each call and each change, retrieve the
> AudioManager object from the list and perform your actions. When the feature get stopped, first execute StopConnection
> and then Dispose to save as much power as possible. Garbage collection does the rest.
> **For large systems:** For large systems, it is recommended to queue each start with the
> packet [p-queue](https://www.npmjs.com/package/p-queue), to give ffmpeg enough time..
## 👋 Support
Please create an [issue](https://github.com/FrauJulian/DiscordAudioStreamNPM/issues) on github or write [
@@ -15,105 +28,101 @@ Please create an [issue](https://github.com/FrauJulian/DiscordAudioStreamNPM/iss
## 📝 Usage
### Install
### Installation
> **Supported package managers:** npm, yarn, pnpm, bun
#### Recommended:
**Node.js 22.12.0 or newer is required.**
```bash
npm install discord-audio-stream @snazzah/davey @discordjs/opus
yarn add discord-audio-stream @snazzah/davey @discordjs/opus
pnpm add discord-audio-stream @snazzah/davey @discordjs/opus
bun add discord-audio-stream @snazzah/davey @discordjs/opus
npm install discord-audio-stream
yarn add discord-audio-stream
pnpm add discord-audio-stream
bun add discord-audio-stream
```
You need `@snazzah/davey` and one of the encryption libraries to run this package!
#### Required Dependencies
**Encryption Libraries (npm install):**
> You only need to install one of these libraries if your system does not support `aes-256-gcm` (verify by running
> `require('node:crypto').getCiphers().includes('aes-256-gcm')`).
- `sodium-native`
- `sodium`
- `@stablelib/xchacha20poly1305`
- `@noble/ciphers`
- `libsodium-wrappers`
**Opus Libraries (npm install):**
- `@discordjs/opus`
- > You only need to install `libsodium-wrappers` if your system does not support `aes-256-gcm` (verify by running
> `require('node:crypto').getCiphers().includes('aes-256-gcm')`).
- `@snazzah/davey`
- `opusscript`
- `prism-media`
- **FFmpeg** (one of those)
- [`FFmpeg`](https://ffmpeg.org/) (installed and added to environment)
- `ffmpeg-static`
**FFmpeg:**
### AudioManager Instance
- [`FFmpeg`](https://ffmpeg.org/) (installed and added to environment)
- `ffmpeg-static`
#### Options
### Create Instance
**Declaration:**
```js
let audioManager = new AudioManager();
```
AudioManager(ffmpegMode: string, renewInMs, number, connectionData: VoiceConnectionDataModel, audioData: VoiceAudioDataModel): IDisposable, IAudioManager
```
or (with parameters)
- `ffmpegMode`: 'Native' or 'Standalone'
- Native: [`FFmpeg`](https://ffmpeg.org/) installed on your system
- Standalone: FFmpeg-static as NodeJs library
- `renewInMs`: renewal time > default is 1,5h (5400000ms)
- `connectionData`: Options for voice connection. (type of **VoiceConnectionDataModel**)
- `audioData`: Options for audio player. (type of **VoiceAudioDataModel**)
#### Example
```js
let audioManager = new AudioManager(
{
VoiceChannelId: 0, //voice channel id where to play music
GuildId: 0, //guild id
VoiceAdapter: 0, //guild VoiceAdapter
},
{
ResourceType: '', //resource type like link or file
Resource: '', //auto play link or file name
},
'Native',
5400000,
{
VoiceChannelId: 0, //voice channel id where to play music
GuildId: 0, //guild id
VoiceAdapter: 0, //guild VoiceAdapter
},
{
ResourceType: '', //resource type like link or file
Resource: '', //auto play link or file name
},
);
```
### Properties of the AudioManager
### Fields and Methods of AudioManager
#### Properties
#### Fields
copilot-pull-request-reviewer[bot] commented 2025-12-16 15:15:53 +00:00 (Migrated from github.com)
Review

Syntax error in type declaration. There's a misplaced comma in 'renewInMs, number'. Should be 'renewInMs: number'.

Syntax error in type declaration. There's a misplaced comma in 'renewInMs, number'. Should be 'renewInMs: number'.
| Callable with | Type | Description |
| ----------------- | ---------------------------- | ----------------------------------------------- |
| `VoiceConnection` | **VoiceConnection** | VoiceConnection instance from discord.js/voice. |
| `AudioPlayer` | **AudioPlayer** | AudioPlayer instance from discord.js/voice. |
| `AudioResource` | **AudioResource** | AudioResource instance from discord.js/voice. |
| `ConnectionData` | **VoiceConnectionDataModel** | Global variable for connection data. |
| `AudioData` | **AudioDataModel** | Global variable for audio data. |
| Name | Type | Security | Description |
| --------- | -------- | --------- | ------------------------------------- |
| `Active` | **bool** | protected | To check if the connection is active. |
| `Playing` | **bool** | protected | To check if it is playing audio. |
#### Methods
| Callable with | Parameters | Return type | Description | |
| ------------------------------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------- | -------------------------------------- |
| `OverrideVoiceConnectionData` | `connectionData` (type of **VoiceConnectionDataModel**) | void | Method to override global connectionData variable. | |
| `OverrideVoiceAudioDataModel` | `audioData` (type of **VoiceAudioDataModel**) | void | Method to override global audioData variable. | |
| `OverrideRenewInMs` | `renewInMs` (type of int, default value is 5400000) | void | Method to override global renewInMs variable. | |
| `CreateConnection` | `isRenew` (type of boolean, default value is false) | void | Method to join the voice connection. | |
| `PlayAudioOnConnection` | | void | Method to play audio on the existing voice connection. | |
| `PauseAudio` | | void | Method to pause the audio. | |
| `ResumeAudio` | | void | Method to resume the audio. | |
| `StopAudioOnConnection` | | void | Method to stop the audio without destroying voice connection. | |
| `CreateConnectionAndPlayAudio` | | void | Method to join the voice connection and play audio. | |
| `DestroyConnection` | | void | Method to destroy the voice connection. | |
| `Dispose` | | void | Dispose all data in object. | |
| `SetVolume` | `volume` (type of number, 0 - 100 percent) | void | Method to set the audio volume. | Method to set the volume of the audio. |
| `SetMaxListeners` | `maxListeners` (type of number) | void | Method to set the max listeners of the audio stream. | |
| Name | Parameters | Return type | Description |
| ----------------------------- | ------------------------------------------------------- | ------------- | ---------------------------------------- |
| `OverrideVoiceConnectionData` | `connectionData` (type of **VoiceConnectionDataModel**) | void | To override intern connectionData field. |
| `OverrideVoiceAudioDataModel` | `audioData` (type of **VoiceAudioDataModel**) | void | To override intern audioData field. |
| `CreateAndPlay` | | Promise<void> | Join channel and start playing audio. |
| `CreateConnection` | | void | Let it connect to voice channel. |
| `PlayAudio` | | Promise<void> | To start playing audio. |
| `PauseAudio` | | void | Pause the audio, if it is playing |
| `ResumeAudio` | | void | Resume the audio, if it is paused. |
| `SetVolume` | `volume` (type of number, 0 - 100 percent) | void | To set the audio volume. |
| `StopConnection` | | Promise<void> | Method to disconnect the voice channel. |
| `Dispose` | | void | Dispose all data in object. |
##### Types History
## 📝 Types
- [**VoiceConnection** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/VoiceConnection.ts#L166)
- [**AudioPlayer** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/audio/AudioPlayer.ts#L155)
- [**AudioResource** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/audio/AudioResource.ts#L44)
- [**VoiceConnectionDataModel** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/main/src/Models/VoiceConnectionDataModel.d.ts#L3)
- [**VoiceAudioDataModel** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/main/src/Models/VoiceAudioDataModel.d.ts#L1)
- [**DiscordGatewayAdapterCreator** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/util/adapter.ts#L50)
- [**VoiceConnectionDataModel** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L3)
- [**VoiceAudioDataModel** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L21)
- [**IDisposable** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L38)
- [**IAudioManager** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L45)
## 📋 Contributors:
~ [**FrauJulian - Julian Lechner**](https://fraujulian.xyz/).
~ [**FrauJulian - Julian Lechner**](https://fraujulian.xyz/) - CODEOWNER
## 🤝 Enjoy the package?
-1316
View File
File diff suppressed because it is too large Load Diff
+72 -82
View File
@@ -1,101 +1,91 @@
/** @type {import('eslint').Linter.FlatConfig[]} */
const config = [
// Globale Ignores (ersetzt .eslintignore)
{
ignores: ['dist/', 'node_modules/'],
},
// Haupt-Setup für TS/JS-Dateien
{
files: ['**/*.{ts,tsx,js,cjs,mjs}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module', // du schreibst TS mit ES-Imports; Laufzeit ist CJS nach Transpile
parser: require('@typescript-eslint/parser'),
parserOptions: {
// schnelles Profil: kein Type-Checker, nur Syntax-Linting
ecmaFeatures: {},
},
{
ignores: ['dist/', 'node_modules/'],
},
plugins: {
'@typescript-eslint': require('@typescript-eslint/eslint-plugin'),
import: require('eslint-plugin-import'),
promise: require('eslint-plugin-promise'),
'unused-imports': require('eslint-plugin-unused-imports'),
n: require('eslint-plugin-n'),
},
{
files: ['**/*.{ts,tsx,js,cjs,mjs}'],
rules: {
/* Basis */
'no-debugger': 'warn',
/* TypeScript */
'@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }],
'@typescript-eslint/no-unused-vars': 'off', // schneller über unused-imports
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: require('@typescript-eslint/parser'),
parserOptions: {
ecmaFeatures: {},
},
},
],
'@typescript-eslint/no-explicit-any': 'off', // pragmatisch für großen Bot
/* Imports */
'import/first': 'error',
'import/no-duplicates': 'error',
'import/newline-after-import': 'warn',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'**/*.config.{js,cjs,mjs,ts}',
'**/scripts/**',
'**/*.test.{ts,js}',
'**/Testing.{ts,js}',
'**/.eslintrc.{js,cjs}',
],
plugins: {
'@typescript-eslint': require('@typescript-eslint/eslint-plugin'),
import: require('eslint-plugin-import'),
promise: require('eslint-plugin-promise'),
'unused-imports': require('eslint-plugin-unused-imports'),
n: require('eslint-plugin-n'),
},
],
/* Node (wir kompilieren TS → CJS; ES-Syntax erlauben) */
'n/no-missing-import': 'off', // TypeScript-Resolver übernimmt
'n/no-unsupported-features/es-syntax': 'off',
rules: {
/* Base */
'no-debugger': 'warn',
/* Promises (Discord-/DB-/HTTP-Logik) */
'promise/catch-or-return': 'warn',
'promise/always-return': 'off',
/* TypeScript */
'@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }],
'@typescript-eslint/no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{ vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
/* bewusste „Fire-and-forget“-Promises mit `void` */
'no-void': ['warn', { allowAsStatement: true }],
},
/* Imports */
'import/first': 'error',
'import/no-duplicates': 'error',
'import/newline-after-import': 'warn',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'**/*.config.{js,cjs,mjs,ts}',
'**/scripts/**',
'**/*.test.{ts,js}',
'**/Testing.{ts,js}',
'**/.eslintrc.{js,cjs}',
],
},
],
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
// nutzt automatisch deine tsconfig-Pfade
/* Node */
'n/no-missing-import': 'off',
'n/no-unsupported-features/es-syntax': 'off',
/* Promis */
'promise/catch-or-return': 'warn',
'promise/always-return': 'off',
/* „Fire-and-forget“-Promises `void` */
'no-void': ['warn', { allowAsStatement: true }],
},
node: true,
},
},
},
// Reine JS/CJS-Skripte (Build/Hooks) dürfen require/exporte nutzen
{
files: ['**/*.{js,cjs}'],
languageOptions: {
sourceType: 'script',
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
},
node: true,
},
},
},
rules: {
'n/global-require': 'off',
{
files: ['**/*.{js,cjs}'],
languageOptions: {
sourceType: 'script',
},
rules: {
'n/global-require': 'off',
},
},
},
];
module.exports = config;
+17 -17
View File
@@ -2,24 +2,24 @@
const base = require('./eslint.config.cjs');
const typeAwareLayer = {
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: require('@typescript-eslint/parser'),
parserOptions: {
project: ['./tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: require('@typescript-eslint/parser'),
parserOptions: {
project: ['./tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
},
},
plugins: {
'@typescript-eslint': require('@typescript-eslint/eslint-plugin'),
},
rules: {
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-misused-promises': ['warn', { checksVoidReturn: false }],
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
},
},
plugins: {
'@typescript-eslint': require('@typescript-eslint/eslint-plugin'),
},
rules: {
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-misused-promises': ['warn', { checksVoidReturn: false }],
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
},
};
module.exports = [...base, typeAwareLayer];
+10294 -9853
View File
File diff suppressed because it is too large Load Diff
+94 -88
View File
@@ -1,91 +1,97 @@
{
"name": "discord-audio-stream",
"version": "0.6.20",
"license": "LGPL-2.1-only",
"description": "NodeJS library to make stream any audio on discord easier.",
"exports": {
".": {
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
}
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"directories": {
"lib": "src"
},
"scripts": {
"lint": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs --fix",
"lint:types": "eslint . --config eslint.config.typeaware.cjs --ext .ts,.tsx",
"format": "prettier --write .",
"format:check": "prettier --check .",
"update:dependencies": "npx npm-check-updates --upgrade && pnpm install && bun install && npm install && yarn install && echo PLEASE delete your node_modules directory and install again with your package manager.",
"update:version": "npm version patch --no-git-tag-version",
"clean:dist": "del-cli dist",
"build": "npm run clean:dist && npm run update:version && tsup",
"prepare": "husky"
},
"repository": "https://github.com/FrauJulian/Discord-Audio-Stream",
"bugs": "https://github.com/FrauJulian/Discord-Audio-Stream/issues",
"funding": "https://ko-fi.com/FrauJulian",
"keywords": [
"discordjs",
"discord.js",
"discordjs/voice",
"audio",
"voice"
],
"contributors": [
"FrauJulian - Lechner Julian <fraujulian@lechner.top>"
],
"lint-staged": {
"*.{ts,tsx,js,cjs,mjs}": [
"eslint --fix",
"prettier --write"
"name": "discord-audio-stream",
"version": "0.7.DDDhhmm",
"license": "LGPL-2.1-only",
"description": "Discord library to make stream any audio easier.",
"exports": {
".": {
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
}
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
},
"dependencies": {
"@discordjs/voice": "^0.19.0",
"@typescript-eslint/eslint-plugin": "^8.46.0",
"@typescript-eslint/parser": "^8.46.0"
},
"devDependencies": {
"@types/ejs": "^3.1.5",
"@types/node": "^24.7.1",
"del-cli": "^7.0.0",
"eslint": "^9.37.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-n": "^17.23.1",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-unused-imports": "^4.2.0",
"husky": "^9.1.7",
"npm-check-updates": "^19.0.0",
"prettier": "^3.6.2",
"ts-jest": "^29.4.5",
"tsup": "^8.5.0",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=18"
},
"private": false,
"publishConfig": {
"access": "public"
}
"directories": {
"lib": "src"
},
"scripts": {
"update:dependencies": "npx npm-check-updates --upgrade",
"update:version": "node set-version.js",
"lint": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs --fix",
"lint:types": "eslint . --config eslint.config.typeaware.cjs --ext .ts,.tsx",
"format": "prettier --write .",
"format:check": "prettier --check .",
"build": "del-cli dist && npm run update:version && tsup",
"prepare": "husky"
},
"repository": "https://github.com/FrauJulian/Discord-Audio-Stream",
"bugs": "https://github.com/FrauJulian/Discord-Audio-Stream/issues",
"funding": "https://ko-fi.com/FrauJulian",
"keywords": [
"discordjs",
"discordjs/voice",
"music",
"voice",
"audio",
"24/7"
],
"contributors": [
"FrauJulian - Lechner Julian <fraujulian@lechner.top>"
],
"lint-staged": {
"*.{ts,tsx,js,cjs,mjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
copilot-pull-request-reviewer[bot] commented 2025-12-16 15:15:53 +00:00 (Migrated from github.com)
Review

Invalid version format. Package versions must follow semantic versioning (semver) format. The placeholder "DDDhhmm" will cause npm publish to fail. This should be replaced by the set-version.js script before publishing.

    "version": "0.7.0",
Invalid version format. Package versions must follow semantic versioning (semver) format. The placeholder "DDDhhmm" will cause npm publish to fail. This should be replaced by the set-version.js script before publishing. ```suggestion "version": "0.7.0", ```
"prettier --write"
]
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^8.50.0",
"@typescript-eslint/parser": "^8.50.0"
},
"devDependencies": {
"@types/ejs": "^3.1.5",
"@types/node": "^25.0.2",
"del-cli": "^7.0.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-n": "^17.23.1",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-unused-imports": "^4.3.0",
"husky": "^9.1.7",
"npm-check-updates": "^19.2.0",
"prettier": "^3.7.4",
"ts-jest": "^29.4.6",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"peerDependencies": {
"@discordjs/voice": "^0.19.0",
"@snazzah/davey": "^0.1.8",
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.0.8",
"prism-media": "^1.3.5"
},
"engines": {
"node": ">=22.12.0"
},
"private": false,
"publishConfig": {
"access": "public"
}
}
-5965
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -1,3 +0,0 @@
ignoredBuiltDependencies:
- esbuild
- unrs-resolver
+21
View File
@@ -0,0 +1,21 @@
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8').toString());
const [major, minor] = pkg.version.split('.');
const prefix = `${major}.${minor}.`;
const now = new Date();
const start = new Date(now.getFullYear(), 0, 0);
const diff = now - start;
const dayOfYear = Math.floor(diff / (1000 * 60 * 60 * 24));
const hh = String(now.getHours()).padStart(2, '0');
const mm = String(now.getMinutes()).padStart(2, '0');
const newVersion = `${prefix}${String(dayOfYear).padStart(3, '0')}${hh}${mm}`;
pkg.version = newVersion;
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
console.log('Version set to: ', newVersion);
+179 -141
View File
@@ -1,159 +1,197 @@
import type { AudioPlayer, AudioResource, VoiceConnection } from '@discordjs/voice';
import { createAudioPlayer, createAudioResource, joinVoiceChannel } from '@discordjs/voice';
import {
createAudioPlayer,
createAudioResource,
entersState,
joinVoiceChannel,
NoSubscriberBehavior,
StreamType,
VoiceConnectionStatus,
} from '@discordjs/voice';
import { join } from 'node:path';
import type { VoiceAudioDataModel, VoiceConnectionDataModel } from './types';
import type { IAudioManager, IDisposable, VoiceAudioDataModel, VoiceConnectionDataModel } from './types';
import { spawn } from 'node:child_process';
import type { Readable } from 'node:stream';
export default class AudioManager {
public VoiceConnection?: VoiceConnection;
public AudioPlayer?: AudioPlayer;
public AudioResource?: AudioResource;
export default class AudioManager implements IAudioManager, IDisposable {
private voiceConnection?: VoiceConnection | null;
private audioPlayer?: AudioPlayer | null;
private audioResource?: AudioResource | null;
protected IsActive?: boolean;
protected IsAudioPlaying?: boolean;
protected Active?: boolean | null;
protected Playing?: boolean | null;
protected ConnectionData?: VoiceConnectionDataModel;
protected AudioData?: VoiceAudioDataModel;
private timeout?: NodeJS.Timeout | null;
private ffmpegProcess?: ReturnType<typeof spawn> | null;
private pcmStream?: Readable | null;
private TimeoutHandle?: NodeJS.Timeout;
private RenewInMs?: number;
constructor(connectionData?: VoiceConnectionDataModel, audioData?: VoiceAudioDataModel, renewInMs = 5400000) {
this.RenewInMs = renewInMs;
this.ConnectionData = connectionData;
this.AudioData = audioData;
}
public OverrideRenewInMs(renewInMs: number = 5400000): void {
this.RenewInMs = renewInMs;
}
public OverrideVoiceConnectionData(connectionData: VoiceConnectionDataModel): void {
this.ConnectionData = connectionData;
}
public OverrideVoiceAudioDataModel(audioData: VoiceAudioDataModel): void {
this.AudioData = audioData;
}
public CreateConnection(isRenew: boolean = false): void {
this.CheckIfNull(this.ConnectionData);
if (!isRenew) {
if (this.IsActive) {
this.DestroyConnection(false);
} else {
this.IsActive = true;
}
constructor(
private readonly ffmpegMode: 'Native' | 'Standalone',
private renewMs: number | null = null,
private connectionData: VoiceConnectionDataModel | null = null,
private audioData: VoiceAudioDataModel | null = null,
) {
this.renewMs = renewMs ? renewMs : 5400000;
}
this.VoiceConnection = joinVoiceChannel({
channelId: this.ConnectionData!.VoiceChannelId.toString(),
guildId: this.ConnectionData!.GuildId.toString(),
adapterCreator: this.ConnectionData!.VoiceAdapter,
});
public OverrideVoiceConnectionData(connectionData: VoiceConnectionDataModel): void {
this.connectionData = connectionData;
}
this.TimeoutHandle = setTimeout((): void => {
this.DestroyConnection(false);
if (this.IsActive) {
this.CreateConnection(true);
public OverrideVoiceAudioDataModel(audioData: VoiceAudioDataModel): void {
copilot-pull-request-reviewer[bot] commented 2025-12-16 15:15:52 +00:00 (Migrated from github.com)
Review

Use nullish coalescing operator (??) instead of ternary for more precise null/undefined handling. The current code treats 0 as falsy when it could be a valid value. Change to this.renewMs = renewMs ?? 5400000;

        this.renewMs = renewMs ?? 5400000;
Use nullish coalescing operator (??) instead of ternary for more precise null/undefined handling. The current code treats 0 as falsy when it could be a valid value. Change to `this.renewMs = renewMs ?? 5400000;` ```suggestion this.renewMs = renewMs ?? 5400000; ```
this.audioData = audioData;
}
if (this.IsAudioPlaying) {
this.PlayAudioOnConnection();
public CreateConnection(): void {
this.voiceConnection = joinVoiceChannel({
channelId: this.connectionData!.VoiceChannelId,
guildId: this.connectionData!.GuildId,
adapterCreator: this.connectionData!.VoiceAdapter,
});
this.timeout = setTimeout(async (): Promise<void> => {
await this.StopConnection();
await this.CreateAndPlay();
}, this.renewMs!);
}
public async PlayAudio(): Promise<void> {
if (!this.audioData || !this.voiceConnection) return;
await entersState(this.voiceConnection!, VoiceConnectionStatus.Ready, 20_000);
if (this.ffmpegProcess) {
this.ffmpegProcess.kill('SIGKILL');
this.ffmpegProcess = null;
}
}
}, this.RenewInMs);
}
public PlayAudioOnConnection(): void {
this.CheckIfNull(this.AudioData);
let source: string;
if (this.AudioData!.ResourceType === 'File') {
this.AudioResource = createAudioResource(join(__dirname, this.AudioData!.Resource), { inlineVolume: true });
} else if (this.AudioData!.ResourceType === 'Link') {
this.AudioResource = createAudioResource(this.AudioData!.Resource, { inlineVolume: true });
} else {
throw new Error('Invalid resource type.');
if (this.audioData!.ResourceType === 'File') {
source = join(__dirname, this.audioData!.Resource);
} else if (this.audioData!.ResourceType === 'Link') {
source = this.audioData!.Resource;
} else {
throw new TypeError('Invalid resource type.');
}
this.ffmpegProcess = spawn(
this.ffmpegMode === 'Native' ? 'ffmpeg' : require('ffmpeg-static'),
[
'-loglevel',
'error',
'-i',
source,
'-analyzeduration',
'0',
'-f',
's16le',
'-ar',
'48000',
'-ac',
'2',
'pipe:1',
],
{
stdio: ['ignore', 'pipe', 'pipe'],
},
);
this.pcmStream = this.ffmpegProcess!.stdout as Readable;
this.audioResource = createAudioResource(this.pcmStream, {
inputType: StreamType.Raw,
inlineVolume: true,
});
this.audioPlayer = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Play,
},
});
this.voiceConnection!.subscribe(this.audioPlayer);
this.audioPlayer.play(this.audioResource);
this.Playing = true;
}
this.AudioPlayer = createAudioPlayer();
this.AudioPlayer.play(this.AudioResource);
this.VoiceConnection!.subscribe(this.AudioPlayer!);
this.IsAudioPlaying = true;
}
public StopAudioOnConnection(): void {
if (this.IsAudioPlaying) {
this.DestroyConnection(false);
this.CreateConnection();
} else {
throw new Error('Audio is not playing.');
}
}
public PauseAudio(): void {
if (this.IsAudioPlaying) {
this.AudioPlayer!.pause();
this.IsAudioPlaying = false;
} else {
throw new TypeError('Audio is not playing.');
}
}
public ResumeAudio(): void {
if (!this.IsAudioPlaying) {
this.AudioPlayer!.unpause();
this.IsAudioPlaying = true;
} else {
throw new TypeError('Audio is playing.');
}
}
public CreateConnectionAndPlayAudio(): void {
this.CreateConnection();
this.PlayAudioOnConnection();
}
public DestroyConnection(resetValidationParameters: boolean = true): void {
if (this.CheckIfNull(this.VoiceConnection)) return;
this.VoiceConnection!.destroy();
if (resetValidationParameters) {
this.IsActive = false;
this.IsAudioPlaying = false;
}
}
public SetMaxListeners(maxListeners: number): void {
if (this.CheckIfNull(this.VoiceConnection)) return;
this.VoiceConnection!.setMaxListeners(maxListeners);
}
public SetVolume(volumeInPercent: number): void {
if (volumeInPercent < 0 || volumeInPercent > 100) throw new Error('Volume must be between 0 and 100.');
if (this.CheckIfNull(this.VoiceConnection)) return;
this.AudioResource!.volume!.setVolume(volumeInPercent / 100);
}
public Dispose(): void {
this.VoiceConnection = undefined;
this.AudioPlayer = undefined;
this.AudioResource = undefined;
this.IsActive = undefined;
this.IsAudioPlaying = undefined;
this.ConnectionData = undefined;
this.AudioData = undefined;
this.TimeoutHandle = undefined;
this.RenewInMs = undefined;
}
private CheckIfNull<T>(value: T | null): boolean {
if (value === null) {
throw new Error(`${value} cannot be null in this case.`);
public PauseAudio(): void {
if (this.Playing) {
this.audioPlayer!.pause();
this.Playing = false;
} else {
throw new ReferenceError('Audio is not playing.');
}
}
return true;
}
public ResumeAudio(): void {
if (!this.Playing) {
this.audioPlayer!.unpause();
this.Playing = true;
} else {
throw new ReferenceError('Audio is playing.');
}
}
public async CreateAndPlay(): Promise<void> {
this.CreateConnection();
await this.PlayAudio();
}
public async StopConnection(): Promise<void> {
this.voiceConnection?.disconnect();
this.voiceConnection?.destroy();
this.voiceConnection = null;
this.Active = false;
this.Playing = false;
}
public SetVolume(volumeInPercent: number): void {
if (volumeInPercent < 0 || volumeInPercent > 100) throw new Error('Volume must be between 0 and 100.');
this.audioResource!.volume!.setVolume(volumeInPercent / 100);
}
public Dispose(): void {
try {
if (this.timeout) {
clearTimeout(this.timeout);
}
if (this.audioPlayer) {
this.audioPlayer.stop(true);
}
if (this.audioPlayer) {
this.audioPlayer!.stop();
if (this.audioResource && this.audioResource.playStream) {
this.audioResource!.playStream.destroy();
}
}
if (this.voiceConnection) {
this.voiceConnection!.destroy();
}
if (this.ffmpegProcess) {
this.ffmpegProcess.kill('SIGKILL');
this.ffmpegProcess = null;
}
this.pcmStream?.destroy();
this.pcmStream = null;
this.audioPlayer = null;
this.audioResource = null;
this.voiceConnection = null;
this.timeout = null;
this.Active = null;
this.Playing = null;
this.connectionData = null;
this.audioData = null;
this.renewMs = null;
} catch {}
}
}
+45 -22
View File
@@ -1,32 +1,55 @@
import type { DiscordGatewayAdapterCreator } from '@discordjs/voice';
export type VoiceConnectionDataModel = {
/**
* The ID of the voice channel to connect to.
*/
VoiceChannelId: string;
/**
* The ID of the voice channel to connect to.
*/
VoiceChannelId: string;
/**
* The Id of the guild (server) to connect to.
*/
GuildId: string;
/**
* The Id of the guild (server) to connect to.
*/
GuildId: string;
/**
* The adapter creator for the voice connection.
* Can be archived by the guild instance via the voiceAdapterCreator property.
*/
VoiceAdapter: DiscordGatewayAdapterCreator;
/**
* The adapter creator for the voice connection.
* Can be archived by the guild instance via the voiceAdapterCreator property.
*/
VoiceAdapter: DiscordGatewayAdapterCreator;
};
export type VoiceAudioDataModel = {
/**
* The unique identifier for the audio resource.
*/
ResourceType: 'Link' | 'File';
/**
* The unique identifier for the audio resource.
*/
ResourceType: 'Link' | 'File';
/**
* The URL or file path of the audio resource.
* any is to be assumed to require(filepath)
*/
Resource: string | any;
/**
* The URL or file path of the audio resource.
* any is to be assumed to require(filepath)
*/
Resource: string | any;
};
/**
* An interface which is implemented in audio manager
* for a proper Dispose functionality.
*/
export type IDisposable = {
Dispose(): void;
};
/**
* An interface to handle instances later on.
*/
export type IAudioManager = {
OverrideVoiceConnectionData(connectionData: VoiceConnectionDataModel): void;
OverrideVoiceAudioDataModel(audioData: VoiceAudioDataModel): void;
CreateAndPlay(): Promise<void>;
CreateConnection(): void;
PlayAudio(): Promise<void>;
PauseAudio(): void;
ResumeAudio(): void;
StopConnection(): Promise<void>;
SetVolume(volumeInPercent: number): void;
};
+35 -18
View File
@@ -1,20 +1,37 @@
{
"compilerOptions": {
"strict": true,
"allowJs": false,
"noImplicitAny": true,
"esModuleInterop": true,
"strictNullChecks": true,
"target": "ES2022",
"moduleResolution": "Node",
"module": "CommonJS",
"declaration": true,
"isolatedModules": true,
"noEmit": true,
"outDir": "dist",
"types": ["node"],
"typeRoots": ["node_modules/@types"]
},
"include": ["src"],
"exclude": ["node_modules"]
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"types": [],
"rootDir": "./",
"outDir": "./dist",
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"skipLibCheck": true,
"noEmitOnError": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
-4480
View File
File diff suppressed because it is too large Load Diff