Как телеграм бот для парсинга постов превратился в ИС для новостной редакции, пережил Google Sheets, SQLite и рождение собственного сайта. История о том, как костыли становились архитектурой.

Оглавление

Введение

В редакции новостного канала было 7–11 человек, 6–11 постов в день и один чат с топиками. И всё это держалось на Google‑таблице и ручном труде.

Перед тем, как мы начали этот непростой путь, процесс работы редакции был следующим:

  1. Автор предлагает инфоповод — присылает сообщение о нем в топик «Предложка».

  2. Выпускающий редактор решает нужна ли такая новость, ставит реакцию на сообщение автора.

  3. Если новость одобрена, автор пишет новость и пересылает в топик «Готовые».

  4. Оттуда выпускающий пересылает готовые посты в канал.

  5. Вечером‑ночью выпускающий редактор собирает топ постов (по собранным репостам) из канала в сообщение и отправляет в топик «Итоги». После этого он же заполняет Google‑таблицу — отмечает сколько постов написал каждый автор сегодня.

Итоги работы редакции подводятся каждый месяц: в конце или в начале следующего. Админ пишет, кто в чем был молодец, и рассказывает, кто получит деньги. Конечно, пороги для выплат были озвучены заранее, и все были в курсе. В общем, это всегда был очень приятный и атмосферный момент.

Ниже пример таблицы, которую вручную вели в течение месяца. Месяц делился на периоды по каким‑то соображениям владельца редакции — крайние даты периодов отмечены желтым. Зеленым цветом отмечены ячейки авторов, чей пост оказался первым в топе дня.

Пример того как выглядела таблица редакции за январь
Пример того как выглядела таблица редакции за январь

Этап 1. «Давайте просто спарсим посты»

Однажды нам захотелось посмотреть топ постов по репостам за месяц. До этого у нас уже был небольшой опыт парсинга постов: для учебного проекта мы спарсили посты канала за год. Но мы парсили их с сайта TGStat и использовали для этого Selenium. Сейчас сложно сказать, почему тогда мы решили сделать именно так. Скорее всего, тогда нам показалось, что так сделать проще, чем получить данные прямо с телеграма.

Позже коллега из редакции на учебе получил задание связанное с парсингом телеграма. Самое главное — там уже был код для парсинга с библиотекой Telethon и достаточная инструкция для старта.

В начале февраля 2024 года бот спарсил посты за январь, собрал топы по разным показателям и прислал в чат редакции. Пока это были просто топы постов — заголовок и цифры.

Сразу мы решили, что он мог бы сам собирать топ постов по репостам за день. Но без авторов нам такой топ не нужен. Поэтому боту обязательно нужно как‑то их найти. Пост в канале никак явно не связан с человеком, который его написал. В чате редакции хранятся только исходники постов — сообщения авторов в топике «Готовые». Получается, что просто нужно для каждого поста найти его исходное сообщение. Тут было важно учесть, что пост в канале может быть отредактирован.

В итоге ночная смена бота начиналась в 00:03 по мск и выглядела вот так:

  1. Бот парсит посты за день.

  2. Бот парсит сообщения чата редакции за день.

  3. Для каждого поста бот находит самое похожее на него сообщение из чата — автор сообщения = автор поста.

  4. Бот составляет топ постов за день и отправляет в чат.

Для сравнения текстов сначала мы использовали инструменты библиотеки fuzzywuzzy, но по каким‑то причинам бот часто неправильно определял автора, и мы перешли на SequenceMatcher из библиотеки difflib. Последнюю мы используем до сих пор, результаты нас устраивают.

Сначала порог схожести, при преодолении которого сообщение считалось исходником поста, был очень оптимистичным — 80 процентов. Тогда нам показалось, что этого достаточно, чтобы учесть правки, которые вносятся в пост уже в канале. Позже порог опустится до 60, но медианное значение схожести постов будет в районе 90%. При этом низкий показатель схожести не приводит к неверному определению автора, поэтому порог не поднимаем.

Ниже — распределение схожести поста с исходником на выборке постов за последние три месяца. Значения с минимальной схожестью свойственны не новостным постам: рекламе, информационным постам, мемам. Кроме того, низкий показатель схожести бывает у постов, к которым не было найдено исходное сообщение. Почему иногда оно не находится, мы до сих пор не знаем, просто терпим.

Гистограмма распределения значений схожести текста поста с найденным исходным текстом
Гистограмма распределения значений схожести текста поста с найденным исходным текстом

Этап 2. «А давайте он ещё заполнит таблицу»

В топ дня попадают только 5 постов, но авторы находились для всех постов из канала. Такая ценная информация была бы полезна редактору, который заполняет таблицу в конце дня. Или бот бы мог справиться с этим сам…

Может показаться, что логичнее было бы заставить бота записывать посты в БД и потом собрать какой‑то интерфейс, чтобы просматривать данные. Нам тоже казалось это логичным, а еще — слишком крутым и сложным. Мы решили, что проще просто заставить бота заполнять таблицу. Google‑таблицу удобно просматривать, ею легко делиться, и что‑то в ней править тоже легко.

Для работы с google‑таблицей мы использовали Google Sheets API и библиотеку gspread. Из статьи на «Хабре» мы узнали, как настроить Google Cloud, и оттуда же получили немного кода для работы с таблицей.

Пришлось немного изменить страницу месяца, чтобы бот смог в ней ориентироваться: в столбце авторов были указаны юзернеймы и страница месяца теперь называлась в формате «MM.YYYY».

Прошло еще немного времени, и мы дополнили таблицу данными о репостах и о предложенных постах. Репосты были получены при парсинге постов с канала ночью, но мы не указывали репосты за вчера — иначе бы пост, вышедший ближе к ночи, не успел получить все свои репосты. Специально для заполнения репостов мы парсили посты за позавчера и записывали в таблицу их. Такой подход мы считаем справедливым.

Количество предложенных постов, с одной стороны, показывает активность автора, с другой — вместе с количеством выпущенных постов они отражают то, насколько тщательно автор сам умеет выбирать инфоповод для канала.

Как бот работал с предложенными постами

Бот может получать сообщения и понимать из какого они топика. Но не все сообщения в топике «Предложка» это предложенные посты. Там же может происходить обсуждение или какое‑то общение по теме. Чтобы бот понимал, что из всех сообщений это именно предложенный инфоповод, было решено что выпускающие редакторы будут отмечать такие сообщения реакциями ? и ? (на одобренные и неодобренные посты соответственно).

Чтобы подсчитать количество предложенных постов, было решено хранить все сообщения из топика «Предложка» в БД (хранились только дата, id сообщения, юзернейм автора и флаг о том, есть ли на сообщении нужная реакция. Эта таблица называется «Сообщения»). В качестве БД была выбрана SQLite. Потому что работать с ней казалось просто и понятно. Возможно, потому что другие варианты мы и не пытались рассматривать. Ночью перед заполнением google‑таблицы из БД запрашивалось количество записей с реакцией за вчерашний день, сгруппированное по авторам. И значения суммировались со значениями соответствующих ячеек таблицы. Для этого в таблице был заведен отдельный столбец.

Далее на скриншотах примеры того, как в итоге выглядела таблица.

Пример того как выглядела таблица, которую заполнял бот. На скриншоте первый период августа 2024 года
Пример того как выглядела таблица, которую заполнял бот. На скриншоте первый период августа 2024 года
Пример того как выглядела таблица, которую заполнял бот. На скриншоте второй период августа 2024 года
Пример того как выглядела таблица, которую заполнял бот. На скриншоте второй период августа 2024 года

Еще позже мы решили отмечать в таблице, кто был выпускающим редактором в день и сколько дней за период человек провел на выпуске. Сейчас мы думаем, что можно было записывать в таблицу сообщений, кто именно оставил реакцию. Но почему‑то было решено вести счет в отдельном CSV‑файле и ночью выбирать оттуда редактора, который оставил наибольшее количество реакций. Глупо? Да. Работает? Тоже, да.

Еще немного о том, как бот отмечал выпускающих редакторов

Почему нужно было выбирать выпускающего с наибольшим количеством реакций? Потому что редакторы могли забывать правило о реакциях или временно друг друга подменять. Почему смены не были определены заранее? Все были заняты учебой или работой, и иногда бывало сложно предсказать, у кого будет время. Поэтому выпускающие могли договариваться о смене накануне. Конечно, в конце дня трудяга мог отметить себя сам, но мы стремились к тому, чтобы в таблице работал только бот.

