Skip to content

Live now playing

NowPlayingFeed connects your bot to your website. The site shows your bot (avatar, name, online status) and the song it’s playing right now, with artwork and a live progress bar. It updates the moment a song starts, pauses or ends.

Only the song is shared: no server names, server ids or usernames.

Websites can’t ask Discord what a bot is playing, so your bot shares it:

Lavalink → your bot (Raya + NowPlayingFeed) → your website
GET /now-playing JSON snapshot
GET /now-playing/stream live updates (Server-Sent Events)
  1. Add the feed to your bot. bot is how your bot appears on the site. A function keeps the name and avatar up to date:

    bot.js
    const { NowPlayingFeed } = require('raya.js');
    const feed = new NowPlayingFeed({
    port: 8787,
    cors: 'https://neuzgg.github.io', // your website origin
    bot: () => ({
    name: client.user?.username ?? 'Raya',
    avatar: client.user?.displayAvatarURL({ size: 256 }),
    url: 'https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID', // optional "Add to Discord" button
    }),
    });
    await raya.use(feed);
  2. Check it works. Play something and open http://localhost:8787/now-playing. You should see your bot and the song.

  3. Make it reachable over HTTPS. Websites on HTTPS (like GitHub Pages) can’t load http:// feeds. The easiest way is a Cloudflare Tunnel, which also works if your bot runs at home:

    Terminal window
    cloudflared tunnel --url http://localhost:8787

    It prints a https://….trycloudflare.com address. For a permanent address, use a named tunnel or a reverse proxy (Caddy, Nginx) with your own domain.

  4. Point your website at it. Read the feed with a few lines of JavaScript: see Show it on any website.

While the bot is offline or nothing is playing, the feed says so, so your site never has to show fake data.

If your bot plays in several servers at once, the site keeps showing the same song while it plays, so the card doesn’t jump around. When that song pauses or ends, it switches to the most recently started song. To keep some servers out completely, pass a filter:

new NowPlayingFeed({
bot: () => ({ name: client.user.username, avatar: client.user.displayAvatarURL() }),
nowPlaying: (guildId) => !PRIVATE_SERVERS.has(guildId), // these servers never supply the song
});

To also show individual servers that opt in, add publish. It returns how a server appears, or null to keep it private:

const PUBLIC_SERVERS = new Set(['123456789012345678']);
new NowPlayingFeed({
bot: () => ({ name: client.user.username, avatar: client.user.displayAvatarURL() }),
publish: (guildId) => {
const guild = PUBLIC_SERVERS.has(guildId) && client.guilds.cache.get(guildId);
return guild ? { name: guild.name, icon: guild.iconURL() } : null;
},
});

Leave out port and mount the feed on a server you already run:

const feed = new NowPlayingFeed({ bot });
await raya.use(feed);
app.get(['/now-playing', '/now-playing/stream'], (req, res) => feed.handle(req, res));

You don’t need this docs site. A few lines of plain JavaScript are enough:

<p id="now-playing">Connecting…</p>
<script>
const text = document.getElementById('now-playing');
const feed = new EventSource('https://your-address/now-playing/stream');
feed.addEventListener('snapshot', (event) => {
const { bot, nowPlaying } = JSON.parse(event.data);
text.textContent = nowPlaying
? `${bot.name} is playing ${nowPlaying.track.title} by ${nowPlaying.track.author}`
: `${bot.name} is not playing anything`;
});
feed.addEventListener('error', () => (text.textContent = 'Bot offline'));
</script>

Always set feed data with textContent, not innerHTML, because song titles come from the internet.

{
"bot": {
"name": "Raya",
"avatar": "https://cdn.discordapp.com/avatars/.../....png",
"url": "https://discord.com/oauth2/authorize?client_id=...",
"uptime": 11520000
},
"nowPlaying": {
"track": {
"title": "Blinding Lights",
"author": "The Weeknd",
"uri": "https://open.spotify.com/track/...",
"artworkUrl": "https://i.scdn.co/image/...",
"source": "spotify",
"duration": 200040,
"isStream": false
},
"position": 64250,
"paused": false,
"speed": 1
},
"players": [],
"totals": { "playing": 3 },
"time": 1789000000000
}

nowPlaying is null when nothing is playing. position is the position when the snapshot was sent. To animate a progress bar, add (now - receivedAt) × speed while not paused. A new snapshot arrives on every song change, pause, seek or stop, and when the position drifts by more than 2 seconds.

Option Default Description
bot – { name, avatar?, url? } or a function returning it. Enables the bot and its current song
nowPlaying true with bot false, or (guildId) => boolean to choose which servers can supply the song
publish – Optional per-server cards: (guildId) => { name, icon? } | null
port – Start a standalone server on this port
host all interfaces Interface for the standalone server
path '/now-playing' Snapshot URL; the stream is at path + '/stream'
cors '*' Access-Control-Allow-Origin
interval 2000 ms between checks for pauses, seeks and drift
maxClients 500 Maximum simultaneous stream connections
includeTotals true Include how many servers are playing (a count only)

You need bot, publish, or both.

Member Description
feed.url Standalone server URL once listening
feed.connections Connected stream clients
feed.snapshot() Current data
feed.refresh() Push an update to every client now
feed.handle(req, res) Serve the feed on your own server; returns false for other URLs