added comments
This commit is contained in:
parent
e0e7d8655c
commit
de1f1e9d55
52
README.md
52
README.md
@ -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
61
cogs/database.py
Normal 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
34
cogs/weather.py
Normal 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
32
main.py
@ -1,11 +1,11 @@
|
|||||||
import discord, asyncio, json, datetime, os
|
import discord, asyncio, json, datetime, os
|
||||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
from bot.weather import OpenWeatherMapAPIClient
|
from cogs.weather import OpenWeatherMapAPIClient
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
from google.oauth2.credentials import Credentials
|
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 bot.database import UserLocation, TaskCompleted
|
from cogs.database import UserLocation, TaskCompleted
|
||||||
from asyncio import to_thread
|
from asyncio import to_thread
|
||||||
from discord import app_commands
|
from discord import app_commands
|
||||||
from discord.ui import View, Select
|
from discord.ui import View, Select
|
||||||
@ -14,9 +14,9 @@ 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 = './secret/credentials.json'
|
CREDENTIALS_FILE = './.secret/credentials.json'
|
||||||
TOKEN_FILE = './secret/token.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']
|
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")
|
weather_client = OpenWeatherMapAPIClient(WEATHER_TOKEN, "MyDiscordWeatherBot")
|
||||||
intents = discord.Intents(messages=True, guilds=True)
|
intents = discord.Intents(messages=True, guilds=True)
|
||||||
@ -49,7 +49,7 @@ def parse_rfc3339_to_local_date(ts):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
#Calendar authentification
|
#Se connecter au compte google depuis lequel on voudrait les infos
|
||||||
async def authenticate():
|
async def authenticate():
|
||||||
if os.path.exists(TOKEN_FILE):
|
if os.path.exists(TOKEN_FILE):
|
||||||
print("Token file exists; skipping interactive authentication.")
|
print("Token file exists; skipping interactive authentication.")
|
||||||
@ -59,11 +59,11 @@ async def authenticate():
|
|||||||
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
|
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
|
||||||
credentials = flow.run_local_server(port=0)
|
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())
|
f.write(credentials.to_json())
|
||||||
print(f"Saved credentials to {TOKEN_FILE} for instant access")
|
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):
|
class TaskSelect(Select):
|
||||||
def __init__(self, options):
|
def __init__(self, options):
|
||||||
super().__init__(placeholder="Choose a task to complete...",
|
super().__init__(placeholder="Choose a task to complete...",
|
||||||
@ -106,12 +106,14 @@ class TaskSelect(Select):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await interaction.followup.send(f"Error: {e}", ephemeral=True)
|
await interaction.followup.send(f"Error: {e}", ephemeral=True)
|
||||||
|
|
||||||
|
#ajout de classe TaskSelect à la partie ui
|
||||||
class TasksView(View):
|
class TasksView(View):
|
||||||
def __init__(self, options, timeout=120):
|
def __init__(self, options, timeout=120):
|
||||||
super().__init__(timeout=timeout)
|
super().__init__(timeout=timeout)
|
||||||
self.add_item(TaskSelect(options))
|
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")
|
@bot.tree.command(name="daily_tasks", description="Check today's saved tasks and complete them")
|
||||||
async def today(interaction: discord.Interaction):
|
async def today(interaction: discord.Interaction):
|
||||||
await interaction.response.defer(thinking=True)
|
await interaction.response.defer(thinking=True)
|
||||||
@ -132,7 +134,8 @@ async def today(interaction: discord.Interaction):
|
|||||||
|
|
||||||
options = []
|
options = []
|
||||||
total = 0
|
total = 0
|
||||||
|
|
||||||
|
#Boucle for qui passe par chaque liste des tâches
|
||||||
for tl in lists:
|
for tl in lists:
|
||||||
tl_id = tl.get("id")
|
tl_id = tl.get("id")
|
||||||
tl_title = tl.get("title") or "<untitled>"
|
tl_title = tl.get("title") or "<untitled>"
|
||||||
@ -142,7 +145,7 @@ async def today(interaction: discord.Interaction):
|
|||||||
items = tasks_res.get("items", []) or []
|
items = tasks_res.get("items", []) or []
|
||||||
|
|
||||||
|
|
||||||
|
#Boucle for qui passe par chaque tâche dans le liste de tâche
|
||||||
for t in items:
|
for t in items:
|
||||||
if not isinstance(t, dict):
|
if not isinstance(t, dict):
|
||||||
continue
|
continue
|
||||||
@ -177,7 +180,7 @@ async def today(interaction: discord.Interaction):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await interaction.followup.send(f"An error occured:{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")
|
@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)
|
||||||
@ -252,7 +255,7 @@ async def events(interaction: discord.Interaction):
|
|||||||
await interaction.followup.send(f'An error occurred: {e}')
|
await interaction.followup.send(f'An error occurred: {e}')
|
||||||
|
|
||||||
|
|
||||||
#Weather commad tree
|
#Commande qui enregistre le localisation du user
|
||||||
@bot.command(name="set_location")
|
@bot.command(name="set_location")
|
||||||
async def set_location(ctx, *, location: str):
|
async def set_location(ctx, *, location: str):
|
||||||
user_id = ctx.author.id
|
user_id = ctx.author.id
|
||||||
@ -261,6 +264,8 @@ async def set_location(ctx, *, location: str):
|
|||||||
await ctx.send(f"Location set to: {location}")
|
await ctx.send(f"Location set to: {location}")
|
||||||
print(f"{username} just set location 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!")
|
@bot.tree.command(name="weather", description="Check the weather!")
|
||||||
async def current_weather(interaction: discord.Interaction, location: str = None):
|
async def current_weather(interaction: discord.Interaction, location: str = None):
|
||||||
user_id = interaction.user.id
|
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}")
|
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:
|
if isinstance(current_weather, dict) and 'main' in current_weather and 'weather' in current_weather:
|
||||||
weather_condition = current_weather['weather'][0]['main']
|
weather_condition = current_weather['weather'][0]['main']
|
||||||
temp = current_weather['main']['temp']
|
temp = current_weather['main']['temp']
|
||||||
@ -296,6 +301,7 @@ async def current_weather(interaction: discord.Interaction, location: str = None
|
|||||||
else:
|
else:
|
||||||
await interaction.response.send_message("Could not retrieve weather data. Please check the location or try again. ")
|
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
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
print(f'Logged in as {bot.user}')
|
print(f'Logged in as {bot.user}')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user