Хардкорная разработка под телеграм. бот-модератор своими руками. часть 1

Шаг 0: теоретическая база об API Telegram-ботов

Основной инструмент, который используют при создании Телеграм-ботов – это интерфейс прикладного программирования HTML, или API HTML. Этот элемент принимает запросы посетителей и отправляет ответы в виде информации. Готовые конструкции упрощают работу над программой. Чтобы написать бот для Телеграма, необходимо воспользоваться этим электронным адресом: https://api.telegram.org/bot<token>/METHOD_NAME

Для правильного функционирования бота также нужен токен – комбинация символов, защищающая программу и открывающая доступ к ней доверенным разработчикам. Каждый токен уникален. Строка присваивается боту при создании. Методы могут быть разными: getUpdates, getChat и прочие. Выбор метода зависит от того, какого алгоритма работы разработчики ожидают от бота. Пример токена:

123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11

В ботах используются запросы GET и POST. Параметры методов нередко приходится дополнять – к примеру, когда по задумке метод sendMessage должен отправить id чата и какой-либо текст. Параметры для доработки метода можно передать строкой запроса URL с помощью application/x-www-form-urlencoded или через application-json. Эти способы не подходят для загрузки файлов. Также обязательна кодировка UTF-8. Отправив запрос к API, можно получить результат в JSON-формате. Взгляните на ответ программы на извлечение информации через метод getME:

GET https://api.telegram.org/bot<token>/getMe{   ok: true,   result: {       id: 231757398,       first_name: "Exchange Rate Bot",       username: "exchangetestbot"   }

}

Существует два способа получить пользовательские сообщения в ботах. Обе методики действенны, но подходят в разных случаях. Чтобы получить сообщения, можно вручную написать запрос с методом getUpdates – программа выдаст на экран массив данных Update. Запросы нужно отправлять регулярно, после анализа каждого массива отправка повторяется. Избежать повторного появления проверенных объектов поможет offset – параметр, определяющий количество пропущенных записей перед загрузкой нового результата. Преимущества метода getUpdates проявятся, если:

  • нет возможности настроить HTTPS;
  • используются сложные языки сценариев;
  • сервер бота время от времени меняется;
  • бот нагружен пользователями.

Второй метод, который можно прописать для получения пользовательских сообщений – setWebhook. Он используется один раз, не нужно постоянно отправлять новые запросы. Webhook пересылает обновления данных на указанный адрес URL. Для применения этого способа потребуется SSL-сертификат. Webhook будет полезен в этих случаях:

  • используются веб-языки программирования;
  • бот не перегружен, пользователей не слишком много;
  • сервер не меняется, программа остается на одном сервере на долгое время.

Telegram-сервис @BotFather предназначен для создания чат-ботов. Основные настройки также устанавливаются через эту систему – BotFather поможет сделать описание, поставить фотографию профиля, добавить инструменты поддержки. Библиотеки – наборы HTML-запросов для Телеграм-ботов – доступны в интернете, их достаточно много. При создании программы-примера применена pyTelegramBotApi.

Инициализация класса бота

Процедура требует создания файла «wabot.py», после чего осуществляется описание класса для разрабатываемого бота. Программист не должен забывать импортировать сведения библиотеки.

Обратите внимание, библиотека json  предусматривает обработку одноименного формата. Используется с целью обращения к АРI сайта

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

  1. Перейти в раздел проведения тестирований.
  2. Инициировать анализ сформированных запросов.
  3. Протестировать Webhook.

Дополнительно требуется присвоить атрибут класса параметров. При этом Dict_messages  — словарь, включающий в себя сведения из уведомлений в формате json. Чтобы изучить структуру, требуется выполнить переход в категорию «Проверка WebHook». В дальнейшем выполняется запуск тестирования и отправляется сообщение с любым содержанием в мобильное приложение. На дисплее автоматически отображается оповещение.

Что вы можете ожидать от этой серии?

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

