diff --git a/garfmain.py b/garfmain.py index ed8d9fb..7cab5d3 100644 --- a/garfmain.py +++ b/garfmain.py @@ -10,6 +10,7 @@ from garfpy import ( Kroger, GarfAI, GarfbotRespond, + WeatherAPI, ) @@ -28,6 +29,7 @@ garf_respond = GarfbotRespond() garfield = GarfAI() iputils = IPUtils() kroger = Kroger() +weather = WeatherAPI() @garfbot.event @@ -107,6 +109,12 @@ async def on_message(message): ) await garfield.garfpic(message, prompt) + # Weather + elif lower.startswith("garfbot weather "): + location = lower[16:] + embed = await weather.weather(location) + await message.channel.send(embed=embed) + # GarfBot help elif lower.strip() == "garfbot help": embed = discord.Embed(title="**Need help?**", color=0x4D4D4D) diff --git a/garfpy/__init__.py b/garfpy/__init__.py index a11fd0b..16147aa 100644 --- a/garfpy/__init__.py +++ b/garfpy/__init__.py @@ -8,3 +8,4 @@ from .respond import GarfbotRespond from .aod import aod_message from .qr import generate_qr from .iputils import IPUtils +from .weather import WeatherAPI \ No newline at end of file diff --git a/garfpy/garfai.py b/garfpy/garfai.py index 2e3c883..b3f81ec 100644 --- a/garfpy/garfai.py +++ b/garfpy/garfai.py @@ -48,11 +48,11 @@ class GarfAI: async with session.get(image_url) as resp: if resp.status == 200: image_data = await resp.read() - ram_image = io.BytesIO(image_data) - ram_image.seek(0) + image = io.BytesIO(image_data) + image.seek(0) timestamp = message.created_at.strftime("%Y%m%d%H%M%S") filename = f"{timestamp}_generated_image.png" - sendfile = discord.File(fp=ram_image, filename=filename) + sendfile = discord.File(fp=image, filename=filename) try: await message.channel.send(file=sendfile) except Exception as e: diff --git a/garfpy/weather.py b/garfpy/weather.py new file mode 100644 index 0000000..b152b7b --- /dev/null +++ b/garfpy/weather.py @@ -0,0 +1,203 @@ +import discord +import aiohttp +import config +from garfpy import logger + + +class WeatherAPI: + def __init__(self, api_key=None): + self.api_key = api_key or config.WEATHER_TOKEN + self.base_url = "https://api.openweathermap.org/data/2.5/weather" + + def parse_location(self, location): + location = location.strip().lower() + + if location.isdigit(): + if len(location) == 5: + return {"zip": f"{location},US"} + else: + return {"zip": location} + + parts = location.split() + + if len(parts) == 1: + return {"q": f"{parts[0]},US"} + + elif len(parts) == 2: + city, second = parts + + if len(second) == 2 and second.upper() not in [ + "AK", + "AL", + "AR", + "AZ", + "CA", + "CO", + "CT", + "DE", + "FL", + "GA", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VT", + "WA", + "WI", + "WV", + "WY", + "DC", + "AS", + "GU", + "MP", + "PR", + "VI", + ]: + return {"q": f"{city},{second.upper()}"} + else: + return {"q": f"{city},{second},US"} + + elif len(parts) == 3: + city, state, country = parts + return {"q": f"{city},{state},{country.upper()}"} + + else: + # Check if last part looks like country code + if len(parts[-1]) == 2: + city_parts = parts[:-1] + country = parts[-1] + city_name = " ".join(city_parts) + return {"q": f"{city_name},{country.upper()}"} + + elif len(parts) >= 2 and len(parts[-1]) == 2 and len(parts[-2]) <= 2: + city_parts = parts[:-2] + state = parts[-2] + country = parts[-1] + city_name = " ".join(city_parts) + return {"q": f"{city_name},{state},{country.upper()}"} + + else: + city_name = " ".join(parts) + return {"q": f"{city_name},US"} + + async def get_weather(self, location, units="metric"): + location_params = self.parse_location(location) + + params = { + **location_params, + "appid": self.api_key, + "units": units, + } + + try: + async with aiohttp.ClientSession() as session: + async with session.get(self.base_url, params=params) as response: + response.raise_for_status() + return await response.json() + except aiohttp.ClientError as e: + logger.error(f"Error fetching weather data for '{location}': {e}") + return None + + def weather_embed(self, weather_data): + if not weather_data: + embed = discord.Embed( + title="❌ Error", + description="Could not fetch weather data", + color=discord.Color.red(), + ) + return embed + + weather_emojis = { + "clear sky": "☀️", + "few clouds": "🌤️", + "scattered clouds": "⛅", + "broken clouds": "☁️", + "shower rain": "🌦️", + "rain": "🌧️", + "thunderstorm": "⛈️", + "snow": "❄️", + "mist": "🌫️", + } + + condition = weather_data["weather"][0]["description"].lower() + emoji = weather_emojis.get(condition, "🌍") + + embed = discord.Embed( + title=f"{emoji} Weather in {weather_data['name']}", + description=f"{weather_data['weather'][0]['description'].title()}", + color=discord.Color.blue(), + ) + + embed.add_field( + name="🌡️ Temperature", + value=f"{weather_data['main']['temp']}°C\nFeels like {weather_data['main']['feels_like']}°C", + inline=True, + ) + + embed.add_field( + name="💧 Humidity", + value=f"{weather_data['main']['humidity']}%", + inline=True, + ) + + embed.add_field( + name="🗜️ Pressure", + value=f"{weather_data['main']['pressure']} hPa", + inline=True, + ) + + if "wind" in weather_data: + embed.add_field( + name="💨 Wind Speed", + value=f"{weather_data['wind']['speed']} m/s", + inline=True, + ) + + if "visibility" in weather_data: + embed.add_field( + name="👁️ Visibility", + value=f"{weather_data['visibility'] / 1000} km", + inline=True, + ) + + embed.set_footer( + text=f"Lat: {weather_data['coord']['lat']}, Lon: {weather_data['coord']['lon']}" + ) + + return embed + + async def weather(self, location): + weather_data = await self.get_weather(location) + embed = self.weather_embed(weather_data) + return embed