diff --git a/cron.db b/cron.db new file mode 100644 index 0000000..3cb349f Binary files /dev/null and b/cron.db differ diff --git a/funguy.py b/funguy.py index 0b5802a..fa4aeaf 100755 --- a/funguy.py +++ b/funguy.py @@ -20,7 +20,7 @@ from plugins.config import FunguyConfig # Whitelist of allowed plugins to prevent arbitrary code execution ALLOWED_PLUGINS = {'ai', 'config', 'cron', 'date', 'fortune', 'help', 'isup', 'karma', 'loadplugin', 'plugins', 'proxy', 'sd_text', 'stable-diffusion', - 'xkcd', 'youtube-preview', 'youtube-search'} + 'xkcd', 'youtube-preview', 'youtube-search', 'weather'} class FunguyBot: """ diff --git a/plugins/loadplugin.py b/plugins/loadplugin.py index 22dd1ab..592e609 100644 --- a/plugins/loadplugin.py +++ b/plugins/loadplugin.py @@ -56,6 +56,7 @@ async def load_plugin(plugin_name): 'xkcd': 'plugins.xkcd', 'youtube-preview': 'plugins.youtube-preview', 'youtube-search': 'plugins.youtube-search', + 'weather': 'plugins.weather' } # Get the module path from the mapping diff --git a/plugins/weather.py b/plugins/weather.py new file mode 100644 index 0000000..bb78eac --- /dev/null +++ b/plugins/weather.py @@ -0,0 +1,167 @@ +""" +This plugin provides a command to get weather information for a location. +""" + +import logging +import requests +import os +import simplematrixbotlib as botlib +from dotenv import load_dotenv + +# Load environment variables from .env file in the parent directory +# Get the directory where this plugin file is located +plugin_dir = os.path.dirname(os.path.abspath(__file__)) +# Get the parent directory (main bot directory) +parent_dir = os.path.dirname(plugin_dir) +# Load .env from parent directory +dotenv_path = os.path.join(parent_dir, '.env') +load_dotenv(dotenv_path) + +# OpenWeatherMap API configuration +# Get your free API key from: https://openweathermap.org/api +WEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY", "") +WEATHER_API_URL = "https://api.openweathermap.org/data/2.5/weather" + +async def handle_command(room, message, bot, prefix, config): + """ + Function to handle the !weather command. + + Args: + room (Room): The Matrix room where the command was invoked. + message (RoomMessage): The message object containing the command. + bot (Bot): The bot object. + prefix (str): The command prefix. + config (dict): Configuration parameters. + + Returns: + None + """ + match = botlib.MessageMatch(room, message, bot, prefix) + if match.is_not_from_this_bot() and match.prefix() and match.command("weather"): + logging.info("Received !weather command") + + # Check if API key is configured + if not WEATHER_API_KEY: + await bot.api.send_text_message( + room.room_id, + "Weather API key not configured. Please set OPENWEATHER_API_KEY environment variable." + ) + return + + args = match.args() + + # Check if location was provided + if len(args) < 1: + await bot.api.send_text_message( + room.room_id, + "Usage: !weather \nExample: !weather London or !weather New York,US" + ) + logging.info("Sent usage message for !weather") + return + + location = ' '.join(args) + + try: + # Make API request to OpenWeatherMap + params = { + 'q': location, + 'appid': WEATHER_API_KEY, + 'units': 'metric' # Use metric units (Celsius) + } + + response = requests.get(WEATHER_API_URL, params=params, timeout=10) + response.raise_for_status() + + weather_data = response.json() + + # Extract relevant weather information + city_name = weather_data['name'] + country = weather_data['sys']['country'] + temp = weather_data['main']['temp'] + feels_like = weather_data['main']['feels_like'] + humidity = weather_data['main']['humidity'] + description = weather_data['weather'][0]['description'].capitalize() + wind_speed = weather_data['wind'].get('speed', 0) + + # Convert temperature to Fahrenheit for display + temp_f = (temp * 9/5) + 32 + feels_like_f = (feels_like * 9/5) + 32 + + # Get weather emoji based on condition + weather_emoji = get_weather_emoji(weather_data['weather'][0]['main']) + + # Format the weather message + weather_message = f""" +{weather_emoji} Weather for {city_name}, {country}
+Condition: {description}
+Temperature: {temp:.1f}°C ({temp_f:.1f}°F)
+Feels like: {feels_like:.1f}°C ({feels_like_f:.1f}°F)
+Humidity: {humidity}%
+Wind Speed: {wind_speed} m/s +""".strip() + + await bot.api.send_markdown_message(room.room_id, weather_message) + logging.info(f"Sent weather information for {city_name}") + + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + await bot.api.send_text_message( + room.room_id, + f"Location '{location}' not found. Please check the spelling and try again." + ) + elif e.response.status_code == 401: + await bot.api.send_text_message( + room.room_id, + "Weather API authentication failed. Please check the API key configuration." + ) + else: + await bot.api.send_text_message( + room.room_id, + f"Error fetching weather data: HTTP {e.response.status_code}" + ) + logging.error(f"HTTP error fetching weather for '{location}': {e}") + + except requests.exceptions.RequestException as e: + await bot.api.send_text_message( + room.room_id, + f"Error connecting to weather service: {e}" + ) + logging.error(f"Request error fetching weather for '{location}': {e}") + + except (KeyError, ValueError) as e: + await bot.api.send_text_message( + room.room_id, + "Error parsing weather data. Please try again later." + ) + logging.error(f"Error parsing weather data for '{location}': {e}") + + +def get_weather_emoji(condition): + """ + Get an emoji based on weather condition. + + Args: + condition (str): Weather condition from API. + + Returns: + str: Weather emoji. + """ + weather_emojis = { + 'Clear': '☀️', + 'Clouds': '☁️', + 'Rain': '🌧️', + 'Drizzle': '🌦️', + 'Thunderstorm': '⛈️', + 'Snow': '❄️', + 'Mist': '🌫️', + 'Fog': '🌫️', + 'Haze': '🌫️', + 'Smoke': '🌫️', + 'Dust': '🌫️', + 'Sand': '🌫️', + 'Ash': '🌫️', + 'Squall': '💨', + 'Tornado': '🌪️' + } + + return weather_emojis.get(condition, '🌡️')