The post has been translated automatically. Original language: Russian
This is the second and final part of the story about my Telegram bot @SaveContentMeBot. In the first one, I explained how the bot went from a chat link to a subscription. Then a Mini App appeared for viewing Instagram profiles, and then I deleted it along with my subscriptions. Along with the tests, it took about 8,300 lines, almost 40% of the code.
1. What has the Mini App taught you
The Mini App opened the Instagram profile by nickname or link: stories, highlights, posts and Reels. I learned three technical lessons from it.
The link to the picture is not a picture yet. The videos were playing back, but empty rectangles remained in place of the photos. Instagram images have a caption. Cross-Origin-Resource-Policy: same-origin, and the browser does not allow them to be embedded in someone else's page. The intermediate route on my server helped, but with restrictions: only allowed domains and HTTPS, a signed token with an expiration date, verification of the type and size of the response, without HTML and SVG.
The task may live longer than the request. The server function on Vercel takes 60 seconds, and the external service sometimes responded for a minute and a half. I have separated the launch and the receipt of the result: the server launches the task and responds 202 with the pending sign, and the page asks again. To prevent two people who open the same profile from launching two paid tasks, a key is created in Redis with the condition "only if it is not already available".
The cache does not live longer than the links inside it. The profile was stored for 12 hours, although the data itself becomes obsolete more slowly: the links to the video in it stopped working after about a day and a half.
2. Why did I delete it
The Mini App worked, but it was overloaded: four tabs, a free mode with blurry publications, a separate viewing budget, and two pricing plans. His page alone took up 3,297 lines.
Recently, a loved one made it clear to me that sometimes simplicity is more important than versatility. The simpler the product, the easier it is for users to understand why they need it, and the easier it is to gain an audience. The bot that sends the video via the link is explained in one phrase. The bot with the Mini App, tabs, and pricing plans is no longer there.
Subscriptions followed. No real payments had been made by that time, so I didn't have to cancel other people's subscriptions. If I go back to them, I'll redesign them. There are still downloads via the link, daily limits against abuse, and budgets for paid requests: the bot, which is free for the user, spends the project's money anyway.
3. Deleting a code is easier than deleting a function.
Buttons, commands, and accounts remain in the chat history, and a person can click on them after a week. Therefore, every old input should receive a clear answer.:
- The old Mini App button opens a page stating that the feature is no longer available.;
- The remote function buttons respond with "This button no longer works," and the commands also prompt: "Just send a link to a video or post.";
- the old payment invoice is rejected immediately, with the reason "The bot is not selling anything right now", and does not hang until the timeout.
The bot put the Mini App button in the menu in each personal chat separately, and deleting the code did not remove it. It was reset by a script for well-known users, but for others it is reset at the next command.
Another trap is old branches. I translated the bot into other languages in a branch where there were still subscriptions. Before the deployment, I checked which commit works in the prod, and realized that if I had hit this branch, the payments would have returned. After removing the features, the old branch becomes a different product.
4. What appeared after simplification
The free time was spent on the main scenario:
- For Instagram posts that are not downloaded directly, after Apify, another backup service appeared — FastSaver. It is not called if the post has already been received, but the sending stopped in the middle: part of the album could have gone into the chat, and a replay would have sent it twice.
- TikTok photo posts come as an album, although yt-dlp does not support such posts.
- The bot responds in Russian, Kazakh, Ukrainian and English.
- I first gave the promise in /privacy that the bot does not store correspondence that does not relate to it to three AI agents with the task of refuting it by code. They found that the commands for other bots in the groups ended up in the log along with the text. I fixed the code, not the wording.
Instead of output
In two parts, the bot went from a chat link to a subscription and back again. Technically, the most useful were the analysis of boundaries: repeats, timeouts, failures and deletions. The grocery lesson is simpler: a function that is difficult to explain gets in the way, even when it is working. Now @SaveContentMeBot is doing one thing again — it receives a link and returns a video or post to the chat.
Это вторая и заключительная часть истории о моём Telegram-боте @SaveContentMeBot. В первой я рассказал, как бот прошёл путь от ссылки в чате до подписки. Дальше появился Mini App для просмотра профилей Instagram, а потом я удалил его вместе с подписками. Вместе с тестами ушло около 8 300 строк, почти 40% кода.
1. Чему научил Mini App
Mini App открывал профиль Instagram по нику или ссылке: сторис, хайлайты, посты и Reels. Из него я вынес три технических урока.
Ссылка на картинку — ещё не картинка. Видео воспроизводились, а на месте фото оставались пустые прямоугольники. У изображений Instagram стоит заголовок Cross-Origin-Resource-Policy: same-origin, и браузер не даёт встроить их в чужую страницу. Помог промежуточный маршрут на моём сервере, но с ограничениями: только разрешённые домены и HTTPS, подписанный токен со сроком действия, проверка типа и размера ответа, без HTML и SVG.
Задача может жить дольше запроса. Серверной функции на Vercel отводится 60 секунд, а внешний сервис иногда отвечал полторы минуты. Я разделил запуск и получение результата: сервер запускает задачу и отвечает 202 с признаком pending, а страница переспрашивает. Чтобы двое, открывшие один профиль, не запустили две платные задачи, перед запуском в Redis создаётся ключ с условием «только если его ещё нет».
Кэш живёт не дольше ссылок внутри него. Профиль хранился 12 часов, хотя сами данные устаревают медленнее: ссылки на видео в нём переставали работать примерно через полтора дня.
2. Почему я его удалил
Mini App работал, но был перегружен: четыре вкладки, бесплатный режим с размытыми публикациями, отдельный бюджет на просмотр, два тарифа. Одна только его страница занимала 3 297 строк.
Недавно близкий человек дал мне понять: иногда простота важнее многофункциональности. Чем проще продукт, тем легче пользователям понять, зачем он нужен, и тем проще набрать аудиторию. Бот, который присылает видео по ссылке, объясняется одной фразой. Бот с Mini App, вкладками и тарифами — уже нет.
Следом ушли и подписки. Ни одного реального платежа к тому моменту не прошло, так что отменять чужие подписки не пришлось. Если вернусь к ним, спроектирую заново. Остались скачивание по ссылке, дневные лимиты против злоупотреблений и бюджеты на платные запросы: бесплатный для пользователя бот всё равно тратит деньги проекта.
3. Удалить код проще, чем удалить функцию
Кнопки, команды и счета остаются в истории чатов, и человек может нажать на них через неделю. Поэтому каждый старый вход должен получить понятный ответ:
- старая кнопка Mini App открывает страницу о том, что функции больше нет;
- кнопки удалённых функций отвечают «Эта кнопка больше не работает», а команды ещё и подсказывают: «Просто пришлите ссылку на видео или пост»;
- старый счёт на оплату отклоняется сразу, с причиной «Бот сейчас ничего не продаёт», и не висит до таймаута.
Кнопку Mini App в меню бот ставил в каждый личный чат отдельно, и удаление кода её не снимало. Известным пользователям её сбросил скрипт, у остальных она сбрасывается при следующей команде.
Ещё одна ловушка — старые ветки. Перевод бота на другие языки я делал в ветке, где подписки ещё были. Перед деплоем сверил, какой коммит работает в проде, и понял: задеплой я эту ветку, и оплаты вернулись бы. После удаления функций старая ветка становится другим продуктом.
4. Что появилось после упрощения
Освободившееся время ушло на основной сценарий:
- Для постов Instagram, которые не скачиваются напрямую, после Apify появился ещё один запасной сервис — FastSaver. Он не вызывается, если пост уже получен, но отправка оборвалась на середине: часть альбома могла уйти в чат, и повтор прислал бы её дважды.
- Фото-посты TikTok приходят альбомом, хотя yt-dlp такие посты не поддерживает.
- Бот отвечает на русском, казахском, украинском и английском.
- Обещание в /privacy, что бот не хранит переписку, которая к нему не относится, я сначала отдал трём ИИ-агентам с заданием опровергнуть его по коду. Они нашли, что команды для других ботов в группах попадали в журнал вместе с текстом. Исправил код, а не формулировку.
Вместо вывода
За две части бот прошёл путь от ссылки в чате до подписки и обратно. Технически самыми полезными оказались разборы границ: повторов, таймаутов, сбоев и удаления. Продуктовый урок проще: функция, которую сложно объяснить, мешает, даже когда работает. Сейчас @SaveContentMeBot снова делает одно дело — получает ссылку и возвращает в чат видео или пост.