П/ВИН

Anti-ban улучшения Telegram: geo-match, typing, лимиты

·8 мин чтения
Anti-ban улучшения Telegram: geo-match, typing, лимиты

Каждый, кто хоть раз занимался массовой автоматизацией Telegram, знает этот неприятный момент: ты запускаешь систему, всё работает несколько дней — и вдруг пачка аккаунтов улетает в бан. Причём без очевидной причины. Именно с этим мы и столкнулись в проекте tg-army, и именно для этого был разработан и реализован план anti-ban улучшений, который я хочу подробно разобрать.

Заодно расскажу про параллельную работу по рефакторингу импорта аккаунтов и исследование стека для автоматизации других соцсетей — всё это части одного большого продукта.

Контекст: что такое tg-army

tg-army — это сервис для массовой работы с Telegram-аккаунтами. Грубо говоря, оркестратор, который управляет пулом аккаунтов, рассылает сообщения, следит за состоянием каждого аккаунта и умеет закупать новые через сторонние маркетплейсы. Написан на Python, данные хранятся в PostgreSQL, фронт — небольшое веб-приложение для управления.

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

Четыре причины банов — и четыре решения

Анализ поведения системы и изучение материалов по anti-ban тактикам (в том числе блог GramGPT) помог выделить четыре ключевых паттерна, которые триггерили заморозку аккаунтов.

1. Geo-Match Validation: аккаунт и прокси должны быть из одной страны

Первая и, пожалуй, самая очевидная проблема — несоответствие геолокации аккаунта и прокси, через который он работает. Telegram отлично видит, когда российский аккаунт внезапно начинает сессию с нидерландского IP. Это красный флаг.

Мы добавили функцию validate_geo_match в account_pool.py, которая сравнивает страну аккаунта (берётся с маркетплейса при покупке) со страной прокси:

def validate_geo_match(account: Account, proxy: Proxy) -> GeoMatchResult:
    """
    Проверяет соответствие геолокации аккаунта и прокси.
    Возвращает GeoMatchResult с полем is_match и деталями.
    """
    account_country = account.country_code  # например 'RU'
    proxy_country = proxy.country_code      # например 'NL'
    
    is_match = account_country == proxy_country
    return GeoMatchResult(
        is_match=is_match,
        account_country=account_country,
        proxy_country=proxy_country
    )

Если гео не совпадает — система логирует предупреждение в lolz_market.py при закупке аккаунта и блокирует настройку в account_setup.py на этапе check_all(). Аккаунт помечается как требующий смены прокси, а не просто молча падает.

Покрыто тестами в tests/test_geo_match.py — 12 тестов, все зелёные.

2. IP Settle Delay: аккаунт должен «отлежаться» на новом IP

Даже если гео совпадает, резкая смена IP — это стресс для аккаунта. Telegram замечает, когда аккаунт, который последние недели сидел с одного IP, вдруг оказывается на другом и сразу начинает активную деятельность.

Решение — задержка перед стартом настройки. Мы ввели 12-часовой период «оседания» (IP_SETTLE_DELAY = 12 * 3600), в течение которого аккаунт просто существует на новом IP без какой-либо активности.

Функция should_start_setup в account_setup.py:

def should_start_setup(account: Account, settings: Settings) -> bool:
    """
    Проверяет, прошло ли достаточно времени с момента
    назначения прокси для безопасного старта настройки.
    """
    if account.proxy_assigned_at is None:
        return False
    
    settle_delay = settings.get('IP_SETTLE_DELAY', 12 * 3600)
    elapsed = time.time() - account.proxy_assigned_at.timestamp()
    return elapsed >= settle_delay

Важный инсайт из исследования GramGPT: мы изначально правили профиль (аватар, имя, bio) сразу после закупки аккаунта. Это был главный триггер заморозки. Правильный порядок — сначала 24-48 часов отлёжки, потом 2-4 дня прогрева, и только потом настройка профиля.

3. Typing Simulation: имитация человеческого печатания

Боты, которые отправляют сообщения мгновенно без каких-либо задержек — слишком очевидны для антифрод-систем. Человек всегда тратит время на набор текста.

Мы добавили в sender.py две функции: calc_typing_delay вычисляет реалистичную задержку на основе длины сообщения, а _simulate_typing отправляет статус «печатает...» через Telethon API:

def calc_typing_delay(text: str, wpm: int = 45) -> float:
    """
    Рассчитывает задержку печатания.
    Средний человек печатает ~45 слов в минуту.
    Добавляем небольшой случайный разброс для реализма.
    """
    words = len(text.split())
    base_delay = (words / wpm) * 60  # в секундах
    jitter = random.uniform(0.8, 1.2)  # ±20% вариации
    return max(1.0, base_delay * jitter)
 
async def _simulate_typing(self, client, chat_id: int, duration: float):
    """Отправляет статус typing на заданное время."""
    async with client.action(chat_id, 'typing'):
        await asyncio.sleep(duration)

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

4. Channel-Per-Account Limit: один аккаунт не должен быть в слишком многих каналах

Ещё один триггер — аккаунт, который вступает в 50+ каналов за короткое время или одновременно состоит в сотнях каналов. Telegram расценивает это как спам-поведение.

Мы добавили функцию count_account_channels() в pg_database.py, которая считает, сколько каналов уже привязано к аккаунту:

async def count_account_channels(self, account_id: int) -> int:
    """Возвращает количество каналов, привязанных к аккаунту."""
    result = await self.pool.fetchval(
        'SELECT COUNT(*) FROM channel_accounts WHERE account_id = $1',
        account_id
    )
    return result or 0

