Skip to content

A Shoukaku wrapper that have built-in queue system and other feature

License

Notifications You must be signed in to change notification settings

Takiyo0/Kazagumo

Repository files navigation

Kazagumo

A Shoukaku wrapper with built in queue system

AppVeyor Downloads npm GitHub contributors GitHub commit activity GitHub last commit NPM

Kazagumo

Kazagumo © Azur Lane

Features:

✓ Built-in queue system
✓ Easy to use
✓ Plugin system
✓ Uses shoukaku v4 + capable of Lavalink v4
✓ Stable 🙏

Note

⚠️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) ⚠️

Documentation

Please read the docs first before asking methods

Kazagumo; https://takiyo0.github.io/Kazagumo
Shoukaku by Deivu; https://deivu.github.io/Shoukaku

Installation

npm i kazagumo

Metadata

version: 3.2.1
pre-release: false
Last build: 9-2-2024 22.13 PM

Environment

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 and TVHTML5EMBEDDED to be removed from oauth clients, since it's the default config
  • ** = to do that, you need to add source option into SearchOptions. Example: kazagumo.search(query, {source: "spsearch:"}); (⚠️you need to include : in the last of spsearch or anything to replace source)

Plugins

npm i kazagumo-spotify

npm i kazagumo-apple

npm i kazagumo-filter

npm i kazagumo-nico

npm i kazagumo-deezer

npm i stone-deezer

Lavalink installation

Basically you can follow this Official Step

Changes v2 -> v3

// 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

Support

⚠️ Please read the docs first before asking question ⚠️

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

Enable playerMoved event

import { Kazagumo, Payload, Plugins } from "kazagumo";

const kazagumo = new Kazagumo({
    ...,
    plugins: [new Plugins.PlayerMoved(client)]
}, Connector, Nodes, ShoukakuOptions)

Example bot

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('');

Known issue

This part should be in kazagumo-spotify but whatever
  • 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);

Contributors