В таблице же нам хотелось визуально отмечать день на выпускающего. Кажется, что было бы круто выделять ячейку цветом. Но Google Sheets API не предлагал удобного инструмента для этого, зато позволял форматировать текст ячейки — цифра в день выпускающего была выделена жирным шрифтом. И еще в таблицу был добавлен столбец, с количеством дней на выпуске. Значение ячеек изменялось ночь, после того как бот определял выпускающего.

Было бы красиво, если бы в первых строках таблицы были выпускающие — у них бы был заполнен столбец дней выпуска, а для рядовых редакторов он был бы пустым. Но тогда возникали ошибки при получении данных из таблицы, потому что мы получали сначала столбец редакторов, потом нужный столбец — например, дней выпуска, — и каждому значению первого столбца должно было соответствовать какое‑то значение второго столбца. Конечно, можно было обрабатывать эти данные уже в коде и выдавать нули тем, кому ничего не соответствует. Но мы просто заполнили столбец в таблице нулями.

И немного о том, как бот хранил посты

На самом деле, сначала у нас была идея сохранять в БД все сообщения из топика «Готовые». Как будто у нас была бы таблица с черновиками новостей, но в процессе работы над постом он мог сильно измениться, поэтому нужно было бы, чтобы бот реагировал на каждый апдейт сообщения и перезаписывал его в таблицу. А ночью ему бы все равно пришлось измерять схожесть текстов. Мы решили, что проще и лучше, чтобы бот собирал сообщения из чата именно ночью: тогда они уже будут иметь свой окончательный вид. Еще бывает так, что пост написан заранее или просто сам по себе не такой важный и его публикуют через несколько дней. Решили, что бот будет получать сообщения из чата за последние 4 дня. Парсинг чата не занимал значительного времени, а CSV‑файл сообщений не занимал много места. Все были довольны итоговым решением.

Кстати, данные о постах мы тоже хранили в БД. Добавили таблицы «Посты» и «ДеталиПостов». В первую ночью пачкой загружались посты за вчера с авторами — там были ссылка, заголовок, текст, дата, автор и схожесть текста поста с текстом сообщения автора — уверенность в авторе («confidence»).

На данном этапе ошибки в определении автора решались просто, но исключительно чьими‑то руками: я изменяла отметки в таблице и… заползала на сервер, выгружала файл БД, правила в нем нужного автора, закидывала файлик обратно. Об ошибках сообщали редакторы, когда видели их в топе ночью или при проверке таблицы перед итогами периода.

Если бот не смог найти автора поста, он присылал в чат админов уведомление об этом. Далее была собрана такая логика: админ отвечал на сообщение бота и передавал юзернейм автора. Бот получал ссылку из своего же сообщения, находил по ней пост в таблице и заполнял полученного автора.

Пример того как бот получал автора, если не смог определить самостоятельно
Пример того как бот получал автора, если не смог определить самостоятельно

Этап 3. «Теперь накинем ему еще задач»

Так как теперь бот жонглирует почти всеми данными, которые у нас есть, нам оставалось только придумать что с ними можно сделать. Далее описаны наши реализованные задумки.

Сообщение о выплатах

Сначала мы решили, что нет ничего логичнее, чем выбрать четкие нормы для авторов и, опираясь на них, позволить принимать решения о выплатах… но не боту, а таблице.

В таблицу был добавлен столбец «Выплаты». В нем были формулы, которые выдавали «Да» или «Нет» в зависимости от показателей автора. Когда мы начинали, условия были простыми: написать за период 20 постов.

Бот в этом случае просто получал из таблицы эти решения и разные показатели, чтобы собрать красивое сообщение с итогами периода. Чтобы не нагружать сообщение цифрами, количество репостов, предложек и постов мы указывали в табличке и прикрепляли PNG‑картинкой к сообщению бота.

К тому моменту мы уже стабилизировали два периода для редакторов: первый период начинался после 28-го числа прошлого месяца, а второй период начинался 15-го числа текущего. Так как бот заполнял данными таблицу за месяц, а показатели рассчитывались формулами в ячейках, для первого периода текущего месяца данные за последние числа прошлого месяца подтягивались по формулам. Это было очень удобно с точки зрения счета, но не очень удобно для человека, который создавал новую таблицу каждый месяц.

