Automation
Shopping Bot
A Norwegian-language Telegram bot that turns the family group chat into a persistent, shared shopping list.
In Plain English
Instead of texting your family "can you buy milk?" and hoping they remember, everyone adds items to a shared shopping list right inside Telegram (a messaging app like WhatsApp). Anyone in the household can add, remove, or check the list from their phone, and everything stays in sync because it all runs through one bot (a small program that automatically responds to messages) on a server (a computer that is always on and connected to the internet).
Problem
Household shopping coordination is one of those problems that sounds trivial until you actually live it. Paper lists get left on the kitchen counter when you are already halfway to the store. Shared note apps like Google Keep or Apple Notes require everyone in the household to install the same application, create accounts, and agree on a platform. In a family where some members use Android and others use iOS, where tech literacy ranges from "builds servers for fun" to "still calls it the Google," finding common ground is an uphill battle.
Text messages about groceries are even worse. A quick "we need bread" gets buried under a conversation about weekend plans, a meme from a cousin, and a voice note about dinner. By the time someone is actually standing in the supermarket aisle, that message is fifty scrolls up and long forgotten. The information exists, but it is not accessible at the moment it matters.
The real insight behind this project was that the family already had a shared communication channel that everyone checked multiple times a day: the family Telegram group chat. Rather than introducing yet another app, the shopping list needed to live exactly where the conversation already happened. The bot needed to be entirely in Norwegian (because "Legg til" is what the family actually says, not "Add item"), lightweight enough to run on a modest VPS with no fuss, and simple enough that non-technical family members could use it without reading a single line of documentation. Type /a melk and move on with your day.
Architecture
Multi-user Telegram bot with long-polling, async command handlers, MarkdownV2 formatting, thread-safe JSON persistence, and systemd process management on a Linux VPS.
Features
Multi-User Shared List
Entire household, one list
Every family member interacts with the same list through the same Telegram group chat they already use daily. When someone adds "brød" from their phone on the bus, it appears instantly for the person standing in the bakery aisle. Each item is stamped with the Telegram first_name of whoever added it and a DD.MM HH:MM timestamp, so there is never any confusion about who requested what or when. The list is not just shared; it is transparent.
Undo Support
/undo restores last removal
Anyone who has fumbled a phone screen while walking knows how easy it is to tap the wrong item. The bot maintains a last_removed buffer in the JSON data file that holds the most recently deleted item, including all its metadata. A quick /u command restores it to the bottom of the list as if nothing happened. This single feature prevents the most common source of frustration: accidentally removing something and having no way to get it back without re-adding it manually.
Safe Clear with Confirmation
Two-step /clear ja
Clearing the entire shopping list is a destructive action, and the bot treats it that way. Typing /clear alone does not wipe anything; it displays a warning message showing the current item count and instructs the user to type "/clear ja" to confirm. This two-step confirmation pattern prevents the all-too-real scenario where someone accidentally clears a list of twenty items that took the family a week to build up. The Norwegian "ja" (yes) keeps the confirmation natural for the household.
Single-Letter Command Aliases
6 shorthand commands
Every command has a single-letter alias registered as a separate CommandHandler: /a for add, /l for list, /r for remove, /u for undo. These aliases exist because the bot was designed for the real-world scenario of thumb-typing on a phone while standing in a grocery store. Saving five keystrokes per interaction sounds minor, but across hundreds of uses it adds up to a meaningfully smoother experience. The full command names still work for discoverability.
Norwegian Language Interface
Full Norwegian UI
Every string the bot sends is in Norwegian: "Handleliste" for shopping list, "Lagt til" for added, "Fjernet" for removed, "Ingenting a angre" for nothing to undo. This was not an afterthought or a localization layer; the bot was written Norwegian-first because the family speaks Norwegian. Error messages, confirmation prompts, the welcome text, even the /clear confirmation word ("ja") are all native. There is no language toggle because there is no need for one.
Thread-Safe Persistence
threading.Lock on all I/O
The bot uses a global threading.Lock that wraps every read and write to the JSON data file. When User A adds "melk" at the exact same moment User B removes item 3, the mutex ensures these operations execute sequentially rather than corrupting the file with interleaved writes. The python-telegram-bot library handles messages asynchronously, which means concurrent modifications are not a theoretical concern but a practical reality for a multi-user household bot.
How It Works
Token Resolution and Startup
When the bot process starts, it first checks the TELEGRAM_BOT_TOKEN environment variable. If that is empty, it falls back to reading a config.json file in the same directory. This dual-source pattern exists because the systemd service file sets the token via its Environment directive, while local development uses the config file. Once a valid token is found, the bot initializes the shopping_list.json data file if it does not already exist, seeding it with an empty items array and a null last_removed field.
Long-Polling the Telegram API
The bot builds an Application instance using python-telegram-bot's builder pattern and registers seven CommandHandler objects, one for each command and its alias. It then calls run_polling() with allowed_updates=Update.ALL_TYPES, which starts an infinite loop of HTTP requests to the Telegram Bot API, asking "any new messages since I last checked?" This long-polling approach means the bot does not need a public IP or webhook endpoint. It just reaches out to Telegram's servers and waits for work, making it deployable on any VPS with outbound internet access.
Command Parsing and Metadata Capture
When a family member types "/a brød" in the chat, the Telegram API dispatches it to the add_item handler. The handler joins all arguments after the command into a single item name, then pulls the user's first_name from the Telegram Update object. It constructs a dictionary with the item name, the user's display name, and a Norwegian-format timestamp (DD.MM HH:MM via strftime). This metadata bundle is appended to the items array, and the response confirms the addition using MarkdownV2 formatting with the escape_md() helper preventing special characters from breaking the markup.
Thread-Safe File Operations
Every read and write to the shopping_list.json file passes through the global _file_lock, a threading.Lock instance. The load_list() function acquires the lock, reads the file, parses the JSON, and releases the lock. The save_list() function does the same for writes, using json.dump with indent=2 and ensure_ascii=False to produce human-readable, UTF-8 encoded output that preserves Norwegian characters like ae, o-slash, and a-ring. This locking strategy is simple but effective: it trades a tiny amount of throughput for complete data integrity under concurrent access.
Always-On via systemd
The bot runs as a systemd service on the VPS with Restart=always and RestartSec=10. If the Python process crashes due to an unhandled exception, a network timeout, or an out-of-memory event, systemd waits ten seconds and starts it again automatically. If the entire server reboots, the WantedBy=multi-user.target directive ensures the bot starts with the OS. Logs flow to both a local bot.log file (via Python's logging module with dual StreamHandler and FileHandler) and the system journal, so troubleshooting can happen through either journalctl or direct file inspection.
Tech Stack
Language
Python 3
Bot Framework
python-telegram-bot v21+
Storage
JSON flat file (UTF-8)
Concurrency
asyncio + threading.Lock
Deployment
Linux VPS with systemd
Logging
Python logging + journalctl