Реферальная система в Telegram ботах

  • Tutorial

Всем привет! Наверняка вы видели в различных ботах реферальную ссылку типа https://t.me/<юзернейм_бота>?start=<число>. Обычно в качестве числа указывается Telegram ID реферера. В этой статье я расскажу как обрабатывать такие ссылки в своем боте.

Для разработки ботов я использую Python библиотеку pytelegrambotapi.

Процесс создания бота через @BotFather опущу, приступлю сразу к коду. Есть файл config.py с переменной TOKEN, в которой хранится токен бота. В файле bot.py будем "химичить". Напишем обработчик команды /start и выведем все, что хранится в объекте сообщения.

import telebot

import config

bot = telebot.TeleBot(config.TOKEN)


@bot.message_handler(commands=["start"])
def start_command_handler(msg):
    print(msg)


if __name__ == '__main__':
    bot.polling(none_stop=True)

Нас интересует строчка 'text': '/start'. Попробуем теперь перейти по ссылке вида https://t.me/<юзернейм_бота>?start=test и изменим 10-ю строчку на print(msg.text)

В консоли вывелось/start test. Делаем вывод, что в msg.text хранится необходимая нам информация из реферальной ссылки, которую необходимо обработать.

Приступим к обработке. Учитываем, что не всегда пользователь нажимает /start по реферальной ссылке, поэтому текста после /start может и не быть. Отличительным символом служит пробел (вспоминаем /start test). Значит будем проверять наличие ID реферера по нему.

@bot.message_handler(commands=["start"])
def start_command_handler(msg):
    user_id = msg.from_user.id
    referrer = None

    if " " in msg.text:
        referrer_candidate = msg.text.split()[1]
				# Идем далее

Если пользователь перейдет по ссылке https://t.me/<юзернейм_бота>?start=test test2, То test2 не попадет в msg.text, потому что там имеется пробел, который говорит, что test2 не является частью ссылки. Именно поэтому в msg.text находится только один символ пробела. А значит если он имеется, то и есть некоторая информация из реферальной ссылки. Поэтому в первой ветке мы создаем список (msg.text.split()), и берем его второй элемент (всем ведь известно, что нумерация списка идет с нуля, да?).

Хорошо, на этом этапе мы имеем текст из ссылки. Мы подразумеваем, что там число, но от пользователя можно поджидать чего угодно. Вдруг кому-то захочется "сломать" бота, указав в ссылке вместо ID реферера какой-то текст. Для этого проверим данные.

@bot.message_handler(commands=["start"])
def start_command_handler(msg):
    user_id = msg.from_user.id
    referrer = None

    # Проверяем наличие хоть какой-то дополнительной информации из ссылки
    if " " in msg.text:
        referrer_candidate = msg.text.split()[1]

        # Пробуем преобразовать строку в число
        try:
            referrer_candidate = int(referrer_candidate)
						# Идем далее
        except ValueError:
            pass

Здесь, используя try... except мы преобразуем при помощи int() информацию в число. Если в переменной referer хранится не число, то int() вызовет ошибку ValueError

Теперь в referer записано число. Но тут появляется загвоздка: функция int() может перевести строку "-101" в число -101. То есть на данном этапе в referer может храниться отрицательное число. Почему это не важно, расскажу чуть позднее.

Пользователь может вставить в ссылку свой TG ID. Реферальная система подразумевает под собой некий бонус за переход, поэтому таким образом можно выдать самому себе бонус. Предусмотрим это. ID пользователя получаем, используя user_id = msg.from_user.id

@bot.message_handler(commands=["start"])
def start_command_handler(msg):
    user_id = msg.from_user.id
    referrer = None

    # Проверяем наличие хоть какой-то дополнительной информации из ссылки
    if " " in msg.text:
        referrer_candidate = msg.text.split()[1]

        # Пробуем преобразовать строку в число
        try:
            referrer_candidate = int(referrer_candidate)

            # Проверяем на несоответствие TG ID пользователя TG ID реферера
            if user_id != referrer_candidate:
              # Идем дальше
							pass

        except ValueError:
            pass

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

