From b9300431b6591789a603516367956ecf2aea6444 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Tue, 13 Dec 2022 16:25:59 +0100 Subject: [PATCH 01/29] tmp clean docker --- Discord-Bot-main/.dockerignore | 1 + Discord-Bot-main/Bot/.env | 1 + Discord-Bot-main/Bot/UR-Bot.py | 16 ++++--- Discord-Bot-main/Dockerfile | 9 ++++ Discord-Bot-main/Install/Dockerfile | 8 ---- Discord-Bot-main/Install/Install.ps1 | 46 -------------------- Discord-Bot-main/Install/LaunchInstall.bat | 1 - Discord-Bot-main/Install/docker-compose.yml | 11 ----- Discord-Bot-main/Install/requirements.txt | Bin 372 -> 0 bytes Discord-Bot-main/docker-compose.yaml | 11 +++++ 10 files changed, 31 insertions(+), 73 deletions(-) create mode 100644 Discord-Bot-main/.dockerignore create mode 100644 Discord-Bot-main/Dockerfile delete mode 100644 Discord-Bot-main/Install/Dockerfile delete mode 100644 Discord-Bot-main/Install/Install.ps1 delete mode 100644 Discord-Bot-main/Install/LaunchInstall.bat delete mode 100644 Discord-Bot-main/Install/docker-compose.yml delete mode 100644 Discord-Bot-main/Install/requirements.txt create mode 100644 Discord-Bot-main/docker-compose.yaml diff --git a/Discord-Bot-main/.dockerignore b/Discord-Bot-main/.dockerignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/Discord-Bot-main/.dockerignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/Discord-Bot-main/Bot/.env b/Discord-Bot-main/Bot/.env index a979cc9..ea55438 100644 --- a/Discord-Bot-main/Bot/.env +++ b/Discord-Bot-main/Bot/.env @@ -1 +1,2 @@ +# token for dev TOKEN=MTA0MDI3NTE3NTY4NzYwNjM3Mg.GRMHqA.yEreIkPlGQh4wMWz5_TK3yLPf3ygEMfGCYrwmk diff --git a/Discord-Bot-main/Bot/UR-Bot.py b/Discord-Bot-main/Bot/UR-Bot.py index 50727e3..30eda99 100644 --- a/Discord-Bot-main/Bot/UR-Bot.py +++ b/Discord-Bot-main/Bot/UR-Bot.py @@ -7,24 +7,27 @@ load_dotenv() -TOKEN = os.getenv('TOKEN') +TOKEN = os.getenv('DISCORD_TOKEN') +print("coucou") +print("coucou") +print(TOKEN) class UR_BOT(commands.Bot): async def on_ready(self): print('--- We have successfully loggged in as {0.user}'.format(self)) - + async def on_message(self, message): if message.author == self.user: return - + await DebugBot.debug_on_message(message); return await bot.process_commands(message) - - + + intent = discord.Intents.default() intent.members = True @@ -63,7 +66,6 @@ async def reload_module(ctx): await reload_module(); await ctx.channel.send("The scripts has been reloaded."); return 0; - -bot.run(TOKEN) +bot.run(TOKEN) diff --git a/Discord-Bot-main/Dockerfile b/Discord-Bot-main/Dockerfile new file mode 100644 index 0000000..3c8c275 --- /dev/null +++ b/Discord-Bot-main/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.7-alpine +WORKDIR /app +RUN apk add --no-cache gcc musl-dev linux-headers +RUN apk update +RUN apk add git +COPY /Bot/requirements.txt bot/requirements.txt +RUN pip3 install -r bot/requirements.txt +COPY /Bot/ ./bot/ +CMD ["python3", "bot/UR-Bot.py"] diff --git a/Discord-Bot-main/Install/Dockerfile b/Discord-Bot-main/Install/Dockerfile deleted file mode 100644 index 8002204..0000000 --- a/Discord-Bot-main/Install/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM python:3.7-alpine -WORKDIR UR-BOT-PROJECT -RUN apk add --no-cache gcc musl-dev linux-headers -COPY requirements.txt requirements.txt -RUN apk update -RUN apk add git -RUN pip3 install -r requirements.txt -ENTRYPOINT ["python3", "UR-Bot.py"] diff --git a/Discord-Bot-main/Install/Install.ps1 b/Discord-Bot-main/Install/Install.ps1 deleted file mode 100644 index 8d7f85b..0000000 --- a/Discord-Bot-main/Install/Install.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -Param( - - [Parameter(Mandatory,ValueFromPipeline,HelpMessage='Enter image name type = String)')] - [Alias('image','image_name','in')] - [string] - $ImageName, - - [Parameter(Mandatory,ValueFromPipeline,HelpMessage='Enter container name (type = String)')] - [Alias('container','container_name','cn')] - [string] - $ContainerName, - - [Parameter(Mandatory = $false,ValueFromPipeline=$false)] - [Alias('run','start','st','rn')] - [Switch]$gorun - -) # On r�cup�re les param�tre - -if($ImageName.Length -lt 3) -{ - $error = [string]"The image name length must be than or equal of 4" - Write-Error -Message $error -Category InvalidArgument - return; -} # On check si la longueur de ImageName est < que 4 - -if($ContainerName.Length -lt 3) -{ - $error = [string]"The container name length must be than or equal of 4" - Write-Error -Message $error -Category InvalidArgument - return; - -} # On check si la longueur du ContainerName est < que 4 - -$env:ENV_IMAGE_NAME = $ImageName # l'image pour le docker-compose.yml -$env:ENV_CONTAINER_NAME = $ContainerName # le nom du containeur pour le docker-compose.yml - -docker build . -t $ImageName - -docker compose -p $ContainerName create --no-recreate # On monte le containeur - -if($gorun.IsPresent) -{ - # cls - docker start -a -i $ContainerName - -} # Si on run ou pas (-run / -start) diff --git a/Discord-Bot-main/Install/LaunchInstall.bat b/Discord-Bot-main/Install/LaunchInstall.bat deleted file mode 100644 index 4a7e6ee..0000000 --- a/Discord-Bot-main/Install/LaunchInstall.bat +++ /dev/null @@ -1 +0,0 @@ -powershell -exec bypass .\Install.ps1 -image_name ur-bot-image-ubuntu -container_name ur-bot-container -run diff --git a/Discord-Bot-main/Install/docker-compose.yml b/Discord-Bot-main/Install/docker-compose.yml deleted file mode 100644 index ab183e9..0000000 --- a/Discord-Bot-main/Install/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: '3' - -services: - os: - image: ${ENV_IMAGE_NAME} - container_name: ${ENV_CONTAINER_NAME} - stdin_open: true # docker run -i - tty: true # docker run -t - hostname: ${ENV_CONTAINER_NAME} - volumes: - - ../Bot:/UR-BOT-PROJECT diff --git a/Discord-Bot-main/Install/requirements.txt b/Discord-Bot-main/Install/requirements.txt deleted file mode 100644 index 28fddf9f777c5c45d51461bda32614e26e383817..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 372 zcmYk2;R=E<6ot?4phr>6r25mdgs7CDPSPS@zWVMqBgSCcx#zp*?(;6y%IT(=HR{aT zE7g%*ut&5BCDT?Lm7Et1s+927s#k;l;Jo@WJiAzH#5)eta+WD+Bvo`QQELdnZQaZr zI@Qo8`Z!->ITp_`f@RzL9xeU>&f2~3uRV!6pU)^&8uOP#m|0^V72| nNB);~rh0@m=&B2KuUI|tW`r=KO%SHAzgql!-7obCHT=vM7)3a5 diff --git a/Discord-Bot-main/docker-compose.yaml b/Discord-Bot-main/docker-compose.yaml new file mode 100644 index 0000000..cb55d04 --- /dev/null +++ b/Discord-Bot-main/docker-compose.yaml @@ -0,0 +1,11 @@ +version: '3' +name: union des rolistes + +services: + bot_base: + build: . #utilise le dockerfile pour cree le container a utiliser + container_name: bot_base + environment: + - DISCORD_TOKEN=MTA0MDI3NTE3NTY4NzYwNjM3Mg.GRMHqA.yEreIkPlGQh4wMWz5_TK3yLPf3ygEMfGCYrwmk #token pour comm avec discord + - BOT_PREFIX=/ #prefix pour les commandes + #- BOT_EXTEND=coucou, coucou2 #extensions a charger From 66f19fccfe023b1c5b9615d02740f1c6125dcfd5 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Tue, 13 Dec 2022 17:28:04 +0100 Subject: [PATCH 02/29] add auto load requirements all requirement need to start with requirement (start file name protected) --- Discord-Bot-main/Dockerfile | 5 +---- Discord-Bot-main/docker-compose.yaml | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Discord-Bot-main/Dockerfile b/Discord-Bot-main/Dockerfile index 3c8c275..6431c71 100644 --- a/Discord-Bot-main/Dockerfile +++ b/Discord-Bot-main/Dockerfile @@ -1,9 +1,6 @@ FROM python:3.7-alpine WORKDIR /app -RUN apk add --no-cache gcc musl-dev linux-headers -RUN apk update -RUN apk add git COPY /Bot/requirements.txt bot/requirements.txt RUN pip3 install -r bot/requirements.txt COPY /Bot/ ./bot/ -CMD ["python3", "bot/UR-Bot.py"] +CMD find . -name 'requirement*' -type f -exec pip3 install -r '{}' ';' && python3 bot/UR-Bot.py diff --git a/Discord-Bot-main/docker-compose.yaml b/Discord-Bot-main/docker-compose.yaml index cb55d04..8d0fda3 100644 --- a/Discord-Bot-main/docker-compose.yaml +++ b/Discord-Bot-main/docker-compose.yaml @@ -5,7 +5,8 @@ services: bot_base: build: . #utilise le dockerfile pour cree le container a utiliser container_name: bot_base + restart: always #redemarre le container si il s'eteint environment: - - DISCORD_TOKEN=MTA0MDI3NTE3NTY4NzYwNjM3Mg.GRMHqA.yEreIkPlGQh4wMWz5_TK3yLPf3ygEMfGCYrwmk #token pour comm avec discord + - DISCORD_TOKEN=--------------------------------- #token pour comm avec discord - BOT_PREFIX=/ #prefix pour les commandes #- BOT_EXTEND=coucou, coucou2 #extensions a charger From f5e8a52f5e3b61ac1f49192cad29e3e39f9f5dc0 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Tue, 13 Dec 2022 17:59:13 +0100 Subject: [PATCH 03/29] choice prefix in env --- Discord-Bot-main/Bot/UR-Bot.py | 8 +++---- Discord-Bot-main/Bot/event.py | 41 ++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Discord-Bot-main/Bot/UR-Bot.py b/Discord-Bot-main/Bot/UR-Bot.py index 30eda99..6897f9c 100644 --- a/Discord-Bot-main/Bot/UR-Bot.py +++ b/Discord-Bot-main/Bot/UR-Bot.py @@ -7,10 +7,10 @@ load_dotenv() -TOKEN = os.getenv('DISCORD_TOKEN') -print("coucou") -print("coucou") -print(TOKEN) +TOKEN = os.getenv('DISCORD_TOKEN', None) +if TOKEN is None: + print("The discord token is not defined \n\t defined it in the .env file (dev) \n\t or in the environment in docker-compose") + exit(1) class UR_BOT(commands.Bot): diff --git a/Discord-Bot-main/Bot/event.py b/Discord-Bot-main/Bot/event.py index bd5803d..cc28eaf 100644 --- a/Discord-Bot-main/Bot/event.py +++ b/Discord-Bot-main/Bot/event.py @@ -1,68 +1,71 @@ import asyncio import discord +from dotenv import load_dotenv +import os -BOT_PREFIX = "$" +load_dotenv() +BOT_PREFIX = os.getenv('BOT_PREFIX', '$') HELP_DATA = { - "About": + "About": { - "cmd_0": + "cmd_0": { "cmd": "credit", "help": "Affiche les crédits" }, - "cmd_1": + "cmd_1": { "cmd": "version", "help": "Affiche les numéros de version" }, }, - "General": + "General": { - "cmd_0": + "cmd_0": { "cmd": "cancel", "help": "Annule l'action en cours" }, - "cmd_1": + "cmd_1": { "cmd": "done", "help": "Confirme l'action en cours" }, - "cmd_2": + "cmd_2": { "cmd": "edit", "help": "Édite un message" }, - "cmd_3": + "cmd_3": { "cmd": "lang", "help": "Change la langue de l'utilisateur" }, }, - "Planning": + "Planning": { - "cmd_0": + "cmd_0": { "cmd": "cal", "help": "Permet d'accéder au calendrier" }, - "cmd_1": + "cmd_1": { "cmd": "jdr", "help": "Envoie un lien pour créer une partie" }, - "cmd_2": + "cmd_2": { "cmd": "site", "help": "Permet d'accéder au calendrier" @@ -70,9 +73,9 @@ }, - "Presentation": + "Presentation": { - "cmd_0": + "cmd_0": { "cmd": "prez", "help": "Envoie un lien pour se présenter" @@ -81,7 +84,7 @@ "No Category": { - "cmd_0": + "cmd_0": { "cmd": "help", "help": "Affiche ce message" @@ -103,7 +106,7 @@ def GetMaxStrSizeInArray(array:dict,callback=None): async def on_ping(event): await event.author.send("pong"); - + async def on_message(event,*args,**kwargs): if event.content.startswith('hi'): @@ -123,9 +126,9 @@ async def on_prez(event,*args,**kwargs): await event.author.send(embed=embed) # envoie un message de presentation privée à l'auteur qui a fait a commandes async def on_help(event,*args): - embed = discord.Embed(title="URBOT - helper", color= 0x0CC1EE) + embed = discord.Embed(title="URBOT - helper", color= 0x0CC1EE) embed.set_author(name="UR-BOT", icon_url="https://cdn.discordapp.com/avatars/1040275175687606372/33d5a8782c1d658caeeae59799e722b0.webp?size=32") - + HELP = str(f"**prefix : {BOT_PREFIX}**\n\n\t```diff\n") for x in HELP_DATA.items(): HELP += f"\r+ {x[0]}\n\n\t" From 1254a624aff5f6c7a10e576a968063c03dae576b Mon Sep 17 00:00:00 2001 From: Mortifia Date: Wed, 14 Dec 2022 13:07:25 +0100 Subject: [PATCH 04/29] python discord v2 + disable debugBot file --- Discord-Bot-main/Bot/.env | 3 +- .../Bot/{DebugBot.py => OLD_DebugBot.py} | 0 Discord-Bot-main/Bot/UR-Bot.py | 70 +++++++++--------- Discord-Bot-main/Bot/event.py | 7 +- Discord-Bot-main/Bot/requirements.txt | Bin 372 -> 372 bytes Discord-Bot-main/Dockerfile | 2 +- 6 files changed, 40 insertions(+), 42 deletions(-) rename Discord-Bot-main/Bot/{DebugBot.py => OLD_DebugBot.py} (100%) diff --git a/Discord-Bot-main/Bot/.env b/Discord-Bot-main/Bot/.env index ea55438..c1731cf 100644 --- a/Discord-Bot-main/Bot/.env +++ b/Discord-Bot-main/Bot/.env @@ -1,2 +1,3 @@ # token for dev -TOKEN=MTA0MDI3NTE3NTY4NzYwNjM3Mg.GRMHqA.yEreIkPlGQh4wMWz5_TK3yLPf3ygEMfGCYrwmk +DISCORD_TOKEN="-------------------------------------------" +BOT_PREFIX="/" \ No newline at end of file diff --git a/Discord-Bot-main/Bot/DebugBot.py b/Discord-Bot-main/Bot/OLD_DebugBot.py similarity index 100% rename from Discord-Bot-main/Bot/DebugBot.py rename to Discord-Bot-main/Bot/OLD_DebugBot.py diff --git a/Discord-Bot-main/Bot/UR-Bot.py b/Discord-Bot-main/Bot/UR-Bot.py index 6897f9c..9fec2ed 100644 --- a/Discord-Bot-main/Bot/UR-Bot.py +++ b/Discord-Bot-main/Bot/UR-Bot.py @@ -1,71 +1,67 @@ +#ext import import discord from dotenv import load_dotenv -import DebugBot +# import DebugBot from discord.ext import commands import os +#local import +import event - -load_dotenv() -TOKEN = os.getenv('DISCORD_TOKEN', None) -if TOKEN is None: - print("The discord token is not defined \n\t defined it in the .env file (dev) \n\t or in the environment in docker-compose") - exit(1) - -class UR_BOT(commands.Bot): +class BOT_BASE(commands.Bot): async def on_ready(self): print('--- We have successfully loggged in as {0.user}'.format(self)) async def on_message(self, message): - if message.author == self.user: return - - await DebugBot.debug_on_message(message); - + # await DebugBot.debug_on_message(message); return await bot.process_commands(message) - - - +#set listeners intent = discord.Intents.default() intent.members = True intent.messages = True +intent.message_content = True #v2 -bot = UR_BOT(command_prefix= DebugBot.event.BOT_PREFIX,intents=intent) +bot = BOT_BASE(command_prefix= event.BOT_PREFIX,intents=intent) #build bot bot.remove_command('help') - - @bot.command(name="ping") @commands.guild_only() async def ping(ctx): - await DebugBot.debug_on_ping(ctx) - - - + print("ping") + await event.on_ping(ctx) @bot.command(name="help") async def help(ctx): - await DebugBot.debug_on_help(ctx) + await event.on_help(ctx) @bot.command(name="prez") async def prez(ctx): - await DebugBot.debug_on_prez(ctx) - - -@DebugBot.update_all_modules -async def reload_module(): - return 0; - + await event.on_prez(ctx) +# @DebugBot.update_all_modules +# async def reload_module(): +# return 0 @bot.command(aliases=['reload', 'rld']) async def reload_module(ctx): - await reload_module(); - await ctx.channel.send("The scripts has been reloaded."); - return 0; - - -bot.run(TOKEN) + await reload_module() + await ctx.channel.send("The scripts has been reloaded.") + return 0 + +# if the file is run directly, run the bot +if __name__ == '__main__': + #load token from env + load_dotenv() + TOKEN = os.getenv('DISCORD_TOKEN', None) + + # exit if no token found + if TOKEN is None: + print("The discord token is not defined \n\t defined it in the .env file (dev) \n\t or in the environment in docker-compose") + exit(1) + + # run bot + bot.run(TOKEN) diff --git a/Discord-Bot-main/Bot/event.py b/Discord-Bot-main/Bot/event.py index cc28eaf..fffda6e 100644 --- a/Discord-Bot-main/Bot/event.py +++ b/Discord-Bot-main/Bot/event.py @@ -104,9 +104,10 @@ def GetMaxStrSizeInArray(array:dict,callback=None): async def on_ping(event): - await event.author.send("pong"); - - + await asyncio.gather( # concurent await + event.message.add_reaction('🏓'), + event.author.send("pong") + ) async def on_message(event,*args,**kwargs): if event.content.startswith('hi'): diff --git a/Discord-Bot-main/Bot/requirements.txt b/Discord-Bot-main/Bot/requirements.txt index 28fddf9f777c5c45d51461bda32614e26e383817..43e9c5989c264730b21a1e2a7ab043c8d1e97b0c 100644 GIT binary patch delta 19 Zcmeyu^o41{22LXeJqAM{Hki0`HvmA41}Xpm delta 19 Zcmeyu^o41{22Mi;JqB|iHlDb1HvmAq1~LEu diff --git a/Discord-Bot-main/Dockerfile b/Discord-Bot-main/Dockerfile index 6431c71..889f397 100644 --- a/Discord-Bot-main/Dockerfile +++ b/Discord-Bot-main/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7-alpine +FROM python:3.8-alpine WORKDIR /app COPY /Bot/requirements.txt bot/requirements.txt RUN pip3 install -r bot/requirements.txt From 5a649a94f2b1d2723a7b6cef0fc60b9a29e40287 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Wed, 14 Dec 2022 14:54:50 +0100 Subject: [PATCH 05/29] tmp desactivate custom help (need little work to aotomate doc with base data) --- Discord-Bot-main/Bot/UR-Bot.py | 16 +++++----------- Discord-Bot-main/Bot/extends/base/event.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 Discord-Bot-main/Bot/extends/base/event.py diff --git a/Discord-Bot-main/Bot/UR-Bot.py b/Discord-Bot-main/Bot/UR-Bot.py index 9fec2ed..1d0a682 100644 --- a/Discord-Bot-main/Bot/UR-Bot.py +++ b/Discord-Bot-main/Bot/UR-Bot.py @@ -4,6 +4,7 @@ # import DebugBot from discord.ext import commands import os +import asyncio #local import import event @@ -26,15 +27,12 @@ async def on_message(self, message): intent.message_content = True #v2 bot = BOT_BASE(command_prefix= event.BOT_PREFIX,intents=intent) #build bot -bot.remove_command('help') +# bot.remove_command('help') -@bot.command(name="ping") -@commands.guild_only() -async def ping(ctx): - print("ping") - await event.on_ping(ctx) +#load extension base +asyncio.run(bot.load_extension("extends.base.event")) -@bot.command(name="help") +@bot.command(name="_help") async def help(ctx): await event.on_help(ctx) @@ -42,10 +40,6 @@ async def help(ctx): async def prez(ctx): await event.on_prez(ctx) -# @DebugBot.update_all_modules -# async def reload_module(): -# return 0 - @bot.command(aliases=['reload', 'rld']) async def reload_module(ctx): await reload_module() diff --git a/Discord-Bot-main/Bot/extends/base/event.py b/Discord-Bot-main/Bot/extends/base/event.py new file mode 100644 index 0000000..dd0927b --- /dev/null +++ b/Discord-Bot-main/Bot/extends/base/event.py @@ -0,0 +1,21 @@ +import asyncio +from discord.ext import commands + +class Base(commands.Cog, name='base'): + def __init__(self, bot): + self.bot = bot + self._last_member = None + + async def cog_load(self): # called when the cog is loaded + print(self.__class__.__name__ + " is loaded") + + @commands.command(name="ping", help='ping pong with the bot',aliases=['pong'], ) + @commands.guild_only() + async def _ping(self, event): + await asyncio.gather( # concurent await + event.message.add_reaction('🏓'), + event.author.send("pong") + ) + +async def setup(bot): + await bot.add_cog(Base(bot)) \ No newline at end of file From 6dea2ebda61e3126c5cdaf916b50be6082640b6b Mon Sep 17 00:00:00 2001 From: Mortifia Date: Wed, 14 Dec 2022 15:42:11 +0100 Subject: [PATCH 06/29] clean old file add auto add extension --- Discord-Bot-main/Bot/OLD_DebugBot.py | 45 ----------------- Discord-Bot-main/Bot/UR-Bot.py | 49 ++++++++----------- .../Bot/{event.py => extends/base/tmp.py} | 47 +++++++++++------- 3 files changed, 49 insertions(+), 92 deletions(-) delete mode 100644 Discord-Bot-main/Bot/OLD_DebugBot.py rename Discord-Bot-main/Bot/{event.py => extends/base/tmp.py} (85%) diff --git a/Discord-Bot-main/Bot/OLD_DebugBot.py b/Discord-Bot-main/Bot/OLD_DebugBot.py deleted file mode 100644 index 4cc5d90..0000000 --- a/Discord-Bot-main/Bot/OLD_DebugBot.py +++ /dev/null @@ -1,45 +0,0 @@ - - -from importlib import reload -import asyncio -import event -import types - - - -# décaration d'un décorateur a fonction (@update_module(module_name)) -def update_module(module): - def __update__(func): - async def callback(*args, **kwargs): - reload(__import__(module,globals(), locals(), [], 0)) # (recherche le module pour voir si il est déà chargé sinon il import dynamiquement) puis la fonction reload recharge le module - return await func(*args, **kwargs) - - return (callback) - - return __update__ - - -def update_all_modules(func): - async def __update__(*args, **kwargs): - for name, val in globals().items(): - if isinstance(val, types.ModuleType): - print(val.__name__) - reload(__import__(val.__name__,globals(), locals(), [], 0)) - - reload(__import__("DebugBot",globals(), locals(), [], 0)) - - return await func(*args, **kwargs) - - return __update__ - -async def debug_on_message(*args,**kwargs): - return await event.on_message(*args,**kwargs) - -async def debug_on_ping(*args,**kwargs): - return await event.on_ping(*args,**kwargs) - -async def debug_on_prez(*args,**kwargs): - return await event.on_prez(*args,**kwargs) - -async def debug_on_help(*args,**kwargs): - return await event.on_help(*args,**kwargs) diff --git a/Discord-Bot-main/Bot/UR-Bot.py b/Discord-Bot-main/Bot/UR-Bot.py index 1d0a682..cf0c854 100644 --- a/Discord-Bot-main/Bot/UR-Bot.py +++ b/Discord-Bot-main/Bot/UR-Bot.py @@ -6,11 +6,17 @@ import os import asyncio -#local import -import event +#load token from env +load_dotenv() +TOKEN = os.getenv('DISCORD_TOKEN', None) +BOT_PREFIX = os.getenv('BOT_PREFIX', '$') -class BOT_BASE(commands.Bot): +# exit if no token found +if TOKEN is None: + print("The discord token is not defined \n\t defined it in the .env file (dev) \n\t or in the environment in docker-compose") + exit(1) +class BOT_BASE(commands.Bot): async def on_ready(self): print('--- We have successfully loggged in as {0.user}'.format(self)) @@ -26,36 +32,21 @@ async def on_message(self, message): intent.messages = True intent.message_content = True #v2 -bot = BOT_BASE(command_prefix= event.BOT_PREFIX,intents=intent) #build bot -# bot.remove_command('help') - -#load extension base -asyncio.run(bot.load_extension("extends.base.event")) - -@bot.command(name="_help") -async def help(ctx): - await event.on_help(ctx) - -@bot.command(name="prez") -async def prez(ctx): - await event.on_prez(ctx) +bot = BOT_BASE(command_prefix=BOT_PREFIX, intents=intent) #build bot -@bot.command(aliases=['reload', 'rld']) -async def reload_module(ctx): - await reload_module() - await ctx.channel.send("The scripts has been reloaded.") - return 0 +#laod all extensions in the glob "./**/*.py" +# like that to limit side effect between extension +async def load_all_extensions(): + for dir in os.listdir('./extends'): + for file in os.listdir('./extends/'+dir): + if file.endswith('.py'): + print(dir+'/'+file) + await bot.load_extension('extends.'+dir+'.'+file[:-3]) # if the file is run directly, run the bot if __name__ == '__main__': - #load token from env - load_dotenv() - TOKEN = os.getenv('DISCORD_TOKEN', None) - - # exit if no token found - if TOKEN is None: - print("The discord token is not defined \n\t defined it in the .env file (dev) \n\t or in the environment in docker-compose") - exit(1) + # load all extensions + asyncio.run(load_all_extensions()) # run bot bot.run(TOKEN) diff --git a/Discord-Bot-main/Bot/event.py b/Discord-Bot-main/Bot/extends/base/tmp.py similarity index 85% rename from Discord-Bot-main/Bot/event.py rename to Discord-Bot-main/Bot/extends/base/tmp.py index fffda6e..8e5ca6f 100644 --- a/Discord-Bot-main/Bot/event.py +++ b/Discord-Bot-main/Bot/extends/base/tmp.py @@ -1,9 +1,8 @@ - import asyncio -import discord -from dotenv import load_dotenv import os - +from dotenv import load_dotenv +import discord +from discord.ext import commands load_dotenv() BOT_PREFIX = os.getenv('BOT_PREFIX', '$') @@ -91,8 +90,7 @@ }, } -}; - +} def GetMaxStrSizeInArray(array:dict,callback=None): _size=0; @@ -102,7 +100,6 @@ def GetMaxStrSizeInArray(array:dict,callback=None): _size = _r return _size - async def on_ping(event): await asyncio.gather( # concurent await event.message.add_reaction('🏓'), @@ -116,20 +113,15 @@ async def on_message(event,*args,**kwargs): async def on_prez(event,*args,**kwargs): embed = discord.Embed(url="http://presentation.unionrolistes.fr/?webhook=https://discord.com/api/webhooks/875068900612665396/DJusy0eGs9Xyx2os-dodBVfWia2fbhfBzfmnDM9g-30ozoFYAuZBHVXaD9TKaC1wwBwg", description="⬆️ Here is the link to create your presentation.", title="Union Roliste - Presentation", color= 0x0CC1EE) embed.set_author(name=event.author.display_name, icon_url=event.author.avatar_url) - DATA = ["**:pen_ballpoint: Nom\n**","**:pen_ballpoint: Prenom\n**",":round_pushpin: **Address\n**",":telephone: **N° Telephone\n**", ":postbox: **Code postal\n**","**:computer: Support (Windows / Linux / Mac)\n**","**Expérience en programmation\n**"] embed.add_field(name="**\n**", value="**───────────────────────────────**", inline=False) - embed.set_footer(text="Union Roliste dev presentation.", icon_url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - embed.set_thumbnail(url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - await event.author.send(embed=embed) # envoie un message de presentation privée à l'auteur qui a fait a commandes async def on_help(event,*args): embed = discord.Embed(title="URBOT - helper", color= 0x0CC1EE) embed.set_author(name="UR-BOT", icon_url="https://cdn.discordapp.com/avatars/1040275175687606372/33d5a8782c1d658caeeae59799e722b0.webp?size=32") - HELP = str(f"**prefix : {BOT_PREFIX}**\n\n\t```diff\n") for x in HELP_DATA.items(): HELP += f"\r+ {x[0]}\n\n\t" @@ -137,15 +129,34 @@ async def on_help(event,*args): for cmd in x[1].items(): _offset = (c - cmd[1]["cmd"].__len__())+1 # centrage de lq flèche (->) HELP += cmd[1]["cmd"]+ (" "*_offset) + "-> "+cmd[1]["help"]+"\n\t" # Ecrit la command -> description - HELP += f"\n```" - embed.add_field(name="**\n**", value="**───────────────────────────────**", inline=False) - embed.add_field(name="**\n**", value=HELP, inline=False) - embed.set_footer(text="Union Roliste commands helper.", icon_url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - embed.set_thumbnail(url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") # set le logo en haut a droit + await event.channel.send(embed=embed) + +class Tmp(commands.Cog, name='tmp'): + def __init__(self, bot): + self.bot = bot + self._last_member = None + + async def cog_load(self): # called when the cog is loaded + print(self.__class__.__name__ + " is loaded") + + @commands.command(name="_help") + async def help(Self, ctx): + await on_help(ctx) + + @commands.command(name="prez") + async def prez(Self, ctx): + await on_prez(ctx) + + @commands.command(aliases=['reload', 'rld']) + async def reload_module(Self, ctx): + await reload_module() + await ctx.channel.send("The scripts has been reloaded.") + return 0 - await event.channel.send(embed=embed); +async def setup(bot): + await bot.add_cog(Tmp(bot)) \ No newline at end of file From f73266be4f3dac63a974d8f8ee6a865d72ac9eb4 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Wed, 14 Dec 2022 16:18:56 +0100 Subject: [PATCH 07/29] add protection in extension loading --- Discord-Bot-main/Bot/UR-Bot.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Discord-Bot-main/Bot/UR-Bot.py b/Discord-Bot-main/Bot/UR-Bot.py index cf0c854..9b78bc6 100644 --- a/Discord-Bot-main/Bot/UR-Bot.py +++ b/Discord-Bot-main/Bot/UR-Bot.py @@ -1,4 +1,4 @@ -#ext import +# ext import import discord from dotenv import load_dotenv # import DebugBot @@ -6,7 +6,7 @@ import os import asyncio -#load token from env +# load token from env load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN', None) BOT_PREFIX = os.getenv('BOT_PREFIX', '$') @@ -16,32 +16,48 @@ print("The discord token is not defined \n\t defined it in the .env file (dev) \n\t or in the environment in docker-compose") exit(1) + class BOT_BASE(commands.Bot): async def on_ready(self): print('--- We have successfully loggged in as {0.user}'.format(self)) async def on_message(self, message): if message.author == self.user: - return + return # await DebugBot.debug_on_message(message); return await bot.process_commands(message) -#set listeners + +# set listeners intent = discord.Intents.default() intent.members = True intent.messages = True -intent.message_content = True #v2 +intent.message_content = True # v2 -bot = BOT_BASE(command_prefix=BOT_PREFIX, intents=intent) #build bot +bot = BOT_BASE(command_prefix=BOT_PREFIX, intents=intent) # build bot -#laod all extensions in the glob "./**/*.py" +# laod all extensions in the glob "./**/*.py" # like that to limit side effect between extension + + async def load_all_extensions(): for dir in os.listdir('./extends'): + # run pip install -r requirements.txt if exists + if os.path.exists('./extends/'+dir+'/requirements.txt'): + try: + print('pip install -r ./extends/'+dir+'/requirements.txt') + os.system('pip install -r ./extends/'+dir+'/requirements.txt') + except: + print('pip install -r ./extends/' + + dir+'/requirements.txt failed') for file in os.listdir('./extends/'+dir): if file.endswith('.py'): - print(dir+'/'+file) - await bot.load_extension('extends.'+dir+'.'+file[:-3]) + try: + print(dir+'/'+file) + await bot.load_extension('extends.'+dir+'.'+file[:-3]) + except Exception as e: + print('Failed to load extension {0}.'.format(file)) + print(e) # if the file is run directly, run the bot if __name__ == '__main__': From db9cb5834cd4533c952f0214fbe4b43082291a93 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Thu, 15 Dec 2022 12:59:18 +0100 Subject: [PATCH 08/29] update ping and doc --- Discord-Bot-main/Bot/extends/base/event.py | 13 +++--- Discord-Bot-main/README.md | 47 +++++++++++++--------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/Discord-Bot-main/Bot/extends/base/event.py b/Discord-Bot-main/Bot/extends/base/event.py index dd0927b..ee2074f 100644 --- a/Discord-Bot-main/Bot/extends/base/event.py +++ b/Discord-Bot-main/Bot/extends/base/event.py @@ -1,21 +1,24 @@ import asyncio from discord.ext import commands + class Base(commands.Cog, name='base'): def __init__(self, bot): self.bot = bot self._last_member = None - async def cog_load(self): # called when the cog is loaded + async def cog_load(self): # called when the cog is loaded print(self.__class__.__name__ + " is loaded") - @commands.command(name="ping", help='ping pong with the bot',aliases=['pong'], ) + @commands.command(name="ping", help='ping pong with the bot', aliases=['pong'], ) @commands.guild_only() async def _ping(self, event): - await asyncio.gather( # concurent await + await asyncio.gather( # concurent await event.message.add_reaction('🏓'), - event.author.send("pong") + event.send('Pong! 🏓 {0} ms'.format( + round(self.bot.latency, 3) * 1000)) ) + async def setup(bot): - await bot.add_cog(Base(bot)) \ No newline at end of file + await bot.add_cog(Base(bot)) diff --git a/Discord-Bot-main/README.md b/Discord-Bot-main/README.md index 91c1d98..3e396c4 100644 --- a/Discord-Bot-main/README.md +++ b/Discord-Bot-main/README.md @@ -1,21 +1,32 @@ # Discord-Bot + [doc lib](https://discordpy.readthedocs.io/en/stable/api.html?highlight=on_message#discord.Guild.get_channel) -###### doc : https://discordpy.readthedocs.io/en/stable/api.html?highlight=on_message#discord.Guild.get_channel - -# Instalation - -> **Télécharger** ![Docker](docker.com) -> -> **Télécharger** ![Discord-Bot-main](https://github.com/UnionRolistes/Bot_Base/tree/11-cree-une-application-docker-pour-facilit%C3%A9-le-developpement/Discord-Bot-main) -> -> **Lancer le script** ![LaunchInstall.bat](https://github.com/UnionRolistes/Bot_Base/blob/11-cree-une-application-docker-pour-facilit%C3%A9-le-developpement/Discord-Bot-main/Install/LaunchInstall.bat) dans [Powershell](https://learn.microsoft.com/fr-fr/powershell/scripting/overview?view=powershell-7.3) -> -> **Fini !. Le script (LaunchInstall.bat) lancera automatiquement le container et par concequant le bot discord** -> -> -> **Commandes disponible :** -> -> - **$prez** -> - **$help** -> - **$ping** +## Instalation +- Install [Docker](https://docs.docker.com/desktop/) (only need engine and compose) +- dowload repo + + +## first use (or if you want to change the token) + +- [tuto](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) to get your token +- change the token in the .env file + +## use + +- start : `docker-compose up --build -d` (in terminal in path of the project) +- log : `docker-compose logs -f` (in terminal in path of the project) +- stop : `docker-compose down` (in terminal in path of the project) + +## use other repo to extend the bot + +- go to parent folder of the bot +- clone repo needed +- merge docker-compose with `docker-compose -f docker-compose.yml -f ../repo-x/docker-compose.yml config > docker-compose-merge.yml` + > add `-f repo-y/docker-compose.yml` for each repo +- start command here + +## Commandes disponible dans le bot base + +- `$help` : show all command available +- `$ping` : show pong and time to respond ( test if bot is alive ) From b312f8aeff67215cdfbe867101c7bdb6dbde571d Mon Sep 17 00:00:00 2001 From: Mortifia Date: Fri, 16 Dec 2022 16:14:18 +0100 Subject: [PATCH 09/29] demo ready (need more control and automate merge of extend) --- Discord-Bot-main/.dockerignore | 3 +- Discord-Bot-main/Bot/.env | 4 +- Discord-Bot-main/Bot/extends/base/tmp.py | 75 +++---- Discord-Bot-main/Dockerfile | 8 +- Discord-Bot-main/README.md | 17 +- Discord-Bot-main/apache/ATTENTION!.txt | 2 + Discord-Bot-main/apache/default-httpd.conf | 227 +++++++++++++++++++++ Discord-Bot-main/docker-compose.yaml | 12 -- Discord-Bot-main/docker-compose.yml | 25 +++ 9 files changed, 312 insertions(+), 61 deletions(-) create mode 100644 Discord-Bot-main/apache/ATTENTION!.txt create mode 100644 Discord-Bot-main/apache/default-httpd.conf delete mode 100644 Discord-Bot-main/docker-compose.yaml create mode 100644 Discord-Bot-main/docker-compose.yml diff --git a/Discord-Bot-main/.dockerignore b/Discord-Bot-main/.dockerignore index 2eea525..a85a5bc 100644 --- a/Discord-Bot-main/.dockerignore +++ b/Discord-Bot-main/.dockerignore @@ -1 +1,2 @@ -.env \ No newline at end of file +.env +# Bot/.env \ No newline at end of file diff --git a/Discord-Bot-main/Bot/.env b/Discord-Bot-main/Bot/.env index c1731cf..e6544fb 100644 --- a/Discord-Bot-main/Bot/.env +++ b/Discord-Bot-main/Bot/.env @@ -1,3 +1,3 @@ # token for dev -DISCORD_TOKEN="-------------------------------------------" -BOT_PREFIX="/" \ No newline at end of file +DISCORD_TOKEN="--------------------------------------" +BOT_PREFIX="$" \ No newline at end of file diff --git a/Discord-Bot-main/Bot/extends/base/tmp.py b/Discord-Bot-main/Bot/extends/base/tmp.py index 8e5ca6f..1cd8bbc 100644 --- a/Discord-Bot-main/Bot/extends/base/tmp.py +++ b/Discord-Bot-main/Bot/extends/base/tmp.py @@ -37,20 +37,20 @@ "help": "Confirme l'action en cours" }, - "cmd_2": + "cmd_2": { "cmd": "edit", "help": "Édite un message" }, - "cmd_3": + "cmd_3": { "cmd": "lang", "help": "Change la langue de l'utilisateur" }, }, - "Planning": + "Planning": { "cmd_0": { @@ -64,7 +64,7 @@ "help": "Envoie un lien pour créer une partie" }, - "cmd_2": + "cmd_2": { "cmd": "site", "help": "Permet d'accéder au calendrier" @@ -89,74 +89,67 @@ "help": "Affiche ce message" }, } - } -def GetMaxStrSizeInArray(array:dict,callback=None): - _size=0; + +def GetMaxStrSizeInArray(array: dict, callback=None): + _size = 0 for cmd in array: _r = callback(cmd) - if(_r > _size): - _size = _r + if (_r > _size): + _size = _r return _size -async def on_ping(event): - await asyncio.gather( # concurent await - event.message.add_reaction('🏓'), - event.author.send("pong") - ) -async def on_message(event,*args,**kwargs): +async def on_message(event, *args, **kwargs): if event.content.startswith('hi'): await event.channel.send(f'Hello! Mis a jour : {args}') -async def on_prez(event,*args,**kwargs): - embed = discord.Embed(url="http://presentation.unionrolistes.fr/?webhook=https://discord.com/api/webhooks/875068900612665396/DJusy0eGs9Xyx2os-dodBVfWia2fbhfBzfmnDM9g-30ozoFYAuZBHVXaD9TKaC1wwBwg", description="⬆️ Here is the link to create your presentation.", title="Union Roliste - Presentation", color= 0x0CC1EE) - embed.set_author(name=event.author.display_name, icon_url=event.author.avatar_url) - DATA = ["**:pen_ballpoint: Nom\n**","**:pen_ballpoint: Prenom\n**",":round_pushpin: **Address\n**",":telephone: **N° Telephone\n**", ":postbox: **Code postal\n**","**:computer: Support (Windows / Linux / Mac)\n**","**Expérience en programmation\n**"] - embed.add_field(name="**\n**", value="**───────────────────────────────**", inline=False) - embed.set_footer(text="Union Roliste dev presentation.", icon_url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - embed.set_thumbnail(url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - await event.author.send(embed=embed) # envoie un message de presentation privée à l'auteur qui a fait a commandes - -async def on_help(event,*args): - embed = discord.Embed(title="URBOT - helper", color= 0x0CC1EE) - embed.set_author(name="UR-BOT", icon_url="https://cdn.discordapp.com/avatars/1040275175687606372/33d5a8782c1d658caeeae59799e722b0.webp?size=32") + +async def on_help(event, *args): + embed = discord.Embed(title="URBOT - helper", color=0x0CC1EE) + embed.set_author( + name="UR-BOT", icon_url="https://cdn.discordapp.com/avatars/1040275175687606372/33d5a8782c1d658caeeae59799e722b0.webp?size=32") HELP = str(f"**prefix : {BOT_PREFIX}**\n\n\t```diff\n") for x in HELP_DATA.items(): - HELP += f"\r+ {x[0]}\n\n\t" - c = GetMaxStrSizeInArray(x[1].items(),lambda a : a[1]['cmd'].__len__()) # Obtenir la chaine la plus longe pour ensuite center la fleche (->) - for cmd in x[1].items(): - _offset = (c - cmd[1]["cmd"].__len__())+1 # centrage de lq flèche (->) - HELP += cmd[1]["cmd"]+ (" "*_offset) + "-> "+cmd[1]["help"]+"\n\t" # Ecrit la command -> description + HELP += f"\r+ {x[0]}\n\n\t" + # Obtenir la chaine la plus longe pour ensuite center la fleche (->) + c = GetMaxStrSizeInArray(x[1].items(), lambda a: a[1]['cmd'].__len__()) + for cmd in x[1].items(): + _offset = (c - cmd[1]["cmd"].__len__()) + \ + 1 # centrage de lq flèche (->) + # Ecrit la command -> description + HELP += cmd[1]["cmd"] + (" "*_offset) + "-> "+cmd[1]["help"]+"\n\t" HELP += f"\n```" - embed.add_field(name="**\n**", value="**───────────────────────────────**", inline=False) + embed.add_field( + name="**\n**", value="**───────────────────────────────**", inline=False) embed.add_field(name="**\n**", value=HELP, inline=False) - embed.set_footer(text="Union Roliste commands helper.", icon_url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - embed.set_thumbnail(url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") # set le logo en haut a droit + embed.set_footer(text="Union Roliste commands helper.", + icon_url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") + # set le logo en haut a droit + embed.set_thumbnail( + url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") await event.channel.send(embed=embed) + class Tmp(commands.Cog, name='tmp'): def __init__(self, bot): self.bot = bot self._last_member = None - async def cog_load(self): # called when the cog is loaded + async def cog_load(self): # called when the cog is loaded print(self.__class__.__name__ + " is loaded") @commands.command(name="_help") async def help(Self, ctx): await on_help(ctx) - @commands.command(name="prez") - async def prez(Self, ctx): - await on_prez(ctx) - @commands.command(aliases=['reload', 'rld']) async def reload_module(Self, ctx): await reload_module() await ctx.channel.send("The scripts has been reloaded.") return 0 + async def setup(bot): - await bot.add_cog(Tmp(bot)) \ No newline at end of file + await bot.add_cog(Tmp(bot)) diff --git a/Discord-Bot-main/Dockerfile b/Discord-Bot-main/Dockerfile index 889f397..a31a1fe 100644 --- a/Discord-Bot-main/Dockerfile +++ b/Discord-Bot-main/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.8-alpine WORKDIR /app -COPY /Bot/requirements.txt bot/requirements.txt -RUN pip3 install -r bot/requirements.txt -COPY /Bot/ ./bot/ -CMD find . -name 'requirement*' -type f -exec pip3 install -r '{}' ';' && python3 bot/UR-Bot.py +COPY /Bot/requirements.txt requirements.txt +RUN pip3 install -r requirements.txt +COPY /Bot/ ./ +CMD find . -name 'requirement*' -type f -exec pip3 install -r '{}' ';' ; ls -la ; python -u UR-Bot.py diff --git a/Discord-Bot-main/README.md b/Discord-Bot-main/README.md index 3e396c4..65c153d 100644 --- a/Discord-Bot-main/README.md +++ b/Discord-Bot-main/README.md @@ -6,7 +6,6 @@ - Install [Docker](https://docs.docker.com/desktop/) (only need engine and compose) - dowload repo - ## first use (or if you want to change the token) - [tuto](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) to get your token @@ -26,7 +25,23 @@ > add `-f repo-y/docker-compose.yml` for each repo - start command here +```cmd + docker-compose -f .\docker-compose.yml -f ..\..\Bot_Presentation\bot_docker\docker-compose.yml config >test.yml + docker-compose -f test.yml up --build -d +``` + ## Commandes disponible dans le bot base - `$help` : show all command available - `$ping` : show pong and time to respond ( test if bot is alive ) + + + +## dev + +### web + +pour travailler en local penser a faire mentir le dns de votre machine pour que le nom de domaine voulu pointe la ou il faut + +- windows : `C:\WINDOWS\system32\drivers\etc\hosts` +- linux : `/etc/hosts` \ No newline at end of file diff --git a/Discord-Bot-main/apache/ATTENTION!.txt b/Discord-Bot-main/apache/ATTENTION!.txt new file mode 100644 index 0000000..961a074 --- /dev/null +++ b/Discord-Bot-main/apache/ATTENTION!.txt @@ -0,0 +1,2 @@ +httpd.conf doit obligatoirement etre en `UTF-8`et line ending en `LF` (Unix) +sinon apache bug. (cat peut transformé l'encodage et les line ending) \ No newline at end of file diff --git a/Discord-Bot-main/apache/default-httpd.conf b/Discord-Bot-main/apache/default-httpd.conf new file mode 100644 index 0000000..ae4b2c3 --- /dev/null +++ b/Discord-Bot-main/apache/default-httpd.conf @@ -0,0 +1,227 @@ +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration +# + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# NOTE! If you intend to place this on an NFS (or otherwise network) +# mounted filesystem then please read the Mutex documentation (available +# at ); +# you will save yourself a lot of trouble. +# +# Do NOT add a slash at the end of the directory path. +# +#ServerRoot "/etc/apache2" + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +#Mutex file:${APACHE_LOCK_DIR} default + +# +# The directory where shm and other runtime files will be stored. +# + +DefaultRuntimeDir ${APACHE_RUN_DIR} + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# +# Options Indexes FollowSymLinks +# AllowOverride None +# Require all granted +# + + + + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + + +# +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %O" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/Discord-Bot-main/docker-compose.yaml b/Discord-Bot-main/docker-compose.yaml deleted file mode 100644 index 8d0fda3..0000000 --- a/Discord-Bot-main/docker-compose.yaml +++ /dev/null @@ -1,12 +0,0 @@ -version: '3' -name: union des rolistes - -services: - bot_base: - build: . #utilise le dockerfile pour cree le container a utiliser - container_name: bot_base - restart: always #redemarre le container si il s'eteint - environment: - - DISCORD_TOKEN=--------------------------------- #token pour comm avec discord - - BOT_PREFIX=/ #prefix pour les commandes - #- BOT_EXTEND=coucou, coucou2 #extensions a charger diff --git a/Discord-Bot-main/docker-compose.yml b/Discord-Bot-main/docker-compose.yml new file mode 100644 index 0000000..80f25c8 --- /dev/null +++ b/Discord-Bot-main/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3' +name: uniondesroliste + +services: + bot_base: + build: . #utilise le dockerfile pour cree le container a utiliser + container_name: bot_base + restart: always #redemarre le container si il s'eteint + environment: + - DISCORD_TOKEN=---------------------------------------- #token pour comm avec discord + - BOT_PREFIX=$ #prefix pour les commandes + #- BOT_EXTEND=coucou, coucou2 #extensions a charger + volumes: + - ./Bot/extends/base:/app/extends/base #dossier ou est l'extension de la base + php: + image: php:8-apache + # environment: + # - APACHE_HTTP_PORT_NUMBER=80 + # volumes: + # - ./apache/default-httpd.conf:/etc/apache2/apache2.conf:ro # si besoin de modifier le fichier de conf apache (normalement pas besoin) + ports: + - "80:80" + restart: always + depends_on: + - bot_base From 585dfaf10a89278d70b7564daf925880e570557d Mon Sep 17 00:00:00 2001 From: Mortifia Date: Tue, 20 Dec 2022 11:15:43 +0100 Subject: [PATCH 10/29] =?UTF-8?q?desactiv=C3=A9=20la=20fonction=20par=20se?= =?UTF-8?q?cu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Discord-Bot-main/Bot/extends/base/tmp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Discord-Bot-main/Bot/extends/base/tmp.py b/Discord-Bot-main/Bot/extends/base/tmp.py index 1cd8bbc..67a36ed 100644 --- a/Discord-Bot-main/Bot/extends/base/tmp.py +++ b/Discord-Bot-main/Bot/extends/base/tmp.py @@ -146,7 +146,7 @@ async def help(Self, ctx): @commands.command(aliases=['reload', 'rld']) async def reload_module(Self, ctx): - await reload_module() + # await reload_module() # work ??? await ctx.channel.send("The scripts has been reloaded.") return 0 From 73e41e79f2afafb8cfb3822564fef62749bacb36 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Tue, 20 Dec 2022 11:17:11 +0100 Subject: [PATCH 11/29] update docker ignore --- Discord-Bot-main/.dockerignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Discord-Bot-main/.dockerignore b/Discord-Bot-main/.dockerignore index a85a5bc..086d24d 100644 --- a/Discord-Bot-main/.dockerignore +++ b/Discord-Bot-main/.dockerignore @@ -1,2 +1,3 @@ .env -# Bot/.env \ No newline at end of file +# Bot/.env +test.yml \ No newline at end of file From 3a899ed5001d54b78ff33efde090d94dac094ac6 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Tue, 20 Dec 2022 11:18:13 +0100 Subject: [PATCH 12/29] add gitignore --- Discord-Bot-main/.dockerignore | 1 - Discord-Bot-main/.gitignore | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 Discord-Bot-main/.gitignore diff --git a/Discord-Bot-main/.dockerignore b/Discord-Bot-main/.dockerignore index 086d24d..00e212d 100644 --- a/Discord-Bot-main/.dockerignore +++ b/Discord-Bot-main/.dockerignore @@ -1,3 +1,2 @@ .env # Bot/.env -test.yml \ No newline at end of file diff --git a/Discord-Bot-main/.gitignore b/Discord-Bot-main/.gitignore new file mode 100644 index 0000000..d3d90e3 --- /dev/null +++ b/Discord-Bot-main/.gitignore @@ -0,0 +1 @@ +test.yml \ No newline at end of file From c32419003fbe60e4c10c381d1126d7ce650c7247 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Mon, 2 Jan 2023 11:42:37 +0100 Subject: [PATCH 13/29] add api service (python) (#18) * add api_base * add discord client base (api), add cors (api), requirement clean (bot) * ajout du reverse proxy(apache) et du client discord pour l'api * move to root, add version, add credit, other littel update * update doc (little to not lost info) --- .../.dockerignore => .dockerignore | 1 + .gitignore | 7 +- Discord-Bot-main/.gitignore | 1 - Discord-Bot-main/Bot/extends/base/event.py | 24 --- Discord-Bot-main/Bot/requirements.txt | Bin 372 -> 0 bytes Discord-Bot-main/README.md | 47 ----- Discord-Bot-main/docker-compose.yml | 25 --- README.md | 172 +++++------------ README_old.md | 128 +++++++++++++ add_repo.sh | 80 -------- .../apache => apache}/ATTENTION!.txt | 0 apache/Dockerfile | 4 + .../apache => apache}/default-httpd.conf | 0 apache/vhost/000_api.conf | 13 ++ api/Dockerfile | 7 + api/extends/base/_discord.py | 32 ++++ api/extends/base/base.py | 8 + api/main.py | 42 +++++ api/requirements.txt | 4 + {Discord-Bot-main/Bot => bot}/.env | 0 {Discord-Bot-main => bot}/Dockerfile | 6 +- {Discord-Bot-main/Bot => bot}/UR-Bot.py | 12 +- bot/__pycache__/DebugBot.cpython-310.pyc | Bin 0 -> 1703 bytes bot/__pycache__/event.cpython-310.pyc | Bin 0 -> 3759 bytes .../base/__pycache__/base.cpython-311.pyc | Bin 0 -> 5987 bytes .../base/__pycache__/event.cpython-310.pyc | Bin 0 -> 1254 bytes .../base/__pycache__/event.cpython-311.pyc | Bin 0 -> 2053 bytes .../base/__pycache__/event.cpython-38.pyc | Bin 0 -> 1206 bytes .../base/__pycache__/tmp.cpython-310.pyc | Bin 0 -> 4990 bytes .../base/__pycache__/tmp.cpython-311.pyc | Bin 0 -> 6022 bytes .../base/__pycache__/tmp.cpython-38.pyc | Bin 0 -> 3675 bytes .../__pycache__/tmp_not_used.cpython-311.pyc | Bin 0 -> 6031 bytes bot/extends/base/base.py | 86 +++++++++ bot/extends/base/credits.txt | 6 + .../extends/base/tmp_not_used.py | 0 bot/requirements.txt | Bin 0 -> 86 bytes docker-compose.yml | 42 +++++ install.sh | 95 ---------- src/Bot_Base.service | 15 -- src/bot/URbot.py | 151 --------------- src/bot/__init__.py | 21 --- src/bot/cog_About.py | 47 ----- src/bot/cog_General.py | 54 ------ src/bot/info/__init__.py | 0 src/bot/info/credits.txt | 2 - src/bot/info/version.txt | 1 - src/bot/settings.py | 1 - src/bot/strings.py | 25 --- src/bot/templates/__init__.py | 0 src/bot/templates/credit_template.txt | 0 src/bot/templates/service_template.txt | 4 - src/bot/templates/version_template.txt | 6 - src/locale/_pot/bot_base.pot | 23 --- src/locale/en/LC_MESSAGES/bot_base.mo | Bin 952 -> 0 bytes src/locale/en/LC_MESSAGES/bot_base.po | 50 ----- src/locale/fr/LC_MESSAGES/bot_base.mo | Bin 2450 -> 0 bytes src/locale/fr/LC_MESSAGES/bot_base.po | 173 ----------------- src/locale/special-rp/LC_MESSAGES/bot_base.mo | Bin 1663 -> 0 bytes src/locale/special-rp/LC_MESSAGES/bot_base.po | 96 ---------- src/start.py | 5 - src/urpy/__init__.py | 7 - src/urpy/get_ressources.py | 4 - src/urpy/help.py | 82 -------- src/urpy/localization.py | 84 --------- src/urpy/my_commands.py | 120 ------------ src/urpy/requirements.txt | 3 - src/urpy/templates/__init__.py | 0 .../planning_annoucement_template.txt | 14 -- src/urpy/utils.py | 121 ------------ src/urpy/xml.py | 175 ------------------ src/www/css/master.css | 37 ---- src/www/img/ccby.png | Bin 430 -> 0 bytes src/www/img/ur-bl2.png | Bin 88875 -> 0 bytes src/www/index.html | 37 ---- start.sh | 22 --- updateBot.sh | 21 --- 76 files changed, 429 insertions(+), 1814 deletions(-) rename Discord-Bot-main/.dockerignore => .dockerignore (66%) delete mode 100644 Discord-Bot-main/.gitignore delete mode 100644 Discord-Bot-main/Bot/extends/base/event.py delete mode 100644 Discord-Bot-main/Bot/requirements.txt delete mode 100644 Discord-Bot-main/README.md delete mode 100644 Discord-Bot-main/docker-compose.yml create mode 100644 README_old.md delete mode 100644 add_repo.sh rename {Discord-Bot-main/apache => apache}/ATTENTION!.txt (100%) create mode 100644 apache/Dockerfile rename {Discord-Bot-main/apache => apache}/default-httpd.conf (100%) create mode 100644 apache/vhost/000_api.conf create mode 100644 api/Dockerfile create mode 100644 api/extends/base/_discord.py create mode 100644 api/extends/base/base.py create mode 100644 api/main.py create mode 100644 api/requirements.txt rename {Discord-Bot-main/Bot => bot}/.env (100%) rename {Discord-Bot-main => bot}/Dockerfile (65%) rename {Discord-Bot-main/Bot => bot}/UR-Bot.py (91%) create mode 100644 bot/__pycache__/DebugBot.cpython-310.pyc create mode 100644 bot/__pycache__/event.cpython-310.pyc create mode 100644 bot/extends/base/__pycache__/base.cpython-311.pyc create mode 100644 bot/extends/base/__pycache__/event.cpython-310.pyc create mode 100644 bot/extends/base/__pycache__/event.cpython-311.pyc create mode 100644 bot/extends/base/__pycache__/event.cpython-38.pyc create mode 100644 bot/extends/base/__pycache__/tmp.cpython-310.pyc create mode 100644 bot/extends/base/__pycache__/tmp.cpython-311.pyc create mode 100644 bot/extends/base/__pycache__/tmp.cpython-38.pyc create mode 100644 bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc create mode 100644 bot/extends/base/base.py create mode 100644 bot/extends/base/credits.txt rename Discord-Bot-main/Bot/extends/base/tmp.py => bot/extends/base/tmp_not_used.py (100%) create mode 100644 bot/requirements.txt create mode 100644 docker-compose.yml delete mode 100755 install.sh delete mode 100755 src/Bot_Base.service delete mode 100755 src/bot/URbot.py delete mode 100755 src/bot/__init__.py delete mode 100644 src/bot/cog_About.py delete mode 100644 src/bot/cog_General.py delete mode 100755 src/bot/info/__init__.py delete mode 100755 src/bot/info/credits.txt delete mode 100755 src/bot/info/version.txt delete mode 100644 src/bot/settings.py delete mode 100644 src/bot/strings.py delete mode 100644 src/bot/templates/__init__.py delete mode 100644 src/bot/templates/credit_template.txt delete mode 100644 src/bot/templates/service_template.txt delete mode 100644 src/bot/templates/version_template.txt delete mode 100644 src/locale/_pot/bot_base.pot delete mode 100644 src/locale/en/LC_MESSAGES/bot_base.mo delete mode 100644 src/locale/en/LC_MESSAGES/bot_base.po delete mode 100644 src/locale/fr/LC_MESSAGES/bot_base.mo delete mode 100644 src/locale/fr/LC_MESSAGES/bot_base.po delete mode 100644 src/locale/special-rp/LC_MESSAGES/bot_base.mo delete mode 100644 src/locale/special-rp/LC_MESSAGES/bot_base.po delete mode 100644 src/start.py delete mode 100644 src/urpy/__init__.py delete mode 100644 src/urpy/get_ressources.py delete mode 100644 src/urpy/help.py delete mode 100644 src/urpy/localization.py delete mode 100644 src/urpy/my_commands.py delete mode 100644 src/urpy/requirements.txt delete mode 100644 src/urpy/templates/__init__.py delete mode 100644 src/urpy/templates/planning_annoucement_template.txt delete mode 100644 src/urpy/utils.py delete mode 100644 src/urpy/xml.py delete mode 100644 src/www/css/master.css delete mode 100644 src/www/img/ccby.png delete mode 100644 src/www/img/ur-bl2.png delete mode 100644 src/www/index.html delete mode 100755 start.sh delete mode 100644 updateBot.sh diff --git a/Discord-Bot-main/.dockerignore b/.dockerignore similarity index 66% rename from Discord-Bot-main/.dockerignore rename to .dockerignore index 00e212d..ff25d66 100644 --- a/Discord-Bot-main/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ .env # Bot/.env +extends diff --git a/.gitignore b/.gitignore index e307f0a..d3d90e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1 @@ -__pycache__ -src/bot/cogs/ - -src/bot/.idea/ - -src/urpy/.idea/ +test.yml \ No newline at end of file diff --git a/Discord-Bot-main/.gitignore b/Discord-Bot-main/.gitignore deleted file mode 100644 index d3d90e3..0000000 --- a/Discord-Bot-main/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test.yml \ No newline at end of file diff --git a/Discord-Bot-main/Bot/extends/base/event.py b/Discord-Bot-main/Bot/extends/base/event.py deleted file mode 100644 index ee2074f..0000000 --- a/Discord-Bot-main/Bot/extends/base/event.py +++ /dev/null @@ -1,24 +0,0 @@ -import asyncio -from discord.ext import commands - - -class Base(commands.Cog, name='base'): - def __init__(self, bot): - self.bot = bot - self._last_member = None - - async def cog_load(self): # called when the cog is loaded - print(self.__class__.__name__ + " is loaded") - - @commands.command(name="ping", help='ping pong with the bot', aliases=['pong'], ) - @commands.guild_only() - async def _ping(self, event): - await asyncio.gather( # concurent await - event.message.add_reaction('🏓'), - event.send('Pong! 🏓 {0} ms'.format( - round(self.bot.latency, 3) * 1000)) - ) - - -async def setup(bot): - await bot.add_cog(Base(bot)) diff --git a/Discord-Bot-main/Bot/requirements.txt b/Discord-Bot-main/Bot/requirements.txt deleted file mode 100644 index 43e9c5989c264730b21a1e2a7ab043c8d1e97b0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 372 zcmYk2(F%e<5Jm5E(5EP7QoZyoAu1)PD`}BGU!B=qL56|dow;Z3?C)EumD58rG3rVj zmFi3u6!0#3u@?<%CtzNUE4vQmr8bwM{d1 zm{dcbnB#np`B)-nOKH&$aMtOCpWNf9^ZtxdMgA%BbKhgU84nJ90>yD_IKMs1eB>Lq kGupai)zL4Q({t{I docker-compose-merge.yml` - > add `-f repo-y/docker-compose.yml` for each repo -- start command here - -```cmd - docker-compose -f .\docker-compose.yml -f ..\..\Bot_Presentation\bot_docker\docker-compose.yml config >test.yml - docker-compose -f test.yml up --build -d -``` - -## Commandes disponible dans le bot base - -- `$help` : show all command available -- `$ping` : show pong and time to respond ( test if bot is alive ) - - - -## dev - -### web - -pour travailler en local penser a faire mentir le dns de votre machine pour que le nom de domaine voulu pointe la ou il faut - -- windows : `C:\WINDOWS\system32\drivers\etc\hosts` -- linux : `/etc/hosts` \ No newline at end of file diff --git a/Discord-Bot-main/docker-compose.yml b/Discord-Bot-main/docker-compose.yml deleted file mode 100644 index 80f25c8..0000000 --- a/Discord-Bot-main/docker-compose.yml +++ /dev/null @@ -1,25 +0,0 @@ -version: '3' -name: uniondesroliste - -services: - bot_base: - build: . #utilise le dockerfile pour cree le container a utiliser - container_name: bot_base - restart: always #redemarre le container si il s'eteint - environment: - - DISCORD_TOKEN=---------------------------------------- #token pour comm avec discord - - BOT_PREFIX=$ #prefix pour les commandes - #- BOT_EXTEND=coucou, coucou2 #extensions a charger - volumes: - - ./Bot/extends/base:/app/extends/base #dossier ou est l'extension de la base - php: - image: php:8-apache - # environment: - # - APACHE_HTTP_PORT_NUMBER=80 - # volumes: - # - ./apache/default-httpd.conf:/etc/apache2/apache2.conf:ro # si besoin de modifier le fichier de conf apache (normalement pas besoin) - ports: - - "80:80" - restart: always - depends_on: - - bot_base diff --git a/README.md b/README.md index a2b4fe2..ab7c322 100755 --- a/README.md +++ b/README.md @@ -1,128 +1,44 @@ -[![forthebadge](https://forthebadge.com/images/badges/cc-nc-sa.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/made-with-markdown.svg)](https://forthebadge.com) ![](https://img.shields.io/badge/Made%20For-DISCORD-blue) -## Bot Python (UR-Bot) - -[TOCM] - -[TOC] - - -``` -├── Description du projet -├── Languages utilisés -├── Credit , participant, organisation -| -├── 1) Bot Base -│ ├── - But -│ ├── -Installation -│ └── - Usage -| -├── 2.1) Bot-Prez -│ ├── - But -│ ├── - Installation -│ └── - Usage -| -├── 2.2) WebPrez -| -├── 3.1) Bot-planning -| ├── - But -| ├── - Installation -| └── - Usage -| -├── 3.2) Web-Planning -├── - But -├── - Installation -└── - Usage -``` - - - -## Description -> Le BotPresentation(Python3.7) permet à un utilisateur d'accéder à un formulaire de présentation via la commande $prez. Les informations saisies sont ensuite mises en forme et postées sur le discord de l'union des Rôlistes via un Webhook dans la section #presentation - -## Languages utilisés - - Py ( Python ) - - XML ( eXtensible Markup Language ) - - HTML ( eXtensible Markup Language ) - - CSS ( eXtensible Markup Language ) - - sh ( Bash ) - - -Credits -> [credits.md](https://github.com/UnionRolistes/Bot_Base/blob/main/credits.md) - - -# 1) Bot Base - -###### https://github.com/UnionRolistes/Bot_Base - Bot_Base est un repo commun aux autres projet. -Il permet de simplifier l'installation d'un ou plusieurs éléments. - ### Installation - **Pour une 1ère installation** : ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" ``` - - **Pour une mise à jour** : ``` "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh" ``` - - How to setup URbot - The discord bot for managing servers dedicated to rpgs - --- - ##### - 1 ) Install a linux based OS (we'll be using Debian as a reference) - - ##### - 2 ) Install git - - ##### - 3 ) ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" ``` - - **It installs the bot and the 2 sub features** : - - 1. Bot_Planning and Web_Planning - 2. Bot_Presentation and Web_Presentation - - ##### - 4 ) If you want to choose the features to install --> ```"cd /usr/local/src/Bot_Base && sudo git pull && sudo bash install.sh" Then "sudo bash " ``` - - ##### - 5 ) Start the bot and the web sites with "cd/usr/local/src/Bot_Base && sudo bash start.sh && sudo service apache2 restart " - -# 2.1) Bot-Prez - - ###### https://github.com/UnionRolistes/Bot_Presentation - Le BotPresentation(Python3.7) permet à un utilisateur d'accéder à un formulaire de présentation via la commande $prez. Les informations saisies sont ensuite mises en forme et postées sur le discord de l'union des Rôlistes via un Webhook dans la section #presentation - - ![](https://github.com/UnionRolistes/Bot_Base/blob/main/img/BotPresentation_Grafcet-page-001.jpg?raw=true) - ![](https://github.com/UnionRolistes/Bot_Base/blob/main/img/BotPresentation_Grafcet-page-002.jpg?raw=true) - - ### Installation - - **Pour une 1ère installation** : ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" ``` - ---- - -- **Pour une mise à jour** : ``` "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh" ``` - -# 2.2) WebPrez - -# 3.1) Bot-planning - > - ###### https://github.com/UnionRolistes/Bot_Planning_python - - > - Le BotPlanning(Python3.7) et FormulaireJdR (HTML CSS PHP) est un projet lancé a l'initiative de l'Union des Rôlistes (**http://unionrolistes.fr**) un bot discord capable de générer des messages correctement mis en forme, annoncant de prochaine partie de JdR, quel soit physique ou a distance. actuellement les message finaux sont visible sur le discord de l'union des Rôlistes via un Webhook dans la section #Planning-JdR . - - ### Installation - - **Pour une 1ère installation** : "cd /usr/local/src && sudo git clone **https://github.com/UnionRolistes/Bot_Base** && cd Bot_Base && sudo bash updateBot.sh" - -**Pour une mise à jour** : "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh" - -**how to use (in discord)** une fois sur votre serveur discord, et apres avoir vérifier que le bot (ou role des bot) pouvais ecrire dans le canal où vous vous trouvez ecrivez $cal la commande s'effacera, puis vous receverez un message privé avec les instruction. - -# 3.2) Web-Planning - - ###### https://github.com/UnionRolistes/Web_Planning - #### Web_Planning : Formulaire de création de partie - Le formulaire permet a un animateur (MJ) de proposer une session de JdR sur un serveur discord. Via la commande $jdr, il recoit un lien vers un formulaire, avec diverses entrées. Une fois le formulaire completé et validé, celui ci est envoyé vers le discord, et l'annonce mise en forme est alors disponible sur le discord dans le canal #planning-jdr, les joueurs potentiels peuvent alors lire et réagir pour s'y inscrire. Ensuite le MJ peut les contacter pour les informations supplémentaires, telles que la creation de personnage. - - #### Web_Planning : Calendrier - Calendrier web affichant les parties prévues. Via la commande $cal l'utilisateur reçoit le lien du calendrier Ce calendrier affiche horizontalement les parties prévues, par semaine ou par mois. Cliquer sur un événement donnera accès à plus de détails sur la partie, ainsi qu'un lien vers le message discord pour pouvoir s'y inscrire. Les administrateurs ont accès à une section permettant de pré remplir le formulaire avec les données d'une partie déjà existante, afin de la dupliquer. Ils peuvent aussi afficher tous les détails d'une partie selon une mise en forme leur permettant de copier facilement le texte pour une exportation sur les réseaux sociaux - - ### Installation - **Pour une 1ère installation** : ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" sudo nano /var/www/html/Web_Planning/php/config.php.default" ``` --> Remplir ce fichier avec le Client ID, Secret ID et Redirect_URI du Bot, trouvable sur Discord developer (**https://discord.com/developers/applications**) - -``` sudo nano /etc/apache2/sites-available/100-UR_Planning.conf``` --> Remplacer "serverName planning.unionrolistes.fr" (ligne 9) par la redirection saisie sur votre hébergeur en ligne - -**Pour une mise à jour** : ``` "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh"``` - -# TODO -https://github.com/orgs/UnionRolistes/projects/1/views/1 +# Union des Roliste base : bot / api / web + [doc lib](https://discordpy.readthedocs.io/en/stable/api.html?highlight=on_message#discord.Guild.get_channel) + +## Instalation + +- Install [Docker](https://docs.docker.com/desktop/) (only need engine and compose) +- dowload repo + +## first use (or if you want to change the token) + +- [tuto](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) to get your token +- change the token in the .env file + +## use + +- start : `docker-compose up --build -d` (in terminal in path of the project) +- log : `docker-compose logs -f` (in terminal in path of the project) +- stop : `docker-compose down` (in terminal in path of the project) + +## use other repo to extend the bot + +- go to parent folder of the bot +- clone repo needed +- merge docker-compose with `docker-compose -f docker-compose.yml -f ../repo-x/docker-compose.yml config > docker-compose-merge.yml` + > add `-f repo-y/docker-compose.yml` for each repo +- start command here (dev for prod future script) + - `docker-compose -f .\docker-compose.yml -f ..\Bot_Presentation\docker-compose.yml -f ..\Bot_Planning_python\bot\docker-compose.yml config >test.yml` + - `docker-compose -f test.yml up --build -d` + +## Commandes disponible dans le bot base + +- `$help` : show all command available +- `$ping` : show pong and time to respond ( test if bot is alive ) + + + +## dev + +### web + +pour travailler en local penser a faire mentir le dns de votre machine pour que le nom de domaine voulu pointe la ou il faut + +- windows : `C:\WINDOWS\system32\drivers\etc\hosts` +- linux : `/etc/hosts` \ No newline at end of file diff --git a/README_old.md b/README_old.md new file mode 100644 index 0000000..f6efafb --- /dev/null +++ b/README_old.md @@ -0,0 +1,128 @@ +[![forthebadge](https://forthebadge.com/images/badges/cc-nc-sa.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/made-with-markdown.svg)](https://forthebadge.com) ![](https://img.shields.io/badge/Made%20For-DISCORD-blue) +## Bot Python (UR-Bot) + +[TOCM] + +[TOC] + + +``` +├── Description du projet +├── Languages utilisés +├── Credit , participant, organisation +| +├── 1) Bot Base +│ ├── - But +│ ├── -Installation +│ └── - Usage +| +├── 2.1) Bot-Prez +│ ├── - But +│ ├── - Installation +│ └── - Usage +| +├── 2.2) WebPrez +| +├── 3.1) Bot-planning +| ├── - But +| ├── - Installation +| └── - Usage +| +├── 3.2) Web-Planning +├── - But +├── - Installation +└── - Usage +``` + + + +## Description +> Le BotPresentation(Python3.7) permet à un utilisateur d'accéder à un formulaire de présentation via la commande $prez. Les informations saisies sont ensuite mises en forme et postées sur le discord de l'union des Rôlistes via un Webhook dans la section #presentation + +## Languages utilisés + - Py ( Python ) + - XML ( eXtensible Markup Language ) + - HTML ( eXtensible Markup Language ) + - CSS ( eXtensible Markup Language ) + - sh ( Bash ) + + +Credits -> [credits.md](https://github.com/UnionRolistes/Bot_Base/blob/main/credits.md) + + +# 1) Bot Base + +###### https://github.com/UnionRolistes/Bot_Base + Bot_Base est un repo commun aux autres projet. +Il permet de simplifier l'installation d'un ou plusieurs éléments. + ### Installation + **Pour une 1ère installation** : ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" ``` + + **Pour une mise à jour** : ``` "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh" ``` + + How to setup URbot - The discord bot for managing servers dedicated to rpgs + --- + ##### - 1 ) Install a linux based OS (we'll be using Debian as a reference) + + ##### - 2 ) Install git + + ##### - 3 ) ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" ``` + + **It installs the bot and the 2 sub features** : + + 1. Bot_Planning and Web_Planning + 2. Bot_Presentation and Web_Presentation + + ##### - 4 ) If you want to choose the features to install --> ```"cd /usr/local/src/Bot_Base && sudo git pull && sudo bash install.sh" Then "sudo bash " ``` + + ##### - 5 ) Start the bot and the web sites with "cd/usr/local/src/Bot_Base && sudo bash start.sh && sudo service apache2 restart " + +# 2.1) Bot-Prez + + ###### https://github.com/UnionRolistes/Bot_Presentation + Le BotPresentation(Python3.7) permet à un utilisateur d'accéder à un formulaire de présentation via la commande $prez. Les informations saisies sont ensuite mises en forme et postées sur le discord de l'union des Rôlistes via un Webhook dans la section #presentation + + ![](https://github.com/UnionRolistes/Bot_Base/blob/main/img/BotPresentation_Grafcet-page-001.jpg?raw=true) + ![](https://github.com/UnionRolistes/Bot_Base/blob/main/img/BotPresentation_Grafcet-page-002.jpg?raw=true) + + ### Installation + + **Pour une 1ère installation** : ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" ``` + +--- + +- **Pour une mise à jour** : ``` "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh" ``` + +# 2.2) WebPrez + +# 3.1) Bot-planning + > - ###### https://github.com/UnionRolistes/Bot_Planning_python + + > - Le BotPlanning(Python3.7) et FormulaireJdR (HTML CSS PHP) est un projet lancé a l'initiative de l'Union des Rôlistes (**http://unionrolistes.fr**) un bot discord capable de générer des messages correctement mis en forme, annoncant de prochaine partie de JdR, quel soit physique ou a distance. actuellement les message finaux sont visible sur le discord de l'union des Rôlistes via un Webhook dans la section #Planning-JdR . + + ### Installation + + **Pour une 1ère installation** : "cd /usr/local/src && sudo git clone **https://github.com/UnionRolistes/Bot_Base** && cd Bot_Base && sudo bash updateBot.sh" + +**Pour une mise à jour** : "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh" + +**how to use (in discord)** une fois sur votre serveur discord, et apres avoir vérifier que le bot (ou role des bot) pouvais ecrire dans le canal où vous vous trouvez ecrivez $cal la commande s'effacera, puis vous receverez un message privé avec les instruction. + +# 3.2) Web-Planning + + ###### https://github.com/UnionRolistes/Web_Planning + #### Web_Planning : Formulaire de création de partie + Le formulaire permet a un animateur (MJ) de proposer une session de JdR sur un serveur discord. Via la commande $jdr, il recoit un lien vers un formulaire, avec diverses entrées. Une fois le formulaire completé et validé, celui ci est envoyé vers le discord, et l'annonce mise en forme est alors disponible sur le discord dans le canal #planning-jdr, les joueurs potentiels peuvent alors lire et réagir pour s'y inscrire. Ensuite le MJ peut les contacter pour les informations supplémentaires, telles que la creation de personnage. + + #### Web_Planning : Calendrier + Calendrier web affichant les parties prévues. Via la commande $cal l'utilisateur reçoit le lien du calendrier Ce calendrier affiche horizontalement les parties prévues, par semaine ou par mois. Cliquer sur un événement donnera accès à plus de détails sur la partie, ainsi qu'un lien vers le message discord pour pouvoir s'y inscrire. Les administrateurs ont accès à une section permettant de pré remplir le formulaire avec les données d'une partie déjà existante, afin de la dupliquer. Ils peuvent aussi afficher tous les détails d'une partie selon une mise en forme leur permettant de copier facilement le texte pour une exportation sur les réseaux sociaux + + ### Installation + **Pour une 1ère installation** : ``` "cd /usr/local/src && sudo git clone https://github.com/UnionRolistes/Bot_Base && cd Bot_Base && sudo bash updateBot.sh" sudo nano /var/www/html/Web_Planning/php/config.php.default" ``` --> Remplir ce fichier avec le Client ID, Secret ID et Redirect_URI du Bot, trouvable sur Discord developer (**https://discord.com/developers/applications**) + +``` sudo nano /etc/apache2/sites-available/100-UR_Planning.conf``` --> Remplacer "serverName planning.unionrolistes.fr" (ligne 9) par la redirection saisie sur votre hébergeur en ligne + +**Pour une mise à jour** : ``` "cd /usr/local/src/Bot_Base && sudo git checkout . && sudo git pull && sudo bash updateBot.sh"``` + +# TODO +https://github.com/orgs/UnionRolistes/projects/1/views/1 diff --git a/add_repo.sh b/add_repo.sh deleted file mode 100644 index 5f00a4a..0000000 --- a/add_repo.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -# This script starts the discord bot of "L'Union des Rôlistes" - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -# TODO : Add commands verifications with $? (renvoie souvent 0 si tout se passe bien) -tmp='/usr/local/src' #'tmp/ur_bot/cogs' -ur_bot_dir='/usr/local/src/URbot' -cogs_folder="$ur_bot_dir/bot/cogs" -repo="$tmp/$1" -src="$repo/src" -a2=/etc/apache2 -www=/var/www/html - -# checks repo name has been supplied -if [ -z ${1+x} ]; then - echo 'error: argument not supplied' - echo - echo 'usage : ./add_repo.sh ' - exit -fi - -if [ ! -d "$repo" ] ; then - # downloads the repo from github.com - git clone "https://github.com/UnionRolistes/$1.git" "$repo" # TODO add several cogs at once - - # checks that the clone was successful - if [ $? != 0 ]; then - echo Failure - exit - fi -else - # updates the repo - cd "$repo" || exit - #git checkout . --> Dit qu'on est sur aucune branche. Problème, les branches n'ont pas toute une branche master (parfois main), donc on ne peut pas rajouter git pull origin master pour l'instant - git stash - git pull -fi - -# installs cogs (located in the src folder and starting with 'cog_') -echo ------------ TEXT: Installing cogs... ------------ -if [ ! -d $cogs_folder ] ; then - mkdir $cogs_folder -fi -find "$src" -maxdepth 1 -name "cog_*" -exec cp -vra '{}' $cogs_folder \; -echo - -# installs locale -if [ -d "$src/locale" ]; then - echo ------------ TEXT: Installing translations... ------------ - echo - cp -vra "$src/locale/." "$ur_bot_dir/locale" -fi - -echo ------------ TEXT: Installation complete. ------------ - -# installs www -if [ -d "$src/www" ]; then - if [ ! -d "$www/$1" ]; then - mkdir -v "$www/$1" - fi - cp -vra "$src/www/." "$www/$1" - chmod -R 775 "$www/$1" -fi - -# installs virtualhosts -#if [ -d "$src/sites-available" ]; then -# echo ------------ TEXT: Installing virtualhosts... ------------ -# cp -vra "$src/sites-available/." "$a2/sites-available" -# for f in "$src/sites-available/*.conf"; do -# ln -s $a2/sites-available/$f $a2/sites-enabled/$f -# a2ensite "$(basename "$f")" -# done -# systemctl reload apache2 -#fi - -# TODO bash 3 + -# TODO add installation of the cog's requirements.txt diff --git a/Discord-Bot-main/apache/ATTENTION!.txt b/apache/ATTENTION!.txt similarity index 100% rename from Discord-Bot-main/apache/ATTENTION!.txt rename to apache/ATTENTION!.txt diff --git a/apache/Dockerfile b/apache/Dockerfile new file mode 100644 index 0000000..aadf189 --- /dev/null +++ b/apache/Dockerfile @@ -0,0 +1,4 @@ +FROM php:8-apache + +RUN a2enmod proxy && \ + a2enmod proxy_http diff --git a/Discord-Bot-main/apache/default-httpd.conf b/apache/default-httpd.conf similarity index 100% rename from Discord-Bot-main/apache/default-httpd.conf rename to apache/default-httpd.conf diff --git a/apache/vhost/000_api.conf b/apache/vhost/000_api.conf new file mode 100644 index 0000000..b75a848 --- /dev/null +++ b/apache/vhost/000_api.conf @@ -0,0 +1,13 @@ + + ServerName api.unionrolistes.fr + + ProxyRequests Off + + Order deny,allow + Allow from all + + + ProxyPass http://api:3000/ + ProxyPassReverse http://api:3000/ + + \ No newline at end of file diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..a9b362d --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.11 +WORKDIR /app +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt +EXPOSE 80 +COPY / ./ +CMD find . -name 'requirement*' -type f -exec pip3 install -r '{}' ';' ; ls -la ; uvicorn main:app --host 0.0.0.0 --port 3000 diff --git a/api/extends/base/_discord.py b/api/extends/base/_discord.py new file mode 100644 index 0000000..35ab086 --- /dev/null +++ b/api/extends/base/_discord.py @@ -0,0 +1,32 @@ +import discord +import asyncio +from dotenv import load_dotenv +import os + +tmp_discord_client = None + +# start discord client (doit etre deplacer dans api_base) + + +async def discord_client(): + global tmp_discord_client + if tmp_discord_client: + return tmp_discord_client + intents = discord.Intents.default() + tmp_discord_client = discord.Client(intents=intents) + load_dotenv() + TOKEN = os.getenv("DISCORD_TOKEN") + await tmp_discord_client.start(TOKEN, reconnect=True) + await tmp_discord_client.wait_until_ready() + return tmp_discord_client + + +async def get_discord_client(): + global tmp_discord_client + if tmp_discord_client: + return tmp_discord_client + tmp_discord_client = await discord_client() + return await tmp_discord_client + + +# start discord client (doit etre deplacer dans api_base) diff --git a/api/extends/base/base.py b/api/extends/base/base.py new file mode 100644 index 0000000..ab6a15b --- /dev/null +++ b/api/extends/base/base.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") +async def root(): + return {"message": "Hello from modular API!"} diff --git a/api/main.py b/api/main.py new file mode 100644 index 0000000..2ca1586 --- /dev/null +++ b/api/main.py @@ -0,0 +1,42 @@ +import asyncio +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +import os +import importlib + +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"] +) + + +def load_all_extensions(): + for dir in os.listdir("./extends"): + # run pip install -r requirements.txt if exists + if os.path.exists("./extends/" + dir + "/requirements.txt"): + try: + print("pip install -r ./extends/" + dir + "/requirements.txt") + os.system("pip install -r ./extends/" + dir + + "/requirements.txt") + except: + print("pip install -r ./extends/" + dir + + "/requirements.txt failed") + for file in os.listdir("./extends/" + dir): + if file.endswith(".py") and not file.startswith("_"): + try: + print(dir + "/" + file) + # dynamic import + tmp = importlib.import_module("extends." + dir + "." + + file[:-3]) + app.include_router(tmp.router) + except Exception as e: + print("Failed to load import {0}.".format(file)) + print(e) + + +load_all_extensions() diff --git a/api/requirements.txt b/api/requirements.txt new file mode 100644 index 0000000..9d7d5b0 --- /dev/null +++ b/api/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.88.0 +uvicorn[standard]==0.20.0 +discord.py==2.1.0 +requests==2.28.1 \ No newline at end of file diff --git a/Discord-Bot-main/Bot/.env b/bot/.env similarity index 100% rename from Discord-Bot-main/Bot/.env rename to bot/.env diff --git a/Discord-Bot-main/Dockerfile b/bot/Dockerfile similarity index 65% rename from Discord-Bot-main/Dockerfile rename to bot/Dockerfile index a31a1fe..16af888 100644 --- a/Discord-Bot-main/Dockerfile +++ b/bot/Dockerfile @@ -1,6 +1,6 @@ -FROM python:3.8-alpine +FROM python:3.11 WORKDIR /app -COPY /Bot/requirements.txt requirements.txt +COPY /requirements.txt requirements.txt RUN pip3 install -r requirements.txt -COPY /Bot/ ./ +COPY ./ ./ CMD find . -name 'requirement*' -type f -exec pip3 install -r '{}' ';' ; ls -la ; python -u UR-Bot.py diff --git a/Discord-Bot-main/Bot/UR-Bot.py b/bot/UR-Bot.py similarity index 91% rename from Discord-Bot-main/Bot/UR-Bot.py rename to bot/UR-Bot.py index 9b78bc6..eb89437 100644 --- a/Discord-Bot-main/Bot/UR-Bot.py +++ b/bot/UR-Bot.py @@ -6,10 +6,12 @@ import os import asyncio -# load token from env +# load .env file load_dotenv() -TOKEN = os.getenv('DISCORD_TOKEN', None) -BOT_PREFIX = os.getenv('BOT_PREFIX', '$') + +# get token from .env file +TOKEN = os.getenv('DISCORD_TOKEN') # get token from .env file +BOT_PREFIX = os.getenv('BOT_PREFIX') # get prefix from .env file # exit if no token found if TOKEN is None: @@ -24,7 +26,6 @@ async def on_ready(self): async def on_message(self, message): if message.author == self.user: return - # await DebugBot.debug_on_message(message); return await bot.process_commands(message) @@ -36,6 +37,8 @@ async def on_message(self, message): bot = BOT_BASE(command_prefix=BOT_PREFIX, intents=intent) # build bot +groups = {} + # laod all extensions in the glob "./**/*.py" # like that to limit side effect between extension @@ -59,6 +62,7 @@ async def load_all_extensions(): print('Failed to load extension {0}.'.format(file)) print(e) + # if the file is run directly, run the bot if __name__ == '__main__': # load all extensions diff --git a/bot/__pycache__/DebugBot.cpython-310.pyc b/bot/__pycache__/DebugBot.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da27e871135f85cbf14242187f40bd5d5706eeeb GIT binary patch literal 1703 zcmb7E%}*0S6rY*hZMWMN5D60EM?fVuk;cTEA;e%!!d`JjPQcU;TtZ+jlsYew81={0^yKsQcgKLF!yMe!hP-h3)ew6 zo>mtQ=_dRC3LK0E`pClk3NtPP`9vj=^oUB_~R(bxWwiZcy&56BQ#gE-c@MYLoZaRL5C$)-KZ#O_Fw{{Z~EC+SR-7ew- zGCMr!aw~Atr$IsmP~HZJ`f*;?M%B z$2yP+s_UJFOFa(lqfK!Q30e@7NR;j3DHEus%onJh6^Q4BmxCTpp`5E{YDbQmtHeYEI;d9d|TmBVXTBt}d4p-fEM4xOfF+p5Y0 zK1?9iP{xwka8%aL9N8& z!Wct6gih7L7&_%hK{tKY&MdmbSOGsv+yvniJlHUJ`V;2y91w(Oi=J(=KHY#`F}xiQ zQej1*-SpzvX?Ow~H+`n6BNfLK$L_GlT6fVr`eFOCR4jmSVHDyP8oUhzn&1wwY8LFY z{IGGZtK!HNsx0B%MI`9)sy>9*xy%Qsltw=8UERlfFKAuj163Z5eB1}AluvT$A1(0f z=|3*(#Ji#EM=C=CK~}w~09N@`2@n{eKochr8pC9+q(7!r{CmbTKnl#H>NCu|Su{0M HD`@`!$&_4z literal 0 HcmV?d00001 diff --git a/bot/__pycache__/event.cpython-310.pyc b/bot/__pycache__/event.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18cbe1fcb8509a58f978c4be05999cb1ae7d4b51 GIT binary patch literal 3759 zcmcInOKcp+5uNFo{cyRYD4CK-Tc43FOWKu5E|=yv`j{WdQerWJB+Jf1!Fo8|yF1kE z%(`cWpTQE4VIy!3f`KHLz%nA{5P(4rxdg~H(ILm&2RS(>0YMT32oUI?&q$l&i(SI){hP;%Acw8@|Q*D9TfjhAY9?9qf|PzT2aww4r^)^ zP3?^763onMPTEKK(WD;g_J<#7{8j!MKSrariuQ4SqT4?CC{@Yu^V|^G8sn$<>z}16J^T%R z`ZJ}H<8SgasC)TYehzh>7kCkMA6IWHg}0tULuI+3#=VYb@+$WN;jYEGh40>}-n@0? z>h%wwsi!MxOA2lW;UIYDYc<u821Y-)~qIv^@ea-ab`_O-}c<_==y}IbR zHCr~nks}fIAWnbsLx?Ag&^4OE_szP9b;oq;(W%7-Dy%i}@76;>A~|Qnz;r;@!1^1wx+gc|lqsdT zX)$cO`=!v(P!>`AGay{uVr^wf`F?&&YpZSTQ*}#k>uq*lmji8mi+zkX+>I?u72G>Y zAtlK^aTeF*teDpQI92uSNJ!eb!m3gtE|r8l8(EyYw{+$1y#+6*E||W!w`BX4C;12l zMw+JW(#$=v2I)szoAKbgBDi60+z#YzY=F@fRYBu@PRrWwvK+Pt-tpthSX^_E_WUHnk|s*|xgPt^)wBA@I2Z-L&OU2mjxH_Qj83F3+tO z(s9Q0H(ks2;&j~%8bZdIT|dm5oL8mjICq>j!=T~GSceHXf-#k7^WqNVVeA}dJh$3{ zu~tboLNHPxhrj;kl45O-B4i$t=Z`4%o=ty zcuhEtcgnb7`-W+(lIPAD1wGDK9-Pe$;+!AAef@PiXkZXwK;|1WABqP#;uMv#)$DUJUspD@A3+KKntZ4_j2J|(ryuM z#Hqjz909!a98d1BSa;2)h_g1Fr3&UkCK@gl=UT$8t|0DPp6v#6hOz85bH!qG>>H+V ztlODAJ}14<<<(Z`HxL9^RxHkQ4g>gD92(GcE)b4rHSj!O7SqeW|Fv;vcZg1Sb*|<4 z!HTyrhuK9Bm`{Dvp}7)Hhh17BAQa1Qhpm<;1LNEWw#&VB-xxFAv)yn5^$pW1(2=Kb zWU+WCD>2x5>(Z*a#$H%#JDvGj|BVnk#}Aa&e~X^l|D6jQ}r1Q0>5-JVvtS!O(Kd&1&K+ zxsC+nEpZQFWX%>%cU8?pb|T|G+5zG8Krmejg=L7A`pmJ2<2ahV<(L=-3f90q`Cw)Bg@oSJ^ zklK*B2e~EXnL`N@CVkAl=;!z_<(vsJvejO+k9CnTzuMPk{P;d(Jh2@0zkdt4BLIU^ zK_nw*ab*Q2i>TyzkSP8Btr2iUeAneH?v8eS#B8-X*x2#HvGLN|rSkOT`1It|%=A=g zYGS%PHZj2`&6(+$vNg`9CTA>D2vbbX!Yjpexx7*u1*oO)B8dOrH-BW~Afxk&3fZzO3+q+$3eWdM7uPXm`KgtX9!f&He6|z2KP56>eVooVKHV2b5?S_(&HvdcpCK5&4(U=x`-_ zK2wcI8@Xh3QahIGiS!)Amt%Bj3FN9A2aQhbjdQ?R({#DtwZ*8U=Ri&pnIt!PE5Sb_P29e~|&%P9iogk!L zVs#@{twb4Ik5{W9hWFnL1twj>C)mq>9fUpTSNqigmRAQ6pabfVI;yhgS$%*Fux_X> zgRP)0LyIAjfK-XoogXoh?lxu~a*w*8Up0wR@;Z&`+_r*_Hk3FenMM-!k~Vxvup|pj zlCSKArpNJ%0`oGwr7ri|*e_I;-dV{sU_j0dxQW literal 0 HcmV?d00001 diff --git a/bot/extends/base/__pycache__/base.cpython-311.pyc b/bot/extends/base/__pycache__/base.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50584dfea0384e2f5292674c3eadb329eeb097ca GIT binary patch literal 5987 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 CAF?z6 literal 0 HcmV?d00001 diff --git a/bot/extends/base/__pycache__/event.cpython-38.pyc b/bot/extends/base/__pycache__/event.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e23a37343d1d6ee1dd93039dfca62eb8372439c GIT binary patch literal 1206 zcmZWoJ#Q015Z%4o^PL@A3Mfht64D{jxJjrG1&Sh40F8wd(Ok2-+-#h)?!)Y!0UJ{} zZA3vyfnDZz@Gq8H@mYpGw;ppt4^oIaQ)u+oP2W_`%atn)xgaG zig|RqJ}%ib|2+f#2;QSA=LHjpwc(n?CA&InYDH~6GOCGB4M5>Dr< zMYfCb6~%0!s<5g7A0Yl6#Ko~nMCJ4*tuQrVR>q*9>soK9)1tCXDHE*MNJ*)SI9F13 zxu)!Ou}c-;mgw(G`k#7kYI?2;yYg5uOpE?xn{+rVQN2y8)37>KAyR{0Ji^?9@>p@I zvZ3|kEJbYQ2H%vW#`GACip}{HG0k1z3$*3}oViyu5UD>1z@0fDw%7uF3+DwQe$H07 zz21mO{Q2eMr_n7mUSb9JL)wP#AASgPb4lE#KTvm@Sd1ALNm|-QKgOc9^>bxR+*h_8 z14ymnq)N+zdRl;OoRm6`E9>cUR6yNg>t`{xH5qrEnnUZ=oxM(_CyAjBW;Sh9%oV)F z1HSoJ{F$_tt)>gTZc~%rwxrBU7-iTT+m`b6D9)Cx(oJ-03Z$ta)?Yml+v<@QFJdkvc9$q?Ff6v7S-YXt;x()nw@h^m9YgMB5EWO;T#u#YeLQ>K?A*)%Vt- X)i{^)`ee!3KD`h_6CLojyM6l~E@=%6 literal 0 HcmV?d00001 diff --git a/bot/extends/base/__pycache__/tmp.cpython-310.pyc b/bot/extends/base/__pycache__/tmp.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7582047bcd8a40d8a94a6b5e139d4c32d95f067 GIT binary patch literal 4990 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 literal 0 HcmV?d00001 diff --git a/bot/extends/base/__pycache__/tmp.cpython-311.pyc b/bot/extends/base/__pycache__/tmp.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..babad194fa6cdf6e5c3b1ba35a6c8ef9e260efca GIT binary patch literal 6022 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 literal 0 HcmV?d00001 diff --git a/bot/extends/base/__pycache__/tmp.cpython-38.pyc b/bot/extends/base/__pycache__/tmp.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0670de89d0e6e67cbccc1da3424be8ff3d5e079 GIT binary patch literal 3675 zcmcInOK%j}6|P&aezrGa{R8p?A{JR@U&~}QQj|p^MG;bF@}257#)d3Y_I;iEI_I8q zzH|D1e}7fMxAn~{??3w#<*zh&_bX#?6(#-`Dz0!AD0a7HHp5sA)RtyztV`2v9W*U4 zTBdC>MQG0y)a+G>>)hB>?J_rciI-ofyuzzR+lPLQ_ZR&EzOQHp`Tn9E;s=U$_=V0t z;s=ZV2p=ulvEKO53ymM-Cn5Vv7r_w+1^6>~to&&6(Wv+|Uz2HaZ)IX@F}^ zxn6?9ED>5>l*%~Qc@&E5&|DNQ`LgxF6$P{Var^6EfjwcRq16&`>@JI358QA$J3hC9 z276unhs&uTmV#4h;s<{0CL)z}T~=^_By;NWHfa}}PC-;RC}g`MaDQTPu;}3-gtXk$ z!lVef^o6vwRW8%daKQC&E%FOC0v~d;AxH6c*pY_9YP&M=g{{ZnwXW^lD`CM(F|_rz z6j^%wf1DNL45%U&VIrg~VO2J?ttU@x%U(BZV|Fo06T5U%ghIN3UA_bHLqA-$Yj>oe z{VqLr);Awnb9jg4NIr#O7_OA=Q%}Xg+n<4shBAi|pG3vky4q2$DL)@w*E+1DJ!9*7 zNAIW)by@D{>*}L&M}_gJ^O9lpmQpt)X$VH=2En51JVxM>0h9pA4UH+?F=lthRX)ZK@xvq&X8X`Tvcb?-R%@&M9hD#5*sZu@^Vz`t zy8t)qv_)7Yr2qFn)?k&nk@!g<;OS8i$;`Ze_cT^zd?iWR@x^A-<6(pQu@^}WQ)~bz zo9>zm?Z(YB6K5wTr_Y_4K6id*`uxQCsp-k))D%DG&P>ltdT03gb2FYRge%Tnn7(j9 zOixZOPBb2i#rBtE?w6+~al0P6Es>Xf7><+5AR8DThr=)VPhe2^vi|Y$YPIsdqf7(&J2L8B#y|ve)&(+#K~x>MClsSXG0JAwg5?R&3ul9ammCMET^un!{+Hi<^Z)xFAHM-4{R9`~#v0&S zUEEfAPrr?2cyGFx#zOYoy3l^wJU@8``kb8kGQK=HF>!M3@>x=30+f=U;$A$Z22~Pz ziq@jw^?|(9v!L9#P87g{NE|m!Rw9{eaUvzTcV4-5{i{39wb^^Kxq*PtiVNhD6i4!k z%ek}Ui=el8DMIX)c|RQlE?bMC>j&}>mdeA#Ls=f7{!ywLsBE1M&)Jild4;B+`Dkeg z+xIHH)YEVayW$yC3L5~Ls*3u9sSW~yhSh^?kPR{Q2VI*0#ujtESrb%K0|jg4kFnRI z1K#}zd#|Fzw@?X%axYsYxI&hqagFO}bz!WU1!OVGvH&{FE)_InTgxi%+hCMy74$M6 z050##wR^4ht~f<*KxK|DD_ntf6bCSl8ka68TWlVB(-xy7uQ`DmCjeY%N0KsQ5iH3o z*tz$0%8uiQe&RU4q+`3NY_hC|+Z)?c?|CH2{F>HN8kG=8F@Q1~fn2?~qRwYk3&Dia zGFS{^L|giZ5f#Vrz*_7$Ic_GbAXk-Fan5_3dC{^%`+rS0k>pB`uk8ujC|q}o74GrD z$11p_@Cz>4vt1A>FN2J`Nvm6Uq6It=GNRPtK09^~*=X!Zzn#R^iW^&t0y$Rq4#67*PS({O zr;s;s+#YW8{_c9G6>(&pza_p4t!hIo+fSVDnxC&%eqAiudvos$ZZK*$Xlky{uJPRU2T%Os*xZw!DG)_iT%p zEwna@P6sHTz|5Z^lX?4EF!iqPRJ=G)1m;j23SEB4!VO?T+WtN9(lsJ^7yUY%Wk2@E zPead-ay3Hc(*5}$X_aiE%KLgS*B~e?q&JrZ{lqBnO-|7oo%>$W1vW}$3UkV}xgJl& zT_1X&ynLyvp(_;PV#*F R_WoerAFPy(@}YwV{tGquq!j=F literal 0 HcmV?d00001 diff --git a/bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc b/bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..724c4510fca24c8c9cd7f05c577826a66d45edec GIT binary patch literal 6031 zcmcH-TWlN0aqq>KNJ_FO$(CgK6!}HD*2|V%Hb%k(0^+nTEHnZ04{Lvym{UikfA`S>(v?(FRB?Ci`ezX}9=2-1U8Ci1U(g#Jc9%E?n|tnOwIx`QynjD{winweza zo7LDTH_0(hA3w=MAEyaX*QARRjj2{+kw=q`_&af{t<#hdMVOX<7qE{EH32i{71RR-Ff zcw4D0-W4XjxCeJBzG)V}a#NVB!`(O0q#thw7+}$9gm>ITc&Fl;3zm7ryB_Vn#{<_; zN?h0DFyJ>}W&(xxd`~+^!;Dp@#bhkun5pR3;L{U{MWeEg4a@i1*hOjl!l~Eaxcr>i zZn+{v!KxV#!Mu^_X*Dvdh?-)E5%SFg=r_Ww#YLjn;%60Y-f~}4h@r;x__lTEdOZ5g z0}?YttcWEXm?^+aso$vvU@2LTC|Y9kh_1&qSZ0?TF@cz<=wc)mCx*r2n64z+PQ>(S zl|)~NVKdtZtAFzmu&0P|U5qM*AyeTXrxbt}EiaOiUG6Gl17Hx3(^!Td+SZex4|ac-Kp489)P$!ccTmA;b$< ziB08miYVp1hXqR*nTo|t%Y9nW6(VbvXB@V#tNM)PA14Z(FH>PB>PBPY36R4~jNGsU znGpF#q;ds-JXI_X%IaYNaPgQ0Hi=H6cbXSC7A2V^cMmS)LnuyK;FElky<)2)VUzp< zyV#OsK^56iLdiZrVS%(m15{Afv?)1q#S$b#O(=w3af_9RAwWZ<>3LV5JU`#3TnDYi zM&FcdD1ByhUeaTx6gL#yJAcCpomR~A^7RRmOaPHL^bxuP@(PfUpcumdQmEh$q)rv; zLq)_1tu{z=g+O!q@~y~6?<~HPW()q{t>%x`cPIgF; zZdj(~BwWz(A(T}Scyvy}tdjm*rHvWPKH_Q(J7_SQshj{%sU+$I9=U3<8Dv(R1VY$t zmR%9d`V3*u=j(3(XWDMeA)29_Zb-GR2yW8@xHla3CVA}JQo#o5b)R#I0746#2@bKG z0L#CI_1~<312<9at`>ycq=*fa}e8%cHhhx!csKyV;Ncx4rhh11_4QUdc`w(1VRB$v*0+owddMN)$3rn zASD7lJwBiJ-FM%`>h!cP!3Fk+wi8Kk(EUBV<26QsUg;(%tFHl|rWrG9DuUUyb_T{b zrdl-xm&J{eeSl^=CWx8bk_7% z8qQE0(2X_#DfGDU@xYVNW93Qv%As@1htA~=jph%HrjTvr?8TllVuIUyOmzG^>+>df zA4nk~fIND7;xdrw>G|vJ)c-H{^t=w?$p9>53D+P7QY@FMYbr!zotJbPqg{wW_yqA* zIgTvpRd3>OS$9f`(mMnGCO!r3nv@)otw!;_1A_yg7=s6o8%GEG`*&VDx}R!<3C^Aj z!nz=34s-%4L5WT&I2^RxB~`S9Q?v&hnqo?F+?&al{fJ z%8D9xFhGXvF&SfNT2-{t;_a4raX8^4{h(E;3xo#r^GGh zy2V7Cc*QNnrl(=LK`kFuTACb(=`JY)c@u`BP>g?r(m3AoTYuBy`K;Lg*pu~-Rs1+(=>|KJ1Q{9}3l7))#m-TKMx{dW%k`tYYmZXYQk-b<-cm_AWx-MqAK>FV7B znH_hJuC(_rxA*7T2lMTNxz@o#L*p}!Y2B1QU38&90 zz}Kg8p+k?mAInd6{;B0jZ!UD^FD<#y7_6K=nLb(c+AH~x;7y;&@uAd75YUHXzZ!GI z(>cO|kj}DWwv<9tO5?Va#-X3KpE}q}o9~CdR#Ia*{z#rblI4$7#GGwAT&W@Sd0#lo zzcf3M4Cq27(U_8^)eivNK?fH15npTZu>js3Mjn3a9C*miu#Y_J=6N^F^I#tszB-G$7@eWXyC2A1u zL<7?Y%qZMuZYpM4eo2!J6GBJ0vxHu8L(!(mIG`GoX!*8yBuUj(Q<4&XSi86Um8N*D z03gC_YDx2poRKK!wpp$@9C|SJmiI&B1zTSC0@)ZLkTIAD z%u`GC0FGz^jtCyV=Jo0$a=ej;o(^S%K7Lo$=xworMKTFD+&c+D^(;j-8_Kb^D~RP;NBQc@h57Fd{^qHw|%C zHpD3f9_kKH@T9Z@j4-?A1jyS!x>^oaz(JOxF%17(BpRtiYPx^I_dt)-WwZ7!Kt6h81*XaqEFXuk6pqmgc zz?HQn2lvCaT2%K+E?xl7AD<^wF@Fc8>dXtiz$be?-t*hYQun8`w`X&{ZF%3eEdSE% z?A*SFjj*h7LyxF2i;cl!-1*av(Cv_80Ia%F9Mnr=CHv@IGYb7<&kleHjeg;;Gc{s5 zkI9pZ_54^P-nI_SK)|R zRFj{gx>VgS12+SyKq1hQzOeYSTgl8sE)dQK!YN-dh(aBOP)||dygtwa0MN6w5E`oL zY4i)OBBEfsjon){2H0(dQ2&OWT^o9at9rtlDMbp7pwO1u)qo=O{J09+S)-bxtxa@3 z1-;1I@U#ggRFfB6FcK<{?5-kZkG#cRU;+@SP?+)-=U5j5R|7zXx2yGF6U)%QL#m*! omFX#>3fSL332X%E-HSh3x@3<$qr*R};p}7>5$^8>U@PN)05^!9#sB~S literal 0 HcmV?d00001 diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py new file mode 100644 index 0000000..45b5f05 --- /dev/null +++ b/bot/extends/base/base.py @@ -0,0 +1,86 @@ +import sys +import asyncio +import importlib +import os +from discord.ext import commands +from dotenv import load_dotenv + +load_dotenv() +VERSION = os.getenv('BOT_BASE_VERSION') + +sys.path.append('..') + + +class Base(commands.Cog, name='base'): + def __init__(self, bot): + self.bot = bot + self._last_member = None + + async def cog_load(self): # called when the cog is loaded + print(self.__class__.__name__ + " is loaded") + + @commands.command(name="ping", help='ping pong with the bot', aliases=['pong', 'p'], ) + @commands.guild_only() + async def _ping(self, event): + await asyncio.gather( # concurent await + event.message.add_reaction('🏓'), + event.send('Pong! 🏓 {0} ms'.format( + round(self.bot.latency, 3) * 1000)) + ) + + @commands.command(name="credits", help='affiche les credits', aliases=['credit', 'c'], ) + async def _credits(self, event): + credits = "```properties\n" + # get directory path + pwd = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + # load local credits first + try: + with open(f'{pwd}/credits.txt', 'r') as f: + credits += f.read() + except Exception as e: + print(e) + # for each directory + for directory in os.listdir(pwd): + # read credits file + try: + with open(f'{pwd}/{directory}/credits.txt', 'r') as f: + credits += '\n' + f.read() + except Exception as e: + print(e) + await event.send(credits+'```') + + # send version of the bot + @commands.command(name="version", help='affiche la version du bot', aliases=['v'], ) + async def _version(self, event): + txt = f'URBot_base version : {VERSION}' + # get directory path + pwd = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + # for each directory + for directory in os.listdir(pwd): + # for each file in the directory + for file in os.listdir(f'{pwd}/{directory}'): + # if the file is a python file + if file.endswith('.py') and not file.startswith('__'): + # get the file name without the extension + file_name = file[:-3] + # try import the function version in the file + try: + print( + f'try run : extends.{directory}.{file_name}.version()') + module = importlib.import_module( + f'extends.{directory}.{file_name}').version() + try: + txt += f'\n{module}' + except Exception as e: + print(e) + except Exception as e: + print(e) + await event.send(txt) + + +async def setup(bot): + await bot.add_cog(Base(bot)) + + +# def version(): +# return f'URBot_base version : {VERSION}' diff --git a/bot/extends/base/credits.txt b/bot/extends/base/credits.txt new file mode 100644 index 0000000..0fead11 --- /dev/null +++ b/bot/extends/base/credits.txt @@ -0,0 +1,6 @@ +URBot_base Le bot discord de l'Union des Rôlistes + contributeurs : + - Mortifia#7249 + - Eὔδοξος#2170 + - Lyss#4053 + - Maestro#8364 diff --git a/Discord-Bot-main/Bot/extends/base/tmp.py b/bot/extends/base/tmp_not_used.py similarity index 100% rename from Discord-Bot-main/Bot/extends/base/tmp.py rename to bot/extends/base/tmp_not_used.py diff --git a/bot/requirements.txt b/bot/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..b5a3904fd59c736a2b232d751743a15d12644a2c GIT binary patch literal 86 zcmezWFNGnKp_n0=A)ld$A%#JYp@5;1!4?ROfNVn`Heldo-~x-5Fk}E# /dev/null - echo - exit -fi -if [ -e $install_folder/.bot_token ]; then - echo '------------ TEXT: Token déjà saisi ------------' - echo - exit -fi - -echo '------------ TEXT: Creation des fichiers de stockage webhook ------------' -echo -# Create 2 files for webhook storing -if [ ! -e $install_folder/wh ]; then - touch $install_folder/wh - chmod 776 $install_folder/wh - exit -fi -if [ ! -e $install_folder/whPrez ]; then - touch $install_folder/whPrez - chmod 776 $install_folder/wh - exit -fi - -echo '------------ TEXT: Installation du bot terminée ------------' -echo - -echo '------------ TEXT: Begin webserver installation ------------' -echo - -apt install -y apache2 -apt install -y php -apt install -y libapache2-mod-php # TODO check if necessary -apt install -y php-xml -apt install -y php-curl -a2enmod cgid -systemctl restart apache2 - -# TODO add user name diff --git a/src/Bot_Base.service b/src/Bot_Base.service deleted file mode 100755 index b5df668..0000000 --- a/src/Bot_Base.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Discord Bot for unionrolistes.fr -RequiresMountsFor=/usr/local/src/ -After=multi-user.target - -[Service] -Type=simple -ExecStart=/usr/local/src/URbot/start.py -Restart=always -RestartSec=5s -StandardOutput=journal -StandardError=inherit - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/src/bot/URbot.py b/src/bot/URbot.py deleted file mode 100755 index e87c3b7..0000000 --- a/src/bot/URbot.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/opt/virtualenv/URBot/bin/python - -import inspect -import logging -import os -import platform -import sys -import importlib -from importlib import resources - -from discord.ext import commands - -from urpy import MyHelpCommand -from urpy.utils import error_log, code_block, log -from bot import _, strings -from bot import localization - -import urpy - -debug = platform.system() == 'Windows' - -from bot import info -from bot import settings -from bot.cog_General import General -from bot.cog_About import About - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - - -class URBot(urpy.MyBot): - """ - Discord bot for "l'Union des Rôlistes". Contains global settings - and functions. - - Additional groups of functions can be added at runtime through - the .add_cog method. - """ - - def __init__(self): - """ - Creates an instance of URBot. Use the run method to start it. - """ - self.localization = localization # TODO move localization to urpy - super(URBot, self).__init__(settings.command_prefix, help_command=MyHelpCommand(self.localization)) - self.cog_general = General(self) - - # adds base cogs to the bot - self.add_cog(self.cog_general) - self.add_cog(About(self)) - - self.isDebugMode = debug - - @staticmethod - def get_credits(): - """ Return creditted people. """ - return resources.read_text(info, 'credits.txt') - - @staticmethod - def get_version(): - """ Return version number. """ - return resources.read_text(info, 'version.txt') - - @staticmethod - def get_name(): - """ Return title of the bot.""" - return _(strings.bot_title) - - async def on_ready(self): - """ Listener to the on_ready event. """ - error_log("We have logged in as {}!".format(self.user)) - - async def invoke(self, ctx: commands.Context): - self.localization.set_current_user(ctx.author.id) - await super(URBot, self).invoke(ctx) - - async def on_command_error(self, context, exception: commands.CommandError): - """ Listener to the on_command_error event. """ # TODO better docstring - if isinstance(exception, commands.UserInputError): - await context.send(_("{err_msg}\nIncorrect usage ☹ Check help of the command for more information.").format( - err_msg=code_block(exception))) - else: - raise exception - - def add_to_command(self, command: str, *callbacks): - """ - Add one or several callbacks to a cog_General discord command. - - @param command str - name of the command (ex: 'edit') - @callbacks callbacks iterator[functions] - callbacls - """ - self.cog_general.add_to_command(command, *callbacks) - - -if debug: - cogs_path = os.path.abspath('cogs') - token_path = '../../../bot_token' -else: - cogs_path = '/usr/local/src/URbot/bot/cogs' - token_path = '/usr/local/src/URbot/.bot_token' - - -def main(): - logging.basicConfig(level=logging.INFO) - ur_bot = URBot() - log("Loading cogs...") - # adds the "cogs" folder to the import path - sys.path.append(cogs_path) - - if os.path.exists(cogs_path): - # scans the "cogs" folder for cogs to add to the bot - for dir_entry in os.scandir(cogs_path): - dir_entry: os.DirEntry - - # imports and adds all found cogs - if dir_entry.is_dir() and dir_entry.name != '__pycache__' and not dir_entry.name.startswith('.'): - - # tries to import the cog module - try: - module = importlib.import_module(f"{dir_entry.name}.cog") - - except ValueError as e: # TODO fix error handling - error_log( - f"The package \'{dir_entry.name}\' does not contain a module named \'cog.py\' (in {dir_entry.path}).") - - else: - # retrieves all discord.Cog based classes - cog_classes = filter(lambda member: inspect.isclass(member[1]) and issubclass(member[1], commands.Cog), - inspect.getmembers(module)) - for Cog in cog_classes: - # adds cog to the bot - cog = Cog[1](ur_bot) - ur_bot.add_cog(cog) - log(f"Loaded : {cog.qualified_name}") - log("Done") - else: - log("No cogs found") - - # reads the bot token - with open(token_path) as f: - bot_token = f.read() - - # starts the bot - ur_bot.run(bot_token) - - -if __name__ == '__main__': - main() diff --git a/src/bot/__init__.py b/src/bot/__init__.py deleted file mode 100755 index a78b127..0000000 --- a/src/bot/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -import gettext - -from urpy import Localization - -import platform - -if platform.system() == 'Windows': - localedir = '../locale' -else: - localedir = '/usr/local/bin/URbot/locale' - -localization = Localization() -domain = 'bot_base' -# TODO automate adding all languages -localization.set_localedir(localedir) -localization.add_translation('bot_base', ['fr']) -localization.add_translation('bot_base', ['en']) -localization.add_translation('bot_base', ['special-rp', 'fr']) - - -_ = localization.gettext \ No newline at end of file diff --git a/src/bot/cog_About.py b/src/bot/cog_About.py deleted file mode 100644 index d7f6b77..0000000 --- a/src/bot/cog_About.py +++ /dev/null @@ -1,47 +0,0 @@ -from discord.ext import commands -from bot import templates -from bot import _, strings -import urpy -from urpy.utils import * - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -class About(commands.Cog): - """ This cog contains commands used to get general information about the bot. """ - __doc__ = strings.About_descr - - def __init__(self, owner: urpy.MyBot): - """ Creates an about cog. """ - super(About, self).__init__() - self.bot = owner - - @commands.command(brief=strings.version_brief, help=strings.version_help) - async def version(self, ctx: commands.Context): - """ Displays the version numbers. """ - await self.send_info_msg(ctx) - - @commands.command(brief=strings.credit_brief, help=strings.credit_help) - async def credit(self, ctx: commands.Context): - """ Displays the credits. """ - await self.send_info_msg(ctx, with_credits=True) - - async def send_info_msg(self, ctx, with_credits=False): - """ Sends a message containing the names, version numbers [and credits] of all the services. """ - # generates the descriptions for all the subservices of the bot - services = "\n\n".join( - formatted_template(templates, 'service_template.txt', - name=cog.get_name(), - version=cog.get_version(), - credits=cog.get_credits() if with_credits else "") - - for name, cog in self.bot.cogs.items() if isinstance(cog, urpy.MyCog) - ) - - # sends the message - await ctx.send(code_block(formatted_template(templates, 'version_template.txt', - version=self.bot.get_version(), - name=self.bot.get_name(), - services=services, - credits=self.bot.get_credits() if with_credits else ""))) diff --git a/src/bot/cog_General.py b/src/bot/cog_General.py deleted file mode 100644 index 1e684f6..0000000 --- a/src/bot/cog_General.py +++ /dev/null @@ -1,54 +0,0 @@ -from discord.ext import commands -import bot -from bot import localization, _, strings -import urpy - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - - -class General(commands.Cog): - __doc__ = strings.General_descr - - def __init__(self, URBot: urpy.MyBot): - self.bot = URBot - self.callbacks = { - 'edit': [], - 'done': [], - 'cancel': [] - } - - async def call_callbacks(self, command: str, ctx: commands.Context): - for callback in self.callbacks[command]: - await callback(ctx) - - @commands.command(brief=strings.edit_brief, help=strings.edit_help) - async def edit(self, ctx): - """ Call callbacks bound to the edit command. """ - await self.call_callbacks('edit', ctx) - - @commands.command(brief=strings.done_brief, help=strings.done_help) - async def done(self, ctx): - """ Call callbacks bound to the done command. """ - await self.call_callbacks('done', ctx) - - @commands.command(brief=strings.cancel_brief) - async def cancel(self, ctx): - """ Call callbacks bound to the cancel command. """ - await self.call_callbacks('cancel', ctx) - - @commands.command(brief=strings.lang_brief, help=strings.lang_help) - async def lang(self, ctx: commands.Context, language): - """ Switches to specified language """ - if language in localization.languages: - localization.set_user_language(language) - await ctx.send(_("Your language has successfully been set to english !")) - - else: - await ctx.send(_("Sorry, i don't know this language !")) - - def add_to_command(self, command: str, *callbacks): - """ Adds a callback to the specified command. It will be called on command invokation.""" - for callback in callbacks: - self.callbacks[command].append(callback) diff --git a/src/bot/info/__init__.py b/src/bot/info/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/src/bot/info/credits.txt b/src/bot/info/credits.txt deleted file mode 100755 index 2746973..0000000 --- a/src/bot/info/credits.txt +++ /dev/null @@ -1,2 +0,0 @@ -Lyss -Maestro#8364 \ No newline at end of file diff --git a/src/bot/info/version.txt b/src/bot/info/version.txt deleted file mode 100755 index a2268e2..0000000 --- a/src/bot/info/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.3.1 \ No newline at end of file diff --git a/src/bot/settings.py b/src/bot/settings.py deleted file mode 100644 index 7c9c702..0000000 --- a/src/bot/settings.py +++ /dev/null @@ -1 +0,0 @@ -command_prefix = '$' \ No newline at end of file diff --git a/src/bot/strings.py b/src/bot/strings.py deleted file mode 100644 index 3c2c804..0000000 --- a/src/bot/strings.py +++ /dev/null @@ -1,25 +0,0 @@ -from urpy.localization import lcl - -bot_title = lcl('URbot - The discord bot of "l\'Union des Rôlistes"') -lang_brief = lcl('Switches to specified language') -lang_help = lcl( - """\ -Switches to specified language - -Available languages : - - en - - fr - - special-rp\ -""") -done_brief = lcl('Confirms the current action') -done_help = lang_brief -edit_brief = lcl('Edits a message') -edit_help = edit_brief -cancel_brief = lcl('Cancels the current action') -cancel_help = cancel_brief -version_brief = lcl('Displays the version numbers') -version_help = version_brief -credit_brief = lcl('Displays the credits') -credit_help = credit_brief -About_descr = lcl('This category groups various commands to display general information about the bot.') -General_descr = lcl('This category groups various commands whose utility depends on the context.') diff --git a/src/bot/templates/__init__.py b/src/bot/templates/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/bot/templates/credit_template.txt b/src/bot/templates/credit_template.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/bot/templates/service_template.txt b/src/bot/templates/service_template.txt deleted file mode 100644 index a8b5d7c..0000000 --- a/src/bot/templates/service_template.txt +++ /dev/null @@ -1,4 +0,0 @@ - {name} - ------ - -> version : {version} - {credits} \ No newline at end of file diff --git a/src/bot/templates/version_template.txt b/src/bot/templates/version_template.txt deleted file mode 100644 index 0b29378..0000000 --- a/src/bot/templates/version_template.txt +++ /dev/null @@ -1,6 +0,0 @@ -{name} -====== - -> Version : {version} - {credits} - -{services} \ No newline at end of file diff --git a/src/locale/_pot/bot_base.pot b/src/locale/_pot/bot_base.pot deleted file mode 100644 index b19fa95..0000000 --- a/src/locale/_pot/bot_base.pot +++ /dev/null @@ -1,23 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2021-07-22 22:55+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=cp1252\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5\n" - - -#: bot\URbot.py:61 -msgid "bot_title" -msgstr "" - -#: bot\URbot.py:70 -msgid \ No newline at end of file diff --git a/src/locale/en/LC_MESSAGES/bot_base.mo b/src/locale/en/LC_MESSAGES/bot_base.mo deleted file mode 100644 index e4d70bf38beae8f9b522ecee29596f50e9bc8f38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 952 zcma)4-D(su6pq^ZGggG+l?onvV^y;5juy+5MYKOsw7Ar*h&LrWb9Of}n+!>&y3gRX zSH6OmK7inRh(3T%;Ei9p)2iTwfsZdaIr+{{&ez4c_X6u2a1|(k3&2Mpu5ZA3;5!iQ zKY-}_6IcM|Pt14`oZx(|;alM7zXDzc)`90ZmnVgI0{#q6!3(E^xD0*(z65>+z5=%3 zi{N+QnAZm|B#zg7vpWMm4d7bLH8x0#vp~#KJRI0M>=*2FCmyq5r^@Cuuwg&wP_oFC z$@$2fJ1+Fmi^3Xi-2bp1u=CoQ=A!59YtDn*F64{ZMMg>QncU?A9o^)H3Y<~ePCK%F zU#2Ogo$Oxcb~|milc&mzYBgfnXEnjQo$Z~?-!h$6J6X3TunDq1t)LS4ZMajFN}Dyx z$I5vQ-M#*nT>b6ml<`A$a?|9t&}Nj;>OhBNR(#+>KAL7!O-CG}SgV>+r?ry2l#PRR z8TD+emhK{audLBBxn, YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: \n" -"POT-Creation-Date: 2021-07-22 21:19+0200\n" -"PO-Revision-Date: 2021-07-22 21:41+0200\n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5\n" -"X-Generator: Poedit 3.0\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Last-Translator: \n" -"Language: en\n" - -#: bot\URbot.py:61 -msgid "bot_title" -msgstr "URbot - The discord bot of \"l'Union des Rôlistes\"" - -#: bot\cog_About.py:9 -msgid "About_descr" -msgstr "This category groups various commands to display general information about the bot." - -#: bot\cog_About.py:16 -msgid "version_descr" -msgstr "Displays the version numbers" - -#: bot\cog_About.py:21 -msgid "credit_descr" -msgstr "Displays the credits" - -#: bot\cog_General.py:6 -msgid "General_descr" -msgstr "This category groups various commands whose utility depends on the context." - -#: bot\cog_General.py:20 -msgid "edit_descr" -msgstr "Edits a message" - -#: bot\cog_General.py:25 -msgid "done_descr" -msgstr "Confirms the current action" - -#: bot\cog_General.py:30 -msgid "cancel_descr" -msgstr "Cancels the current action" diff --git a/src/locale/fr/LC_MESSAGES/bot_base.mo b/src/locale/fr/LC_MESSAGES/bot_base.mo deleted file mode 100644 index 175f5bdd5c200b492bb95679da41c4d6c28fa6ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2450 zcma)7TZWs|HV#dLqwRdw#CzWM9${@ViM zB|LB7xr65wJjb8F3*%ehYrtE;XMuNsuLFMoz6`tzWVw65L%@H4hk*z83Go*22=Gnd z67Vvx16&9G0=xh`^rR35_yw>HybF9E_%HAxu)SZ1U!n6R@Kt;-J|#o|{2a)BDIoiC z6LA?AzXYBD z!i{P?k2l+ZivnT-u2r#feqO|b(8K{eJfNl;FW?Oq#KHQrdOtUwb39^6npkC?ik_ml zbWWLqWL#)##FFjzrAd5*nMt+l?|JI1_IV};<2|PmU3hWd;twAZf|xD%Po12~7?> zVmk;Hx24WxCsR`qPf=`Z#Co zR8nV#y2>agGtwrtt}i*aBs;b&s-$;p(R@suJ3Z?am4(i9F`z`{3e_QKtBPk$p{}v6 zL9Xa}>&Er@8xwa3hN*9zs(fGxIoWCkpV<=OU@7yKirvDK826GM?={8d8X`c>Hqbzb zIJPb!Ua=`1%Nm=8qfJnywb756_J#7t1T4B~)~P2w`7(~t4QZKW1L`Pc$Sb7Bq7q$5 z^-%8X%DF3j-@Or>H!$K;3B6&k|x zNqDJl&xnH6Ps25}t+`plvj{MvcB?%fwoZoalhpb!YAw9iYPVX!iu6Ue;iU1IEUbg% z%D64V4JG@#z9UNAm2+#s#q$@JXNKmRtpHc5z+DTuS`l&O=5UR)IYn_#Iz)eZbK_jN zFgwTQQsu&BM4xEWjcB2xi{Rs`xeAqpX9f}FgRUy7HpmAw-~1rB9F7mLo2!;@C!K)# z)vQEIhUd_bJ|dG>pZ;|F6xGwyZyWlE=HEF54YgQ!MtYUYq8CxKIamE=dl%GT2Zi@5 zsBlDCoXxh@m2`1$6@p8c_FQnkiNFh-6xkGVK?=QD#X_0Tr_Jz0XStWn)>kM=9wqfZ2N>mdqe z8*4NO?J;?5sd5HHHX4G-G<(|5t%HMnUB^Vmia7xMEAZ+r3iH$*4GnT6*`ntgt+>2R z@sMJtDwcdu@oxFAN=;X7$^Sk`uLD;wo1`i9^BSs~T2uWkQ3ATExlT33vMHSU`T@FA zlrzJili_`XeXjf~t7=rNpqjxYrfZzrhPW)f*DwNIZmMgRnE%%{J;OYBrsBbsDhpF8 ztV+5E3yMn?)+U9Q)X|7>G(<7DQ~YSCs-SV}N8fY%>?XiX{s%yZFtzFl8P+_wm}Q7( V+iH}fAvLVC15>ttI5Sg5{12mX`6vJY diff --git a/src/locale/fr/LC_MESSAGES/bot_base.po b/src/locale/fr/LC_MESSAGES/bot_base.po deleted file mode 100644 index a9a8eba..0000000 --- a/src/locale/fr/LC_MESSAGES/bot_base.po +++ /dev/null @@ -1,173 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: \n" -"POT-Creation-Date: 2021-07-27 06:08+0200\n" -"PO-Revision-Date: 2021-07-27 06:08+0200\n" -"Last-Translator: \n" -"Language-Team: \n" -"Language: fr_FR\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5\n" -"X-Generator: Poedit 3.0\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Poedit-Basepath: ../../..\n" -"X-Poedit-KeywordsList: lcl\n" -"X-Poedit-SearchPath-0: bot\n" -"X-Poedit-SearchPath-1: urpy\n" -"X-Poedit-SearchPathExcluded-0: bot/cogs\n" - -#: bot/URbot.py:77 -#, python-brace-format -msgid "" -"{err_msg}\n" -"Incorrect usage ☹ Check help of the command for more information." -msgstr "" -"{err_msg}\n" -"Utilisation incorrecte ☹ Allez voir l'aide de la commande pour plus " -"d'information." - -#: bot/cog_General.py:42 -msgid "Your language has successfully been set to english !" -msgstr "Votre langue a bien été passée en français !" - -#: bot/cog_General.py:45 -msgid "Sorry, i don't know this language !" -msgstr "Désolé, je ne connais pas cette langue !" - -#: bot/strings.py:3 -msgid "URbot - The discord bot of \"l'Union des Rôlistes\"" -msgstr "URBot - Le bot discord de l'Union des Rôlistes" - -#: bot/strings.py:4 -msgid "Switches to specified language" -msgstr "Change la langue de l'utilisateur" - -#: bot/strings.py:6 -msgid "" -"Switches to specified language\n" -"\n" -"Available languages :\n" -" - en\n" -" - fr\n" -" - special-rp" -msgstr "" -"Change la langue de l'utilisateur\n" -"\n" -"Langues disponibles :\n" -" - en\n" -" - fr\n" -" - special-rp" - -#: bot/strings.py:14 -msgid "Confirms the current action" -msgstr "Confirme l'action en cours" - -#: bot/strings.py:16 -msgid "Edits a message" -msgstr "Édite un message" - -#: bot/strings.py:18 -msgid "Cancels the current action" -msgstr "Annule l'action en cours" - -#: bot/strings.py:20 -msgid "Displays the version numbers" -msgstr "Affiche les numéros de version" - -#: bot/strings.py:22 -msgid "Displays the credits" -msgstr "Affiche les crédits" - -#: bot/strings.py:24 -msgid "" -"This category groups various commands to display general information about " -"the bot." -msgstr "" -"Cette catégorie comporte des commandes donnant des informations générales " -"sur le bot." - -#: bot/strings.py:25 -msgid "" -"This category groups various commands whose utility depends on the context." -msgstr "" -"Cette catégorie regroupe des commandes générales qui jouent un rôle " -"différent en fonction du contexte." - -#: urpy/help.py:12 -msgid "Shows this message" -msgstr "Affiche ce message" - -#: urpy/help.py:13 -msgid "Commands:" -msgstr "Commandes:" - -#: urpy/help.py:14 -msgid "No Category" -msgstr "" - -#: urpy/help.py:46 -#, python-brace-format -msgid "" -"Type {0}{1} command for more info on a command.\n" -"You can also type {0}{1} category for more info on a category." -msgstr "" -"Entrez {0}{1} commande pour plus d'info sur une commande.\n" -"Vous pouvez aussi entrer {0}{1} catégorie pour plus d'info sur une catégorie." - -#~ msgid "Error: Webhook not supplied." -#~ msgstr "Erreur : Webhook non fourni." - -#~ msgid "Other" -#~ msgstr "Sans Catégorie" - -#~ msgid "version_descr" -#~ msgstr "Affiche les numéros de version" - -#~ msgid "credit_descr" -#~ msgstr "Affiche les crédits" - -#~ msgid "About_descr" -#~ msgstr "" -#~ "Cette catégorie comporte des commandes donnant des informations générales " -#~ "sur le bot." - -#~ msgid "General_descr" -#~ msgstr "" -#~ "Cette catégorie regroupe des commandes générales qui jouent un rôle " -#~ "différent en fonction du contexte." - -#~ msgid "bot_title" -#~ msgstr "URBot - Le bot discord de l'Union des Rôlistes" - -#~ msgid "edit_descr" -#~ msgstr "Édite un message" - -#~ msgid "done_descr" -#~ msgstr "Confirme l'action en cours" - -#~ msgid "cancel_descr" -#~ msgstr "Annule l'action en cours" - -#, fuzzy -#~| msgid "credit_descr" -#~ msgid "credits.txt" -#~ msgstr "Affiche les crédits" - -#, fuzzy -#~| msgid "cancel_descr" -#~ msgid "cancel" -#~ msgstr "Annule l'action en cours" - -#, fuzzy -#~| msgid "Switches to specified language" -#~ msgid " Switches to specified language " -#~ msgstr "Change la langue de l'utilisateur" - -#~ msgid "Incorrect usage. Check 'help lang' for more information." -#~ msgstr "Utilisation incorrecte ☹ Allez voir l'aide en tapant '$help lang'." diff --git a/src/locale/special-rp/LC_MESSAGES/bot_base.mo b/src/locale/special-rp/LC_MESSAGES/bot_base.mo deleted file mode 100644 index 47084991bc9317971e4fd002e35c6231a3795671..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1663 zcmZvcyKh`Y6o)4eUJDW;0wM9zu_z3R?|RoEB3xq-egs+A!o((Ws%ZA!b9aZ#ow>|o z&Bi!_4pD=IXs9TRM2kdiiGq$EQRFX3Ma^$^H(3Yy%E#Z%Tc`|ggmF#&d1p`9;%dpY#r9-wA5MZW}UaQrAySgoOj;2*jegyT3gbGY|lyKwG7rl zb88yvY>#DqYMfK$THBl38?#%w?M@3d5>PT(ihE9hzX*${3uTt67)S@#S3cdlnq8Ug zOLtZ5vJJRiD$~#DN>7E>e)b_7b`v9ZY^o=^g!A{a$sk*mo*NkkIknr159P)uE;t$^=z_f zdiSZNZcfoPqn5bF0l7Dfggd0zQ-kCq+l^*rs609L(mAV;9yX=)`tiFOWfT(K`IF`sA4AXblc1sc{(0nr9!- zPK7llq6DoAQt4!KI621lCQXdrkURftMy5NhRcJ9e3r`>1*``k-0w7b6&^}Bo2|5+#(pLCES9YbI21dT#JO(7m62`R!DPts*5-Tjv*2_N&f6|wTX4HXi!=8Z0WRe>4YHS}l{ye7W-%fc&b*03% F#eaq#{&WBU diff --git a/src/locale/special-rp/LC_MESSAGES/bot_base.po b/src/locale/special-rp/LC_MESSAGES/bot_base.po deleted file mode 100644 index faeec26..0000000 --- a/src/locale/special-rp/LC_MESSAGES/bot_base.po +++ /dev/null @@ -1,96 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: \n" -"POT-Creation-Date: 2021-07-23 04:21+0200\n" -"PO-Revision-Date: 2021-07-23 05:09+0200\n" -"Last-Translator: \n" -"Language-Team: \n" -"Language: fr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.0\n" -"X-Poedit-Basepath: ../../..\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Poedit-KeywordsList: lcl\n" -"X-Poedit-SearchPath-0: bot\n" -"X-Poedit-SearchPath-1: urpy\n" - -#: bot/URbot.py:81 -#, python-brace-format -msgid "" -"{err_msg}\n" -"Incorrect usage ☹ Check help of the command for more information." -msgstr "" -"{err_msg}\n" -"Ben alors, on ne sait plus comment utiliser cette commande Voyageur ? ☹ " -"Consultez votre manuel si vous requérez assistance." - -# 🪶 is a feather. -#: bot/cog_General.py:42 -msgid "Your language has successfully been set to english !" -msgstr "Bienvenue dans le chemin de Traverse ! 🪶" - -#: bot/cog_General.py:45 -msgid "Sorry, i don't know this language !" -msgstr "Mmm, non. Désolé ! Ce dialecte m'est inconnu." - -#: bot/strings.py:3 -msgid "URbot - The discord bot of \"l'Union des Rôlistes\"" -msgstr "" - -#: bot/strings.py:4 -msgid "Switches to specified language" -msgstr "Portail de téléportation" - -#: bot/strings.py:6 -msgid "Confirms the current action" -msgstr "Prends une décision avec assurance" - -#: bot/strings.py:8 -msgid "Edits a message" -msgstr "Direction le scribble" - -#: bot/strings.py:10 -msgid "Cancels the current action" -msgstr "Retourne sur ses pas en plein désarroi" - -#: bot/strings.py:12 -msgid "Displays the version numbers" -msgstr "Affiche les dates de publication des différentes sections" - -#: bot/strings.py:14 -msgid "Displays the credits" -msgstr "Affiche les auteurs de ce manuel" - -#: bot/strings.py:16 -msgid "" -"This category groups various commands to display general information about " -"the bot." -msgstr "" - -#: bot/strings.py:17 -msgid "" -"This category groups various commands whose utility depends on the context." -msgstr "" - -#: urpy/classes/help.py:12 -msgid "Shows this message" -msgstr "Affiche le manuel du Voyageur" - -#: urpy/classes/help.py:13 -msgid "Commands:" -msgstr "" - -#: urpy/classes/help.py:14 -msgid "No Category" -msgstr "" - -#: urpy/classes/help.py:46 -#, python-brace-format -msgid "" -"Type {0}{1} command for more info on a command.\n" -"You can also type {0}{1} category for more info on a category." -msgstr "" -"~ {0}{1} commande ~\n" -"Bonne route Voyageur. Que la déesse veille sur vous." diff --git a/src/start.py b/src/start.py deleted file mode 100644 index b303b77..0000000 --- a/src/start.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/opt/virtualenv/URBot/bin/python -from bot.URbot import main - -if __name__ == '__main__': - main() diff --git a/src/urpy/__init__.py b/src/urpy/__init__.py deleted file mode 100644 index 6c3e23b..0000000 --- a/src/urpy/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from urpy.localization import Localization, lcl -from urpy.help import MyHelpCommand -from urpy.my_commands import MyBot, MyCog, MyContext -from urpy.get_ressources import * - -base_localization = Localization('../locale') # TODO path for linux -_ = base_localization.gettext diff --git a/src/urpy/get_ressources.py b/src/urpy/get_ressources.py deleted file mode 100644 index 82d18be..0000000 --- a/src/urpy/get_ressources.py +++ /dev/null @@ -1,4 +0,0 @@ -from importlib import resources - -def get_planning_anncmnt_mdl() -> str: - return resources.read_text('urpy.templates', "planning_annoucement_template.txt") diff --git a/src/urpy/help.py b/src/urpy/help.py deleted file mode 100644 index 4f17ef1..0000000 --- a/src/urpy/help.py +++ /dev/null @@ -1,82 +0,0 @@ -import discord -from discord.ext import commands - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -from urpy.localization import lcl - -_ = lcl - - -class MyHelpCommand(commands.DefaultHelpCommand): - def __init__(self, localization, **options): - super().__init__( - help=lcl('Shows this message'), - commands_heading=lcl('Commands:'), - no_category=lcl('No Category'), **options) # TODO 'No Category' in french - global _ - # This check is necessary cause HelpCommand is deep copied every time in discord api. """ - if _ is lcl: - _ = localization.gettext - - def add_command_formatting(self, command): - """A utility function to format the non-indented block of commands and groups. - - Parameters - ------------ - command: :class:`Command` - The command to format. - """ - - if command.description: - self.paginator.add_line(_(command.description), empty=True) - - signature = self.get_command_signature(command) - self.paginator.add_line(signature, empty=True) - domain = getattr(command.cog, 'domain', None) - if command.help: - try: - self.paginator.add_line(_(command.help, domain), empty=True) - except RuntimeError: - for line in command.help.splitlines(): - self.paginator.add_line(line) # TODO localization here - self.paginator.add_line() - - def get_ending_note(self): - """:class:`str`: Returns help command's ending note. This is mainly useful to override for i18n purposes.""" - command_name = self.invoked_with - return _("Type {0}{1} command for more info on a command.\n" - "You can also type {0}{1} category for more info on a category.").format(self.clean_prefix, - command_name) - - def add_indented_commands(self, commands, *, heading, max_size=None): - if not commands: - return - if heading.startswith('\u200b'): - heading = _(heading[1:-1]) + ':' - self.paginator.add_line(_(heading[:-1]) + heading[-1]) - max_size = max_size or self.get_max_size(commands) - - get_width = discord.utils._string_width - for command in commands: - domain = getattr(command.cog, 'domain', None) - name = command.name - width = max_size - (get_width(name) - len(name)) - entry = '{0}{1:<{width}} {2}'.format(self.indent * ' ', name, _(command.short_doc, domain), width=width) - self.paginator.add_line(self.shorten_text(entry)) - - async def send_cog_help(self, cog): - domain = getattr(cog, 'domain', None) - if cog.description: - self.paginator.add_line(_(cog.description, domain), empty=True) - filtered = await self.filter_commands(cog.get_commands(), sort=self.sort_commands) - self.add_indented_commands(filtered, heading=_(self.commands_heading, domain)) - - note = self.get_ending_note() - if note: - self.paginator.add_line() - self.paginator.add_line(note) - - await self.send_pages() diff --git a/src/urpy/localization.py b/src/urpy/localization.py deleted file mode 100644 index 424fdab..0000000 --- a/src/urpy/localization.py +++ /dev/null @@ -1,84 +0,0 @@ -import gettext - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -def lcl(s: str, domain=None): - """ Marks a string for localization. """ - return s - - -class Localization: # TODO default localedir - def __init__(self, localedir='', default_language='fr', default_domain='bot_base'): - self._localedir = localedir - self.languages = {} # TODO make languages a class - self._default_language = default_language - self.users_custom_language = {} - self._current_user_id = '' - self._default_domain = default_domain - self._current_domain = None - # TODO add handling of several domains (context manager) - # TODO autoload - - def add_translation(self, domain, codes, *aliases): # TODO make aliases - language = gettext.translation(domain, self._localedir, codes, fallback=True) - if codes[0] not in self.languages: - self.languages[codes[0]] = {} - if 'en' not in self.languages: - self.languages['en'] = {} - - self.languages[codes[0]][domain] = language.gettext - if domain not in self.languages['en']: - self.add_translation(domain, ['en']) - - def set_default_language(self, lang_code): - if lang_code in self.languages: - self._default_language = lang_code - return True - else: - return False - - def set_user_language(self, lang_code): - if lang_code in self.languages: - if lang_code == self._default_language: - if self._current_user_id in self.users_custom_language: - del self.users_custom_language[self._current_user_id] - else: - self.users_custom_language[self._current_user_id] = lang_code - return True - else: - return False - - def set_current_user(self, user_id): - self._current_user_id = user_id - - def set_current_domain(self, domain): - self._current_domain = domain - - def gettext(self, s, domain=None): - if not s: - return "" - - if domain is None: - if self._current_domain is not None: - domain = self._current_domain - else: - domain = self._default_domain - - if self._current_user_id in self.users_custom_language: - lang = self.users_custom_language[self._current_user_id] - else: - lang = self._default_language - # TODO automatize fallback based on folder name - if lang in self.languages: - lang = self._default_language if domain not in self.languages[lang] else lang # TODO optimize all this - if domain in self.languages[lang]: - return self.languages[lang][domain](s) - else: - return s - else: - return s - - def set_localedir(self, param): - self._localedir = param diff --git a/src/urpy/my_commands.py b/src/urpy/my_commands.py deleted file mode 100644 index 586f9be..0000000 --- a/src/urpy/my_commands.py +++ /dev/null @@ -1,120 +0,0 @@ -from functools import partial -from types import MethodType - -import discord -from discord.ext import commands -from importlib import resources -from abc import ABC, abstractmethod, ABCMeta - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -class VersionNCreditInterface(ABC): - @staticmethod - @abstractmethod - def get_credits(): - """ Return credits. """ - pass - - @staticmethod - @abstractmethod - def get_version() -> str: - """ Return version number.""" - pass - - @staticmethod - @abstractmethod - def get_name() -> str: - """ Return name.""" - pass - - -class MyBot(commands.Bot, VersionNCreditInterface): - @abstractmethod - def __init__(self, command_prefix='$', *args, **kwargs): - super(MyBot, self).__init__(command_prefix=command_prefix, *args, **kwargs) - -class MyCogMeta(type(commands.Cog), VersionNCreditInterface, ABCMeta): - pass - -# TODO: shaky code, needs to be cleaned -class MyCog(commands.Cog, VersionNCreditInterface, metaclass=MyCogMeta): - def __init__(self, bot, domain): - self.bot = bot - self.domain = domain - - @commands.Cog.listener() - async def on_ready(self): - print(f"\t| {self.qualified_name} started.") - -# TODO: à améliorer -from functools import wraps - - -# def add_to_comm(name): -# def decorator(method): -# @wraps(method) -# def _impl(self, *method_args, **method_kwargs): -# print(str(method)) -# return _impl -# -# return decorator - - - # args[0].bot.add_to_command(name, partial(func, args[0])) -# -# class Test: -# def __init__(self, name): -# self.name = name -# -# @add_to_comm("non") -# def test(self): -# print("doggo") -# -# @add_to_comm("yes") -# def toost(self): -# print("cat") -# -# t = Test("Alpha") -# # t.test() -# # t.toost() - -class MyContext(commands.Context): - def __init__(self, ctx: commands.Context, delete_after=None): - """ - Creates a Context whose messages sent are deleted by default. - - @author Lyss - @mail - @date 28/06/21 - - Parameters - ---------- - ctx : context to copy - """ - super().__init__(**ctx.__dict__) - self.delete_after = delete_after - - async def send(self, content=None, **kwargs): - """ - Override Context.send to delete messages by default. - - @author Lyss - @mail - @date 28/06/21 - - Parameters - ---------- - content - kwargs - - Returns - ------- - - """ - if 'delete_after' not in kwargs: - kwargs['delete_after'] = self.delete_after - - await self.message.delete(delay=kwargs['delete_after']) - await super().send(content, **kwargs) diff --git a/src/urpy/requirements.txt b/src/urpy/requirements.txt deleted file mode 100644 index 9826c61..0000000 --- a/src/urpy/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -discord -requests -lxml \ No newline at end of file diff --git a/src/urpy/templates/__init__.py b/src/urpy/templates/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/urpy/templates/planning_annoucement_template.txt b/src/urpy/templates/planning_annoucement_template.txt deleted file mode 100644 index 106fdfe..0000000 --- a/src/urpy/templates/planning_annoucement_template.txt +++ /dev/null @@ -1,14 +0,0 @@ - ** Nouveau jeu de rôle ! ** - - **Type ** {type} -:calendar: ** Date ** {date} -:clapper: **Titre** {title} -:timer: ** Durée moyenne du scénario ** {length} -:crown: **MJ** {pseudoMJ} -:d10: ** Système** {system} -:baby::skin-tone-1: **PJ Mineur ** {minors_allowed} -:star2: ** Plateformes ** {platforms} -:grey_question: ** Détails** -{details} - -** Participe ** ✅ / ** Ne participe pas ❌ ** \ No newline at end of file diff --git a/src/urpy/utils.py b/src/urpy/utils.py deleted file mode 100644 index 5ddc76d..0000000 --- a/src/urpy/utils.py +++ /dev/null @@ -1,121 +0,0 @@ -import re -import sys -import requests -import discord -from importlib import resources -from urpy import _ - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -def error_log(*msg, name="BOT"): - print(f"{name}:ERROR|", *msg, file=sys.stderr) - - -async def get_public_ip() -> str: - """ - Return public IP. - - @author Lyss - @mail - @date 28/06/21 - - - Returns - ------- - str - IP address. - """ - return requests.get('https://api.ipify.org').text - -async def get_informations(msg: discord.Message): - """ - Extract information from an announcement message. - - @author Lyss - @mail - @date 28/06/21 - - Parameters - ---------- - msg : str - Announcement message. - - Returns - ------- - dict[str, str] - A dict linking an information name to its value. - Ex: " ** Type ** One Shoot " becomes {'type': 'One Shoot'} - """ - infos = {} - - # Matches strings of the form : ' ** {name} ** {value} ' ending on ':', '**' or '\n' - for match in re.finditer("\*\*(.*)\*\* *(?:\n| )(.*)\n(?::|\*\*|\n)", msg.embeds[0].description): - infos[match.group(1).strip().lower()] = match.group(2) - return infos - - -def code_block(s): - return f"```{s}```" - - -def edit_fmt(s): - return f"\|~ {s} ~|" - - -def code_line(s: str): - return f"`{s}`" - - -def formatted_template(template_pckg, template_name, **kwargs): - res = "" - size_last_line = 0 - with resources.open_text(template_pckg, template_name) as f: - for line in f: - # regex match to analyze the line -> the goal is to identify chars that indicate the position of - # an underline such as === and --- (the ---- form doesn't accept anything after it, otherwise - # it is not considered as an underline) - match = re.match('([ \t]*)(?:(-+)([ \t\n]*$)|(=*)((?:({.*})|.)*))', line, flags=re.DOTALL | re.MULTILINE) - new_line = match.group(1) - - # case of a --- underline - if match.group(2): - new_line += match.group(2)[0] * size_last_line - new_line += match.group(3) - else: - # case of a === underline - if match.group(4): - new_line += match.group(4)[0] * size_last_line - # formats text that doesn't symbolize an underline - new_line += match.group(5).format(**kwargs) - - size_last_line = len(new_line.strip()) - - if size_last_line or (not size_last_line and not match.group(6)): - res += new_line - - if res.endswith('\n'): - return res[:-1] - else: - return res - - -def log(*msgs, name='BOT'): - print(f"{name}|", *msgs, file=sys.stderr) - - -def html_header_content_type(*msgs): - print("Content-Type: text/html") - print() - print(*msgs, sep='\n') - - -def html_header_relocate(dest: str): - print("Status: 303 See other") - print(f"Location: {dest}") - print() - - -def html_header_webhook_not_supplied(): - html_header_content_type('Error: Webhook not supplied.') diff --git a/src/urpy/xml.py b/src/urpy/xml.py deleted file mode 100644 index 10d0e6d..0000000 --- a/src/urpy/xml.py +++ /dev/null @@ -1,175 +0,0 @@ -import pickle -from pathlib import Path - -import discord -from discord import Webhook, AsyncWebhookAdapter -from aiohttp import ClientSession -from lxml import etree as et -import cgi -from urpy.utils import error_log, log -from urpy.localization import lcl, Localization -import re -from datetime import datetime - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - - -game_tag = 'partie' -title_tag = 'titre' -max_players_tag = 'capacite' -min_players_tag = 'minimum' -nb_joined_tag = 'inscrits' -date_tag = 'date' -time_tag = 'heure' -length_tag = 'duree' -type_tag = 'type' -mj_tag = 'mj' -system_tag = 'systeme' -minors_allowed_tag = 'pjMineur' -platforms_tag = 'plateformes' -details_tag = 'details' -link_tag = 'lien' - -tags = [title_tag, max_players_tag, min_players_tag, nb_joined_tag, - date_tag, time_tag, length_tag, type_tag, mj_tag, system_tag, - minors_allowed_tag, platforms_tag, details_tag, link_tag] - -tags_to_form = { - title_tag: 'jdr_title', - max_players_tag: 'maxJoueurs', - min_players_tag: 'minJoueurs', - #date_tag: 'jdr_date', car on ne veut pas que celui là s'écrive automatiquement. On veut d'abord séparer la date et l'heure et faire le mettre sous la forme Y-M-D - length_tag: 'jdr_length', - type_tag: 'jdr_type', - system_tag: 'jdr_system', - minors_allowed_tag: 'jdr_pj', - details_tag: 'jdr_details', -} - -tags_to_lambda = { - nb_joined_tag: lambda f: '0', - #time_tag: lambda f: '15h00', - mj_tag: lambda f: f"<@{f.getvalue('user_id')}> [{f.getvalue('pseudo')}]", - platforms_tag: lambda f: " ".join(f.getlist('platform')), - link_tag: lambda f: 'https://discord.com/channels/TODO' -} - -_ = lcl - - -class Calendar: - """ - This class allows updating an xml file that contains the data of an event calendar. - """ - creators_to_webhook = {} - - def __init__(self, fp: str, localization: Localization = None): # TODO localization - """ - Create an xmlCalendar. - - @fp path to xml file - """ - self.fp = fp - import os - from urpy import utils - #utils.html_header_content_type() - #print(os.listdir("/usr/share/urbot/")) - #print(open("/usr/share")) - self.tree: et.ElementTree = et.parse(self.fp, et.XMLParser(remove_blank_text=True)) - if localization is not None: - global _ - _ = localization.gettext - - def get_last_id(self) -> int: # TODO change name and more - root = self.tree.getroot() - - if 'last_id' in root.attrib: - return int(root.get('last_id')) - else: - last_id = self.find_last_id() - root.set('last_id', str(last_id)) - return last_id - - def find_last_id(self) -> int: - return max(map(lambda e: int(e.attrib['id']), self.tree.getroot())) - # TODO check Publish - - async def add_event(self, form: cgi.FieldStorage, embed: discord.Embed): - """ Add an event to the calendar. """ - - print('Debug: Chargement du webhook...') - with open(f'/usr/local/src/URbot/wh', 'rb') as f: # TODO clean up - d = pickle.load(f) - print('Debug: Webhook chargé !') - - wh_url, guild_id, channel_id = d[int(form.getvalue('user_id'))] - - async with ClientSession() as client: - webhook: Webhook = Webhook.from_url(wh_url, adapter=AsyncWebhookAdapter(client)) - - msg = await webhook.send("", wait=True, embed=embed, allowed_mentions=discord.AllowedMentions(users=True)) - - try: - root = self.tree.getroot() - root.set('last_id', str(int(root.get('last_id')) + 1)) - parent = et.SubElement(self.tree.getroot(), game_tag, id=root.get('last_id')) - - for tag in tags: - new_elmnt = et.SubElement(parent, tag) - if tag in tags_to_form: - new_elmnt.text = form.getvalue(tags_to_form[tag], 'NotFound') - elif tag == link_tag: - new_elmnt.text = f"https://discord.com/channels/{guild_id}/{channel_id}/{msg.id}" - elif tag == date_tag: - - date_string = form.getvalue(tags_to_form[date_tag]) #On récupére la date en string (actuellement sous la forme 10/08/2021 11:00) - date = datetime.strptime(date_string, "%d/%m/%Y %H:%M") #On transforme ce string en objet (Doit avoir la même mise en forme / / / : que le string cité ci-dessus) - date = date.strftime("%Y-%m-%d")#On récupère uniquement la date sous la forme 2021-08-10, pour la compatibilité dans le calendrier web - #Ces changements de format ne concernent pas le message Discord, déjà posté, mais l'écriture dans le xml. Le calendrier php a besoin d'une date et heure sous ce format pour fonctionner - - new_elmnt.text = date - elif tag == time_tag: - - date_string = form.getvalue(tags_to_form[date_tag]) #On récupére la date en string (actuellement sous la forme 10/08/2021 11:00) - date2 = datetime.strptime(date_string, "%d/%m/%Y %H:%M") #On transforme ce string en objet (Doit avoir la même mise en forme / / / : que le string cité ci-dessus) - heure = date2.strftime("%Hh%M") #On récupère uniquement l'heure, sous la forme 12h00 - new_elmnt.text = heure - else: - new_elmnt.text = tags_to_lambda[tag](form) - except Exception as e: - print("Problème lors de l'écriture dans le XML") #Si l'écriture dans le xml ne marche pas, il ne faut pas que cela empêche de poster le message sur Discord - - def remove_event(self, id, show_errors=True): # TODO better name for show_errors - root = self.tree.getroot() - - try: - root.remove(next(e for e in root if e.get('id') == str(id))) - except StopIteration: - if show_errors: - error_log(f"Attempt to remove an event that doesn't exist. Event id : {id}") - else: - log(f"Event with id {id} successfully removed.") - - def remove_events(self, ids: str): - """ - - :param ids: - """ - - groups = ids.split(" ") - - for group in groups: - if group.isnumeric(): - self.remove_event(group) - elif re.match("^[0-9]*-[0-9]*$", group): - start, end = (int(id) for id in group.split('-')) - assert start <= end - for id in range(start, end): - self.remove_event(id, show_errors=False) - else: - raise ValueError("Incorrect format of the ids' string.") - - def save(self): - self.tree.write(self.fp, pretty_print=True) diff --git a/src/www/css/master.css b/src/www/css/master.css deleted file mode 100644 index 7dfede1..0000000 --- a/src/www/css/master.css +++ /dev/null @@ -1,37 +0,0 @@ - - -/*Police et taille d'écriture : */ -html { - /* 1 */ - font-size: 16px; - font-weight: normal; - line-height: 1.5; - /* 2 */ - -webkit-text-size-adjust: 100%; - /* 3 */ - color: white; -} -body{ -padding: 20px; -overflow: auto; -background-color: #23272A; -} -.titleCenter{ /*!IMPORTANT, utilisé dans plusieurs pages*/ -text-align: center; -} -.divCenter{ - margin: 0 auto; -} - - -/*Cadre : */ -#UR { - padding: 1.2em; - width: 85%; /*Taille horizontale du formulaire*/ - margin-left: 7%; - border : 1px solid #000; - box-shadow: 5px 8px 6px black; - background: #2C2F33; -} - -a{ text-decoration: none; } \ No newline at end of file diff --git a/src/www/img/ccby.png b/src/www/img/ccby.png deleted file mode 100644 index 6c37b6f53656f9463be88c52a51f51ad3633e047..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 430 zcmV;f0a5;mP)IWd00004XF*Lt007q5 z)K6G40001%P)t-s|NsB)?(W^)-OA3&%*@Ql%*e*d#>K|Py1%-+ySuZvva7MGs;a7) znVFQClaG*(h=_=XhlhrShJb*8eS&>=cXx7jac661W@ctvURzaFRZC1uL_|bGLqkGB zLP0`7J3Tu&J2^8mGchwUE-)@9C?_N(BO)RqAt50kARrkT84(c?4-gLy4h{_t4Gj$q z4Gjzc000fQuD$>O0K-W{K~#9!t&`glf*=q;m!z^*jV_oO-RxpzYWx2`*AR}4(@QBk z@WL{O1Iq$g4j`}^8i+cUB&sEq2Z+w2wVwe7xu_3OXGC}25xBp8tU0X^`|EDNLI6)m zF-0i{bwqR~?>vCsWadA>1&tt$LCTEz9>@aG9h$ru@ULM=oaw5>iN3SU5yykt@OQs2 zLltFA`XI}uVGAIZ0=$eOrYPyb=G1t@R=~r}a+QPUv<7Z37XBfIG^=>Kd)H2}-OHBa Y1;PoQK(nY$LaOUS5FGrIl$Vkm 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

