added comments

This commit is contained in:
Tenzing Kandang 2026-04-29 22:44:59 +02:00
parent e0e7d8655c
commit de1f1e9d55
4 changed files with 165 additions and 14 deletions

View File

@ -1 +1,51 @@
# Dask bot
# Dask bot*
Dask bot(or however you want to name it) is an open source discord bot which allows you to visualize the weather, your events saved in a google calendar and also mark your tasks in google tasks as completed for the day. It also keeps log of the tasks completed so at the end of your day, you can check out who did what at what time.
This bot is meant to be launched and maintained by yourself for full control on what you want the bot to be. So no third party can come between your bot and your group (Except the weather and google services ofcourse).
Reccomended for a small group of people(4-5). One google account for the groups tasks and events. To be used by a "admin" who has the role of giving tasks and managing the server.
## What you need to get started
Python3
A openweather account for weather forecasts
A google account that you intend to use for your group
Discord server
VScode or any programming tool
And some programming knowledge would help
## Following will be a step by step guide to ensure you successfully get the bot to work.
1. Creating/Adding the bot
After downloading or cloning the source code in a safe proper place, you'll need to add/create the bot in discord. This guide from geeks for geeks will help you for this process. <link> Scroll down to the "How to create a bot using Python" part. Follow till step 4. For the 0Auth2 section, you will also want to check on, 'Send messages' and 'View channels' in 'Bot permissions'.
2. Adding some requirements
Now you will open VScode or whatever software you have and open our bot folder. In the .env file you will want to add the discord token that you previously saved while creating the bot on discord.
For the weather token, log in to your openweather account(if not created, create one <link>) > go to API keys > and copy the key in the .env
You will also want to go the main.py file, at the bottom of the file at line 317, you're gonna want to add the channels id where you want the bot to send a message when it's online. Generally the general channel lol. For that you want to go enable the Developper mode in your discord settings and then right click on the channel to copy id.
3. GOOGLE
This part can be a bit confusing, but stay focused folks. Here is a guide to succesfully integrate google calendar to your bot <link>. In step 3 you also want to enable Google tasks API cause we need it. At step 5 you're going to choose 'Desktop application' instead of 'Web application'. Give them the same name as your bot so it's not complicated later. You are also going to want to download the JSON file and save it in the .secret folder of our bot. Rename it to credentials.json. While adding the scopes to step 7, you're going to choose,
- calendarlist.readonly
- events.public.readonly
- calendar.freebusy
- calendars.readonly
- calendar events
- calendar.events.owned.readonly
- tasks
- tasks.readonly
Everything after step 8 is not necessary, unless you're curious about it.
4. Final requirements
Almost everything is setup now. You must have noticed the requirements.txt, it contains everything you need to install for the bot to run. But first, We're going to make a python's virtual environnement where the bot is going to run as a server. Be in your bot's folder and type
`python -m venv . dvenv`
It will create the folder for the virtual environnement. And now to launch it
`source dvenv/bin/activate`
Boom, now you should be in the virtual environnement where you can safely run the server.
Now to install the requirements.txt:
`pip install -r requirements.txt`
And you are now done! The bot is ready to be online !
Start the bot
`python3 main.py`
There you go your bot is now online and ready to serve. Once it's started you should be able to log in to your google account, and see the bot online on your server. You can keep it running 24/7 or turn it on whenever you want, it's up to you! Add more stuff if you want, remove stuff if you want, it's all yours!
PS. DON'T SHARE YOUR TOKENS TO PEOPLE YOU DONT TRUST !!!

61
cogs/database.py Normal file
View File

