Задача пришла из кровати. Она читает перед сном: бумажную книгу лёжа держать неудобно, телефон выжигает глаза за полчаса — значит электронка с подсветкой, и только она. Читает при этом не классику, а живой самиздат с Литнета и подобных площадок: тонкая, ранимая душа любит романы и совершенно не кривляется от современных авторов.
И вот мне озвучивают две проблемы. Первая: у самиздата нет редактора, и в текстах попадаются слова, которые ей противны — «заменить бы одни плохие слова на другие плохие, и читать стало бы гораздо приятнее». Вторая: авторы пишут сериалом, по главе-две в неделю, а скачать книгу — ни целиком, ни частями — сайт не даёт: он заинтересован держать читателя внутри своей экосистемы. Значит читать надо в браузере или в их приложении, то есть с телефона, то есть ровно там, где глаза устают.
Вызов принят. Сел, покрутил в голове, нарисовал на бумажке схемку — что должна уметь эта штуковина:
- проверять, не вышли ли новые главы;
- выкачивать книгу целиком, если она уже дописана, или только новые главы, если пишется;
- прогонять текст через свой список цензуры-замен;
- выгружать готовое прямо на электронную книгу;
- и всё это — через телеграм-бот: кинула ссылку, дальше он сам проверяет раз в сутки.
За вечер наклепал такую штуковину для любимой. И вот тут начинается то, ради чего этот разбор.
Сразу поправка к слову «бот». Снаружи это телеграм-бот, и это самая маленькая часть. Бот — тонкий вход: принять ссылку, ответить человеку, по команде запустить прогон вне расписания. Работу делают слои под ним, каждый со своей ответственностью: расписание на сервере, дешёвый детект новых глав, забор под подпиской, фильтр слов, сборка выпуска, доставка, сторож. Польза от такой нарезки вылезла на второй же день: когда система переезжала с моего ноутбука на сервер, переписывать пришлось только слой запуска. Остальные шесть переезда не заметили. По той же причине скачиватель под конкретную площадку меняется, не трогая ни фильтр, ни доставку, — а фильтр работает на любом EPUB, откуда бы тот ни приехал.
Первое: страшное на вид оказалось лёгким. Забор глав с сайта, который специально не даёт скачивать, выглядел главным препятствием — а закрылся быстрее всего: под эти площадки есть открытый Elib2Ebook, он умеет забирать книгу под учёткой. Не писать своё, а взять готовое и обернуть — треть проекта ушла за полчаса.
Второе: лёгкое на вид оказалось настоящей работой. «Заменить плохие слова» звучит как поиск-замена на пять минут. На живом тексте это ломается сразу: у слова, которое надо заменить, есть ни в чём не виноватые однокоренные соседи — и на двух книгах таких ложных попаданий набралась четверть. Плюс замена должна встать в предложение по падежу и с тем же регистром, будто её там и писали. В итоге правила отделены от словаря: код знает грамматику, файл знает слова — чем именно ругаться, вопрос вкуса, а не программирования. И каждая замена пишется в лог с пятью словами контекста по бокам, чтобы через месяц править список по фактам, а не по воспоминаниям.
Третье, и главное: такая штуковина ломается тихо. Она работает в фоне и молчит, когда сказать нечего, — а значит любая поломка выглядит точно так же, как спокойная неделя без обновлений.
- Протухла сессия, кончилась подписка, автор закрыл главы — и скачивание вернёт не ошибку, а страницы-обрубки «продолжение доступно по подписке». Всё остальное отработает штатно: фильтр почистит пустоту, выпуск соберётся, файл уедет на читалку. Никто не заметит, пока она не откроет книгу. Лечится проверкой объёма каждой главы перед отправкой.
- Сайт банит за частоту: первая же проверка после десятка запросов подряд ответила «Получен бан, попробуйте позже». Поэтому сначала одна дешёвая проверка оглавления — один запрос, без авторизации, — и только если появилось новое, тяжёлое скачивание.
- Умер таймер, уснула машина, упал контейнер — снова похоже на «просто нет новых глав». Поэтому прогон отмечает время своего успеха, а отдельный сторож смотрит на возраст этой отметки: тишина дольше 48 часов — и он пишет мне. Дёргает сторожа бот, а не таймер: таймер — это как раз то, что сторожат, проверить самого себя ему нечем.
Четвёртое, из-за чего это ощущается продуктом, а не скриптом: кому что говорить. Одно событие — один адресат. Ей — только про книги и человеческим языком. Мне — стектрейсы, протухшие токены, сменившаяся вёрстка. Она не должна видеть ничего, что не может починить и от чего будет тревожиться. Три строчки кода и половина ощущения от вещи.
Грабли, для тех, кто пойдёт похожим маршрутом. Развернул на сервере, а дома забыл выключить того же бота — и получил классику: Телеграм отдаёт сообщение кому-то одному, ссылка от неё уехала на домашнюю машину, серверный бот десять раз в минуту писал в журнал 409 Conflict, а в чате всё выглядело нормально, бот же ответил. Диагноз в одну команду — journalctl -u alenabook-bot -n 20: если там 409, где-то поднят второй бот с тем же токеном. Ещё: в .env попал путь с пробелом, и файл перестал читаться шеллом; лимиты памяти нужно вешать не только на юнит, но и на контейнер — он живёт вне cgroup юнита.
Мораль. Просили-то фильтр слов. Если бы я сделал только его, им бы не пользовались: чтобы фильтр пригодился, книгу всё равно надо где-то взять, куда-то положить и не забыть проверить, не вышло ли новое. Полезной вещь стала там, где закрылся весь маршрут человека, а не одно его звено. И второе, менее очевидное: у вещей, которые работают в фоне и молчат, основная работа — не в том, чтобы они работали, а в том, чтобы их молчание было отличимо от состояния УМЕР/отвалился.