Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: eris compat mode #1937

Merged
merged 4 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Discord Player is a robust framework for developing Discord Music bots using Jav
- Out-of-the-box voice states handling
- IP Rotation support
- Easy serialization and deserialization
- Limited support for [Eris](https://npmjs.com/eris)

> Eris compat mode does not support `VoiceStateUpdate` handler. You need to handle it manually.

## Installation

Expand Down Expand Up @@ -134,6 +137,16 @@ player.events.on('playerStart', (queue, track) => {
});
```

## Eris Setup

Discord Player has limited support for Eris. You can use the following code to set up Discord Player with Eris:

```js index.js
const { Player, createErisCompat } = require('discord-player');

const player = new Player(createErisCompat(client));
```

Let's move on to the command part. You can define the command as per your requirements. We will only focus on the command part:

```js play.js
Expand Down
23 changes: 23 additions & 0 deletions apps/eris-bot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "eris-bot",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "tsx src/index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/node": "^22.2.0",
"tsx": "^4.17.0"
},
"dependencies": {
"@discord-player/extractor": "workspace:^",
"discord-player": "workspace:^",
"dotenv": "^16.4.5",
"eris": "^0.17.2",
"mediaplex": "^0.0.9"
}
}
109 changes: 109 additions & 0 deletions apps/eris-bot/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* eslint-disable no-console */
import 'dotenv/config';
import Eris from 'eris';
import { Player, createErisCompat } from 'discord-player';

const client = Eris(process.env.DISCORD_TOKEN!, {
intents: ['all']
});

const player = new Player(createErisCompat(client));

player.extractors.loadDefault((ext) => ext !== 'YouTubeExtractor');

player.on('debug', console.log).events.on('debug', (queue, msg) => console.log(`[${queue.guild.name}] ${msg}`));

client.once('ready', () => {
console.log('Ready!');
console.log(player.scanDeps());
});

player.events.on('playerStart', async (queue, track) => {
const meta = queue.metadata as { channel: string };

await client.createMessage(meta.channel, `Now playing: ${track.title}`);
});

player.events.on('playerFinish', async (queue, track) => {
const meta = queue.metadata as { channel: string };

await client.createMessage(meta.channel, `Finished track: ${track.title}`);
});

client.on('messageCreate', async (message) => {
if (message.author.bot) return;
if (!message.content.startsWith('!')) return;
if (!message.guildID) return;

const [command, ...args] = message.content.slice(1).split(' ');

switch (command) {
case 'ping':
return client.createMessage(message.channel.id, 'Pong!');
case 'play': {
const voiceChannel = message.member?.voiceState.channelID;
if (!voiceChannel) return client.createMessage(message.channel.id, 'You need to be in a voice channel!');

const query = args.join(' ');

const { track } = await player.play(voiceChannel, query, {
requestedBy: message.author.id,
nodeOptions: {
metadata: { channel: message.channel.id },
volume: 50
}
});

return client.createMessage(message.channel.id, `Loaded: ${track.title}`);
}
case 'pause': {
const queue = player.queues.get(message.guildID);
if (!queue) return client.createMessage(message.channel.id, 'No queue found!');

queue.node.pause();

return client.createMessage(message.channel.id, 'Paused the queue!');
}
case 'resume': {
const queue = player.queues.get(message.guildID);
if (!queue) return client.createMessage(message.channel.id, 'No queue found!');

queue.node.resume();

return client.createMessage(message.channel.id, 'Resumed the queue!');
}
case 'stop': {
const queue = player.queues.get(message.guildID);
if (!queue) return client.createMessage(message.channel.id, 'No queue found!');

queue.delete();

return client.createMessage(message.channel.id, 'Stopped the queue!');
}
case 'skip': {
const queue = player.queues.get(message.guildID);
if (!queue) return client.createMessage(message.channel.id, 'No queue found!');

const success = queue.node.skip();

if (!success) return client.createMessage(message.channel.id, 'Cannot skip the track!');

return client.createMessage(message.channel.id, 'Skipped the track!');
}
case 'volume': {
const queue = player.queues.get(message.guildID);
if (!queue) return client.createMessage(message.channel.id, 'No queue found!');

if (!args.length) return client.createMessage(message.channel.id, `Current volume: ${queue.node.volume}`);

const volume = parseInt(args[0], 10);
if (isNaN(volume)) return client.createMessage(message.channel.id, 'Invalid volume!');

queue.node.setVolume(volume);

return client.createMessage(message.channel.id, `Set the volume to: ${volume}`);
}
}
});

