WebhookDelivery
in package
Posts another network's chat into Discord.
Uses a webhook so each relayed line carries the speaker's own name and
avatar instead of arriving as a wall of identical bot messages. That is not
only cosmetic: webhook messages carry a webhook_id, which is how the relay
recognises its own output and refuses to send it back out again.
Webhooks need Manage Webhooks. When that is missing — or creation fails for any other reason — delivery falls back to an ordinary bot message with the author's name inline, so a misconfigured server degrades to an uglier bridge rather than a silent one.
Staying inside Discord's budget
Two things here exist because of the rate limits rather than the feature:
- Every send goes through OutboundPacer. One message on a busy network fans out to every Discord channel following that room, and with more than one connector installed they all draw on one token's budget.
- A channel we cannot create a webhook in is remembered. Without that memo, a server that never granted Manage Webhooks costs one rejected request per relayed message — and 10,000 rejections in ten minutes is a Cloudflare ban on the whole host, not a throttle.
Nothing here builds an HTTP client. Webhook::execute() and
Channel::sendMessage() both go through the one Discord\Http the bot owns,
which is where the per-route buckets live.
Tags
Table of Contents
Constants
- USERNAME_LIMIT : mixed = 80
- Discord rejects a webhook username longer than this.
- VIA_BOT : mixed = 'bot'
- VIA_WEBHOOK : mixed = 'webhook'
- How a delivered copy was posted, which decides how it can be edited.
Properties
- $cache : array<string, Webhook|false>
- $discord : Discord
- $pacer : OutboundPacer
- $webhookName : string
Methods
- __construct() : mixed
- deliver() : PromiseInterface<string|int, array{message_id: string, via: string}>
- Delivers one message to one Discord channel, and says where it landed.
- edit() : PromiseInterface
- Rewrites a copy this delivery posted, to what the original now says.
- forget() : void
- Forgets a channel's cached webhook — call when delivery starts failing.
- queued() : int
- How many deliveries are waiting on Discord's budget.
- safeUsername() : string
- Discord rejects webhook usernames containing "discord", and caps them at 80 characters. A display name from another network can be either, and the bridge should not drop a message over it.
- body() : MessageBuilder
- The part of a message both paths share.
- create() : PromiseInterface<string|int, Webhook>
- escape() : string
- Neutralises Discord markdown in a name shown in the fallback path.
- fallback() : PromiseInterface<string|int, array{message_id: string, via: string}>
- An ordinary bot message, for a channel the bot cannot create a webhook in. Uglier — every line carries the bot's name and picture, so the author has to be written into the text — but never silent.
- webhookFor() : PromiseInterface<string|int, Webhook>
Constants
USERNAME_LIMIT
Discord rejects a webhook username longer than this.
public
mixed
USERNAME_LIMIT
= 80
VIA_BOT
public
mixed
VIA_BOT
= 'bot'
VIA_WEBHOOK
How a delivered copy was posted, which decides how it can be edited.
public
mixed
VIA_WEBHOOK
= 'webhook'
Properties
$cache
private
array<string, Webhook|false>
$cache
= []
Channel id => webhook, or false when we know we can't have one.
$discord read-only
private
Discord
$discord
$pacer read-only
private
OutboundPacer
$pacer
$webhookName read-only
private
string
$webhookName
= 'Bridge'
Methods
__construct()
public
__construct(Discord $discord, OutboundPacer $pacer[, string $webhookName = 'Bridge' ]) : mixed
Parameters
- $discord : Discord
- $pacer : OutboundPacer
- $webhookName : string = 'Bridge'
deliver()
Delivers one message to one Discord channel, and says where it landed.
public
deliver(Channel $channel, string $author, string|null $text[, string|null $avatarUrl = null ][, string $suffix = '' ][, array{filename: string, content: string}|null $file = null ]) : PromiseInterface<string|int, array{message_id: string, via: string}>
allowed_mentions is empty on every path: this text came from another
network and is untrusted, and somebody typing @everyone there must not
ping a Discord server. Neutering it at the API rather than by mangling
the text means they still read as having typed it.
The webhook is executed with wait, so Discord answers with the message
it created. That id is what lets a later edit on the other network find
this copy; without it every edit would have to be posted as a second
message.
Parameters
- $channel : Channel
- $author : string
- $text : string|null
- $avatarUrl : string|null = null
- $suffix : string = ''
-
Appended to the display name, so it is obvious a relayed line is not a Discord account.
- $file : array{filename: string, content: string}|null = null
-
A file to upload with it.
Return values
PromiseInterface<string|int, array{message_id: string, via: string}>edit()
Rewrites a copy this delivery posted, to what the original now says.
public
edit(Channel $channel, string $messageId, string $via, string $author, string $text[, string $suffix = '' ]) : PromiseInterface
A webhook message is edited through the webhook that posted it; a fallback message through the bot, which authored it. Either way it goes through the pacer, because an edit spends the same channel's budget as a send.
Parameters
- $channel : Channel
- $messageId : string
- $via : string
- $author : string
- $text : string
- $suffix : string = ''
Return values
PromiseInterfaceforget()
Forgets a channel's cached webhook — call when delivery starts failing.
public
forget(Channel $channel) : void
Parameters
- $channel : Channel
queued()
How many deliveries are waiting on Discord's budget.
public
queued() : int
Return values
intsafeUsername()
Discord rejects webhook usernames containing "discord", and caps them at 80 characters. A display name from another network can be either, and the bridge should not drop a message over it.
public
static safeUsername(string $author[, string $suffix = '' ]) : string
Parameters
- $author : string
- $suffix : string = ''
Return values
stringbody()
The part of a message both paths share.
private
body(string|null $text[, array{filename: string, content: string}|null $file = null ]) : MessageBuilder
Parameters
- $text : string|null
- $file : array{filename: string, content: string}|null = null
Return values
MessageBuildercreate()
private
create(Channel $channel) : PromiseInterface<string|int, Webhook>
Parameters
- $channel : Channel
Return values
PromiseInterface<string|int, Webhook>escape()
Neutralises Discord markdown in a name shown in the fallback path.
private
static escape(string $text) : string
Parameters
- $text : string
Return values
stringfallback()
An ordinary bot message, for a channel the bot cannot create a webhook in. Uglier — every line carries the bot's name and picture, so the author has to be written into the text — but never silent.
private
fallback(Channel $channel, string $author, string|null $text, string $suffix, array{filename: string, content: string}|null $file) : PromiseInterface<string|int, array{message_id: string, via: string}>
Parameters
- $channel : Channel
- $author : string
- $text : string|null
- $suffix : string
- $file : array{filename: string, content: string}|null
Return values
PromiseInterface<string|int, array{message_id: string, via: string}>webhookFor()
private
webhookFor(Channel $channel) : PromiseInterface<string|int, Webhook>
Parameters
- $channel : Channel