A Shoukaku wrapper with built in queue system
Kazagumo © Azur Lane
✓ Built-in queue system
✓ Easy to use
✓ Plugin system
✓ Uses shoukaku v4 + capable of Lavalink v4
✓ Supports dave*
✓ Stable 🙏
*: requires at least lavalink v4.2.0 and above⚠️ Please check Environment that Kazagumo 3.2.0 is verified working on. It's recommended to use the latest version of lavalink. If you encounter any problem, try using previous version. If issue still persist, please open an issue or ask me in Discord (I will answer if I have time)⚠️
Please read the docs first before asking methods
Kazagumo; https://takiyo0.github.io/Kazagumo
Shoukaku by Deivu; https://guide.shoukaku.shipgirl.moe
npm i kazagumo
version: 3.4.2
pre-release: false
Last build: 06-03-2026 02.27 AM
The new lavalink system that separate YouTube plugins made configuration a bit harder. I will list all working environment that's known working.
| Environment | Case 1 | Case 2 | Case 3 |
|---|---|---|---|
| Lavalink Version | v4.0.7 | v4.0.7 | v4.0.7 |
| Youtube Plugin Version | v1.7.2 | v1.7.2 | none |
| LavaSrc Plugin Version | v4.1.1 | v4.1.1 | v4.1.1 |
| Kazagumo Version | v3.2.0 | v3.2.0 | v3.2.0 |
| Shoukaku Version | v4.1.0 (built-in v3.2.0) | v4.1.0 (built-in v3.2.0) | v4.1.0 (built-in v3.2.0) |
| Youtube Plugin Config | youtube.oauth.enabled = true youtube.oauth.accessToken = "filled" youtube.oauth.clients = MUSIC,ANDROID_TESTSUITE,WEB |
youtube.oauth.enabled = true youtube.oauth.accessToken = "filled" youtube.oauth.clients = MUSIC,ANDROID_TESTSUITE,WEB,TVHTML5EMBEDDED |
none |
| Lavalink Config | server.sources.youtube = false server.sources.youtubeSearchEnabled = false |
server.sources.youtube = false server.sources.youtubeSearchEnabled = false |
server.sources.youtube = true server.sources.youtubeSearchEnabled = true |
| LavaSrc Config | lavasrc.sources.youtube = true | lavasrc.sources.youtube = true | lavasrc.sources.youtube = true |
| Result | |||
| YouTube Playlist Load* | ✅ | ❌ | ✅ |
| YouTube Track Load | ✅ | ✅ | ❌ |
| YouTube Search | ✅ | ✅ | ✅ |
| LavaSrc Spotify Playlist Load | ✅ | ✅ | ✅ |
| LavaSrc Spotify Track Load | ✅ | ✅ | ✅ |
| LavaSrc Spotify Search (spsearch:query)** | ✅ | ✅ | ✅ |
| Summary | ✅ works just fine | ➖ cannot load youtube playlist | ❌ cannot play any track youtube related. including spotify |
Note:
*= youtube playlist load with YouTube plugin requires oauth enabled and accessToken filled andTVHTML5EMBEDDEDto be removed from oauth clients, since it's the default config**= to do that, you need to addsourceoption intoSearchOptions. Example:kazagumo.search(query, {source: "spsearch:"});(⚠️ you need to include:in the last ofspsearchor anything to replace source)
- Official spotify plugin
npm i kazagumo-spotify
- Additional apple plugin
npm i kazagumo-apple
- Additional filter plugin
npm i kazagumo-filter
- Additional nicovideo.jp plugin
npm i kazagumo-nico
- Additional deezer plugin
npm i kazagumo-deezer
- Stone-Deezer deezer plugin
npm i stone-deezer
Basically you can follow this Official Step
// You can get ShoukakuPlayer from here
+<KazagumoPlayer>.shoukaku
+ this.player.players.get("69696969696969").shoukaku
// Search tracks
- this.player.getNode().rest.resolve("ytsearch:pretender Official髭男dism") // Shoukaku
+ this.player.search("pretender Official髭男dism") // Kazagumo
// Create a player
- this.player.getNode().joinChannel(...) // Shoukaku
+ this.player.createPlayer(...) // Kazagumo
// Add a track to the queue. MUST BE A kazagumoTrack, you can get from <KazagumoPlayer>.search()
+ this.player.players.get("69696969696969").queue.add(kazagumoTrack) // Kazagumo
// Play a track
- this.player.players.get("69696969696969").playTrack(shoukakuTrack) // Shoukaku
+ this.player.players.get("69696969696969").play() // Kazagumo, take the first song on queue
+ this.player.players.get("69696969696969").play(kazagumoTrack) // Kazagumo, will unshift current song and
forceplay this song
// Play previous song
+ this.player.players.get("69696969696969").play(this.player.players.get("69696969696969").getPrevious()) //
Kazagumo, make sure it's not undefined first
// Pauses or resumes the player. Control from kazagumoPlayer instead of shoukakuPlayer
- this.player.players.get("69696969696969").setPaused(true) // Shoukaku
+ this.player.players.get("69696969696969").pause(true) // Kazagumo
// Set filters. Access shoukakuPlayer from <KazagumoPlayer>.player
- this.player.players.get("69696969696969").setFilters({lowPass: {smoothing: 2}}) // Shoukaku
+ this.player.players.get("69696969696969").shoukaku.setFilters({lowPass: {smoothing: 2}}) // Kazagumo
// Set volume, use Kazagumo's for smoother volume
- this.player.players.get("69696969696969").setVolume(1) // Shoukaku 100% volume
+ this.player.players.get("69696969696969").setVolume(100) // Kazagumo 100% volume
// Skip the current song
- this.player.players.get("69696969696969").stopTrack() // Stoptrack basically skip on shoukaku
+ this.player.players.get("69696969696969").skip() // skip on kazagumo. easier to find :vKazagumo support server: https://discord.gg/nPPW2Gzqg2 (anywhere lmao)
Shoukaku support server: https://discord.gg/FVqbtGu (#development)
Report if you found a bug here https://github.com/Takiyo0/Kazagumo/issues/new/choose
import {Kazagumo, Payload, Plugins} from "kazagumo";
const kazagumo = new Kazagumo({
...,
plugins: [new Plugins.PlayerMoved(client)]
}, Connector, Nodes, ShoukakuOptions)const {Client, GatewayIntentBits} = require('discord.js');
const {Guilds, GuildVoiceStates, GuildMessages, MessageContent} = GatewayIntentBits;
const {Connectors} = require("shoukaku");
const {Kazagumo, KazagumoTrack} = require("../dist");
const Nodes = [{
name: 'owo',
url: 'localhost:2333',
auth: 'youshallnotpass',
secure: false
}];
const client = new Client({intents: [Guilds, GuildVoiceStates, GuildMessages, MessageContent]});
const kazagumo = new Kazagumo({
defaultSearchEngine: "youtube",
// MAKE SURE YOU HAVE THIS
send: (guildId, payload) => {
const guild = client.guilds.cache.get(guildId);
if (guild) guild.shard.send(payload);
}
}, new Connectors.DiscordJS(client), Nodes);
client.on("ready", () => console.log(client.user.tag + " Ready!"));
kazagumo.shoukaku.on('ready', (name) => console.log(`Lavalink ${name}: Ready!`));
kazagumo.shoukaku.on('error', (name, error) => console.error(`Lavalink ${name}: Error Caught,`, error));
kazagumo.shoukaku.on('close', (name, code, reason) => console.warn(`Lavalink ${name}: Closed, Code ${code}, Reason ${reason || 'No reason'}`));
kazagumo.shoukaku.on('debug', (name, info) => console.debug(`Lavalink ${name}: Debug,`, info));
kazagumo.shoukaku.on('disconnect', (name, count) => {
const players = [...kazagumo.shoukaku.players.values()].filter(p => p.node.name === name);
players.map(player => {
kazagumo.destroyPlayer(player.guildId);
player.destroy();
});
console.warn(`Lavalink ${name}: Disconnected`);
});
kazagumo.on("playerStart", (player, track) => {
client.channels.cache.get(player.textId)?.send({content: `Now playing **${track.title}** by **${track.author}**`})
.then(x => player.data.set("message", x));
});
kazagumo.on("playerEnd", (player) => {
player.data.get("message")?.edit({content: `Finished playing`});
});
kazagumo.on("playerEmpty", player => {
client.channels.cache.get(player.textId)?.send({content: `Destroyed player due to inactivity.`})
.then(x => player.data.set("message", x));
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 kazagumo.createPlayer({
guildId: msg.guild.id,
textId: msg.channel.id,
voiceId: channel.id,
volume: 40
})
let result = await kazagumo.search(query, {requester: msg.author});
if (!result.tracks.length) return msg.reply("No results found!");
if (result.type === "PLAYLIST") player.queue.add(result.tracks); // do this instead of using for loop if you want queueUpdate not spammy
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}`});
}
if (msg.content.startsWith("!skip")) {
let player = kazagumo.players.get(msg.guild.id);
if (!player) return msg.reply("No player found!");
player.skip();
log(msg.guild.id);
return msg.reply({content: `Skipped to **${player.queue[0]?.title}** by **${player.queue[0]?.author}**`});
}
if (msg.content.startsWith("!forceplay")) {
let player = kazagumo.players.get(msg.guild.id);
if (!player) return msg.reply("No player found!");
const args = msg.content.split(" ");
const query = args.slice(1).join(" ");
let result = await kazagumo.search(query, {requester: msg.author});
if (!result.tracks.length) return msg.reply("No results found!");
player.play(new KazagumoTrack(result.tracks[0].getRaw(), msg.author));
return msg.reply({content: `Forced playing **${result.tracks[0].title}** by **${result.tracks[0].author}**`});
}
if (msg.content.startsWith("!previous")) {
let player = kazagumo.players.get(msg.guild.id);
if (!player) return msg.reply("No player found!");
const previous = player.getPrevious(); // we get the previous track without removing it first
if (!previous) return msg.reply("No previous track found!");
await player.play(player.getPrevious(true)); // now we remove the previous track and play it
return msg.reply("Previous!");
}
})
client.login('');- Force playing song from spotify module (player.play(result.tracks[0]);
result.tracks[0]is from spotify) is currently not working. ONLY WHEN YOU DO player.play(thing), NOT player.play() OR player.queue.add(new KazagumoTrack(...)) Please use this workaround
const {KazagumoTrack} = require("kazagumo"); // CommonJS
import {KazagumoTrack} from "kazagumo"; // ES6; don't laugh if it's wrong
let track = result.tracks[0] // the spotify track
let convertedTrack = new KazagumoTrack(track.getRaw()._raw, track.author);
player.play(convertedTrack);
- Deivu as the owner of Shoukaku
> Github: https://github.com/Deivu
>- Takiyo as the owner of this project
> Github: https://github.com/Takiyo0
