Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
feat: missing methods and receiver
Browse files Browse the repository at this point in the history
  • Loading branch information
twlite committed Feb 19, 2022
1 parent 4c566a9 commit 37e53b7
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 94 deletions.
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DartJS

DartJS is a Discord.js framework that aims to provide similar voice interface of Discord.js v12.
DartJS is a Discord.js voice framework that aims to provide similar voice interface of Discord.js v12.

# Installation

Expand All @@ -10,16 +10,19 @@ $ npm i --save dartjs

> You may need to install encryption library and opus engine as well.
# Why?
# Note

This library was created just for learning purpose. There is no point of using this library unless you really have to :D
* Use `<Dispatcher>.once` instead of `<Dispatcher>.on`

> This library was created just for learning purpose and a personal music bot. You don't have to use this
unless you really want to.

# Example

```js
const Discord = require("discord.js");
const client = new Discord.Client({
intents: [Discord.Intents.GUILDS, Discord.Intents.GUILD_VOICE_STATES, Discord.Intents.GUILD_MESSAGES]
intents: [Discord.Intents.GUILDS, Discord.Intents.GUILD_VOICE_STATES, Discord.Intents.GUILD_MESSAGES]
});
const { DartVoiceManager } = require("dartjs");
const voiceManager = new DartVoiceManager(client);
Expand All @@ -28,19 +31,19 @@ const ytdl = require("ytdl-core");
client.on("ready", () => console.log("Bot is online!"));

client.on("messageCreate", message => {
if (message.author.bot) return;

if (message.content === "!play") {
voiceManager.join(message.member.voice.channel)
.then(connection => {
const dispatcher = connection.play(ytdl("https://www.youtube.com/watch?v=dQw4w9WgXcQ"));
dispatcher.on("start", () => message.channel.send("Music started!"));
dispatcher.on("finish", () => {
connection.disconnect();
message.channel.send("Music finished!");
});
});
}
if (message.author.bot) return;

if (message.content === "!play") {
voiceManager.join(message.member.voice.channel)
.then(connection => {
const dispatcher = connection.play(ytdl("https://www.youtube.com/watch?v=dQw4w9WgXcQ"));
dispatcher.once("start", () => message.channel.send("Music started!"));
dispatcher.once("finish", () => {
connection.disconnect();
message.channel.send("Music finished!");
});
});
}
});

client.login("XXX");
Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dartjs",
"version": "1.0.1",
"version": "1.1.0",
"description": "Very simple framework that provides discord.js v12 voice interface",
"main": "dist/index.js",
"module": "dist/index.mjs",
Expand All @@ -24,7 +24,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/DevSnowflake/dartjs.git"
"url": "git+https://github.com/CesiumLabs/dartjs.git"
},
"keywords": [
"dartjs",
Expand All @@ -35,15 +35,15 @@
"author": "DevAndromeda",
"license": "MIT",
"bugs": {
"url": "https://github.com/DevSnowflake/dartjs/issues"
"url": "https://github.com/CesiumLabs/dartjs/issues"
},
"homepage": "https://github.com/DevSnowflake/typescript-template#readme",
"homepage": "https://github.com/CesiumLabs/typescript-template#readme",
"devDependencies": {
"@favware/rollup-type-bundler": "^1.0.3",
"@types/node": "^16.7.1",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"discord.js": "^13.1.0",
"discord.js": "^13.6.0",
"eslint": "^7.32.0",
"gen-esm-wrapper": "^1.1.2",
"husky": "^7.0.1",
Expand All @@ -53,9 +53,10 @@
"ts-node": "^10.2.1",
"tweetnacl": "^1.0.3",
"typescript": "^4.3.5",
"ytdl-core": "^4.9.1"
"youtube-sr": "^4.1.13",
"ytdl-core": "^4.10.1"
},
"dependencies": {
"@discordjs/voice": "^0.6.0"
"@discordjs/voice": "^0.8.0"
}
}
6 changes: 3 additions & 3 deletions src/core/DartVoiceManager.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Client, Snowflake, GuildVoiceChannelResolvable, VoiceBasedChannelTypes } from "discord.js";
import { Collection } from "discord.js";
import VoiceConnection from "./VoiceConnection";
import { VoiceChannels } from "../types/types";
import { VoiceChannels, VoiceJoinConfig } from "../types/types";

export default class DartVoiceManager {
public connections = new Collection<Snowflake, VoiceConnection>();
public constructor(public readonly client: Client) {}

public async join(channel: GuildVoiceChannelResolvable) {
public async join(channel: GuildVoiceChannelResolvable, options?: VoiceJoinConfig) {
const vc = this.client.channels.resolve(channel ?? "");
if (!vc || !vc.isVoice()) throw new Error("Voice channel was not provided!");

Expand All @@ -23,7 +23,7 @@ export default class DartVoiceManager {
return connection;
}
} else {
const connection = await VoiceConnection.createConnection(vc, this);
const connection = await VoiceConnection.createConnection(vc, this, options);
this.connections.set(vc.guildId, connection);

return connection;
Expand Down
121 changes: 71 additions & 50 deletions src/core/StreamDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,125 +10,146 @@ export default class StreamDispatcher extends EventEmitter<DispatcherEvents> {
public audioResource: AudioResource = null;
private readyLock = false;

constructor(public readonly connection: VoiceConnection) {
public constructor(public readonly connection: VoiceConnection) {
super();
this.attachEvents();
this.connection.voice.subscribe(this.audioPlayer);
}

public cleanUp() {
this.connection.voice.removeAllListeners("stateChange");
this.connection.voice.removeAllListeners("debug");
this.connection.voice.removeAllListeners("error");
this.audioPlayer.removeAllListeners("stateChange");
this.audioPlayer.removeAllListeners("error");
}

private attachEvents() {
this.connection.voice.on("stateChange", async (_, newState) => {
if (newState.status === VoiceConnectionStatus.Disconnected) {
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
if (!this.connection.voice.eventNames().includes("stateChange"))
this.connection.voice.on("stateChange", async (_, newState) => {
if (newState.status === VoiceConnectionStatus.Disconnected) {
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
try {
await entersState(this.connection.voice, VoiceConnectionStatus.Connecting, 5_000);
} catch {
this.connection.voiceManager.connections.delete(this.connection.channel.guildId);
this.connection.emit("disconnect");
this.connection.voice.destroy();
}
} else if (this.connection.voice.rejoinAttempts < 5) {
await wait((this.connection.voice.rejoinAttempts + 1) * 5_000);
this.connection.voice.rejoin();
} else {
this.connection.voiceManager.connections.delete(this.connection.channel.guildId);
this.connection.emit("disconnect");
this.connection.voice.destroy();
}
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
this.audioPlayer?.stop();
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
this.readyLock = true;
try {
await entersState(this.connection.voice, VoiceConnectionStatus.Connecting, 5_000);
await entersState(this.connection.voice, VoiceConnectionStatus.Ready, 20_000);
} catch {
this.connection.manager.connections.delete(this.connection.channel.guildId);
if (this.connection.voice.state.status !== VoiceConnectionStatus.Destroyed) this.connection.voice.destroy();
this.connection.voiceManager.connections.delete(this.connection.channel.guildId);
this.connection.emit("disconnect");
this.connection.voice.destroy();
} finally {
this.readyLock = false;
}
} else if (this.connection.voice.rejoinAttempts < 5) {
await wait((this.connection.voice.rejoinAttempts + 1) * 5_000);
this.connection.voice.rejoin();
} else {
this.connection.manager.connections.delete(this.connection.channel.guildId);
this.connection.emit("disconnect");
this.connection.voice.destroy();
}
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
this.audioPlayer?.stop();
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
this.readyLock = true;
try {
await entersState(this.connection.voice, VoiceConnectionStatus.Ready, 20_000);
} catch {
if (this.connection.voice.state.status !== VoiceConnectionStatus.Destroyed) this.connection.voice.destroy();
this.connection.manager.connections.delete(this.connection.channel.guildId);
this.connection.emit("disconnect");
} finally {
this.readyLock = false;
});

if (!this.audioPlayer.eventNames().includes("stateChange"))
this.audioPlayer.on("stateChange", (oldState, newState) => {
if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
this.emit("finish");
} else if (newState.status === AudioPlayerStatus.Playing && oldState.status === AudioPlayerStatus.Buffering) {
this.emit("start");
}
}
});
});

this.audioPlayer.on("stateChange", (oldState, newState) => {
if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
this.emit("finish");
} else if (newState.status === AudioPlayerStatus.Playing) {
this.emit("start");
}
});
if (!this.connection.voice.eventNames().includes("debug")) this.connection.voice.on("debug", (m) => void this.connection.emit("debug", m));
if (!this.connection.voice.eventNames().includes("error")) this.connection.voice.on("error", (error) => void this.connection.emit("error", error));
if (!this.audioPlayer.eventNames().includes("debug")) this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
if (!this.audioPlayer.eventNames().includes("error")) this.audioPlayer.on("error", (error) => void this.emit("error", error));
}

public end(force = false) {
this.audioPlayer.stop(force);
}

this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
this.audioPlayer.on("error", (error) => void this.emit("error", error));
public stop(force = false) {
this.end(force);
}

playStream(stream: Readable | string, options?: PlayOptions) {
public playStream(stream: Readable | string, options?: PlayOptions) {
this.audioResource = createAudioResource(stream, {
inputType: (options?.type as StreamType) || StreamType.Arbitrary,
inlineVolume: options?.inlineVolume ?? true
});

this.end(true);
this.audioPlayer.play(this.audioResource);
}

setVolume(amount: number) {
public setVolume(amount: number) {
const lastVolume = this.volume;
if (lastVolume === amount || !this.audioResource?.volume) return false;
this.audioResource?.volume?.setVolume(amount);
this.emit("volumeChange", lastVolume, this.volume);
return true;
}

setVolumeLogarithmic(amount: number) {
public setVolumeLogarithmic(amount: number) {
const lastVolume = this.volume;
if (lastVolume === amount || !this.audioResource?.volume) return false;
this.audioResource?.volume?.setVolumeLogarithmic(amount);
this.emit("volumeChange", lastVolume, this.volume);
return true;
}

setVolumeDecibels(amount: number) {
public setVolumeDecibels(amount: number) {
const lastVolume = this.volume;
if (lastVolume === amount || !this.audioResource?.volume) return false;
this.audioResource?.volume?.setVolumeDecibels(amount);
this.emit("volumeChange", lastVolume, this.volume);
return true;
}

get volume() {
public get volume() {
return this.audioResource?.volume?.volume ?? 1;
}

get volumeDecibels() {
public get volumeDecibels() {
return this.audioResource?.volume?.volumeDecibels ?? 1;
}

get volumeLogarithmic() {
public get volumeLogarithmic() {
return this.audioResource?.volume?.volumeLogarithmic ?? 1;
}

get volumeEditable() {
public get volumeEditable() {
return Boolean(this.audioResource?.volume);
}

get streamTime() {
public get streamTime() {
return this.audioResource?.playbackDuration ?? 0;
}

get totalStreamTime() {
public get totalStreamTime() {
return this.audioPlayer?.state.status === AudioPlayerStatus.Playing ? this.audioPlayer?.state.playbackDuration : 0;
}

get paused() {
public get paused() {
return this.audioPlayer.state.status === AudioPlayerStatus.Paused || this.audioPlayer.state.status === AudioPlayerStatus.AutoPaused;
}

pause(silence = false) {
public pause(silence = false) {
this.audioPlayer?.pause(silence);
}

resume() {
public resume() {
this.audioPlayer?.unpause();
}
}
Expand Down
Loading

0 comments on commit 37e53b7

Please sign in to comment.