Skip to content

Turn the echo bot into a step-by-step Twitch chat lesson (and stop it crashing on PING) - #2

Merged
NatLee merged 7 commits into
masterfrom
feat/step-by-step-lesson
Nov 3, 2026
Merged

NatLee merged 7 commits into
masterfrom
feat/step-by-step-lesson

Conversation

@NatLee

@NatLee NatLee commented Nov 3, 2026

Copy link
Copy Markdown
Owner

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 became https), 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.py is 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.py is the echo bot: settings come from .env instead of being pasted into the code, it reconnects with back-off, and --debug shows every raw IRC line with the token masked. It now talks to irc.chat.twitch.tv:6697 over TLS instead of plain-text irc.twitch.tv:6667.

Old scripts keep working: twitchSocket(channelName=, botName=, botAuth=), getMsg() and sendMsg() live on in twitchSocket.py on top of the new client, with a DeprecationWarning and a "not before 2027-11-04" removal note. The one behaviour change is that getMsg() 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 the irc package, and the README explains why the lesson keeps the raw socket. There's also examples/eventsub_echo.py as 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.py is 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.py as 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.

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/.
@NatLee
NatLee merged commit e1587cc into master Nov 3, 2026
5 checks passed
@NatLee
NatLee deleted the feat/step-by-step-lesson branch November 3, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant