tasks pas fini
This commit is contained in:
parent
a5fd33774c
commit
3aab9eace2
197
task_bot.py
197
task_bot.py
@ -1,43 +1,138 @@
|
|||||||
import os
|
import os
|
||||||
import discord
|
import discord
|
||||||
import datetime
|
import datetime
|
||||||
|
import pytz
|
||||||
import google_auth_oauthlib.flow
|
import google_auth_oauthlib.flow
|
||||||
from weather import OpenWeatherMapAPIClient
|
from weather import OpenWeatherMapAPIClient
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
|
from google.oauth2.credentials import Credentials
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from database import UserLocation
|
||||||
global service
|
from asyncio import to_thread
|
||||||
service = None
|
|
||||||
|
|
||||||
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
||||||
WEATHER_TOKEN = os.getenv("WEATHER_TOKEN")
|
WEATHER_TOKEN = os.getenv("WEATHER_TOKEN")
|
||||||
|
|
||||||
|
CREDENTIALS_FILE = './calendar/credentials.json'
|
||||||
|
TOKEN_FILE = './calendar/token.json'
|
||||||
|
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/tasks.readonly']
|
||||||
|
|
||||||
weather_client = OpenWeatherMapAPIClient(WEATHER_TOKEN, "MyDiscordWeatherBot")
|
weather_client = OpenWeatherMapAPIClient(WEATHER_TOKEN, "MyDiscordWeatherBot")
|
||||||
intents = discord.Intents(messages=True, guilds=True)
|
intents = discord.Intents(messages=True, guilds=True)
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
bot = commands.Bot(command_prefix='!', intents=intents)
|
bot = commands.Bot(command_prefix='!', intents=intents)
|
||||||
|
user_location_db = UserLocation()
|
||||||
|
TZ = pytz.timezone("Europe/Paris")
|
||||||
|
|
||||||
|
|
||||||
|
def build_calendar_service():
|
||||||
|
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
|
||||||
|
return build('calendar', 'v3', credentials=creds)
|
||||||
|
|
||||||
|
def build_tasks_service():
|
||||||
|
creds = creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
|
||||||
|
return build('tasks', 'v1', credentials=creds)
|
||||||
|
|
||||||
|
def parse_google_time(s: str) -> datetime.datetime:
|
||||||
|
dt = datetime.datetime.fromisoformat(s)
|
||||||
|
if 'T' not in s:
|
||||||
|
return dt.replace(tzinfo=datetime.timezone.utc)
|
||||||
|
|
||||||
|
if s.endswith('Z'):
|
||||||
|
return datetime.datetime.fromisoformat(s.replace('Z', '+00:00'))
|
||||||
|
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
return dt.replace(tzinfo=datetime.timezone.utc)
|
||||||
|
return dt
|
||||||
|
|
||||||
|
def is_due_today(due_iso: str, tz: pytz.timezone = TZ) -> bool:
|
||||||
|
if not due_iso:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
dt = datetime.datetime.fromisoformat(due_iso.replace("Z", "+00:00"))
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return dt.astimezone(tz).date() == datetime.datetime.now(tz).date()
|
||||||
|
|
||||||
|
def due_time_str(due_iso: str, tz: pytz.timezone = TZ) -> str:
|
||||||
|
if not due_iso:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
dt = datetime.datetime.fromisoformat(due_iso.replace("Z", "+00:00")).astimezone(tz)
|
||||||
|
return dt.strftime("%H:%M")
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
#Calendar authentification
|
#Calendar authentification
|
||||||
async def authenticate():
|
async def authenticate():
|
||||||
|
if os.path.exists(TOKEN_FILE):
|
||||||
|
print("Token file exists; skipping interactive authentication.")
|
||||||
|
return
|
||||||
|
|
||||||
print("Authenticating..")
|
print("No token found: running interactive OAuth flow(will open browser).")
|
||||||
global service
|
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
|
||||||
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file('./calendar/credentials.json', SCOPES)
|
|
||||||
credentials = flow.run_local_server(port=0)
|
credentials = flow.run_local_server(port=0)
|
||||||
service = build('calendar', 'v3', credentials=credentials)
|
|
||||||
|
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()
|
||||||
|
try:
|
||||||
|
service = build_tasks_service()
|
||||||
|
tl_res = service.tasklists().list(maxResults=100).execute()
|
||||||
|
lists = tl_res.get("items", [])
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Today's tasks",
|
||||||
|
color=0x2ecc71,
|
||||||
|
timestamp=datetime.datetime.now(tz=TZ)
|
||||||
|
)
|
||||||
|
any_tasks = False
|
||||||
|
|
||||||
|
for tl in lists:
|
||||||
|
tl_id = tl["id"]
|
||||||
|
tl_title = tl.get("title", "Untitled")
|
||||||
|
tasks_res = service.tasks().list(tasklist=tl_id, showCompleted=True, maxResults=200).execute()
|
||||||
|
items = tasks_res.get("items", [])
|
||||||
|
day_tasks = [t for t in items if is_due_today(t.get("due"))]
|
||||||
|
if not day_tasks:
|
||||||
|
continue
|
||||||
|
any_tasks = True
|
||||||
|
lines = []
|
||||||
|
for t in sorted(day_tasks, key=lambda x: x.get("due") or ""):
|
||||||
|
status = "✅done" if t.get("status") == "completed" else "🔲"
|
||||||
|
time = due_time_str(t.get("due"))
|
||||||
|
title = t.get("title", "(no title)")
|
||||||
|
tid = t.get("id", "")
|
||||||
|
lines.append(f"{status} {title}{(' - ' + time) if time else ''} (id:{tid})")
|
||||||
|
value = "\n".join(lines)[:1024]
|
||||||
|
embed.add_field(name=tl_title, value=value, inline=False)
|
||||||
|
|
||||||
|
if not any_tasks:
|
||||||
|
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')
|
@bot.tree.command(name='weekly_events', description="Check this weeks saved events")
|
||||||
async def events(interaction: discord.Interaction):
|
async def events(interaction: discord.Interaction):
|
||||||
await interaction.response.defer(thinking=True)
|
await interaction.response.defer(thinking=True)
|
||||||
|
|
||||||
now = datetime.datetime.utcnow()
|
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()
|
current_weekday = now.weekday()
|
||||||
|
|
||||||
start_of_week = now - datetime.timedelta(days=current_weekday)
|
start_of_week = now - datetime.timedelta(days=current_weekday)
|
||||||
@ -46,22 +141,34 @@ async def events(interaction: discord.Interaction):
|
|||||||
end_of_week = start_of_week + datetime.timedelta(days=6)
|
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)
|
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:
|
try:
|
||||||
events_result = service.events().list(
|
events_result = service.events().list(
|
||||||
calendarId='primary',
|
calendarId='primary',
|
||||||
timeMin=start_of_week.isoformat() + 'Z',
|
timeMin=start_of_week.isoformat(),
|
||||||
timeMax=end_of_week.isoformat() + 'Z',
|
timeMax=end_of_week.isoformat(),
|
||||||
singleEvents=True,
|
singleEvents=True,
|
||||||
orderBy='startTime'
|
orderBy='startTime'
|
||||||
).execute()
|
).execute()
|
||||||
|
|
||||||
|
print(f"API Response: {events_result}")
|
||||||
events = events_result.get('items',[])
|
events = events_result.get('items',[])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if not events:
|
if not events:
|
||||||
await interaction.response.send_message('No events this week !')
|
embed = discord.Embed(
|
||||||
|
title="No events this week!",
|
||||||
|
description="Go outside maybe"
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="📅 Weekly Events Summary",
|
title="📅 Weekly Events Summary",
|
||||||
|
description="This week's events",
|
||||||
color=discord.Color.blue()
|
color=discord.Color.blue()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,7 +180,9 @@ async def events(interaction: discord.Interaction):
|
|||||||
event_time = datetime.datetime.fromisoformat(event_time_str)
|
event_time = datetime.datetime.fromisoformat(event_time_str)
|
||||||
|
|
||||||
event_time_local = event_time.strftime("%A, %B %d, %Y, %H:%M")
|
event_time_local = event_time.strftime("%A, %B %d, %Y, %H:%M")
|
||||||
task = f"**{event['summary']}**\n⏰ **At: {event_time_local}**"
|
|
||||||
|
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)
|
embed.add_field(name="\u200b", value=task, inline=False)
|
||||||
|
|
||||||
await interaction.followup.send(embed=embed)
|
await interaction.followup.send(embed=embed)
|
||||||
@ -83,8 +192,21 @@ async def events(interaction: discord.Interaction):
|
|||||||
|
|
||||||
|
|
||||||
#Weather commad tree
|
#Weather commad tree
|
||||||
@bot.tree.command(name="weather")
|
@bot.command(name="set_location")
|
||||||
async def current_weather(interaction: discord.Interaction, location: str):
|
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
|
print(f"Received weather command from {interaction.user.display_name}") # Log intéraction
|
||||||
|
|
||||||
@ -93,36 +215,22 @@ async def current_weather(interaction: discord.Interaction, location: str):
|
|||||||
print(f"JSON API {current_weather}")
|
print(f"JSON API {current_weather}")
|
||||||
|
|
||||||
# Check that current_weather is a dictionary and contains necessary keys
|
# Check that current_weather is a dictionary and contains necessary keys
|
||||||
if not isinstance(current_weather, dict) or 'main' not in current_weather or 'weather' not in current_weather:
|
if isinstance(current_weather, dict) and 'main' in current_weather and 'weather' in current_weather:
|
||||||
await interaction.response.send_message("Could not retrieve weather data. Please check the location.")
|
weather_condition = current_weather['weather'][0]['main']
|
||||||
return
|
temp = current_weather['main']['temp']
|
||||||
|
icon = current_weather['weather'][0]['icon']
|
||||||
|
|
||||||
weather_list = current_weather['weather']
|
embed = discord.Embed(
|
||||||
if len(weather_list) == 0:
|
title=f"Current weather in {location}",
|
||||||
await interaction.response.send_message("Weather information is not available.")
|
description=f"Temperature: {temp}°C\nWeather condition: {weather_condition}",
|
||||||
return
|
color=discord.Color.yellow()
|
||||||
|
)
|
||||||
|
embed.set_thumbnail(url=f"https://openweathermap.org/img/wn/{icon}.png")
|
||||||
|
|
||||||
weather_condition = weather_list[0]['main']
|
await interaction.response.send_message(embed=embed)
|
||||||
|
|
||||||
temp = current_weather['main']['temp']
|
else:
|
||||||
|
await interaction.response.send_message("Could not retrieve weather data. Please check the location or try again. ")
|
||||||
icon = current_weather['weather']['icon']
|
|
||||||
embed = discord.Embed(
|
|
||||||
title=f"Current weather in {location}",
|
|
||||||
description=f"Temperature: {temp}°C\nSky: {weather_condition}",
|
|
||||||
)
|
|
||||||
embed.set_thumbnail(url=f"https://openweathermap.org/img/wn/{icon}.png")
|
|
||||||
|
|
||||||
await interaction.response.send_message(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
@bot.tree.command(name='hello')
|
|
||||||
async def hello_command(interaction: discord.Interaction):
|
|
||||||
|
|
||||||
print(f"Received hello command from {interaction.user.display_name}") # Log intéraction
|
|
||||||
|
|
||||||
user_nick = interaction.user.display_name
|
|
||||||
await interaction.response.send_message(f'Hello {user_nick}!')
|
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
@ -135,5 +243,6 @@ async def on_ready():
|
|||||||
print("Registered Commands:")
|
print("Registered Commands:")
|
||||||
for command in commands:
|
for command in commands:
|
||||||
print(f"- {command.name}")
|
print(f"- {command.name}")
|
||||||
|
print("your bot is online and ready to serve !")
|
||||||
|
|
||||||
bot.run(DISCORD_TOKEN)
|
bot.run(DISCORD_TOKEN)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user