Skip to main content

Lavalink

Transformez votre bot en un DJ professionnel grâce à la puissance de l'écosystème Lavalink. Ce package utilise lavalink-client en coulisses, offrant une solution performante et efficace pour gérer les flux audio sur Discord. En tirant parti de Lavalink, votre bot acquiert la capacité de gérer la lecture audio, les files d'attente et les contrôles en temps réel avec une latence minimale, le transformant en un système musical professionnel et pleinement fonctionnel.

Installation

npm i @necord/lavalink necord discord.js lavalink-client

Utilisation

Une fois le processus d'installation terminé, nous pouvons importer le NecordLavalinkModule avec votre NecordModule dans le module racine AppModule :

app.module.ts
import { NecordLavalinkModule } from '@necord/lavalink';
import { Module } from '@nestjs/common';
import { Client } from 'discord.js';
import { AppService } from './app.service';

@Module({
imports: [
NecordModule.forRoot({
token: process.env.DISCORD_TOKEN,
intents: [
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildVoiceStates
],
}),
NecordLavalinkModule.forRoot({
// At least 1 node is required
nodes: [
{
authorization: 'youshallnotpass',
host: 'localhost',
port: 2333,
}
]
})
],
providers: [AppService]
})
export class AppModule {}

Découvrez davantage d'options de module dans la documentation officielle de lavalink-client.

Écouteurs

Dans @necord/lavalink, la gestion des événements fonctionne de la même manière que les écouteurs Necord par défaut, avec quelques changements.

  • Au lieu des décorateurs On/Once par défaut et de ContextOf, vous pouvez utiliser
app.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Context } from 'necord';
import { OnLavalinkManager, OnNodeManager, LavalinkManagerContextOf, NodeManagerContextOf } from '@necord/lavalink';

@Injectable()
export class AppService {
private readonly logger = new Logger(AppService.name);

@OnNodeManager('connect')
public onConnect(@Context() [node]: NodeManagerContextOf<'connect'>) {
this.logger.log(`Node: ${node.options.id} Connected`);
}

@OnLavalinkManager('playerCreate')
public onPlayerCreate(@Context() [player]: LavalinkManagerContextOf<'playerCreate'>) {
this.logger.log(`Player created at ${player.guildId}`);
}
}

Événements LavalinkManager

