Skip to content

Run the bot

At this phase phpbotscout serve connects to Discord, logs the messages in the channels you name, and does nothing else. It cannot post, moderate, or answer — not because those are switched off, but because the code that would do them is not built.

Before you start

You need the Discord application registered and installed, with the Message Content and Server Members intents enabled. See Register the Discord application.

Configure

Create .phpbotscout.yaml in the working directory, or use any source the config store reads:

discord:
  guilds:
    - space: "1531227937678954747"      # the guild (server) ID
      channels:                          # the allowlist — empty permits nothing
        - "1531227938622800055"
      auth:
        env: DISCORD_PHPBOTSCOUT_TOKEN   # the NAME of the variable, not the token

health:
  host: "127.0.0.1"                      # explicit interface, never 0.0.0.0
  port: 8081

Then export the token itself:

export DISCORD_PHPBOTSCOUT_TOKEN='…'

The credential

auth takes exactly one of three forms, following the toolkit's convention:

Form Meaning
env: NAME the name of an environment variable holding the token — recommended, and the only form permitted under CI
keychain: service/account an OS keychain entry
value: … the token itself; refused when CI=true

Setting two is an error rather than a preference, because guessing which one you meant is the wrong kind of helpful where a secret is concerned.

The allowlist fails closed

An empty channels list permits nothing and is rejected at startup. A watchlist that silently meant everywhere would be the wrong default for reading people's messages.

One guild, for now

guilds is a list, but exactly one entry is accepted. Multi-tenancy is a v1 non-goal; the list shape exists so supporting several later is a configuration change rather than a migration of everyone's config file.

Run

phpbotscout serve

You should see the allowlist confirmed at startup:

INFO starting http server tls=false addr=127.0.0.1:8081
INFO watching channels space=1531227937678954747 channels=[1531227938622800055]

That line is worth reading. It is how you confirm the bot is watching what you meant before it has seen a single message.

Stop it with SIGTERM or Ctrl-C. It closes the gateway and exits cleanly; a second signal forces the exit.

Check it is healthy

Three endpoints, mounted on the health listener:

curl -s localhost:8081/healthz | jq
{
  "overall_healthy": true,
  "services": [
    {"name": "discord-gateway/1531227937678954747", "status": "OK"},
    {"name": "health", "status": "OK"}
  ]
}
Endpoint Answers
/livez should this process be restarted?
/readyz is the bot currently seeing messages?
/healthz both, per service

The distinction matters. A bot that is up but disconnected looks exactly like a quiet channel, so readiness tracks the gateway connection: reconnecting is live but not ready.

The listener binds loopback by default and refuses to start on 0.0.0.0. Binding every interface and relying on a firewall to be correct is how private services become public ones.

What it reads, and what it keeps

Discord's intents are guild-wide, not per-channel. There is no way to ask the gateway for only your allowlisted channels, so the bot receives every message in the guild and discards the ones it must not read. The filtering is necessarily done by the bot.

That makes "what it reads" and "what it keeps" different answers:

Allowlisted channel Any other channel
Received from Discord yes yes
Written to the log yes, content redacted nothing at all
Counted yes one unlabelled counter

Nothing about a message from an unwatched channel is retained — not its content, not its channel, not its author. A per-channel counter would build an activity profile of exactly the channels the allowlist exists to exclude.

Message content that is logged passes through redact first, because it is untrusted input from a public server.

Changing configuration while it runs

Setting Behaviour
discord.guilds[].channels applied immediately
discord.guilds[].space needs a restart
discord.guilds[].auth needs a restart

The allowlist is pure filtering, so it hot-reloads. Identity is not: applying a changed credential live would mean tearing down a working session on a config write, and a typo would take the bot off Discord until somebody noticed — at whatever time the file happened to be edited.

An ignored change is never silent. Editing either logs a warning naming the field:

WARN configuration change requires a restart field=auth

Testing

just test-e2e        # BDD scenarios; no credentials, no network
just test-discord    # forced-disconnect regression test; needs live credentials

test-discord cuts a real TCP connection to prove the session resumes rather than starting fresh. A reconnect that re-identified would drop every message buffered during the gap — for a support bot, questions nobody will ever answer, with no error and nothing to notice. The bot reports that case as a warning and a counter, and deliberately stays ready: the session is usable, and restarting it would not bring the lost messages back.