Telegram бот на Python, работа с геолокацией пользователя

Telegram бот на Python, работа с геолокацией пользователя

Автор: Рамис | Статьи 11 мая 2023

Часть 1. Простой Telegram бот на Python, метод getUpdates

Часть 2. Telegram бот на Python, отправка файлов, встроенная клавиатура

Часть 3. Telegram бот на Python, работа с геолокацией пользователяа

Вот и третья часть, где мы продолжим создавать телеграм бота с помощью Python и библиотеки requests. Сегодня вы узнаете как работать с геолокацией пользователя, и как с помощью бесплатного "API геокодера" определить адрес по координатам.

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

Это часть добавлена по просьбе одно из читателей сайта ramziv.com :)

просьбе одно из читателей сайта ramziv.com

Код телеграм бота

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

Прошу вас обратить внимание на функцию run, я добавил новое условие, так как бот прекращал работу ошибкой, если вместо текста отправить ему файл, картинку или геолокацию. Данный код будет работать на Python 3.8 и выше.

#https://ramziv.com/blog/29
import requests
import time
import json

TOKEN = 'ТОКЕН'
URL = 'https://api.telegram.org/bot'

def get_updates(offset=0):
    result = requests.get(f'{URL}{TOKEN}/getUpdates?offset={offset}').json()
    return result['result']

def send_message(chat_id, text):
    requests.get(f'{URL}{TOKEN}/sendMessage?chat_id={chat_id}&text={text}')

def reply_keyboard(chat_id, text):
    reply_markup ={ "keyboard": [["Привет", "Hello"]], "resize_keyboard": True, "one_time_keyboard": True}
    data = {'chat_id': chat_id, 'text': text, 'reply_markup': json.dumps(reply_markup)}
    requests.post(f'{URL}{TOKEN}/sendMessage', data=data)

def check_message(chat_id, message):
    if message.lower() in ['привет', 'hello']:
        send_message(chat_id, 'Привет :)')
    else:
        reply_keyboard(chat_id, 'Я не понимаю тебя :(')

def run():
    update_id = get_updates()[-1]['update_id'] # Сохраняем ID последнего отправленного сообщения боту
    while True:
        time.sleep(2)
        messages = get_updates(update_id) # Получаем обновления
        for message in messages:
            # Если в обновлении есть ID больше чем ID последнего сообщения, значит пришло новое сообщение
            if update_id < message['update_id']:
                update_id = message['update_id']# Сохраняем ID последнего отправленного сообщения боту
                if (user_message := message['message'].get('text')): # Проверим, есть ли текст в сообщении
                    check_message(message['message']['chat']['id'], user_message) # Отвечаем

if __name__ == '__main__':
    run()

Геолокация пользователя

Что бы получить геолокацию пользователя, добавим в нашу клавиатуру новую кнопку с текстом "Где я нахожусь" и укажем ключ request_location со значением True.

#https://ramziv.com/blog/29
def reply_keyboard(chat_id, text):
    reply_markup ={ "keyboard": [["Привет", "Hello"], [{"request_location":True, "text":"Где я нахожусь"}]], "resize_keyboard": True, "one_time_keyboard": True}
    data = {'chat_id': chat_id, 'text': text, 'reply_markup': json.dumps(reply_markup)}
    requests.post(f'{URL}{TOKEN}/sendMessage', data=data)

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

#https://ramziv.com/blog/29
def run():
    update_id = get_updates()[-1]['update_id'] # Сохраняем ID последнего отправленного сообщения боту
    while True:
        time.sleep(2)
        messages = get_updates(update_id) # Получаем обновления
        for message in messages:
            # Если в обновлении есть ID больше чем ID последнего сообщения, значит пришло новое сообщение
            if update_id < message['update_id']:
                update_id = message['update_id']# Сохраняем ID последнего отправленного сообщения боту
                if (user_message := message['message'].get('text')): # Проверим, есть ли текст в сообщении
                    check_message(message['message']['chat']['id'], user_message) # Отвечаем
                if (user_location := message['message'].get('location')): # Проверим, если ли location в сообщении 
                    print(user_location)

Запустим, и проверим нашего бота.

Если нажать кнопку "Где я нахожусь", всплывает push сообщения, где телеграм спрашивает разрешение на отправку геолокации боту.

Телеграм бот на Python

Нажимаем "Ок", и видим что в консоли появилось сообщение с координатами пользователя.

{'latitude': 54.602916, 'longitude': 53.699995}

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

API геокодер

просьбе одно из читателей сайта ramziv.com

LocationIQ API: позволяет определять координаты и получать сведения об объекте по его адресу (прямое геокодирование) и наоборот, определять адрес объекта по его координатам (обратное геокодирование). Сервис предлагает бесплатный план, где разрешено делать 5000 запросов в день, с 2 запросами в секунду.