Nom de l'événementDescription
trackStartÉmis chaque fois qu'une piste est lue.
trackEndÉmis chaque fois qu'une piste a fini d'être lue.
trackStuckÉmis chaque fois qu'une piste se bloque pendant la lecture.
trackErrorÉmis chaque fois qu'une piste rencontre une erreur.
queueEndÉmis lorsque la piste s'est terminée, mais qu'il n'y a plus de pistes dans la file d'attente. (trackEnd n'est PAS exécuté)
playerCreateÉmis chaque fois qu'un lecteur est créé.
playerMoveÉmis chaque fois qu'un lecteur est déplacé entre des canaux vocaux.
playerDisconnectÉmis chaque fois qu'un lecteur est déconnecté d'un canal.
playerSocketCloseÉmis chaque fois qu'un socket de nœud est fermé pour un lecteur spécifique.
playerDestroyÉmis chaque fois qu'un lecteur est détruit.
playerUpdateÉmis chaque fois qu'un lecteur reçoit une mise à jour de l'événement playerUpdate de Lavalink.
playerMuteChangeÉmis chaque fois que l'état vocal du lecteur lié à la mise en sourdine change.
playerDeafChangeEmitted whenever Player's voice state related to deafing is changed.
playerSuppressChangeEmitted whenever Player's voice state related to supressing is changed.
playerQueueEmptyStartEmitted whenever the Queue empty handler started (timeout).
playerQueueEmptyEndEmitted whenever the Queue empty handler finished (successfully) and destroyed the player.
playerQueueEmptyCancelEmitted whenever the Queue empty handler cancelled (e.g. because a new track got added).
playerVoiceJoinEmitted whenever a user joins the player's voice channel.
playerVoiceLeaveEmitted whenever a user leaves the player's voice channel.
debugEmitted for several erros, and logs within lavalink-client, if managerOptions.advancedOptions.enableDebugEvents is true.

SponsorBlock Plugin Events

Nom de l'événementDescription
SegmentsLoadedEmitted whenever Segments are loaded.
SegmentSkippedEmitted whenever a specific Segment was skipped.
ChapterStartedEmitted whenever a specific Chapter starts playing.
ChaptersLoadedEmitted whenever Chapters are loaded.

LavaLyrics Plugin Events

Nom de l'événementDescription
LyricsLineEmitted whenever a Lyrics line is received.
LyricsFoundEmitted whenever a Lyrics is found.
LyricsNotFoundEmitted whenever a Lyrics is not found.

NodeManager Events

Nom de l'événementDescription
createEmitted whenever a Node is created.
destroyEmitted whenever a Node is destroyed.
connectEmitted whenever a Node is connected.
reconnectingEmitted whenever a Node is reconnecting.
reconnectinprogressEmitted whenever a node starts to reconnect. (if you have a reconnection delay, the reconnecting event will be emitted after the retryDelay.) Useful to check wether the internal node reconnect system works or not.
disconnectEmitted whenever a Node is disconnects.
errorEmitted whenever a Node is error.
rawEmits every single Node event.

Providers

  • @necord/lavalink have snippets to access the lavalink-client methods. You can inject the managers using constructor.
app.service.ts
import { Injectable } from '@nestjs/common';
import { LavalinkManager, NodeManager } from 'lavalink-client';

@Injectable()
export class AppService {
public constructor(
private readonly lavalinkManager: LavalinkManager,
private readonly nodeManager: NodeManager,
) {}
}
Class (Type to be injected)Manager Property (Will access to)Description
LavalinkManagerlavalinkManagerLavalink Manager
NodeManagerlavalinkManager.nodeManagerNode Manager
PlayerManagerlavalinkManager (player functions)Player Manager

Play Tracks

  • Here you'll view a sample of PlayCommand using the best techniques of Slash Commands tutorial.
app.commands.ts
import { Injectable, UseInterceptors } from '@nestjs/common';
import { NecordLavalinkService, PlayerManagerService } from '@necord/lavalink';
import { Context, Options, SlashCommand, SlashCommandContext } from 'necord';
import { QueryDto } from './query.dto';
import { SourceAutocompleteInterceptor } from 'source.autocomplete';

@Injectable()
export class AppCommands {
public constructor(
private readonly playerManager: PlayerManagerService,
private readonly lavalinkService: NecordLavalinkService
) {}

@UseInterceptors(SourceAutocompleteInterceptor)
@SlashCommand({
name: 'play',
description: 'play a track',
})
public async onPlay(
@Context() [interaction]: SlashCommandContext,
@Options() { query, source }: QueryDto,
) {
const player =
this.playerManager.get(interaction.guild.id) ??
this.playerManager.create({
...this.lavalinkService.extractInfoForPlayer(interaction),
// optional configurations:
selfDeaf: true,
selfMute: false,
volume: 100,
});

await player.connect();

const res = await player.search(
{
query,
source: source ?? 'soundcloud'
},
interaction.user.id,
);

await player.queue.add(res.tracks[0]);
if (!player.playing) await player.play();

// It's extremely recommended to use `trackStart` event for this announcement
return interaction.reply({
content: `Now playing ${res.tracks[0].info.title}`,
});
}
}
query.dto.ts
import { SearchPlatform } from 'lavalink-client';
import { StringOption } from 'necord';

export class QueryDto {
@StringOption({
name: 'query',
description: '<name | url> of the requested track',
required: true
})
public readonly query!: string;

@StringOption({
name: 'source',
description: 'source of the track',
autocomplete: true,
required: false,
})
public readonly source?: SearchPlatform;
}
source.autocomplete.ts
import { Injectable } from '@nestjs/common';
import { AutocompleteInteraction } from 'discord.js';
import { DefaultSources } from 'lavalink-client';
import { AutocompleteInterceptor } from 'necord';

@Injectable()
export class SourceAutocompleteInterceptor extends AutocompleteInterceptor {
public transformOptions(interaction: AutocompleteInteraction) {
const focused = interaction.options.getFocused(true);
let choices: string[];

if (focused.name === 'source') {
choices = [DefaultSources.soundcloud] // Note that some Sources needs extra plugins/configuration to property work
}

return interaction.respond(
choices
.filter((choice) => choice.startsWith(focused.value.toString()))
.map((choice) => ({ name: choice, value: choice })),
);
}
}

You can view a working example here.