Repository navigation
Turn the echo bot into a step-by-step Twitch chat lesson (and stop it crashing on PING) - #2
Merged
Merged
Conversation
There is no way to test a Twitch bot without a Twitch account and a token, and we have neither. tools/fake_twitch_server.py speaks just enough of Twitch's IRC dialect (CAP REQ, PASS/NICK, JOIN, PRIVMSG, PING/PONG, RECONNECT, NOTICE) with the reply lines from Twitch's docs. Tests use it as a context manager on a free port. People learning from the repo can run it in a terminal and chat as a viewer by typing "alice: hello". It does not import the bot's code, so a parser bug in the bot can't hide behind the same bug in the server.
The 2019 client read one recv() as one line, cut messages with
split(":"), returned (None, None) for PING and never noticed a closed
connection. twitch_chat.py replaces it with a client meant to be read
top to bottom, in nine commented steps: TLS connect, CAP REQ, PASS and
NICK, JOIN, line buffering, parsing (tags, prefix, command, params),
PING/PONG, sending under Twitch's rate limit, and ConnectionClosed.
It connects to irc.chat.twitch.tv:6697 with TLS by default, waits for
the join with a timeout, turns Twitch's login NOTICE into an
AuthenticationError, hides the token in debug logs and still uses only
the standard library. Tests run against the fake server, including
one per 2019 bug.
Scripts written for the old version keep working with the same calls: twitchSocket(channelName=, botName=, botAuth=), getMsg() and sendMsg(), printing the same lines as before. Underneath they now use TwitchChat, so they get TLS and the bug fixes for free. One visible difference: getMsg() waits for the next chat message instead of returning (None, None) for PINGs. That (None, None) is what crashed the old main.py a few minutes after start, so nobody can have relied on it working. All old names live in twitchSocket.py, which emits a DeprecationWarning and says when it can go (not before 2027-11-04).
The token used to be pasted into main.py, one commit away from being public. main.py now reads TWITCH_CHANNEL, TWITCH_BOT_NAME and TWITCH_OAUTH_TOKEN from the environment or a .env file (ignored by git; .env.example is the template), plus optional TWITCH_IRC_HOST, TWITCH_IRC_PORT and TWITCH_IRC_TLS to point it at the practice server. When the connection drops or Twitch sends RECONNECT, the bot waits 1, 2, 4 ... 30 s and connects again. A wrong token or channel stops it with Twitch's own message instead, since retrying won't help. --debug prints every raw IRC line, with the token masked.
Issue #1 asked why the bot doesn't use a ready-made IRC package. examples/irc_library_echo.py is the same bot on the irc package, so you can see what a package takes off your hands and what is still Twitch-specific. The README explains why the lesson keeps a raw socket. examples/eventsub_echo.py is the "next step": Twitch now recommends EventSub plus the Send Chat Message API over IRC. It follows Twitch's example chatbot (validate the token, look up ids, subscribe within 10 s of the welcome, skip the bot's own messages). Both are tested: the irc version against the fake IRC server, the EventSub version against small fakes of the Twitch API and EventSub WebSocket. Neither has run against the real Twitch.
The repo never had CI. The workflow checks that the bot imports with no packages installed (it promises the standard library only), then runs ruff and the test suite against the fake Twitch server on five Python versions.
The old README was a clone command and a code sample with the token pasted in. The new one starts with a cover, lets you try the bot on the fake server before you have a token, explains how to get a token with the right scopes, and then walks through Twitch chat step by step: Mermaid sequence diagrams for the handshake, PING/PONG and sending, a picture of how one IRC line splits into four parts, and a terminal capture from a real practice run (clearly marked as the fake server). It also covers the irc package and EventSub examples, the 2019 API and the 2019 bugs. readme.md becomes README.md. Picture sources are HTML files in docs/images/src/.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The 2019 echo bot had a short life expectancy: the first PING from Twitch made
getMsg()return(None, None),sendMsg(None)blew up with a TypeError, and that was the end of the bot. It also cut every message at the first colon (so every link becamehttps), lost the second line when two arrived in one packet, could crash on a Chinese character split between packets, and spun at 100% CPU forever once the connection closed. All eight problems are reproduced against a fake server in the revamp notes before anything was changed.So this turns the repo into what it was trying to be: a small Twitch bot you can learn from by reading it.
twitch_chat.pyis a raw-socket IRC client in nine commented steps (TLS connect, CAP REQ, PASS/NICK, JOIN, line buffering, parsing tags/prefix/command/params, PING/PONG, sending under Twitch's rate limit, reconnecting), still standard library only.main.pyis the echo bot: settings come from.envinstead of being pasted into the code, it reconnects with back-off, and--debugshows every raw IRC line with the token masked. It now talks toirc.chat.twitch.tv:6697over TLS instead of plain-textirc.twitch.tv:6667.Old scripts keep working:
twitchSocket(channelName=, botName=, botAuth=),getMsg()andsendMsg()live on intwitchSocket.pyon top of the new client, with a DeprecationWarning and a "not before 2027-11-04" removal note. The one behaviour change is thatgetMsg()now waits for a real chat message instead of handing back(None, None), which was the thing that crashed it.For issue #1 ("why not use an existing IRC package?") there's now
examples/irc_library_echo.py, the same bot on theircpackage, and the README explains why the lesson keeps the raw socket. There's alsoexamples/eventsub_echo.pyas a "next step", since Twitch's docs now recommend EventSub + the Send Chat Message API over IRC. I'm not closing the issue from here.The README got rewritten as a walkthrough: cover image, try-it-without-a-token quick start, how to get a token and which scopes, then each step with Mermaid sequence diagrams, a picture of how one IRC line splits into four parts, and a terminal capture from a real practice run (labelled as the fake server, because it is).
Testing:
tools/fake_twitch_server.pyis a small fake Twitch IRC server (also handy for practising). 69 pytest tests run against it: parsing, the rate limiter, the handshake, every 2019 bug as a regression test, TLS with a test CA, the old API,python main.pyas a real process, the irc example as a real process, and the EventSub example against fakes of the Twitch API and EventSub WebSocket. Locally green in Docker on Python 3.11, 3.12 and 3.14, plus ruff and a gitleaks scan. CI runs the same on 3.10 to 3.14.Not done: nothing here has touched the real Twitch. We have no account token to test with, so the handshake, tags and replies are checked against what Twitch's docs say, not against Twitch itself. The EventSub example also closes the old socket before opening the new one on
session_reconnect, which Twitch says can drop a message in between; it's a teaching example and says so.