Начинающий:Общая идея о том, как структура разработана и используется для этого конкретного проекта. Вы должны быть в состоянии загрузить коды с Github и успешно завершить настройку. Это включает в себя установку пакетов, создание и настройку Slack и учетной записи IBM Watson, запуск однократных файлов для генерации ссылок и рекомендаций к фильмам. Вы можете добавить дополнительные навыки в IBM Watson (например, небольшую беседу, генерирующую статические ответы) и увидеть результаты в слабой среде.

Промежуточное:Вы должны иметь возможность использовать эту платформу в качестве шаблона для разработки своего собственного чат-бота, который можно развернуть в другом домене. Кроме того, вы можете расширить базу знаний для чат-бота, добавив новые источники данных, которые включают в себя написание кодов для подключения к различным базам данных (эластичный поиск, базы данных SQL, Excel и т. Д.). Кроме того, вы можете добавить дополнительные функции НЛП для бота и увидеть результаты в спокойной среде.

Выбор бокса и системы

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

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

Операционная система подойдет любая, но если это ваш первый проект, то лучше выбрать Ubuntu: она проще в администрировании, чем CentOS.

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

Для Ubuntu обновление проводится в 2 команды:

root@box-10000:~# apt-get update
root@box-10000:~# apt-get upgrade

Их можно совместить в одну:

root@box-10000:~# apt-get update && apt-get upgrade

Когда все основные компоненты системы будут обновлены, можно приступать к подготовке бокса к размещению телеграм-бота.

Создание бота

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

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

Выберите его и в диалоговом окне напишите команду /start и бот в ответном сообщение пришлет список всех доступных команд:

Нас интересует создание нового бота, поэтому выбираем команду /newbot. Команду можно как напечатать самостоятельно, так и выбрать мышью в сообщении и она автоматически отправится:

Первым шагом нам предлагают дать имя новому боту, оно может быть произвольным. Мы назовем его PocketAdmin:

Теперь требуется указать идентификатор бота (username), он должен заканчиваться на _bot и быть уникальным в системе. Мы укажем PocketAdminTech_bot:

На этом создание бота завершено. В последнем сообщении нам пришла ссылка на нашего нового бота t.me/PocketAdminTech_bot и токен (закрашен), необходимый для взаимодействия с API.

Обязательно сохраните токен и храните его в тайне!

Модифицируем код

Отлично! Теперь нам осталось заменить строку в функции на . Ваш код должен выглядеть так:

from telegram.ext import Updater, InlineQueryHandler, CommandHandlerimport requestsimport redef get_url():    contents = requests.get('https://random.dog/woof.json').json()    url = contents    return urldef get_image_url():    allowed_extension =     file_extension = ''    while file_extension not in allowed_extension:        url = get_url()        file_extension = re.search("(*)$",url).group(1).lower()    return urldef bop(bot, update):    url = get_image_url()    chat_id = update.message.chat_id    bot.send_photo(chat_id=chat_id, photo=url)def main():    updater = Updater('YOUR_TOKEN')    dp = updater.dispatcher    dp.add_handler(CommandHandler('bop',bop))    updater.start_polling()    updater.idle()if __name__ == '__main__':    main()

Всё должно работать идеально. Этот код можно найти на моём GitHub.

Поздравляю с окончанием урока, теперь у вас есть классный бот для Telegram.

Перевод статьи Dzaky Widya Putra: Learn to build your first bot in Telegram with Python

Собираем все воедино

Если мы запустим нашу программу сейчас, веб-приложение еще не будет работать. Flask должен прослушивать запросы аналогично библиотеке Telegram. Чтобы запустить сервер Flask, мы вполне можем в конце нашей программы использовать  .

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

#updater.idle()
app.run(host='0.0.0.0', port=8080)

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

Проверяем расширение файла с помощью регулярного выражения

Чтобы решить эту проблемы, мы будем использовать регулярное выражение.

Отличить изображение от видео или GIF, можно по расширению файла. Нам понадобится последняя часть URL.

https://random.dog/*****.JPG

Во-первых, необходимо определить, какие расширения файлов допускаются в нашей программе.

allowed_extension = 

Затем использовать регулярное выражение, чтобы извлечь расширение файла из URL.

file_extension = re.search("(*)$",url).group(1).lower()

Создайте функцию , используя этот код. Она будет перебирать URL, пока вы не получите файл с подходящим расширением (jpg, jpeg, png).

