TwitchGateway
in package
Owns the Twitch side of the bridge: one IRC connection, the set of channels it is joined to, and a paced outbound queue.
One connection serves every guild. Two servers following the same streamer share a single JOIN — Links::targets() is already deduplicated — and sync() moves the connection to a new set of channels with a diff rather than a reconnect, so reconfiguring one guild does not interrupt the others.
Tags
Table of Contents
Constants
- CAPACITY : mixed = 18
- Twitch's chat limit for an account that is not a moderator: 20 messages per 30 seconds, counted across *every* channel it speaks in, not per channel. Kept a little under, since a breach mutes the account.
- ECHO_WINDOW : mixed = 30.0
- How long a sent line is remembered for recognising its echo. Twitch delivers a message to the room in well under a second; this only has to outlast a slow network, not a conversation.
- MAX_QUEUE : mixed = 100
- How much may wait. A busy Discord channel produces more than Twitch will take — Discord allows a message a second, Twitch fewer than one every 1.6 — so without a bound the backlog grows for as long as the conversation lasts, and is still being played out long after it ended.
- PER : mixed = 30.0
Properties
- $budget : RateLimiter
- The account's one send budget.
- $clock : callable(): float
- $draining : bool
- $dropped : int
- How many messages were dropped since the backlog last cleared.
- $joined : array<int, string>
- $logger : LoggerInterface
- $loop : LoopInterface
- $nick : string
- $onChat : callable(ChatMessage): void|null
- $queues : array<string, array<int, array{text: string, replyTo: string|null}>>
- What is waiting, one queue per channel, in the order channels take turns.
- $sent : array<string, array<int, array{text: string, at: float}>>
- What this connection said recently, per channel, oldest first.
- $twitch : Twitch
Methods
- __construct() : mixed
- joined() : array<int, string>
- The channels this connection is currently in, lower-case.
- listen() : void
- Starts listening. Lines the bridge itself sent are dropped here, since relaying them would bounce every Discord message straight back into Discord.
- onChat() : void
- Registers the handler for inbound Twitch chat.
- queued() : int
- How many messages are waiting, for logging and health checks.
- send() : void
- Queues a message for a Twitch channel.
-
sync()
: array{join: list
, part: list } - Brings the joined set in line with the routing table.
- applyJoins() : void
- drain() : void
- Sends whatever the budget allows, one message per channel in turn, then re-arms a timer for the rest. Each channel's own messages stay in order.
- isEcho() : bool
- Whether a message is one of this connection's own lines coming back.
Constants
CAPACITY
Twitch's chat limit for an account that is not a moderator: 20 messages per 30 seconds, counted across *every* channel it speaks in, not per channel. Kept a little under, since a breach mutes the account.
public
mixed
CAPACITY
= 18
ECHO_WINDOW
How long a sent line is remembered for recognising its echo. Twitch delivers a message to the room in well under a second; this only has to outlast a slow network, not a conversation.
public
mixed
ECHO_WINDOW
= 30.0
MAX_QUEUE
How much may wait. A busy Discord channel produces more than Twitch will take — Discord allows a message a second, Twitch fewer than one every 1.6 — so without a bound the backlog grows for as long as the conversation lasts, and is still being played out long after it ended.
public
mixed
MAX_QUEUE
= 100
PER
public
mixed
PER
= 30.0
Properties
$budget read-only
The account's one send budget.
private
RateLimiter
$budget
$clock
private
callable(): float
$clock
$draining
private
bool
$draining
= false
$dropped
How many messages were dropped since the backlog last cleared.
private
int
$dropped
= 0
$joined
private
array<int, string>
$joined
= []
Channels currently joined, lower-case, no leading '#'.
$logger read-only
private
LoggerInterface
$logger
$loop read-only
private
LoopInterface
$loop
$nick read-only
private
string
$nick
$onChat
private
callable(ChatMessage): void|null
$onChat
= null
$queues
What is waiting, one queue per channel, in the order channels take turns.
private
array<string, array<int, array{text: string, replyTo: string|null}>>
$queues
= []
One budget is shared by every channel, so a single queue would let one flooded bridge — or one viewer spamming commands — use all of it and push every other channel's messages out of the backlog. Taking turns gives each channel an equal share whenever there is contention.
$sent
What this connection said recently, per channel, oldest first.
private
array<string, array<int, array{text: string, at: float}>>
$sent
= []
$twitch read-only
private
Twitch
$twitch
Methods
__construct()
public
__construct(Twitch $twitch, LoopInterface $loop, LoggerInterface $logger, string $nick[, RateLimiter|null $budget = null ][, callable(): float|null $clock = null ]) : mixed
Parameters
- $twitch : Twitch
- $loop : LoopInterface
- $logger : LoggerInterface
- $nick : string
- $budget : RateLimiter|null = null
- $clock : callable(): float|null = null
-
Defaults to
microtime(true).
joined()
The channels this connection is currently in, lower-case.
public
joined() : array<int, string>
Re-established on every start from the routing table, so the startup check compares it against what was restored from disk rather than assuming the JOINs landed.
Return values
array<int, string>listen()
Starts listening. Lines the bridge itself sent are dropped here, since relaying them would bounce every Discord message straight back into Discord.
public
listen() : void
Only those lines, though — not everything from this account. The bot speaks as the host's own Twitch account, so a message from it is usually the host typing in chat, and that is exactly what the bridge is for. Twitch does not send a connection its own messages back; a line from this account that matches one just sent is dropped all the same, in case something ever does.
onChat()
Registers the handler for inbound Twitch chat.
public
onChat(callable $handler) : void
Parameters
- $handler : callable
queued()
How many messages are waiting, for logging and health checks.
public
queued() : int
Return values
intsend()
Queues a message for a Twitch channel.
public
send(string $channel, string $text[, string|null $replyTo = null ]) : void
Never sends around the budget: Twitch mutes the account for 30 minutes if the send limit is exceeded, so everything goes through the one account-wide bucket, whichever channel it is for.
Command replies come through here too, not just relayed chat. Both speak as the same account against the same limit, and two senders that each stay under it will still breach it together.
Parameters
- $channel : string
- $text : string
- $replyTo : string|null = null
-
An IRCv3 message id to thread the reply onto.
sync()
Brings the joined set in line with the routing table.
public
sync(Links $links) : array{join: list, part: list}
Parameters
- $links : Links
Return values
array{join: listwhat actually changed
applyJoins()
private
applyJoins(array<int, string> $logins) : void
Parameters
- $logins : array<int, string>
drain()
Sends whatever the budget allows, one message per channel in turn, then re-arms a timer for the rest. Each channel's own messages stay in order.
private
drain() : void
isEcho()
Whether a message is one of this connection's own lines coming back.
private
isEcho(ChatMessage $message) : bool
Each sent line can be matched once, so the host saying the same thing a moment later is still relayed.
Parameters
- $message : ChatMessage