Пример периодов: сегодня 13 сентября. Завтра будет последний день первого периода сентября, а начался он 29 августа. То есть при расчете данных за этот период нужно учесть данные с 29 по 31 августа и с 1 по 14 сентября. Второй период сентября начнется 15-го числа и закончится 28-го.

Пример сообщения с итогами периода
Пример сообщения с итогами периода
Топ авторов за месяц

Также бот подводил итоги за месяц: считалось количество постов и количество репостов. Кажется, почти сразу ввели формулу расчета очков, чтобы из рейтинга не выпадали выпускающие редакторы, так как они не могли активно заниматься написанием постов. Формула была следующей:

Количество постов × 4 + количество предложек × 0,2 + количество репостов × 0,2 + дни выпускающим редактором × 2

Собирался топ-3 авторов с наибольшим количеством очков, они получали премию.

Тут бот тоже опирался на значения из таблицы. Внезапно, вместо того чтобы сделать результирующий столбец, мы заставили бота получать данные из парных колонок и суммировать. Кажется, это не наш стиль — мы бы, конечно, запихали формулу в ячейки таблицы.

Рейтинг авторов. В сообщение попадают топ-3, но все авторы могут посмотреть свои очки на картинке
Рейтинг авторов. В сообщение попадают топ-3, но все авторы могут посмотреть свои очки на картинке
Напоминание о событиях

В редакции в какой‑то момент появилась Google‑таблица с праздниками. Там указывались важные даты по теме канала, дни рождения редакторов и дни вступления в редакцию.

Сначала бот получил задачу на ночь: выгрузить все записи из таблицы и выбрать те, день и месяц которых совпадают с текущей датой (сегодняшние праздники) и завтрашней (напоминание, чтобы редакторы смогли подготовить материалы). Список подходящих событий просто высылался в нужный топик — «События».

Вместе с этим была введена еще одна задача бота: каждое 1-е число месяца высылать список событий на месяц. В этот момент редакторы могли застолбить за собой событие и подготовить пост к нужной дате.

Кажется, что было бы удобно где‑то указать редактора, который хочет о событии написать? Мы тоже так думали. Пройдет год, прежде чем такой функционал у нас появится.

Также хотелось иметь инструмент, который позволял бы записывать новый праздник, не выходя из Telegram. Такое решение было придумано: статичная страница в мини‑аппе, которая получала тип события, дату, название и… передавала в бота. Бот записывал в таблицу. Да‑да.

Система ачивок

Является ли праздником день вступления автора в редакцию? Да, если мы говорим о годе. Много ли редакторов провело год в редакции? Нет, но всех героев мы чтим. Такая дата на самом деле нужна для ачивки по нахождению в редакции. 3, 6, 9, 12, 18, 24, 30, 36 месяцев — полный набор ачивок. Хранились ли они где‑то? Нет, вместе с текстом поздравления они были захардкожены. Достижение этих ачивок проверялось сразу после получения списка праздников на текущую дату.

Но были и другие ачивки — по количеству постов, по количеству репостов. Они тоже проверялись ночью. Боту приходилось выгружать данные до ночного заполнения таблицы и сразу после. Затем два набора данных сравнивались с ачивками. Сообщения о достижениях отправлялись в топик «Итоги». Далее ачивки по постам найдут себе другое место в режиме работы бота. Ниже пример функции, которая собирает сообщение с поздравлениями:

def create_achive_post_text(milestone_achievers):
    # Шаблоны поздравлений
    milestones = [10, 100, 250, 500, 750, 1000, 1500, 2000, 2500]
    congratulations_templates = {
        10: "{user} - автор <b>{milestone}</b> постов. Великолепное начало ✨",
        100: "Вау, {user}, молодец! Выпущено уже <b>{milestone}</b> твоих постов! ??",
        250: "Ура, {user}, тобою написано уже <b>{milestone}</b> крутых постов! ?",
        500: "Супер круто, {user}! Позади <b>{milestone}</b> постов! ???",
        750: "Поздравляю, {user}! Уже <b>{milestone}</b> постов, это супер круто! ??",
        1000: "Невероятно! {user}, тобою написана <b>{milestone}</b> постов!????",
        1500: "Оаоао, {user}, написано уже <b>{milestone}</b> постов! Тебя не остановить! ?",
        2000: "Звание крутейшего магистра журналистики сегодня получает {user}. Позади <b>{milestone}</b> постов! ?",
        2500: "Победа! {user} автор 2500 новостей. С таким опытом: и в ТАСС, и в НАС, и в ВАС! ??"
    }
    answer = []
    for milestone in milestones:
        if milestone_achievers[milestone]:
            for user in milestone_achievers[milestone]:
                formatted_user = format_username(user)
                message = congratulations_templates[milestone].format(user=formatted_user, milestone=milestone)
                answer.append(message)

    return answer

Этап 4. «Боту нужна БД»

Получается, что вокруг каждого редактора крутится очень много разной информации: его ачивки, праздники (которые он на себя взял), его же посты и статус в текущем периоде. Как бы хотелось, чтобы каждый редактор имел такое место, в котором все это собрано и красиво представлено — такая личная страница редактора.

Конечно, мы понимали, что речь идет о странице на сайте. От мыслей о сайте становилось жутко. Казалось, что это что‑то большое и серьезное. Но все всегда начинается с чего‑то маленького и дурацкого. Например, переезд БД с SQLite на PostgreSQL.

На самом деле на этот шаг нас толкнул переезд бота с сервера в Москве на сервер в Астане. А на это нас толкнули проблемы с прокси, которые мы не могли решить. Перевезти бота оказалось проще.

На уже свежем сервере мы завели БД PostgreSQL. Но мы не перенесли данные, только создали уже существующие таблицы. Было большим облегчением, что для бота, кроме подключения, ничего не изменилось. Все получилось легко, и на волне энтузиазма мы создали таблицы для событий/типов событий и перенесли туда события из Google‑таблицы.

Это был маленький шаг, но мы им очень гордимся. На логику бота это почти не повлияло, но придало нам какой‑то уверенности и контроля. Для себя настроили доступ к БД через DBeaver, и все данные оказались под рукой. Так они не казались далекими и чужими.

При переезде на русском сервере был позорно забыт экземпляр мини‑аппа для календаря. Расстраиваться мы не стали и навайбкодили в DeepSeek страницу с календарем в так называемый «сайт». Не стали даже связывать сайт с ботом, просто добавили пароль на вход и поделились в редакции ссылкой. На странице можно было добавить или отредактировать праздник, а также просмотреть все праздники на текущий месяц и на любой следующий.

Это очень важный момент. Так мы отказались от одной Google‑таблицы.

Позже бот вместо простого списка событий на новый месяц стал присылать события отдельными сообщениями с кнопками. И авторы могли взять на себя событие, нажав кнопку. Тогда бот записывал в БД автора как ответственного за событие. Ниже страница календаря на сентябрь.

Страница календаря
Страница календаря
Через кнопки к сообщениям редакторы брали событие на себя
Через кнопки к сообщениям редакторы брали событие на себя

Этап 5. «А пусть авторы сами видят свои показатели»

В какой‑то момент до нас дошло, что необязательно боту ждать ночи, чтобы найти автора каждого поста. Достаточно ловить пост из канала и запускать поиск, проверять ачивку по постам, делать запись в таблицу и БД. Мы это реализовали.

Тут описано, как мы это сделали и какие были нюансы

Поиск автора сразу после публикации поста дает выпускающим больше времени указать автора, если бот сам не смог его найти. Редакторам всегда нравилось, что в топе постов за день рядом с крутыми постами указываются их юзернеймы. И они бывают расстроены, если вместо себя видят там «@не_найдено». На самом деле о такой фиче мы давно мечтали, но куда‑то делся страх, и мы начали ее делать.

В основном это был тот же процесс, что и ночью, просто триггером было не точное время, а пост в канале. Из изменений: не нужно получать посты из канала — бот получает пост в специальный обработчик. Но возникли дополнительные сложности: бывает, пост попадает в канал не вовремя или недоделанным, тогда его удаляют из канала и через несколько минут выкладывают заново.

Получается, что один и тот же пост отмечается на автора дважды.

