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

wavecord

v1.1.0

Published

A modern Discord.js radio client - search 500k+ stations from Radio Browser and stream them into voice channels

Readme

wavecord

A modern Discord radio client for discord.js v14. Search and stream from 500,000+ live radio stations powered by Radio Browser.

Features

  • 500k+ stations - search by name, country, language, genre, codec, and bitrate
  • TypeScript-first - full type exports, no any
  • Multi-guild - independent state per server
  • Auto-reconnect - handles stream interruptions gracefully
  • Volume control - per-guild volume, configurable default
  • Standalone - uses @discordjs/voice directly, no extra frameworks required

Requirements

  • Node.js 18+
  • discord.js v14+
  • @discordjs/voice v0.17+
  • FFmpeg installed and on your PATH

Installation

npm install wavecord

You will also need the following peer dependencies if you don't have them:

npm install @discordjs/voice discord.js

And one of the following Opus encoders:

npm install @discordjs/opus
# or
npm install opusscript

Usage

import { RadioClient } from 'wavecord'

const radio = new RadioClient()

// Search for stations
const stations = await radio.search('lofi')
console.log(stations[0]) // { id, name, url, codec, bitrate, country, ... }

// Play the top result for 'jazz' in a voice channel
const station = await radio.play(voiceChannel, 'jazz')
console.log(`Now playing: ${station.name}`)

// Or pass a Station object directly
const [first] = await radio.search('BBC Radio 1')
await radio.play(voiceChannel, first)

// Volume (0–1)
radio.setVolume(guild.id, 0.5)

// Stop and disconnect
radio.stop(guild.id)       // stop stream, stay in channel
radio.disconnect(guild.id) // stop stream and leave

Advanced Search

const stations = await radio.search({
  name: 'BBC',
  countryCode: 'GB',
  codec: 'MP3',
  bitrateMin: 128,
  limit: 10
})

All fields in SearchOptions are optional:

| Field | Type | Description | |-------|------|-------------| | name | string | Station name | | country | string | Country name | | countryCode | string | ISO 3166-1 alpha-2 code (e.g. GB, US) | | language | string | Language name | | tag | string | Single genre/tag | | tags | string[] | Multiple tags | | codec | string | Audio codec (e.g. MP3, AAC) | | bitrateMin | number | Minimum bitrate in kbps | | bitrateMax | number | Maximum bitrate in kbps | | limit | number | Max results (default: 5) |

API

new RadioClient(options?)

| Option | Type | Default | Description | |--------|------|---------|-------------| | defaultVolume | number | 1 | Default volume for all streams (0–1) |

Methods

| Method | Returns | Description | |--------|---------|-------------| | search(query, limit?) | Promise<Station[]> | Search for stations | | play(channel, query) | Promise<Station> | Join channel and start streaming | | stop(guildId) | void | Stop stream, stay in channel | | disconnect(guildId) | void | Stop stream and leave channel | | disconnectAll() | void | Leave all channels | | setVolume(guildId, volume) | void | Set volume 0–1 | | getVolume(guildId) | number | Get current volume | | currentStation(guildId) | Station \| null | Currently playing station | | isPlaying(guildId) | boolean | Whether a station is active | | hasConnection(guildId) | boolean | Whether a voice connection exists |

Events

radio.on('stationStart', (station, guildId) => {
  console.log(`[${guildId}] Now playing: ${station.name}`)
})

radio.on('stationEnd', (station, guildId) => {
  console.log(`[${guildId}] Stopped: ${station.name}`)
})

radio.on('error', (error, guildId) => {
  console.error(`[${guildId}] Error:`, error)
})

Example: /radio slash command

import { RadioClient } from 'wavecord'
import { Client, GatewayIntentBits } from 'discord.js'

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

radio.on('stationStart', (station, guildId) => {
  console.log(`[${guildId}] Now playing: ${station.name} (${station.codec} ${station.bitrate}kbps)`)
})

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isChatInputCommand()) return
  if (interaction.commandName !== 'radio') return

  const query = interaction.options.getString('query', true)
  const member = interaction.guild?.members.cache.get(interaction.user.id)
  const voiceChannel = member?.voice.channel

  if (!voiceChannel) {
    return interaction.reply({ content: 'You need to be in a voice channel.', ephemeral: true })
  }

  await interaction.deferReply()

  const stations = await radio.search(query, 5)
  if (!stations.length) {
    return interaction.editReply('No stations found.')
  }

  const station = await radio.play(voiceChannel, stations[0])
  return interaction.editReply(`Now playing: **${station.name}** - ${station.country} (${station.codec} ${station.bitrate}kbps)`)
})

License

MIT