Новое пополнение в списке «Российские разработоки мессенджеров». Нет, делаем его не по причине отключения нас от мировых сервисов...
Предыстория
В октябре был в путешествии, за пару месяц до, наша компания как и многие компании в РФ стали переходить на свои мессенджеры в замен Telegram. У нас выбор пал на Mattermost, ну это такой, аналог Slack. За период примерно 3 месяца я столкнулся с максимально ужасным UI по моему мнению. Нет, проработка его очень отличная. Но вот трудности в самом представлении для пользователей — ужас.
Threads
Треды в MM аналоги мини‑чатов внутри какого‑то сообщения. Естественно каждый начинает писать внутри треда, но когда ты пытаешься найти нужное сообщение и из 500шт у тебя половина «Не понял(а) что мы там решили делать» или «Что там за проблемы в....» и тому подобное. Получаем в итоге не информативную историю. Например надо проверить макеты или написать ТЗ, или по какой‑то задаче уже общались — найти в списке который каждый день обновляется на 20+ таких тредов — это не реально. Нет архивированности и подсветки важных.
Для меня — боль.
Чаты‑серверы (создание только на уровне DevOps)
Возьмем в пример TG:
Создаем папку, в нее кидаем чаты которые оформлены в формате «Форум» и там есть топики. Все понятно, информативно, топики не скачут с низу вверх от новых к старым.
Или Discord — создаем чат сервер, выбираем его и вуаля там темы, темы разных форматов. Пиши в тот который удобно, все зафиксировано в строгой позиции.
Ну ведь удобно же!!
В ММ не так, во всяком случае у нас. У нас один чат сервер в котором уже у меня скопилось 40 топиков. И это только в которые меня добавили. И при получении сообщения они скачут в вверх. Прочел — ушло. Не удобно это.
Отсутствие звонков
В дорогой версии ММ компаниям предлагается сервис звонков — у нас не так. Поэтому используются все доступные средства связи. Каждая встреча + 1 новая вкладка в и без того переполненном браузере. Общаетесь вы, надо перейти в доку почитать, и потом ищи среди всех вкладок где у тебя там иконка звука работает.
Это три ключевых для меня минуса. Как раз в путешествии был ноутбук, в один из вечеров начал накидывать скиллет в Figma. Накидывал его максимально грубо, выделил основные зоны + дополнительные. Когда прибыл домой, начал писать Back‑end.
На все написание бэка ушло примерно 2.5–3 месяца. Делал так, написал бэк — через AI написал фронт. Для меня было удобно писать именно сервисную часть по причине того что и так описание сервисов делаю на работе. Продумываешь архитектуру, пишешь сервис и уже потом оболочкой занимается AI. Последние недели 3, нас двое, я + знакомый фронтенд. исправляем косяки ИИ и вносим некоторые визуальные правки.
UI
Краткое описание
В моем понимании сейчас есть уже успешные сервисы такие как ДС и ТГ. Да, печально что они не доступны у нас, но они дали нам отличные возможности и примеры которые стоит использовать. Собственно весь дизайн поход на ДС, с функциями ТГ и ММ.
Ниже сводка по общим API фронта:
Сервис | Кол-во запросов |
|---|---|
Auth | 5 |
User | 10 |
Chat | 28 |
Topics | 10 |
Threads | 4 |
Files | 7 |
Permissions | 16 |
Voice | 8 |
Calls | 6 |
Emoji | 12 |
Jira | 4 |
Links | 1 |
Poll | 2 |
Search | 1 |
Typing | 2 (WebSocket) |
ИТОГО | 116 |
Форма авторизации


Форма для личного использования добавлена по стольку, по скольку. Основной упор - для компании. Для личного - очень много юр.волокиты в нашей стране.
Основной интерфейс

Меню пользователя

Личные сообщения



Общий чат





Доска чатов

