Takiyo0 / Kazagumo

A Shoukaku wrapper that have built-in queue system and other feature
MIT License
56 stars 38 forks source link
lavalink lavalink-client shoukaku wrapper wrapper-library

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:

Plugins

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

Contributors