Merge pull request #8 from FrauJulian/dev

Push new Version 0.5.0 to main
This commit is contained in:
Julian Lechner
2025-04-06 16:45:43 +02:00
committed by GitHub
19 changed files with 1466 additions and 496 deletions
+1
View File
@@ -1,3 +1,4 @@
.idea
.git
node_modules
dist
+3
View File
@@ -1,4 +1,7 @@
.idea
.git
.gitignore
.npmignore
node_modules
src
tsconfig.json
+28
View File
@@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2025, Julian Lechner
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-58
View File
@@ -1,58 +0,0 @@
# License Agreement
**Copyright © 2024 Julian Lechner. All rights reserved.**
## 1. Grant of License
Subject to the terms and conditions of this Agreement, the Licensor hereby grants the Licensee a limited, non-exclusive, non-transferable, revocable license to access, view, and test the Software solely for personal, non-commercial purposes. **No other rights are granted.** The following actions are **strictly prohibited**:
- **Copying, reproducing, or replicating** the Software, in whole or in part, except for personal viewing.
- **Modifying, merging, adapting, translating, or creating derivative works** based on the Software.
- **Distributing, redistributing, transmitting, sublicensing, leasing, renting, or publishing** the Software in any form.
- **Selling, reselling, or otherwise exploiting** the Software for any commercial purpose.
## 2. Termination
This License Agreement is effective until terminated. This license will **automatically terminate without notice** if you fail to comply with any provision of this Agreement. Upon termination, you must immediately cease all use of the Software and destroy all copies, full or partial, in your possession or control.
## 3. Legal Compliance and Enforcement
Unauthorized use of the Software constitutes a violation of intellectual property laws and may result in **severe civil and criminal penalties**. The Licensor reserves the right to seek all remedies available at law and in equity for violations of this Agreement.
## 4. Disclaimer of Warranties
The Software is provided **"AS IS"**, without warranty of any kind, express or implied, including but not limited to the warranties of **merchantability**, **fitness for a particular purpose**, and **non-infringement**. The Licensor does not warrant that the Software will meet your requirements or that its operation will be uninterrupted or error-free.
## 5. Limitation of Liability
In no event shall the Licensor be liable for any **direct, indirect, incidental, special, consequential, or punitive damages**, including but not limited to loss of profits, data, or use, incurred by you or any third party, whether in an action in contract or tort, arising from your access to, or use of, the Software.
## 6. Indemnification
You agree to **indemnify, defend, and hold harmless** the Licensor from and against any and all claims, liabilities, damages, losses, or expenses (including legal fees and costs) arising out of or in any way connected with your violation of this Agreement or use of the Software.
## 7. Governing Law and Jurisdiction
This Agreement shall be governed by and construed in accordance with the laws of **Austria**, without regard to its conflict of law principles. Any legal action or proceeding arising under this Agreement will be brought exclusively in the courts located in **Austria**, and you hereby irrevocably consent to personal jurisdiction and venue therein.
## 8. Entire Agreement
This Agreement constitutes the **entire understanding** between you and the Licensor regarding the Software and supersedes all prior agreements, whether written or oral.
---
For any inquiries regarding permissions, please contact:
**Julian Lechner**
Ulmenstraße 9
3380 Pöchlarn
Email: [fraujulian@lechner.top](mailto:fraujulian@lechner.top)
Phone: +43 660 9254001
Please report any abuse to the following email address:
[abuse@fraujulian.xyz](mailto:abuse@fraujulian.xyz)
---
**By accessing or using this Software, you acknowledge that you have read, understood, and agree to be bound by the terms and conditions of this License Agreement.**
+52 -31
View File
@@ -4,61 +4,82 @@
![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)
<p>This NPM package was created to make it easier to stream audio to Discord.</p>
<p>There is a security feature of discord that stops the stream of audio after some time. This problem can be solved by disconnecting the Discord channel every 1.5 hours for 2 seconds and then reconnecting the channel. (If you use this package!) I recommend using a database to store the data e.g. MySQL, SQLite, json file and other database types.</p>
<p>The package does not fix this error yet. However, it is currently being worked on!</p>
**This module is designed to work with [discord.js](https://discord.js.org/) v14. This package doesn't support older
**This module is designed to work with [discord.js/voice](https://www.npmjs.com/package/@discordjs/voice) v0.18. This
package doesn't support older
versions!**
## 👋 Support
Please create an [issue](https://github.com/FrauJulian/DiscordAudioStreamNPM/issues) on github or write [`fraujulian`](https://discord.com/users/860206216893693973) on discord!
Please create an [issue](https://github.com/FrauJulian/DiscordAudioStreamNPM/issues) on github or write [
`fraujulian`](https://discord.com/users/860206216893693973) on discord!
## 📝 Usage
### Install package
```bash
npm i discord-audio-stream
npm install discord-audio-stream opusscript @discordjs/voice ffmpeg-static sodium-native
```
### Code Snippet - Start
### Create Instance
```js
const AudioStream = require("discord-audio-stream");
AudioStream.startAudio({
VoiceChannelID: 0, //Integer | voice channel id | e.g. interaction.member.voice.channel.id
GuildID: 0, //Integer | guild id | e.g. interaction.guild.id
VoiceAdapter: 0, //Integer | aoice adapter creator | e.g. interaction.guild.voiceAdapterCreator
Type: "", //String | choose the Audio Resource | File or Link
Resource: "", //String | audio stream link or file location | e.g. https://synradiode.stream.laut.fm/synradiode
})
let audioManager = new AudioManager();
```
### Code Snippet - Stop
or (with parameters)
```js
const AudioStream = require("discord-audio-stream");
AudioStream.stopAudio({
GuildID: 0, //Integer | guild id | e.g. interaction.guild.id
})
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
});
```
### Code Snippet - Set Max Listeners
### Properties of the AudioManager
```js
const AudioStream = require("discord-audio-stream");
#### Properties
AudioStream.setMaxAudioListeners({
GuildID: 0, //Integer | guild id | e.g. interaction.guild.id
MaxListeners: 0, //Integer | max Listeners | e.g. 30
})
```
| 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. |
| `IsActive` | boolean | Variable to check if audio stream is active. |
| `IsAudioPlaying` | boolean | Variable to check if audio is playing. |
| `ConnectionData` | **VoiceConnectionDataModel** | Global variable for connection data. |
| `AudioData` | **AudioDataModel** | Global variable for audio data. |
#### Methods
| Callable with | Parameters | Return type | Description |
|--------------------------------|--------------------------------------------------------------------------------------------------------|-------------|--------------------------------------------------------------------|
| `OverrideOptions` | `connectionData` (type of **VoiceConnectionDataModel**), `audioData` (type of **VoiceAudioDataModel**) | void | Method to override global variables, connectionData and audioData. |
| `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. |
| `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. |
##### Types History
- [**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**](https://github.com/FrauJulian/Discord-Audio-Stream/blob/main/src/Models/VoiceConnectionDataModel.d.ts#L3) (custom type)
- [**VoiceAudioDataModel**](https://github.com/FrauJulian/Discord-Audio-Stream/blob/main/src/Models/VoiceAudioDataModel.d.ts#L1) (custom type)
## 📋 Credits:
~ [**FrauJulian**](https://fraujulian.xyz/).
## 🤝 Enjoy the package?
+1165 -311
View File
File diff suppressed because it is too large Load Diff
+19 -10
View File
@@ -1,7 +1,7 @@
{
"name": "discord-audio-stream",
"version": "0.4.1",
"description": "Node libary to make discord audio streaming easier.",
"version": "0.5.0",
"description": "Node library to make discord audio streaming easier.",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@@ -18,23 +18,32 @@
"audio",
"music"
],
"contributors": [
{
"name": "Julian Lechner aka. FrauJulian",
"github": "https://github.com/FrauJulian",
"web": "https://fraujulian.xyz/"
}
],
"author": "FrauJulian | Julian Lechner",
"license": "CUSTOM-LICENSE",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/FrauJulian/DiscordAudioStreamNPM/issues"
},
"homepage": "https://github.com/FrauJulian/DiscordAudioStreamNPM#readme",
"devDependencies": {
"@types/ejs": "^3.1.5",
"@types/node": "^20.12.8",
"tsup": "^8.0.2",
"typescript": "^5.4.5"
"@types/node": "^22.13.16",
"tsup": "^8.4.0",
"typescript": "^5.8.2"
},
"dependencies": {
"@discordjs/voice": "^0.16.1",
"@discordjs/opus": "^0.10.0",
"@discordjs/voice": "^0.18.0",
"@noble/ciphers": "^1.2.1",
"ffmpeg-static": "^5.2.0",
"fs": "^0.0.1-security",
"libsodium-wrappers": "^0.7.13",
"opusscript": "^0.0.8"
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.1.1",
"sodium-native": "^5.0.0"
}
}
-11
View File
@@ -1,11 +0,0 @@
const {getVoiceConnection} = require("@discordjs/voice");
import {maxListenerMethodOptions} from "../Utils/Options/MaxListenerMethodOptions";
export function setMaxAudioListeners({
GuildID,
MaxListeners
}: maxListenerMethodOptions) {
let audioConnection = getVoiceConnection(GuildID);
audioConnection.setMaxListeners(MaxListeners);
}
-47
View File
@@ -1,47 +0,0 @@
const {joinVoiceChannel, createAudioPlayer, createAudioResource} = require("@discordjs/voice");
const {join} = require("node:path");
import {startMethodOptions} from "../Utils/Options/StartMethodOptions";
export function startAudio({
VoiceChannelID,
GuildID,
VoiceAdapter,
ResourceType,
Resource
}: startMethodOptions) {
let audioPlayer = createAudioPlayer();
function streamFile(file: string) {
let audioResource = createAudioResource(join(__dirname, file), {inlineVolume: true});
audioPlayer.play(audioResource);
joinVoiceChannel({
channelId: VoiceChannelID,
guildId: GuildID,
adapterCreator: VoiceAdapter
}).subscribe(audioPlayer);
}
function streamLink(link: string) {
let audioResource = createAudioResource(link);
audioPlayer.play(audioResource);
joinVoiceChannel({
channelId: VoiceChannelID,
guildId: GuildID,
adapterCreator: VoiceAdapter
}).subscribe(audioPlayer)
}
switch (ResourceType) {
case "Link":
streamLink(Resource);
break;
case "File":
streamFile(Resource);
break;
default:
throw new TypeError("Invalid audio type");
}
}
-10
View File
@@ -1,10 +0,0 @@
const {getVoiceConnection} = require("@discordjs/voice");
import {stopMethodOptions} from "../Utils/Options/StopMethodOptions";
export function stopAudio({
GuildID
}: stopMethodOptions) {
let audioConnection = getVoiceConnection(GuildID);
audioConnection.destroy();
}
+12
View File
@@ -0,0 +1,12 @@
export interface VoiceAudioDataModel {
/**
* 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;
}
+19
View File
@@ -0,0 +1,19 @@
import {DiscordGatewayAdapterCreator} from "@discordjs/voice";
export interface VoiceConnectionDataModel {
/**
* The ID of the voice channel to connect to.
*/
VoiceChannelId: number;
/**
* The Id of the guild (server) to connect to.
*/
GuildId: number;
/**
* The adapter creator for the voice connection.
* Can be archived by the guild instance via the voiceAdapterCreator property.
*/
VoiceAdapter: DiscordGatewayAdapterCreator;
}
+138
View File
@@ -0,0 +1,138 @@
import {
AudioPlayer,
AudioResource,
createAudioPlayer,
createAudioResource,
joinVoiceChannel,
VoiceConnection
} from "@discordjs/voice";
import {IAudioManager} from "./IAudioManager";
import {VoiceConnectionDataModel} from "../../Models/VoiceConnectionDataModel";
import {VoiceAudioDataModel} from "../../Models/VoiceAudioDataModel";
import {join} from "node:path";
export class AudioManager implements IAudioManager {
public VoiceConnection: VoiceConnection | null = null;
public AudioPlayer: AudioPlayer | null = null;
public AudioResource: AudioResource | null = null;
public IsActive: boolean = false;
public IsAudioPlaying: boolean = false;
public ConnectionData: VoiceConnectionDataModel | null;
public AudioData: VoiceAudioDataModel | null;
constructor(connectionData: VoiceConnectionDataModel | null = null, audioData: VoiceAudioDataModel | null = null) {
this.ConnectionData = connectionData;
this.AudioData = audioData;
}
OverrideOptions(connectionData: VoiceConnectionDataModel | null = null, audioData: VoiceAudioDataModel | null = null): void {
this.ConnectionData = connectionData;
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;
}
}
this.VoiceConnection = joinVoiceChannel({
channelId: this.ConnectionData!.VoiceChannelId.toString(),
guildId: this.ConnectionData!.GuildId.toString(),
adapterCreator: this.ConnectionData!.VoiceAdapter
});
setTimeout((): void => {
this.RenewConnectionAndAudio();
}, 5400000);
}
public PlayAudioOnConnection(): void {
this.CheckIfNull(this.AudioData);
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.");
}
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 {
this.CheckIfNull(this.AudioPlayer);
this.AudioPlayer!.pause();
}
public ResumeAudio(): void {
this.CheckIfNull(this.AudioPlayer);
this.AudioPlayer!.unpause();
}
public CreateConnectionAndPlayAudio(): void {
this.CreateConnection();
this.PlayAudioOnConnection();
}
public DestroyConnection(resetValidationParameters: boolean = true): void {
this.CheckIfNull(this.VoiceConnection);
this.VoiceConnection!.destroy();
if (resetValidationParameters) {
this.IsActive = false;
this.IsAudioPlaying = false;
}
}
public SetMaxListeners(maxListeners: number): void {
this.CheckIfNull(this.VoiceConnection);
this.VoiceConnection!.setMaxListeners(maxListeners);
}
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);
}
private RenewConnectionAndAudio(): void {
this.DestroyConnection(false);
if (this.IsActive) {
this.CreateConnection(true);
if (this.IsAudioPlaying) {
this.PlayAudioOnConnection();
}
}
}
private CheckIfNull<T>(value: T | null): boolean {
if (value === null) {
throw new Error(`${value} cannot be null in this case.`);
}
return true;
}
}
@@ -0,0 +1,25 @@
import {VoiceConnection} from "@discordjs/voice";
import {VoiceAudioDataModel} from "../../Models/VoiceAudioDataModel";
import {VoiceConnectionDataModel} from "../../Models/VoiceConnectionDataModel";
export interface IAudioManager {
OverrideOptions(connectionData?: VoiceConnectionDataModel | null, audioData?: VoiceAudioDataModel | null): void;
CreateConnection(isRenew: boolean): void;
PlayAudioOnConnection(): void;
PauseAudio(): void;
ResumeAudio(): void;
StopAudioOnConnection(): void;
CreateConnectionAndPlayAudio(): void;
DestroyConnection(resetValidationParameters: boolean): void;
SetVolume(volumeInPercent: number): void;
SetMaxListeners(maxListeners: number): void;
}
@@ -1,4 +0,0 @@
export type maxListenerMethodOptions = {
GuildID: number;
MaxListeners: number;
};
-7
View File
@@ -1,7 +0,0 @@
export type startMethodOptions = {
VoiceChannelID: number;
GuildID: number;
VoiceAdapter: number;
ResourceType: "Link" | "File";
Resource: string;
};
-3
View File
@@ -1,3 +0,0 @@
export type stopMethodOptions = {
GuildID: number;
};
+3 -3
View File
@@ -1,3 +1,3 @@
export * from "./Methods/StartMethod";
export * from "./Methods/StopMethod";
export * from "./Methods/MaxListenerMethod";
export {AudioManager} from "./Services/AudioManager/AudioManager";
export type {VoiceConnectionDataModel} from "./Models/VoiceConnectionDataModel";
export type {VoiceAudioDataModel} from "./Models/VoiceAudioDataModel";
+1 -1
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"strict": true,
"allowJs": true,
"allowJs": false,
"noImplicitAny": true,
"esModuleInterop": true,
"strictNullChecks": true,