timezone fix
This commit is contained in:
36
bot.py
36
bot.py
@@ -17,7 +17,8 @@ from weather import (
|
|||||||
get_daily_weather_summary,
|
get_daily_weather_summary,
|
||||||
get_coords_by_city,
|
get_coords_by_city,
|
||||||
get_tomorrow_weather_summary,
|
get_tomorrow_weather_summary,
|
||||||
get_weekly_weather_summary
|
get_weekly_weather_summary,
|
||||||
|
get_timezone_by_coords
|
||||||
)
|
)
|
||||||
import database as db # Импортируем нашу базу данных
|
import database as db # Импортируем нашу базу данных
|
||||||
|
|
||||||
@@ -90,22 +91,29 @@ async def cmd_change_city(message: types.Message, state: FSMContext):
|
|||||||
async def process_location_input(message: types.Message, state: FSMContext):
|
async def process_location_input(message: types.Message, state: FSMContext):
|
||||||
user_id = message.from_user.id
|
user_id = message.from_user.id
|
||||||
city_name_display = "Выбранная локация"
|
city_name_display = "Выбранная локация"
|
||||||
|
timezone = None
|
||||||
|
|
||||||
if message.location:
|
if message.location:
|
||||||
lat = message.location.latitude
|
lat = message.location.latitude
|
||||||
lon = message.location.longitude
|
lon = message.location.longitude
|
||||||
|
# Для координат нужно определить часовой пояс отдельным запросом
|
||||||
|
timezone = await get_timezone_by_coords(lat, lon)
|
||||||
|
if not timezone:
|
||||||
|
await message.answer("Не удалось определить часовой пояс для вашей геопозиции. Попробуйте ввести название города.")
|
||||||
|
return
|
||||||
elif message.text:
|
elif message.text:
|
||||||
lat, lon, found_name = await get_coords_by_city(message.text)
|
lat, lon, found_name, timezone = await get_coords_by_city(message.text)
|
||||||
if not lat:
|
if not lat:
|
||||||
await message.answer("К сожалению, я не нашел такой город 😔. Попробуй уточнить название.")
|
await message.answer("К сожалению, я не нашел такой город 😔. Попробуй уточнить название.")
|
||||||
return
|
return
|
||||||
city_name_display = found_name
|
city_name_display = found_name
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Сохраняем локацию в БД
|
# Сохраняем локацию и часовой пояс в БД
|
||||||
await db.save_user_location(user_id, lat, lon)
|
await db.save_user_location(user_id, lat, lon, timezone)
|
||||||
|
logging.info(f"Сохранена локация для {user_id}: lat={lat}, lon={lon}, timezone={timezone}")
|
||||||
|
|
||||||
# Проверяем, есть ли уже у пользователя настроенное время
|
# Проверяем, есть ли уже у пользователя настроенное время
|
||||||
user = await db.get_user(user_id)
|
user = await db.get_user(user_id)
|
||||||
if user and user["time"]:
|
if user and user["time"]:
|
||||||
@@ -114,7 +122,7 @@ async def process_location_input(message: types.Message, state: FSMContext):
|
|||||||
else:
|
else:
|
||||||
await state.set_state(WeatherSetup.waiting_for_time)
|
await state.set_state(WeatherSetup.waiting_for_time)
|
||||||
await message.answer(
|
await message.answer(
|
||||||
f"Отлично! Локация **{city_name_display}** найдена.\n\nТеперь напиши время (в формате ЧЧ:ММ):",
|
f"Отлично! Локация **{city_name_display}** найдена (часовой пояс: {timezone}).\n\nТеперь напиши время для ежедневной рассылки (в формате ЧЧ:ММ):",
|
||||||
reply_markup=ReplyKeyboardRemove()
|
reply_markup=ReplyKeyboardRemove()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,6 +144,12 @@ async def process_time(message: types.Message, state: FSMContext):
|
|||||||
try:
|
try:
|
||||||
dt = datetime.strptime(time_text, "%H:%M")
|
dt = datetime.strptime(time_text, "%H:%M")
|
||||||
|
|
||||||
|
# Получаем пользователя, чтобы узнать его часовой пояс
|
||||||
|
user = await db.get_user(user_id)
|
||||||
|
if not user or not user["timezone"]:
|
||||||
|
await message.answer("Не удалось найти ваш часовой пояс. Пожалуйста, установите город заново (команда /start).")
|
||||||
|
return
|
||||||
|
|
||||||
# Сохраняем время в БД
|
# Сохраняем время в БД
|
||||||
await db.update_user_time(user_id, time_text)
|
await db.update_user_time(user_id, time_text)
|
||||||
|
|
||||||
@@ -144,8 +158,8 @@ async def process_time(message: types.Message, state: FSMContext):
|
|||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
send_weather_update,
|
send_weather_update,
|
||||||
trigger='cron',
|
trigger='cron',
|
||||||
hour=dt.hour,
|
hour=dt.hour, minute=dt.minute,
|
||||||
minute=dt.minute,
|
timezone=user["timezone"],
|
||||||
args=[user_id],
|
args=[user_id],
|
||||||
id=str(user_id),
|
id=str(user_id),
|
||||||
replace_existing=True
|
replace_existing=True
|
||||||
@@ -204,8 +218,8 @@ async def restore_jobs():
|
|||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
send_weather_update,
|
send_weather_update,
|
||||||
trigger='cron',
|
trigger='cron',
|
||||||
hour=dt.hour,
|
hour=dt.hour, minute=dt.minute,
|
||||||
minute=dt.minute,
|
timezone=user["timezone"],
|
||||||
args=[user["user_id"]],
|
args=[user["user_id"]],
|
||||||
id=str(user["user_id"]),
|
id=str(user["user_id"]),
|
||||||
replace_existing=True
|
replace_existing=True
|
||||||
|
|||||||
16
database.py
16
database.py
@@ -13,7 +13,8 @@ async def init_db():
|
|||||||
user_id INTEGER PRIMARY KEY,
|
user_id INTEGER PRIMARY KEY,
|
||||||
lat REAL,
|
lat REAL,
|
||||||
lon REAL,
|
lon REAL,
|
||||||
time TEXT
|
time TEXT,
|
||||||
|
timezone TEXT
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
await db.commit()
|
await db.commit()
|
||||||
@@ -25,16 +26,17 @@ async def get_user(user_id: int):
|
|||||||
async with db.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)) as cursor:
|
async with db.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)) as cursor:
|
||||||
return await cursor.fetchone()
|
return await cursor.fetchone()
|
||||||
|
|
||||||
async def save_user_location(user_id: int, lat: float, lon: float):
|
async def save_user_location(user_id: int, lat: float, lon: float, timezone: str):
|
||||||
"""Сохраняет или обновляет только координаты пользователя."""
|
"""Сохраняет или обновляет координаты и часовой пояс пользователя."""
|
||||||
async with aiosqlite.connect(DB_PATH) as db:
|
async with aiosqlite.connect(DB_PATH) as db:
|
||||||
await db.execute('''
|
await db.execute('''
|
||||||
INSERT INTO users (user_id, lat, lon)
|
INSERT INTO users (user_id, lat, lon, timezone)
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT(user_id) DO UPDATE SET
|
ON CONFLICT(user_id) DO UPDATE SET
|
||||||
lat=excluded.lat,
|
lat=excluded.lat,
|
||||||
lon=excluded.lon
|
lon=excluded.lon,
|
||||||
''', (user_id, lat, lon))
|
timezone=excluded.timezone
|
||||||
|
''', (user_id, lat, lon, timezone))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
async def update_user_time(user_id: int, time: str):
|
async def update_user_time(user_id: int, time: str):
|
||||||
|
|||||||
45
weather.py
45
weather.py
@@ -1,5 +1,7 @@
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
async def get_coords_by_city(city_name: str):
|
async def get_coords_by_city(city_name: str):
|
||||||
"""Ищет координаты по названию города."""
|
"""Ищет координаты по названию города."""
|
||||||
@@ -9,18 +11,31 @@ async def get_coords_by_city(city_name: str):
|
|||||||
try:
|
try:
|
||||||
async with session.get(url, timeout=5) as response:
|
async with session.get(url, timeout=5) as response:
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
return None, None, None
|
return None, None, None, None
|
||||||
|
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
results = data.get("results")
|
results = data.get("results")
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
return None, None, None
|
return None, None, None, None
|
||||||
|
|
||||||
city_data = results[0]
|
city_data = results[0]
|
||||||
return city_data.get("latitude"), city_data.get("longitude"), city_data.get("name", city_name)
|
return (
|
||||||
|
city_data.get("latitude"),
|
||||||
|
city_data.get("longitude"),
|
||||||
|
city_data.get("name", city_name),
|
||||||
|
city_data.get("timezone")
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None, None, None
|
return None, None, None, None
|
||||||
|
|
||||||
|
async def get_timezone_by_coords(lat: float, lon: float) -> str | None:
|
||||||
|
"""Определяет часовой пояс по координатам, делая запрос к API погоды."""
|
||||||
|
# Делаем минимальный запрос к API, чтобы получить часовой пояс
|
||||||
|
data = await fetch_weather_data(lat, lon, 1)
|
||||||
|
if data and "timezone" in data:
|
||||||
|
return data["timezone"]
|
||||||
|
return None
|
||||||
|
|
||||||
async def fetch_weather_data(lat: float, lon: float, days: int):
|
async def fetch_weather_data(lat: float, lon: float, days: int):
|
||||||
"""Базовая функция для запроса данных о погоде на N дней."""
|
"""Базовая функция для запроса данных о погоде на N дней."""
|
||||||
@@ -30,14 +45,20 @@ async def fetch_weather_data(lat: float, lon: float, days: int):
|
|||||||
f"&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max"
|
f"&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max"
|
||||||
f"&timezone=auto&forecast_days={days}"
|
f"&timezone=auto&forecast_days={days}"
|
||||||
)
|
)
|
||||||
async with aiohttp.ClientSession() as session:
|
# Логика с повторными попытками для надежности
|
||||||
try:
|
for attempt in range(3):
|
||||||
async with session.get(url, timeout=5) as response:
|
async with aiohttp.ClientSession() as session:
|
||||||
if response.status != 200:
|
try:
|
||||||
return None
|
async with session.get(url, timeout=10) as response:
|
||||||
return await response.json()
|
if response.status == 200:
|
||||||
except Exception:
|
return await response.json()
|
||||||
return None
|
logging.warning(f"Попытка {attempt+1}: API погоды вернуло статус {response.status}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"Попытка {attempt+1}: Ошибка при запросе к API погоды: {e}")
|
||||||
|
|
||||||
|
if attempt < 2: # Не спим после последней попытки
|
||||||
|
await asyncio.sleep(attempt + 2) # Спим 2, 3 секунды
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_daily_weather_summary(lat: float, lon: float) -> str:
|
async def get_daily_weather_summary(lat: float, lon: float) -> str:
|
||||||
"""Прогноз на сегодня."""
|
"""Прогноз на сегодня."""
|
||||||
|
|||||||
Reference in New Issue
Block a user