Another lavalink wrapper but focus on stability and rich features
- Stable client
- Support TypeScript
- 100% Compatible with Lavalink
- Object-oriented
- Easy to setup
- Inbuilt Queue System
- Extendable Player, Queue, Rest class
- Backward compatible (Can run lavalink version 3.7.x)
- Driver based (avaliable to run Nodelink v2 and port older lavalink version)
- Plugin system
- Using PWSL by The PerformanC Organization for better WS implementation
- Compatible with bun
npm i rainlink
Here: https://rainlinkjs.vercel.app/
| Name | Creator | Variants |
|---|---|---|
| ByteBlaze | RainyXeon | Original |
| Lunox | adh319 | Original |
If you want to add your own bot create a pull request with your bot added. Please add your full name.
This is the list of all rainlink plugin currently supported
| Name | Type | Link | Author |
|---|---|---|---|
| rainlink-nico | Official | npmjs / github | RainyXeon |
| rainlink-deezer | Official | npmjs / github | RainyXeon |
| rainlink-apple | Official | npmjs / github | RainyXeon |
| rainlink-spotify | Official | npmjs / github | RainyXeon |
| rainlink-voice | Official | npmjs / github | RainyXeon |
This is the list of all rainlink driver currently supported (codename is made up by me)
| Driver Name | Voice Server | Language | Supported Version | Codename | Notes |
|---|---|---|---|---|---|
| lavalink/v4/koinu | Lavalink | Java | v4.0.0 - v4.x.x | koinu | |
| lavalink/v3/koto | Lavalink | Java | v3.0.0 - v3.7.x | koto | filter and resume in lavalink below v3.4 not supported |
| nodelink/v2/nari | Nodelink | Javascript | v2.0.0 - v2.x.x | nari | Some filter mode in nodelink not supported |
| frequenc/v1/miku | FrequenC | C | IN TESTING | miku | This driver is in testing so don't use it or you will have errors |
const {Client, GatewayIntentBits} = require('discord.js'); const {Guilds, GuildVoiceStates, GuildMessages, MessageContent} = GatewayIntentBits; const {Rainlink, Library} = require("rainlink"); const Nodes = [{ name: 'owo', host: '192.168.0.66', port: 2333, auth: 'youshallnotpass', secure: false, }]; const client = new Client({intents: [Guilds, GuildVoiceStates, GuildMessages, MessageContent]}); const rainlink = new Rainlink({ library: new Library.DiscordJS(client), nodes: Nodes, }); client.on("ready", () => console.log(client.user?.tag + " Ready!")); rainlink.on('nodeConnect', (node) => console.log(`Lavalink ${node.options.name}: Ready!`)); rainlink.on('nodeError', (node, error) => console.error(`Lavalink ${node.options.name}: Error Caught,`, error)); rainlink.on("nodeClosed", (node) => console.warn(`Lavalink ${node.options.name}: Closed`)) // rainlink.on('debug', (name, info) => console.debug(`Lavalink ${name}: Debug,`, info)); rainlink.on('nodeDisconnect', (node, code, reason) => { console.warn(`Lavalink ${node.options.name}: Disconnected, Code ${code}, Reason ${reason || 'No reason'}`) }); rainlink.on("trackStart", (player, track) => { client.channels.cache.get(player.textId).send({content: `Now playing **${track.title}** by **${track.author}**`}) }); rainlink.on("trackEnd", (player) => { client.channels.cache.get(player.textId).send({content: `Finished playing`}) }); rainlink.on("queueEmpty", player => { client.channels.cache.get(player.textId).send({content: `Destroyed player due to inactivity.`}) player.destroy(); }); client.on("messageCreate", async msg => { if (msg.author.bot) return; if (msg.content.startsWith("!play")) { const args = msg.content.split(" "); const query = args.slice(1).join(" "); const {channel} = msg.member.voice; if (!channel) return msg.reply("You need to be in a voice channel to use this command!"); let player = await rainlink.create({ guildId: msg.guild.id, textId: msg.channel.id, voiceId: channel.id, shardId: 0, volume: 40 }) let result = await rainlink.search(query, {requester: msg.author}); if (!result.tracks.length) return msg.reply("No results found!"); if (result.type === "PLAYLIST") for (let track of result.tracks) player.queue.add(track); else player.queue.add(result.tracks[0]); if (!player.playing || !player.paused) player.play(); return msg.reply({content: result.type === "PLAYLIST" ? `Queued ${result.tracks.length} from ${result.playlistName}` : `Queued ${result.tracks[0].title}`}); } }) client.login('');
src/Interface/Manager.ts + RainlinkNodeOptions.legacyWS + RainlinkNodeOptions.region + src/Player/RainlinkVoice src/Player/RainlinkPLayer - RainlinkPlayer.state - RainlinkPlayer.lastvoiceId - RainlinkPlayer.region - RainlinkPlayer.lastRegion - RainlinkPlayer.serverUpdat - RainlinkPlayer.sessionId
+ src/Interface/Events.tssrc/index.ts - Plugin - Plugin.VoiceReceiver - Plugin.PlayerMoved src/Interface/Constants.ts - VoiceConnect = 'voiceConnect', - VoiceDisconnect = 'voiceDisconnect', - VoiceError = 'voiceError', - VoiceStartSpeaking = 'voiceStartSpeaking', - VoiceEndSpeaking = 'voiceEndSpeaking', src/Plugin/RainlinkPlugin.ts + isRainlinkPlugin
src/Node/RainlinkRest.ts - getLavalinkInfo() + getInfo()
src/Player/RainlinkPlayer.ts - <RainlinkPlayer>.setFilter("nightcore") + <RainlinkPlayer>.filter.set() + <RainlinkPlayer>.filter src/Interface/Constants.ts + RainlinkFilterMode src/index.ts (Add new class) + RainlinkFilter
- @Deivu: Founder of @shipgirlproject and creator of Shoukaku / inspired
- @0t4u: Member of @shipgirlproject / inspired
- @Takiyo0: Creator of Kazagumo / Player and queue inspiration
- @Solaris9: Original creator of erela.js / Event system inspiration
- And everyone who contribute my project π
- @RainyXeon: Owner of Rainlink
- @PAINFUEG0: Tester of #TeamRain