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
✓ Stable 🙏
Please read the docs first before asking methods
Kazagumo; https://takiyo0.github.io/Kazagumo
Shoukaku by Deivu; https://deivu.github.io/Shoukaku
npm i kazagumo
version: 3.2.1
pre-release: false
Last build: 9-2-2024 22.13 PM
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 andTVHTML5EMBEDDED
to be removed from oauth clients, since it's the default config -
**
= to do that, you need to addsource
option intoSearchOptions
. Example:kazagumo.search(query, {source: "spsearch:"});
(⚠️ you need to include:
in the last ofspsearch
or 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 :v
Kazagumo 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