Зарегистрируемся, и получим токен.

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

https://eu1.locationiq.com/v1/reverse.php?key=<TOKEN>&lat=<LATITUDE>&lon=<LONGITUDE>&format=json

В ответ мы получим JSON, где указанно все о переданных координатах.

LocationIQ JSON

Теперь осталось повторить все на Python. Создадим функцию geocoder которая будет принимать координаты, и возвращать текст 'Твое местоположение: + адрес из API геокодера'.

#https://ramziv.com/blog/29
def geocoder(latitude, longitude):
    token = 'pk.токен'
    headers = {"Accept-Language": "ru"}
    address = requests.get(f'https://eu1.locationiq.com/v1/reverse.php?key={token}&lat={latitude}&lon={longitude}&format=json', headers=headers).json()
    return f'Твое местоположение: {address.get("display_name")}'

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

Полный код.

#https://ramziv.com/blog/29
import requests
import time
import json

TOKEN = 'ТОКЕН'
URL = 'https://api.telegram.org/bot'

def get_updates(offset=0):
    result = requests.get(f'{URL}{TOKEN}/getUpdates?offset={offset}').json()
    return result['result']

def send_message(chat_id, text):
    requests.get(f'{URL}{TOKEN}/sendMessage?chat_id={chat_id}&text={text}')

def reply_keyboard(chat_id, text):
    reply_markup ={ "keyboard": [["Привет", "Hello"], [{"request_location":True, "text":"Где я нахожусь"}]], "resize_keyboard": True, "one_time_keyboard": True}
    data = {'chat_id': chat_id, 'text': text, 'reply_markup': json.dumps(reply_markup)}
    requests.post(f'{URL}{TOKEN}/sendMessage', data=data)

def check_message(chat_id, message):
    if message.lower() in ['привет', 'hello']:
        send_message(chat_id, 'Привет :)')
    else:
        reply_keyboard(chat_id, 'Я не понимаю тебя :(')

def geocoder(latitude, longitude):
    token = 'pk.токен'
    headers = {"Accept-Language": "ru"}
    address = requests.get(f'https://eu1.locationiq.com/v1/reverse.php?key={token}&lat={latitude}&lon={longitude}&format=json', headers=headers).json()
    return f'Твое местоположение: {address.get("display_name")}'

def run():
    update_id = get_updates()[-1]['update_id'] # Присваиваем ID последнего отправленного сообщения боту
    while True:
        time.sleep(2)
        messages = get_updates(update_id) # Получаем обновления
        for message in messages:
            # Если в обновлении есть ID больше чем ID последнего сообщения, значит пришло новое сообщение
            if update_id < message['update_id']:
                update_id = message['update_id']# Присваиваем ID последнего отправленного сообщения боту
                if (user_message := message['message'].get('text')): # Проверим, есть ли текст в сообщении
                    check_message(message['message']['chat']['id'], user_message) # Отвечаем
                if (user_location := message['message'].get('location')): # Проверим, если ли location в сообщении
                    latitude = user_location['latitude']
                    longitude = user_location['longitude']
                    send_message(message['message']['chat']['id'], geocoder(latitude, longitude))

if __name__ == '__main__':
    run()

Проверим как работает.

Телеграм бот Python

Ответ с местоположением выглядит смешно :), но это сделано для демонстрации. Вы можете в функции geocoder заменить address.get("display_name") на address["address"].get("town") и в ответ получать только название города.

Конец

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

Комментарии

ballout
@ballout
13 марта 2023

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

Ответить
Рамис
@ramas
14 марта 2023

@ballout, Здравствуйте. Я посмотрел наш код, и по логике он работает правильно. Когда приходит сообщение с местоположением, бот запускает две функции geocoder и where. Решения 1: добавить базу данных. Запросить и сохранить в базу местоположение пользователя, при запросе погоды брать данные с базы. Решение 2: Объединить две функции в одну, и при запросе местоположения или погоды отправлять сообщение со всей информацией (погода и местоположение).

Ответить
ballout
@ballout
15 марта 2023

@ramas, спасибо огромное за подсказку, пойду пробовать!)))

Ответить
ddurov
@ddurov
18 октября 2023

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

Ответить
Рамис
@ramas
24 октября 2023

@ddurov, Здравствуйте. Для реализации такого потребуется база с географическими координатами аптек. По ней делать выборку по ближайшим координатам пользователя и отправлять пользователю.

Ответить
Alexander
@Alexander
06 ноября 2023

@ramas, есть какой-то другой сайт это не робит

Ответить
ddurov
@ddurov
13 ноября 2023

@ramas, а как можно сделать такую базу и реализовать в коде?

Ответить
Markdown
Войти