├── .env.example ├── .gitignore ├── .node-version ├── README.md ├── package.json ├── src ├── commands │ ├── Join.ts │ ├── Leave.ts │ ├── Nightcore.ts │ ├── Ping.ts │ ├── Play.ts │ ├── Queue.ts │ └── Remove.ts ├── index.ts └── lib │ ├── Bot.ts │ ├── Utils.ts │ ├── command │ ├── Command.ts │ └── CommandContext.ts │ └── index.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | BOT_TOKEN=your bot token 2 | LAVA_HOST=localhost 3 | LAVA_PASS=youshallnotpass 4 | SPOTIFY_CLIENT_ID=your spotify client id 5 | SPOTIFY_CLIENT_SECRET=your spotify client secret -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editor settings 2 | .idea/ 3 | .vscode/ 4 | 5 | # typescript distribution 6 | dist/ 7 | 8 | # node modules 9 | node_modules/ 10 | 11 | # environment variables 12 | *.env 13 | 14 | # log files 15 | *.log 16 | 17 | # yarn lock files 18 | *.lock 19 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16.6.1 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discord.js v13 example bot 2 | 3 | This is an example of lavaclient using discord.js v13 4 | 5 | ## Setup 6 | 7 | - 1. Create an application in the [Discord Developer Portal](https://discord.com/developers/applications). 8 | - 2. Grab the bot token from the "Bot" tab. 9 | - 3. Click the `Use this template` button 10 | - 4. Install all dependencies: `yarn` or `npm install` 11 | - 5. Setup lavalink 12 | - 1. Make sure you have java 11 or above installed. 13 | - 2. Grab a jar from the [Lavalink Releases Page](https://github.com/freyacodes/lavalink/releases) 14 | - 3. Create an [application.yml](https://github.com/freyacodes/lavalink/blob/master/LavalinkServer/application.yml.example) in the same folder as the Lavalink.jar 15 | - 4. Start lavalink using: `java -jar Lavalink.jar` 16 | - 6. Rename `.env.example` to `.env` and replace the value of `BOT_TOKEN` to the token from step 2 17 | - 7. Run the Bot 18 | - production: `yarn start` 19 | - development: `yarn dev` or `yarn dev --force-sync` if you're modifying **application commands** (updating, creating, deleting, etc...) 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lavaclient/discord.js", 3 | "version": "0.0.0", 4 | "description": "Discord.js example bot", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/lavaclient/discord-v13-example", 7 | "author": "melike2d", 8 | "license": "GPL-3.0-only", 9 | "private": false, 10 | "_moduleAliases": { 11 | "@lib": "dist/lib" 12 | }, 13 | "scripts": { 14 | "build": "yarn clean && tsc", 15 | "clean": "rimraf dist", 16 | "dev": "yarn build && node .", 17 | "start": "yarn dev --force-sync" 18 | }, 19 | "dependencies": { 20 | "@lavaclient/queue": "^2.0.4", 21 | "@lavaclient/spotify": "^3.0.0", 22 | "discord.js": "discordjs/discord.js", 23 | "dotenv": "^10.0.0", 24 | "lavaclient": "^4.0.4", 25 | "module-alias": "^2.2.2" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^16.10.2", 29 | "rimraf": "^3.0.2", 30 | "type-fest": "^2.3.2", 31 | "typescript": "^4.4.3" 32 | }, 33 | "engines": { 34 | "node": ">=16.6.x" 35 | } 36 | } -------------------------------------------------------------------------------- /src/commands/Join.ts: -------------------------------------------------------------------------------- 1 | import { 2 | command, 3 | Command, 4 | CommandContext, 5 | MessageChannel, 6 | Utils, 7 | } from "@lib"; 8 | 9 | @command({ name: "join", description: "Joins the member's voice channel." }) 10 | export default class Join extends Command { 11 | async exec(ctx: CommandContext) { 12 | /* check if the invoker is in a voice channel. */ 13 | const vc = ctx.guild?.voiceStates?.cache?.get(ctx.user.id)?.channel; 14 | if (!vc) { 15 | return ctx.reply(Utils.embed("Join a vc bozo"), { ephemeral: true }) 16 | } 17 | 18 | /* check if a player already exists for this guild. */ 19 | const player = ctx.client.music.createPlayer(vc.guild.id); 20 | if (player.connected) { 21 | return ctx.reply(Utils.embed("I'm already connected to a vc."), { ephemeral: true }); 22 | } 23 | 24 | /* set the queue channel so that we can send track start embeds. */ 25 | player.queue.channel = ctx.channel as MessageChannel; 26 | 27 | /* connect to the vc. */ 28 | await player.connect(vc.id); 29 | 30 | return ctx.reply(Utils.embed(`Joined ${vc}`)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/Leave.ts: -------------------------------------------------------------------------------- 1 | import { command, Command, CommandContext, Utils } from "@lib"; 2 | 3 | @command({ name: "leave", description: "Leaves the VC that the bot is currently in." }) 4 | export default class Leave extends Command { 5 | async exec(ctx: CommandContext) { 6 | /* check if a player exists for this guild. */ 7 | const player = ctx.client.music.players.get(ctx.guild!.id); 8 | if (!player?.connected) { 9 | return ctx.reply(Utils.embed("I couldn't find a player for this guild."), { ephemeral: true }); 10 | } 11 | 12 | /* check if the user is in the player's voice channel. */ 13 | const vc = ctx.guild?.voiceStates?.cache?.get(ctx.user.id)?.channel; 14 | if (!vc || player.channelId !== vc.id) { 15 | return ctx.reply(Utils.embed("You're not in my voice channel, bozo."), { ephemeral: true }); 16 | } 17 | 18 | await ctx.reply(Utils.embed(`Left <#${player.channelId}>`)); 19 | 20 | /* leave the player's voice channel. */ 21 | player.disconnect(); 22 | ctx.client.music.destroyPlayer(player.guildId); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Nightcore.ts: -------------------------------------------------------------------------------- 1 | import { command, Command, CommandContext, Utils } from "@lib"; 2 | 3 | @command({ name: "nightcore", description: "Enabled the nightcore filter in this guild." }) 4 | export default class Nightcore extends Command { 5 | async exec(ctx: CommandContext) { 6 | /* check if there is a player for this guild. */ 7 | const player = ctx.player 8 | if (!player?.connected) { 9 | return ctx.reply(Utils.embed("There's no active player for this guild."), { ephemeral: true }); 10 | } 11 | 12 | /* toggle the nightcore filter. */ 13 | player.filters.timescale = (player.nightcore = !player.nightcore) 14 | ? { speed: 1.125, pitch: 1.125, rate: 1 } 15 | : undefined; 16 | 17 | await player.setFilters(); 18 | return ctx.reply(Utils.embed(`${player.nightcore ? "Enabled" : "Disabled"} the **nightcore** filter!`)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/Ping.ts: -------------------------------------------------------------------------------- 1 | import { command, Command, CommandContext, Utils } from "@lib"; 2 | 3 | @command({ name: "ping", description: "Shows the latency of the bot." }) 4 | export default class Ping extends Command { 5 | exec(ctx: CommandContext) { 6 | ctx.reply(Utils.embed(`Pong! **Heartbeat:** *${Math.round(ctx.client.ws.ping)}ms*`), { ephemeral: true }); 7 | } 8 | } -------------------------------------------------------------------------------- /src/commands/Play.ts: -------------------------------------------------------------------------------- 1 | import { command, Command, CommandContext, MessageChannel, Utils } from "@lib"; 2 | import { SpotifyItemType } from "@lavaclient/spotify"; 3 | 4 | import type { Addable } from "@lavaclient/queue"; 5 | 6 | @command({ 7 | name: "play", 8 | description: "Plays a song in the current vc.", 9 | options: [ 10 | { 11 | name: "query", 12 | description: "The search query.", 13 | type: "STRING", 14 | required: true 15 | }, 16 | { 17 | name: "next", 18 | description: "Whether to add the results to the top of the queue.", 19 | type: "BOOLEAN", 20 | required: false 21 | } 22 | ] 23 | }) 24 | export default class Play extends Command { 25 | async exec(ctx: CommandContext, { query, next }: { query: string, next: boolean }) { 26 | /* check if the invoker is in a vc. */ 27 | const vc = ctx.guild?.voiceStates?.cache?.get(ctx.user.id)?.channel 28 | if (!vc) { 29 | return ctx.reply(Utils.embed("Join a voice channel bozo"), { ephemeral: true }); 30 | } 31 | 32 | /* check if a player already exists, if so check if the invoker is in our vc. */ 33 | let player = ctx.client.music.players.get(ctx.guild!.id); 34 | if (player && player.channelId !== vc.id) { 35 | return ctx.reply(Utils.embed(`Join <#${player.channelId}> bozo`), { ephemeral: true }); 36 | } 37 | 38 | let tracks: Addable[] = [], msg: string = ""; 39 | if (ctx.client.music.spotify.isSpotifyUrl(query)) { 40 | const item = await ctx.client.music.spotify.load(query); 41 | switch (item?.type) { 42 | case SpotifyItemType.Track: 43 | const track = await item.resolveYoutubeTrack(); 44 | tracks = [ track ]; 45 | msg = `Queued track [**${item.name}**](${query}).`; 46 | break; 47 | case SpotifyItemType.Artist: 48 | tracks = await item.resolveYoutubeTracks(); 49 | msg = `Queued the **Top ${tracks.length} tracks** for [**${item.name}**](${query}).`; 50 | break; 51 | case SpotifyItemType.Album: 52 | case SpotifyItemType.Playlist: 53 | tracks = await item.resolveYoutubeTracks(); 54 | msg = `Queued **${tracks.length} tracks** from ${SpotifyItemType[item.type].toLowerCase()} [**${item.name}**](${query}).`; 55 | break; 56 | default: 57 | return ctx.reply({ content: "Sorry, couldn't find anything :/", ephemeral: true }); 58 | } 59 | } else { 60 | const results = await ctx.client.music.rest.loadTracks(/^https?:\/\//.test(query) 61 | ? query 62 | : `ytsearch:${query}`); 63 | 64 | switch (results.loadType) { 65 | case "LOAD_FAILED": 66 | case "NO_MATCHES": 67 | return ctx.reply({ content: "uh oh something went wrong", ephemeral: true }); 68 | case "PLAYLIST_LOADED": 69 | tracks = results.tracks; 70 | msg = `Queued playlist [**${results.playlistInfo.name}**](${query}), it has a total of **${tracks.length}** tracks.`; 71 | break 72 | case "TRACK_LOADED": 73 | case "SEARCH_RESULT": 74 | const [track] = results.tracks; 75 | tracks = [track]; 76 | msg = `Queued [**${track.info.title}**](${track.info.uri})`; 77 | break; 78 | } 79 | } 80 | 81 | /* create a player and/or join the member's vc. */ 82 | if (!player?.connected) { 83 | player ??= ctx.client.music.createPlayer(ctx.guild!.id); 84 | player.queue.channel = ctx.channel as MessageChannel; 85 | await player.connect(vc.id, { deafened: true }); 86 | } 87 | 88 | /* reply with the queued message. */ 89 | const started = player.playing || player.paused; 90 | await ctx.reply(Utils.embed({ 91 | description: msg, 92 | footer: next ? { text: "Added to the top of the queue." } : undefined 93 | }), { ephemeral: !started }); 94 | 95 | /* do queue tings. */ 96 | player.queue.add(tracks, { requester: ctx.user.id, next }); 97 | if (!started) { 98 | await player.queue.start() 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/commands/Queue.ts: -------------------------------------------------------------------------------- 1 | import { command, Command, CommandContext, Utils } from "@lib"; 2 | 3 | const formatIndex = (index: number, size: number) => (index + 1).toString().padStart(size.toString().length, "0") 4 | 5 | @command({ name: "queue", description: "Shows the tracks that are in the queue." }) 6 | export default class Queue extends Command { 7 | async exec(ctx: CommandContext) { 8 | /* check if a player exists for this guild. */ 9 | const player = ctx.client.music.players.get(ctx.guild!.id); 10 | if (!player?.connected) { 11 | return ctx.reply(Utils.embed("I couldn't find a player for this guild."), { ephemeral: true }); 12 | } 13 | 14 | /* check if the queue is empty. */ 15 | if (!player.queue.tracks.length) { 16 | return ctx.reply(Utils.embed("There are no tracks in the queue.")); 17 | } 18 | 19 | /* respond with an embed of the queue. */ 20 | const size = player.queue.tracks.length; 21 | const str = player.queue.tracks 22 | .map((t, idx) => `\`#${formatIndex(idx, size)}\` [**${t.title}**](${t.uri}) ${t.requester ? `<@${t.requester}>` : ""}`) 23 | .join("\n"); 24 | 25 | return ctx.reply(Utils.embed({ 26 | description: str, 27 | title: `Queue for **${ctx.guild?.name}**` 28 | })); 29 | } 30 | } -------------------------------------------------------------------------------- /src/commands/Remove.ts: -------------------------------------------------------------------------------- 1 | import { command, Command, CommandContext, Utils } from "@lib"; 2 | 3 | @command({ 4 | name: "remove", 5 | description: "Removes a track from the queue.", 6 | options: [ 7 | { 8 | name: "index", 9 | description: "The index of the track to remove.", 10 | type: "INTEGER", 11 | required: true 12 | } 13 | ] 14 | }) 15 | export default class Remove extends Command { 16 | async exec(ctx: CommandContext, { index }: { index: number }) { 17 | /* check if a player exists for this guild. */ 18 | const player = ctx.client.music.players.get(ctx.guild!.id); 19 | if (!player?.connected) { 20 | return ctx.reply(Utils.embed("I couldn't find a player for this guild."), { ephemeral: true }); 21 | } 22 | 23 | /* check if the user is in the player's voice channel. */ 24 | const vc = ctx.guild?.voiceStates?.cache?.get(ctx.user.id)?.channel; 25 | if (!vc || player.channelId !== vc.id) { 26 | return ctx.reply(Utils.embed("You're not in my voice channel, bozo."), { ephemeral: true }); 27 | } 28 | 29 | /* remove the track from the queue. */ 30 | const removedTrack = player.queue.remove(index - 1); 31 | if (!removedTrack) { 32 | /* no track was removed. */ 33 | return ctx.reply(Utils.embed("No tracks were removed."), { ephemeral: true }); 34 | } 35 | 36 | return ctx.reply(Utils.embed(`The track [**${removedTrack.title}**](${removedTrack.uri}) was removed.`)); 37 | } 38 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import "module-alias/register"; 3 | import { load } from "@lavaclient/spotify"; 4 | import { Utils, Bot, CommandContext } from "@lib"; 5 | import { join } from "path"; 6 | 7 | load({ 8 | client: { 9 | id: process.env.SPOTIFY_CLIENT_ID!, 10 | secret: process.env.SPOTIFY_CLIENT_SECRET!, 11 | }, 12 | autoResolveYoutubeTracks: true 13 | }); 14 | 15 | const client = new Bot() 16 | 17 | client.music.on("connect", () => { 18 | console.log(`[music] now connected to lavalink`) 19 | }); 20 | 21 | client.music.on("queueFinish", queue => { 22 | const embed = Utils.embed("Uh oh, the queue has ended :/"); 23 | 24 | queue.channel.send({ embeds: [ embed ] }); 25 | queue.player.disconnect() 26 | queue.player.node.destroyPlayer(queue.player.guildId); 27 | }) 28 | 29 | client.music.on("trackStart", (queue, song) => { 30 | const embed = Utils.embed(`Now playing [**${song.title}**](${song.uri}) ${song.requester ? `<@${song.requester}>` : ""}`) 31 | queue.channel.send({ embeds: [embed] }); 32 | }); 33 | 34 | client.on("ready", async () => { 35 | await Utils.syncCommands(client, join(__dirname, "commands"), !process.argv.includes("--force-sync")); 36 | client.music.connect(client.user!.id); // Client#user shouldn't be null on ready 37 | console.log("[discord] ready!"); 38 | }); 39 | 40 | client.on("interactionCreate", interaction => { 41 | if (interaction.isCommand()) { 42 | const options = Object.assign({}, ...interaction.options.data.map(i => { 43 | const value = i.role ?? i.channel ?? i.member ?? i.user ?? i.value; 44 | return { [i.name]: value } 45 | })) 46 | 47 | client.commands.get(interaction.commandId)?.exec(new CommandContext(interaction), options); 48 | } 49 | }); 50 | 51 | client.login(process.env.BOT_TOKEN) 52 | -------------------------------------------------------------------------------- /src/lib/Bot.ts: -------------------------------------------------------------------------------- 1 | import { Client, Collection, Snowflake } from "discord.js"; 2 | import { Node } from "lavaclient"; 3 | 4 | import { Command } from "./command/Command"; 5 | 6 | export class Bot extends Client { 7 | readonly music: Node; 8 | readonly commands: Collection = new Collection(); 9 | 10 | constructor() { 11 | super({ 12 | intents: ["GUILDS", "GUILD_MESSAGES", "GUILD_VOICE_STATES"] 13 | }); 14 | 15 | this.music = new Node({ 16 | sendGatewayPayload: (id, payload) => this.guilds.cache.get(id)?.shard?.send(payload), 17 | connection: { 18 | host: process.env.LAVA_HOST!, 19 | password: process.env.LAVA_PASS!, 20 | port: 2333 21 | } 22 | }); 23 | 24 | this.ws.on("VOICE_SERVER_UPDATE", data => this.music.handleVoiceUpdate(data)); 25 | this.ws.on("VOICE_STATE_UPDATE", data => this.music.handleVoiceUpdate(data)); 26 | } 27 | } 28 | 29 | declare module "discord.js" { 30 | interface Client { 31 | readonly music: Node 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/Utils.ts: -------------------------------------------------------------------------------- 1 | import { MessageEmbed, MessageEmbedOptions } from "discord.js"; 2 | import { lstatSync, readdirSync } from "fs"; 3 | import { join } from "path"; 4 | 5 | import type { Command } from "@lib"; 6 | import type { Bot } from "./Bot"; 7 | import type { NewsChannel, TextChannel, ThreadChannel } from "discord.js"; 8 | 9 | export type MessageChannel = TextChannel | ThreadChannel | NewsChannel; 10 | 11 | export abstract class Utils { 12 | static PRIMARY_COLOR = 0xfff269; 13 | 14 | static embed(embed: MessageEmbedOptions | string): MessageEmbed { 15 | const options: MessageEmbedOptions = typeof embed === "string" ? { description: embed } : embed; 16 | options.color ??= Utils.PRIMARY_COLOR; 17 | 18 | return new MessageEmbed(options); 19 | } 20 | 21 | static walk(directory: string): string[] { 22 | function read(dir: string, files: string[] = []) { 23 | for (const item of readdirSync(dir)) { 24 | const path = join(dir, item), stat = lstatSync(path) 25 | if (stat.isDirectory()) { 26 | files.concat(read(path, files)) 27 | } else if (stat.isFile()) { 28 | files.push(path); 29 | } 30 | } 31 | 32 | return files; 33 | } 34 | 35 | return read(directory); 36 | } 37 | 38 | static async syncCommands(client: Bot, dir: string, soft: boolean = false) { 39 | const commands: Command[] = []; 40 | for (const path of Utils.walk(dir)) { 41 | const { default: Command } = await import(path); 42 | if (!Command) { 43 | continue; 44 | } 45 | 46 | commands.push(new Command()); 47 | } 48 | 49 | const commandManager = client.application!.commands, 50 | existing = await commandManager.fetch(); 51 | 52 | /* do soft sync */ 53 | if (soft) { 54 | for (const command of commands) { 55 | const ref = existing.find(c => c.name === command.data.name) 56 | if (!ref) { 57 | continue 58 | } 59 | 60 | command.ref = ref; 61 | client.commands.set(ref.id, command); 62 | } 63 | 64 | console.log(`[discord] slash commands: registered ${client.commands.size}/${commands.length} commands.`); 65 | return; 66 | } 67 | 68 | /* get the slash commands to add, update, or remove. */ 69 | const adding = commands.filter(c => existing.every(e => e.name !== c.data.name)) 70 | , updating = commands.filter(c => existing.some(e => e.name === c.data.name)) 71 | , removing = [ ...existing.filter(e => commands.every(c => c.data.name !== e.name)).values() ]; 72 | 73 | console.log(`[discord] slash commands: removing ${removing.length}, adding ${adding.length}, updating ${updating.length}`) 74 | 75 | /* update/create slash commands. */ 76 | const creating = [...adding, ...updating], 77 | created = await commandManager.set(creating.map(c => c.data)); 78 | 79 | for (const command of creating) { 80 | command.ref = created.find(c => c.name === command.data.name)!; 81 | client.commands.set(command.ref.id, command); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/lib/command/Command.ts: -------------------------------------------------------------------------------- 1 | import type { ApplicationCommand, ApplicationCommandData } from "discord.js"; 2 | import type { CommandContext } from "./CommandContext"; 3 | import type { Class } from "type-fest"; 4 | 5 | export function command(data: ApplicationCommandData) { 6 | return (target: Class) => { 7 | return class extends target { 8 | constructor(...args: any[]) { 9 | super(data, ...args); 10 | } 11 | } 12 | } 13 | } 14 | 15 | export class Command { 16 | readonly data: ApplicationCommandData; 17 | 18 | ref!: ApplicationCommand; 19 | 20 | constructor(data: ApplicationCommandData) { 21 | this.data = data; 22 | } 23 | 24 | exec(ctx: CommandContext, options?: Record): any { 25 | void [ctx, options]; 26 | }; 27 | } -------------------------------------------------------------------------------- /src/lib/command/CommandContext.ts: -------------------------------------------------------------------------------- 1 | import { Guild, Message, MessageEmbed } from "discord.js"; 2 | import type { Client, CommandInteraction, InteractionReplyOptions, User } from "discord.js"; 3 | import type { APIMessage } from "discord-api-types"; 4 | import { Player } from "lavaclient"; 5 | import type { MessageChannel } from "../index"; 6 | 7 | export class CommandContext { 8 | readonly interaction: CommandInteraction; 9 | 10 | constructor(interaction: CommandInteraction) { 11 | this.interaction = interaction; 12 | } 13 | 14 | get client(): Client { 15 | return this.interaction.client; 16 | } 17 | 18 | get player(): Player | null { 19 | return (this.guild && this.client.music.players.get(this.guild.id)) ?? null; 20 | } 21 | 22 | get guild(): Guild | null { 23 | return this.interaction.guild; 24 | } 25 | 26 | get user(): User { 27 | return this.interaction.user; 28 | } 29 | 30 | get channel(): MessageChannel { 31 | return this.interaction.channel as MessageChannel; 32 | } 33 | 34 | /* overloads: not fetching the reply */ 35 | reply(content: MessageEmbed, options?: Omit): Promise 36 | reply(content: string, options?: Omit): Promise 37 | reply(options: InteractionReplyOptions): Promise 38 | 39 | /* overloads: fetch reply */ 40 | reply(content: MessageEmbed, options?: Omit & { fetchReply: true }): Promise 41 | reply(content: string, options?: Omit & { fetchReply: true }): Promise 42 | reply(options: InteractionReplyOptions & { fetchReply: true }): Promise; 43 | 44 | /* actual method */ 45 | reply(content: string | MessageEmbed | InteractionReplyOptions, options: InteractionReplyOptions = {}): Promise { 46 | if (typeof content === "string" || content instanceof MessageEmbed) { 47 | return this.interaction.reply({ 48 | [typeof content === "string" ? "content" : "embeds"]: typeof content === "string" 49 | ? content 50 | : [content], 51 | ...options 52 | }); 53 | } 54 | 55 | return this.interaction.reply(content); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import "@lavaclient/queue/register"; 2 | import type { MessageChannel } from "./Utils"; 3 | 4 | export * from "./Bot"; 5 | export * from "./Utils"; 6 | 7 | export * from "./command/Command"; 8 | export * from "./command/CommandContext"; 9 | 10 | declare module "lavaclient" { 11 | interface Player { 12 | nightcore: boolean; 13 | } 14 | } 15 | 16 | declare module "@lavaclient/queue" { 17 | interface Queue { 18 | channel: MessageChannel; 19 | } 20 | } 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* compilation */ 4 | "lib": [ 5 | "ES2020", 6 | "ES2020.BigInt", 7 | "ES2020.Promise", 8 | "ES2020.String" 9 | ], 10 | "target": "ES2020", 11 | "allowJs": true, 12 | 13 | /* compilation */ 14 | "rootDir": "./src", 15 | "outDir": "./dist", 16 | "declaration": false, 17 | "skipLibCheck": true, 18 | "baseUrl": ".", 19 | "removeComments": true, 20 | "importHelpers": true, 21 | 22 | /* strict check */ 23 | "strict": true, 24 | "strictBindCallApply": true, 25 | "strictNullChecks": true, 26 | "strictFunctionTypes": true, 27 | "strictPropertyInitialization": true, 28 | "alwaysStrict": true, 29 | "importsNotUsedAsValues": "preserve", 30 | "checkJs": true, 31 | "noImplicitAny": true, 32 | // "noImplicitReturns": true, 33 | "noImplicitThis": true, 34 | // "noUnusedLocals": true, 35 | "noUnusedParameters": true, 36 | "experimentalDecorators": true, 37 | "emitDecoratorMetadata": true, 38 | 39 | /* types */ 40 | "types": [ 41 | "node" 42 | ], 43 | 44 | /* module options */ 45 | "module": "CommonJS", 46 | "esModuleInterop": true, 47 | "paths": { 48 | "@lib": [ 49 | "src/lib/index.ts" 50 | ], 51 | "@core": [ 52 | "src/core/*" 53 | ] 54 | } 55 | }, 56 | "exclude": [ 57 | "./dist", 58 | "./node_modules" 59 | ], 60 | "include": [ 61 | "./src" 62 | ] 63 | } 64 | --------------------------------------------------------------------------------