@bot.message_handler(commands=["start"])
def start_command_handler(msg):
    user_id = msg.from_user.id
    referrer = None

    # Проверяем наличие хоть какой-то дополнительной информации из ссылки
    if " " in msg.text:
        referrer_candidate = msg.text.split()[1]

        # Пробуем преобразовать строку в число
        try:
            referrer_candidate = int(referrer_candidate)

            # Проверяем на несоответствие TG ID пользователя TG ID реферера
            # Также проверяем, есть ли такой реферер в базе данных
            if user_id != referrer_candidate and referrer_candidate in get_all_users():
                referer = referrer_candidate

        except ValueError:
            pass

Что необходимо делать в случае, если у пользователя и так уже есть реферер? Не будем ведь при каждом использовании реферальной ссылки выдавать бонус за приведенного реферала, это нас попросту разорит. Поэтому необходимо написать проверку. Напишем функцию has_referrer(), которая вернет True при наличии у пользователя реферера и False при его отсутствии. Логично ее написать в начале для оптимизации.

@bot.message_handler(commands=["start"])
def start_command_handler(msg):
    user_id = msg.from_user.id
    # Проверяем наличие закрепленного реферера за пользователем
    if not has_referrer():
        referrer = None

        # Проверяем наличие хоть какой-то дополнительной информации из ссылки
        if " " in msg.text:
            referrer_candidate = msg.text.split()[1]

            # Пробуем преобразовать строку в число
            try:
                referrer_candidate = int(referrer_candidate)

                # Проверяем на несоответствие TG ID пользователя TG ID реферера
                # Также проверяем, есть ли такой реферер в базе данных
                if user_id != referrer_candidate and referrer_candidate in get_all_users():
                    referer = referrer_candidate

            except ValueError:
                pass

Надеюсь, что статья будет для вас полезна!

Комментарии 7

    +1

    Привет:) Статью прочитал, надо больше практики, а то на код страшновато смотреть, хоть для первого раза это вполне себе неплохо)
    Но, в первую очередь, хотел бы посоветовать тебе использовать aiogram, вместо telebot.
    Плюсы aiogram и его преимущества перед telebot:
    1) Бот не падает на поллинге.
    2) Наличие машины состояний (FSM), а не тупого next_step_handler.
    3) Наличие Middleware и фильтров.
    4) Поддержка API день в день: по статистике, аио выходит раньше других.
    5) Errors handler и исключения (в телеботе общие исключения на все типы ошибок).
    6) В телеботе исходники без тайп хинтинга и автодополнение работает хуже.
    7) Аиограм — это полноценный фреймворк, а не обёртка над апи (телебот же это обертка обёртка). Приятно смотреть на структуру.
    8) Ведётся не только поддержка API, но и развитие самого фреймворка: в третьей версии будут улучшения в структуре и логике, новые фичи.
    9) Адекватное комьюнити в телеге (ru: @aiogram_ru, eng: @aiogram).

      0
      Здравствуйте! Можете сказать, пожалуйста, что именно с кодом не так?
        0
        Подозреваю, что минусуют за то, что в статье можно было лишь ограничиться информацией о том, что через ссылку на бота можно передать произвольное значение параметра.
        Остальное реализуется своими силами на удобном языке.
        И тогда статья бы влезла в 160 символов и напоминала бы скорее твитт.
        А как и на чём писать hello,world бота для telegram уже давно протрубили из каждого чайника.
          0

          Более того это описано в официальной доке и в куче примеров от телеги.

            0
            Вы не представляете, насколько детские недочеты ляпает каждый второй автор helloWorld «статей», даже тут, на Хабре. Их может быть и миллион, но это не показатель. Видимо пока их не будет 2 миллиона, ничего не поменяется. Как пример, абсолютно все дают инструкцию: «Запускайте командой»
            python first_bot.py

            Но ни у одного не дошла голова, что тот, кто читает такие инструкции — абсолютный новичок и если ему не сказать, что на Linux системах надо писать не python, а python3, новенький будет биться в агониях не понимая, в чем дело. Получается, что учителей много, а реально за руку никто вести не хочет, так что справедливости ради, чем больше самых примитивных статей, затрагивающих разные аспекты, как первоначальной настройки, так и API — тем лучше.
            0

            Лучше для проверки значений, если хочешь проверить int()

            Используй: some_text.isdigit()

            Он вернет True, если текст int()

            Так красивее выглядит и лаконичней, чем исключения.

              0

              Насколько я знаю, if работает медленнее, чем конструкция try…except, поэтому использую ее. Насчёт лаконичности согласен

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

            Самое читаемое