npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hibiki.js

v1.0.3

Published

A powerful, fully-featured Lavalink / NodeLink client for Discord bots. First-class TypeScript support, built-in resolvers, smart search, fairplay queuing, voice receive, and more.

Downloads

20

Readme

Hibiki

A powerful Lavalink & NodeLink client for Discord bots. Built for both beginners who want something that just works and advanced users who need full control.

npm install hibiki.js

Quick Start

import { Hibiki } from "hibiki.js";
import { Client, GatewayIntentBits } from "discord.js";

const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] });

const manager = new Hibiki({
  nodes: [{ host: "localhost", port: 2333, password: "youshallnotpass" }],
  sendPayload: (guildId, payload) => {
    client.guilds.cache.get(guildId)?.shard.send(payload);
  },
  defaultSearchEngine: "ytmsearch",
});

client.once("ready", () => {
  manager.init(client.user!.id);
  console.log("Ready!");
});

client.on("raw", (packet) => {
  if (packet.t === "VOICE_STATE_UPDATE" || packet.t === "VOICE_SERVER_UPDATE") {
    manager.voice.handle(packet);
  }
});

manager.on("trackStart", (player, track) => {
  console.log(`Now playing: ${track.info.title}`);
});

client.login("your-token");

Playing Music

const player = manager.createPlayer({
  guildId: "123456789",
  voiceChannelId: "987654321",
  textChannelId: "111111111",
  selfDeaf: true,
});

player.connect();

// Search and play
const result = await player.search("Never Gonna Give You Up");
if (result.tracks.length) {
  player.queue.add(result.tracks[0]);
  await player.play();
}

// Smart search — finds the best match across engines
const smart = await player.smartSearch("blinding lights weeknd");
if (smart.bestMatch) {
  player.queue.add(smart.bestMatch);
  await player.play();
}

Spotify / Apple Music / Deezer

Hibiki ships built-in resolvers that convert Spotify, Apple Music and Deezer tracks into playable audio using your configured search engines — no separate plugin needed.

import { Spotify, Deezer, Apple } from "hibiki.js";

const spotify = new Spotify({
  clientId: "your-spotify-client-id",
  clientSecret: "your-spotify-client-secret",
  engines: ["ytmsearch", "spsearch"], // tried in order
});

const deezer = new Deezer();
const apple = new Apple();

// Single track
const track = spotify.url("https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT");
player.queue.add(track); // resolves automatically when it's about to play

// Playlist (UnresolvedTrack[] — resolved lazily as they play)
const playlist = await spotify.playlist("https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M");
player.queue.add(playlist);
await player.play();

// Deezer playlist
const deezerPlaylist = await deezer.playlist("https://www.deezer.com/playlist/1234567");
player.queue.add(deezerPlaylist.tracks);

// From title + artist (no API call needed, resolves at play time)
const unresolved = spotify.query("Blinding Lights", "The Weeknd", msg.author);
player.queue.add(unresolved);

Tracks are resolved lazily — only when they're about to play — so adding a 500-track playlist is instant.


Queue

// Add tracks
player.add(track);               // preferred — handles fairplay + emits queueUpdate
player.queue.add(track);         // raw add
player.queue.addNext(track);     // play after current
player.queue.prepend(track);     // insert at position 1

// Inspect
player.queue.current;            // currently playing (may be unresolved)
player.queue.next;               // next up
player.queue.upcoming;           // everything after current
player.queue.previous;           // history (always resolved, newest first)
player.queue.size;               // total including current
player.queue.duration();         // ms, excludes streams

// Manipulate
player.queue.remove(2);          // remove index 2
player.queue.move(3, 1);         // move index 3 to index 1
player.queue.shuffle();
player.queue.clear();
player.queue.clearPast();        // clear history

// Bump a track (or range) to top
await player.bump(5);            // move position 5 to top
await player.bump(3, 6);         // move positions 3–6 to top

// Loop
player.repeat("none");           // "none" | "track" | "queue"

// Fairplay — interleaves tracks so no single user dominates the queue
player.setFairplay(true);
player.fairplaySort();
player.fairplayStats();          // { total, requesters, distribution }

// Listen for any queue change
manager.on("queueUpdate", (player, reason, queue) => {
  // reason: "add" | "remove" | "move" | "shuffle" | "clear" | "fairplay" | "bump"
  updateNowPlayingMessage(player);
});

Player Controls

await player.pause();
await player.resume();
await player.skip();
await player.previous();         // go back in history
await player.stop();             // stop + clear queue
await player.seek(30000);        // seek to 30s
await player.forward(10000);     // forward 10s
await player.rewind(10000);      // rewind 10s
await player.replay();           // restart current track
await player.vol(80);            // 0–1000
player.grab();                   // get current track

// 24/7 mode
player.set24(true);

// Autoplay
player.setAutoplay(true);

// Move to a different node
await player.moveNode("node-2");

Filters

import { FilterBuilder } from "hibiki.js";

await player.filter(new FilterBuilder().bassBoost(2));
await player.filter(new FilterBuilder().nightcore());
await player.filter(new FilterBuilder().vaporwave());
await player.filter(new FilterBuilder().eightD());
await player.filter(new FilterBuilder().soft());
await player.filter(new FilterBuilder().trebleBass());
await player.filter(new FilterBuilder().television());

// Custom
await player.filter(
  new FilterBuilder()
    .setTimescale({ speed: 1.1, pitch: 1.05 })
    .setEqualizer([{ band: 0, gain: 0.3 }, { band: 1, gain: 0.2 }])
);

await player.unfilter();

NodeLink Extras

// Lyrics — real-time line-by-line
await player.lyricsOn();
manager.on("lyricsLine", (player, line) => console.log(line.line));
manager.on("lyricsNotFound", (player) => console.log("No lyrics"));

// Or fetch full lyrics at once
const lyrics = await player.lyrics();

// YouTube chapters
const chapters = await player.chapters();

// Mix layers — play a second track over the current one
const mix = await player.mix(encodedTrack, 0.5); // volume 0–1
await player.unmix(mix.mixId);

// Fading
await player.fading({
  enabled: true,
  trackStart: { duration: 2000, curve: "linear" },
  trackEnd:   { duration: 3000, curve: "exponential" },
});

// Voice receive
const socket = manager.voiceReceive(guildId);
socket.on("packet", (packet) => { /* raw PCM */ });

// YouTube live chat
const chat = manager.liveChat({ videoId: "dQw4w9WgXcQ" });
chat.on("message", (msg) => console.log(msg));

Events

manager.on("nodeReady",        (node) => {});
manager.on("nodeDisconnect",   (node, code, reason) => {});
manager.on("nodeError",        (node, error) => {});
manager.on("nodeStats",        (node, stats) => {});

manager.on("trackStart",       (player, track) => {});
manager.on("trackEnd",         (player, track, reason) => {});
manager.on("trackError",       (player, track, exception) => {});
manager.on("trackStuck",       (player, track, threshold) => {});

manager.on("queueEnd",         (player) => {});
manager.on("queueUpdate",      (player, reason, queue) => {});
manager.on("playerCreate",     (player) => {});
manager.on("playerDestroy",    (player) => {});
manager.on("playerMove",       (player, oldChannel, newChannel) => {});
manager.on("playerDisconnect", (player, channelId) => {});
manager.on("playerPause",      (player) => {});
manager.on("playerResume",     (player) => {});
manager.on("playerSeek",       (player, oldPos, newPos) => {});
manager.on("volumeChange",     (player, oldVol, newVol) => {});
manager.on("nodeSwitched",     (player, oldNode, newNode) => {});
manager.on("socketClosed",     (player, code, reason, byRemote) => {});
manager.on("fairplayToggled",  (player, enabled) => {});

// Custom autoplay handler
manager.on("autoplay", async (player) => {
  const result = await player.search("chill lofi music");
  if (result.tracks[0]) {
    player.queue.add(result.tracks[0]);
    await player.play();
  }
});

manager.on("debug", (msg) => console.log("[Hibiki]", msg));

Error Handling

import { PlayerError, ErrorCode } from "hibiki.js";

try {
  await player.seek(99999);
} catch (e) {
  if (e instanceof PlayerError) {
    switch (e.code) {
      case ErrorCode.PLAYER_SEEK_STREAM:
        return msg.reply("You can't seek a live stream.");
      case ErrorCode.PLAYER_NOT_PLAYING:
        return msg.reply("Nothing is playing.");
      case ErrorCode.PLAYER_DESTROYING:
        return;
      default:
        console.error(e);
    }
  }
}

Available codes: NOT_INITIATED, ALREADY_INITIATED, NO_NODES, NODE_NOT_FOUND, NODE_ALREADY_EXISTS, NODE_NOT_CONNECTED, PLAYER_DESTROYING, PLAYER_NOT_PLAYING, PLAYER_EMPTY_QUEUE, PLAYER_NO_HISTORY, PLAYER_SEEK_STREAM, PLAYER_SEEK_UNRESOLVED, TRACK_RESOLVE_FAILED, REST_RATE_LIMITED, REST_REQUEST_FAILED, NODELINK_ONLY.


Persistence

import type { PersistenceAdapter, SavedState } from "hibiki.js";
import { createClient } from "redis";

const redis = createClient();
await redis.connect();

const adapter: PersistenceAdapter = {
  async save(guildId, state) {
    await redis.set(`hibiki:${guildId}`, JSON.stringify(state), { EX: 86400 });
  },
  async load(guildId) {
    const raw = await redis.get(`hibiki:${guildId}`);
    return raw ? JSON.parse(raw) : null;
  },
  async delete(guildId) {
    await redis.del(`hibiki:${guildId}`);
  },
  async keys() {
    const keys = await redis.keys("hibiki:*");
    return keys.map((k) => k.replace("hibiki:", ""));
  },
};

const manager = new Hibiki({ persistence: adapter, ...rest });

// Restore players after restart
const voiceMap = new Map([
  ["guild123", { voiceChannelId: "vc456", textChannelId: "txt789" }],
]);
const restored = await manager.restoreAllPlayers(voiceMap);
console.log(`Restored ${restored} players`);

Plugins

import type { Plugin } from "hibiki.js";

const myPlugin: Plugin = {
  name: "my-plugin",

  load(manager) {
    console.log("Plugin loaded!");
  },

  unload(manager) {
    console.log("Plugin unloaded.");
  },

  onTrackStart(player, track) {
    console.log(`[${player.guildId}] Started: ${track.info.title}`);
  },

  onQueueEnd(player) {},

  onNodeDisconnect(node, code, reason) {
    console.warn(`Node ${node.name} disconnected: ${code} ${reason}`);
  },
};

const manager = new Hibiki({ plugins: [myPlugin], ...rest });

manager.loadPlugin(myPlugin);
manager.unloadPlugin("my-plugin");

Shard-Aware Node Routing

const manager = new Hibiki({
  totalShards: 4,
  nodes: [
    { name: "node-shards-0-1", host: "node1.example.com", port: 2333, password: "pass", shards: [0, 1] },
    { name: "node-shards-2-3", host: "node2.example.com", port: 2333, password: "pass", shards: [2, 3] },
  ],
  ...rest,
});

manager.createPlayer({ guildId, voiceChannelId, shardId: 2 });

Custom Queue

import { Queue } from "hibiki.js";
import type { QueueEntry } from "hibiki.js";

class MyQueue extends Queue {
  add(track: QueueEntry | QueueEntry[], position?: number): void {
    super.add(track, position);
  }
}

const manager = new Hibiki({ queueConstructor: MyQueue, ...rest });

Requirements