В _select_account() добавлена проверка лимита — по умолчанию MAX_CHANNELS_PER_ACCOUNT = 20. Аккаунт, уже работающий с 20 каналами, не получает новых назначений:

# В _select_account() перед возвратом аккаунта
channel_count = await self.db.count_account_channels(account.id)
if channel_count >= settings.get('MAX_CHANNELS_PER_ACCOUNT', 20):
    continue  # ищем следующий подходящий аккаунт

TDD-подход: сначала тест, потом код

Все четыре улучшения реализованы через TDD — сначала пишем падающий тест, потом пишем код, который его проходит. Это позволило:

  • Чётко зафиксировать ожидаемое поведение до написания реализации
  • Убедиться, что тест действительно проверяет нужное (а не проходит сразу)
  • Иметь регрессионное покрытие для будущих изменений

Итог — 12 новых тестов в tests/test_geo_match.py и tests/test_ip_settle.py, все зелёные. Пример теста для geo-match:

def test_geo_match_same_country():
    account = Account(country_code='RU')
    proxy = Proxy(country_code='RU')
    result = validate_geo_match(account, proxy)
    assert result.is_match is True
 
def test_geo_match_different_country():
    account = Account(country_code='RU')
    proxy = Proxy(country_code='NL')
    result = validate_geo_match(account, proxy)
    assert result.is_match is False
    assert result.account_country == 'RU'
    assert result.proxy_country == 'NL'

Рефакторинг импорта аккаунтов

Параллельно с anti-ban работой мы починили импорт аккаунтов из архивов lolz.team. Проблема была простой, но неочевидной: bulk_import.py открывал JSON-файлы без явного указания кодировки.

# Было (падало с UnicodeDecodeError на cp1251 файлах)
with open(json_file) as f:
    meta = json.load(f)
 
# Стало (с graceful fallback на cp1251)
with open(json_file, 'rb') as f:
    raw = f.read()
try:
    meta = json.loads(raw.decode('utf-8'))
except UnicodeDecodeError:
    meta = json.loads(raw.decode('cp1251'))

Архивы с lolz.team генерируются на Windows-серверах, и часть JSON-файлов содержит метаданные в кодировке Windows-1251. Байт 0xC8 — это буква «И» в cp1251. Python 3 на Linux по умолчанию ожидает UTF-8 и падал именно на таких байтах.

Заодно убрали отдельную страницу «ZIP Импорт» из навбара — перенесли ZIP-загрузку карточкой на основную страницу /import. Стало чище: один роут, три способа импорта на одной странице.

Исследование стека для мультиплатформенной автоматизации

Одновременно шло исследование расширения системы на Instagram, TikTok и другие платформы. Запустили 10 параллельных агентов для поиска актуальных решений на март 2026.

Ключевые находки:

Instagram: instagrapi (6k+ звёзд) — де-факто стандарт для Instagram Private API на Python. Поддерживает DM, комментарии, загрузку Reels. Авторы активно обновляют, есть async-форк aiograpi.

Anti-detect браузеры: AdsPower, GoLogin, Dolphin Anty — все живы в 2026, активно развиваются. Для наших задач (Telethon/MTProto) они не нужны, но актуальны для Instagram/TikTok через браузер.

Конвертация tdata: для импорта аккаунтов в формате Telegram Desktop (tdata) существует библиотека opentele, которая умеет конвертировать tdata → Telethon session. Пока отложили в пользу нативного Telethon-формата.

Деплой и результат

Все настройки вынесены в БД — IP_SETTLE_DELAY, MAX_CHANNELS_PER_ACCOUNT, параметры typing. Это позволяет менять их без деплоя.

Сервис tg-army-orchestrator рестартован без ошибок. Импорты модулей чистые. 12 новых тестов — все проходят.

Изменения затронули файлы:

  • src/account_pool.py — geo-match валидация
  • src/account_setup.py — IP settle delay + интеграция проверок в check_all()
  • src/lolz_market.py — предупреждение при гео-несоответствии
  • src/sender.py — typing simulation во всех методах отправки
  • src/pg_database.pycount_account_channels()
  • src/bulk_import.py — cp1251 fallback
  • tests/test_geo_match.py, tests/test_ip_settle.py — новые тесты

Выводы и уроки

Анти-бан защита — это не одна волшебная настройка, а система из нескольких независимых слоёв. Каждый слой по отдельности снижает риск незначительно, но вместе они создают профиль поведения, неотличимый от человеческого. Geo-match убирает самый грубый сигнал — несоответствие страны. Settle delay имитирует естественное поведение при смене устройства. Typing simulation убирает паттерн мгновенных сообщений. Лимит каналов предотвращает флаг «слишком активный бот».

ТDD в данном случае оказался не просто хорошей практикой, а практической необходимостью. Когда пишешь защиту от банов, очень легко написать код, который «выглядит правильно», но не покрывает крайние случаи. Тесты заставляют явно описать все сценарии: что если gео совпадает, что если нет, что если прокси не назначен, что если прошло ровно 12 часов.

Проблема с кодировкой cp1251 — классический пример технического долга на стыке экосистем. Windows-генерируемые файлы на Linux-сервере без явного указания кодировки — это бомба замедленного действия. Правило простое: всегда явно указывай кодировку при открытии файлов, а если работаешь с внешними источниками — добавляй fallback.

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

План 2026-03-13-anti-ban-improvements.md реализован полностью. Следующий шаг — мониторинг показателей банов в продакшене и тюнинг параметров IP_SETTLE_DELAY и MAX_CHANNELS_PER_ACCOUNT на основе реальных данных. Anti-ban — это итеративный процесс, не одноразовое решение.

Полезные ссылки

Паша Вин
Паша Вин

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.