The problem
A shop selling phones in Tashkent already had customers in Telegram — and a catalogue that lived as a folder of photos and a person retyping specifications into chat all day. Every “what do you have in stock?” cost a human twenty minutes.
They did not need a marketplace. They needed the browsing part automated and the selling part left to a person: categories, models, a card with photos and specs, search, and a button that hands the conversation to a manager.
Two shops, one codebase
The same thing was needed twice: PLUSLINE for phones, KEYLINE for keyboards. Same structure, different texts, categories, and one extra field — phones carry a condition flag, new or used, keyboards do not.
They are one codebase in two copies, not a shared library. For two small shops run by different people, a shared package would mean coordinated releases and a versioning story for a change that takes five minutes. The cost is honest and written down: touch a shared mechanism in one, check whether the other needs it too.
Constraints and tradeoffs
A JSON file instead of a database
The catalogue is a few hundred items edited by one person through the admin panel.
A database would add an operational surface — backups, migrations, a process to keep alive —
to a bot that otherwise runs from a single file. products.json is the whole store.
The price is that writes must be atomic: a crash mid-write does not corrupt a row, it destroys the catalogue. Every save goes to a temporary file and then replaces the original.
Photos go as an album, not one by one
A product with five photos sent as five messages is unreadable. Telegram media groups solve it — the photos become one swipeable card — but they come with rules that shape the card: at most ten files, and only the first one carries a caption. That single constraint decides how the whole product card is composed.
No cart, no payment
- No checkout. Payments mean a payment provider, refunds, receipts and legal responsibility. The shop already sells fine through a person; the bot's job is to get the customer to that person knowing what they want.
- No recommendations. A few hundred items do not need a ranking model. Categories and search cover it.
- Strings are not in the code. All of them live in
texts.py, because the two shops differ mostly by wording and the owners edit it themselves.
Architecture
Screens and states
Categories. Counts come from the catalogue, so an empty category cannot be browsed into.
Product card. One media group, caption on the first photo — the format the platform allows.
Admin. The owner edits the shop from the same chat, no dashboard to log into.
Search. Plain substring matching over names — enough at this size, and it never surprises anyone.
Results and lessons
What I would tell myself at the start
- Write down that the two copies diverge. They already had when I came back to them. The note in the repository — “fixing a shared mechanism? check the other one” — is worth more than any amount of intended-to-be-shared code.
- Read the media group rules before designing the card. Ten files and a caption only on the first are not details; they are the layout.
- Atomic writes from the first commit. A JSON catalogue is a fine database right up
until the process dies halfway through
write(). - Keep every string in one file. The owners rewrite wording constantly, and none of them are going to open a handler to do it.