Код не выходит из функций
есть проблема, программа не выходит из функций когда к ней нет обращения, например если я обращусь к функций для показа погоды и после её показа, то бот будет продолжать реагировать на название города даже если не будет обращения к функций
import logging
from aiogram import Bot, Dispatcher, executor, types
import datetime
import requests
import pyowm
from bs4 import BeautifulSoup as BS
bot = Bot(token="***")
WEATHER_SERVICE_API_KEY = ("***")
dp = Dispatcher(bot)
logging.basicConfig(level=logging.INFO)
now = datetime.datetime.now()
@dp.message_handler(commands="start")
async def hello(message):
keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
buttons = ["❄ Погода", "? Курс","? Акций "]
keyboard.add(*buttons)
if now.hour >= 6 and now.hour < 12:
await bot.send_message(message.chat.id,text="Доброе утро", reply_markup=keyboard)
elif now.hour >= 12 and now.hour < 18:
await bot.send_message(message.chat.id,text="Добрый день", reply_markup=keyboard)
elif now.hour >= 18 and now.hour < 23:
await bot.send_message(message.chat.id,text="Добрый вечер", reply_markup=keyboard)
else:
await bot.send_message(message.chat.id,text="Доброй ночи", reply_markup=keyboard)
@dp.message_handler(lambda message: message.text == "? Курс")
async def conv(message: types.Message):
data = requests.get('https://www.cbr-xml-daily.ru/daily_json.js').json()
USD = data['Valute']['USD']['Value']
USDP = data['Valute']['USD']['Previous']
EUR = data['Valute']['EUR']['Value']
EURP = data['Valute']['EUR']['Previous']
await message.reply(f"Курс рубля по данным ЦБ:\n<b>"\
f"К доллару {USD}</b> ?\n<u>"\
f"Предыдущий курс доллара {USDP}</u>\n<b>"\
f"К евро {EUR}</b> ?\n<u>"\
f"Предыдущий курс евро {EURP}</u>",parse_mode='html')
@dp.message_handler(lambda message: message.text == "❄ Погода")
async def location(message):
await message.reply('напиши название города и я пришлю сводку погоды')
@dp.message_handler(lambda message: message)
async def get_weather(message):
try:
weather = message.text
r = requests.get(f"http://api.openweathermap.org/data/2.5/weather?q={weather}&appid={WEATHER_SERVICE_API_KEY}&units=metric&lang=ru")
data = r.json()
city = data["name"]
cur_weather = data['main']['temp']
humidity = data['main']['humidity']
pressure = data['main']['pressure']
wind = data['wind']['speed']
await message.answer(f"<u>{datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}</u>\n"
f'<b>Погода в городе:</b> {city}\n<b>Температура:</b> {cur_weather}Celsius\n'
f'<b>Влажность:</b> {humidity}%\n<b>Давление:</b> {pressure} мм.рт.ст \n<b>Ветер:</b> {wind} м/с\n'
f'***Хорошего Дня)***', parse_mode='html')
except:
await message.answer("ошибка")
@dp.message_handler(lambda message: message.text == "? Акций")
async def ticket (message: types.Message):
await message.reply('напиши индекс акций и я пришлю нынешнюю цену ')
@dp.message_handler(lambda message: message)
async def get_ticket(message: types.Message):
try:
tickets = message.text
ticket = (f"https://www.tinkoff.ru/invest/stocks/{tickets}/")
#заголовки для URL запроса.(добавляется к ссылке при URL запросе)
headers = {'user agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0"}
html = requests.get(ticket, headers)
soup = BS(html.content, 'html.parser')
convert = soup.findAll('span', {'class': 'SecurityInvitingScreen__priceValue_c1Ah9'})
price = (convert[0].text[0:])
await message.answer(f"Цена данной акций: {price}")
return tickets
except:
print("не работает")
if __name__ == "__main__":
executor.start_polling(dp, skip_updates=True)
Ответы (1 шт):
Эхх, ещё один участник культа "любителей вставить хендлер внутрь другого хендлера", которых развелось очень много в последнее время. Скажите мне пожалуйста кто вам сказал так кодить? Кто он? Я серьезно.
Попробую объяснить почему у вас это все дело не работает.
Итак юзер жмет кнопку старт, и получает реплай клавиатуру. Жмет на "Погода", запускается хендлер с этим фильтром. Пока всё ок. Пока. Однако, внутри этого хендлера у вас ещё один, который по факту работает как? Ну он должен быть последним в списке регистрируемых хендлеров, так как создался только что, но это создает проблемы, как минимуму, для кнопки "Акции" у которой всё ровно так же.
Так вот, юзер жмакнул на погоду и ввел город. Всё еще все будет работать. Однако хендлер то уже зарегистрирован и по факту юзер может дальше вводить названия городов и хендлер будет срабатывать. Почему?
У вас есть вот эти 4 хендлера, который регистрируются при запуске бота. Остальные он не видит, так как они внутри других. И вот у вас создался внутренний список с этими хендлерами у которых есть свои фильтры, которые просто отвратительны.
Пояснение по фильтрам: вот это - lambda message: message.text == "? Акций" меняйте на `text="? Акций". Зачем вам лямбда, если есть такие красивые встроенные фильтры?
После того как юзер жмакнул на погоду, появился новый хендлер, но с пустым фильтром (по умолчанию принимает только текст), который впитывает в себя всё что напишет юзер, и что не было отфильтровано вышестоящими хендлерами.
И если вдруг появится хендлер с любым другим фильтром, который реагирует на текст, то он будет проигнорирован, так как есть хендлер выше.
Короче, к главному. Как фиксить?
Для начала перестаньте вписывать один хендлер внутрь другого. Сделали? Круто.
Теперь, вам пора знакомиться с FSM. Это легко, но думаю информации в интернете предостаточно. Сюда кину как бы я это сделал.
import datetime
from aiogram import Bot, Dispatcher, executor, types
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters import Text
from aiogram.dispatcher.filters.state import StatesGroup, State
import requests
from data import config
bot = Bot(token=config.BOT_TOKEN)
storage = MemoryStorage()
dp = Dispatcher(bot, storage=storage)
WEATHER_SERVICE_API_KEY = "___"
now = datetime.datetime.now()
# создаем класс стейтов
# каждая переменная сделанная таким же способом, будет отдельным стейтом
# вы можете называть их как захотите
class FSM_type_1(StatesGroup):
weather = State()
@dp.message_handler(commands="start")
async def start(message: types.Message):
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
buttons = ["❄ Погода", "? Курс", "? Акций "]
markup.add(*buttons)
# немного поменял вывод сообщения
time_dict = {
"Доброе утро": range(6, 12),
"Добрый день": range(12, 18),
"Добрый вечер": range(18, 24),
"Доброй ночи": range(0, 6),
}
for text, time in time_dict.items():
if now.hour in time:
await message.answer(f"{text}", reply_markup=markup)
@dp.message_handler(text="❄ Погода")
async def weather(message: types.Message):
await message.reply('Введите название города, для получения сводки по погоде')
# когда мы готовы ловить сообщения ставим стейт
await FSM_type_1.weather.set()
# вот такой фильтр нужен, чтобы ловить при определенном стейте
@dp.message_handler(state=FSM_type_1.weather)
async def get_weather(message: types.Message, state: FSMContext):
try:
weather = message.text
r = requests.get(
f"http://api.openweathermap.org/data/2.5/weather?q={weather}&appid={WEATHER_SERVICE_API_KEY}&units=metric&lang=ru")
data = r.json()
city = data["name"]
cur_weather = data['main']['temp']
humidity = data['main']['humidity']
pressure = data['main']['pressure']
wind = data['wind']['speed']
await message.answer(f"<u>{datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}</u>\n"
f'<b>Погода в городе:</b> {city}\n<b>Температура:</b> {cur_weather}Celsius\n'
f'<b>Влажность:</b> {humidity}%\n<b>Давление:</b> {pressure} мм.рт.ст \n<b>Ветер:</b> {wind} м/с\n'
f'***Хорошего Дня)***', parse_mode='html')
# завершаем стейт
await state.finish()
except Exception as e:
await message.answer("Город не найден.\nВведите корректное название города\n"
f"Если хотите завершить ввод погоды напишите 'Отмена'")
# ну и добавил прекращение ввода
# работает как завершалка стейтов, будьте осторожны, если не хотите чтобы
# не сбросить нужный стейт этой командой
@dp.message_handler(commands=["отмена"], state="*")
@dp.message_handler(Text(equals="отмена", ignore_case=True), state="*")
async def cancel_fsm(message: types.Message, state: FSMContext):
await state.finish()
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)