@ -0,0 +1,61 @@
import sqlite3
class UserLocation:
def __init__(self, db_name='users_location.db'):
self.db_name = db_name
self.create_table()
def create_table(self):
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_locations (
username TEXT NOT NULL,
user_id INTEGER PRIMARY KEY,
location TEXT NOT NULL
)
''')
conn.commit()
conn.close()
def set_user_location(self, username:str, user_id: int, location: str):
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO user_locations (username, user_id, location) VALUES (?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET location=excluded.location''', (username, user_id, location))
conn.commit()
conn.close()
def get_user_location(self, user_id: int) -> str:
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
cursor.execute('SELECT location FROM user_locations WHERE user_id = ?', (user_id,))
row = cursor.fetchone()
conn.close()
return row[0] if row else None
class TaskCompleted:
def __init__(self, db_name='tasks_completed.db'):
self.db_name = db_name
self.create_table()
def create_table(self):
conn = sqlite3.connect(self.db_name)
cursor = conn.execute('''
CREATE TABLE IF NOT EXISTS task_completed (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
username TEXT NOT NULL,
task TEXT NOT NULL,
time TEXT NOT NULL
)''')
conn.commit()
conn.close()
def set_info(self, user_id:int, username: str, task: str, time: str):
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO task_completed (user_id, username, task, time) VALUES (?, ?, ?, ?)''', (user_id, username, task, time))
conn.commit()
conn.close()

34
cogs/weather.py Normal file
View File

@ -0,0 +1,34 @@
import requests
import os
from geopy.geocoders import Nominatim
from dotenv import load_dotenv
load_dotenv()
WEATHER_TOKEN = os.getenv("WEATHER_TOKEN")
class OpenWeatherMapAPIClient:
def __init__(self, api_token, name):
self.base_url = "https://api.openweathermap.org"
self._api_token = WEATHER_TOKEN
self.name = name
def get_geodata(self, location):
geolocator = Nominatim(user_agent=self.name)
geodata = geolocator.geocode(location, language="en-us").raw
return geodata["lat"], geodata["lon"]
def get_current_weather(self, location, units="metric"):
url = f"{self.base_url}/data/2.5/weather"
lat, lon = self.get_geodata(location)
params = {
"lat": lat,
"lon": lon,
"units": units,
"appid": self._api_token,
}
response = requests.get(url, params=params)
data = response.json()
return data

32
main.py
View File

@ -1,11 +1,11 @@
import discord, asyncio, json, datetime, os
from google_auth_oauthlib.flow import InstalledAppFlow
from bot.weather import OpenWeatherMapAPIClient
from cogs.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 bot.database import UserLocation, TaskCompleted
from cogs.database import UserLocation, TaskCompleted
from asyncio import to_thread
from discord import app_commands
from discord.ui import View, Select
@ -14,9 +14,9 @@ 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']
CREDENTIALS_FILE = './.secret/credentials.json'
TOKEN_FILE = './.secret/saved_token.json'
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/tasks.readonly', 'https://www.googleapis.com/auth/tasks'] #les scopes sont des permissions securisée de google
weather_client = OpenWeatherMapAPIClient(WEATHER_TOKEN, "MyDiscordWeatherBot")
intents = discord.Intents(messages=True, guilds=True)
@ -49,7 +49,7 @@ def parse_rfc3339_to_local_date(ts):
except Exception:
return None
#Calendar authentification
#Se connecter au compte google depuis lequel on voudrait les infos
async def authenticate():
if os.path.exists(TOKEN_FILE):
print("Token file exists; skipping interactive authentication.")
@ -59,11 +59,11 @@ async def authenticate():
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
credentials = flow.run_local_server(port=0)
with open(TOKEN_FILE, 'w') as f:
with open(TOKEN_FILE, 'w') as f: #sauvegarde les credentiels du compte utilisé pour pas se connecter à chaque fois
f.write(credentials.to_json())
print(f"Saved credentials to {TOKEN_FILE} for instant access")
#Marl task as completed
#Classe qui marque les tâches comme "fait"
class TaskSelect(Select):
def __init__(self, options):
super().__init__(placeholder="Choose a task to complete...",
@ -106,12 +106,14 @@ class TaskSelect(Select):
except Exception as e:
await interaction.followup.send(f"Error: {e}", ephemeral=True)
#ajout de classe TaskSelect à la partie ui
class TasksView(View):
def __init__(self, options, timeout=120):
super().__init__(timeout=timeout)
self.add_item(TaskSelect(options))
#commande qui affiche les tâches à faire sauvegardé sur google tasks
@bot.tree.command(name="daily_tasks", description="Check today's saved tasks and complete them")
async def today(interaction: discord.Interaction):
await interaction.response.defer(thinking=True)
@ -132,7 +134,8 @@ async def today(interaction: discord.Interaction):
options = []
total = 0
#Boucle for qui passe par chaque liste des tâches
for tl in lists:
tl_id = tl.get("id")
tl_title = tl.get("title") or "<untitled>"
@ -142,7 +145,7 @@ async def today(interaction: discord.Interaction):
items = tasks_res.get("items", []) or []
#Boucle for qui passe par chaque tâche dans le liste de tâche
for t in items:
if not isinstance(t, dict):
continue
@ -177,7 +180,7 @@ async def today(interaction: discord.Interaction):
except Exception as e:
await interaction.followup.send(f"An error occured:{e}")
#commande qui affiche les évenements sauvegardé sur google agenda
@bot.tree.command(name='weekly_events', description="Check this weeks saved events")
async def events(interaction: discord.Interaction):
await interaction.response.defer(thinking=True)
@ -252,7 +255,7 @@ async def events(interaction: discord.Interaction):
await interaction.followup.send(f'An error occurred: {e}')
#Weather commad tree
#Commande qui enregistre le localisation du user
@bot.command(name="set_location")
async def set_location(ctx, *, location: str):
user_id = ctx.author.id
@ -261,6 +264,8 @@ async def set_location(ctx, *, location: str):
await ctx.send(f"Location set to: {location}")
print(f"{username} just set location to {location}")
#Commande qui affiche la météo du jour
@bot.tree.command(name="weather", description="Check the weather!")
async def current_weather(interaction: discord.Interaction, location: str = None):
user_id = interaction.user.id
@ -277,7 +282,7 @@ async def current_weather(interaction: discord.Interaction, location: str = None
print(f"API RESPONSE {current_weather}")
# Check that current_weather is a dictionary and contains necessary keys
# Vérifier que current_weather est un dictionnaire et contient les clés nécessaires
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']
@ -296,6 +301,7 @@ async def current_weather(interaction: discord.Interaction, location: str = None
else:
await interaction.response.send_message("Could not retrieve weather data. Please check the location or try again. ")
#Cette partie est le "main" qui écoute les commandes et le lance quand ils sont appelés
@bot.event
async def on_ready():
print(f'Logged in as {bot.user}')