Bot
extends MessageCommandClient
in package
The bot: one Discord gateway connection, one HTTP client, and however many connectors the application installed.
Extending MessageCommandClient rather than owning one is what lets the
Discord side keep everything that class already provides — prefix handling,
aliases, cooldowns, the command registry — while each connector brings the
equivalent for its own network. None of them know about each other: they are
all fed from one ActionRegistry, and a command written once appears in
every chat.
One client, deliberately
There is exactly one Discord\Http in the process, built by Discord
itself, and every request to Discord goes through it — including the relay's
webhook executions, which are Webhook::execute() and therefore already
routed there. That object holds the per-route rate-limit buckets and the
concurrency cap, so a second client would keep its own empty bucket table and
race the first into 429s against the same token. Connectors are forbidden to
build one; see Connector.
What the buckets cannot do is decline to send. Merging two bots into one halved the budget — two applications with two tokens became one — so relayed traffic is additionally paced per destination channel by OutboundPacer, leaving room for the commands somebody is waiting on.
Tags
Table of Contents
Constants
- BRIDGE_CHECK_DELAY : mixed = 10.0
- How long after startup to check the restored bridges.
- GITHUB : mixed = 'https://github.com/discord-php/DiscordPHP-Bridge'
- Where this lives. Printed into chat, so it has to resolve.
- INTENTS : mixed = \Discord\WebSockets\Intents::GUILDS | \Discord\...
- MESSAGE_CONTENT is privileged; without it every relayed message is empty.
Properties
- $actions : ActionRegistry
- $components : ComponentRouter
- $config : Config
- $connectors : array<string, Connector>
- $cooldowns : Cooldowns
- $declared : array<string|int, mixed>
- Every top-level slash command this build defines; see {@see Support\CommandSync}.
- $delivery : WebhookDelivery|null
- $lastCheck : string|null
- The last bridge check's headline.
- $modules : array<int, Module>
- $modulesBooted : bool
- $pacer : OutboundPacer
- $relay : ChatRelay|null
- $started : bool
- $startupWarnings : array<int, string>
- $store : Store
- $up : array<string, bool>
Methods
- __construct() : mixed
- addConnector() : self
- Installs a connector.
- addModule() : self
- addStartupWarnings() : void
- Records trouble found before the bot had a log or an owner to report it to — reading a damaged store, most obviously.
- components() : ComponentRouter
- connector() : Connector|null
- One connector by name, or `null` when it is not installed.
- connectors() : array<string, Connector>
- cooldowns() : Cooldowns
- One clock for every surface, so a cooldown cannot be dodged by switching chats.
- declareCommand() : void
- Records a top-level command name this build defines, so the prune pass spares it.
- declaredCommands() : array<int, string>
- delivery() : WebhookDelivery
- getActions() : ActionRegistry
- getConfig() : Config
- getLastCheck() : string|null
- The last bridge check's headline, or `null` before it has run.
- getStore() : Store
- isUp() : bool
- Whether a connector has started and not failed.
- modules() : array<int, Module>
- notifyOwner() : void
- Tells a human, when one is configured, rather than only the log.
- pacer() : OutboundPacer
- rememberCheck() : void
- What the last bridge check found.
- shutdown() : void
- Flushes state and disconnects every connector.
- sync() : void
- Brings one connector's memberships in line with the routing table. Safe to call before it has connected; it becomes a no-op there.
- bootModules() : void
- guildsWithBridges() : array<int, string>
- Every guild with a bridge on any connector.
- pruneCommands() : void
- Removes global commands this build no longer defines.
- reportRestoredBridges() : void
- Reports what was restored from disk, and whether it still works.
- start() : void
- Wires up Discord once it is ready, then connects every connector.
- startConnector() : PromiseInterface<string|int, bool>
- Starts one connector, then joins its rooms.
- verifyBridges() : void
- Probes every restored bridge, then says what is wrong with which.
Constants
BRIDGE_CHECK_DELAY
How long after startup to check the restored bridges.
public
mixed
BRIDGE_CHECK_DELAY
= 10.0
Long enough for the guild caches to fill and the joins to land.
GITHUB
Where this lives. Printed into chat, so it has to resolve.
public
mixed
GITHUB
= 'https://github.com/discord-php/DiscordPHP-Bridge'
INTENTS
MESSAGE_CONTENT is privileged; without it every relayed message is empty.
public
mixed
INTENTS
= \Discord\WebSockets\Intents::GUILDS | \Discord\WebSockets\Intents::GUILD_MESSAGES | \Discord\WebSockets\Intents::MESSAGE_CONTENT
Properties
$actions read-only
private
ActionRegistry
$actions
$components read-only
private
ComponentRouter
$components
$config read-only
private
Config
$config
$connectors
private
array<string, Connector>
$connectors
= []
keyed by Connector::name()
$cooldowns read-only
private
Cooldowns
$cooldowns
$declared
Every top-level slash command this build defines; see {@see Support\CommandSync}.
private
array<string|int, mixed>
$declared
= []
$delivery
private
WebhookDelivery|null
$delivery
= null
$lastCheck
The last bridge check's headline.
private
string|null
$lastCheck
= null
$modules
private
array<int, Module>
$modules
= []
$modulesBooted
private
bool
$modulesBooted
= false
$pacer read-only
private
OutboundPacer
$pacer
$relay
private
ChatRelay|null
$relay
= null
$started
private
bool
$started
= false
$startupWarnings
private
array<int, string>
$startupWarnings
= []
Trouble found before there was anywhere to report it.
$store read-only
private
Store
$store
$up
private
array<string, bool>
$up
= []
connector name => whether it started
Methods
__construct()
public
__construct(Config $config, Store $store[, array<string|int, mixed> $options = [] ]) : mixed
Parameters
addConnector()
Installs a connector.
public
addConnector(Connector $connector) : self
Booted straight away rather than at ready, because whatever it
registers has to be in place before the first message arrives; it is
connected later, in start().
Parameters
- $connector : Connector
Return values
selfaddModule()
public
addModule(Module $module) : self
Parameters
- $module : Module
Return values
selfaddStartupWarnings()
Records trouble found before the bot had a log or an owner to report it to — reading a damaged store, most obviously.
public
addStartupWarnings(string $what, array<int, string> $warnings) : void
Parameters
- $what : string
- $warnings : array<int, string>
components()
public
components() : ComponentRouter
Return values
ComponentRouterconnector()
One connector by name, or `null` when it is not installed.
public
connector(string $name) : Connector|null
Parameters
- $name : string
Return values
Connector|nullconnectors()
public
connectors() : array<string, Connector>
Return values
array<string, Connector>cooldowns()
One clock for every surface, so a cooldown cannot be dodged by switching chats.
public
cooldowns() : Cooldowns
Return values
CooldownsdeclareCommand()
Records a top-level command name this build defines, so the prune pass spares it.
public
declareCommand(string $name) : void
Parameters
- $name : string
declaredCommands()
public
declaredCommands() : array<int, string>
Return values
array<int, string>delivery()
public
delivery() : WebhookDelivery
Return values
WebhookDeliverygetActions()
public
getActions() : ActionRegistry
Return values
ActionRegistrygetConfig()
public
getConfig() : Config
Return values
ConfiggetLastCheck()
The last bridge check's headline, or `null` before it has run.
public
getLastCheck() : string|null
Worth showing next to the bridges themselves: one whose Discord channel or remote room went away while the bot was down reads exactly like a working one from a listing alone.
Return values
string|nullgetStore()
public
getStore() : Store
Return values
StoreisUp()
Whether a connector has started and not failed.
public
isUp(string $name) : bool
Parameters
- $name : string
Return values
boolmodules()
public
modules() : array<int, Module>
Return values
array<int, Module>notifyOwner()
Tells a human, when one is configured, rather than only the log.
public
notifyOwner(string $markdown) : void
A bot that needs attention and only whispers it into a logfile stays broken until somebody happens to look.
Parameters
- $markdown : string
pacer()
public
pacer() : OutboundPacer
Return values
OutboundPacerrememberCheck()
What the last bridge check found.
public
rememberCheck(string $summary) : void
Parameters
- $summary : string
shutdown()
Flushes state and disconnects every connector.
public
shutdown() : void
For a signal handler: once the loop stops a queued write would never run, and the change somebody just made would be the one lost.
sync()
Brings one connector's memberships in line with the routing table. Safe to call before it has connected; it becomes a no-op there.
public
sync(string $connector) : void
Parameters
- $connector : string
bootModules()
private
bootModules() : void
guildsWithBridges()
Every guild with a bridge on any connector.
private
guildsWithBridges() : array<int, string>
Return values
array<int, string>pruneCommands()
Removes global commands this build no longer defines.
private
pruneCommands(bool $everyConnectorStarted) : void
Renaming a command is two operations and only one of them is obvious:
publishing /twitch leaves the /relay it replaced sitting in every
server's command list, pointing at a handler that is gone. That reads as
a broken bot rather than a renamed one.
Only ever run when every connector started. One that failed to boot never declared its commands, and pruning against an incomplete list would delete the working commands of whichever package happened to be unlucky — a far worse outcome than leaving a stale name for one restart.
Parameters
- $everyConnectorStarted : bool
reportRestoredBridges()
Reports what was restored from disk, and whether it still works.
private
reportRestoredBridges() : void
Configuration outlives the process, and both ends of a bridge can stop working while the bot is down — a Discord channel deleted, the bot removed from the server, a room renamed. None of that produces an error at startup; it produces a bridge that quietly relays nothing, which looks exactly like a quiet day.
start()
Wires up Discord once it is ready, then connects every connector.
private
start() : void
Discord's half goes first and does not wait. The catalogue is complete the moment the connectors are installed — each adds its actions then — so there is nothing to wait for, and waiting would let a network that is slow to come up, or never does, hold Discord's commands hostage. An operator has to be able to ask what went wrong from somewhere.
Each connector then starts on its own. One that fails does not take the others with it; its rooms are simply never joined, and the bridge check says so.
startConnector()
Starts one connector, then joins its rooms.
private
startConnector(string $name, Connector $connector) : PromiseInterface<string|int, bool>
Joining waits for the start rather than racing it: a connector cannot join anything before it has a connection to join with, and one that tried would report every room as missing.
Parameters
- $name : string
- $connector : Connector
Return values
PromiseInterface<string|int, bool> —Whether it came up. Never rejects.
verifyBridges()
Probes every restored bridge, then says what is wrong with which.
private
verifyBridges() : void
A connector that is down is reported once, as itself, rather than as a dozen separate bridges that each "cannot hear their room" — the dozen would be true and would hide the one thing worth fixing. Bridges for a connector that is not installed at all are reported the same way: they are kept, but nothing is relaying them.