p;O6`(+D&~k;RDcsI( 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 XPm4@i>&OKNVnzt(^)w33THpOYM}%bH diff --git a/src/www/index.html b/src/www/index.html deleted file mode 100644 index 1c766c3..0000000 --- a/src/www/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - L'Union des Rolistes - - - - - - - -

-

Ce site est actuellement en maintenance

-

Veuillez nous excuser pour la gêne occasionnée

- -
Union des Rôlistes
-
- - -
- \ No newline at end of file diff --git a/start.sh b/start.sh deleted file mode 100755 index e67be02..0000000 --- a/start.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# This script starts the discord bot of "L'Union des Rôlistes" - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -# start / restart the service -echo "Starting..." -sudo systemctl restart Bot_Base - -# wait a little to find out if the bot was successfully started -sleep 1 - -# check SubState -sub_state=$(systemctl show -p SubState Bot_Base --value) - -if [ "$sub_state" = "running" ]; then - echo "Success!" -else - echo "Failure... (use 'journalctl -eu Bot_Base' to check the log.)" -fi \ No newline at end of file diff --git a/updateBot.sh b/updateBot.sh deleted file mode 100644 index 00a3685..0000000 --- a/updateBot.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# This script updates the files for the discord bot of "L'Union des Rôlistes", and restart it - -#UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA) -#To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ -#Ask a derogation at Contact.unionrolistes@gmail.com - -cd /usr/local/src/Bot_Base -git pull #Pour mettre à jour le script add_repo.sh -bash add_repo.sh Bot_Base -bash install.sh -bash add_repo.sh Bot_Planning_python -bash add_repo.sh Web_Planning -bash add_repo.sh Web_Presentation -bash add_repo.sh Bot_Presentation - -#Droits particuliers : -chmod 776 /var/www/html/Web_Planning/Calendar/data/events.xml - -service apache2 restart -bash start.sh \ No newline at end of file From 3e6e42b87b5382e436fddeaaed6e397bcf58bb30 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Thu, 5 Jan 2023 10:19:48 +0100 Subject: [PATCH 14/29] v0 help command * first commit * first commit * link to histories --' * avoid critical data to pass freely * debug first call loop * new help * clean Co-authored-by: Sx-Cheats --- .gitignore | 5 ++- api/extends/base/_discord.py | 2 +- bot/.env.default | 3 ++ bot/__pycache__/DebugBot.cpython-310.pyc | Bin 1703 -> 0 bytes bot/__pycache__/event.cpython-310.pyc | Bin 3759 -> 0 bytes .../base/__pycache__/base.cpython-311.pyc | Bin 5987 -> 7876 bytes .../base/__pycache__/event.cpython-310.pyc | Bin 1254 -> 0 bytes .../base/__pycache__/event.cpython-311.pyc | Bin 2053 -> 0 bytes .../base/__pycache__/event.cpython-38.pyc | Bin 1206 -> 0 bytes .../base/__pycache__/tmp.cpython-310.pyc | Bin 4990 -> 0 bytes .../base/__pycache__/tmp.cpython-311.pyc | Bin 6022 -> 0 bytes .../base/__pycache__/tmp.cpython-38.pyc | Bin 3675 -> 0 bytes .../__pycache__/tmp_not_used.cpython-311.pyc | Bin 6031 -> 6031 bytes bot/extends/base/base.py | 33 +++++++++++++- default.docker-compose.yml | 42 ++++++++++++++++++ 15 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 bot/.env.default delete mode 100644 bot/__pycache__/DebugBot.cpython-310.pyc delete mode 100644 bot/__pycache__/event.cpython-310.pyc delete mode 100644 bot/extends/base/__pycache__/event.cpython-310.pyc delete mode 100644 bot/extends/base/__pycache__/event.cpython-311.pyc delete mode 100644 bot/extends/base/__pycache__/event.cpython-38.pyc delete mode 100644 bot/extends/base/__pycache__/tmp.cpython-310.pyc delete mode 100644 bot/extends/base/__pycache__/tmp.cpython-311.pyc delete mode 100644 bot/extends/base/__pycache__/tmp.cpython-38.pyc create mode 100644 default.docker-compose.yml diff --git a/.gitignore b/.gitignore index d3d90e3..51c03d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -test.yml \ No newline at end of file +test.yml +.env + +docker-compose.yml \ No newline at end of file diff --git a/api/extends/base/_discord.py b/api/extends/base/_discord.py index 35ab086..64b2585 100644 --- a/api/extends/base/_discord.py +++ b/api/extends/base/_discord.py @@ -28,5 +28,5 @@ async def get_discord_client(): tmp_discord_client = await discord_client() return await tmp_discord_client - +asyncio.create_task(get_discord_client()) # start discord client (doit etre deplacer dans api_base) diff --git a/bot/.env.default b/bot/.env.default new file mode 100644 index 0000000..e6544fb --- /dev/null +++ b/bot/.env.default @@ -0,0 +1,3 @@ +# token for dev +DISCORD_TOKEN="--------------------------------------" +BOT_PREFIX="$" \ No newline at end of file diff --git a/bot/__pycache__/DebugBot.cpython-310.pyc b/bot/__pycache__/DebugBot.cpython-310.pyc deleted file mode 100644 index da27e871135f85cbf14242187f40bd5d5706eeeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1703 zcmb7E%}*0S6rY*hZMWMN5D60EM?fVuk;cTEA;e%!!d`JjPQcU;TtZ+jlsYew81={0^yKsQcgKLF!yMe!hP-h3)ew6 zo>mtQ=_dRC3LK0E`pClk3NtPP`9vj=^oUB_~R(bxWwiZcy&56BQ#gE-c@MYLoZaRL5C$)-KZ#O_Fw{{Z~EC+SR-7ew- zGCMr!aw~Atr$IsmP~HZJ`f*;?M%B z$2yP+s_UJFOFa(lqfK!Q30e@7NR;j3DHEus%onJh6^Q4BmxCTpp`5E{YDbQmtHeYEI;d9d|TmBVXTBt}d4p-fEM4xOfF+p5Y0 zK1?9iP{xwka8%aL9N8& z!Wct6gih7L7&_%hK{tKY&MdmbSOGsv+yvniJlHUJ`V;2y91w(Oi=J(=KHY#`F}xiQ zQej1*-SpzvX?Ow~H+`n6BNfLK$L_GlT6fVr`eFOCR4jmSVHDyP8oUhzn&1wwY8LFY z{IGGZtK!HNsx0B%MI`9)sy>9*xy%Qsltw=8UERlfFKAuj163Z5eB1}AluvT$A1(0f z=|3*(#Ji#EM=C=CK~}w~09N@`2@n{eKochr8pC9+q(7!r{CmbTKnl#H>NCu|Su{0M HD`@`!$&_4z diff --git a/bot/__pycache__/event.cpython-310.pyc b/bot/__pycache__/event.cpython-310.pyc deleted file mode 100644 index 18cbe1fcb8509a58f978c4be05999cb1ae7d4b51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3759 zcmcInOKcp+5uNFo{cyRYD4CK-Tc43FOWKu5E|=yv`j{WdQerWJB+Jf1!Fo8|yF1kE z%(`cWpTQE4VIy!3f`KHLz%nA{5P(4rxdg~H(ILm&2RS(>0YMT32oUI?&q$l&i(SI){hP;%Acw8@|Q*D9TfjhAY9?9qf|PzT2aww4r^)^ zP3?^763onMPTEKK(WD;g_J<#7{8j!MKSrariuQ4SqT4?CC{@Yu^V|^G8sn$<>z}16J^T%R z`ZJ}H<8SgasC)TYehzh>7kCkMA6IWHg}0tULuI+3#=VYb@+$WN;jYEGh40>}-n@0? z>h%wwsi!MxOA2lW;UIYDYc<u821Y-)~qIv^@ea-ab`_O-}c<_==y}IbR zHCr~nks}fIAWnbsLx?Ag&^4OE_szP9b;oq;(W%7-Dy%i}@76;>A~|Qnz;r;@!1^1wx+gc|lqsdT zX)$cO`=!v(P!>`AGay{uVr^wf`F?&&YpZSTQ*}#k>uq*lmji8mi+zkX+>I?u72G>Y zAtlK^aTeF*teDpQI92uSNJ!eb!m3gtE|r8l8(EyYw{+$1y#+6*E||W!w`BX4C;12l zMw+JW(#$=v2I)szoAKbgBDi60+z#YzY=F@fRYBu@PRrWwvK+Pt-tpthSX^_E_WUHnk|s*|xgPt^)wBA@I2Z-L&OU2mjxH_Qj83F3+tO z(s9Q0H(ks2;&j~%8bZdIT|dm5oL8mjICq>j!=T~GSceHXf-#k7^WqNVVeA}dJh$3{ zu~tboLNHPxhrj;kl45O-B4i$t=Z`4%o=ty zcuhEtcgnb7`-W+(lIPAD1wGDK9-Pe$;+!AAef@PiXkZXwK;|1WABqP#;uMv#)$DUJUspD@A3+KKntZ4_j2J|(ryuM z#Hqjz909!a98d1BSa;2)h_g1Fr3&UkCK@gl=UT$8t|0DPp6v#6hOz85bH!qG>>H+V ztlODAJ}14<<<(Z`HxL9^RxHkQ4g>gD92(GcE)b4rHSj!O7SqeW|Fv;vcZg1Sb*|<4 z!HTyrhuK9Bm`{Dvp}7)Hhh17BAQa1Qhpm<;1LNEWw#&VB-xxFAv)yn5^$pW1(2=Kb zWU+WCD>2x5>(Z*a#$H%#JDvGj|BVnk#}Aa&e~X^l|D6jQ}r1Q0>5-JVvtS!O(Kd&1&K+ zxsC+nEpZQFWX%>%cU8?pb|T|G+5zG8Krmejg=L7A`pmJ2<2ahV<(L=-3f90q`Cw)Bg@oSJ^ zklK*B2e~EXnL`N@CVkAl=;!z_<(vsJvejO+k9CnTzuMPk{P;d(Jh2@0zkdt4BLIU^ zK_nw*ab*Q2i>TyzkSP8Btr2iUeAneH?v8eS#B8-X*x2#HvGLN|rSkOT`1It|%=A=g zYGS%PHZj2`&6(+$vNg`9CTA>D2vbbX!Yjpexx7*u1*oO)B8dOrH-BW~Afxk&3fZzO3+q+$3eWdM7uPXm`KgtX9!f&He6|z2KP56>eVooVKHV2b5?S_(&HvdcpCK5&4(U=x`-_ zK2wcI8@Xh3QahIGiS!)Amt%Bj3FN9A2aQhbjdQ?R({#DtwZ*8U=Ri&pnIt!PE5Sb_P29e~|&%P9iogk!L zVs#@{twb4Ik5{W9hWFnL1twj>C)mq>9fUpTSNqigmRAQ6pabfVI;yhgS$%*Fux_X> zgRP)0LyIAjfK-XoogXoh?lxu~a*w*8Up0wR@;Z&`+_r*_Hk3FenMM-!k~Vxvup|pj zlCSKArpNJ%0`oGwr7ri|*e_I;-dV{sU_j0dxQW diff --git a/bot/extends/base/__pycache__/base.cpython-311.pyc b/bot/extends/base/__pycache__/base.cpython-311.pyc index 50584dfea0384e2f5292674c3eadb329eeb097ca..e2961dc76ebbff93bbd41661118e3afa1e50ca26 100644 GIT binary patch delta 2524 zcmZuyOKclO7@mE3ZP#`j@^G3o?xs!JhBirGB#o0+v_TXV2$YIPBPw^}*|<*pVs=wn zy5IeN46q2XS+pdE6=d%oCDZ>XH+Vd$%zr*5B z$f{y-(-}=a%^vS2G-}HiP0FekO)05qh4ix)uPVtI;=c1O`yb1|N7%N-k?!+KY9r|SqEj_G5~-Av@p+2)gMf@}p+Za|)yNyIKF zQc_W+SSFQ{(^ygWNpdm)uE9XjBp}2{BCBQ67IPuSf>^y#e?NlvpBq4Z88J`^3ADiG z(QKUqtuXqJ;*Rkb(7ERpxI8zzA)aS1GQ=B4dA5-{4uYF0TaljS!-IM5I#k+Cc%8A@a$%TcX^)rvK+=- zV>pir_zINpHEuz$^_ zw|)iXUGR!_c!Cqiva);`__nNv#dv2SKBwv1gCRZQZqmQ-v=n@kv5ozKWjdx&{SyTE z95tr#3?lR*`Gz4K@abnP=8{U;B|-%?6I5E9su4>hN;WCS6#M+y;ag(tf}Bn(Nzy|Ny9wP4 z(nlryb;BnvPskcDIw#cPVzTBaa1^K1xaux|1dW0=JCByQp17Q^F^#VSpK3t5|AuD~ z)m&(6SJ7V$b$xbeEi|wi8mNSZ%+OE`ah`D-iYKd0?Pk;Vl?9{eF!bud!zF(?ZiWXd z!J(>eYk9ZX`K;+H>|OI6T=gBiJyP+FnZ7Z@?tgY{yY|+~t82X@tGy$Y-chr6w9*|h zyCap3F|%W==)d10)e!56*ifAKy=Qkd*jr<1Yyx`-^-v(6XFb3rMR0X{EIi*kuNG8Sba{0;SsZ zKiTgm^yFFIXf&0<^GPKdh5t(Qy?HrFi#Hl2^y0C&6B&&JU_@FeOxq&y`9u;&GwI}I zvWrsbHAr5epa6X1oG`n-2>qMeKy|3${ZLbj8&8d8g`V2`FsMT?(?n}N_;-gM~* zK(K#FOZs42kNycCbTM%C*uSAxRulBoZIMJjJ87Sue$EOIC6&3T)K9o2${52WZX4-m z>`S2GYO(}X(dOY!0W)bD^eF)69Np)|HNJh7Z!cY}@L`h=8+=&*we5uKMPMOAdRzP6 zBNKGEpTb!HiZSn{`>7 delta 746 zcmX?N`&f@}IWI340}z}%w=~&+XCt2@qsKQOcRE8VLlk2QLljdAV+%tRa|#oXWMN`R zWzAv(DF=ZR<`k9`))cmMrZw!#m>C#W12F`E)v-?&5EWxe;h4OiQNEr7O)F;#R|-WG;sKvP!3%wmY*MAOTc!k;3L&WLVliXetgt`wnQ22J5xEG$lm#i^4QFgeQJ zVou60xy2KolUQ65pPQPSlv}pH7BizYx6&*|4fWPidBb&v1+q6*E2R2J)lD- z3kZrbnoZUgv}CgZa*IqSX9-HNnS+>Cle+|E87()j5Oil^_S58^oF}Ca%LkGX0TG}e zxy7EGpPQSQmvW0My)-i?B|bkdr?N;5By9~Myn)0@h9Z6tn-@f|!w8T$#czN_0~~%} zn*3ktx;cXctK0_=0nrB1af>Ah=t)h+A_*WTnGs0904tEu48)(KCOgU~GLLPM%>`>O3IHj9`|}rvO>TZlX-=wLQS{_H ovi*?`jEq1}VJAB*dTeH-L1aII)qFu#>Bhv!sQ7^alK@)>06^lLs{jB1 diff --git a/bot/extends/base/__pycache__/event.cpython-310.pyc b/bot/extends/base/__pycache__/event.cpython-310.pyc deleted file mode 100644 index d7fcc973640b614e848d563198f868fc5adda7d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1254 zcmZWoOKTiQ5bmDodF*Oslpy2~2y7siJy_)Ag9yf04oNOcz>veR(2S7I=( zMFAV>YI4XihpdkN9r+iXV=nP6=Ma*7H7kY1Zs_Vqb=Oy4J=)#fp}_ci=jY7^JA|``3_$#Ip z>C>XE>%1XX@Xhv!aCnI69zYn8NfKE=k^)!>Hwc+B(IRF9O6HQ%SD}g4$+e&X4X?zd zyp&l=JHQ)x^(c3y0H6N{?|B|$x;=;sc_B2IKmzNDcrB-sz=tPomyL9lyH3}pJ~0-T z%9-k^We;Bqn_&6t==f;%`15aOkJ?T%)a-HTiq_IST->YkvSH24Jnsy+&QAD+`OY-m z;quI z&Z}kF%+C*4j+ZUOv$8uKcc*5I@Ii|!pesi5>eN(ApX5~ue(s%|Fiy|w2pi_LmKWf) z4oO~Pw^kIaqb>&9WRX&M8Ial+*U()DUxD?P;)Z)PU2!P?{N=}=R_|f+HGKGZ%*XNf zpZ+ke-5ZWz`3K}=0C(J3Q_=N&U`~Ye%jhP?|fw2 zRYQFt_@v5_+~RB!^l|i2pWnB`8EKBuE|A?Fh~R|^hccCWQvG}9pY!1+<@{ti8Q2kC z_@UNyOREYg@*}OkUFFp#VRituWmA^x*e5rwdbz`YOc^N!Fjbzo(tG9 zNGej9s=Y9VBfZ8eE>V;OH^w`q$8qgb^dUAc(Kc(*L0%#W+bOX84&9P)1W_uzW!Plc zx7_iOGu>)=7n++I`#{=H_70A6uFgH@ zch7H`Op1W~HTX;ZiA2aBXhf3eD(xju4hScl+9Zc>$)-gqCs87ij|rFW60Vrih8%s! z+3$&Cg^*KtI+9a(@-E3SPH&Q|`Wy$BvouumZn0=M+|PICi5Ke{?QcOjASTICPI3~b zu!77bKt&=Z+lbF)JA@eGJNJWDty}ccj(v!%^Y)pg6U0$tzAU`(@-iFxqH0k%ow^|4)L zPi?%V?J9NkjjFiY;3D}rCNQurJMgq`0DM9y>>_x@Um+W@wSj>N;dBe$$KW}Qy%g90 zS(28wQkvy!wrg;cXXTKUJj)5f0bS3-j(uIn;2ooA>Ux%XNI4(Z(7ZuFq(MQ)Z@U|d zOfl~g%y~J)N6J1S~a@83@)XVET^zFhNzWXfUm4zT?^Jt z4fC(y5QL{-D50`$+NCgQ*cN2d?VPehoibfDJ&n-J#us=-N1~5LqnUw@NLMBMxJro_1FkO^3)uBG> zm>B;@fDS!gB*^gCp3_KQ*!%Qxdg@Vn>gaYYJzG!D!qEQGy`}2-%;WL-N8|HPM@GNr z_t)!VQzx`GGE*O!Ir+RcGT)Nv#MLtb&>}Rm09t0D#gyUH83AY#W#E5)X^Vx(q2`hwy-176T$7i8^cj_sB)D1%X%bSK-=XHFkUhlBt z18)u-7@uzgG-XO@gA8qpt3kAC@7+ucQz%V8?7qKu{cufqTIfDIpL2;)U`0QOX#NH_ CAF?z6 diff --git a/bot/extends/base/__pycache__/event.cpython-38.pyc b/bot/extends/base/__pycache__/event.cpython-38.pyc deleted file mode 100644 index 8e23a37343d1d6ee1dd93039dfca62eb8372439c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1206 zcmZWoJ#Q015Z%4o^PL@A3Mfht64D{jxJjrG1&Sh40F8wd(Ok2-+-#h)?!)Y!0UJ{} zZA3vyfnDZz@Gq8H@mYpGw;ppt4^oIaQ)u+oP2W_`%atn)xgaG zig|RqJ}%ib|2+f#2;QSA=LHjpwc(n?CA&InYDH~6GOCGB4M5>Dr< zMYfCb6~%0!s<5g7A0Yl6#Ko~nMCJ4*tuQrVR>q*9>soK9)1tCXDHE*MNJ*)SI9F13 zxu)!Ou}c-;mgw(G`k#7kYI?2;yYg5uOpE?xn{+rVQN2y8)37>KAyR{0Ji^?9@>p@I zvZ3|kEJbYQ2H%vW#`GACip}{HG0k1z3$*3}oViyu5UD>1z@0fDw%7uF3+DwQe$H07 zz21mO{Q2eMr_n7mUSb9JL)wP#AASgPb4lE#KTvm@Sd1ALNm|-QKgOc9^>bxR+*h_8 z14ymnq)N+zdRl;OoRm6`E9>cUR6yNg>t`{xH5qrEnnUZ=oxM(_CyAjBW;Sh9%oV)F z1HSoJ{F$_tt)>gTZc~%rwxrBU7-iTT+m`b6D9)Cx(oJ-03Z$ta)?Yml+v<@QFJdkvc9$q?Ff6v7S-YXt;x()nw@h^m9YgMB5EWO;T#u#YeLQ>K?A*)%Vt- X)i{^)`ee!3KD`h_6CLojyM6l~E@=%6 diff --git a/bot/extends/base/__pycache__/tmp.cpython-310.pyc b/bot/extends/base/__pycache__/tmp.cpython-310.pyc deleted file mode 100644 index d7582047bcd8a40d8a94a6b5e139d4c32d95f067..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4990 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 diff --git a/bot/extends/base/__pycache__/tmp.cpython-311.pyc b/bot/extends/base/__pycache__/tmp.cpython-311.pyc deleted file mode 100644 index babad194fa6cdf6e5c3b1ba35a6c8ef9e260efca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6022 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 diff --git a/bot/extends/base/__pycache__/tmp.cpython-38.pyc b/bot/extends/base/__pycache__/tmp.cpython-38.pyc deleted file mode 100644 index c0670de89d0e6e67cbccc1da3424be8ff3d5e079..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3675 zcmcInOK%j}6|P&aezrGa{R8p?A{JR@U&~}QQj|p^MG;bF@}257#)d3Y_I;iEI_I8q zzH|D1e}7fMxAn~{??3w#<*zh&_bX#?6(#-`Dz0!AD0a7HHp5sA)RtyztV`2v9W*U4 zTBdC>MQG0y)a+G>>)hB>?J_rciI-ofyuzzR+lPLQ_ZR&EzOQHp`Tn9E;s=U$_=V0t z;s=ZV2p=ulvEKO53ymM-Cn5Vv7r_w+1^6>~to&&6(Wv+|Uz2HaZ)IX@F}^ zxn6?9ED>5>l*%~Qc@&E5&|DNQ`LgxF6$P{Var^6EfjwcRq16&`>@JI358QA$J3hC9 z276unhs&uTmV#4h;s<{0CL)z}T~=^_By;NWHfa}}PC-;RC}g`MaDQTPu;}3-gtXk$ z!lVef^o6vwRW8%daKQC&E%FOC0v~d;AxH6c*pY_9YP&M=g{{ZnwXW^lD`CM(F|_rz z6j^%wf1DNL45%U&VIrg~VO2J?ttU@x%U(BZV|Fo06T5U%ghIN3UA_bHLqA-$Yj>oe z{VqLr);Awnb9jg4NIr#O7_OA=Q%}Xg+n<4shBAi|pG3vky4q2$DL)@w*E+1DJ!9*7 zNAIW)by@D{>*}L&M}_gJ^O9lpmQpt)X$VH=2En51JVxM>0h9pA4UH+?F=lthRX)ZK@xvq&X8X`Tvcb?-R%@&M9hD#5*sZu@^Vz`t zy8t)qv_)7Yr2qFn)?k&nk@!g<;OS8i$;`Ze_cT^zd?iWR@x^A-<6(pQu@^}WQ)~bz zo9>zm?Z(YB6K5wTr_Y_4K6id*`uxQCsp-k))D%DG&P>ltdT03gb2FYRge%Tnn7(j9 zOixZOPBb2i#rBtE?w6+~al0P6Es>Xf7><+5AR8DThr=)VPhe2^vi|Y$YPIsdqf7(&J2L8B#y|ve)&(+#K~x>MClsSXG0JAwg5?R&3ul9ammCMET^un!{+Hi<^Z)xFAHM-4{R9`~#v0&S zUEEfAPrr?2cyGFx#zOYoy3l^wJU@8``kb8kGQK=HF>!M3@>x=30+f=U;$A$Z22~Pz ziq@jw^?|(9v!L9#P87g{NE|m!Rw9{eaUvzTcV4-5{i{39wb^^Kxq*PtiVNhD6i4!k z%ek}Ui=el8DMIX)c|RQlE?bMC>j&}>mdeA#Ls=f7{!ywLsBE1M&)Jild4;B+`Dkeg z+xIHH)YEVayW$yC3L5~Ls*3u9sSW~yhSh^?kPR{Q2VI*0#ujtESrb%K0|jg4kFnRI z1K#}zd#|Fzw@?X%axYsYxI&hqagFO}bz!WU1!OVGvH&{FE)_InTgxi%+hCMy74$M6 z050##wR^4ht~f<*KxK|DD_ntf6bCSl8ka68TWlVB(-xy7uQ`DmCjeY%N0KsQ5iH3o z*tz$0%8uiQe&RU4q+`3NY_hC|+Z)?c?|CH2{F>HN8kG=8F@Q1~fn2?~qRwYk3&Dia zGFS{^L|giZ5f#Vrz*_7$Ic_GbAXk-Fan5_3dC{^%`+rS0k>pB`uk8ujC|q}o74GrD z$11p_@Cz>4vt1A>FN2J`Nvm6Uq6It=GNRPtK09^~*=X!Zzn#R^iW^&t0y$Rq4#67*PS({O zr;s;s+#YW8{_c9G6>(&pza_p4t!hIo+fSVDnxC&%eqAiudvos$ZZK*$Xlky{uJPRU2T%Os*xZw!DG)_iT%p zEwna@P6sHTz|5Z^lX?4EF!iqPRJ=G)1m;j23SEB4!VO?T+WtN9(lsJ^7yUY%Wk2@E zPead-ay3Hc(*5}$X_aiE%KLgS*B~e?q&JrZ{lqBnO-|7oo%>$W1vW}$3UkV}xgJl& zT_1X&ynLyvp(_;PV#*F R_WoerAFPy(@}YwV{tGquq!j=F diff --git a/bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc b/bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc index 724c4510fca24c8c9cd7f05c577826a66d45edec..474e62db6d699a57dac5f9d0792d9cef67d52a9d 100644 GIT binary patch delta 20 acmeCz@7L#E&dbZi00c+3ZQ97)A`SpKUj^3y delta 20 acmeCz@7L#E&dbZi00b)=7Hs5h5eEP^{RMLX diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index 45b5f05..dc33811 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -12,7 +12,7 @@ class Base(commands.Cog, name='base'): - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self._last_member = None @@ -77,8 +77,39 @@ async def _version(self, event): print(e) await event.send(txt) + @commands.command(name="help", help='affiche les commandes, aliases, et descripton', aliases=['h'], ) + async def on_help(self, event): + # get all commands and sort them by category + commandsByCat = {} + for command in self.bot.walk_commands(): + if command.cog_name not in commandsByCat: + commandsByCat[command.cog_name] = [] + commandsByCat[command.cog_name].append(command) + # sort categories by key + commandsByCat = dict(sorted(commandsByCat.items())) + # sort commands by name alphabetically in each category + for cat in commandsByCat: + commandsByCat[cat] = sorted( + commandsByCat[cat], key=lambda x: x.name) + # create help message + help = (f"```properties\n" + f"prefix {self.bot.command_prefix}\n") + # add category and commands to the message + for cat in commandsByCat: + # example : "category : " + help += f"\n{cat} : \n" + for command in commandsByCat[cat]: + # examle : " command [alias] : help" + aliases = f'''{' ' + str(command.aliases).replace("'", "") if command.aliases != [] else ''}''' + msg = f'\n > {command.help}' if command.help != None else '' + help += f" {command.name}{aliases}{msg}\n" + help += "```" + await event.channel.send(help) + async def setup(bot): + # remove old help + bot.remove_command('help') await bot.add_cog(Base(bot)) diff --git a/default.docker-compose.yml b/default.docker-compose.yml new file mode 100644 index 0000000..fbc26db --- /dev/null +++ b/default.docker-compose.yml @@ -0,0 +1,42 @@ +version: '3' +name: Union-des-rolistes + +services: + bot: + build: ./bot #utilise le dockerfile pour cree le container a utiliser + container_name: bot_base + restart: always #redemarre le container si il s'eteint + environment: + - DISCORD_TOKEN=--------------------------------------- #token pour comm avec discord + - BOT_PREFIX=$ #prefix pour les commandes + - BOT_BASE_VERSION=x.x.x #version du bot + volumes: + - ./Bot/extends/base:/app/extends/base #dossier ou est l'extension de la base + api: + build: ./api #utilise le dockerfile pour cree le container a utiliser + container_name: api_base + ports: + - "3000:3000" + restart: always #redemarre le container si il s'eteint + environment: + #reuse les variables d'environnement du container bot_base + - DISCORD_TOKEN=--------------------------------------- + - PYTHONUNBUFFERED=1 #get print of python + php: + image: php:8-apache + build: ./apache #utilise le dockerfile pour cree le container a utiliser + container_name: php_base + # environment: + # - APACHE_HTTP_PORT_NUMBER=80 + volumes: + # - ./apache/default-httpd.conf:/etc/apache2/apache2.conf:ro # si besoin de modifier le fichier de conf apache (normalement pas besoin) + - ./apache/vhost/000_api.conf:/etc/apache2/sites-enabled/000_api.conf:ro #fichier de conf apache pour l'api + ports: + - "80:80" + links: + - api + restart: always + depends_on: + # attend le lancement de ces containers avant de lancer le container + - bot + - api From d527d00fe23c3108e475290cc8d8d2eb29cacf6d Mon Sep 17 00:00:00 2001 From: Mortifia Date: Thu, 5 Jan 2023 19:24:49 +0100 Subject: [PATCH 15/29] help update (#21) --- bot/UR-Bot.py | 2 +- bot/extends/base/base.py | 18 ++-- bot/extends/base/tmp_not_used.py | 155 ------------------------------- 3 files changed, 11 insertions(+), 164 deletions(-) delete mode 100644 bot/extends/base/tmp_not_used.py diff --git a/bot/UR-Bot.py b/bot/UR-Bot.py index eb89437..681ddc2 100644 --- a/bot/UR-Bot.py +++ b/bot/UR-Bot.py @@ -35,7 +35,7 @@ async def on_message(self, message): intent.messages = True intent.message_content = True # v2 -bot = BOT_BASE(command_prefix=BOT_PREFIX, intents=intent) # build bot +bot = BOT_BASE(command_prefix=BOT_PREFIX, intents=intent, case_insensitive=True) # build bot groups = {} diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index dc33811..c512069 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -10,8 +10,10 @@ sys.path.append('..') +# class base with case insensitive -class Base(commands.Cog, name='base'): + +class Base(commands.Cog, name='Base'): def __init__(self, bot: commands.Bot): self.bot = bot self._last_member = None @@ -77,8 +79,8 @@ async def _version(self, event): print(e) await event.send(txt) - @commands.command(name="help", help='affiche les commandes, aliases, et descripton', aliases=['h'], ) - async def on_help(self, event): + @commands.command(name="help", help='affiche les commandes, aliases, et descripton', aliases=['h', '?']) + async def _help(self, event): # get all commands and sort them by category commandsByCat = {} for command in self.bot.walk_commands(): @@ -92,17 +94,17 @@ async def on_help(self, event): commandsByCat[cat] = sorted( commandsByCat[cat], key=lambda x: x.name) # create help message - help = (f"```properties\n" - f"prefix {self.bot.command_prefix}\n") + help = (f"```ansi\n" + f"prefix {self.bot.command_prefix}\n") # add category and commands to the message for cat in commandsByCat: # example : "category : " - help += f"\n{cat} : \n" + help += f"\n{cat} : \n" for command in commandsByCat[cat]: # examle : " command [alias] : help" aliases = f'''{' ' + str(command.aliases).replace("'", "") if command.aliases != [] else ''}''' - msg = f'\n > {command.help}' if command.help != None else '' - help += f" {command.name}{aliases}{msg}\n" + msg = f' -- {command.help}' if command.help != None else '' + help += f" {command.name}{aliases}{msg}\n" help += "```" await event.channel.send(help) diff --git a/bot/extends/base/tmp_not_used.py b/bot/extends/base/tmp_not_used.py deleted file mode 100644 index 67a36ed..0000000 --- a/bot/extends/base/tmp_not_used.py +++ /dev/null @@ -1,155 +0,0 @@ -import asyncio -import os -from dotenv import load_dotenv -import discord -from discord.ext import commands - -load_dotenv() -BOT_PREFIX = os.getenv('BOT_PREFIX', '$') - -HELP_DATA = { - "About": - { - "cmd_0": - { - "cmd": "credit", - "help": "Affiche les crédits" - }, - - "cmd_1": - { - "cmd": "version", - "help": "Affiche les numéros de version" - }, - }, - - "General": - { - "cmd_0": - { - "cmd": "cancel", - "help": "Annule l'action en cours" - }, - - "cmd_1": - { - "cmd": "done", - "help": "Confirme l'action en cours" - }, - - "cmd_2": - { - "cmd": "edit", - "help": "Édite un message" - }, - - "cmd_3": - { - "cmd": "lang", - "help": "Change la langue de l'utilisateur" - }, - }, - - "Planning": - { - "cmd_0": - { - "cmd": "cal", - "help": "Permet d'accéder au calendrier" - }, - - "cmd_1": - { - "cmd": "jdr", - "help": "Envoie un lien pour créer une partie" - }, - - "cmd_2": - { - "cmd": "site", - "help": "Permet d'accéder au calendrier" - }, - - }, - - "Presentation": - { - "cmd_0": - { - "cmd": "prez", - "help": "Envoie un lien pour se présenter" - }, - }, - - "No Category": - { - "cmd_0": - { - "cmd": "help", - "help": "Affiche ce message" - }, - } -} - - -def GetMaxStrSizeInArray(array: dict, callback=None): - _size = 0 - for cmd in array: - _r = callback(cmd) - if (_r > _size): - _size = _r - return _size - - -async def on_message(event, *args, **kwargs): - if event.content.startswith('hi'): - await event.channel.send(f'Hello! Mis a jour : {args}') - - -async def on_help(event, *args): - embed = discord.Embed(title="URBOT - helper", color=0x0CC1EE) - embed.set_author( - name="UR-BOT", icon_url="https://cdn.discordapp.com/avatars/1040275175687606372/33d5a8782c1d658caeeae59799e722b0.webp?size=32") - HELP = str(f"**prefix : {BOT_PREFIX}**\n\n\t```diff\n") - for x in HELP_DATA.items(): - HELP += f"\r+ {x[0]}\n\n\t" - # Obtenir la chaine la plus longe pour ensuite center la fleche (->) - c = GetMaxStrSizeInArray(x[1].items(), lambda a: a[1]['cmd'].__len__()) - for cmd in x[1].items(): - _offset = (c - cmd[1]["cmd"].__len__()) + \ - 1 # centrage de lq flèche (->) - # Ecrit la command -> description - HELP += cmd[1]["cmd"] + (" "*_offset) + "-> "+cmd[1]["help"]+"\n\t" - HELP += f"\n```" - embed.add_field( - name="**\n**", value="**───────────────────────────────**", inline=False) - embed.add_field(name="**\n**", value=HELP, inline=False) - embed.set_footer(text="Union Roliste commands helper.", - icon_url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - # set le logo en haut a droit - embed.set_thumbnail( - url="https://avatars.githubusercontent.com/u/62179928?s=200&v=4") - await event.channel.send(embed=embed) - - -class Tmp(commands.Cog, name='tmp'): - def __init__(self, bot): - self.bot = bot - self._last_member = None - - async def cog_load(self): # called when the cog is loaded - print(self.__class__.__name__ + " is loaded") - - @commands.command(name="_help") - async def help(Self, ctx): - await on_help(ctx) - - @commands.command(aliases=['reload', 'rld']) - async def reload_module(Self, ctx): - # await reload_module() # work ??? - await ctx.channel.send("The scripts has been reloaded.") - return 0 - - -async def setup(bot): - await bot.add_cog(Tmp(bot)) From a211e01eb5e60c2c1ac93d56e0c761baa17f0c04 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Thu, 12 Jan 2023 10:25:11 +0100 Subject: [PATCH 16/29] 20 $help full color (#23) * help * tmp * install (need test and better ux (var in .env not directly in environement) --- api/main.py | 2 ++ update.ps1 | 18 ++++++++++++++++++ update.sh | 11 +++++++++++ 3 files changed, 31 insertions(+) create mode 100644 update.ps1 create mode 100644 update.sh diff --git a/api/main.py b/api/main.py index 2ca1586..0d618e5 100644 --- a/api/main.py +++ b/api/main.py @@ -1,4 +1,5 @@ import asyncio +import traceback from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -37,6 +38,7 @@ def load_all_extensions(): except Exception as e: print("Failed to load import {0}.".format(file)) print(e) + print(''.join(traceback.format_exception(None, e, e.__traceback__))) load_all_extensions() diff --git a/update.ps1 b/update.ps1 new file mode 100644 index 0000000..1bc9cd7 --- /dev/null +++ b/update.ps1 @@ -0,0 +1,18 @@ +# Set the current directory to the parent directory +Set-Location .. + +# Find all docker-compose.yml files in subdirectories with a depth of 3 or less +$files = Get-ChildItem -Path . -Filter docker-compose.yml -Recurse -Depth 3 + +# for each file found, add "-f $file.x" to the tmp variable +$tmp = $files | ForEach-Object { " -f $($_.FullName)" } + +# Set the current directory to the Bot_Base directory +Set-Location Bot_Base + +# Use docker-compose to generate a new docker-compose file based on the files found +& docker-compose $tmp config > "conf-build-docker-compose.yml" + + +# Use docker-compose to restart all containers in the stack defined in the new docker-compose file +& docker-compose -f conf-build-docker-compose.yml up -d --remove-orphans --force-recreate --build \ No newline at end of file diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..1a147a5 --- /dev/null +++ b/update.sh @@ -0,0 +1,11 @@ +#!/bin/bash +cd .. +#all docker-compose depth 3 +tmp="" +find . -maxdepth 3 -name "docker-compose.yml" |while read i; do + tmp=$tmp + " -f " + $i +done +cd Bot_Base +docker-compose $tmp config > conf-build-docker-compose.yml +#docker-desktop force restart of all containers of the stack +docker-compose -f conf-build-docker-compose.yml up -d --remove-orphans --force-recreate --build From e8a8cf764040efe6a6c5678dbcbf86b88436a984 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Fri, 13 Jan 2023 12:27:36 +0100 Subject: [PATCH 17/29] tmp --- .gitignore | 9 +- .../base/__pycache__/base.cpython-311.pyc | Bin 7876 -> 0 bytes .../__pycache__/tmp_not_used.cpython-311.pyc | Bin 6031 -> 0 bytes bot/extends/base/base.py | 10 +- bot/extends/base/version.txt | 1 + default.docker-compose.yml | 42 ------ docker-compose.yml | 11 +- env/default.bot.env | 2 + env/default.oauth.env | 2 + env/default.token.env | 1 + script/config.sh | 35 +++++ script/dotenv.sh | 123 ++++++++++++++++++ script/readme.md | 39 ++++++ 13 files changed, 221 insertions(+), 54 deletions(-) delete mode 100644 bot/extends/base/__pycache__/base.cpython-311.pyc delete mode 100644 bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc create mode 100644 bot/extends/base/version.txt delete mode 100644 default.docker-compose.yml create mode 100644 env/default.bot.env create mode 100644 env/default.oauth.env create mode 100644 env/default.token.env create mode 100644 script/config.sh create mode 100644 script/dotenv.sh create mode 100644 script/readme.md diff --git a/.gitignore b/.gitignore index 51c03d1..bb1ee54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ test.yml -.env -docker-compose.yml \ No newline at end of file +__pycache__/ + +.env +# not load env file +env/* +#invert to keep default .env (keep the default.*) +!env/default* \ No newline at end of file diff --git a/bot/extends/base/__pycache__/base.cpython-311.pyc b/bot/extends/base/__pycache__/base.cpython-311.pyc deleted file mode 100644 index e2961dc76ebbff93bbd41661118e3afa1e50ca26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7876 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 Po~9@QPU}vhFZ}-ia8T6A diff --git a/bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc b/bot/extends/base/__pycache__/tmp_not_used.cpython-311.pyc deleted file mode 100644 index 474e62db6d699a57dac5f9d0792d9cef67d52a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6031 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 diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index c512069..51cfaa7 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -6,7 +6,6 @@ from dotenv import load_dotenv load_dotenv() -VERSION = os.getenv('BOT_BASE_VERSION') sys.path.append('..') @@ -54,7 +53,7 @@ async def _credits(self, event): # send version of the bot @commands.command(name="version", help='affiche la version du bot', aliases=['v'], ) async def _version(self, event): - txt = f'URBot_base version : {VERSION}' + txt = version() # get directory path pwd = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # for each directory @@ -115,5 +114,8 @@ async def setup(bot): await bot.add_cog(Base(bot)) -# def version(): -# return f'URBot_base version : {VERSION}' +def version(): + #read file + with open('version.txt', 'r') as f: + VERSION = f.read() + return f'URBot_base version : {VERSION}' diff --git a/bot/extends/base/version.txt b/bot/extends/base/version.txt new file mode 100644 index 0000000..60a2d3e --- /dev/null +++ b/bot/extends/base/version.txt @@ -0,0 +1 @@ +0.4.0 \ No newline at end of file diff --git a/default.docker-compose.yml b/default.docker-compose.yml deleted file mode 100644 index fbc26db..0000000 --- a/default.docker-compose.yml +++ /dev/null @@ -1,42 +0,0 @@ -version: '3' -name: Union-des-rolistes - -services: - bot: - build: ./bot #utilise le dockerfile pour cree le container a utiliser - container_name: bot_base - restart: always #redemarre le container si il s'eteint - environment: - - DISCORD_TOKEN=--------------------------------------- #token pour comm avec discord - - BOT_PREFIX=$ #prefix pour les commandes - - BOT_BASE_VERSION=x.x.x #version du bot - volumes: - - ./Bot/extends/base:/app/extends/base #dossier ou est l'extension de la base - api: - build: ./api #utilise le dockerfile pour cree le container a utiliser - container_name: api_base - ports: - - "3000:3000" - restart: always #redemarre le container si il s'eteint - environment: - #reuse les variables d'environnement du container bot_base - - DISCORD_TOKEN=--------------------------------------- - - PYTHONUNBUFFERED=1 #get print of python - php: - image: php:8-apache - build: ./apache #utilise le dockerfile pour cree le container a utiliser - container_name: php_base - # environment: - # - APACHE_HTTP_PORT_NUMBER=80 - volumes: - # - ./apache/default-httpd.conf:/etc/apache2/apache2.conf:ro # si besoin de modifier le fichier de conf apache (normalement pas besoin) - - ./apache/vhost/000_api.conf:/etc/apache2/sites-enabled/000_api.conf:ro #fichier de conf apache pour l'api - ports: - - "80:80" - links: - - api - restart: always - depends_on: - # attend le lancement de ces containers avant de lancer le container - - bot - - api diff --git a/docker-compose.yml b/docker-compose.yml index fbc26db..3c1d423 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,10 +6,9 @@ services: build: ./bot #utilise le dockerfile pour cree le container a utiliser container_name: bot_base restart: always #redemarre le container si il s'eteint - environment: - - DISCORD_TOKEN=--------------------------------------- #token pour comm avec discord - - BOT_PREFIX=$ #prefix pour les commandes - - BOT_BASE_VERSION=x.x.x #version du bot + env_file: + - ./env/token.env + - ./env/bot.env volumes: - ./Bot/extends/base:/app/extends/base #dossier ou est l'extension de la base api: @@ -19,9 +18,9 @@ services: - "3000:3000" restart: always #redemarre le container si il s'eteint environment: - #reuse les variables d'environnement du container bot_base - - DISCORD_TOKEN=--------------------------------------- - PYTHONUNBUFFERED=1 #get print of python + env_file: + - ./env/token.env # get token php: image: php:8-apache build: ./apache #utilise le dockerfile pour cree le container a utiliser diff --git a/env/default.bot.env b/env/default.bot.env new file mode 100644 index 0000000..8921015 --- /dev/null +++ b/env/default.bot.env @@ -0,0 +1,2 @@ +BOT_PREFIX=$ # prefix pour les commandes +BOT_BASE_VERSION=x.x.x # version du bot (va sauté) \ No newline at end of file diff --git a/env/default.oauth.env b/env/default.oauth.env new file mode 100644 index 0000000..1b69f12 --- /dev/null +++ b/env/default.oauth.env @@ -0,0 +1,2 @@ +CLIENT_ID=xxxx +CLIENT_SECRET=xxxx \ No newline at end of file diff --git a/env/default.token.env b/env/default.token.env new file mode 100644 index 0000000..fa05e7c --- /dev/null +++ b/env/default.token.env @@ -0,0 +1 @@ +DISCORD_TOKEN=xxxx #token pour comm avec discord diff --git a/script/config.sh b/script/config.sh new file mode 100644 index 0000000..5317ddc --- /dev/null +++ b/script/config.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +cd .. +cd env + +# for each file in the env directory +for file in ./; do + # if file is a default.*.env file + if [[ $file == default.*.env ]]; then + # check if the file wihout the default exists + env_name=${file#default.} + env_name=${env_name%.env} + #print value of env_name + echo $env_name + # if it does not exist, create it + if [ ! -f $env_name.env ]; then + touch $env_name.env + fi + # append the contents of the default file to the env file + cat $file >> $env_name.env + fi + + + # #example if, if else, else + # if [[ $file == default.*.env ]]; then + # env_name=${file#default.} + # env_name=${env_name%.env} + # touch $env_name.env + # cat $file >> $env_name.env + # elif [[ $file == *.env ]]; then + # env_name=${file%.env} + # touch $env_name.env + # cat $file >> $env_name.env + # else + # echo "File $file is not a valid env file" + # fi diff --git a/script/dotenv.sh b/script/dotenv.sh new file mode 100644 index 0000000..97578fe --- /dev/null +++ b/script/dotenv.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# --- +# This file is automatically generated from dotenv.md - DO NOT EDIT +# --- + +__dotenv= +__dotenv_file= +__dotenv_cmd=.env + +.env() { + REPLY=() + [[ $__dotenv_file || ${1-} == -* ]] || .env.--file .env || return + if declare -F -- ".env.${1-}" >/dev/null; then .env."$@"; return ; fi + .env --help >&2; return 64 +} + +.env.-f() { .env.--file "$@"; } + +.env.get() { + .env::arg "get requires a key" "$@" && + [[ "$__dotenv" =~ ^(.*(^|$'\n'))([ ]*)"$1="(.*)$ ]] && + REPLY=${BASH_REMATCH[4]%%$'\n'*} && REPLY=${REPLY%"${REPLY##*[![:space:]]}"} +} + +.env.parse() { + local line key + while IFS= read -r line; do + line=${line#"${line%%[![:space:]]*}"} # trim leading whitespace + line=${line%"${line##*[![:space:]]}"} # trim trailing whitespace + if [[ ! "$line" || "$line" == '#'* ]]; then continue ; fi + if (($#)); then + for key; do + if [[ $key == "${line%%=*}" ]]; then REPLY+=("$line"); break; + fi + done + else + REPLY+=("$line") + fi + done <<<"$__dotenv" + ((${#REPLY[@]})) +} + +.env.export() { ! .env.parse "$@" || export "${REPLY[@]}"; } + +.env.set() { + .env::file load || return ; local key saved=$__dotenv + while (($#)); do + key=${1#+}; key=${key%%=*} + if .env.get "$key"; then + REPLY=() + if [[ $1 == +* ]]; then shift; continue # skip if already found + elif [[ $1 == *=* ]]; then + __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[3]}$1$'\n'${BASH_REMATCH[4]#*$'\n'} + else + __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[4]#*$'\n'} + continue # delete all occurrences + fi + elif [[ $1 == *=* ]]; then + __dotenv+="${1#+}"$'\n' + fi + shift + done + [[ $__dotenv == "$saved" ]] || .env::file save +} + +.env.puts() { echo "${1-}">>"$__dotenv_file" && __dotenv+="$1"$'\n'; } + +.env.generate() { + .env::arg "key required for generate" "$@" || return + .env.get "$1" && return || REPLY=$("${@:2}") || return + .env::one "generate: ouptut of '${*:2}' has more than one line" "$REPLY" || return + .env.puts "$1=$REPLY" +} + +.env.--file() { + .env::arg "filename required for --file" "$@" || return + __dotenv_file=$1; .env::file load || return + (($#<2)) || .env "${@:2}" +} + +.env::arg() { [[ "${2-}" ]] || { echo "$__dotenv_cmd: $1" >&2; return 64; }; } + +.env::one() { [[ "$2" != *$'\n'* ]] || .env::arg "$1"; } + +.env::file() { + local REPLY=$__dotenv_file + case "$1" in + load) + __dotenv=; ! [[ -f "$REPLY" ]] || __dotenv="$(<"$REPLY")"$'\n' || return ;; + save) + if [[ -L "$REPLY" ]] && declare -F -- realpath.resolved >/dev/null; then + realpath.resolved "$REPLY" + fi + { [[ ! -f "$REPLY" ]] || cp -p "$REPLY" "$REPLY.bak"; } && + printf %s "$__dotenv" >"$REPLY.bak" && mv "$REPLY.bak" "$REPLY" + esac +} +.env.-h() { .env.--help "$@"; } +.env.--help() { + echo "Usage: + $__dotenv_cmd [-f|--file FILE] COMMAND [ARGS...] + $__dotenv_cmd -h|--help +Options: + -f, --file FILE Use a file other than .env +Read Commands: + get KEY Get raw value of KEY (or fail) + parse [KEY...] Get trimmed KEY=VALUE lines for named keys (or all) + export [KEY...] Export the named keys (or all) in shell format +Write Commands: + set [+]KEY[=VALUE]... Set or unset values (in-place w/.bak); + sets default + puts STRING Append STRING to the end of the file + generate KEY [CMD...] Set KEY to the output of CMD unless it already exists; + return the new or existing value." +} + +__dotenv() { + set -eu + __dotenv_cmd=${0##*/} + .env.export() { .env.parse "$@" || return 0; printf 'export %q\n' "${REPLY[@]}"; REPLY=(); } + .env "$@" || return $? + ${REPLY[@]+printf '%s\n' "${REPLY[@]}"} +} +if [[ $0 == "${BASH_SOURCE-}" ]]; then __dotenv "$@"; exit; fi \ No newline at end of file diff --git a/script/readme.md b/script/readme.md new file mode 100644 index 0000000..477cbcd --- /dev/null +++ b/script/readme.md @@ -0,0 +1,39 @@ + +# script + +## config.sh + +## EXT script + + + +### dotenv cli + + $ dotenv --help + Usage: + dotenv [-f|--file FILE] COMMAND [ARGS...] + dotenv -h|--help + + Options: + -f, --file FILE Use a file other than .env + + Read Commands: + get KEY Get raw value of KEY (or fail) + parse [KEY...] Get trimmed KEY=VALUE lines for named keys (or all) + export [KEY...] Export the named keys (or all) in shell format + + Write Commands: + set [+]KEY[=VALUE]... Set or unset values (in-place w/.bak); + sets default + puts STRING Append STRING to the end of the file + generate KEY [CMD...] Set KEY to the output of CMD unless it already exists; + return the new or existing value. + + $ echo ' # This is my .env file' >prod.env + $ echo ' FOO=bar ' >>prod.env + + $ cat prod.env + # This is my .env file + FOO=bar + + $ dotenv -f prod.env get FOO + bar From 90942685237bfe36cd0627cd344fa6f0ef6374fa Mon Sep 17 00:00:00 2001 From: mortifia Date: Fri, 13 Jan 2023 14:23:45 +0100 Subject: [PATCH 18/29] tmp --- env/default.bot.env | 2 +- env/default.token.env | 2 +- script/config.sh | 40 +++++++++++++++++++++++++++++++++++----- script/dotenv.sh | 0 4 files changed, 37 insertions(+), 7 deletions(-) mode change 100644 => 100755 script/config.sh mode change 100644 => 100755 script/dotenv.sh diff --git a/env/default.bot.env b/env/default.bot.env index 8921015..27eaee2 100644 --- a/env/default.bot.env +++ b/env/default.bot.env @@ -1,2 +1,2 @@ BOT_PREFIX=$ # prefix pour les commandes -BOT_BASE_VERSION=x.x.x # version du bot (va sauté) \ No newline at end of file +BOT_BASE_VERSION=x.x.x # version du bot (va sauter) \ No newline at end of file diff --git a/env/default.token.env b/env/default.token.env index fa05e7c..46c4519 100644 --- a/env/default.token.env +++ b/env/default.token.env @@ -1 +1 @@ -DISCORD_TOKEN=xxxx #token pour comm avec discord +DISCORD_TOKEN=xxxx #token pour comm avec discord \ No newline at end of file diff --git a/script/config.sh b/script/config.sh old mode 100644 new mode 100755 index 5317ddc..c6ac5fa --- a/script/config.sh +++ b/script/config.sh @@ -1,23 +1,53 @@ -#!/usr/bin/env bash cd .. cd env # for each file in the env directory -for file in ./; do +for file in *; do # if file is a default.*.env file if [[ $file == default.*.env ]]; then + echo $file # check if the file wihout the default exists env_name=${file#default.} env_name=${env_name%.env} - #print value of env_name echo $env_name + + #print value of env_name # if it does not exist, create it if [ ! -f $env_name.env ]; then touch $env_name.env fi - # append the contents of the default file to the env file - cat $file >> $env_name.env + # for each line in the default file + while IFS=: read -r -u 9 line || [ -n "$line" ]; do # || [ -n "$line" ] est tres important sinon le dernier element ne sera pas lu + # if the line is not in the file, add it + if ! grep -q "$line" $env_name.env; then + #get env variable name + env_var=${line%=*} + #get env variable value without comment + env_value=${line#*=} + #remove comment + env_value=${env_value%%#*} + #if line has a comment + if [[ $line == *#* ]]; then + #get comment with comment symbol + env_comment=\#${line#*#} + else + env_comment="" + fi + echo $line + echo "name: $env_var || value: $env_value || comment: $env_comment" + echo "---------------------------------" + #ask user to change value of env variable + echo "Enter value for $env_var (default: $env_value):" + printf "" + read varname + if [ -z "$varname" ]; then + varname=$env_value + fi + + fi + done 9< $file fi +done # #example if, if else, else diff --git a/script/dotenv.sh b/script/dotenv.sh old mode 100644 new mode 100755 From c355d66d4f09182a93e6f1cddcb011b93f48e228 Mon Sep 17 00:00:00 2001 From: mortifia Date: Sat, 14 Jan 2023 22:43:52 +0100 Subject: [PATCH 19/29] tmp update script --- .gitignore | 1 + docker-compose.yml | 18 +++---- script/build_all.sh | 4 ++ script/config.sh | 61 +++++++++++++-------- script/config_all.sh | 11 ++++ script/dotenv.sh | 123 ------------------------------------------- script/readme.md | 43 ++++++--------- script/start.sh | 2 + 8 files changed, 81 insertions(+), 182 deletions(-) create mode 100755 script/build_all.sh create mode 100755 script/config_all.sh delete mode 100755 script/dotenv.sh create mode 100755 script/start.sh diff --git a/.gitignore b/.gitignore index bb1ee54..f04d462 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ test.yml +compose-build.yml __pycache__/ diff --git a/docker-compose.yml b/docker-compose.yml index 3c1d423..1597e80 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,16 +3,16 @@ name: Union-des-rolistes services: bot: - build: ./bot #utilise le dockerfile pour cree le container a utiliser + build: ${PWD}/bot #utilise le dockerfile pour cree le container a utiliser container_name: bot_base restart: always #redemarre le container si il s'eteint env_file: - - ./env/token.env - - ./env/bot.env + - ${PWD}/env/token.env + - ${PWD}/env/bot.env volumes: - - ./Bot/extends/base:/app/extends/base #dossier ou est l'extension de la base + - ${PWD}/bot/extends/base:/app/extends/base #dossier ou est l'extension de la base api: - build: ./api #utilise le dockerfile pour cree le container a utiliser + build: ${PWD}/api #utilise le dockerfile pour cree le container a utiliser container_name: api_base ports: - "3000:3000" @@ -20,16 +20,14 @@ services: environment: - PYTHONUNBUFFERED=1 #get print of python env_file: - - ./env/token.env # get token + - ${PWD}/env/token.env # get token php: image: php:8-apache - build: ./apache #utilise le dockerfile pour cree le container a utiliser + build: ${PWD}/apache #utilise le dockerfile pour cree le container a utiliser container_name: php_base - # environment: - # - APACHE_HTTP_PORT_NUMBER=80 volumes: # - ./apache/default-httpd.conf:/etc/apache2/apache2.conf:ro # si besoin de modifier le fichier de conf apache (normalement pas besoin) - - ./apache/vhost/000_api.conf:/etc/apache2/sites-enabled/000_api.conf:ro #fichier de conf apache pour l'api + - ${PWD}/apache/vhost/000_api.conf:/etc/apache2/sites-enabled/000_api.conf:ro #fichier de conf apache pour l'api ports: - "80:80" links: diff --git a/script/build_all.sh b/script/build_all.sh new file mode 100755 index 0000000..74af145 --- /dev/null +++ b/script/build_all.sh @@ -0,0 +1,4 @@ +# find all docker-compose.yml files with depth 3 add '-f' to each file stored in COMPOSE_FILES var +COMPOSE_FILES=`find ../ -maxdepth 3 -name docker-compose.yml -exec echo -f {} \;` +# echo $COMPOSE_FILES +docker-compose $COMPOSE_FILES config > compose-build.yml \ No newline at end of file diff --git a/script/config.sh b/script/config.sh index c6ac5fa..51a213e 100755 --- a/script/config.sh +++ b/script/config.sh @@ -1,49 +1,66 @@ -cd .. -cd env +#!/bin/bash +# run to env directory and execute this script to create env files and set values # for each file in the env directory for file in *; do # if file is a default.*.env file if [[ $file == default.*.env ]]; then - echo $file # check if the file wihout the default exists env_name=${file#default.} env_name=${env_name%.env} - echo $env_name - - #print value of env_name + echo "---- $env_name.env ----" # if it does not exist, create it if [ ! -f $env_name.env ]; then touch $env_name.env fi # for each line in the default file while IFS=: read -r -u 9 line || [ -n "$line" ]; do # || [ -n "$line" ] est tres important sinon le dernier element ne sera pas lu - # if the line is not in the file, add it - if ! grep -q "$line" $env_name.env; then + # trim start of line + line="${line#"${line%%[![:space:]]*}"}" + # if line is not empty and does not start with # + if [[ ! -z $line ]] && [[ $line != \#* ]]; then + #get env variable name env_var=${line%=*} - #get env variable value without comment + #get env variable value env_value=${line#*=} - #remove comment + #remove comment if exist env_value=${env_value%%#*} - #if line has a comment - if [[ $line == *#* ]]; then - #get comment with comment symbol - env_comment=\#${line#*#} - else - env_comment="" + # #if line has a comment get it + # if [[ $line == *#* ]]; then + # #get comment with comment symbol + # env_comment=\#${line#*#} + # else + # env_comment="" + # fi + + # if env_var exist in $env_name.env rewrite env_value with value in $env_name.env + # ps use actual value user as default valur + if grep -q "$env_var" $env_name.env; then + env_value=$(grep "$env_var" $env_name.env | cut -d '=' -f2) + env_value=${env_value%%#*} fi - echo $line - echo "name: $env_var || value: $env_value || comment: $env_comment" - echo "---------------------------------" #ask user to change value of env variable echo "Enter value for $env_var (default: $env_value):" printf "" - read varname - if [ -z "$varname" ]; then - varname=$env_value + read input + # if varname as character + if test ! -z "$input"; then + env_value=$input fi + #if env_var exist in $env_name.env rewrite it + if grep -q "$env_var" $env_name.env; then + sed -i "s/$env_var=.*/$env_var=$env_value/g" $env_name.env + else + #add line to file + echo "$env_var=$env_value" >> $env_name.env + fi + + + ##add line to file + #echo "$env_var=$env_value $env_comment" >> $env_name.env + #------------------------------------------------------------------------------------------------ fi done 9< $file fi diff --git a/script/config_all.sh b/script/config_all.sh new file mode 100755 index 0000000..74b31c0 --- /dev/null +++ b/script/config_all.sh @@ -0,0 +1,11 @@ +cd .. +# for each directory in the current directory +for dir in *; do + # if env directory exist + if [ -d "$dir/env" ]; then + #run config.sh in env directory + cd $dir/env + ../../Bot_Base/script/config.sh + cd ../.. + fi +done \ No newline at end of file diff --git a/script/dotenv.sh b/script/dotenv.sh deleted file mode 100755 index 97578fe..0000000 --- a/script/dotenv.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -# --- -# This file is automatically generated from dotenv.md - DO NOT EDIT -# --- - -__dotenv= -__dotenv_file= -__dotenv_cmd=.env - -.env() { - REPLY=() - [[ $__dotenv_file || ${1-} == -* ]] || .env.--file .env || return - if declare -F -- ".env.${1-}" >/dev/null; then .env."$@"; return ; fi - .env --help >&2; return 64 -} - -.env.-f() { .env.--file "$@"; } - -.env.get() { - .env::arg "get requires a key" "$@" && - [[ "$__dotenv" =~ ^(.*(^|$'\n'))([ ]*)"$1="(.*)$ ]] && - REPLY=${BASH_REMATCH[4]%%$'\n'*} && REPLY=${REPLY%"${REPLY##*[![:space:]]}"} -} - -.env.parse() { - local line key - while IFS= read -r line; do - line=${line#"${line%%[![:space:]]*}"} # trim leading whitespace - line=${line%"${line##*[![:space:]]}"} # trim trailing whitespace - if [[ ! "$line" || "$line" == '#'* ]]; then continue ; fi - if (($#)); then - for key; do - if [[ $key == "${line%%=*}" ]]; then REPLY+=("$line"); break; - fi - done - else - REPLY+=("$line") - fi - done <<<"$__dotenv" - ((${#REPLY[@]})) -} - -.env.export() { ! .env.parse "$@" || export "${REPLY[@]}"; } - -.env.set() { - .env::file load || return ; local key saved=$__dotenv - while (($#)); do - key=${1#+}; key=${key%%=*} - if .env.get "$key"; then - REPLY=() - if [[ $1 == +* ]]; then shift; continue # skip if already found - elif [[ $1 == *=* ]]; then - __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[3]}$1$'\n'${BASH_REMATCH[4]#*$'\n'} - else - __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[4]#*$'\n'} - continue # delete all occurrences - fi - elif [[ $1 == *=* ]]; then - __dotenv+="${1#+}"$'\n' - fi - shift - done - [[ $__dotenv == "$saved" ]] || .env::file save -} - -.env.puts() { echo "${1-}">>"$__dotenv_file" && __dotenv+="$1"$'\n'; } - -.env.generate() { - .env::arg "key required for generate" "$@" || return - .env.get "$1" && return || REPLY=$("${@:2}") || return - .env::one "generate: ouptut of '${*:2}' has more than one line" "$REPLY" || return - .env.puts "$1=$REPLY" -} - -.env.--file() { - .env::arg "filename required for --file" "$@" || return - __dotenv_file=$1; .env::file load || return - (($#<2)) || .env "${@:2}" -} - -.env::arg() { [[ "${2-}" ]] || { echo "$__dotenv_cmd: $1" >&2; return 64; }; } - -.env::one() { [[ "$2" != *$'\n'* ]] || .env::arg "$1"; } - -.env::file() { - local REPLY=$__dotenv_file - case "$1" in - load) - __dotenv=; ! [[ -f "$REPLY" ]] || __dotenv="$(<"$REPLY")"$'\n' || return ;; - save) - if [[ -L "$REPLY" ]] && declare -F -- realpath.resolved >/dev/null; then - realpath.resolved "$REPLY" - fi - { [[ ! -f "$REPLY" ]] || cp -p "$REPLY" "$REPLY.bak"; } && - printf %s "$__dotenv" >"$REPLY.bak" && mv "$REPLY.bak" "$REPLY" - esac -} -.env.-h() { .env.--help "$@"; } -.env.--help() { - echo "Usage: - $__dotenv_cmd [-f|--file FILE] COMMAND [ARGS...] - $__dotenv_cmd -h|--help -Options: - -f, --file FILE Use a file other than .env -Read Commands: - get KEY Get raw value of KEY (or fail) - parse [KEY...] Get trimmed KEY=VALUE lines for named keys (or all) - export [KEY...] Export the named keys (or all) in shell format -Write Commands: - set [+]KEY[=VALUE]... Set or unset values (in-place w/.bak); + sets default - puts STRING Append STRING to the end of the file - generate KEY [CMD...] Set KEY to the output of CMD unless it already exists; - return the new or existing value." -} - -__dotenv() { - set -eu - __dotenv_cmd=${0##*/} - .env.export() { .env.parse "$@" || return 0; printf 'export %q\n' "${REPLY[@]}"; REPLY=(); } - .env "$@" || return $? - ${REPLY[@]+printf '%s\n' "${REPLY[@]}"} -} -if [[ $0 == "${BASH_SOURCE-}" ]]; then __dotenv "$@"; exit; fi \ No newline at end of file diff --git a/script/readme.md b/script/readme.md index 477cbcd..b05ec8a 100644 --- a/script/readme.md +++ b/script/readme.md @@ -1,39 +1,28 @@ - # script ## config.sh -## EXT script - - +il permet de mettre a jour tout les env sans avoir a tripatouiller les fichiers .env -### dotenv cli +## config_all.sh - $ dotenv --help - Usage: - dotenv [-f|--file FILE] COMMAND [ARGS...] - dotenv -h|--help +il permet de mettre a jour tout les env sans avoir a tripatouiller les fichiers (met aussi a jour les env de toutes les extension).env - Options: - -f, --file FILE Use a file other than .env +## build_all.sh - Read Commands: - get KEY Get raw value of KEY (or fail) - parse [KEY...] Get trimmed KEY=VALUE lines for named keys (or all) - export [KEY...] Export the named keys (or all) in shell format +il construit tout le docker-compose avec la base et toutes les extensions - Write Commands: - set [+]KEY[=VALUE]... Set or unset values (in-place w/.bak); + sets default - puts STRING Append STRING to the end of the file - generate KEY [CMD...] Set KEY to the output of CMD unless it already exists; - return the new or existing value. +## start - $ echo ' # This is my .env file' >prod.env - $ echo ' FOO=bar ' >>prod.env +lance le docker-compose \ +(il faut avoir construit le docker-compose avant) \ +(les services serotn temporairement inacessible moins de 10s) - $ cat prod.env - # This is my .env file - FOO=bar +## usage - $ dotenv -f prod.env get FOO - bar +```bash + # pwd = /xxxxxxxxxxxxx/union_des_rolistes/Bot_Base + ./config_all.sh + ./build_all.sh + ./start.sh +``` diff --git a/script/start.sh b/script/start.sh new file mode 100755 index 0000000..94ad2a4 --- /dev/null +++ b/script/start.sh @@ -0,0 +1,2 @@ +# cd .. +docker-compose -f compose-build.yml up -d --build --force-recreate --remove-orphans \ No newline at end of file From 41f7702959da47b482dd9f77fc8a0b5143af8db5 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Mon, 19 Jun 2023 14:30:21 +0200 Subject: [PATCH 20/29] 20 $help full color (#36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * help * tmp * install (need test and better ux (var in .env not directly in environement) * Modification du fichier .giitgnore * Revert "Modification du fichier .giitgnore" This reverts commit a563a3c3f77e43701e23a6da926bd1c12e415e14. * 32 env ne doit pas etre tracker commit sécurité (#33) .gitignore updated and track removed from .env * delete .env (only .env.default in repo) * Modification du fichier .giitgnore * Revert "Modification du fichier .giitgnore" This reverts commit 687d4adb770bfe30f457590f27083f04bac8c768. * not track .pyc files --------- Co-authored-by: rayan alhaj * 34 enhance readme to add how to dev with a extend for dev wiout docker (#35) * MISE A JOUR DU README * MISE A JOUR DU README --------- Co-authored-by: rayan alhaj --------- Co-authored-by: rayan alhaj --- .dockerignore | 1 + README.md | 11 ++++++++++- bot/.env | 3 --- 3 files changed, 11 insertions(+), 4 deletions(-) delete mode 100644 bot/.env diff --git a/.dockerignore b/.dockerignore index ff25d66..da94bde 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .env # Bot/.env extends +.pyc diff --git a/README.md b/README.md index ab7c322..c07ccae 100755 --- a/README.md +++ b/README.md @@ -41,4 +41,13 @@ pour travailler en local penser a faire mentir le dns de votre machine pour que le nom de domaine voulu pointe la ou il faut - windows : `C:\WINDOWS\system32\drivers\etc\hosts` -- linux : `/etc/hosts` \ No newline at end of file +- linux : `/etc/hosts` + +## Développer sous windows sans utiliser docker +L'utilisation du lien symbolique est la meilleur solution dans ce cas. +car copier-coler a chaque fois ou git submodule sont des solution plus compliquer a tester pour dev. +Cette méthode est conseillée quand on travaille sur un plugin. +Exemple: +```cmd + cmd /c mklink /D ..\Bot_Base\bot\extends\Presentation ..\..\..\Bot_Presentation\bot\extends +``` \ No newline at end of file diff --git a/bot/.env b/bot/.env deleted file mode 100644 index e6544fb..0000000 --- a/bot/.env +++ /dev/null @@ -1,3 +0,0 @@ -# token for dev -DISCORD_TOKEN="--------------------------------------" -BOT_PREFIX="$" \ No newline at end of file From 0db9c96b2aefd91feaa91c1652830134b58d5a8f Mon Sep 17 00:00:00 2001 From: rayan Date: Mon, 19 Jun 2023 16:37:16 +0200 Subject: [PATCH 21/29] remplacer "u" par "U" --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1597e80..841f43e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ version: '3' -name: Union-des-rolistes +name: union-des-rolistes services: bot: From 98c1fdf6ef31d34a524b47b9c404df8593a9caa2 Mon Sep 17 00:00:00 2001 From: rayan Date: Tue, 20 Jun 2023 13:11:20 +0200 Subject: [PATCH 22/29] Mise a jour du readme --- script/readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/script/readme.md b/script/readme.md index b05ec8a..b244171 100644 --- a/script/readme.md +++ b/script/readme.md @@ -26,3 +26,11 @@ lance le docker-compose \ ./build_all.sh ./start.sh ``` +## clone bot and plug-in +```bash +cd Bot-Base +./script/config_all.sh +./script/build_all.sh +./script/start.sh +./update.sh (normalemnt pas besoin) +... \ No newline at end of file From 0d038288cfb0aa0e374eb0e129caec12ddca9b01 Mon Sep 17 00:00:00 2001 From: rayan Date: Thu, 22 Jun 2023 15:08:27 +0200 Subject: [PATCH 23/29] Mise a jour du readme --- script/readme.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/script/readme.md b/script/readme.md index b244171..4708c95 100644 --- a/script/readme.md +++ b/script/readme.md @@ -33,4 +33,23 @@ cd Bot-Base ./script/build_all.sh ./script/start.sh ./update.sh (normalemnt pas besoin) -... \ No newline at end of file +... +``` +## installation et paramétrage de Docker et WSL + +pour windows: +installer wsl -> https://learn.microsoft.com/fr-fr/windows/wsl/install +installer docker -> https://docs.docker.com/engine/install/ + +pour linux: +installer docker -> https://docs.docker.com/engine/install/ubuntu/ +liste des commandes: +1-sudo apt-get install docker.io +2-sudo systemctl +3-start docker +4-sudo usermod -aG docker your_username +5-docker version +6-mkdir dossier +7-cd dossier +8-git clone lien-site_ +9-docker-compose -f docker-compose.yml ../Bot_Planning_python/bot/docker-compose.yml config > conf-build-docker-compose.yml \ No newline at end of file From 17829a42ec3f79bbaaa02d91212524bc2c7eaf22 Mon Sep 17 00:00:00 2001 From: Mortifia Date: Fri, 30 Jun 2023 12:28:48 +0200 Subject: [PATCH 24/29] 37 fixation du bug $v (#38) * fix bug * fix bug --------- Co-authored-by: rayan --- bot/extends/base/base.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index 51cfaa7..6b4eebb 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -115,7 +115,12 @@ async def setup(bot): def version(): - #read file - with open('version.txt', 'r') as f: - VERSION = f.read() + try: + # Lecture du fichier + with open('version.txt', 'r') as f: + VERSION = f.read() return f'URBot_base version : {VERSION}' + except FileNotFoundError: + return 'Erreur : le fichier version.txt est introuvable.' + except Exception as e: + return f'Erreur lors de la lecture du fichier : {str(e)}' From 0a524132956d2027951c0b02fc55e3c417da4810 Mon Sep 17 00:00:00 2001 From: Younes B Date: Wed, 30 Aug 2023 17:04:52 +0200 Subject: [PATCH 25/29] Update credits.txt (#45) ajout d'un nouveau contributeur --- .gitignore | 1 + bot/extends/base/credits.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f04d462..473e456 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ test.yml compose-build.yml +.docker-compose-merge.yml __pycache__/ diff --git a/bot/extends/base/credits.txt b/bot/extends/base/credits.txt index 0fead11..ea2f99e 100644 --- a/bot/extends/base/credits.txt +++ b/bot/extends/base/credits.txt @@ -4,3 +4,4 @@ URBot_base Le bot discord de l'Union des Rôlistes - Eὔδοξος#2170 - Lyss#4053 - Maestro#8364 + - phrixus#7269 From 4ce74570910d5d3395469ae184a650a71628ffd2 Mon Sep 17 00:00:00 2001 From: Younes Bourakadi Date: Thu, 31 Aug 2023 15:27:32 +0200 Subject: [PATCH 26/29] Refactor base.py et ajouter couleurs aux messages --- bot/extends/base/base.py | 236 ++++++++++++++++++++--------------- bot/extends/base/credits.txt | 2 +- 2 files changed, 134 insertions(+), 104 deletions(-) diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index 6b4eebb..a588bc6 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -1,126 +1,156 @@ -import sys -import asyncio -import importlib import os +import asyncio from discord.ext import commands from dotenv import load_dotenv load_dotenv() -sys.path.append('..') - -# class base with case insensitive - - class Base(commands.Cog, name='Base'): def __init__(self, bot: commands.Bot): self.bot = bot self._last_member = None - async def cog_load(self): # called when the cog is loaded - print(self.__class__.__name__ + " is loaded") + # Chargement initial du Cog + async def cog_load(self): + print(f"{self.__class__.__name__} est chargée") - @commands.command(name="ping", help='ping pong with the bot', aliases=['pong', 'p'], ) + # Commande de ping + @commands.command(name="ping", help='ping pong avec le bot', aliases=['pong', 'p']) @commands.guild_only() - async def _ping(self, event): - await asyncio.gather( # concurent await - event.message.add_reaction('🏓'), - event.send('Pong! 🏓 {0} ms'.format( - round(self.bot.latency, 3) * 1000)) + async def _ping(self, ctx): + await self._send_pong_response(ctx) + + # Commande d'affichage des crédits + @commands.command(name="credits", help='affiche les crédits', aliases=['credit', 'c']) + async def _credits(self, ctx): + credits = await self._get_credits() + if credits: + await ctx.send(credits) + else: + await ctx.send("Aucune information de crédits disponible.") + + # Commande d'affichage des versions + @commands.command(name="version", help='affiche la version du bot', aliases=['v']) + async def _version(self, ctx): + versions = await self._get_versions() + if versions: + await ctx.send(versions) + else: + await ctx.send("Aucune information de versions disponible.") + + # Commande d'aide + @commands.command(name="help", help='affiche les commandes, alias et description', aliases=['h', '?']) + async def _help(self, ctx): + help_msg = await self._generate_help_message() + await ctx.send(help_msg) + + # Envoie une réponse "Pong !" avec le temps de latence du bot + async def _send_pong_response(self, ctx): + latency = round(self.bot.latency, 3) * 1000 + await asyncio.gather( + ctx.message.add_reaction('🏓'), + ctx.send(f'Pong ! 🏓 {latency} ms') ) - @commands.command(name="credits", help='affiche les credits', aliases=['credit', 'c'], ) - async def _credits(self, event): - credits = "```properties\n" - # get directory path - pwd = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # load local credits first - try: - with open(f'{pwd}/credits.txt', 'r') as f: - credits += f.read() - except Exception as e: - print(e) - # for each directory - for directory in os.listdir(pwd): - # read credits file - try: - with open(f'{pwd}/{directory}/credits.txt', 'r') as f: - credits += '\n' + f.read() - except Exception as e: - print(e) - await event.send(credits+'```') + # Récupère les informations de crédits depuis les fichiers + async def _get_credits(self): + credits = "" + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + for directory in os.listdir(parent_dir): + credits_file_path = os.path.join(parent_dir, directory, 'credits.txt') + if os.path.exists(credits_file_path): + try: + with open(credits_file_path, 'r') as f: + credits += f.read() + '\n\n' + except Exception as e: + print(e) + if credits: + credits_lines = credits.splitlines() + formatted_credits = ( + f"```ansi\n" + f"\x1b[1;34m{credits_lines[0]}\x1b[0m\n" # Titre en bleu foncé + f"\x1b[1;32m{credits_lines[1]}\x1b[0m\n" # Ligne en vert clair + ) + for line in credits_lines[2:]: + formatted_credits += f"\x1b[1;36m{line}\x1b[0m\n" # Lignes en cyan clair + formatted_credits += "```" + return formatted_credits + else: + return None - # send version of the bot - @commands.command(name="version", help='affiche la version du bot', aliases=['v'], ) - async def _version(self, event): - txt = version() - # get directory path - pwd = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # for each directory - for directory in os.listdir(pwd): - # for each file in the directory - for file in os.listdir(f'{pwd}/{directory}'): - # if the file is a python file - if file.endswith('.py') and not file.startswith('__'): - # get the file name without the extension - file_name = file[:-3] - # try import the function version in the file - try: - print( - f'try run : extends.{directory}.{file_name}.version()') - module = importlib.import_module( - f'extends.{directory}.{file_name}').version() - try: - txt += f'\n{module}' - except Exception as e: - print(e) - except Exception as e: - print(e) - await event.send(txt) + # Récupère les informations de versions depuis les fichiers + async def _get_versions(self): + versions = "" + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + directory_names = { + 'base': 'Bot_Base', + 'prez': 'Bot_Presentation', + 'planing': 'Bot_Planning', + 'site': 'Web_Site' + } + for directory in directory_names: + version_file_path = os.path.join(parent_dir, directory, 'version.txt') + if os.path.exists(version_file_path): + try: + with open(version_file_path, 'r') as f: + version = f.read().strip() + formatted_version = ( + f"```ansi\n" + f"\x1b[1;34m{directory_names[directory]}\x1b[0m : \x1b[1;37mVersion \x1b[1;33m{version}\x1b[0m\n" + f"```" + ) + versions += formatted_version + except Exception as e: + print(e) + else: + formatted_version = ( + f"```ansi\n" + f"\x1b[1;34m{directory_names[directory]}\x1b[0m : \x1b[1;31mVersion introuvable\x1b[0m\n" + f"```" + ) + versions += formatted_version + if versions: + return versions + else: + return None + + # Génère un message d'aide avec les commandes triées par catégorie + async def _generate_help_message(self): + commands_by_category = self._get_commands_by_category() + help_msg = ( + f"```ansi\n" + f"\x1b[2;34;4mPréfixe\x1b[0m \x1b[2;31m{self.bot.command_prefix}\x1b[0m\n\n" + ) + for cat in commands_by_category: + category_commands = commands_by_category[cat] + command_list = self._get_command_list(category_commands) + help_msg += f"\x1b[2;34;4m{cat}\x1b[0m :\n{command_list}\n" + help_msg += "```" + return help_msg - @commands.command(name="help", help='affiche les commandes, aliases, et descripton', aliases=['h', '?']) - async def _help(self, event): - # get all commands and sort them by category - commandsByCat = {} + # Trie les commandes par catégorie + def _get_commands_by_category(self): + commands_by_category = {} for command in self.bot.walk_commands(): - if command.cog_name not in commandsByCat: - commandsByCat[command.cog_name] = [] - commandsByCat[command.cog_name].append(command) - # sort categories by key - commandsByCat = dict(sorted(commandsByCat.items())) - # sort commands by name alphabetically in each category - for cat in commandsByCat: - commandsByCat[cat] = sorted( - commandsByCat[cat], key=lambda x: x.name) - # create help message - help = (f"```ansi\n" - f"prefix {self.bot.command_prefix}\n") - # add category and commands to the message - for cat in commandsByCat: - # example : "category : " - help += f"\n{cat} : \n" - for command in commandsByCat[cat]: - # examle : " command [alias] : help" - aliases = f'''{' ' + str(command.aliases).replace("'", "") if command.aliases != [] else ''}''' - msg = f' -- {command.help}' if command.help != None else '' - help += f" {command.name}{aliases}{msg}\n" - help += "```" - await event.channel.send(help) + if command.cog_name not in commands_by_category: + commands_by_category[command.cog_name] = [] + commands_by_category[command.cog_name].append(command) + return dict(sorted(commands_by_category.items())) + # Génère la liste de commandes pour une catégorie + def _get_command_list(self, commands): + command_list = "" + for command in commands: + aliases = self._get_command_aliases(command) + msg = f' -- \x1b[2;36m{command.help}\x1b[0m' if command.help is not None else '' + command_list += f"\x1b[2;33m{command.name}\x1b[0m{aliases}{msg}\n" + return command_list + # Récupère les alias d'une commande + def _get_command_aliases(self, command): + return f'''{' ' + str(command.aliases).replace("'", "") if command.aliases != [] else ''}''' + +# Fonction d'initialisation du Cog async def setup(bot): - # remove old help bot.remove_command('help') await bot.add_cog(Base(bot)) - - -def version(): - try: - # Lecture du fichier - with open('version.txt', 'r') as f: - VERSION = f.read() - return f'URBot_base version : {VERSION}' - except FileNotFoundError: - return 'Erreur : le fichier version.txt est introuvable.' - except Exception as e: - return f'Erreur lors de la lecture du fichier : {str(e)}' diff --git a/bot/extends/base/credits.txt b/bot/extends/base/credits.txt index ea2f99e..4bce8a9 100644 --- a/bot/extends/base/credits.txt +++ b/bot/extends/base/credits.txt @@ -4,4 +4,4 @@ URBot_base Le bot discord de l'Union des Rôlistes - Eὔδοξος#2170 - Lyss#4053 - Maestro#8364 - - phrixus#7269 + - Phrixus#7269 From 7ae90edcad05ad63bb0c528682e75a81a91fadf5 Mon Sep 17 00:00:00 2001 From: Younes Bourakadi Date: Thu, 31 Aug 2023 15:30:20 +0200 Subject: [PATCH 27/29] Fix typo --- bot/extends/base/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index a588bc6..499fee1 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -85,7 +85,7 @@ async def _get_versions(self): directory_names = { 'base': 'Bot_Base', 'prez': 'Bot_Presentation', - 'planing': 'Bot_Planning', + 'planning': 'Bot_Planning', 'site': 'Web_Site' } for directory in directory_names: From 9ab4abb005752668afc3ab8176f15320d2c758c8 Mon Sep 17 00:00:00 2001 From: Younes B Date: Thu, 31 Aug 2023 16:25:54 +0200 Subject: [PATCH 28/29] Fix version.txt file path (#40) * Fix version.txt file path * Revert changes from 'git rm -r --cached .' * Updated _version command to fetch and display versions of all bots * Update version text output * Add Web_Site to _version() --- .gitignore | 2 +- bot/extends/base/base.py | 79 +++++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 473e456..3a750ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ test.yml compose-build.yml -.docker-compose-merge.yml +docker-compose-merge.yml __pycache__/ diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index 6b4eebb..6987be9 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -10,8 +10,6 @@ sys.path.append('..') # class base with case insensitive - - class Base(commands.Cog, name='Base'): def __init__(self, bot: commands.Bot): self.bot = bot @@ -50,34 +48,51 @@ async def _credits(self, event): print(e) await event.send(credits+'```') - # send version of the bot + @commands.command(name="version", help='affiche la version du bot', aliases=['v'], ) async def _version(self, event): - txt = version() - # get directory path - pwd = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # for each directory - for directory in os.listdir(pwd): - # for each file in the directory - for file in os.listdir(f'{pwd}/{directory}'): - # if the file is a python file - if file.endswith('.py') and not file.startswith('__'): - # get the file name without the extension - file_name = file[:-3] - # try import the function version in the file - try: - print( - f'try run : extends.{directory}.{file_name}.version()') - module = importlib.import_module( - f'extends.{directory}.{file_name}').version() - try: - txt += f'\n{module}' - except Exception as e: - print(e) - except Exception as e: - print(e) + txt = "```properties\n" + + # Obtenir le répertoire parent du fichier base.py + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + # Correspondance des noms de répertoires aux noms souhaités + directory_names = { + 'base': 'Bot_Base', + 'prez': 'Bot_Presentation', + 'planing': 'Bot_Planning', + 'site': 'Web_Site' + } + + # Parcourir les répertoires dans le répertoire parent + for directory in os.listdir(parent_dir): + # Construire le chemin du fichier version.txt + version_file_path = os.path.join(parent_dir, directory, 'version.txt') + + # Vérifier si le fichier version.txt existe + if os.path.exists(version_file_path): + try: + # Lire la version depuis le fichier version.txt + with open(version_file_path, 'r') as f: + version = f.read().strip() + + # Obtenir le nom souhaité à partir de la correspondance + directory_name = directory_names.get(directory, directory) + + txt += f'Version {directory_name} : {version}\n' + except Exception as e: + print(e) + else: + # Obtenir le nom souhaité à partir de la correspondance + directory_name = directory_names.get(directory, directory) + + txt += f'Version {directory_name} : Fichier introuvable\n' + + txt += "```" await event.send(txt) + + @commands.command(name="help", help='affiche les commandes, aliases, et descripton', aliases=['h', '?']) async def _help(self, event): # get all commands and sort them by category @@ -112,15 +127,3 @@ async def setup(bot): # remove old help bot.remove_command('help') await bot.add_cog(Base(bot)) - - -def version(): - try: - # Lecture du fichier - with open('version.txt', 'r') as f: - VERSION = f.read() - return f'URBot_base version : {VERSION}' - except FileNotFoundError: - return 'Erreur : le fichier version.txt est introuvable.' - except Exception as e: - return f'Erreur lors de la lecture du fichier : {str(e)}' From 2abee0e54cfc15b1a90f04a27becfef9dc890af8 Mon Sep 17 00:00:00 2001 From: Younes Bourakadi Date: Fri, 1 Sep 2023 11:31:35 +0200 Subject: [PATCH 29/29] Enlever commentaire --- bot/extends/base/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/extends/base/base.py b/bot/extends/base/base.py index 4eb6312..cd7bacd 100644 --- a/bot/extends/base/base.py +++ b/bot/extends/base/base.py @@ -5,7 +5,6 @@ load_dotenv() -# class base with case insensitive class Base(commands.Cog, name='Base'): def __init__(self, bot: commands.Bot): self.bot = bot