Automate things
A Telegram bot that operates my Mac: lock it, take a screenshot, record a meeting, set a reminder, watch a room through the webcam.
A core that does not know Telegram exists
The 41 actions live in a core that imports no networking library at all. That is verified by running it with the Telegram library blocked from importing. Telegram is one front end on top of it, with 46 commands. A command-line front end uses the same core, so a shortcut on the phone can run an action over SSH and get the same answer Telegram would give.
The reason is practical. The actions are the part worth keeping, and they should not depend on one chat service. Adding another front end means writing a transport, not touching the actions.
Buttons and typed commands share one table that maps each command to its handler. They cannot drift apart, because there is nothing to keep in sync.
Restarting used to delete reminders, silently
In the first versions reminders lived in memory. Restarting the bot wiped them, and nothing said so. That is the worst kind of failure: the system looks healthy and has forgotten what you asked.
Now everything is in SQLite. On startup the bot reschedules whatever is still pending and tells me about anything that fell due while the Mac was off, instead of swallowing it.
- Voice notes are transcribed by Whisper on the machine. The audio never leaves it.
- A file lock guarantees there is only one instance.
- A small Swift app in the menu bar starts the bot, restarts it if it dies and can keep the Mac awake.
A mailbox that knows nothing
When the Mac is off, a message gets no answer. The optional fix is a small Worker, 153 lines, that holds messages while the Mac is off and replies “I’ll do it when it wakes”.
What it does not do matters more. It runs no commands and stores none of my data: no reminders, no notes, no lists. The Mac stays the source of truth. It is off by default, because the default should be a bot that is entirely local; turning it on buys an instant reply at the cost of messages passing through a third party.
It uses D1 rather than KV. A queue needs a write to be visible on the very next read, and KV is eventually consistent.
A rule I added after it bit me
I once sent a command with the lid closed. The Mac was asleep, the message waited in Telegram’s queue, and it was handled two hours later, when it no longer made sense.
A command is an instruction for a moment. Run late, a correct command becomes a wrong one. Now anything that arrives more than ten minutes late is discarded, and the bot says so instead of running it.
The full write-up, with every command.
- Python66.8%
- HTML18.1%
- Swift8.7%
- JavaScript5.1%
- Shell1.3%