diff --git a/.gitignore b/.gitignore index 8b912e3..1517da8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ venv .idea -app/pronote.json -app/config.json -app/devoirs.json -app/grades.json +drawbot/pronote.json + __pycache__ /desktop.ini + +*.egg-info \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3a62b51 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +VENV = venv +VBIN = $(VENV)/bin + +all: start + +clean: + rm -rf venv + rm -rf *.egg-info + rm -rf __pycache__ + rm vars/*.json + +$(VBIN)/python: + python -m venv venv + chmod +x venv/bin/activate + ./venv/bin/activate + pip install -e . + +vars/config.json: + cp vars/config.json.example vars/config.json + +start: $(VBIN)/python vars/config.json + python drawbot + +.PHONY: all clean start diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 6fce82c..0000000 --- a/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -cogs/dev.py -config.json diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index 7782fa6..0000000 --- a/app/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Dict, Any, List, Union - -__version__: str = '2.0.0' -__author__: str = 'Drawbu' -__maintainer__: str = 'Sigmanificient' - -JsonDict = Dict[str, Any] -JsonList = List[Any] -JsonData = Union[JsonDict, JsonList] diff --git a/app/utils/__init__.py b/app/utils/__init__.py deleted file mode 100644 index daf5375..0000000 --- a/app/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .json_files import json_wr -from .pronote import fetch_homeworks, fetch_grades - - -__all__ = ("json_wr", "fetch_homeworks", "fetch_grades") diff --git a/README.md b/docs/README.md similarity index 71% rename from README.md rename to docs/README.md index 17aec79..090c907 100644 --- a/README.md +++ b/docs/README.md @@ -18,31 +18,34 @@ Help server: https://discord.gg/XGXydQyKhQ | :warning: | We do not take responsibility for a possible leak of your passwords, which is why you need to host the bot yourself. | ## Installation + ```sh git clone https://github.com/drawbu/drawbot +cd drawbot -cd /path - -pip install -r requirements.txt +pip install -e . ``` ## Launch ```sh -py run.py +py drawbot ``` +*If you are using a Linux distribution, you can use the `make` command to install dependencies and run the bot.* + ## Files documentation -The bot will create **2** json files in the **app** folder: +The bot will create **2** json files in the **vars** folder: -- config.json - devoirs.json - grades.json -### app/ config.json +### vars/config.json -This file stocks your private logins and info's to make to bor running: +This file stocks your private logins and info's to make to bot running. +You can find a **config.example.json** file in the **vars** folder. +> `config.json` **Example** ```json5 { "token": "( ͡° ͜ʖ ͡°)", @@ -53,18 +56,19 @@ This file stocks your private logins and info's to make to bor running: "url": "https://demo.index-education.net/pronote/eleve.html?login=true" } ``` -THESE ARE JUST EXAMPLES -In `"token"`, you need to add your bot token.
-In `"prefix"`, the prefix your bot will use.
-In `"channelID"`, the ID of the Discord channel.
-In `"username"`, your Pronote username.
-In `"password"`, your Pronote password.
-In `"url"`, the url of your pronote client.
+copy that file as **config.json** and fill in the values as follows: + +- `"token"`: your bot token.
+- `"prefix"`: the prefix your bot will use.
+- `"channelID"`: the ID of the Discord channel.
+- `"username"`: your Pronote username.
+- `"password"`: your Pronote password.
+- `"url"`: the url of your pronote client.
If you can't connect to Pronote, check if your establishment is not using an ENT. In this case, see what you need to do with the help of the pronote wrapper project: [pronotepy](https://github.com/bain3/pronotepy) -### app/ devoirs.json +### vars/devoirs.json Automatically generated JSON containing homeworks information. diff --git a/drawbot/__init__.py b/drawbot/__init__.py new file mode 100644 index 0000000..3bf50f3 --- /dev/null +++ b/drawbot/__init__.py @@ -0,0 +1,8 @@ +"""A Pronote notifier discord bot.""" +from .bot import Bot + +__version__: str = "2.0.0" +__author__: str = "Drawbu" +__maintainer__: str = "Sigmanificient" + +__all__ = ("Bot",) diff --git a/run.py b/drawbot/__main__.py similarity index 55% rename from run.py rename to drawbot/__main__.py index 677907c..5b24601 100644 --- a/run.py +++ b/drawbot/__main__.py @@ -1,11 +1,11 @@ -from app.bot import Bot +from drawbot import Bot -def main() -> None: +def main(): """Entry point to run the bot client.""" bot = Bot() bot.run() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/app/bot.py b/drawbot/bot.py similarity index 55% rename from app/bot.py rename to drawbot/bot.py index 291ddef..cef0fab 100644 --- a/app/bot.py +++ b/drawbot/bot.py @@ -2,15 +2,14 @@ import sys from typing import Optional +from discord import LoginFailure from discord.ext import commands -from app import JsonData -from app.utils import json_wr +from .utils import json_wr, JsonData class Bot(commands.Bot): - - def __init__(self) -> None: + def __init__(self): """Initialize the bot and load config for token and prefix.""" self.embed_color: int = 0x1E744F self._token: Optional[str] = None @@ -21,7 +20,7 @@ def __init__(self) -> None: "channelID": "", "username": "", "password": "", - "url": "" + "url": "", } self.config: JsonData = json_wr("config") @@ -29,8 +28,8 @@ def __init__(self) -> None: for key in default_config.keys(): if self.config.get(key, "") == "": print( - f"Veuillez indiquer remplir la valeur \"{key}\" " - "dans le fichier config.json" + f'Veuillez indiquer remplir la valeur "{key}" ' + "dans le fichier vars/config.json" ) sys.exit() @@ -40,12 +39,18 @@ def __init__(self) -> None: self.remove_command("help") - for filename in os.listdir("app/cogs"): - if filename.endswith(".py"): - self.load_extension(f"app.cogs.{filename[:-3]}") + for filename in os.listdir("drawbot/cogs"): + if filename.endswith(".py") and filename != "__init__.py": + self.load_extension(f"drawbot.cogs.{filename[:-3]}") - def run(self) -> None: - super().run(self._token) + def run(self): + try: + super().run(self._token) + except LoginFailure: + print( + "Echec de la connexion au client." + "Veuillez vérifier que votre token est correct." + ) - async def on_ready(self) -> None: + async def on_ready(self): print(f"Connecté en temps que {self.user.name} !") diff --git a/drawbot/cogs/.gitignore b/drawbot/cogs/.gitignore new file mode 100644 index 0000000..46ce8a2 --- /dev/null +++ b/drawbot/cogs/.gitignore @@ -0,0 +1 @@ +dev.py diff --git a/drawbot/cogs/__init__.py b/drawbot/cogs/__init__.py new file mode 100644 index 0000000..a1798b7 --- /dev/null +++ b/drawbot/cogs/__init__.py @@ -0,0 +1 @@ +"""Cogs sub-package for drawbot.""" diff --git a/app/cogs/informations.py b/drawbot/cogs/information.py similarity index 55% rename from app/cogs/informations.py rename to drawbot/cogs/information.py index cfb98ae..710eefd 100644 --- a/app/cogs/informations.py +++ b/drawbot/cogs/information.py @@ -4,42 +4,41 @@ from discord.ext.commands import Context -from app import JsonDict +from ..utils import json_wr, JsonDict -from app.utils import json_wr - - -class Informations(commands.Cog): +class Information(commands.Cog): def __init__(self, client): """Initialize the different commands.""" self.client = client @commands.command( - name='help', aliases=('h', 'aide'), + name="help", + aliases=("h", "aide"), ) - async def help_command(self, ctx: Context) -> None: + async def help_command(self, ctx: Context): help_embed: Embed = discord.Embed( title="Help of the Pronote", description=( "Un bot qui traque vos devoir pronote " "et vous les notifient sur discord." ), - color=self.client.embed_color + color=self.client.embed_color, ).add_field( - name=f'{ctx.prefix}here', - value='change le salon d \'envoi des nouveaux devoirs' + name=f"{ctx.prefix}here", + value="change le salon d 'envoi des nouveaux devoirs", ) await ctx.send(embed=help_embed) @commands.command( - name='channel', aliases=('here',), + name="channel", + aliases=("here",), ) - async def change_channel(self, ctx: Context) -> None: - pronote_config: JsonDict = json_wr('pronote') - pronote_config['channelID'] = ctx.channel.id - json_wr('pronote', data=pronote_config) + async def change_channel(self, ctx: Context): + pronote_config: JsonDict = json_wr("pronote") + pronote_config["channelID"] = ctx.channel.id + json_wr("pronote", data=pronote_config) await ctx.send( embed=discord.Embed( @@ -48,10 +47,10 @@ async def change_channel(self, ctx: Context) -> None: "Le salon pour envoyer les nouveaux devoirs " "à bien été mis à jour" ), - color=self.client.embed_color + color=self.client.embed_color, ) ) -def setup(client) -> None: - client.add_cog(Informations(client)) +def setup(client): + client.add_cog(Information(client)) diff --git a/app/cogs/pronote.py b/drawbot/cogs/pronote.py similarity index 76% rename from app/cogs/pronote.py rename to drawbot/cogs/pronote.py index a979d08..ed23783 100644 --- a/app/cogs/pronote.py +++ b/drawbot/cogs/pronote.py @@ -5,22 +5,20 @@ from discord.ext.commands import Context from discord.ext import commands, tasks -from app import JsonDict -from app.utils import json_wr, fetch_homeworks, fetch_grades +from ..utils import json_wr, fetch_homeworks, fetch_grades, JsonDict class Pronote(commands.Cog): - def __init__(self, client): """Initialize the search for new homeworks.""" self.client = client @commands.Cog.listener() - async def on_ready(self) -> None: + async def on_ready(self): self.refresh_pronote.start() @tasks.loop(seconds=300) - async def refresh_pronote(self) -> None: + async def refresh_pronote(self): date = time.strftime("%Y-%m-%d %H:%M", time.gmtime()) @@ -28,7 +26,7 @@ async def refresh_pronote(self) -> None: pronote: pronotepy.Client = pronotepy.Client( self.client.config["url"], self.client.config["username"], - self.client.config["password"] + self.client.config["password"], ) except pronotepy.CryptoError: @@ -48,10 +46,8 @@ async def refresh_pronote(self) -> None: return try: - pronote_channel: discord.TextChannel = ( - await self.client.fetch_channel( - int(self.client.config.get("channelID")) - ) + pronote_channel: discord.TextChannel = await self.client.fetch_channel( + int(self.client.config.get("channelID")) ) except discord.errors.NotFound: print("Channel non-trouvé ou inexistant") @@ -74,7 +70,7 @@ def discord_timestamp(t_str: str) -> str: f"Pour le {discord_timestamp(homework['date'])}" ), description=homework["description"], - color=0x1E744F + color=0x1E744F, ) ) @@ -95,28 +91,26 @@ def discord_timestamp(t_str: str) -> str: f"Note + : **{grade['max']}**\n" f"Note - : **{grade['min']}**\n" ), - color=0x1E744F + color=0x1E744F, ) ) print( - (date if any(auths) else "") + ( - '' if not auths["homeworks"] + (date if any(auths) else "") + + ( + "" + if not auths["homeworks"] else f" - {new_homework_count} nouveaux devoirs" - ) + ( - '' if not auths["grades"] - else f" - {new_grades_count} nouveaux notes" - ) + (" !" if any(auths) else "") + ) + + ("" if not auths["grades"] else f" - {new_grades_count} nouveaux notes") + + (" !" if any(auths) else "") ) @commands.command(name="homeworks", aliases=("devoirs",)) async def change_channel(self, ctx: Context) -> None: homeworks: JsonDict = json_wr("devoirs") - embed = discord.Embed( - title="Prochains devoirs", - color=self.client.embed_color - ) + embed = discord.Embed(title="Prochains devoirs", color=self.client.embed_color) today = time.time() homeworks_dict = {} @@ -126,23 +120,19 @@ async def change_channel(self, ctx: Context) -> None: if date_timestamp >= today: homeworks_dict[date_timestamp] = homeworks_list - homeworks_dict = { - key: homeworks_dict[key] - for key in sorted(homeworks_dict) - } + homeworks_dict = {key: homeworks_dict[key] for key in sorted(homeworks_dict)} for date, homeworks_list in homeworks_dict.items(): embed.add_field( name=f"Pour le ", - value="\n".join([ - f"**- {h['subject']} :** {h['description']}" - for h in homeworks_list - ]), - inline=False + value="\n".join( + f"**- {h['subject']} :** {h['description']}" for h in homeworks_list + ), + inline=False, ) await ctx.send(embed=embed) -def setup(client) -> None: +def setup(client): client.add_cog(Pronote(client)) diff --git a/drawbot/utils/__init__.py b/drawbot/utils/__init__.py new file mode 100644 index 0000000..07f9aaf --- /dev/null +++ b/drawbot/utils/__init__.py @@ -0,0 +1,12 @@ +"""Utilities for drawbot.""" +from .json_files import json_wr, JsonData, JsonDict, JsonList +from .pronote import fetch_homeworks, fetch_grades + +__all__ = ( + "json_wr", + "fetch_homeworks", + "fetch_grades", + "JsonData", + "JsonDict", + "JsonList", +) diff --git a/app/utils/json_files.py b/drawbot/utils/json_files.py similarity index 77% rename from app/utils/json_files.py rename to drawbot/utils/json_files.py index 3b35b00..5ac0b17 100644 --- a/app/utils/json_files.py +++ b/drawbot/utils/json_files.py @@ -1,21 +1,21 @@ import json import os -from typing import Optional, Literal +from typing import Dict, Any, List, Union, Optional, Literal -from app import JsonData +JsonDict = Dict[str, Any] +JsonList = List[Any] +JsonData = Union[JsonDict, JsonList] def json_wr( - filename: str, - mode: Literal["r", "w"] = "r", - data=None + filename: str, mode: Literal["r", "w"] = "r", data=None ) -> Optional[JsonData]: """Write and read json files. Parameters ---------- filename : str - Name of the file. It opens the file like "app/" + filename + ".json". + Name of the file. It opens the file like "drawbot/" + filename + ".json". mode : "r", "w", default="r" Action to perform in the file. "r" to load data, "w" to write in the file. @@ -31,7 +31,7 @@ def json_wr( if data is None: data = {} - filename = f"./app/{filename}.json" + filename = f"vars/{filename}.json" if mode == "r": if not os.path.isfile(filename): @@ -49,4 +49,3 @@ def json_wr( elif mode == "w": with open(filename, "w") as f: json.dump(data, f, indent=4) - return diff --git a/app/utils/pronote.py b/drawbot/utils/pronote.py similarity index 53% rename from app/utils/pronote.py rename to drawbot/utils/pronote.py index 9dd66f9..48045d5 100644 --- a/app/utils/pronote.py +++ b/drawbot/utils/pronote.py @@ -1,8 +1,8 @@ from typing import DefaultDict, Generator, Optional from collections import defaultdict -from app.utils import json_wr - +from .json_files import JsonData +from ..utils import json_wr import pronotepy @@ -14,33 +14,11 @@ def fetch_homeworks(pronote_client: pronotepy.Client) -> Optional[Generator]: homeworks[str(homework.date)].append( { "subject": homework.subject.name, - "description": homework.description.replace("\n", " ") + "description": homework.description.replace("\n", " "), } ) - homeworks_file = json_wr("devoirs") - if homeworks == homeworks_file: - return - - json_wr("devoirs", "w", homeworks) - - homeworks_list: list = [] - for key, value in homeworks.items(): - for i in value: - i["date"] = key - homeworks_list.extend(value) - - homeworks_old_list: list = [] - for key, value in homeworks_file.items(): - for i in value: - i["date"] = key - homeworks_old_list.extend(value) - - for homework in homeworks_list: - if homework in homeworks_old_list: - continue - - yield homework + yield from fetch_from_json("devoirs", homeworks) def fetch_grades(pronote_client: pronotepy.Client) -> Optional[Generator]: @@ -56,30 +34,35 @@ def fetch_grades(pronote_client: pronotepy.Client) -> Optional[Generator]: "average": f"{g.average}/{g.out_of}", "coefficient": f"{g.coefficient}", "max": f"{g.max}/{g.out_of}", - "min": f"{g.min}/{g.out_of}" + "min": f"{g.min}/{g.out_of}", } ) - grades_file = json_wr("grades") - if grades == grades_file: - return + yield from fetch_from_json("grades", grades) + + +def fetch_from_json(filename: str, json_data: JsonData): + json_file = json_wr(filename) + + if json_data == json_file: + yield from () - json_wr("grades", "w", grades) + json_wr(filename, "w", json_data) - grades_list: list = [] - for key, value in grades.items(): + json_vals: list = [] + for key, value in json_data.items(): for i in value: i["date"] = key - grades_list.extend(value) + json_vals.extend(value) - grades_old_list: list = [] - for key, value in grades_file.items(): + old_vals: list = [] + for key, value in json_file.items(): for i in value: i["date"] = key - grades_old_list.extend(value) + old_vals.extend(value) - for grade in grades_list: - if grade in grades_old_list: + for value in json_vals: + if value in old_vals: continue - yield grade + yield value diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..383c0bb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=57.0", "wheel>=0.36"] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2cae1db..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -discord~=1.7.3 -pronotepy==2.4.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..64a8d2c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,38 @@ +[metadata] +name = drawbot +version = 2.0.0 +author = Drawbu +description = A Pronote notifier discord bot. +long_description = file: docs/README.md +long_description_content_type = text/markdown +license = MIT +license_file = LICENSE +platform = unix, linux, osx, cygwin, windows +project_urls = + Github repository=https://github.com/drawbu/drawbot + Discord=https://discord.gg/XGXydQyKhQ +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + Topic :: Software Development :: Build Tools + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + +[options] +packages = + drawbot + drawbot.cogs + drawbot.utils +install_requires = + discord~=1.7.3 + pronotepy==2.4.0 +python_requires = > 3.8 +zip_safe = no + + +[options.extras_require] +dev = + black~=22.6.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7f1a176 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + +if __name__ == "__main__": + setup() diff --git a/vars/.gitignore b/vars/.gitignore new file mode 100644 index 0000000..c8b7ddd --- /dev/null +++ b/vars/.gitignore @@ -0,0 +1,3 @@ +* +!config.json.example +!.gitignore diff --git a/vars/config.json.example b/vars/config.json.example new file mode 100644 index 0000000..279724d --- /dev/null +++ b/vars/config.json.example @@ -0,0 +1,8 @@ +{ + "token": "( ͡° ͜ʖ ͡°)", + "prefix": "!", + "channelID": "000000000000000000", + "username": "demonstration", + "password": "pronotevs", + "url": "https://demo.index-education.net/pronote/eleve.html?login=true" +} \ No newline at end of file