Также был период, когда редакция вела параллельно два канала и бот получал посты из них обоих. При этом иногда посты пересылались из одного канала в другой. По логике бота это также два разных поста.

Бот мог понять, что это дубли, только по тексту поста, потому что ссылка всегда была новой.

Казалось бы, это должно привести к тому, что бот сначала ищет схожий пост среди последних постов в БД и, если не нашел, то ищет ему автора и создает запись. Но мы сделали иначе. Проверку на дубли мы добавили в момент создания записи. Если дубль действительно был, в запись устанавливалась ссылка на новый пост. Иначе просто создавалась новая запись — к этому моменту автор был уже найден.

Примерно после этого в Telegram появилась возможность разделять чаты с ботами на топики, как в супергруппах. Решено было для каждого автора завести два топика: для уведомлений о его постах в канале и для уведомлений об одобренных постах в чате редакции.

Возможно, раз у нас уже есть БД, нам было бы правильнее хранить связку «Редактор — id_чата_с_редактором — тип_топика — id_топика» там. Но мы завели для этого JSON‑файл.

Сейчас бот самостоятельно создает топики в чате с автором (кстати, всем новым авторам всегда нужно зайти в бота и нажать «Старт», чтобы он мог им писать), когда ему нужно предупредить о выходе их поста в канал или о том, что в предложке им одобрили новость. Тут еще прослеживается наш страх перед работой с БД и «сложными» структурами. Но работа с файликом не давала сбоев, поэтому он до сих пор на месте.

Это было практичным и нужным решением: так авторы всегда были в курсе, какие посты у них выходили, и могли сами подсчитать, сколько уже вышло и сколько еще нужно для получения выплаты. Было бы намного круче, если бы тут же можно было быстро посмотреть, сколько написано, сколько осталось для достижения выплаты и сколько дней активен автор.

Чат редактора с ботом: бот присылает уведомления об одобренноых новостях и постах из канала
Чат редактора с ботом: бот присылает уведомления об одобренноых новостях и постах из канала

Конечно, это возвращает нас к мыслям о личном кабинете автора…

Этап 6. «Нам нужен сайт»

Но личный кабинет автора никому не нужен!

Редакторы давно могут просто писать посты и жить спокойно. Выпускающий редактор может просто делать свою работу, а админ получает отчеты от бота. Каждый просто занят своим делом, и у всех всё хорошо.

А у нас‑то дел нет, поэтому мы экспериментировали. Сначала мы набросали страницу периода, на которой каждый автор может посмотреть свой статус в текущем периоде и список своих постов. Самым сложным в этом было научить сервер определять края периода, всё остальное было очень просто.

Ещё на этапе работы с календарем мы выбрали FastAPI и Jinja2, а для работы с БД — SQLAlchemy. Тогда казалось, что для небольшого эксперимента нет смысла работать с чем‑то серьезным и внушительным.

И нас было не остановить. Мы перенесли на сайт страницу месяца, прикрутили авторизацию, всё‑таки создали профиль автора и даже страницу аналитики.

Как мы перенесли таблицу месяца

В августе этого года Google Sheets API начал возвращать нам ошибку 503 раз в несколько дней. Сначала мы подумали завести ретраи и не морочить голову.

Но потом мы увидели в этом сигнал, что пора отказаться от Google‑таблиц в пользу собственного сайта.

Так мы добавили на сайт страницу «Отчет». На ней отображаются три таблицы:

  • Таблица первого периода.

  • Таблица второго периода.

  • Таблица с показателями за месяц.

Третью таблицу добавили, потому что итоги месяца для редактора практически никак не связаны с итогами периодов, так как даже вместе оба периода не покрывают целый месяц (при этом даже захватывают кусочек прошлого). А так все суммируется в рамках текущего месяца, и это удобно для просмотра.

Так же, как и в Google‑таблице, в ячейках отмечается количество постов, а ячейки дней выпуска отмечаются цветом для выпускающих. В таблицах периодов кликом по нужной ячейке можно вызвать модалку и изменить выпускающего, если бот определил его неправильно.

Для этого учет дней выпускающих переехал в БД. Но статистика реакций выпускающих за день не покинула CSV‑файл. До сих пор подсчет ведется в нем, а в конце дня в БД записываются данные о том, кто был на выпуске. А казалось бы, нужно просто добавить поле в таблицу сообщений… но снова не в этот раз.

В правых частях таблиц по периодам подсчитывается количество постов, активных дней, дней выпускающим и вердикт о достижении порогов выплат. Пороги выплат не захардкожены, перенесены как отдельный тип ачивки и разделены на пороги для выпускающих и для рядовых редакторов. Но об этом написано далее.

Первый период сентября на странице Отчет
Первый период сентября на странице Отчет
Таблица месяца на странице Отчет
Таблица месяца на странице Отчет
Как мы добавили авторизацию через телеграм

Всё шло отлично, и на волне оптимизма было решено изменить метод авторизации: вместо общего пароля была добавлена авторизация через Telegram + проверка того, является ли пользователь активным редактором (по Telegram ID и полю is_active для сущности editor). Тут же завели таблицу с редакторами в БД. До этого на странице постов за период были просто юзернеймы, подтянутые из записей постов.

Вместе с этим бот стал реагировать на входы и выходы из чата редакции. При выходе из чата для редактора в БД значение is_active переключалось на False. Записи мы не удаляем, потому что никогда не знаешь, когда вернется редактор. При вступлении в редакцию, соответственно, либо создавалась новая запись редактора, если он действительно новый, либо активировалась существующая.

Важно, что в запись редактора были перенесены исторические данные о количестве постов и репостов. В Google‑таблице была страница «История», и такие данные складывались суммой из нужных ячеек со страниц месяцев, которые автор проработал. Просто отредактировали формулы, получили данные до старта работы БД и записали эти цифры авторам как исторические данные. Далее они суммировались с теми, которые хранились в деталях постов в БД — так мы получали значение всех постов/репостов автора.

Как мы сделали долгожданный профиль автора

На такой важной странице указывается вся имеющаяся по редактору информация:

  • с какой даты редактор в команде;

  • является ли выпускающим редактором;

  • сколько всего было написано постов;

  • сколько всего было получено репостов;

  • по текущему периоду: сколько постов, репостов и активных дней;

  • какие праздники редактор взял на себя;

  • полученные ачивки.

Активный день — день, когда автор написал хотя бы один пост. Может учитываться в решении о выплатах.

Для выпускающих указаны даты их дежурств за текущий период, список событий, которые автор взял на себя в этом месяце, а также полученные и неполученные ачивки (с описанием того, сколько осталось сделать).

Это чистой воды фантастика. Получилось намного лучше, чем мы себе это представляли. Сложно сказать, насколько авторам нужна была вся эта информация, но мы однозначно знаем, что на нее приятно смотреть.

Ссылка на свой профиль у каждого автора есть в верхней панели. Чтобы попасть на страницу любого другого автора, можно перейти со страницы отчета (ссылка в юзернейме в строке каждой таблицы) или со страницы постов периода.

Основная информация в профиле автора
Основная информация в профиле автора
Полученные и неполученные достижения в профиле автора
Полученные и неполученные достижения в профиле автора
Как мы добавили диаграммы

В последнюю очередь мы добавили страницу аналитики. На ней находится лента постов из канала (последние 10 с возможностью загрузки дополнительных) и графики с анализом деталей постов. Только сейчас мы поняли, что большим упущением было игнорировать просмотры. Мы никогда их не хранили. А какие красивые графики и тепловые карты можно было нарисовать, опираясь на вовлеченность! Но пока — только диаграмма с количеством постов и столбчатая диаграмма с репостами. Серьезная аналитика, мы знаем.

Из поста в ленте можно вызвать модальное окно, и выпускающий редактор может изменить автора, если бот определил его неправильно. Не получилось для этого найти место лучше. Кажется, что можно было определить такой функционал на страницу периода, но к этому у нас не лежала душа…

Страница Аналитики
Страница Аналитики

Этап 7. «Боту не нужна БД»

На самом деле, бот почти ничего не считал сам. Все данные он брал из Google‑таблицы: она очень удобно всё рассчитывала и сама предлагала даже решения по выплатам.

