Building Weather Chatbots with Discord.py and WeatherAPI
Discord has become the go-to platform for developers, gamers, and communities worldwide. With over 150 million active users, it presents an excellent opportunity to build engaging weather bots that can serve real-time meteorological data directly within chat channels. In this comprehensive tutorial, we’ll create a sophisticated Discord weather bot using Python’s discord.py library and WeatherAPI’s robust endpoints.
By the end of this guide, you’ll have a fully functional Discord bot capable of delivering current weather conditions, multi-day forecasts, weather alerts, and astronomical data to your server members.
Prerequisites and Setup
Before diving into the code, ensure you have the following prerequisites sorted:
- Python 3.8 or higher installed on your system
- A Discord Developer Account and bot token
- A free WeatherAPI account with API key
- Basic understanding of Python and asynchronous programming
First, let’s install the required dependencies:
pip install discord.py requests python-dotenv asyncio
Next, sign up for your free WeatherAPI account to obtain your API key. The free tier provides 100,000 calls per monthβmore than sufficient for most Discord servers.
Discord Bot Configuration
Create a new file called .env to store your sensitive credentials securely:
DISCORD_TOKEN=your_discord_bot_token_here
WEATHER_API_KEY=your_weatherapi_key_here
Now, let’s create the foundation for our weather bot. Create a new file called weather_bot.py:
import discord
from discord.ext import commands
import requests
import asyncio
import os
from dotenv import load_dotenv
from datetime import datetime, timedelta
import json
# Load environment variables
load_dotenv()
# Bot configuration
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
# WeatherAPI configuration
WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')
WEATHER_BASE_URL = 'https://api.weatherapi.com/v1'
@bot.event
async def on_ready():
print(f'{bot.user} has connected to Discord!')
print(f'Bot is in {len(bot.guilds)} guilds')
if __name__ == '__main__':
bot.run(os.getenv('DISCORD_TOKEN'))
Building Core Weather Functions
Let’s create a weather utility class to handle all WeatherAPI interactions efficiently:
class WeatherAPI:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = 'https://api.weatherapi.com/v1'
async def get_current_weather(self, location):
"""Fetch current weather conditions"""
url = f"{self.base_url}/current.json"
params = {
'key': self.api_key,
'q': location,
'aqi': 'yes'
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
return None
async def get_forecast(self, location, days=3):
"""Fetch weather forecast"""
url = f"{self.base_url}/forecast.json"
params = {
'key': self.api_key,
'q': location,
'days': min(days, 10),
'aqi': 'yes',
'alerts': 'yes'
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
return None
async def get_astronomy(self, location, date=None):
"""Fetch astronomical data"""
url = f"{self.base_url}/astronomy.json"
params = {
'key': self.api_key,
'q': location,
'dt': date or datetime.now().strftime('%Y-%m-%d')
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
return None
# Initialize weather API
weather_api = WeatherAPI(WEATHER_API_KEY)
Implementing Discord Commands
Now let’s create our Discord bot commands. We’ll start with a current weather command:
@bot.command(name='weather', help='Get current weather for a location')
async def current_weather(ctx, *, location):
"""Current weather command"""
async with ctx.typing():
data = await weather_api.get_current_weather(location)
if not data:
await ctx.send("β Sorry, I couldn't fetch weather data for that location. Please check the spelling and try again.")
return
current = data['current']
location_info = data['location']
# Create embedded message
embed = discord.Embed(
title=f"π€οΈ Current Weather in {location_info['name']}, {location_info['country']}",
color=0x3498db,
timestamp=datetime.now()
)
embed.add_field(
name="π‘οΈ Temperature",
value=f"{current['temp_c']}Β°C ({current['temp_f']}Β°F)\nFeels like {current['feelslike_c']}Β°C",
inline=True
)
embed.add_field(
name="βοΈ Conditions",
value=current['condition']['text'],
inline=True
)
embed.add_field(
name="π¨ Wind",
value=f"{current['wind_kph']} km/h {current['wind_dir']}",
inline=True
)
embed.add_field(
name="π§ Humidity",
value=f"{current['humidity']}%",
inline=True
)
embed.add_field(
name="π Visibility",
value=f"{current['vis_km']} km",
inline=True
)
embed.add_field(
name="βοΈ UV Index",
value=current['uv'],
inline=True
)
if 'air_quality' in current:
aqi = current['air_quality']
embed.add_field(
name="π¬οΈ Air Quality (PM2.5)",
value=f"{aqi.get('pm2_5', 'N/A')} ΞΌg/mΒ³",
inline=True
)
embed.set_thumbnail(url=f"https:{current['condition']['icon']}")
embed.set_footer(text="Powered by WeatherAPI.com")
await ctx.send(embed=embed)
Advanced Forecast Command
Let’s create a more sophisticated forecast command that displays multi-day weather predictions:
@bot.command(name='forecast', help='Get weather forecast (default 3 days, max 10)')
async def weather_forecast(ctx, location=None, days: int = 3):
"""Weather forecast command"""
if not location:
await ctx.send("β Please specify a location. Usage: `!forecast London 5`")
return
if days > 10:
days = 10
await ctx.send("β οΈ Maximum forecast period is 10 days. Showing 10-day forecast.")
async with ctx.typing():
data = await weather_api.get_forecast(location, days)
if not data:
await ctx.send("β Unable to fetch forecast data. Please check the location and try again.")
return
location_info = data['location']
forecast_days = data['forecast']['forecastday']
embed = discord.Embed(
title=f"π
{days}-Day Weather Forecast",
description=f"π {location_info['name']}, {location_info['country']}",
color=0x2ecc71,
timestamp=datetime.now()
)
for day_data in forecast_days:
date = datetime.strptime(day_data['date'], '%Y-%m-%d')
day = day_data['day']
forecast_text = (
f"π‘οΈ {day['mintemp_c']}Β°C - {day['maxtemp_c']}Β°C\n"
f"βοΈ {day['condition']['text']}\n"
f"π§οΈ {day['daily_chance_of_rain']}% chance of rain\n"
f"π¨ Max wind: {day['maxwind_kph']} km/h"
)
embed.add_field(
name=f"{date.strftime('%A, %d %B')}",
value=forecast_text,
inline=False
)
# Check for weather alerts
if 'alerts' in data and data['alerts']['alert']:
alert_text = "\n".join([alert['headline'] for alert in data['alerts']['alert'][:2]])
embed.add_field(
name="β οΈ Weather Alerts",
value=alert_text[:1000] + "..." if len(alert_text) > 1000 else alert_text,
inline=False
)
embed.set_footer(text="Powered by WeatherAPI.com")
await ctx.send(embed=embed)
Astronomy Data Integration
WeatherAPI provides excellent astronomical data. Let’s create a command to display sunrise, sunset, and moon phase information:
@bot.command(name='astronomy', help='Get astronomical data for a location')
async def astronomy_data(ctx, *, location):
"""Astronomy command"""
async with ctx.typing():
data = await weather_api.get_astronomy(location)
if not data:
await ctx.send("β Could not retrieve astronomical data for that location.")
return
location_info = data['location']
astro = data['astronomy']['astro']
embed = discord.Embed(
title=f"π Astronomical Data",
description=f"π {location_info['name']}, {location_info['country']}",
color=0x9b59b6,
timestamp=datetime.now()
)
embed.add_field(name="π
Sunrise", value=astro['sunrise'], inline=True)
embed.add_field(name="π Sunset", value=astro['sunset'], inline=True)
embed.add_field(name="π Moon Phase", value=astro['moon_phase'], inline=True)
embed.add_field(name="π Moonrise", value=astro['moonrise'], inline=True)
embed.add_field(name="π Moonset", value=astro['moonset'], inline=True)
embed.add_field(name="π Moon Illumination", value=f"{astro['moon_illumination']}%", inline=True)
embed.set_footer(text="Powered by WeatherAPI.com")
await ctx.send(embed=embed)
Error Handling and User Experience
Robust error handling is crucial for a reliable Discord bot. Let’s implement comprehensive error management:
@bot.event
async def on_command_error(ctx, error):
"""Global error handler"""
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send(f"β Missing required argument: `{error.param.name}`. Use `!help {ctx.command.name}` for usage information.")
elif isinstance(error, commands.BadArgument):
await ctx.send("β Invalid argument provided. Please check your input and try again.")
elif isinstance(error, commands.CommandNotFound):
await ctx.send("β Command not found. Use `!help` to see available commands.")
else:
print(f"Unhandled error: {error}")
await ctx.send("β An unexpected error occurred. Please try again later.")
@bot.command(name='help_weather', help='Show weather bot commands')
async def help_weather(ctx):
"""Custom help command"""
embed = discord.Embed(
title="π€ Weather Bot Commands",
description="Get real-time weather data powered by WeatherAPI.com",
color=0xe74c3c
)
commands_info = [
("!weather ", "Get current weather conditions"),
("!forecast [days]", "Get weather forecast (max 10 days)"),
("!astronomy ", "Get sunrise, sunset, and moon data"),
]
for command, description in commands_info:
embed.add_field(name=command, value=description, inline=False)
embed.add_field(
name="π Location Examples",
value="β’ City name: `London`\nβ’ City, Country: `London, UK`\nβ’ Coordinates: `40.7128,-74.0060`\nβ’ Postcode: `SW1A 1AA`",
inline=False
)
embed.set_footer(text="Powered by WeatherAPI.com β’ Sign up for free at weatherapi.com")
await ctx.send(embed=embed)
Advanced Features and Enhancements
To make your weather bot more engaging, consider implementing these additional features:
Automatic Weather Alerts
@bot.command(name='alerts', help='Check for weather alerts in a location')
async def weather_alerts(ctx, *, location):
"""Weather alerts command"""
data = await weather_api.get_forecast(location, days=1)
if not data or 'alerts' not in data:
await ctx.send("β No alert data available for this location.")
return
alerts = data['alerts']['alert']
if not alerts:
await ctx.send(f"β
No active weather alerts for {data['location']['name']}")
return
embed = discord.Embed(
title="β οΈ Active Weather Alerts",
description=f"π {data['location']['name']}, {data['location']['country']}",
color=0xe74c3c
)
for alert in alerts[:3]: # Limit to 3 alerts
embed.add_field(
name=alert['headline'],
value=alert['desc'][:500] + "..." if len(alert['desc']) > 500 else alert['desc'],
inline=False
)
await ctx.send(embed=embed)
Deployment and Best Practices
When deploying your Discord weather bot, consider these best practices:
- Rate Limiting: Implement command cooldowns to prevent API quota exhaustion
- Caching: Cache frequently requested locations to reduce API calls
- Logging: Implement comprehensive logging for debugging and monitoring
- Hosting: Use platforms like Heroku, Railway, or VPS for 24/7 uptime
# Add command cooldown
@commands.cooldown(1, 10, commands.BucketType.user)
@bot.command(name='weather')
async def current_weather(ctx, *, location):
# Command implementation here
pass
Conclusion
You’ve successfully built a comprehensive Discord weather bot using WeatherAPI’s robust endpoints. The bot can deliver current conditions, multi-day forecasts, astronomical data, and weather alerts directly to your Discord server.
WeatherAPI’s reliable infrastructure, with average response times under 200ms and coverage of over 4 million locations worldwide, ensures your bot provides accurate, real-time weather information to your community. The free tier’s 100,000 monthly calls provide excellent value for most Discord servers.
Sign up for your free WeatherAPI account today and start building weather-powered Discord experiences that your server members will love. With WeatherAPI’s comprehensive documentation and multiple SDKs, you can extend this bot with marine data, historical weather, and advanced forecasting features.
Ready to take your Discord bot to the next level? Explore WeatherAPI’s Pro+ plans for extended forecasting, higher rate limits, and premium features that will set your weather bot apart from the rest.