def get_image_url():    allowed_extension =     file_extension = ''    while file_extension not in allowed_extension:        url = get_url()        file_extension = re.search("(*)$",url).group(1).lower()    return url

Чистая реализация официального Telegram Bot API.

API бота предоставляется через класс . Методы, определенные в являются эквивалентами в виде методов , описанных в официальной документации Telegram Bot API. Для удобства, также доступны точные названия методов в виде , указанные в документации Telegram. Так, например, вызов совпадает с вызовом метода .

Все классы объектов Telegram Bot API расположены в основном модуле пакета , например, класс объекта доступен как .

Чтобы сгенерировать токен доступа, необходимо пообщаться с и выполнить несколько простых шагов, описанных в разделе Команды и оповещения в Telegram.

Чтобы получить представление об API и о том, как его использовать с пакетом , запустите интерпретатор Python и выполните следующие несколько шагов.

Сначала создаем экземпляр . Константу следует заменить токеном API, который был получен от :

>>> import telegram
>>> TOKEN = 'Замените эту строку на token, полученный от @BotFather'
>>> bot = telegram.Bot(token=TOKEN)
# Чтобы проверить правильность учетных данных, вызываем метод bot.getMe():
>>> print(bot.get_me())
# {"first_name": "Toledo's Palace Bot", "username": "ToledosPalaceBot"}

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

Как отвечать/получать сообщения на чистом API?

Для получения сообщений, отправленных боту, можно использовать метод API .

Примечание: не нужно использовать , если пишете своего бота с модулем расширения , поскольку получает все обновления/сообщения в автоматическом режиме.

На чистом API это выглядит следующим образом:

updates = bot.get_updates()
print()

Получение изображения, отправленного боту:

updates = bot.get_updates()
print()

Для отправки сообщения всегда нужен будет :