client.connect();
19 changes: 18 additions & 1 deletion apps/website/src/pages/guide/_guides/welcome/welcome.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ Discord Player is a robust framework for developing Discord Music bots using Jav
- Offers easy debugging methods
- Out-of-the-box voice states handling
- IP Rotation support
- Limited support for [Eris](https://npmjs.com/eris)

> Eris compat mode does not support `VoiceStateUpdate` handler. You need to handle it manually.

## Installation

Expand Down Expand Up @@ -94,7 +97,7 @@ $ npm install --save ffmpeg-binaries

YouTube streaming is not supported without installing one of the following package. If you want to add support for YouTube playback, you need to install a streaming library. This step is not needed if you do not plan on using youtube source.

* The default Youtube streaming appears to be unstable due to recent changes. It is recommend to install [`discord-player-youtubei`](https://npm.im/discord-player-youtubei)*
- The default Youtube streaming appears to be unstable due to recent changes. It is recommend to install [`discord-player-youtubei`](https://npm.im/discord-player-youtubei)\*

```bash
$ npm install --save youtube-ext
Expand Down Expand Up @@ -135,6 +138,20 @@ await player.extractors.loadDefault((ext) => ext !== 'YouTubeExtractor');

</CH.Code>

## Eris Setup

Discord Player has limited support for Eris. You can use the following code to set up Discord Player with Eris:

<CH.Code>

```js index.js
const { Player, createErisCompat } = require('discord-player');

const player = new Player(createErisCompat(client));
```

</CH.Code>

Discord Player is mostly events based. It emits different events based on the context and actions. Let's add a basic event listener to notify the user when a track starts to play:

<CH.Code>
Expand Down
13 changes: 13 additions & 0 deletions packages/discord-player/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Discord Player is a robust framework for developing Discord Music bots using Jav
- Out-of-the-box voice states handling
- IP Rotation support
- Easy serialization and deserialization
- Limited support for [Eris](https://npmjs.com/eris)

> Eris compat mode does not support `VoiceStateUpdate` handler. You need to handle it manually.

## Installation

Expand Down Expand Up @@ -134,6 +137,16 @@ player.events.on('playerStart', (queue, track) => {
});
```

## Eris Setup

Discord Player has limited support for Eris. You can use the following code to set up Discord Player with Eris:

```js index.js
const { Player, createErisCompat } = require('discord-player');

const player = new Player(createErisCompat(client));
```

Let's move on to the command part. You can define the command as per your requirements. We will only focus on the command part:

```js play.js
Expand Down
5 changes: 3 additions & 2 deletions packages/discord-player/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "discord-player",
"version": "6.7.1",
"version": "6.8.0-dev.0",
"description": "Complete framework to facilitate music commands using discord.js",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -67,6 +67,7 @@
"@types/ws": "^8.5.3",
"discord-api-types": "^0.37.0",
"discord.js": "^14.15.3",
"eris": "^0.17.2",
"opusscript": "^0.0.8",
"tsup": "^7.2.0",
"typescript": "^5.2.2",
Expand All @@ -77,4 +78,4 @@
"readmeFile": "./README.md",
"tsconfig": "./tsconfig.json"
}
}
}
63 changes: 40 additions & 23 deletions packages/discord-player/src/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { IPRotator } from './utils/IPRotator';
import { Context, createContext } from './hooks';
import { HooksCtx } from './hooks/common';
import { LrcLib } from './lrclib/LrcLib';
import { getCompatName, isClientProxy } from './compat/createErisCompat';

const kSingleton = Symbol('InstanceDiscordPlayerSingleton');

Expand Down Expand Up @@ -104,33 +105,37 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {

super([PlayerEvent.Error]);

if(options.ffmpegPath) {
if(typeof options.ffmpegPath !== "string") throw new TypeError(`Expected type "string" for options.ffmpegPath. Got ${typeof options.ffmpegPath} instead`)
if (options.ffmpegPath) {
if (typeof options.ffmpegPath !== 'string') throw new TypeError(`Expected type "string" for options.ffmpegPath. Got ${typeof options.ffmpegPath} instead`);

process.env.FFMPEG_PATH = options.ffmpegPath
process.env.FFMPEG_PATH = options.ffmpegPath;
}

const isCompatMode = isClientProxy(client);

/**
* The discord.js client
* @type {Client}
*/
this.client = client;

try {
if (!(client instanceof Client)) {
Util.warn(
`Client is not an instance of discord.js@${djsVersion} client, some things may not work correctly. This can happen due to corrupt dependencies or having multiple installations of discord.js.`,
'InvalidClientInstance'
);
}
if (!isCompatMode) {
try {
if (!(client instanceof Client)) {
Util.warn(
`Client is not an instance of discord.js@${djsVersion} client, some things may not work correctly. This can happen due to corrupt dependencies or having multiple installations of discord.js.`,
'InvalidClientInstance'
);
}

const ibf = this.client.options.intents instanceof IntentsBitField ? this.client.options.intents : new IntentsBitField(this.client.options.intents);
const ibf = this.client.options.intents instanceof IntentsBitField ? this.client.options.intents : new IntentsBitField(this.client.options.intents);

if (!ibf.has(IntentsBitField.Flags.GuildVoiceStates)) {
Util.warn('client is missing "GuildVoiceStates" intent', 'InvalidIntentsBitField');
if (!ibf.has(IntentsBitField.Flags.GuildVoiceStates)) {
Util.warn('client is missing "GuildVoiceStates" intent', 'InvalidIntentsBitField');
}
} catch {
// noop
}
} catch {
// noop
}

this.options = {
Expand All @@ -150,10 +155,11 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {
}
} as PlayerInitOptions;

// @ts-ignore private method
this.client.incrementMaxListeners();

this.client.on(Events.VoiceStateUpdate, this.#voiceStateUpdateListener);
if (!isCompatMode) {
// @ts-ignore private method
this.client.incrementMaxListeners();
this.client.on(Events.VoiceStateUpdate, this.#voiceStateUpdateListener);
}

if (typeof this.options.lagMonitor === 'number' && this.options.lagMonitor > 0) {
this.#lagMonitorInterval = setInterval(() => {
Expand Down Expand Up @@ -295,6 +301,13 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {
};
}

/**
* Whether the player is in compatibility mode. Compatibility mode is enabled when non-discord.js client is used.
*/
public isCompatMode() {
return isClientProxy(this.client);
}

/**
* Destroy every single queues managed by this master player instance
* @example ```typescript
Expand All @@ -304,9 +317,13 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {
*/
public async destroy() {
this.nodes.cache.forEach((node) => node.delete());
this.client.off(Events.VoiceStateUpdate, this.#voiceStateUpdateListener);
// @ts-ignore private method
this.client.decrementMaxListeners();

if (!this.isCompatMode()) {
this.client.off(Events.VoiceStateUpdate, this.#voiceStateUpdateListener);
// @ts-ignore private method
this.client.decrementMaxListeners();
}

this.removeAllListeners();
this.events.removeAllListeners();
await this.extractors.unregisterAll();
Expand Down Expand Up @@ -648,7 +665,7 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {
const depsReport = [
'Discord Player',
line,
`- discord-player: ${Player.version}`,
`- discord-player: ${Player.version}${this.isCompatMode() ? ` (${getCompatName(this.client)} compatibility mode)` : ''}`,
`- discord-voip: ${dVoiceVersion}`,
`- discord.js: ${djsVersion}`,
`- Node version: ${process.version} (Detected Runtime: ${runtime}, Platform: ${process.platform} [${process.arch}])`,
Expand Down
Loading
Loading