Нам было тяжело думать о том, что бот теперь должен сам получать данные из БД и обсчитывать их, чтобы собрать итоги периодов или месяца. Но в процессе работы над страницей отчёта мы поняли, что ничего боту считать не надо! Нужные методы готовы и работают на таблицах. Ничего не помешает нам пользоваться ими. Мы, конечно, начали строчить API. Было ли нам страшно? Конечно, было. Сначала долгое время бот даже ползал к API без авторизации… потому что это было страшно и непонятно. Но API стало настоящим спасением и нашей новой игрушкой.

Наша большая боль — данные для ачивок. Без Google‑таблицы боту нужно собрать данные из БД (помним про исторические данные тоже), потом обновить детали постов, потом получить данные снова и посмотреть есть ли ачивки. Но он всегда был просто посредником между редакцией и данными, не хотелось наделять его чем‑то большим. И обсчёт ачивок был делегирован на сервер сайта. Тут же мы отобрали у бота нужду обращаться к БД: посты, детали постов, новых авторов — всё бот начал передавать на сервер, сервер теперь пишет в БД. Почему так? Нам показалось, что так правильно.

А потом мы вспомнили, что бот следит за сообщениями в топике «Предложка» и вернули ему эту функцию. Ну получается, что все‑таки с БД он общается.

Так мы пришли почти к тому, с чего начинали. Теперь бот снова в основном работает только с чатом и каналом. Ну, ещё общается с сайтом, это да.

У бота есть своя ежедневная рутина
У бота есть своя ежедневная рутина

Этап 8. «А дальше сами» или Заключение

Так как у нас больше нет Google‑таблицы, никому не нужно ее настраивать. Это бы освободило кого‑то… Но все еще приходится вручную изменять пороги выплат в БД. Это ложится на плечи того, у кого есть доступ к базе данных.

Понять, что нужно делать, было несложно. Но сложно было принять, что впереди полно работы.

Перешли к сборке панели администратора. В ней выпускающие редакторы могли бы настраивать состав редакции, изменять или добавлять ачивки, менять коэффициенты для расчета очков. Ну и, конечно, задавать правила для выплат.

И панель была собрана:

  • На вкладке управления редакцией выпускающий может активировать/деактивировать автора (было бы круто, чтобы бот при этом удалял из чата или добавлял автора, но этого сейчас нет), назначить выпускающим или разжаловать в обычные авторы.

  • На вкладке управления ачивками можно изменять любые параметры достижения: название, текст поздравления, пороговое значение. Тут же можно создавать новые достижения.

  • На вкладке параметров задаются коэффициенты для формулы расчета очков.

  • На вкладке правил выплат происходит самое интересное — создаются и настраиваются правила для выпускающих и обычных редакторов. Они задаются как логические выражения, например

    • Если редактор «Выпускающий» И дней выпуска = 7 И постов = 10, то Выплата = TRUE

    • Если редактор «Не выпускающий» И постов = 20 ИЛИ активных_дней = 14, то Выплата = TRUE

Тут самые интересные скриншоты с панели админа
Пример списка правил для выплат
Пример списка правил для выплат
Форма настройки правила для выплат
Форма настройки правила для выплат
Список достижений
Список достижений
Форма редактирования достижений
Форма редактирования достижений

Ну вот и все. Теперь редакция все‑все может сама.


На самом деле не рассказано об огромной куче функций бота.

И мы, конечно, понимаем, что редакция, описанная во введении, — это не типовая редакция, ахах. Но именно для нее бот полезен, а сайт приятно его дополняет.

За эти два года мы поняли, что долго чего‑то бояться — это не круто (но остановиться и подумать — это очень хорошо). Страх всегда рядом и шепчет: «А вдруг не получится?», «А вдруг сломается?», «А вдруг это слишком сложно?». Но если слушать его слишком внимательно, можно вообще ничего не сделать.

Мы никогда не строили систему. Мы только решали проблемы по мере их поступления — иногда решали их плохо. CSV вместо БД, JSON вместо таблицы, мини‑апп, который передает данные прямо боту. Всё это выглядит смешно, но оно работало. И только эти маленькие шаги позволили нам дойти до сайта, API и панели администратора.

Так что, если вы сейчас думаете, что «рано», «сложно» или «не потяну» — возможно, вы правы. А возможно, через год или два у вас будет работающая система, о которой вы будете рассказывать взахлеб со смесью гордости и смущения.

Комментарии (0)