Dask_bot/task_bot.py
2026-03-30 16:10:15 +02:00

259 lines
9.4 KiB
Python

import os
import discord
import datetime
from google_auth_oauthlib.flow import InstalledAppFlow
from utils.weather import OpenWeatherMapAPIClient
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
from discord.ext import commands
from dotenv import load_dotenv
from utils.database import UserLocation
from asyncio import to_thread
load_dotenv()
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
WEATHER_TOKEN = os.getenv("WEATHER_TOKEN")
CREDENTIALS_FILE = './secret/credentials.json'
TOKEN_FILE = './secret/token.json'
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/tasks.readonly', 'https://www.googleapis.com/auth/tasks']
weather_client = OpenWeatherMapAPIClient(WEATHER_TOKEN, "MyDiscordWeatherBot")
intents = discord.Intents(messages=True, guilds=True)
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
user_location_db = UserLocation()
TZ = datetime.datetime.now().astimezone().tzinfo
def build_calendar_service():
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
return build('calendar', 'v3', credentials=creds)
def build_tasks_service():
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
service = build('tasks', 'v1', credentials=creds)
return service
def parse_rfc3339_to_local_date(ts):
if not ts:
return None
try:
s = ts.replace("Z", "+00:00") if ts.endswith("Z") else ts
if "T" in ts:
dt = datetime.datetime.fromisoformat(s)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=datetime.timezone.utc)
return dt.astimezone(TZ).date()
return datetime.date.fromisoformat(s)
except Exception:
return None
#Calendar authentification
async def authenticate():
if os.path.exists(TOKEN_FILE):
print("Token file exists; skipping interactive authentication.")
return
print("No token found: running interactive OAuth flow(will open browser).")
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
credentials = flow.run_local_server(port=0)
with open(TOKEN_FILE, 'w') as f:
f.write(credentials.to_json())
print(f"Saved credentials to {TOKEN_FILE} for instant access")
@bot.tree.command(name="daily_tasks", description="Check today's saved tasks")
async def today(interaction: discord.Interaction):
await interaction.response.defer(thinking=True)
try:
service = build_tasks_service()
tl_res = service.tasklists().list(maxResults=100).execute()
lists = tl_res.get("items", []) or []
today = datetime.datetime.now(tz=TZ).date()
embed = discord.Embed(
title="Today's tasks",
color=0x2ecc71,
timestamp=datetime.datetime.now(tz=TZ)
)
added = 0
for tl in lists:
tl_id = tl.get("id")
tl_title = tl.get("title") or "<untitled>"
tasks_res = service.tasks().list(
tasklist=tl_id, showCompleted=True, showHidden=True, maxResults=250).execute() or {}
items = tasks_res.get("items", []) or []
lines = []
for t in items:
if not isinstance(t, dict):
continue
due_date = parse_rfc3339_to_local_date(t.get("due"))
completed_date = parse_rfc3339_to_local_date(t.get("completed"))
print("title:", t.get("title"), "due:", t.get("due"), "completed:", t.get("completed"), "status:", t.get("status"))
if due_date != today and completed_date != today:
continue
status = "" if t.get("status") == "completed" else "🔲"
title = t.get("title") or "(no title)"
if completed_date == today and t.get("completed"):
completed_short = t["completed"].replace("T", " ").split(".")[0]
lines.append(f"{status} {title} - completed {completed_short}")
else:
if due_date:
lines.append(f"{status} {title} (due {due_date.isoformat()})")
else:
lines.append(f"{status}{title}")
if not lines:
continue
value = "\n".join(lines)[:1024]
embed.add_field(name=tl_title, value=value, inline=False)
added += 1
if added == 0:
embed.description = "No tasks due today."
await interaction.followup.send(embed=embed)
except Exception as e:
await interaction.followup.send(f"An error occured:{e}")
@bot.tree.command(name='weekly_events', description="Check this weeks saved events")
async def events(interaction: discord.Interaction):
await interaction.response.defer(thinking=True)
try:
service = build_calendar_service()
except FileNotFoundError:
await interaction.followup.send("Calendar credentials not found.")
return
except Exception as e:
await interaction.followup.send(f"Failed to build service: {e}")
return
now = datetime.datetime.now(datetime.timezone.utc)
current_weekday = now.weekday()
start_of_week = now - datetime.timedelta(days=current_weekday)
start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)
end_of_week = start_of_week + datetime.timedelta(days=6)
end_of_week= end_of_week.replace(hour=23, minute=59, second=59, microsecond=999999)
print(f"Start of week: {start_of_week.isoformat()}, End of week: {end_of_week.isoformat()}")
try:
events_result = service.events().list(
calendarId='primary',
timeMin=start_of_week.isoformat(),
timeMax=end_of_week.isoformat(),
singleEvents=True,
orderBy='startTime'
).execute()
print(f"API Response: {events_result}")
events = events_result.get('items',[])
if not events:
embed.description = "No tasks due today"
await interaction.followup.send(embed=embed)
return
else:
embed = discord.Embed(
title="📅 This week's events",
color=discord.Color.blue(),
timestamp=datetime.datetime.now(tz=TZ)
)
for event in events:
event_time_str = event['start'].get('dateTime', event['start'].get('date'))
if 'Z' in event_time_str:
event_time = datetime.datetime.fromisoformat(event_time_str.replace('Z', '+00:00'))
else:
event_time = datetime.datetime.fromisoformat(event_time_str)
event_time_local = event_time.strftime("%A, %B %d, %Y, %H:%M")
event_description = event.get('description')
task = f"**{event['summary']}**\n{event_time_local}\n Description: {event_description}"
embed.add_field(name="\u200b", value=task, inline=False)
await interaction.followup.send(embed=embed)
except Exception as e:
await interaction.followup.send(f'An error occurred: {e}')
#Weather commad tree
@bot.command(name="set_location")
async def set_location(ctx, *, location: str):
user_id = ctx.author.id
user_location_db.set_user_location(user_id, location)
await ctx.send(f"Location set to: {location}")
@bot.tree.command(name="weather", description="Check the weather!")
async def current_weather(interaction: discord.Interaction, location: str = None):
user_id = interaction.user.id
if location is None:
location = user_location_db.get_user_location(user_id)
if not location:
await interaction.response.send_message("Please provide a location or set one using '!set_location <location>'.\n(Your location will be stored in a database for ease of access)")
return
print(f"Received weather command from {interaction.user.display_name}") # Log intéraction
current_weather = weather_client.get_current_weather(location)
print(f"JSON API {current_weather}")
# Check that current_weather is a dictionary and contains necessary keys
if isinstance(current_weather, dict) and 'main' in current_weather and 'weather' in current_weather:
weather_condition = current_weather['weather'][0]['main']
temp = current_weather['main']['temp']
icon = current_weather['weather'][0]['icon']
embed = discord.Embed(
title=f"Current weather in {location}",
description=f"Temperature: {temp}°C\nWeather condition: {weather_condition}",
color=discord.Color.yellow(),
timestamp=datetime.datetime.now(tz=TZ)
)
embed.set_thumbnail(url=f"https://openweathermap.org/img/wn/{icon}.png")
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message("Could not retrieve weather data. Please check the location or try again. ")
@bot.event
async def on_ready():
print(f'Logged in as {bot.user}')
await authenticate()
await bot.tree.sync()
#Liste commandes enregistré
commands = await bot.tree.fetch_commands()
print("Registered Commands:")
for command in commands:
print(f"- {command.name}")
print("- !set_location")
print("your bot is online and ready to serve !")
bot.run(DISCORD_TOKEN)