Доску чатов делал для интеграции с Jira. Что бы когда в jira создавалась задача — автоматически создавался чат в который добавляется автор задачи и исполнитель. Это позволяет вести диалог в рамках определенного процесса не теряя его из виду.
Прочее
Вообще еще много фичей которые я не стал тут показывать. Настройка звука, настройка цветов и оформление. Основной стиль — стекло (да, спасибо IOS), но его можно заменить на непрозрачный модули с разными цветами. Все окна подстраиваются под цвет бэкграунда. Если загрузить картинку — выделяются основные цвета, создается палитра и весь UI меняется под цвет бэкграунда. Предустановлено 9 различных цветовых тем, 5 различных цветов интерфейса для замены стекла.
Так же есть ToDo лист, он локальный. Делал потому что мне удобно в процессе обсуждения накидать себе сразу задачи и потом просто перенести их в jira. Так же сейчас работаем над авто парсером данных из сообщений. Сервис будет фронтовой. Основная цель если вам написали «Не забудь» или «Завтра надо напомнить» и еще «1500» скриптов которые находят определенные слова — создается напоминание в ToDo листе, с автоматической датой.
Есть паки эмодзи — пользователи сами могут их создавать и настраивать «Публичный» или «Личный».
Back‑end архитектура и пример сохранения сообщений
Верхнеуровневый стэк бэка
Язык: Python 3.10+
Фреймворк: FastAPI + Uvicorn
Базы данных:
MongoDB — основное хранилище
Redis — кэш, pub/sub, очереди
HTTP клиент: httpx
Аутентификация: PyJWT, bcrypt
Real‑time: FastAPI WebSocket + Redis Pub/Sub
Голос: LiveKit + faster‑whisper (транскрипция)
Изображения: Pillow
Название | Краткое описание | Полное описание | Системы защиты |
|---|---|---|---|
API Gateway (8080) | Единая точка входа | Принимает все запросы от клиентов, проверяет JWT токены, делает rate limiting, управляет circuit breaker при падении сервисов. Проксирует запросы дальше по нужным сервисам. Тут же живёт middleware для проверки прав через permissions-service. | JWT валидация — токены проверяются локально через HS256, без похода в auth-service. Проверяется срок жизни, тип токена, наличие в blacklist. Rate Limiting — ограничение запросов через Redis: 300 req/min по умолчанию, 5 для логина, 3 для регистрации. Превысил — получаешь 429. Circuit Breaker — если backend упал 5 раз подряд, Gateway перестаёт слать туда запросы на 30 секунд, возвращает 503 сразу. Защита от каскадных падений. Brute-force protection — после 5 неудачных логинов аккаунт блокируется с квадратичной прогрессией (30с, 120с, 270с...). Отдельно трекается по email и по IP. Headers sanitization — все X-* заголовки от клиента удаляются, Gateway сам добавляет доверенные (X-User-Id, X-Gateway-Key). |
Auth Service (8001) | Авторизация | Регистрация, логин, выдача и обновление JWT токенов. Хранит пользователей в MongoDB, сессии в Redis. Другие сервисы ходят сюда за данными юзера через internal API. | Bcrypt хеширование — пароли хешируются с 12 раундами, в базе хранится только хеш. JWT с коротким TTL — access токен живёт 30 минут, refresh — 7 дней. Token blacklist — при логауте jti токена добавляется в Redis blacklist, повторно использовать нельзя. Internal API Key — эндпоинты /internal/* принимают запросы только с валидным X-Internal-API-Key или с localhost. Input validation — email проверяется regex'ом, username допускает только латиницу/кириллицу/цифры/пробелы/дефисы (защита от injection), пароль требует минимум 8 символов + uppercase + lowercase + цифру. Pydantic schemas — все входящие данные проходят через типизированные модели, невалидный JSON отклоняется до попадания в бизнес-логику. |
User Service (8004) | Профили пользователей | Работа с профилями: смена username, bio, загрузка/удаление аватарок. Отслеживает онлайн-статус пользователей, рассылает его через WebSocket. | Gateway Key проверка — все публичные эндпоинты требуют X-Gateway-Key, прямой доступ к сервису запрещён (403). Валидация файлов — при загрузке аватарок проверяется MIME-тип и размер. ObjectId validation — все ID проверяются на валидность MongoDB ObjectId (защита от NoSQL injection). String sanitization — строки обрезаются до max_length, удаляются пробелы по краям. |
Chat Service (8003) | Управление чатами | Создание чатов (general, direct, task), добавление/удаление участников, инвайт-ссылки. Слушает Redis для событий типа "пользователь вышел из чата". | Gateway Key проверка — прямой доступ запрещён. Membership check — перед любым действием проверяется, что юзер состоит в чате. Rate limiter — 30 операций в минуту на юзера, при спаме блокировка на 2 минуты. Invite expiration — инвайты протухают через заданное время, cleanup-задача их удаляет. Input validation — ObjectId валидация, Pydantic schemas для всех запросов. ContentSanitizer — санитизация названий чатов: экранирование HTML, удаление javascript:/data: URI, очистка от event handlers (onclick и т.п.). |
Messages Service (8014) | Сообщения | Отправка, редактирование, удаление сообщений. Реакции эмодзи, форварды, пины, поиск по сообщениям. Отдаёт историю с cursor-пагинацией. | Gateway Key проверка — прямой доступ запрещён. Ownership check — редактировать/удалять сообщение может только автор (или модератор с правами MANAGE_MESSAGES). Rate limiter — защита от флуда сообщениями (30 msg/min). Permission check — перед отправкой проверяется право SEND_MESSAGES через permissions-service. XSS protection — ContentSanitizer экранирует HTML через html.escape(), удаляет javascript: и data:text/html схемы, вырезает on* event handlers. Code block validation — блоки кода проверяются на опасные паттерны (eval, exec, subprocess, os.system, child_process). Null byte removal — удаление \x00 из сообщений. Max length — сообщения ограничены 50000 символов. |
Topics Service (8002) | Каналы внутри чата | Создание топиков (каналов) в чате, группировка их в папки, изменение порядка. Отмечает прочитанное по топикам. Работает только для general-чатов. | Gateway Key проверка — прямой доступ запрещён. Permission check — создание/удаление топиков требует права MANAGE_CHANNELS. Topic visibility — через permissions-service проверяется, может ли юзер видеть конкретный топик (role-based access). Input validation — ObjectId валидация, sanitize_string для названий топиков. |
Threads Service (8013) | Треды | Ветки обсуждений под конкретными сообщениями. Создание треда, получение сообщений треда, удаление. | Gateway Key проверка — прямой доступ запрещён. Permission check — создание требует SEND_MESSAGES, удаление — MANAGE_MESSAGES. Parent message validation — нельзя создать тред к несуществующему сообщению. ObjectId validation — защита от NoSQL injection. |
Permissions Service (8011) | Права доступа | Система ролей по типу Discord. Создание ролей, назначение юзерам, проверка прав (can send message, can manage channels и т.д.). Переопределения прав на уровне топиков. | Gateway Key проверка — прямой доступ запрещён. Role hierarchy — роли упорядочены по позиции, нельзя редактировать роль выше своей. Permission inheritance — права наследуются от @everyone, потом накладываются роли и topic overrides. Internal API — check_permission вызывается из Gateway middleware перед каждым защищённым эндпоинтом. Pydantic validation — все запросы типизированы. |
File Service (8005) | Загрузка файлов | Чанковая загрузка больших файлов, генерация превью для картинок, скачивание. Хранит файлы на диске, метаданные в MongoDB. | Gateway Key проверка — прямой доступ запрещён. File size limits — максимум 100MB на файл, 10MB на картинку. MIME type whitelist — только разрешённые типы (image/jpeg, image/png, video/mp4, application/pdf и т.д.), остальное отклоняется. Filename sanitization — из имён файлов удаляются опасные символы (<>:"/|?*), path traversal (../../) отсекается, длина ограничена 255 символов. Permission check — для загрузки нужно право ATTACH_FILES в чате. Ownership — скачать файл может только участник чата, в который он загружен. Antivirus scan — загруженные файлы проверяются на вирусы перед финализацией. Chunked upload validation — проверяется соответствие размера чанков заявленному. |
Voice Service (8010) | Голосовые комнаты | Подключение к голосовым каналам через LiveKit. Управление состоянием участников (mute, deaf, speaking). Прямые звонки между юзерами в direct-чатах. | Gateway Key проверка — прямой доступ запрещён. LiveKit tokens — для подключения к комнате генерируется одноразовый JWT с API_SECRET, срок жизни 24 часа. Permission check — для входа в голосовой канал нужно право CONNECT. Room isolation — каждая комната изолирована, LiveKit проверяет токен на принадлежность к комнате. Pydantic schemas — все запросы валидируются. |
Transcription Service (8015) | Расшифровка голоса | Бот, который подключается к голосовым комнатам и транскрибирует речь через Whisper. Работает в фоновом потоке, пишет результаты в чат. | Gateway Key проверка — прямой доступ запрещён. Internal API Key — управляющие эндпоинты доступны только с валидным ключом. Bot token — бот подключается к LiveKit со своим служебным токеном. |
WebSocket Service (8006) | Realtime-соединения | Держит WebSocket-коннекты с клиентами. Другие сервисы шлют ему события через HTTP, он раздаёт их нужным юзерам. Типичные события: new_message, typing, status_changed. | JWT при подключении — при установке WS-соединения проверяется токен из query-параметра. Room isolation — юзер получает события только из чатов, в которых состоит. Internal API — эндпоинты для рассылки событий принимают только запросы с X-Internal-API-Key. |
Links Service (8009) | Превью ссылок | Парсит URL из сообщений, вытаскивает Open Graph теги (title, description, image). Возвращает данные для красивых карточек-превью. | Gateway Key проверка — прямой доступ запрещён. Request timeout — запросы к внешним сайтам ограничены 5 секундами, чтобы не зависнуть. URL validation — базовая проверка что URL выглядит валидным. SSRF protection — не ходит на внутренние адреса (localhost, 127.0.0.1, private IP ranges). |
Jira Service (8008) | Интеграция с Jira | Создание task-чатов привязанных к тикетам Jira. Получение информации о тикетах, синхронизация статусов. | Gateway Key проверка — прямой доступ запрещён. API Token — к Jira сервис ходит с отдельным API-токеном, который не светится клиентам. Credentials isolation — логин/пароль Jira хранятся только в env-переменных сервиса. |
Emoji Service (8016) | Кастомные эмодзи | Паки эмодзи для чатов. Загрузка своих эмодзи, системный пак по умолчанию. Резолвинг шорткодов типа :smile: в картинки. | Gateway Key проверка — прямой доступ запрещён. Pack ownership — управлять паком может только создатель или админ чата. File validation — проверка формата загружаемых эмодзи. Code sanitization — коды эмодзи проверяются на допустимые символы. |
Cleanup Service (8007) | Фоновая очистка | Периодически подчищает старые данные: просроченные инвайты, помеченные на удаление файлы, временные файлы загрузки. | Internal only — сервис вообще не торчит наружу, только internal API. Soft delete — файлы сначала помечаются удалёнными, потом cleanup их физически удаляет. |

Пример хранения сообщения.
{ "_id": "ObjectId('507f1f77bcf86cd799439011')", "chat_id": "507f1f77bcf86cd799439012", "topic_id": "507f1f77bcf86cd799439013", "thread_id": "507f1f77bcf86cd799439014", "sender_id": "507f1f77bcf86cd799439015", "content": "Привет! Вот файл: [file:507f1f77bcf86cd799439020:document.pdf]", "message_type": "text", "reply_to_id": "507f1f77bcf86cd799439016", "forwarded_from": { "original_message_id": "507f1f77bcf86cd799439018", "original_chat_id": "507f1f77bcf86cd799439019", "original_sender_id": "507f1f77bcf86cd799439017", "forwarded_by": "507f1f77bcf86cd799439015", "forwarded_at": "2024-01-15T10:30:00.000Z" }, "reactions": { "👍": ["user_id_1"], "company/lgtm": ["user_id_2", "user_id_3"], "❤️": ["user_id_4"], "stickers/cool": ["user_id_1", "user_id_5"] }, "read_by": [ "507f1f77bcf86cd799439015", "507f1f77bcf86cd799439017" ], "is_edited": false, "is_deleted": false, "status": "sent", "created_at": "2024-01-15T10:30:00.000Z" }
Ключ | Тип | Описание |
|---|---|---|
| ObjectId | Уникальный идентификатор сообщения. Генерируется MongoDB автоматически при вставке. Используется как |
| string | ID чата, в который отправлено сообщение. Ссылка на коллекцию |
| string | ID топика (канала) внутри чата. Только для чатов типа |
| string | ID треда, если сообщение является частью ветки обсуждения. Ссылка на коллекцию |
| string | ID автора сообщения. Ссылка на коллекцию |
| string | Содержимое сообщения. Обычный текст с поддержкой Markdown. Файлы встраиваются в формате |
| string | Тип сообщения. Определяет как рендерить контент. Значения: |
| string | ID сообщения, на которое отвечает текущее. При выдаче через API подгружаются данные оригинала в поле |
| object | Метаданные пересылки. Содержит |
| object | Реакции на сообщение. Словарь где ключ — идентификатор эмодзи, значение — массив user_id проголосовавших. Поддерживает два формата ключей: Unicode эмодзи ( |
| array | Список user_id пользователей, которые прочитали сообщение. Используется для подсчёта прочитавших и отображения галочек. |
| boolean | Флаг редактирования. |
| boolean | Soft delete флаг. |
| string | Статус доставки. Значения: |
| datetime | Время создания сообщения. UTC. Индексируется для сортировки и пагинации. |
Сейчас у нас насчитывается 15 сервисов и 5 сервисов инфраструктуры. Монгу будем переносить на кассандру в будущем (Она более отказоустойчивая и отлично подходит для сообщений).
Ответы на возможные вопросы
Выложи в открытый доступ, сейчас такое через AI за неделю можно сделать
Можно, можно и за 4 дня сделать, можно и бесплатно на работе работать. UI будет выложен в открытый доступ позже, бэк будет в закрытом. По причине что тратил большое кол‑во личного времени, а последнее время и вместе со знакомым. Уважаю наше время.
Будет ли версия для мобилок?
Да, она делается. Но не отдельным кодом, а через стили и адаптив. Сейчас почти все готово для компиляции на IOS и Android.
Скриншотов с мобилки к сожалению нет, будет аналогично веба.

Что со звуком?
Звук получилось сделать довольно чистым. Шумы клавиатуры, мыши, ударов, и прочие легкие шумы — отсекаются. Если на фоне музыка или кино — приглушается без потери качества голоса, при этом голос усиливается. Для этого сделали отдельный AudioProcessor на js.
Какое кол-во пользователей может быть в моменте на встрече?
Согласно документации LiveKit может быть 3000 слушателей и 10 спикеров в моменте. Но закладываем на 1000 слушателей и 20–30 спикеров. Надеюсь получиться выйти на такой показатель. Пока еще не тестировали на открытых серверах
Всем спасибо за прочтение! Очень мало функционала показал, следующую - более детальную по правам, звуку - покажу в следующем посте.
