-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
295 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Weather | ||
|
||
The Weather module enables you to retrieve weather information for a particular location. It offers commands to obtain current weather conditions. | ||
|
||
## Commands | ||
|
||
- `/weather (location)`: Retrieves the current weather information for the specified location. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ msgid "" | |
msgstr "" | ||
"Project-Id-Version: PROJECT VERSION\n" | ||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||
"POT-Creation-Date: 2024-12-11 12:38-0300\n" | ||
"POT-Creation-Date: 2024-12-12 18:14-0300\n" | ||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||
"Language-Team: LANGUAGE <[email protected]>\n" | ||
|
@@ -87,44 +87,44 @@ msgstr "" | |
msgid "The following commands are disabled in this chat:\n" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:28 | ||
msgid "enable" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:28 | ||
msgid "disable" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:29 | ||
msgid "enabled" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:29 | ||
msgid "disabled" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:33 | ||
#: src/korone/modules/disabling/handlers/toggle.py:30 | ||
msgid "" | ||
"You need to specify a command to {action}. Use <code>/{action} " | ||
"<commandname></code>." | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:43 | ||
msgid "You can only {action} one command at a time." | ||
#: src/korone/modules/disabling/handlers/toggle.py:33 | ||
#: src/korone/modules/disabling/handlers/toggle.py:41 | ||
msgid "enable" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:50 | ||
#: src/korone/modules/disabling/handlers/toggle.py:33 | ||
#: src/korone/modules/disabling/handlers/toggle.py:41 | ||
msgid "disable" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:39 | ||
msgid "" | ||
"Unknown command to {action}:\n" | ||
"- <code>{command}</code>\n" | ||
"Check the /disableable!" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:58 | ||
#: src/korone/modules/disabling/handlers/toggle.py:50 | ||
msgid "This command is already {action}." | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:62 | ||
#: src/korone/modules/disabling/handlers/toggle.py:51 | ||
#: src/korone/modules/disabling/handlers/toggle.py:58 | ||
msgid "enabled" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:51 | ||
#: src/korone/modules/disabling/handlers/toggle.py:58 | ||
msgid "disabled" | ||
msgstr "" | ||
|
||
#: src/korone/modules/disabling/handlers/toggle.py:58 | ||
msgid "Command {action}." | ||
msgstr "" | ||
|
||
|
@@ -1175,6 +1175,49 @@ msgstr "" | |
msgid "<b>User link</b>: <a href='tg://user?id={id}'>link</a>\n" | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:23 | ||
msgid "" | ||
"No location provided. You should provide a location. Example: " | ||
"<code>/weather Rio de Janeiro</code>" | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:36 | ||
#: src/korone/modules/weather/handlers/get.py:99 | ||
msgid "Failed to fetch weather data." | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:47 | ||
msgid "No locations found for the provided query." | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:60 | ||
msgid "Please select a location:" | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:74 | ||
msgid "Session expired. Please try again." | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:106 | ||
msgid "Incomplete weather data received." | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:121 | ||
msgid "Temperature" | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:122 | ||
msgid "Temperature feels like:" | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:123 | ||
msgid "Air humidity:" | ||
msgstr "" | ||
|
||
#: src/korone/modules/weather/handlers/get.py:124 | ||
msgid "Wind Speed" | ||
msgstr "" | ||
|
||
#: src/korone/modules/web/handlers/ip.py:20 | ||
msgid "" | ||
"You should provide an IP address or domain name to get " | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
New `Weather` module to get weather information for a location, read more in :doc:`Weather module <modules/weather>`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2024 Hitalo M. <https://github.com/HitaloM> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2024 Hitalo M. <https://github.com/HitaloM> | ||
|
||
from hairydogm.filters.callback_data import CallbackData | ||
|
||
|
||
class WeatherCallbackData(CallbackData, prefix="weather"): | ||
latitude: float | None = None | ||
longitude: float | None = None | ||
page: int | None = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2024 Hitalo M. <https://github.com/HitaloM> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2024 Hitalo M. <https://github.com/HitaloM> | ||
|
||
from hydrogram import Client | ||
from hydrogram.types import CallbackQuery, Message | ||
|
||
from korone.decorators import router | ||
from korone.filters.command import Command, CommandObject | ||
from korone.modules.weather.callback_data import WeatherCallbackData | ||
from korone.modules.weather.utils.api import get_weather_observations, search_location | ||
from korone.modules.weather.utils.types import WeatherResult, WeatherSearch | ||
from korone.utils.caching import cache | ||
from korone.utils.i18n import gettext as _ | ||
from korone.utils.pagination import Pagination | ||
|
||
|
||
@router.message(Command(commands=["weather", "clima"])) | ||
async def get_weather(client: Client, message: Message) -> None: | ||
command = CommandObject(message).parse() | ||
|
||
if not command.args or len(command.args) <= 1: | ||
await message.reply_text( | ||
_( | ||
"No location provided. You should provide a location. " | ||
"Example: <code>/weather Rio de Janeiro</code>" | ||
) | ||
) | ||
return | ||
|
||
cache_key = f"weather_location_{command.args}" | ||
data = await cache.get(cache_key) or await search_location(command.args) | ||
|
||
if data: | ||
await cache.set(cache_key, data, expire=3600) | ||
else: | ||
await message.reply_text(_("Failed to fetch weather data.")) | ||
return | ||
|
||
weather_search = WeatherSearch.model_validate(data.get("location", {})) | ||
locations = list( | ||
zip( | ||
weather_search.address, weather_search.latitude, weather_search.longitude, strict=False | ||
) | ||
) | ||
|
||
if not locations: | ||
await message.reply_text(_("No locations found for the provided query.")) | ||
return | ||
|
||
pagination = Pagination( | ||
objects=locations, | ||
page_data=lambda page: WeatherCallbackData(page=page).pack(), | ||
item_data=lambda item, _: WeatherCallbackData(latitude=item[1], longitude=item[2]).pack(), | ||
item_title=lambda item, _: item[0], | ||
) | ||
|
||
keyboard_markup = pagination.create(page=1) | ||
await cache.set(f"weather_locations_{message.chat.id}", locations, expire=300) | ||
|
||
await message.reply(_("Please select a location:"), reply_markup=keyboard_markup) | ||
|
||
|
||
@router.callback_query(WeatherCallbackData.filter()) | ||
async def callback_weather(client: Client, callback_query: CallbackQuery) -> None: | ||
if not callback_query.data: | ||
return | ||
|
||
data = WeatherCallbackData.unpack(callback_query.data) | ||
chat_id = callback_query.message.chat.id | ||
|
||
if data.page: | ||
locations = await cache.get(f"weather_locations_{chat_id}") | ||
if not locations: | ||
await callback_query.answer(_("Session expired. Please try again."), show_alert=True) | ||
return | ||
|
||
pagination = Pagination( | ||
objects=locations, | ||
page_data=lambda page: WeatherCallbackData(page=page).pack(), | ||
item_data=lambda item, _: WeatherCallbackData( | ||
latitude=item[1], longitude=item[2] | ||
).pack(), | ||
item_title=lambda item, _: item[0], | ||
) | ||
keyboard_markup = pagination.create(page=data.page) | ||
|
||
await callback_query.edit_message_reply_markup(reply_markup=keyboard_markup) | ||
return | ||
|
||
if data.latitude and data.longitude: | ||
cache_key = f"weather_data_{data.latitude}_{data.longitude}" | ||
weather_data = await cache.get(cache_key) or await get_weather_observations( | ||
data.latitude, data.longitude | ||
) | ||
|
||
if weather_data: | ||
await cache.set(cache_key, weather_data, expire=600) | ||
else: | ||
await callback_query.answer(_("Failed to fetch weather data."), show_alert=True) | ||
return | ||
|
||
location_data = weather_data.get("v3-location-point", {}).get("location", {}) | ||
weather_observation = weather_data.get("v3-wx-observations-current", {}) | ||
|
||
if not location_data or not weather_observation: | ||
await callback_query.answer(_("Incomplete weather data received."), show_alert=True) | ||
return | ||
|
||
weather_result = WeatherResult.model_validate({ | ||
**weather_observation, | ||
"id": location_data.get("locId"), | ||
"city": location_data.get("city"), | ||
"adminDistrict": location_data.get("adminDistrict"), | ||
"country": location_data.get("country"), | ||
}) | ||
|
||
text = ( | ||
f"<b>{weather_result.city}, " | ||
f"{weather_result.admin_district}, " | ||
f"{weather_result.country}</b>:\n\n" | ||
f"🌡️ <b>{_('Temperature')}</b>: {weather_result.temperature}°C\n" | ||
f"🌡️ <b>{_('Temperature feels like:')}</b>: {weather_result.temperature_feels_like}°C\n" | ||
f"💧 <b>{_('Air humidity:')}</b>: {weather_result.relative_humidity}%\n" | ||
f"💨 <b>{_('Wind Speed')}</b>: {weather_result.wind_speed} km/h\n\n" | ||
f"- 🌤️ <i>{weather_result.wx_phrase_long}</i>" | ||
) | ||
|
||
await callback_query.edit_message_text(text=text) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2024 Hitalo M. <https://github.com/HitaloM> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2024 Hitalo M. <https://github.com/HitaloM> | ||
|
||
from datetime import timedelta | ||
from typing import Any | ||
|
||
import httpx | ||
|
||
from korone.utils.caching import cache | ||
from korone.utils.logging import logger | ||
|
||
WEATHER_API_KEY = "8de2d8b3a93542c9a2d8b3a935a2c909" | ||
LOCATION_SEARCH_URL = "https://api.weather.com/v3/location/search" | ||
WEATHER_OBSERVATIONS_URL = ( | ||
"https://api.weather.com/v3/aggcommon/v3-wx-observations-current;v3-location-point" | ||
) | ||
LANGUAGE = "en-US" | ||
FORMAT = "json" | ||
UNITS = "m" | ||
|
||
|
||
async def fetch_json(url: str, params: dict) -> dict: | ||
async with httpx.AsyncClient(http2=True, timeout=httpx.Timeout(10.0)) as client: | ||
try: | ||
response = await client.get(url, params=params, timeout=10.0) | ||
response.raise_for_status() | ||
return response.json() | ||
except httpx.HTTPError as err: | ||
await logger.aexception("[Weather] HTTP error occurred: %s", err) | ||
return {} | ||
|
||
|
||
@cache(ttl=timedelta(days=1)) | ||
async def search_location(query: str) -> dict[str, Any]: | ||
params = { | ||
"apiKey": WEATHER_API_KEY, | ||
"query": query, | ||
"language": LANGUAGE, | ||
"format": FORMAT, | ||
} | ||
return await fetch_json(LOCATION_SEARCH_URL, params) | ||
|
||
|
||
async def get_weather_observations(latitude: float, longitude: float) -> dict[str, Any]: | ||
params = { | ||
"apiKey": WEATHER_API_KEY, | ||
"geocode": f"{latitude},{longitude}", | ||
"language": LANGUAGE, | ||
"units": UNITS, | ||
"format": FORMAT, | ||
} | ||
return await fetch_json(WEATHER_OBSERVATIONS_URL, params) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2024 Hitalo M. <https://github.com/HitaloM> | ||
|
||
from pydantic import BaseModel, ConfigDict, Field | ||
|
||
|
||
class WeatherSearch(BaseModel): | ||
latitude: list[float] | ||
longitude: list[float] | ||
address: list[str] | ||
|
||
|
||
class WeatherResult(BaseModel): | ||
model_config = ConfigDict(populate_by_name=True) | ||
|
||
weather_id: str = Field(alias="id") | ||
temperature: float = Field(alias="temperature") | ||
temperature_feels_like: float = Field(alias="temperatureFeelsLike") | ||
relative_humidity: float = Field(alias="relativeHumidity") | ||
wind_speed: float = Field(alias="windSpeed") | ||
wx_phrase_long: str = Field(alias="wxPhraseLong") | ||
city: str = Field(alias="city") | ||
admin_district: str = Field(alias="adminDistrict") | ||
country: str = Field(alias="country") |