Avito for expats abroad: a classifieds board that builds itself from Telegram flea-market chats. A Telethon listener + LLM turn slang and photos into strict listings; React PWA with three markets, subscriptions, an AI assistant for sellers and an admin panel. Formerly Love it o.
All local expat commerce lives in Telegram: Phuket, Pattaya, Samui, Phangan, Tbilisi, Da Nang — giant flea-market chats everywhere. A chat has perfect liquidity and an instant social graph, but zero taxonomy: nobody can filter messages for "a bike under 30,000 ฿ in Pattaya".
"NaviTo — Avito for our people abroad. A classifieds board for Russian-speaking expats that assembles itself from Telegram chats."
The project started as Love it o and became NaviTo in summer 2025: three countries, a PWA, an AI assistant for sellers, subscriptions and an admin panel. 300+ commits.


The model here isn't a chatbot but an invisible data-transformation layer doing a data engineer's job in real time.
telegram_message_id; poorly parsed listings are re-parsed, not duplicated.The system prompt is engineering in its own right: price slang ("3к" → 3000, "free" → 0), five currencies, multi-item detection by pattern ("Sneakers — 350 / Sandals — 250" → two cards), a "one service = one card" rule, photo-to-item matching by visual content.
ITEM FIELDS:
- title: max 50 chars. Product name + model/year. NEVER start with: Продам/Продаю/Куплю…
- price: number. к/тыс = ×1000 (3к→3000). "бесплатно"/"даром" → 0. Find FINAL price.
- currency: THB | RUB | USD | GEL | VND.
- photo_index: 0-based index of the photo showing THIS item.
MULTI-ITEM DETECTION (read the WHOLE message first):
PATTERN B — "Name — price" lines: "Кроссовки - 350\nСандали - 250" → 2 items
PATTERN C — intro + items: first line with NO price → description for ALL items below
IMPORTANT SERVICE RULE: one service provider describing several packages → ONE item only.
# openrouter_analyzer.py — primary model and fallback chain
DEFAULT_MODEL = os.getenv("OPENROUTER_MODEL", "google/gemma-4-31b-it:free")
FALLBACK_MODELS = ["google/gemma-4-26b-it:free", "google/gemini-2.5-flash-lite"]
# if every model fails — a Telegram alert to the admin listing the models tried
Once the chaos is turned into PostgreSQL rows, the React front end takes over: a catalogue with category, city and price filters, a listing page with a gallery, seller contact and a link to the original Telegram message. Photos live in Cloudflare R2 rather than the database — that removed DB transfer and sped up pages.


Three markets with their own currency and pricing logic — Thailand (฿), Georgia (₾), Vietnam (thousand ₫); the region is detected by IP on first visit.
export type MarketSlug = 'thailand' | 'georgia' | 'vietnam';
const CURRENCY_SYMBOLS = { THB: '฿', GEL: '₾', K_VND: 'тыс. ₫', USD: '$', RUB: '₽' };
Sign-in is a Telegram deep link, no phone number. A listing doesn't have to be filled in — it can be told: the user drops photos, the AI recognises the item, asks clarifying questions with quick-reply chips (condition, what's included, area), suggests a recommended price range for the market — and assembles the card. The chat uses a separate model from the parser.

A buyer subscribes to a category in their city and gets new listings in the Telegram bot and via web push (PWA, VAPID). VIP listings rise to the home page; national VIP shows in every city of the country.

The /ad landing — "Avito for our people abroad" — explains the product in one screen.

The architecture removes the flea-market moderator as a profession: the catalogue builds itself, sellers fill in nothing, buyers stop scrolling chats. The platform scales to a new country by adding a market and a list of chats.