Store
in package
Every bridge the bot has, for every connector, in one file.
This is the only thing that remembers what a server configured, so losing it
means every admin has to run link again. JsonFile is what keeps that
from happening — atomic writes, a backup beside it, a damaged file preserved
rather than overwritten, and the write itself kept off the event loop. What
this class adds on top is the shape: bridges are grouped by connector, so
twitch and telegram cannot collide even when a guild bridges the same
Discord channel name on both.
{ "version": 2,
"links": { "twitch": { "<guild>": { "<channel>": "coffeescrafts" } } },
"labels": { "telegram": { "-1001234567890": "My Group" } } }
A file written by the single-platform bots this one replaces has no
version and no connector level; it is migrated on load into whichever
connector the caller names, so an existing installation keeps its bridges
without anyone having to re-run anything.
Entries of the wrong shape are dropped rather than loaded, and what was
dropped is recorded in warnings() — a hand-edited file should not be
able to take the bot down with a TypeError three layers away, nor vanish
silently.
Every mutator hands back a fresh Links, which is what callers act on: the store owns persistence, Links owns routing.
Tags
Table of Contents
Constants
- LEGACY_TITLED : mixed = 'telegram'
- Which connector a legacy file with a `titles` table belongs to.
- VERSION : mixed = 2
- The shape this class writes. Anything older is migrated on load.
Properties
-
$data
: array{version: int, links: array
>>, labels: array >} - $file : JsonFile
- $legacyConnector : string
- $migrated : bool
- $warnings : array<int, string>
Methods
- __construct() : mixed
- connectors() : array<int, string>
- Every connector that has at least one bridge.
- count() : int
- How many bridges exist across every connector.
- filesystem() : Filesystem
- The backend in use, for the startup line.
- flush() : bool
- Writes now, blocking, for shutdown.
- forgetGuild() : Links
- Drops every bridge a guild has configured for one connector.
- label() : string|null
- What a target is called, when the connector has told us.
- link() : Links
- Bridges a Discord channel to a target.
- links() : Links
- One connector's routing table. Empty for a connector with no bridges.
- migrated() : bool
- Whether the file on disk was written by an older version of the bot.
- path() : string
- rememberLabel() : void
- Remembers what a target is called, so a listing can show a name.
- saved() : PromiseInterface
- Resolves when everything changed so far has reached the disk.
- toArray() : array<string, mixed>
- unlink() : Links
- Removes one Discord channel's bridge. No-op when it was not bridged.
- warnings() : array<int, string>
- Anything that went wrong while reading, in the order it was found. Empty on a normal start.
- forgetUnusedLabels() : void
- Forgets the name of a target nothing points at any more.
- keyedByGuild() : bool
- Whether a `links` table is keyed by Discord guild id — the legacy shape — rather than by connector name.
- prune() : void
- Drops a guild, and then a connector, once nothing is left under it.
-
sanitize()
: array{version: int, links: array
>>, labels: array >} - Brings whatever was on disk up to the current shape, dropping what cannot be understood and saying so.
- save() : void
Constants
LEGACY_TITLED
Which connector a legacy file with a `titles` table belongs to.
public
mixed
LEGACY_TITLED
= 'telegram'
Only DiscordPHP-TelegramRelay ever wrote one, so its presence is how that bot's file is told apart from the Twitch bots'. Named here rather than guessed from the targets: a numeric Telegram chat id and an all-digit Twitch login look alike.
VERSION
The shape this class writes. Anything older is migrated on load.
public
mixed
VERSION
= 2
Properties
$data
private
array{version: int, links: array>>, labels: array>}
$data
$file read-only
private
JsonFile
$file
$legacyConnector read-only
private
string
$legacyConnector
= 'twitch'
$migrated
private
bool
$migrated
= false
$warnings
private
array<int, string>
$warnings
= []
Methods
__construct()
public
__construct(string $path[, Filesystem|null $filesystem = null ][, string $legacyConnector = 'twitch' ]) : mixed
Parameters
- $path : string
- $filesystem : Filesystem|null = null
- $legacyConnector : string = 'twitch'
-
Which connector a pre-
versionfile's bridges belong to. The bots this replaces each had exactly one, so the file itself cannot say.
connectors()
Every connector that has at least one bridge.
public
connectors() : array<int, string>
Not the same as the connectors that are registered: a file may name one that is not installed any more, and those bridges are kept rather than quietly dropped.
Return values
array<int, string>count()
How many bridges exist across every connector.
public
count() : int
Return values
intfilesystem()
The backend in use, for the startup line.
public
filesystem() : Filesystem
Return values
Filesystemflush()
Writes now, blocking, for shutdown.
public
flush() : bool
Once the loop stops a queued write would never run, and the change somebody just made would be the one lost.
Return values
boolforgetGuild()
Drops every bridge a guild has configured for one connector.
public
forgetGuild(string $connector, int|string $guildId) : Links
Parameters
- $connector : string
- $guildId : int|string
Return values
Linkslabel()
What a target is called, when the connector has told us.
public
label(string $connector, int|string $target) : string|null
A Telegram chat id says nothing to a human, and the name has to come from an API call the bot may not be able to repeat — it is remembered so a listing does not have to go and ask.
Parameters
- $connector : string
- $target : int|string
Return values
string|nulllink()
Bridges a Discord channel to a target.
public
link(string $connector, int|string $guildId, int|string $discordChannelId, string $target[, string|null $label = null ]) : Links
A Discord channel points at one target per connector, so linking it again replaces the previous one rather than accumulating.
Parameters
- $connector : string
- $guildId : int|string
- $discordChannelId : int|string
- $target : string
- $label : string|null = null
Return values
Linkslinks()
One connector's routing table. Empty for a connector with no bridges.
public
links(string $connector) : Links
Parameters
- $connector : string
Return values
Linksmigrated()
Whether the file on disk was written by an older version of the bot.
public
migrated() : bool
Return values
boolpath()
public
path() : string
Return values
stringrememberLabel()
Remembers what a target is called, so a listing can show a name.
public
rememberLabel(string $connector, int|string $target, string $label) : void
Parameters
- $connector : string
- $target : int|string
- $label : string
saved()
Resolves when everything changed so far has reached the disk.
public
saved() : PromiseInterface
For tests and for anything that genuinely must not continue until the change is durable. Ordinary callers do not wait.
Return values
PromiseInterfacetoArray()
public
toArray() : array<string, mixed>
Return values
array<string, mixed>unlink()
Removes one Discord channel's bridge. No-op when it was not bridged.
public
unlink(string $connector, int|string $guildId, int|string $discordChannelId) : Links
Parameters
- $connector : string
- $guildId : int|string
- $discordChannelId : int|string
Return values
Linkswarnings()
Anything that went wrong while reading, in the order it was found. Empty on a normal start.
public
warnings() : array<int, string>
Return values
array<int, string>forgetUnusedLabels()
Forgets the name of a target nothing points at any more.
private
forgetUnusedLabels() : void
Without this the file grows a label for every chat ever linked, and the names of rooms the bot has long since left outlive the bridges to them.
keyedByGuild()
Whether a `links` table is keyed by Discord guild id — the legacy shape — rather than by connector name.
private
static keyedByGuild(array<string|int, mixed> $links) : bool
Parameters
- $links : array<string|int, mixed>
Return values
boolprune()
Drops a guild, and then a connector, once nothing is left under it.
private
prune(string $connector, string $guildId) : void
Parameters
- $connector : string
- $guildId : string
sanitize()
Brings whatever was on disk up to the current shape, dropping what cannot be understood and saying so.
private
sanitize(array<string, mixed> $data) : array{version: int, links: array>>, labels: array>}
Parameters
- $data : array<string, mixed>
Return values
array{version: int, links: arraysave()
private
save() : void