chat_id = bot.get_updates()[-1.message.chat_id
bot.send_message(chat_id=chat_id, text="I'm a bot, please talk to me!")

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

Ответ на конкретное сообщение, полученное в обновлении:

update.message.reply_text("I'm sorry Dave I'm afraid I can't do that.")

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

Пример бота для ответа на сообщения на чистой реализации API Telegram.

Простой бот для ответа на сообщения Telegram. Пример построен на чистом API Telegram, который реализует пакет .

import logging
from time import sleep

import telegram
from telegram.error import NetworkError, Unauthorized

UPDATE_ID = None

def echo(bot):
    """Эхо сообщение отправленное пользователем."""
    global UPDATE_ID
    # Запрашиваем обновление после последнего update_id
    for update in bot.get_updates(offset=UPDATE_ID, timeout=10):
        UPDATE_ID = update.update_id + 1

        # бот может получать обновления без сообщений
        if update.message
            # не все сообщения содержат текст
            if update.message.text
                # Ответ на сообщение
                update.message.reply_text(f'ECHO: {update.message.text}')


if __name__ == '__main__'
    """Запускаем бота."""
    global UPDATE_ID
    # Токен авторизации бота Telegram
    bot = telegram.Bot('TOKEN')

    # получаем первый ожидающий `update_id`
    try
        UPDATE_ID = bot.get_updates()[.update_id
    except IndexError
        UPDATE_ID = None

    logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    while True
        try
            echo(bot)
        except NetworkError
            sleep(1)
        except Unauthorized
            # Пользователь удалил или заблокировал бота.
            UPDATE_ID += 1

Обучение и запуск бота

После того, как заполнены и валидированы тренировочные данные, настроена конфигурация (https://rasa.com/docs/rasa/model-configuration) можно приступать непосредственно к обучению чат-бота. Это делается с помощью простой команды:

После успешного обучения модель, как правило, сохраняется в папку “models”.

Шпаргалка с полным списком команд rasa:

Команда

Что делает

rasa init

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

rasa train

Обучает модель, используя nlu-файл и истории диалогов, сохраняет обученную модель в ./models.

rasa interactive

Запускает сессию с интерактивным обучением с возможностью обучения модели в процессе диалога с чат-ботом.

rasa shell

Загружает обученную модель и позволяет поговорить с ассистентом в командной строке.

rasa run

Запускает сервер с обученной моделью.

rasa run actions

Запускает action сервер, используя Rasa SDK.

rasa visualize

Визуализирует истории диалогов.

rasa test

Тестирует обученную модель на тестовых данных, хранящихся в файлах, название которых начинается с “test_”.

rasa data split nlu

Делит NLU-данные на трейн и тест в отношении 80/20.

rasa data convert

Конвертирует обучающие данные.

rasa data validate

Проверяет domain, NLU и истории диалогов на несоответствия.

rasa export

Экспортирует диалоги из tracker store в event broker.

rasa x

Локально запускает Rasa X.

rasa -h

Показывает все возможные команды.

Поговорить с обученным ассистентом в командной строке можно запустив команду:

Команда rasa shell —debug позволит запустить чат-бот в режиме debug и посмотреть логи ошибок. Аргумент -m MODEL (путь до модели) или —model MODEL позволяет явно указать модель. По умолчанию rasa shell запускается, используя последнюю модель из папки “models”. Подробнее с аргументами и командами можно ознакомиться здесь.

Установка¶

Для начала давайте создадим каталог для бота, организуем там virtual environment (далее venv) и
установим библиотеку aiogram.
Проверим, что установлен Python версии 3.7 (если вы знаете, что установлен 3.8 и выше, можете пропустить этот кусок):

О версиях aiogram

В этой главе используется aiogram версии 2.9.2, но перед началом работы рекомендую заглянуть в
канал релизов библиотеки и проверить наличие более новой версии. Подойдёт любая
более новая, начинающаяся с цифры 2, поскольку в будущем ожидается релиз aiogram 3.0 с заметными изменениями
и без обратной совместимости.
Чтобы избежать неприятностей, зафиксируемся на 2.9.2 и далее будем обновляться вручную.

Обратите внимание на префикс «venv» в терминале. Он указывает, что мы находимся в виртуальном окружении с именем «venv».
Проверим, что внутри venv вызов команды указывает на всё тот же Python 3.7:. Последней командой мы вышли из venv, чтобы он нам не мешал

Последней командой мы вышли из venv, чтобы он нам не мешал.

Регистрация бота

Для начала нам нужно зарегистрировать нашего бота в Telegram, чтобы сгенерировать учетные данные, которые мы будем использовать для подключения к Telegram API. Каждый бот должен быть привязан к конкретной учетной записи пользователя. Это можно сделать с помощью официального управляющего бота Telegram под названием «BotFather».

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

BotFather

Для активации нажмите на кнопку :

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

Бот у нас спросит следующие данные:

  • имя бота, которое будет отображаться в верхней части чата нового бота, например, «Replit Quick-start Tutorial».
  • имя пользователя, которое будет использоваться для уникальной ссылки на этого бота, например, «@replit_tutorialbot».

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

Токен

Как только мы ответим на все вопросы, BotFather отправит нам наш токен аутентификации, который будет выглядеть примерно так:

Обратите внимание, что вся строка (до двоеточия и после) является токеном

Взаимодействуя с нашим ботом через ваш веб-браузер

Мы можем контролировать наш бот, отправив HTTPS-запросы на телеграмму. Это означает, что самый простой способ взаимодействовать с нашим ботом через веб-браузер. Посещая разные URL-адреса, мы отправляем разные команды нашу бот. SimpleSt Command – это то, где мы получаем информацию о нашем боте. Посетите следующий URL в вашем браузере (подставляя токен бота, который вы получили раньше)

https://api.telegram.org/bot/getme

Первая часть URL указывает, что мы хотим общаться с Telegram API (API.TELEGRAMAM.ORG). Мы следуем это с Чтобы сказать, что мы хотим отправить команду нашему боту, и сразу после того, как мы добавим наш токен, чтобы определить, какой бот мы хотим отправить команду и доказать, что у нас есть. Наконец, мы указываем команду, которую мы хотим отправить ( ), которая в этом случае только что возвращает базовую информацию о нашем боте, используя JSON. Ответ должен выглядеть похоже на следующее:

{"ok":true,"result":{"id":248718785,"first_name":"To Do Bot","username":"exampletodo_bot"}}

Получение сообщений, отправленных на наш бот

Самый простой способ для нас для получения сообщений, отправленных на наш бот, проходит через вызов. Если вы посетите , вы получите ответ JSON всех новых сообщений, отправленных на ваш бот. Наш бот – совершенно новый и, вероятно, еще не получил никаких сообщений, поэтому, если вы сейчас посетите это, вы должны увидеть пустой ответ.

Telegram Bots не могут говорить с пользователями, пока пользователь сначала не инициирует разговор (это уменьшить спам). Для того, чтобы попробовать Звоните, мы сначала отправлю сообщение на наш бот из нашей собственной учетной записи Telegram. Посетить Чтобы открыть разговор с вашим ботом в веб-клиенте (или найти в любой из клиентов Telegram). Вы должны увидеть ваш бот, отображаемый с . кнопка в нижней части экрана. Нажмите эту кнопку, чтобы начать общаться со своим ботом. Отправьте свой бот короткое сообщение, например «Hello».

Теперь посетите URL снова, и вы должны увидеть ответ JSON, показывающую сообщения, которые получили ваш бот (включая один из при нажатии кнопки «Пуск»). Давайте посмотрим на пример этого и выделите данные импорта, которые мы будем писать код для извлечения в следующем разделе.

{"ok":true,"result":[{"update_id":625407400,
"message":{"message_id":1,"from":{"id":24860000,"first_name":"Gareth","last_name":"Dwyer (sixhobbits)","username":"sixhobbits"},"chat":{"id":24860000,"first_name":"Gareth","last_name":"Dwyer (sixhobbits)","username":"sixhobbits","type":"private"},"date":1478087433,"text":"\/start","entities":}},{"update_id":625407401,
"message":{"message_id":2,"from":{"id":24860000,"first_name":"Gareth","last_name":"Dwyer (sixhobbits)","username":"sixhobbits"},"chat":{"id":24860000,"first_name":"Gareth","last_name":"Dwyer (sixhobbits)","username":"sixhobbits","type":"private"},"date":1478087624,"text":"test"}}]}

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

Отправка сообщения от нашего бота

Окончательный вызов API, который мы попробуем в нашем браузере, которое используется для отправки сообщения. Для этого нам нужен идентификатор чата для чата, где мы хотим отправить сообщение. Есть куча разных идентификаторов в ответе JSON от Позвоните, так что убедитесь, что вы получите правильный. Это поле, которое находится внутри поле (24860000 в примере выше, но ваши будут разные). Как только у вас есть этот идентификатор, посетите следующий URL в вашем браузере, подставляя для вашего идентификатора чата.

https://api.telegram.org/bot/sendMessage?chat_id=&text=TestReply

После того, как вы посетили этот URL, вы должны увидеть сообщение от вашего бота, отправленного на ваш, который говорит «Testreply».

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

Функционал для логирования

Согласно тексту справки, бот должен уметь делать две вещи:

  1. Если вы отправляете сообщение боту, он должен где-то его сохранить.
  2. При отправке боту команды он должен отправить вам последнее сообщение.

Для этого мы будем использовать встроенную в Replit базу данных ключ-значение. Начнем с импорта API:

from replit import db

Модуль db — это объект, который ведет себя как словарь, но сохраняет свое содержимое между запусками. Он также сериализует свои ключи в виде строк.

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

def latest_key():
    ks = db.keys()
    if len(ks):
        return max(map(int, ks))
    else:
        return -1 

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

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

def log(update: Update, context: CallbackContext) -> None:
    db = update.message.text 

Этот обработчик получает последний ключ из базы данных, увеличивает его на единицу и создает новую пару ключ — сообщение.

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

dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, log))

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

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

def fetch(update: Update, context: CallbackContext) -> None:
    update.message.reply_text(db.get(str(latest_key()), 'No Messages yet.'))

Мы можем зарегистрировать его вместе с обработчиками остальных команд. Добавьте данную строку после уже существующих строк :

dispatcher.add_handler(CommandHandler("fetch", fetch))
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector