Skip to content

Repository files navigation

Pixel

Pixel is a helpful assistant bot for members and visitors of the Pixelbar hackerspace in Rotterdam.

  • Ask whether the space is open, see upcoming events and get info about Pixelbar.
  • Get an announcement when the space opens or closes.
  • What you can do depends on your tier: guest, friend, member or admin.

Pixel starts on Discord. Its core doesn't depend on any platform, so other platforms (interactive ones like Telegram, outbound-only ones like Mastodon) can be added later as separate adapters.

Status: phase 1 in progress. The core, the access lists and the Discord adapter work, with /help, /ping, /status (live from SpaceAPI), /whoami and /admin (status, reload, level set, level get, sync, capabilities), plus open/closed announcements in Discord, /events (from the server's scheduled events), /info (short answers about Pixelbar) and /ha (list, status, set: the Home Assistant devices you may see, and switching lights and switches on and off if you hold the capability). See docs/architecture.md and docs/identity-and-access.md. To hide admin commands from other people in Discord, see docs/discord-command-visibility.md.

Stack

Concern Choice
Language TypeScript on Node.js LTS (ESM)
Discord discord.js
Error reporting Sentry
Logging pino (JSON to stdout)
Task runner just
Infrastructure Terraform → Azure (later)
Images GitHub Container Registry (later)
Tooling pnpm, Vitest, Biome, zod

Getting started

Prerequisites

  • Node.js (the version in .nvmrc), with pnpm through corepack enable
  • just

Setup

pnpm install
cp .env.example .env                                # fill in the values
cp config/admins.example.yaml config/admins.yaml    # add yourself
cp config/members.example.yaml config/members.yaml
just validate-config
just register                                       # register slash commands on your test guild
just dev                                            # run Pixel with hot reload

Your own dev bot

Never develop against the production bot.

  1. Create an application at https://discord.com/developers/applications.
  2. Under Bot, reset the token and put it in DISCORD_TOKEN. Put the application ID in DISCORD_APP_ID.
  3. Create a test server, invite the bot with the bot and applications.commands scopes, and put the server ID in DISCORD_GUILD_ID.
  4. Turn on Developer Mode in Discord, right-click yourself, choose Copy User ID, and add yourself to config/admins.yaml and to config/members.yaml (admins are members too).

Announcements

Pixel can post when Pixelbar opens or closes, in two styles. Each has its own channel setting, you can use either, both or neither, and they can be the same channel:

  • Live (DISCORD_ANNOUNCE_LIVE_CHANNEL_ID): opening makes a "🟢 Pixelbar is open" post. Closing edits that same post to "🔴 Pixelbar is closed, was open from … to …". Opening again makes a new post, so a closed post never flips back.
  • Timeline (DISCORD_ANNOUNCE_TIMELINE_CHANNEL_ID): a new post for every open and every close, never edited. Good for a status-only channel where you want a log of exactly when it opened and closed.

A change is posted once it has held for two checks in a row (about 30–60 seconds), so flicking the switch doesn't flood the channel. Nothing is posted when Pixel starts.

Give the bot these permissions in each channel: View Channel, Send Messages and Embed Links, plus Read Message History for the live style. Pixel checks this at startup and tells you in the logs if something is missing.

Editing the /info topics

/info answers from markdown files in content/info/, one per topic. You can edit them right in GitHub, and you don't need to know any code. A file looks like this:

---
title: Becoming a member
summary: Member and Friend memberships, what they cost and how to join
order: 30
---
The text of the answer, in markdown…
  • The file name is the topic's name (membership.md): lowercase letters, digits and dashes.
  • {{announcements-channel}} in the text becomes a link to the announcements channel (set by DISCORD_ANNOUNCEMENTS_CHANNEL_ID). It's the only placeholder so far, and a misspelt one fails the checks.
  • Keep answers short and link to the full page on the website, which stays the source of truth for prices, rules and opening times.
  • This repository is public. Never put passwords, door codes or personal details in these files.
  • A broken file fails the checks on your pull request, with a message saying what's wrong.
  • Changes go live on the next deploy. Adding, removing or renaming a topic also needs just register.

Access lists

Tiers come from two YAML files. They are gitignored, because they contain personal data.

  • config/admins.yaml: Pixel admins, each with ids like ["discord:<id>"] (the same shape as members.yaml). Each admin also needs a members.yaml entry holding the same ids.
  • config/members.yaml: paying member and friend memberships (Discord ID and tier), plus optional capabilities. Pixel rewrites this file when admins change tiers, keeping a .bak of the previous version, so it needs a writable location. Hand edits are still fine, and /admin reload picks them up.

Everyone else is a guest.

Discord IDs must be quoted strings, because unquoted numbers lose precision. Pixel won't start if a file is missing or invalid. Run just validate-config to check the files.

Environment variables

These are validated at startup. See .env.example and the full list.

Variable Description
DISCORD_TOKEN Bot token
DISCORD_APP_ID Application ID
DISCORD_GUILD_ID The one guild Pixel serves
DISCORD_ANNOUNCEMENTS_CHANNEL_ID Optional. Where announcements and the weekly poll are posted. /info points people at it
DISCORD_ANNOUNCE_LIVE_CHANNEL_ID Optional. Channel for the live style: one post per opening, edited to "closed" when the space closes
DISCORD_ANNOUNCE_TIMELINE_CHANNEL_ID Optional. Channel for the timeline style: a new post for every open and every close, never edited
DISCORD_ROLE_MEMBER, DISCORD_ROLE_FRIEND Optional. A Discord role (name or ID) that each level is mirrored to. Unset means not mirrored
HOME_ASSISTANT_URL, HOME_ASSISTANT_TOKEN Optional, set both or neither. How Pixel reaches Home Assistant (for example the Nabu Casa URL) and a long-lived token from a non-admin HA user
PIXEL_HOME_ASSISTANT_DIR Optional. Where devices.yaml (the Home Assistant device allow-list) lives. Defaults to config/home-assistant/. Copy devices.example.yaml there when you set up Home Assistant
PIXEL_HOME_SYNC_MINUTES Optional. How often (minutes) Pixel refreshes inventory.yaml, a list of everything Home Assistant has so you can copy devices into devices.yaml. It is not an allow-list. Defaults to 60, 0 for only at startup and on /admin reload
SPACEAPI_URL Optional. Defaults to https://spaceapi.pixelbar.nl/
PIXEL_DATA_DIR Optional. Where Pixel keeps small bits of state (space.state, announcements.state). Defaults to data/
PIXEL_TIMEZONE Optional. The time zone /events shows times in. Defaults to Europe/Amsterdam
PIXEL_CONTENT_DIR Optional. Where the /info topics live (info/*.md). Defaults to content
SENTRY_DSN Optional. Error reporting and Sentry Logs are off if unset
PIXEL_LOG_DIR Optional. Where the rotating log file goes (about two weeks, JSON lines). Defaults to data/logs/. Empty turns the file off. Logs also go to the console and to Sentry Logs

Common commands

Run just to list every recipe.

Command What it does
just dev Run Pixel locally with hot reload
just check Lint, type-check, test and enforce coverage (what CI runs)
just test Run the tests
just coverage Run the tests with coverage; fails below the thresholds in vitest.config.ts
just build / just start Compile to dist/ and run the build
just fmt Auto-format
just validate-config Validate the access list files
just register Register slash commands with Discord
just command-access Show admin-tier commands to the admins in admins.yaml (see docs/discord-command-visibility.md)
just docker-build Build the container image

CI

Every pull request and push to main runs CI. It runs just check (lint, type-check, tests with coverage thresholds) and the production build, and checks that the Docker image builds.

Deployment

Not set up yet. The plan is a single container on Azure Container Apps, with dev and prod environments provisioned by Terraform. See Deployment.

Running and operating Pixel

The operations runbook covers day-to-day tasks and incidents: access lists, moderation, Home Assistant, rotating secrets, deploys and rollbacks, what to do when the bot is down or answering twice, Sentry, privacy requests and disaster recovery. Anyone who runs Pixel should read it first.

Security

Pixel decides who gets member-level access, so its security matters. If you find a vulnerability, email the board at bestuur@pixelbar.nl instead of opening a public issue.

Contributing

Pixel is a Pixelbar community project. If you use an AI coding agent, point it at AGENTS.md.

License

TBD

About

Pixel, your helpful hacking assistant

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages