Merge pull request 'weather' (#6) from weather into main
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Garfbot CI/CD Deployment / Deploy (push) Failing after 4s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Garfbot CI/CD Deployment / Deploy (push) Failing after 4s
				
			Reviewed-on: #6
This commit is contained in:
		| @@ -10,6 +10,7 @@ from garfpy import ( | |||||||
|     Kroger, |     Kroger, | ||||||
|     GarfAI, |     GarfAI, | ||||||
|     GarfbotRespond, |     GarfbotRespond, | ||||||
|  |     WeatherAPI, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -28,6 +29,7 @@ garf_respond = GarfbotRespond() | |||||||
| garfield = GarfAI() | garfield = GarfAI() | ||||||
| iputils = IPUtils() | iputils = IPUtils() | ||||||
| kroger = Kroger() | kroger = Kroger() | ||||||
|  | weather = WeatherAPI() | ||||||
|  |  | ||||||
|  |  | ||||||
| @garfbot.event | @garfbot.event | ||||||
| @@ -107,6 +109,12 @@ async def on_message(message): | |||||||
|         ) |         ) | ||||||
|         await garfield.garfpic(message, prompt) |         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 |     # GarfBot help | ||||||
|     elif lower.strip() == "garfbot help": |     elif lower.strip() == "garfbot help": | ||||||
|         embed = discord.Embed(title="**Need help?**", color=0x4D4D4D) |         embed = discord.Embed(title="**Need help?**", color=0x4D4D4D) | ||||||
|   | |||||||
| @@ -8,3 +8,4 @@ from .respond import GarfbotRespond | |||||||
| from .aod import aod_message | from .aod import aod_message | ||||||
| from .qr import generate_qr | from .qr import generate_qr | ||||||
| from .iputils import IPUtils | from .iputils import IPUtils | ||||||
|  | from .weather import WeatherAPI | ||||||
| @@ -48,11 +48,11 @@ class GarfAI: | |||||||
|                     async with session.get(image_url) as resp: |                     async with session.get(image_url) as resp: | ||||||
|                         if resp.status == 200: |                         if resp.status == 200: | ||||||
|                             image_data = await resp.read() |                             image_data = await resp.read() | ||||||
|                             ram_image = io.BytesIO(image_data) |                             image = io.BytesIO(image_data) | ||||||
|                             ram_image.seek(0) |                             image.seek(0) | ||||||
|                             timestamp = message.created_at.strftime("%Y%m%d%H%M%S") |                             timestamp = message.created_at.strftime("%Y%m%d%H%M%S") | ||||||
|                             filename = f"{timestamp}_generated_image.png" |                             filename = f"{timestamp}_generated_image.png" | ||||||
|                             sendfile = discord.File(fp=ram_image, filename=filename) |                             sendfile = discord.File(fp=image, filename=filename) | ||||||
|                             try: |                             try: | ||||||
|                                 await message.channel.send(file=sendfile) |                                 await message.channel.send(file=sendfile) | ||||||
|                             except Exception as e: |                             except Exception as e: | ||||||
|   | |||||||
							
								
								
									
										203
									
								
								garfpy/weather.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								garfpy/weather.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
		Reference in New Issue
	
	Block a user