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

busybar-discord

v1.4.0

Published

Your Discord voice channel on a BUSY Bar: everyone's avatar in a row, and a ring that lights on whoever is talking

Readme

busybar-discord

[!IMPORTANT] Unofficial community project. Built and maintained by @viirtualp1, not an official Flipper Devices / BUSY product, and not affiliated with, endorsed by, or supported by them. "BUSY Bar" remains their trademark. Nor is it affiliated with Discord Inc. For the real hardware and official apps, visit busy.app.

Your voice channel, on the strip. Everyone in the call is a circle with their own face in it, and the circle round whoever is talking lights up.

  ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮      green: talking
  │ ● │ │   │ │   │ │   │ │   │      red:   muted
  ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯      dim:   listening
        72 × 16, and nothing else

The Bar's START button mutes and unmutes your microphone.

No bot, no server

This does not join your Discord server and there is nothing to invite. It talks to the Discord desktop app already running on this machine, over the local socket it opens for overlays — the same one Streamkit uses. Discord tells it who is in the call and who is speaking; nothing goes near your audio.

Two consequences worth knowing before you start:

  • The desktop app has to be running, on this machine. No client, no picture — the display says so and waits.
  • RPC permissions are only granted to the account that owns the application. Discord whitelists these scopes per app, and the owner is always on the list for their own. So make the application under the account that sits in the call, and it works; hand it to somebody else and it will not.

Getting it going

npm install
cp .env.example .env

Make an application at discord.com/developers/applications. It needs no bot and no permissions. From its OAuth2 page copy the client id and client secret into .env, and add http://localhost to its redirects.

npm run login    # approve the dialog Discord puts up, once
npm start

npm run login writes discord-token.json beside the app and the token is refreshed from then on, so the dialog happens once rather than every start.

The row

The faces size themselves, the way a flex row does. Every circle has the same basis — fourteen pixels, which is the largest square a sixteen-pixel strip has room for — and when the row gets crowded the gap closes first, then the circles shrink together.

| In the call | Circle | Gap | | ----------- | ------ | --- | | 1 | 14px | — | | 2 | 14px | 3px | | 3 | 14px | 3px | | 4 | 14px | 3px | | 5 | 13px | 1px | | 6 | 11px | 1px | | 7 | 9px | 1px |

Nine pixels is the floor: below that a face is a smudge. Anyone past MAX_AVATARS is counted at the end of the strip instead — +3 — rather than being squeezed in.

The row is ordered by who arrived, never by who is talking. Sorting by speech would be livelier and much worse: the circles would reshuffle mid-conversation and you would lose track of which one is which person, which is the only thing the display is for.

Seeing it without five friends

npm run shot -- --all          # one PNG per size, 1 through 7
npm run shot -- --people 5 --speaking 2

No device and no Discord involved — it writes the exact frame the Bar would draw into preview/.

The ring

| Colour | | | -------- | -------------------------------------- | | green | talking | | red | muted, by themselves or by the server | | dark red | deafened — they cannot hear you either | | dim grey | in the call, listening |

The outline is one pixel, always. Colour is what carries the meaning, so a thicker line says nothing extra and takes the space out of the face.

The ring stays on for 350ms after speech stops. Discord reports every gap between words, and without the hold the ring strobes at conversational speed, which is unreadable and unpleasant to sit beside.

The circle is a rectangle with its corner radius set to half its side, which is how the device is asked for a circle. It is drawn after the avatar on purpose: an avatar is a square picture and the ring is round, so the picture's corners fall outside the outline and would cut four bites out of it. The corners are also left transparent, which fixes the same thing a second way, on a device that honours an alpha channel in an uploaded asset.

The button

START toggles your microphone, and that is not a setting. busybar-wm takes OK to cycle apps and BACK to hand the choice back, so START is the only button left over — binding either of the others only breaks the window manager.

[!WARNING] busybar-nowplaying also binds START, to play/pause. Every app behind the proxy sees every button, so if you run both, one press does both things.

BAR_INPUT=0 leaves your microphone alone entirely.

What it asks of the Bar

The device is a small embedded thing on the end of Wi-Fi, and a voice channel is a firehose: five people interrupting each other produce speaking events many times a second, and every one of them changes the picture. Three limits keep that from turning into a denial of service:

  • At most one draw every 250ms. Anything that happens in between replaces what is queued rather than adding to it, so the frame that goes out is still the newest one. This is a rate limit on the device, not a frame rate.
  • Uploads go up one at a time. A full call is five pictures, all wanted in the same instant at startup. Sent together, that is five concurrent uploads at a device that is also being drawn on for the first time.
  • The screen is only wiped when somebody leaves. Elements persist by id, so a disappearance needs a clear — but an arrival does not, and the avatars all arrive one upload apart.

The back panel is not used at all, and that is the fourth limit. Drawing it meant the same faces again plus their names — twenty-six elements a frame instead of ten, and twice the pictures for the device to hold — for a surface nobody looks at unless the Bar is turned round. It was enough to wedge the Bar outright, so it is gone rather than optional.

If the device still struggles, MAX_AVATARS=3.

Behind busybar-wm

Point BUSY_ADDR at the daemon and leave the credentials empty:

BUSY_ADDR=http://127.0.0.1:4111

Nothing else changes. A rank around 45 suits it — a conversation is worth interrupting the clock or the album cover for, and not worth interrupting a live match for.

In a profile the app is a package rather than a checkout, so npm run login is not there to run. The same step is installed as its own command:

cd ~/.busybar/discord && ../node_modules/.bin/busybar-discord-login

It has to run from the app's own directory, because that is where its .env is and where the token is written.

busybar-config picks the app up with nothing added: it scans the profile's packages for anything carrying a busybar field, and finds the spec this one ships. There is no list of apps anywhere to add to.

Configuration

Everything is in .env.example. The ones worth knowing:

| | | | ----------------------- | ------------------------------------------------------- | | DISCORD_CLIENT_ID | The application you made. Required. | | DISCORD_CLIENT_SECRET | Beside it on the same page. Required. | | MAX_AVATARS | How many faces at once. Default 5. | | HIDE_BOTS | A music bot is in the channel, not in the conversation. | | AVATAR_GAIN | Photographs are dark at twelve pixels. Default 1.15. |

The app also ships a machine-readable description of all of this, so busybar-deck can offer the settings without knowing anything about Discord.

Avatars

Fetched from Discord's CDN once, circled, and kept — on disk in avatars/ and on the device as assets. Steady state touches neither: the ring changes colour many times a minute, the picture behind it does not change at all.

One file per person, named by the avatar hash, so a new avatar is a new file and the set stays bounded by the people you actually talk to.

Layout

src/discord/   ipc (the pipe), rpc (commands and events), auth (the one
               dialog), voice (who is in the call), client (all of it, wired)
src/avatars/   fetching, circling, caching, uploading
src/view/      layout (the flex row), frame (what to show), elements (as the
               device wants it)
src/bar/       the display, kept in step

voice.ts and layout.ts are pure and have no idea Discord or a Bar exist, which is why the two things most likely to be wrong — how a room behaves as people join and talk over each other, and whether five faces fit — are testable without either.

License

MIT