From 528ac486f46eb429d58511e9a1f5363a159c813d Mon Sep 17 00:00:00 2001 From: joseph Date: Sat, 9 Dec 2023 21:19:12 +0100 Subject: [PATCH 01/18] initial commit --- README.md | 34 ++++- Resources/Colloscope.ods | Bin 0 -> 39760 bytes Resources/EDT_A.ics | 227 ++++++++++++++++++++++++++++++++ Resources/EDT_B.ics | 220 +++++++++++++++++++++++++++++++ Resources/EDT_Base.ics | 269 ++++++++++++++++++++++++++++++++++++++ Resources/EDT_Langues.ics | 122 +++++++++++++++++ Resources/colles.csv | 74 +++++++++++ Resources/eleves.csv | 46 +++++++ create_calendar.py | 185 ++++++++++++++++++++++++++ main.py | 65 +++++++++ 10 files changed, 1241 insertions(+), 1 deletion(-) create mode 100644 Resources/Colloscope.ods create mode 100644 Resources/EDT_A.ics create mode 100644 Resources/EDT_B.ics create mode 100644 Resources/EDT_Base.ics create mode 100644 Resources/EDT_Langues.ics create mode 100644 Resources/colles.csv create mode 100644 Resources/eleves.csv create mode 100644 create_calendar.py create mode 100644 main.py diff --git a/README.md b/README.md index edcf4ad..ddf08da 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ # staticky-bot -Code du bot privé des MP2I de SL \ No newline at end of file +Code du bot privé des MP2I de SL + +### Librairies: +- discord.py +- dotenv + +Le fichier `.env` doit être renseigné avec +``` +# Le token du bot discord +TOKEN= +# L'ID du serveur +GUILD_ID= +``` +## EDT Generator +Voici la description du module de générateur d'EDT au format `.ics`, importable dans tous les calendriers numériques, depuis l'EDT de la classe et le colloscope + +### Librairies supplémentaires nécessaires: +- icalendar +- pandas + + + +## Traitement des données au prélable +A l'aide d'un site tel que [I love PDF](https://www.ilovepdf.com/fr/pdf_en_excel), il faut transormer le colloscope en format tableur. Il faut ensuite générer le fichier `eleves.csv`, qui contient la liste des élèves, leur groupe et leur demi-classe, puis `colles.csv`, qui contient les colles ainsi que les groupes pour la SI uniquement. (Le reste des infos sur les demi-groupes n'est pas nécessaire). + +Il faut ensuite générer les fichiers communs d'EDT, avec plus ou moins d'automatisation (en vrai c'est pas si long, 1h pour les 4 fichiers quand on sait faire). + +### Fonctionnement +Le bot génère ensuie l'edt en récupérant les colles du groupe, les cours de SI, et les cours de langue correspondant à l'entrée utilisateur + +### TOUDOUX +- Créer un ou deux scripts d'automatisation de création d'emploi du temps, pour qu'il n'y ait besoin que de créer un seul EDT de groupe, et que le 2nd soit créé en décalant d'une semaine, et enfin pour que les vacances soient gérées uniquement en mettant leur date +- Moins prioritaire / pas forcément utile: le fichier `.ics` est mis en ligne sur `mp2i-vms.fr`, pour qu'il soit syncronisé avec le client, et qu'il n'y ait besoin de modifier que sur un seul EDT pour que les changements se propagent. \ No newline at end of file diff --git a/Resources/Colloscope.ods b/Resources/Colloscope.ods new file mode 100644 index 0000000000000000000000000000000000000000..3c62e222bd74150a1182073a249c81bd4c5e66bd GIT binary patch literal 39760 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 bw1R)C datetime.time: + """ + Transforme un string de debut de semaine au format dd/mm/yyyy en datetime.time + """ + return datetime.datetime.strptime(date_string, "%d/%m/%y") + + +def get_colles_groupe(np_colles, groupe, option_langue): + """ + Renvoie les colles pour un groupe sous la forme d'une liste d'évènements ICS + + np_colles: l'array 2D numpy contenant le colloscope + groupe: str, le numéro du groupe + option_langue: str, les options de langue + """ + liste_colles = [] + for row in np_colles[1:]: + + if pandas.isnull(row[0]) or row[0] == "pas de colle": + # il n'y a pas de colle, on skip ! + continue + + + # Si la colle n'est pas la LV1 de l'élève, on skip + if row[0] == "Anglais" and not option_langue.startswith("EN"): + continue + elif row[0] == "Allemand" and not option_langue.startswith("DE"): + continue + elif row[0] == "Espagnol" and not option_langue.startswith("ES"): + continue + + for index, colle in enumerate(row[5:]): + # les quatre premières cases ne sont pas + if pandas.isnull(colle) or colle != groupe: + continue + + new_colle = icalendar.Event() + new_colle.add("summary", f"Colle {row[0]}") + new_colle.add("description", f"{row[3]} {row[4]}") + + date = debut_semaine_to_datetime(np_colles[0][index+5]) + date += datetime.timedelta(days=jour_to_delta[row[1]]) + date += datetime.timedelta(hours=int(row[2][:-1])) + new_colle.add("dtstart", date) + + date += datetime.timedelta(hours=1) + new_colle.add("dtend", date) + + liste_colles.append(new_colle) + + return liste_colles + +def get_cours_SI(np_colles, groupe): + """ + Renvoie les colles pour un groupe sous la forme d'une liste d'évènements ICS, cad + icalendar.Event + + np_colles: l'array 2D numpy contenant le colloscope + groupe: str, le numéro du groupe + """ + liste_cours = [] + for index, cell in enumerate(np_colles[-1][5:]): + heure = 17 + groupes = cell.split("§") + if groupe in groupes or f"\n{groupe}" in groupes: + heure = 16 + + cours_si = icalendar.Event() + cours_si.add("summary", "TD SI") + cours_si.add("description", "M. Derumeaux R415") + + date = debut_semaine_to_datetime(np_colles[0][index+5]) + date += datetime.timedelta(days=jour_to_delta["mardi"]) + date += datetime.timedelta(hours=heure) + cours_si.add("dtstart", date) + + date += datetime.timedelta(hours=1) + cours_si.add("dtend", date) + liste_cours.append(cours_si) + + + return liste_cours + +def get_langues(langues): + """ + Renvoie la liste de cours de langue associés + list[icalendar.Event] + """ + liste_langues = [] + p = Path(__file__).with_name('Calendars') + p = Path(p, f"EDT_Langues.ics") + with p.open('r') as f: + opened_cal = icalendar.Calendar.from_ical(f.read()) + + for event in opened_cal.walk("vevent"): + if event.get("summary") in option_langues[langues]: + liste_langues.append(event) + + return liste_langues + + +def get_calendar(groupe, langues): + """" + Renvoie un calendrier (icalendar.Calendar) pour un groupe avec des langues donné + """ + moitie = None + for row in np_eleves[1:]: + if row[2] == int(groupe): + moitie = row[3] + break + + new_cal = icalendar.Calendar() + p = Path(__file__).with_name('Calendars') + p = Path(p, f"EDT_{moitie}.ics") + + with p.open('r') as f: + opened_cal = icalendar.Calendar.from_ical(f.read()) + + for event in opened_cal.walk("vevent"): + new_cal.add_component(event) + + p = Path(__file__).with_name('Calendars') + p = Path(p, f"EDT_Base.ics") + + with p.open('r') as f: + opened_cal = icalendar.Calendar.from_ical(f.read()) + + for event in opened_cal.walk("vevent"): + new_cal.add_component(event) + + for event in get_langues(langues): + new_cal.add_component(event) + + for event in get_cours_SI(np_colles, groupe): + new_cal.add_component(event) + + for event in get_colles_groupe(np_colles, groupe, langues): + new_cal.add_component(event) + + print(display(new_cal)) + return new_cal.to_ical() \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..0b6c2d3 --- /dev/null +++ b/main.py @@ -0,0 +1,65 @@ +from typing import Optional, List +from io import BytesIO, getenv +import discord + +from discord import app_commands +from discord.app_commands import Choice +from dotenv import load_dotenv + +from create_calendar import get_calendar, get_eleves, display + +load_dotenv() + +MY_GUILD = discord.Object(id=getenv("GUILD_ID")) # replace with your guild id + + +class MyClient(discord.Client): + def __init__(self): + super().__init__(intents=None) + self.tree = app_commands.CommandTree(self) + + async def setup_hook(self): + self.tree.copy_global_to(guild=MY_GUILD) + await self.tree.sync(guild=MY_GUILD) + + +client = MyClient() + +eleves = get_eleves() + + + +@client.event +async def on_ready(): + print(f'Logged in as {client.user} (ID: {client.user.id})') + print('------') + + +@client.tree.command() +@app_commands.choices(langues=[ + Choice(name='Anglais LV1, Autre ou Pas de LV2', value="EN"), + Choice(name='Anglais LV1, Allemand LV2', value="EN-DE"), + Choice(name='Anglais LV1, Espagnol LV2', value="EN-ES"), + Choice(name='Allemand LV1, Anglais LV2', value="DE-EN"), + Choice(name='Espagnol LV1, Anglais LV2', value="ES-EN") + ] +) +@app_commands.describe(groupe="Votre groupe de colle, groupe ∈ ⟦1, 15⟧") +@app_commands.describe(langues="Les options de langue que vous suivez. (Les langues suivies à H4 ne sont pas gérées, mettez 'Autres')") +async def edt( + interaction: discord.Interaction, + groupe: app_commands.Range[int, 1, 15], + langues: Choice[str]): + """ + Génère un emploi du temps prenant compte des colles, des langues et de la semaine. + """ + cal = get_calendar(str(groupe), langues.value) + calendrier = BytesIO(cal) + fichier = discord.File(fp=calendrier, filename=f"EDT-{groupe}-{langues.value}.ics") + + await interaction.response.send_message( + f"Bonjour, votre emploi du temps pour le groupe {groupe} ({langues.name}) au format `.ics` a bien été généré ! \n\n Vous pouvez l'importer dans la plupart des messageries, je vous recommande cependant de créer un nouveau calendrier avec de l'importer, au cas où vous vouliez l'enlever. \n\n*Si vous avez des retours à faire (bug, erreur,..), contactez Joseph.*", + file=fichier) + + +client.run(getenv("TOKEN")) From 65d8b4a699540fc47e61fbf3a4ac5b6dafccf424 Mon Sep 17 00:00:00 2001 From: joseph Date: Sat, 9 Dec 2023 21:29:59 +0100 Subject: [PATCH 02/18] fix some broken stuff --- create_calendar.py | 10 +++++----- main.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/create_calendar.py b/create_calendar.py index d69a572..cd95a9f 100644 --- a/create_calendar.py +++ b/create_calendar.py @@ -5,10 +5,10 @@ from pathlib import Path -pd_eleves = pandas.read_csv('eleves.csv') +pd_eleves = pandas.read_csv('Resources/eleves.csv') np_eleves = pd_eleves.to_numpy() -pd_colles = pandas.read_csv('colles.csv') +pd_colles = pandas.read_csv('Resources/colles.csv') np_colles = pd_colles.to_numpy() @@ -131,7 +131,7 @@ def get_langues(langues): list[icalendar.Event] """ liste_langues = [] - p = Path(__file__).with_name('Calendars') + p = Path(__file__).with_name('Resources') p = Path(p, f"EDT_Langues.ics") with p.open('r') as f: opened_cal = icalendar.Calendar.from_ical(f.read()) @@ -154,7 +154,7 @@ def get_calendar(groupe, langues): break new_cal = icalendar.Calendar() - p = Path(__file__).with_name('Calendars') + p = Path(__file__).with_name('Resources') p = Path(p, f"EDT_{moitie}.ics") with p.open('r') as f: @@ -163,7 +163,7 @@ def get_calendar(groupe, langues): for event in opened_cal.walk("vevent"): new_cal.add_component(event) - p = Path(__file__).with_name('Calendars') + p = Path(__file__).with_name('Resources') p = Path(p, f"EDT_Base.ics") with p.open('r') as f: diff --git a/main.py b/main.py index 0b6c2d3..ac1b7d3 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ from typing import Optional, List -from io import BytesIO, getenv +from io import BytesIO +from os import getenv import discord from discord import app_commands From 696d3921cbadb6d7fdc9837b45a83a40f185eb5c Mon Sep 17 00:00:00 2001 From: joseph Date: Wed, 13 Dec 2023 18:32:19 +0100 Subject: [PATCH 03/18] add timezone to events --- Resources/Base_Calendar.ics | 26 +++++++++++ create_calendar.py | 91 +++++++++++++++++++++++++------------ main.py | 30 +++++++++--- 3 files changed, 111 insertions(+), 36 deletions(-) create mode 100644 Resources/Base_Calendar.ics diff --git a/Resources/Base_Calendar.ics b/Resources/Base_Calendar.ics new file mode 100644 index 0000000..8afc8c0 --- /dev/null +++ b/Resources/Base_Calendar.ics @@ -0,0 +1,26 @@ +BEGIN:VCALENDAR +PRODID:-//mp2i-vms//Stackity Bot Inc//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:EDT +X-WR-TIMEZONE:Europe/Paris +BEGIN:VTIMEZONE +TZID:Europe/Paris +X-LIC-LOCATION:Europe/Paris +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +END:VCALENDAR \ No newline at end of file diff --git a/create_calendar.py b/create_calendar.py index cd95a9f..58042cc 100644 --- a/create_calendar.py +++ b/create_calendar.py @@ -1,6 +1,7 @@ import pandas import datetime import icalendar +import uuid from pathlib import Path @@ -11,6 +12,7 @@ np_eleves = pd_eleves.to_numpy() pd_colles = pandas.read_csv('Resources/colles.csv') np_colles = pd_colles.to_numpy() +local_tz = "Europe/Paris" jour_to_delta = { "lundi": 0, @@ -33,6 +35,32 @@ option_langues = { def display(cal): return cal.to_ical().decode("utf-8").replace('\r\n', '\n').strip() + +def open_ics(filename: str) -> icalendar.Calendar: + """ + Ouvre un fichier .ics et renvoie un objet Calendar + """ + p = Path(__file__).with_name('Resources') + p = Path(p, filename) + with p.open('r') as f: + opened_cal = icalendar.Calendar.from_ical(f.read()) + + return opened_cal + + + +def add_events_from_ics(filename: str, new_cal: icalendar.Calendar): + """ + Ajoute des évènements depuis un fichier .ics vers un objet icalendar.Calendar + """ + cal_to_import = open_ics(filename=filename) + + for event in cal_to_import.walk("vevent"): + new_cal.add_component(event) + + return new_cal + + def get_eleves(): """ Renvoie la liste des eleves avec leur groupe associés à leur groupe, a utiliser dans la /cmd @@ -80,15 +108,19 @@ def get_colles_groupe(np_colles, groupe, option_langue): new_colle = icalendar.Event() new_colle.add("summary", f"Colle {row[0]}") - new_colle.add("description", f"{row[3]} {row[4]}") + new_colle.add("description", f"{row[3]}") + new_colle.add("location", row[4]) + + new_colle.add("dtstamp", datetime.datetime.now()) + new_colle.add("uid", str(uuid.uuid4())) date = debut_semaine_to_datetime(np_colles[0][index+5]) date += datetime.timedelta(days=jour_to_delta[row[1]]) date += datetime.timedelta(hours=int(row[2][:-1])) - new_colle.add("dtstart", date) + new_colle.add("dtstart", date, parameters={"tzid": local_tz}) date += datetime.timedelta(hours=1) - new_colle.add("dtend", date) + new_colle.add("dtend", date, parameters={"tzid": local_tz}) liste_colles.append(new_colle) @@ -112,14 +144,16 @@ def get_cours_SI(np_colles, groupe): cours_si = icalendar.Event() cours_si.add("summary", "TD SI") cours_si.add("description", "M. Derumeaux R415") + cours_si.add("dtstamp", datetime.datetime.now()) + cours_si.add("uid", str(uuid.uuid4())) date = debut_semaine_to_datetime(np_colles[0][index+5]) date += datetime.timedelta(days=jour_to_delta["mardi"]) date += datetime.timedelta(hours=heure) - cours_si.add("dtstart", date) + cours_si.add("dtstart", date, parameters={"tzid": local_tz}) date += datetime.timedelta(hours=1) - cours_si.add("dtend", date) + cours_si.add("dtend", date, parameters={"tzid": local_tz}) liste_cours.append(cours_si) @@ -143,7 +177,7 @@ def get_langues(langues): return liste_langues -def get_calendar(groupe, langues): +def get_calendar(groupe, langues, split: bool = False): """" Renvoie un calendrier (icalendar.Calendar) pour un groupe avec des langues donné """ @@ -152,34 +186,33 @@ def get_calendar(groupe, langues): if row[2] == int(groupe): moitie = row[3] break - - new_cal = icalendar.Calendar() - p = Path(__file__).with_name('Resources') - p = Path(p, f"EDT_{moitie}.ics") - - with p.open('r') as f: - opened_cal = icalendar.Calendar.from_ical(f.read()) - for event in opened_cal.walk("vevent"): - new_cal.add_component(event) - - p = Path(__file__).with_name('Resources') - p = Path(p, f"EDT_Base.ics") + new_cal_edt = open_ics("Base_Calendar.ics") - with p.open('r') as f: - opened_cal = icalendar.Calendar.from_ical(f.read()) + if split: + new_cal_colles = open_ics("Base_Calendar.ics") + + + + new_cal_edt = add_events_from_ics(filename="EDT_Base.ics", new_cal=new_cal_edt) + new_cal_edt = add_events_from_ics(filename=f"EDT_{moitie}.ics", new_cal=new_cal_edt) - for event in opened_cal.walk("vevent"): - new_cal.add_component(event) for event in get_langues(langues): - new_cal.add_component(event) - + new_cal_edt.add_component(event) + for event in get_cours_SI(np_colles, groupe): - new_cal.add_component(event) + new_cal_edt.add_component(event) - for event in get_colles_groupe(np_colles, groupe, langues): - new_cal.add_component(event) + if split: + for event in get_colles_groupe(np_colles, groupe, langues): + new_cal_colles.add_component(event) + return new_cal_edt.to_ical(), new_cal_colles.to_ical() + + else: + for event in get_colles_groupe(np_colles, groupe, langues): + new_cal_edt.add_component(event) + - print(display(new_cal)) - return new_cal.to_ical() \ No newline at end of file + + return [new_cal_edt.to_ical()] \ No newline at end of file diff --git a/main.py b/main.py index ac1b7d3..82301cb 100644 --- a/main.py +++ b/main.py @@ -47,20 +47,36 @@ async def on_ready(): ) @app_commands.describe(groupe="Votre groupe de colle, groupe ∈ ⟦1, 15⟧") @app_commands.describe(langues="Les options de langue que vous suivez. (Les langues suivies à H4 ne sont pas gérées, mettez 'Autres')") +@app_commands.describe(split="Créer deux fichiers différentes, un pour les colles et un pour le rest de l'EDT.") async def edt( interaction: discord.Interaction, groupe: app_commands.Range[int, 1, 15], - langues: Choice[str]): + langues: Choice[str], + split: bool = False): """ Génère un emploi du temps prenant compte des colles, des langues et de la semaine. """ - cal = get_calendar(str(groupe), langues.value) - calendrier = BytesIO(cal) - fichier = discord.File(fp=calendrier, filename=f"EDT-{groupe}-{langues.value}.ics") + cal_list = get_calendar(str(groupe), langues.value, split) + fichiers = [] - await interaction.response.send_message( - f"Bonjour, votre emploi du temps pour le groupe {groupe} ({langues.name}) au format `.ics` a bien été généré ! \n\n Vous pouvez l'importer dans la plupart des messageries, je vous recommande cependant de créer un nouveau calendrier avec de l'importer, au cas où vous vouliez l'enlever. \n\n*Si vous avez des retours à faire (bug, erreur,..), contactez Joseph.*", - file=fichier) + if split: + fichiers.append(discord.File(fp=BytesIO(cal_list[0]), filename=f"EDT-{groupe}-{langues.value}-WO.ics")) + fichiers.append(discord.File(fp=BytesIO(cal_list[1]), filename=f"Colles-{groupe}.ics")) + + message = f""" + \nVotre emploi du temps (`EDT-{groupe}-{langues.value}-WO.ics`) pour le groupe `{groupe}`, `{langues.name}` au format `.ics` a bien été généré, **sans les colles** + \n\nLes colles ont été généré séparemment pour le groupe `{groupe}` : `Colles-{groupe}.ics` + \n\nVous pouvez importer ces fichiers dans la plupart des calendriers, mais il est recommandé de créer un nouveau sous-calendrier pour facilement pouvoir le changer. + """ + + else: + fichiers.append(discord.File(fp=BytesIO(cal_list[0]), filename=f"EDT-{groupe}-{langues.value}.ics")) + message = f""" + \nVotre emploi du temps (`EDT-{groupe}-{langues.value}.ics`) pour le groupe `{groupe}`, `{langues.name}` au format `.ics` a bien été généré, **avec les colles** + \n\nVous pouvez importer ces fichiers dans la plupart des calendriers, mais il est recommandé de créer un nouveau sous-calendrier pour facilement pouvoir le changer. + """ + + await interaction.response.send_message(message, files=fichiers) client.run(getenv("TOKEN")) From 74ff35785388bd392f4c5ad68d0029978ae00bd2 Mon Sep 17 00:00:00 2001 From: voXrey <72698969+voXrey@users.noreply.github.com> Date: Sat, 16 Dec 2023 21:31:36 +0100 Subject: [PATCH 04/18] Cogs system initialised --- cogs/cog_edt.py | 11 +++++++++++ main.py | 25 ++++++++++++++++--------- 2 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 cogs/cog_edt.py diff --git a/cogs/cog_edt.py b/cogs/cog_edt.py new file mode 100644 index 0000000..11e9535 --- /dev/null +++ b/cogs/cog_edt.py @@ -0,0 +1,11 @@ +from discord.ext import commands + + +class EDT: + def __init__(self, bot): + self.bot = bot + print("EDT loaded") + + +def setup(bot): + bot.add_cog(EDT(bot)) diff --git a/main.py b/main.py index 82301cb..9371f43 100644 --- a/main.py +++ b/main.py @@ -16,10 +16,17 @@ MY_GUILD = discord.Object(id=getenv("GUILD_ID")) # replace with your guild id class MyClient(discord.Client): def __init__(self): - super().__init__(intents=None) + super().__init__(intents=discord.Intents.none()) self.tree = app_commands.CommandTree(self) + self.initial_extensions = ["cogs." + f[:-3] for f in os.listdir("./cogs") if f.endswith(".py") and f.name != "__init__.py"] + async def setup_hook(self): + + # cogs + for extension in self.initial_extensions: + await self.load_extension(extension) + self.tree.copy_global_to(guild=MY_GUILD) await self.tree.sync(guild=MY_GUILD) @@ -29,7 +36,6 @@ client = MyClient() eleves = get_eleves() - @client.event async def on_ready(): print(f'Logged in as {client.user} (ID: {client.user.id})') @@ -43,16 +49,17 @@ async def on_ready(): Choice(name='Anglais LV1, Espagnol LV2', value="EN-ES"), Choice(name='Allemand LV1, Anglais LV2', value="DE-EN"), Choice(name='Espagnol LV1, Anglais LV2', value="ES-EN") - ] +] ) @app_commands.describe(groupe="Votre groupe de colle, groupe ∈ ⟦1, 15⟧") -@app_commands.describe(langues="Les options de langue que vous suivez. (Les langues suivies à H4 ne sont pas gérées, mettez 'Autres')") +@app_commands.describe( + langues="Les options de langue que vous suivez. (Les langues suivies à H4 ne sont pas gérées, mettez 'Autres')") @app_commands.describe(split="Créer deux fichiers différentes, un pour les colles et un pour le rest de l'EDT.") async def edt( - interaction: discord.Interaction, - groupe: app_commands.Range[int, 1, 15], - langues: Choice[str], - split: bool = False): + interaction: discord.Interaction, + groupe: app_commands.Range[int, 1, 15], + langues: Choice[str], + split: bool = False): """ Génère un emploi du temps prenant compte des colles, des langues et de la semaine. """ @@ -75,7 +82,7 @@ async def edt( \nVotre emploi du temps (`EDT-{groupe}-{langues.value}.ics`) pour le groupe `{groupe}`, `{langues.name}` au format `.ics` a bien été généré, **avec les colles** \n\nVous pouvez importer ces fichiers dans la plupart des calendriers, mais il est recommandé de créer un nouveau sous-calendrier pour facilement pouvoir le changer. """ - + await interaction.response.send_message(message, files=fichiers) From 19b1f0eeea0343c3dc8d9cadc9bf4c7f43b6234c Mon Sep 17 00:00:00 2001 From: voXrey <72698969+voXrey@users.noreply.github.com> Date: Sun, 17 Dec 2023 00:33:27 +0100 Subject: [PATCH 05/18] - Update edt cog (command removed from main) - Update main - Add a parent class for cogs - Fixes --- .gitignore | 2 +- classes/my_cog.py | 17 ++++++++++ cogs/cog_edt.py | 61 +++++++++++++++++++++++++++++++--- main.py | 85 ++++++++++------------------------------------- 4 files changed, 92 insertions(+), 73 deletions(-) create mode 100644 classes/my_cog.py diff --git a/.gitignore b/.gitignore index 5d381cc..f295d3d 100644 --- a/.gitignore +++ b/.gitignore @@ -158,5 +158,5 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ diff --git a/classes/my_cog.py b/classes/my_cog.py new file mode 100644 index 0000000..9e8423f --- /dev/null +++ b/classes/my_cog.py @@ -0,0 +1,17 @@ +from discord.ext import commands +import discord + + +class MyCog(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + + async def cog_load(self): + print(f"{self.__class__.__name__} loaded!") + + async def cog_unload(self): + print(f"{self.__class__.__name__} unloaded!") + + +async def setup(bot): + await bot.add_cog(EDT(bot)) diff --git a/cogs/cog_edt.py b/cogs/cog_edt.py index 11e9535..826782a 100644 --- a/cogs/cog_edt.py +++ b/cogs/cog_edt.py @@ -1,11 +1,64 @@ +import discord +from discord import app_commands +from discord.app_commands import Choice from discord.ext import commands -class EDT: +from classes.my_cog import MyCog +from create_calendar import get_calendar, get_eleves, display +from io import BytesIO + + +class EDT(MyCog): def __init__(self, bot): + super().__init__(bot) + self.eleves = get_eleves() self.bot = bot - print("EDT loaded") + + @app_commands.command(name="edt") + @app_commands.choices(langues=[ + Choice(name='Anglais LV1, Autre ou Pas de LV2', value="EN"), + Choice(name='Anglais LV1, Allemand LV2', value="EN-DE"), + Choice(name='Anglais LV1, Espagnol LV2', value="EN-ES"), + Choice(name='Allemand LV1, Anglais LV2', value="DE-EN"), + Choice(name='Espagnol LV1, Anglais LV2', value="ES-EN") + ] + ) + @app_commands.describe(groupe="Votre groupe de colle, groupe ∈ ⟦1, 15⟧") + @app_commands.describe( + langues="Les options de langue que vous suivez. (Les langues suivies à H4 ne sont pas gérées, mettez 'Autres')") + @app_commands.describe(split="Créer deux fichiers différentes, un pour les colles et un pour le rest de l'EDT.") + async def edt( + self, + interaction: discord.Interaction, + groupe: app_commands.Range[int, 1, 15], + langues: Choice[str], + split: bool = False): + """ + Génère un emploi du temps prenant compte des colles, des langues et de la semaine. + """ + cal_list = get_calendar(str(groupe), langues.value, split) + fichiers = [] + + if split: + fichiers.append(discord.File(fp=BytesIO(cal_list[0]), filename=f"EDT-{groupe}-{langues.value}-WO.ics")) + fichiers.append(discord.File(fp=BytesIO(cal_list[1]), filename=f"Colles-{groupe}.ics")) + + message = f""" + \nVotre emploi du temps (`EDT-{groupe}-{langues.value}-WO.ics`) pour le groupe `{groupe}`, `{langues.name}` au format `.ics` a bien été généré, **sans les colles** + \n\nLes colles ont été généré séparemment pour le groupe `{groupe}` : `Colles-{groupe}.ics` + \n\nVous pouvez importer ces fichiers dans la plupart des calendriers, mais il est recommandé de créer un nouveau sous-calendrier pour facilement pouvoir le changer. + """ + + else: + fichiers.append(discord.File(fp=BytesIO(cal_list[0]), filename=f"EDT-{groupe}-{langues.value}.ics")) + message = f""" + \nVotre emploi du temps (`EDT-{groupe}-{langues.value}.ics`) pour le groupe `{groupe}`, `{langues.name}` au format `.ics` a bien été généré, **avec les colles** + \n\nVous pouvez importer ces fichiers dans la plupart des calendriers, mais il est recommandé de créer un nouveau sous-calendrier pour facilement pouvoir le changer. + """ + + await interaction.response.send_message(message, files=fichiers) -def setup(bot): - bot.add_cog(EDT(bot)) +async def setup(bot): + await bot.add_cog(EDT(bot)) diff --git a/main.py b/main.py index 9371f43..43e54f2 100644 --- a/main.py +++ b/main.py @@ -1,89 +1,38 @@ from typing import Optional, List -from io import BytesIO -from os import getenv +from os import getenv, listdir import discord from discord import app_commands from discord.app_commands import Choice +from discord.ext import commands from dotenv import load_dotenv -from create_calendar import get_calendar, get_eleves, display load_dotenv() -MY_GUILD = discord.Object(id=getenv("GUILD_ID")) # replace with your guild id - -class MyClient(discord.Client): +class MyClient(commands.Bot): def __init__(self): - super().__init__(intents=discord.Intents.none()) - self.tree = app_commands.CommandTree(self) + super().__init__(intents=discord.Intents.none(), command_prefix="!") - self.initial_extensions = ["cogs." + f[:-3] for f in os.listdir("./cogs") if f.endswith(".py") and f.name != "__init__.py"] + self.MY_GUILD = discord.Object(id=getenv("GUILD_ID")) + + self.initial_extensions = ["cogs." + f[:-3] for f in listdir("./cogs") if + f.endswith(".py") and f.__str__() != "__init__.py"] async def setup_hook(self): - # cogs for extension in self.initial_extensions: await self.load_extension(extension) - self.tree.copy_global_to(guild=MY_GUILD) - await self.tree.sync(guild=MY_GUILD) + self.tree.copy_global_to(guild=self.MY_GUILD) + await self.tree.sync(guild=self.MY_GUILD) + + async def on_ready(self): + print(f'Logged in as {self.user} (ID: {self.user.id})') + print('------') -client = MyClient() - -eleves = get_eleves() - - -@client.event -async def on_ready(): - print(f'Logged in as {client.user} (ID: {client.user.id})') - print('------') - - -@client.tree.command() -@app_commands.choices(langues=[ - Choice(name='Anglais LV1, Autre ou Pas de LV2', value="EN"), - Choice(name='Anglais LV1, Allemand LV2', value="EN-DE"), - Choice(name='Anglais LV1, Espagnol LV2', value="EN-ES"), - Choice(name='Allemand LV1, Anglais LV2', value="DE-EN"), - Choice(name='Espagnol LV1, Anglais LV2', value="ES-EN") -] -) -@app_commands.describe(groupe="Votre groupe de colle, groupe ∈ ⟦1, 15⟧") -@app_commands.describe( - langues="Les options de langue que vous suivez. (Les langues suivies à H4 ne sont pas gérées, mettez 'Autres')") -@app_commands.describe(split="Créer deux fichiers différentes, un pour les colles et un pour le rest de l'EDT.") -async def edt( - interaction: discord.Interaction, - groupe: app_commands.Range[int, 1, 15], - langues: Choice[str], - split: bool = False): - """ - Génère un emploi du temps prenant compte des colles, des langues et de la semaine. - """ - cal_list = get_calendar(str(groupe), langues.value, split) - fichiers = [] - - if split: - fichiers.append(discord.File(fp=BytesIO(cal_list[0]), filename=f"EDT-{groupe}-{langues.value}-WO.ics")) - fichiers.append(discord.File(fp=BytesIO(cal_list[1]), filename=f"Colles-{groupe}.ics")) - - message = f""" - \nVotre emploi du temps (`EDT-{groupe}-{langues.value}-WO.ics`) pour le groupe `{groupe}`, `{langues.name}` au format `.ics` a bien été généré, **sans les colles** - \n\nLes colles ont été généré séparemment pour le groupe `{groupe}` : `Colles-{groupe}.ics` - \n\nVous pouvez importer ces fichiers dans la plupart des calendriers, mais il est recommandé de créer un nouveau sous-calendrier pour facilement pouvoir le changer. - """ - - else: - fichiers.append(discord.File(fp=BytesIO(cal_list[0]), filename=f"EDT-{groupe}-{langues.value}.ics")) - message = f""" - \nVotre emploi du temps (`EDT-{groupe}-{langues.value}.ics`) pour le groupe `{groupe}`, `{langues.name}` au format `.ics` a bien été généré, **avec les colles** - \n\nVous pouvez importer ces fichiers dans la plupart des calendriers, mais il est recommandé de créer un nouveau sous-calendrier pour facilement pouvoir le changer. - """ - - await interaction.response.send_message(message, files=fichiers) - - -client.run(getenv("TOKEN")) +if __name__ == "__main__": + client = MyClient() + client.run(getenv("TOKEN")) From c4b81297bebe53d00fa36e6626ec615196588d26 Mon Sep 17 00:00:00 2001 From: voXrey <72698969+voXrey@users.noreply.github.com> Date: Sun, 17 Dec 2023 16:57:18 +0100 Subject: [PATCH 06/18] Cog fun added - Command "chuchoter" added --- cogs/cog_fun.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 cogs/cog_fun.py diff --git a/cogs/cog_fun.py b/cogs/cog_fun.py new file mode 100644 index 0000000..34a0362 --- /dev/null +++ b/cogs/cog_fun.py @@ -0,0 +1,26 @@ +import discord +from discord import app_commands +from discord.app_commands import Choice +from discord.ext import commands + +from classes.my_cog import MyCog + + +class Fun(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + @app_commands.command(name="chuchoter") + async def chuchoter(self, interaction: discord.Interaction, channel: discord.TextChannel, message: str): + """ Chuchote à Staticky un message qu'il partagera """ + try: + await channel.send(f"*Quelqu'un m'a chuchoter :*\n>>> {message}") + await interaction.response.send_message(f"Message envoyé dans {channel.mention}\n>>> {message}", + ephemeral=True) + except: + await interaction.response.send_message(f"Le message n'a pas pu être envoyé...", ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Fun(bot)) From 3146d269c80f43a84c51a391d4b06ccc2f02bca6 Mon Sep 17 00:00:00 2001 From: voXrey <72698969+voXrey@users.noreply.github.com> Date: Sun, 17 Dec 2023 19:55:00 +0100 Subject: [PATCH 07/18] New command and some fixes - New command "chuchoter" fixed - New cog admin to add admin commands - guilds intents added --- cogs/cog_admin.py | 16 ++++++++++++++++ cogs/cog_fun.py | 18 ++++++++++++++---- main.py | 5 ++++- 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 cogs/cog_admin.py diff --git a/cogs/cog_admin.py b/cogs/cog_admin.py new file mode 100644 index 0000000..8b5b94d --- /dev/null +++ b/cogs/cog_admin.py @@ -0,0 +1,16 @@ +import discord +from discord import app_commands +from discord.app_commands import Choice +from discord.ext import commands + +from classes.my_cog import MyCog + + +class Admin(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + +async def setup(bot): + await bot.add_cog(Admin(bot)) diff --git a/cogs/cog_fun.py b/cogs/cog_fun.py index 34a0362..274be3a 100644 --- a/cogs/cog_fun.py +++ b/cogs/cog_fun.py @@ -1,4 +1,5 @@ import discord +import random from discord import app_commands from discord.app_commands import Choice from discord.ext import commands @@ -13,13 +14,22 @@ class Fun(MyCog): @app_commands.command(name="chuchoter") async def chuchoter(self, interaction: discord.Interaction, channel: discord.TextChannel, message: str): - """ Chuchote à Staticky un message qu'il partagera """ + """ + Chuchote à Staticky un message qu'il partagera (attention ne lui faites pas aveuglément confiance) + :param interaction: discord interaction + :param channel: Salon dans lequel le message sera partagé + :param message: Message à partagerE + """ try: - await channel.send(f"*Quelqu'un m'a chuchoter :*\n>>> {message}") + to_send = f"**Quelqu'un m'a chuchoter :** \"{message}\"" + if random.randint(1, 6) == 1: + to_send += f"\nJe balance, c'est {interaction.user.mention} !" + await channel.send(to_send) await interaction.response.send_message(f"Message envoyé dans {channel.mention}\n>>> {message}", ephemeral=True) - except: - await interaction.response.send_message(f"Le message n'a pas pu être envoyé...", ephemeral=True) + except Exception as e: + await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", + ephemeral=True) async def setup(bot): diff --git a/main.py b/main.py index 43e54f2..1d5ce15 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,9 @@ load_dotenv() class MyClient(commands.Bot): def __init__(self): - super().__init__(intents=discord.Intents.none(), command_prefix="!") + intents = discord.Intents.none() + intents.guilds = True + super().__init__(intents=intents, command_prefix=".") self.MY_GUILD = discord.Object(id=getenv("GUILD_ID")) @@ -30,6 +32,7 @@ class MyClient(commands.Bot): async def on_ready(self): print(f'Logged in as {self.user} (ID: {self.user.id})') + print(f"Discord version : {discord.__version__}") print('------') From db0913f50b88ce08eb4117c030bef08a28a63d45 Mon Sep 17 00:00:00 2001 From: Arthur Date: Sun, 17 Dec 2023 19:58:31 +0100 Subject: [PATCH 08/18] Actualiser cogs/cog_fun.py --- cogs/cog_fun.py | 72 ++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/cogs/cog_fun.py b/cogs/cog_fun.py index 274be3a..aa8c09e 100644 --- a/cogs/cog_fun.py +++ b/cogs/cog_fun.py @@ -1,36 +1,36 @@ -import discord -import random -from discord import app_commands -from discord.app_commands import Choice -from discord.ext import commands - -from classes.my_cog import MyCog - - -class Fun(MyCog): - def __init__(self, bot): - super().__init__(bot) - self.bot = bot - - @app_commands.command(name="chuchoter") - async def chuchoter(self, interaction: discord.Interaction, channel: discord.TextChannel, message: str): - """ - Chuchote à Staticky un message qu'il partagera (attention ne lui faites pas aveuglément confiance) - :param interaction: discord interaction - :param channel: Salon dans lequel le message sera partagé - :param message: Message à partagerE - """ - try: - to_send = f"**Quelqu'un m'a chuchoter :** \"{message}\"" - if random.randint(1, 6) == 1: - to_send += f"\nJe balance, c'est {interaction.user.mention} !" - await channel.send(to_send) - await interaction.response.send_message(f"Message envoyé dans {channel.mention}\n>>> {message}", - ephemeral=True) - except Exception as e: - await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", - ephemeral=True) - - -async def setup(bot): - await bot.add_cog(Fun(bot)) +import discord +import random +from discord import app_commands +from discord.app_commands import Choice +from discord.ext import commands + +from classes.my_cog import MyCog + + +class Fun(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + @app_commands.command(name="chuchoter") + async def chuchoter(self, interaction: discord.Interaction, channel: discord.TextChannel, message: str): + """ + Chuchote à Staticky un message qu'il partagera (attention ne lui faites pas aveuglément confiance) + :param interaction: discord interaction + :param channel: Salon dans lequel le message sera partagé + :param message: Message à partagerE + """ + try: + to_send = f"**Quelqu'un m'a chuchoté :** \"{message}\"" + if random.randint(1, 6) == 1: + to_send += f"\nJe balance, c'est {interaction.user.mention} !" + await channel.send(to_send) + await interaction.response.send_message(f"Message envoyé dans {channel.mention}\n>>> {message}", + ephemeral=True) + except Exception as e: + await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", + ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Fun(bot)) From 4abc4e20b52dbff7cb228817d284fc12db585048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Sun, 17 Dec 2023 23:42:49 +0100 Subject: [PATCH 09/18] =?UTF-8?q?Le=20channel=20par=20d=C3=A9faut=20est=20?= =?UTF-8?q?le=20channel=20de=20l'interaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cogs/cog_fun.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cogs/cog_fun.py b/cogs/cog_fun.py index aa8c09e..3893bd3 100644 --- a/cogs/cog_fun.py +++ b/cogs/cog_fun.py @@ -13,7 +13,7 @@ class Fun(MyCog): self.bot = bot @app_commands.command(name="chuchoter") - async def chuchoter(self, interaction: discord.Interaction, channel: discord.TextChannel, message: str): + async def chuchoter(self, interaction: discord.Interaction, message: str, channel: discord.TextChannel=None): """ Chuchote à Staticky un message qu'il partagera (attention ne lui faites pas aveuglément confiance) :param interaction: discord interaction @@ -21,6 +21,9 @@ class Fun(MyCog): :param message: Message à partagerE """ try: + if channel is None: + channel = interaction.channel + to_send = f"**Quelqu'un m'a chuchoté :** \"{message}\"" if random.randint(1, 6) == 1: to_send += f"\nJe balance, c'est {interaction.user.mention} !" From c8eb921d8b12d5539e305147de752a2044dafc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Mon, 18 Dec 2023 00:40:17 +0100 Subject: [PATCH 10/18] Ajout d'une commande fact --- cogs/cog_fact.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 cogs/cog_fact.py diff --git a/cogs/cog_fact.py b/cogs/cog_fact.py new file mode 100644 index 0000000..492d3b0 --- /dev/null +++ b/cogs/cog_fact.py @@ -0,0 +1,53 @@ +import discord +import random +import aiohttp +import asyncio + +from dotenv import load_dotenv +from discord import app_commands +from discord import Embed +#from discord.app_commands import Choice +from discord.ext import commands + +from classes.my_cog import MyCog + + +class Fact(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + @app_commands.command(name="fact") + async def fact(self, interaction: discord.Interaction, private=False): + """ + Vous reprendriez bien un petit shot de culture G ? + :param interaction: discord interaction + :param private: Garder ce petit bijou pour soi + """ + + headers = { + 'Authorization': f'Bearer {getenv("WIKIMEDIA_ACCESS_TOKEN")}', + 'User-Agent': 'Staticky (https://mp2i-vms.fr)' + } + + try: + async with aiohttp.ClientSession() as session: + await response = session.get("https://fr.wikipedia.org/api/rest_v1/page/random/summary", headers=headers) + + embed = Embed( + title=response.json.get("title", "Titre inconnu"), + url=response.json.get("content_urls").get("desktop").get("page"), + description=response.json.get('extract') + ) + + embed.set_thumbnail(response.json.get('thumbnail').get('source')) + + await interaction.response.send_message("", embed=embed + ephemeral=private) + except Exception as e: + await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", + ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Fact(bot)) From 745fe4dd1f72a8a66d6875c654a47bf260bb3967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Mon, 18 Dec 2023 00:43:18 +0100 Subject: [PATCH 11/18] Correctif --- cogs/cog_fact.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/cog_fact.py b/cogs/cog_fact.py index 492d3b0..efcb9bc 100644 --- a/cogs/cog_fact.py +++ b/cogs/cog_fact.py @@ -32,7 +32,7 @@ class Fact(MyCog): try: async with aiohttp.ClientSession() as session: - await response = session.get("https://fr.wikipedia.org/api/rest_v1/page/random/summary", headers=headers) + response = await session.get("https://fr.wikipedia.org/api/rest_v1/page/random/summary", headers=headers) embed = Embed( title=response.json.get("title", "Titre inconnu"), From 6a2b2cb1ea7ef04e0e41090762dbf56708e49621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Mon, 18 Dec 2023 00:43:18 +0100 Subject: [PATCH 12/18] Correctif --- cogs/cog_fact.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cogs/cog_fact.py b/cogs/cog_fact.py index efcb9bc..0a693d3 100644 --- a/cogs/cog_fact.py +++ b/cogs/cog_fact.py @@ -42,8 +42,7 @@ class Fact(MyCog): embed.set_thumbnail(response.json.get('thumbnail').get('source')) - await interaction.response.send_message("", embed=embed - ephemeral=private) + await interaction.response.send_message("", embed=embed, ephemeral=private) except Exception as e: await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", ephemeral=True) From 65ec845770dd6c9a0046b17b40b8cf7d8427fc49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Mon, 18 Dec 2023 00:43:18 +0100 Subject: [PATCH 13/18] Correctif --- cogs/cog_fact.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cogs/cog_fact.py b/cogs/cog_fact.py index 0a693d3..ba4b3fc 100644 --- a/cogs/cog_fact.py +++ b/cogs/cog_fact.py @@ -3,6 +3,7 @@ import random import aiohttp import asyncio +from os import getenv from dotenv import load_dotenv from discord import app_commands from discord import Embed @@ -18,7 +19,7 @@ class Fact(MyCog): self.bot = bot @app_commands.command(name="fact") - async def fact(self, interaction: discord.Interaction, private=False): + async def fact(self, interaction: discord.Interaction, private: bool=False): """ Vous reprendriez bien un petit shot de culture G ? :param interaction: discord interaction @@ -33,14 +34,15 @@ class Fact(MyCog): try: async with aiohttp.ClientSession() as session: response = await session.get("https://fr.wikipedia.org/api/rest_v1/page/random/summary", headers=headers) + data = await response.json() embed = Embed( - title=response.json.get("title", "Titre inconnu"), - url=response.json.get("content_urls").get("desktop").get("page"), - description=response.json.get('extract') + title=data.get("title", "Titre inconnu"), + url=data.get("content_urls").get("desktop").get("page"), + description=data.get('extract') ) - embed.set_thumbnail(response.json.get('thumbnail').get('source')) + embed.set_thumbnail(url=data.get('thumbnail').get('source')) await interaction.response.send_message("", embed=embed, ephemeral=private) except Exception as e: From 75f8e041e806c5801d902fee198d76dd220eee60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Mon, 18 Dec 2023 01:09:28 +0100 Subject: [PATCH 14/18] Correctif --- cogs/cog_fact.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cogs/cog_fact.py b/cogs/cog_fact.py index ba4b3fc..f9ecef0 100644 --- a/cogs/cog_fact.py +++ b/cogs/cog_fact.py @@ -22,6 +22,7 @@ class Fact(MyCog): async def fact(self, interaction: discord.Interaction, private: bool=False): """ Vous reprendriez bien un petit shot de culture G ? + :param interaction: discord interaction :param private: Garder ce petit bijou pour soi """ From 3184035346f744e7d3e1802101de838657ddd5ec Mon Sep 17 00:00:00 2001 From: voXrey <72698969+voXrey@users.noreply.github.com> Date: Tue, 19 Dec 2023 22:15:34 +0100 Subject: [PATCH 15/18] Cogs management - Cogs file renamed (easier managing) - Cogs commands in admin cog: cog load/unload/reload/list --- cogs/admin.py | 116 ++++++++++++++++++++++++++++++++++ cogs/cog_admin.py | 16 ----- cogs/{cog_edt.py => edt.py} | 0 cogs/{cog_fact.py => fact.py} | 110 ++++++++++++++++---------------- cogs/{cog_fun.py => fun.py} | 78 +++++++++++------------ 5 files changed, 210 insertions(+), 110 deletions(-) create mode 100644 cogs/admin.py delete mode 100644 cogs/cog_admin.py rename cogs/{cog_edt.py => edt.py} (100%) rename cogs/{cog_fact.py => fact.py} (97%) rename cogs/{cog_fun.py => fun.py} (97%) diff --git a/cogs/admin.py b/cogs/admin.py new file mode 100644 index 0000000..36a2ba2 --- /dev/null +++ b/cogs/admin.py @@ -0,0 +1,116 @@ +import discord +from discord import app_commands +from discord.app_commands import Choice +from discord.ext import commands + +from classes.my_cog import MyCog + + +class CogGroup(app_commands.Group, name="cog", description="Gérer les cogs"): + def __init__(self, bot: commands.Bot): + super().__init__() + self.bot = bot + + @app_commands.command(name="load") + async def cog_load(self, interaction: discord.Interaction, extension: str): + """ + Charge une extension + :param interaction: discord interaction + :param extension: Nom de l'extension + """ + try: + await self.bot.load_extension(f"cogs.{extension.lower()}") + await interaction.response.send_message(f"Extension `{extension}` chargée !") + + except commands.ExtensionAlreadyLoaded: + await interaction.response.send_message(f"Extension `{extension}` déjà chargée...") + + except commands.ExtensionNotFound: + await interaction.response.send_message(f"Extension `{extension}` non trouvée...") + + except Exception as e: + print(e) + await interaction.response.send_message(f"Une erreur inconnue est survenue:\n`{e}`") + + @app_commands.command(name="unload") + async def cog_unload(self, interaction: discord.Interaction, extension: str): + """ + Décharge une extension + :param interaction: discord interaction + :param extension: Nom de l'extension + :return: + """ + try: + await self.bot.unload_extension(f"cogs.{extension.lower()}") + await interaction.response.send_message(f"Extension `{extension}` déchargée !") + + except commands.ExtensionNotLoaded: + await interaction.response.send_message(f"Extension `{extension}` non chargée...") + + except commands.ExtensionNotFound: + await interaction.response.send_message(f"Extension `{extension}` non trouvée...") + + except Exception as e: + print(e) + await interaction.response.send_message(f"Une erreur inconnue est survenue:\n`{e}`") + + @app_commands.command(name="reload") + async def cog_reload(self, interaction: discord.Interaction, extension: str): + """ + Recharge une extension + :param interaction: discord interaction + :param extension: Nom de l'extension + """ + + async def fun(): + await self.bot.load_extension(f"cogs.{extension.lower()}") + await interaction.response.send_message(f"Extension `{extension}` rechargée !") + + try: + await self.bot.unload_extension(f"cogs.{extension.lower()}") + await fun() + + except (commands.ExtensionAlreadyLoaded, commands.ExtensionNotLoaded): + await fun() + + except commands.ExtensionNotFound: + await interaction.response.send_message(f"Extension `{extension}` non trouvée...") + + except Exception as e: + print(e) + await interaction.response.send_message(f"Une erreur inconnue est survenue:\n`{e}`") + + @app_commands.command(name="list") + async def cog_list(self, interaction: discord.Interaction): + """ + Voir la liste des extensions + :param interaction: discord interaction + """ + enabled = [str(ext) for ext in self.bot.extensions] + total_list = self.bot.initial_extensions + disabled = list(set(total_list).difference(enabled)) + + msg = "✅ **Enabled**\n" + for ext in enabled: + msg += f"* `{ext[5:]}`\n" + msg += "\n❌ **Disabled**\n" + for ext in disabled: + msg += f"* `{ext[5:]}`\n" + + embed = discord.Embed(title="Liste des extensions", + description=msg, + color=discord.Color.blurple()) + await interaction.response.send_message(embed=embed) + + +class Admin(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + # Add cogs commands group to cog + self.bot.tree.add_command(CogGroup(self.bot)) + + +async def setup(bot): + await bot.add_cog(Admin(bot)) diff --git a/cogs/cog_admin.py b/cogs/cog_admin.py deleted file mode 100644 index 8b5b94d..0000000 --- a/cogs/cog_admin.py +++ /dev/null @@ -1,16 +0,0 @@ -import discord -from discord import app_commands -from discord.app_commands import Choice -from discord.ext import commands - -from classes.my_cog import MyCog - - -class Admin(MyCog): - def __init__(self, bot): - super().__init__(bot) - self.bot = bot - - -async def setup(bot): - await bot.add_cog(Admin(bot)) diff --git a/cogs/cog_edt.py b/cogs/edt.py similarity index 100% rename from cogs/cog_edt.py rename to cogs/edt.py diff --git a/cogs/cog_fact.py b/cogs/fact.py similarity index 97% rename from cogs/cog_fact.py rename to cogs/fact.py index f9ecef0..eba8f34 100644 --- a/cogs/cog_fact.py +++ b/cogs/fact.py @@ -1,55 +1,55 @@ -import discord -import random -import aiohttp -import asyncio - -from os import getenv -from dotenv import load_dotenv -from discord import app_commands -from discord import Embed -#from discord.app_commands import Choice -from discord.ext import commands - -from classes.my_cog import MyCog - - -class Fact(MyCog): - def __init__(self, bot): - super().__init__(bot) - self.bot = bot - - @app_commands.command(name="fact") - async def fact(self, interaction: discord.Interaction, private: bool=False): - """ - Vous reprendriez bien un petit shot de culture G ? - - :param interaction: discord interaction - :param private: Garder ce petit bijou pour soi - """ - - headers = { - 'Authorization': f'Bearer {getenv("WIKIMEDIA_ACCESS_TOKEN")}', - 'User-Agent': 'Staticky (https://mp2i-vms.fr)' - } - - try: - async with aiohttp.ClientSession() as session: - response = await session.get("https://fr.wikipedia.org/api/rest_v1/page/random/summary", headers=headers) - data = await response.json() - - embed = Embed( - title=data.get("title", "Titre inconnu"), - url=data.get("content_urls").get("desktop").get("page"), - description=data.get('extract') - ) - - embed.set_thumbnail(url=data.get('thumbnail').get('source')) - - await interaction.response.send_message("", embed=embed, ephemeral=private) - except Exception as e: - await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", - ephemeral=True) - - -async def setup(bot): - await bot.add_cog(Fact(bot)) +import discord +import random +import aiohttp +import asyncio + +from os import getenv +from dotenv import load_dotenv +from discord import app_commands +from discord import Embed +#from discord.app_commands import Choice +from discord.ext import commands + +from classes.my_cog import MyCog + + +class Fact(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + @app_commands.command(name="fact") + async def fact(self, interaction: discord.Interaction, private: bool=False): + """ + Vous reprendriez bien un petit shot de culture G ? + + :param interaction: discord interaction + :param private: Garder ce petit bijou pour soi + """ + + headers = { + 'Authorization': f'Bearer {getenv("WIKIMEDIA_ACCESS_TOKEN")}', + 'User-Agent': 'Staticky (https://mp2i-vms.fr)' + } + + try: + async with aiohttp.ClientSession() as session: + response = await session.get("https://fr.wikipedia.org/api/rest_v1/page/random/summary", headers=headers) + data = await response.json() + + embed = Embed( + title=data.get("title", "Titre inconnu"), + url=data.get("content_urls").get("desktop").get("page"), + description=data.get('extract') + ) + + embed.set_thumbnail(url=data.get('thumbnail').get('source')) + + await interaction.response.send_message("", embed=embed, ephemeral=private) + except Exception as e: + await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", + ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Fact(bot)) diff --git a/cogs/cog_fun.py b/cogs/fun.py similarity index 97% rename from cogs/cog_fun.py rename to cogs/fun.py index 3893bd3..86317af 100644 --- a/cogs/cog_fun.py +++ b/cogs/fun.py @@ -1,39 +1,39 @@ -import discord -import random -from discord import app_commands -from discord.app_commands import Choice -from discord.ext import commands - -from classes.my_cog import MyCog - - -class Fun(MyCog): - def __init__(self, bot): - super().__init__(bot) - self.bot = bot - - @app_commands.command(name="chuchoter") - async def chuchoter(self, interaction: discord.Interaction, message: str, channel: discord.TextChannel=None): - """ - Chuchote à Staticky un message qu'il partagera (attention ne lui faites pas aveuglément confiance) - :param interaction: discord interaction - :param channel: Salon dans lequel le message sera partagé - :param message: Message à partagerE - """ - try: - if channel is None: - channel = interaction.channel - - to_send = f"**Quelqu'un m'a chuchoté :** \"{message}\"" - if random.randint(1, 6) == 1: - to_send += f"\nJe balance, c'est {interaction.user.mention} !" - await channel.send(to_send) - await interaction.response.send_message(f"Message envoyé dans {channel.mention}\n>>> {message}", - ephemeral=True) - except Exception as e: - await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", - ephemeral=True) - - -async def setup(bot): - await bot.add_cog(Fun(bot)) +import discord +import random +from discord import app_commands +from discord.app_commands import Choice +from discord.ext import commands + +from classes.my_cog import MyCog + + +class Fun(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + @app_commands.command(name="chuchoter") + async def chuchoter(self, interaction: discord.Interaction, message: str, channel: discord.TextChannel=None): + """ + Chuchote à Staticky un message qu'il partagera (attention ne lui faites pas aveuglément confiance) + :param interaction: discord interaction + :param channel: Salon dans lequel le message sera partagé + :param message: Message à partagerE + """ + try: + if channel is None: + channel = interaction.channel + + to_send = f"**Quelqu'un m'a chuchoté :** \"{message}\"" + if random.randint(1, 6) == 1: + to_send += f"\nJe balance, c'est {interaction.user.mention} !" + await channel.send(to_send) + await interaction.response.send_message(f"Message envoyé dans {channel.mention}\n>>> {message}", + ephemeral=True) + except Exception as e: + await interaction.response.send_message(f"Le message n'a pas pu être envoyé...\n`Erreur : {e}`", + ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Fun(bot)) From c9b73d835051b3518d11658b4159b44f56ef965d Mon Sep 17 00:00:00 2001 From: voXrey <72698969+voXrey@users.noreply.github.com> Date: Tue, 19 Dec 2023 22:16:57 +0100 Subject: [PATCH 16/18] quote command added Description: Quote a message with his URL - In utils cog - Needed message intents --- cogs/utils.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 1 + 2 files changed, 54 insertions(+) create mode 100644 cogs/utils.py diff --git a/cogs/utils.py b/cogs/utils.py new file mode 100644 index 0000000..865fbc5 --- /dev/null +++ b/cogs/utils.py @@ -0,0 +1,53 @@ +import discord +from discord import app_commands +from discord.app_commands import Choice +from discord.ext import commands +import pytz + +from classes.my_cog import MyCog + + +class Utils(MyCog): + def __init__(self, bot): + super().__init__(bot) + self.bot = bot + + @app_commands.command(name="quote") + async def quote(self, interaction: discord.Interaction, link: str): + """ + Citer un message + :param interaction: discord interaction + :param link: Lien du message à citer + """ + try: + splited_link = link.split("/") + + channel_id = int(splited_link[5]) + message_id = int(splited_link[6]) + + channel = await self.bot.fetch_channel(channel_id) + quoted_message = await channel.fetch_message(message_id) + + embed = discord.Embed( + description=quoted_message.content, + timestamp=quoted_message.created_at.astimezone(pytz.timezone('Europe/Berlin')), + color=discord.Color.random() + ) + embed.set_author(name=f"{quoted_message.author.name}", + url=link, + icon_url=quoted_message.author.display_avatar.url) + + embed.set_footer(text=f"Quoted by {interaction.user.name}") + + await interaction.channel.send(embed=embed) + await interaction.response.send_message("Citation opérée avec succès !", + ephemeral=True) + + except: + await interaction.response.send_message( + f"Impossible de citer ce message...\nVérifiez que le lien du message soit valide", + ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Utils(bot)) diff --git a/main.py b/main.py index 1d5ce15..856bd5c 100644 --- a/main.py +++ b/main.py @@ -15,6 +15,7 @@ class MyClient(commands.Bot): def __init__(self): intents = discord.Intents.none() intents.guilds = True + intents.messages = True super().__init__(intents=intents, command_prefix=".") self.MY_GUILD = discord.Object(id=getenv("GUILD_ID")) From 047f24964bbdfc89ed8dd6fdae26121a8b69d5e8 Mon Sep 17 00:00:00 2001 From: joseph Date: Wed, 20 Dec 2023 13:49:46 +0100 Subject: [PATCH 17/18] add quote as context menu command --- cogs/utils.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cogs/utils.py b/cogs/utils.py index 865fbc5..2b18537 100644 --- a/cogs/utils.py +++ b/cogs/utils.py @@ -11,7 +11,9 @@ class Utils(MyCog): def __init__(self, bot): super().__init__(bot) self.bot = bot - + self.context_menu_quote = app_commands.ContextMenu(name="Quote", callback=self.quote_message) + bot.tree.add_command(self.context_menu_quote) + @app_commands.command(name="quote") async def quote(self, interaction: discord.Interaction, link: str): """ @@ -49,5 +51,26 @@ class Utils(MyCog): ephemeral=True) + async def quote_message(self, interaction: discord.Interaction, message: discord.Message): + """" + Citer un message + """ + embed = discord.Embed( + description=message.content, + timestamp=message.created_at.astimezone(pytz.timezone('Europe/Berlin')), + color=discord.Color.random() + ) + embed.set_author( + name=f"{message.author.name}", + url=message.jump_url, + icon_url=message.author.display_avatar.url + ) + + embed.set_footer(text=f"Quoted by {interaction.user.name}") + await interaction.channel.send(embed=embed) + await interaction.response.send_message("Citation opérée avec succès !", + ephemeral=True) + + async def setup(bot): await bot.add_cog(Utils(bot)) From 1ec8fca6288dc01ee118fd145d26ae403cd71e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Wed, 20 Dec 2023 21:16:10 +0100 Subject: [PATCH 18/18] Actualiser Resources/eleves.csv --- Resources/eleves.csv | 92 ++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/Resources/eleves.csv b/Resources/eleves.csv index 261d139..8821c28 100644 --- a/Resources/eleves.csv +++ b/Resources/eleves.csv @@ -1,46 +1,46 @@ -Nom,Prénon,Groupe de coll,TD -ABOUJAIB,Alexandre,1,A -AJAN,George,1,A -AKRAD,Lina,5,A -AUBERT,Nicolas,3,A -BADR,Roman,3,A -BAZIRE,Aurélien,3,A -BOIT,Arthur,5,A -BOUBKER,Youssef,5,A -BOUDJEMA,Dylan,1,A -CHIRIAC,Mihnea,7,A -COURIER,Marine,7,A -DAGUIN,Joseph,7,A -DAUGUEN,Gabriel,9,A -DE WEER,Matthias,9,A -DESBOUIS,Katell,11,A -DUPOUY,Jérémie,11,A -HARIRI--GAUTIER-PICARD,Grégoire,11,A -JURICEVIC,Matteo,13,A -KNANOUA,Anas,13,A -LESENNE,Pierrick,13,A -LIN,Hao,9,A -MASBATIN,Lucas,15,A -MAYURAN,Mithushan,15,A -MESSAHLI,Yassine,2,B -MOGUÉROU,Valentin,2,B -MOHELLEBI,Mathéo,2,B -MOUISSET--FERRARA,Maël,4,B -OTTAVI,Corentin,4,B -PONCE,Alexian,4,B -PUJOL,Raphaël,6,B -PUSTETTO,Mathis,6,B -RADICE,Roman,6,B -RAT,Evelyn,8,B -ROUSSE,Louis,8,B -ROUX,Gaetan,8,B -ROUYRE--CROS,Célian,10,B -SOURBE,François-G,10,B -STOURBE,Simon,10,B -THAI,Dany,12,B -THÉODORE,Jonathan,12,B -VANDROUX,Benoit,12,B -VEYSSIERE,Thibaud,14,B -VIÉ,Adrien,14,B -YE,Luan,14,B -ZARKA,Amélie,15,A +Nom,Prénon,Groupe de coll,TD +ABOUJAIB,Alexandre,1,A +AJAN,George,1,A +AKRAD,Lina,5,A +AUBERT,Nicolas,3,A +BADR,Roman,3,A +BAZIRE,Aurélien,3,A +BOIT,Arthur,5,A +BOUBKER,Youssef,5,A +BOUDJEMA,Dylan,1,A +CHIRIAC,Minhea,7,A +COURIER,Marine,7,A +DAGUIN,Joseph,7,A +DAUGUEN,Gabriel,9,A +DE WEER,Matthias,9,A +DESBOUIS,Katell,11,A +DUPOUY,Jérémie,11,A +HARIRI--GAUTIER-PICARD,Grégoire,11,A +JURICEVIC,Matteo,13,A +KNANOUA,Anas,13,A +LESENNE,Pierrick,13,A +LIN,Hao,9,A +MASBATIN,Lucas,15,A +MAYURAN,Mithushan,15,A +MESSAHLI,Yassine,2,B +MOGUÉROU,Valentin,2,B +MOHELLEBI,Mathéo,2,B +MOUISSET--FERRARA,Maël,4,B +OTTAVI,Corentin,4,B +PONCE,Alexian,4,B +PUJOL,Raphaël,6,B +PUSTETTO,Mathis,6,B +RADICE,Roman,6,B +RAT,Evelyn,8,B +ROUSSE,Louis,8,B +ROUX,Gaetan,8,B +ROUYRE--CROS,Célian,10,B +SOURBE,François-G,10,B +STOURBE,Simon,10,B +THAI,Dany,12,B +THÉODORE,Jonathan,12,B +VANDROUX,Benoit,12,B +VEYSSIERE,Thibaud,14,B +VIÉ,Adrien,14,B +YE,Luan,14,B +ZARKA,Amélie,15,A