fix(security): fix high security breach

This commit is contained in:
Romain J 2019-06-27 23:40:25 +02:00
parent 5aadb8e057
commit ff32a7721c
52 changed files with 235 additions and 914 deletions

7
.gitignore vendored
View file

@ -1,9 +1,10 @@
#Don't track user data
data/users/
#Python #Python
__pycache__/ __pycache__/
*.pyc *.pyc
.env .env
config.py config.py
.DS_Store .DS_Store
private.py
#jetbrains
.idea/

17
bot.py
View file

@ -24,10 +24,8 @@ l_extensions = (
'cogs.atc', 'cogs.atc',
'cogs.basics', 'cogs.basics',
'cogs.ci', 'cogs.ci',
'cogs.cog_manager',
'cogs.filter_messages', 'cogs.filter_messages',
'cogs.funs', 'cogs.funs',
'cogs.passport',
'cogs.role', 'cogs.role',
'cogs.search', 'cogs.search',
'cogs.send_logs', 'cogs.send_logs',
@ -46,8 +44,7 @@ class TuxBot(commands.Bot):
super().__init__(command_prefix=self.config.prefix[0], super().__init__(command_prefix=self.config.prefix[0],
description=self.config.description, description=self.config.description,
pm_help=None, pm_help=None,
help_command = None help_command=None)
)
self.client_id = self.config.client_id self.client_id = self.config.client_id
self.session = aiohttp.ClientSession(loop=self.loop) self.session = aiohttp.ClientSession(loop=self.loop)
@ -67,10 +64,10 @@ class TuxBot(commands.Bot):
async def on_command_error(self, ctx, error): async def on_command_error(self, ctx, error):
if isinstance(error, commands.NoPrivateMessage): if isinstance(error, commands.NoPrivateMessage):
await ctx.author.send('Cette commande ne peut pas être utilisée ' await ctx.author.send('Cette commande ne peut pas être utilisee '
'en message privé.') 'en message privee.')
elif isinstance(error, commands.DisabledCommand): elif isinstance(error, commands.DisabledCommand):
await ctx.author.send('Desolé mais cette commande est desactivée, ' await ctx.author.send('Desoler mais cette commande est desactive, '
'elle ne peut donc pas être utilisée.') 'elle ne peut donc pas être utilisée.')
elif isinstance(error, commands.CommandInvokeError): elif isinstance(error, commands.CommandInvokeError):
print(f'In {ctx.command.qualified_name}:', file=sys.stderr) print(f'In {ctx.command.qualified_name}:', file=sys.stderr)
@ -79,13 +76,13 @@ class TuxBot(commands.Bot):
file=sys.stderr) file=sys.stderr)
async def on_ready(self): async def on_ready(self):
log_channel_id = self.get_channel(int(self.config.log_channel_id)) log_channel_id = await self.fetch_channel(self.config.log_channel_id)
print('\n\n---------------------') print('\n\n---------------------')
print('CONNECTÉ :') print('CONNECTÉ :')
print(f'Nom d\'utilisateur: {self.user} {colors.text_style.DIM}' print(f'Nom d\'utilisateur: {self.user} {colors.text_style.DIM}'
f'(ID: {self.user.id}){colors.ENDC}') f'(ID: {self.user.id}){colors.ENDC}')
print(f'Salon de journalisation: {log_channel_id} {colors.text_style.DIM}' print(f'Channel de log: {log_channel_id} {colors.text_style.DIM}'
f'(ID: {log_channel_id.id}){colors.ENDC}') f'(ID: {log_channel_id.id}){colors.ENDC}')
print(f'Prefix: {self.config.prefix[0]}') print(f'Prefix: {self.config.prefix[0]}')
print('Merci d\'utiliser TuxBot') print('Merci d\'utiliser TuxBot')
@ -93,7 +90,7 @@ class TuxBot(commands.Bot):
await self.change_presence(status=discord.Status.dnd, await self.change_presence(status=discord.Status.dnd,
activity=discord.Game( activity=discord.Game(
name=self.config.game), name=self.config.game)
) )
@staticmethod @staticmethod

View file

@ -1,16 +1,10 @@
import aiohttp
from discord.ext import commands from discord.ext import commands
import discord import discord
import asyncio
from .utils import checks from .utils import checks
from .utils.checks import get_user from .utils.checks import get_user
import traceback
import textwrap
from contextlib import redirect_stdout
import inspect
import io
class Admin(commands.Cog): class Admin(commands.Cog):
"""Commandes secrètes d'administration.""" """Commandes secrètes d'administration."""
@ -20,20 +14,37 @@ class Admin(commands.Cog):
self._last_result = None self._last_result = None
self.sessions = set() self.sessions = set()
@staticmethod
def cleanup_code(content):
if content.startswith('```') and content.endswith('```'):
return '\n'.join(content.split('\n')[1:-1])
return content.strip('` \n')
"""---------------------------------------------------------------------""" """---------------------------------------------------------------------"""
@checks.has_permissions(administrator=True) @checks.has_permissions(administrator=True)
@commands.command(pass_context=True) @commands.command(name="upload", pass_context=True)
async def ban(self, ctx, user, *, reason=""): async def _upload(self, ctx, *, url=""):
if len(ctx.message.attachments) >= 1:
file = ctx.message.attachments[0].url
elif url != "":
file = url
else:
em = discord.Embed(title='Une erreur est survenue',
description="Fichier introuvable.",
colour=0xDC3546)
await ctx.send(embed=em)
return
async with aiohttp.ClientSession() as session:
async with session.get(file) as r:
image = await r.content.read()
with open(f"data/tmp/{str(ctx.author.id)}.png", 'wb') as f:
f.write(image)
f.close()
await ctx.send(file=discord.File(f"data/tmp/{str(ctx.author.id)}.png"))
@checks.has_permissions(administrator=True)
@commands.command(name="ban", pass_context=True)
async def _ban(self, ctx, user, *, reason=""):
"""Ban user""" """Ban user"""
user = get_user(ctx.message, user) user = get_user(ctx.message, user)
if user: if user and str(user.id) not in self.bot.config.unkickable_id:
try: try:
await user.ban(reason=reason) await user.ban(reason=reason)
return_msg = f"`{user.mention}` a été banni\n" return_msg = f"`{user.mention}` a été banni\n"
@ -50,11 +61,11 @@ class Admin(commands.Cog):
"""---------------------------------------------------------------------""" """---------------------------------------------------------------------"""
@checks.has_permissions(administrator=True) @checks.has_permissions(administrator=True)
@commands.command(pass_context=True) @commands.command(name="kick", pass_context=True)
async def kick(self, ctx, user, *, reason=""): async def _kick(self, ctx, user, *, reason=""):
"""Kick a user""" """Kick a user"""
user = get_user(ctx.message, user) user = get_user(ctx.message, user)
if user: if user and str(user.id) not in self.bot.config.unkickable_id:
try: try:
await user.kick(reason=reason) await user.kick(reason=reason)
return_msg = f"`{user.mention}` a été kické\n" return_msg = f"`{user.mention}` a été kické\n"
@ -155,7 +166,7 @@ class Admin(commands.Cog):
@checks.has_permissions(administrator=True) @checks.has_permissions(administrator=True)
@commands.command(name='editsay', pass_context=True) @commands.command(name='editsay', pass_context=True)
async def _editsay(self, ctx, id: int, *, new_content: str): async def _editsay(self, ctx, message_id: int, *, new_content: str):
"""Edit a bot's message""" """Edit a bot's message"""
try: try:
try: try:
@ -163,10 +174,10 @@ class Admin(commands.Cog):
except Exception: except Exception:
print(f"Impossible de supprimer le message " print(f"Impossible de supprimer le message "
f"\"{str(ctx.message.content)}\"") f"\"{str(ctx.message.content)}\"")
toedit = await ctx.channel.get_message(id) toedit = await ctx.channel.get_message(message_id)
except discord.errors.NotFound: except discord.errors.NotFound:
await ctx.send(f"Impossible de trouver le message avec l'id " await ctx.send(f"Impossible de trouver le message avec l'id "
f"`{id}` sur ce salon") f"`{message_id}` sur ce salon")
return return
try: try:
await toedit.edit(content=str(new_content)) await toedit.edit(content=str(new_content))
@ -177,7 +188,7 @@ class Admin(commands.Cog):
@checks.has_permissions(administrator=True) @checks.has_permissions(administrator=True)
@commands.command(name='addreaction', pass_context=True) @commands.command(name='addreaction', pass_context=True)
async def _addreaction(self, ctx, id: int, reaction: str): async def _addreaction(self, ctx, message_id: int, reaction: str):
"""Add reactions to a message""" """Add reactions to a message"""
try: try:
try: try:
@ -185,10 +196,10 @@ class Admin(commands.Cog):
except Exception: except Exception:
print(f"Impossible de supprimer le message " print(f"Impossible de supprimer le message "
f"\"{str(ctx.message.content)}\"") f"\"{str(ctx.message.content)}\"")
toadd = await ctx.channel.get_message(id) toadd = await ctx.channel.get_message(message_id)
except discord.errors.NotFound: except discord.errors.NotFound:
await ctx.send(f"Impossible de trouver le message avec l'id " await ctx.send(f"Impossible de trouver le message avec l'id "
f"`{id}` sur ce salon") f"`{message_id}` sur ce salon")
return return
try: try:
await toadd.add_reaction(reaction) await toadd.add_reaction(reaction)
@ -199,7 +210,7 @@ class Admin(commands.Cog):
@checks.has_permissions(administrator=True) @checks.has_permissions(administrator=True)
@commands.command(name='delete', pass_context=True) @commands.command(name='delete', pass_context=True)
async def _delete(self, ctx, id: int): async def _delete(self, ctx, message_id: int):
"""Delete message in current channel""" """Delete message in current channel"""
try: try:
try: try:
@ -207,10 +218,10 @@ class Admin(commands.Cog):
except Exception: except Exception:
print(f"Impossible de supprimer le message " print(f"Impossible de supprimer le message "
f"\"{str(ctx.message.content)}\"") f"\"{str(ctx.message.content)}\"")
todelete = await ctx.channel.get_message(id) todelete = await ctx.channel.get_message(message_id)
except discord.errors.NotFound: except discord.errors.NotFound:
await ctx.send(f"Impossible de trouver le message avec l'id " await ctx.send(f"Impossible de trouver le message avec l'id "
f"`{id}` sur ce salon") f"`{message_id}` sur ce salon")
return return
try: try:
await todelete.delete() await todelete.delete()
@ -221,7 +232,7 @@ class Admin(commands.Cog):
@checks.has_permissions(administrator=True) @checks.has_permissions(administrator=True)
@commands.command(name='deletefrom', pass_context=True) @commands.command(name='deletefrom', pass_context=True)
async def _deletefrom(self, ctx, chan_id: int, *, message_id: str): async def _deletefrom(self, ctx, chan_id: int, *, message_id: int):
"""Delete message in <chan_id> channel""" """Delete message in <chan_id> channel"""
try: try:
chan = self.bot.get_channel(chan_id) chan = self.bot.get_channel(chan_id)
@ -391,151 +402,6 @@ class Admin(commands.Cog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
"""---------------------------------------------------------------------"""
@checks.has_permissions(administrator=True)
@commands.command(pass_context=True, hidden=True)
async def repl(self, ctx):
"""Launches an interactive REPL session."""
variables = {
'ctx': ctx,
'bot': self.bot,
'message': ctx.message,
'guild': ctx.guild,
'channel': ctx.channel,
'author': ctx.author,
'_': None,
}
if ctx.channel.id in self.sessions:
await ctx.send('Already running a REPL session in this channel.'
' Exit it with `quit`.')
return
self.sessions.add(ctx.channel.id)
await ctx.send(
'Enter code to execute or evaluate. `exit()` or `quit` to exit.')
def check(m):
return m.author.id == ctx.author.id and \
m.channel.id == ctx.channel.id and \
m.content.startswith('`')
while True:
try:
response = await self.bot.wait_for('message', check=check,
timeout=10.0 * 60.0)
except asyncio.TimeoutError:
await ctx.send('Exiting REPL session.')
self.sessions.remove(ctx.channel.id)
break
cleaned = self.cleanup_code(response.content)
if cleaned in ('quit', 'exit', 'exit()'):
await ctx.send('Exiting.')
self.sessions.remove(ctx.channel.id)
return
executor = exec
if cleaned.count('\n') == 0:
# single statement, potentially 'eval'
try:
code = compile(cleaned, '<repl session>', 'eval')
except SyntaxError:
pass
else:
executor = eval
if executor is exec:
try:
code = compile(cleaned, '<repl session>', 'exec')
except SyntaxError as e:
await ctx.send(self.get_syntax_error(e))
continue
variables['message'] = response
fmt = None
stdout = io.StringIO()
try:
with redirect_stdout(stdout):
result = executor(code, variables)
if inspect.isawaitable(result):
result = await result
except Exception as e:
value = stdout.getvalue()
fmt = f'```py\n{value}{traceback.format_exc()}\n```'
else:
value = stdout.getvalue()
if result is not None:
fmt = f'```py\n{value}{result}\n```'
variables['_'] = result
elif value:
fmt = f'```py\n{value}\n```'
try:
if fmt is not None:
if len(fmt) > 2000:
await ctx.send('Content too big to be printed.')
else:
await ctx.send(fmt)
except discord.Forbidden:
pass
except discord.HTTPException as e:
await ctx.send(f'Unexpected error: `{e}`')
"""---------------------------------------------------------------------"""
@checks.has_permissions(administrator=True)
@commands.command(pass_context=True, hidden=True, name='eval')
async def _eval(self, ctx, *, body: str):
"""Evaluates a code"""
env = {
'bot': self.bot,
'ctx': ctx,
'channel': ctx.channel,
'author': ctx.author,
'guild': ctx.guild,
'message': ctx.message,
'_': self._last_result
}
env.update(globals())
body = self.cleanup_code(body)
stdout = io.StringIO()
to_compile = f'async def func():\n{textwrap.indent(body, " ")}'
try:
exec(to_compile, env)
except Exception as e:
return await ctx.send(f'```py\n{e.__class__.__name__}: {e}\n```')
func = env['func']
try:
with redirect_stdout(stdout):
ret = await func()
except Exception:
value = stdout.getvalue()
await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
else:
value = stdout.getvalue()
try:
await ctx.message.add_reaction('\u2705')
except Exception:
pass
if ret is None:
if value:
await ctx.send(f'```py\n{value}\n```')
else:
self._last_result = ret
await ctx.send(f'```py\n{value}{ret}\n```')
def setup(bot): def setup(bot):
bot.add_cog(Admin(bot)) bot.add_cog(Admin(bot))

View file

@ -61,10 +61,10 @@ class Basics(commands.Cog):
async def help(self, ctx): async def help(self, ctx):
"""Affiches l'aide du bot""" """Affiches l'aide du bot"""
text = open('texts/help.md').read() text = open('texts/help.md').read()
text = text.split("[split]") em = discord.Embed(title='Commandes de TuxBot', description=text,
for text_result in text: colour=0x89C4F9)
em = discord.Embed(title='Commandes de TuxBot', description=text_result,colour=0x89C4F9)
await ctx.send(embed=em) await ctx.send(embed=em)
def setup(bot): def setup(bot):
bot.add_cog(Basics(bot)) bot.add_cog(Basics(bot))

View file

@ -1,116 +0,0 @@
from discord.ext import commands
import discord
from .utils import checks
from .utils.paginator import HelpPaginator
class CogManager(commands.Cog):
"""Gestionnaire des cogs"""
def __init__(self, bot):
self.bot = bot
self.help = discord.Embed(title='Tuxbot - Commandes cogs', description="Tuxbot - Commandes cogs\n"
"-> .cogs <load/unload/reload/info> *{cog}* : <load/unload/reload/info> *{cog}*\n"
"-> .cogs <list> : donne la liste de tous les cogs\n"
"-> .cogs <null/!(load/unload/reload)>: affiche cette aide", colour=0x89C4F9)
"""---------------------------------------------------------------------"""
@checks.has_permissions(administrator=True)
@commands.group(name="cogs", no_pm=True, pass_context=True,
case_insensitive=True)
async def _cogs(self, ctx):
"""show help about 'cogs' command"""
if ctx.invoked_subcommand is None:
await ctx.send(embed=self.help)
"""---------------------------------------------------------------------"""
@_cogs.command(name="load", pass_context=True)
async def cogs_load(self, ctx, cog: str = ""):
"""load a cog"""
if cog != "":
try:
self.bot.load_extension(cog)
await ctx.send('\N{OK HAND SIGN}')
print("cog : " + str(cog) + " chargé")
except Exception as e:
await ctx.send('\N{PISTOL}')
await ctx.send(f'{type(e).__name__}: {e}')
else:
await ctx.send(embed=self.help)
"""---------------------------------------------------------------------"""
@_cogs.command(name="unload", pass_context=True)
async def cogs_unload(self, ctx, cog: str = ""):
"""unload a cog"""
if cog != "":
try:
self.bot.unload_extension(cog)
await ctx.send('\N{OK HAND SIGN}')
print("cog : " + str(cog) + " déchargé")
except Exception as e:
await ctx.send('\N{PISTOL}')
await ctx.send(f'{type(e).__name__}: {e}')
else:
await ctx.send(embed=self.help)
"""---------------------------------------------------------------------"""
@_cogs.command(name="reload", pass_context=True)
async def cogs_reload(self, ctx, cog: str = ""):
"""reload a cog"""
if cog != "":
try:
self.bot.unload_extension(cog)
self.bot.load_extension(cog)
await ctx.send('\N{OK HAND SIGN}')
print("cog : " + str(cog) + " rechargé")
except Exception as e:
await ctx.send('\N{PISTOL}')
await ctx.send(f'{type(e).__name__}: {e}')
else:
await ctx.send(embed=self.help)
"""---------------------------------------------------------------------"""
@_cogs.command(name="info", pass_context=True)
async def cogs_info(self, ctx, cog: str = ""):
"""show info about a cog"""
if cog != "":
try:
entity = self.bot.get_cog(cog)
if entity is None:
clean = cog.replace('@', '@\u200b')
await ctx.send(f'Command or category "{clean}" not found.')
else:
p = await HelpPaginator.from_cog(ctx, entity)
await p.paginate()
except Exception as e:
await ctx.send('\N{PISTOL}')
await ctx.send(f'{type(e).__name__}: {e}')
else:
await ctx.send(embed=self.help)
"""---------------------------------------------------------------------"""
@_cogs.command(name="list", pass_context=True)
async def cogs_list(self, ctx):
"""list all cogs"""
cogs = ""
for cog in self.bot.cogs:
cogs += f"{cog} *`({self.bot.cogs[cog].__module__})`*:" \
f" {len(self.bot.get_cog_commands(cog))} commandes\n"
await ctx.send(cogs)
def setup(bot):
bot.add_cog(CogManager(bot))

View file

@ -1,224 +0,0 @@
import aiohttp
import datetime
import discord
import imghdr
import os
import shutil
from PIL import Image
from PIL import ImageOps
from discord.ext import commands
from .utils import db
from .utils.checks import get_user
from .utils.passport_generator import generate_passport
class Passport(commands.Cog):
"""Commandes des passeports ."""
def __init__(self, bot):
self.bot = bot
self.conn = db.connect_to_db(self)
self.cursor = self.conn.cursor()
self.cursor.execute("""SHOW TABLES LIKE 'passport'""")
result = self.cursor.fetchone()
if not result:
# Creation table Passport si premiere fois
sql = "CREATE TABLE passport ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, userid TEXT null, os TEXT null, config TEXT null, languages TEXT null, pays TEXT null, passportdate TEXT null, theme CHAR(5) DEFAULT 'dark');"
self.cursor.execute(sql)
@commands.group(pass_context=True)
async def passeport(self, ctx):
"""Passeport"""
if ctx.invoked_subcommand is None:
text = open('texts/passport-info.md').read()
em = discord.Embed(title='Commandes de carte de passeport de TuxBot', description=text, colour=0x89C4F9)
await ctx.send(embed=em)
@passeport.command(pass_context=True)
async def show(self, ctx, user: str = None):
self.conn = db.connect_to_db(self)
self.cursor = self.conn.cursor()
if user == None:
user = get_user(ctx.message, ctx.message.author.name)
else:
user = get_user(ctx.message, user)
wait_message = await ctx.send(f"Je vais chercher le passeport de {user.name} dans les archives, je vous prie de bien vouloir patienter...")
card = await generate_passport(self, user)
s = 'data/users/cards/{0}.png'.format(user.id)
card.save(s, 'png')
with open('data/users/cards/{0}.png'.format(user.id), 'rb') as g:
await ctx.message.channel.send(file=discord.File(g))
await wait_message.delete()
@passeport.command(name="config", pass_context=True)
async def passeport_config(self, ctx):
self.conn = db.connect_to_db(self)
self.cursor = self.conn.cursor()
await ctx.send('Un message privé vous a été envoyé pour configurer votre passeport.')
questions = ["Système(s) d'exploitation :", "Configuration Système :", "Langages de programmation préférés :", "Pays :"]
answers = {}
user_dm = await ctx.author.create_dm()
try:
await user_dm.send("Salut ! Je vais vous posez quelques questions afin de configurer votre passeport, si vous ne voulez pas répondre à une question, envoyez `skip` pour passer à la question suivante.")
except discord.HTTPException:
await ctx.send(f"{str(ctx.message.author.mention)}> il m'est impossible de vous envoyer les messages nécessaire a la configuration de votre passeport :sob:")
return
for x, question in enumerate(questions):
await user_dm.send(question)
def check(m):
return m.channel.id == user_dm.id and m.author.id == user_dm.recipient.id
answer = await self.bot.wait_for('message', check=check)
if answer.content.lower() == 'skip':
answers[x] = 'n/a'
else:
answers[x] = answer.content
try:
self.cursor.execute("""SELECT id, userid FROM passport WHERE userid = %s""", str(user_dm.recipient.id))
result = self.cursor.fetchone()
if result:
self.cursor.execute("""UPDATE passport SET os = %s, config = %s, languages = %s, pays = %s WHERE userid = %s""", (str(answers[0]), str(answers[1]), str(answers[2]), str(answers[3]), str(ctx.message.author.id)))
self.conn.commit()
else:
now = datetime.datetime.now()
self.cursor.execute("""INSERT INTO passport(userid, os, config, languages, pays, passportdate, theme) VALUES(%s, %s, %s, %s, %s, %s, %s)""", (str(ctx.message.author.id), str(answers[0]), str(answers[1]), str(answers[2]), str(answers[3]), now, "dark"))
self.conn.commit()
await user_dm.send('Configuration de votre passeport terminée avec succès, vous pouvez désormais la voir en faisant `.passeport show`.')
except Exception as e:
await user_dm.send(f':sob: Une erreur est survenue : \n {type(e).__name__}: {e}')
@passeport.command(name="background", pass_context=True)
async def passeport_background(self, ctx, *, url=""):
try:
background = ctx.message.attachments[0].url
except:
if url != "":
background = url
else:
em = discord.Embed(title='Une erreur est survenue', description="Image ou URL introuvable.", colour=0xDC3546)
await ctx.send(embed=em)
return
user = ctx.message.author
try:
async with aiohttp.ClientSession() as session:
async with session.get(background) as r:
image = await r.content.read()
except:
em = discord.Embed(title='Une erreur est survenue', description="Image ou URL introuvable.", colour=0xDC3546)
await ctx.send(embed=em)
return
with open(f"data/users/backgrounds/{str(user.id)}.png",'wb') as f:
f.write(image)
isImage = imghdr.what(f"data/users/backgrounds/{str(user.id)}.png")
if isImage == 'png' or isImage == 'jpeg' or isImage == 'jpg' or isImage == 'gif':
f.close()
em = discord.Embed(title='Configuration terminée', description="Fond d'écran enregistré et configuré avec succes", colour=0x28a745)
await ctx.send(embed=em)
else:
f.close()
os.remove(f"data/users/backgrounds/{str(user.id)}.png")
em = discord.Embed(title='Une erreur est survenue', description="Est-ce bien une image que vous avez envoyé ? :thinking:", colour=0xDC3546)
await ctx.send(embed=em)
@passeport.command(name="theme", aliases=["thème"], pass_context=True)
async def passeport_theme(self, ctx, theme: str = ""):
self.conn = db.connect_to_db(self)
self.cursor = self.conn.cursor()
possible_theme = ["dark", "light", "preview"]
if theme.lower() in possible_theme:
if theme.lower() == "dark":
self.cursor.execute("""UPDATE passport SET theme = %s WHERE userid = %s""", ("dark", str(ctx.message.author.id)))
self.conn.commit()
em = discord.Embed(title='Configuration terminée', description="Thème enregistré avec succes", colour=0x28a745)
await ctx.send(embed=em)
elif theme.lower() == "light":
self.cursor.execute("""UPDATE passport SET theme = %s WHERE userid = %s""", ("light", str(ctx.message.author.id)))
self.conn.commit()
em = discord.Embed(title='Configuration terminée', description="Thème enregistré avec succes", colour=0x28a745)
await ctx.send(embed=em)
else:
wait_message = await ctx.send(f"Laissez moi juste le temps de superposer les 2 passeports, je vous prie de bien vouloir patienter...")
cardbg = Image.new('RGBA', (1600, 500), (0, 0, 0, 255))
card_dark = await generate_passport(self, ctx.author, "dark")
card_dark.save(f'data/tmp/{ctx.author.id}_dark.png', 'png')
card_light = await generate_passport(self, ctx.author, "light")
card_light.save(f'data/tmp/{ctx.author.id}_light.png', 'png')
saved_card_dark = Image.open(f'data/tmp/{ctx.author.id}_dark.png')
saved_card_light = Image.open(f'data/tmp/{ctx.author.id}_light.png')
saved_card_dark = ImageOps.fit(saved_card_dark, (800, 500))
saved_card_light = ImageOps.fit(saved_card_light, (800, 500))
cardbg.paste(saved_card_dark, (0, 0))
cardbg.paste(saved_card_light, (800, 0))
cardbg.save(f'data/tmp/{ctx.author.id}.png', 'png')
with open(f'data/tmp/{ctx.author.id}.png', 'rb') as g:
await ctx.send(file=discord.File(g))
await wait_message.delete()
await ctx.send(f"Et voila {ctx.author.mention} ! à gauche votre passeport avec le thème \"dark\" et à droite avec le thème \"light\" :wink:")
shutil.rmtree("data/tmp")
os.mkdir("data/tmp")
else:
em = discord.Embed(title='Une erreur est survenue', description="Les choix possible pour cette commande sont : `dark`, `light`, `preview`", colour=0xDC3546)
await ctx.send(embed=em)
@passeport.command(name="delete", pass_context=True)
async def passeport_delete(self, ctx):
self.conn = db.connect_to_db(self)
self.cursor = self.conn.cursor()
self.cursor.execute("""SELECT id, userid FROM passport WHERE userid = %s""", str(ctx.author.id))
result = self.cursor.fetchone()
if result:
def check(m):
return m.author.id == ctx.author.id and \
m.channel.id == ctx.channel.id
await ctx.send(f"{str(ctx.message.author.mention)}> envoyez `CONFIRMER` afin de supprimer vos données conformément à l'article 17 du `règlement général sur la protection des données` sur le `Droit à l'effacement`")
response = await self.bot.wait_for('message', check=check, timeout=10.0 * 60.0)
if response.content == "CONFIRMER":
os.remove(f"data/users/backgrounds/{str(ctx.author.id)}.png")
self.cursor.execute("""DELETE FROM passport WHERE userid =%s""", (str(ctx.message.author.id)))
self.conn.commit()
em = discord.Embed(title='Suppression confirmée', description="Vos données ont été supprimées avec succès", colour=0x28a745)
await ctx.send(embed=em)
else:
await ctx.send("Déja configure ton passeport avant de la supprimer u_u (après c'est pas logique...)")
def setup(bot):
bot.add_cog(Passport(bot))

View file

@ -23,7 +23,7 @@ class SendLogs(commands.Cog):
@commands.Cog.listener() @commands.Cog.listener()
async def on_ready(self): async def on_ready(self):
self.log_channel = self.bot.get_channel(int(self.bot.config.log_channel_id)) self.log_channel = await self.bot.fetch_channel(int(self.bot.config.log_channel_id))
em = discord.Embed(title="Je suis opérationnel 😃", em = discord.Embed(title="Je suis opérationnel 😃",
description=f"*Instance lancée sur " description=f"*Instance lancée sur "
f"{socket.gethostname()}*", colour=0x5cb85c) f"{socket.gethostname()}*", colour=0x5cb85c)

View file

@ -15,76 +15,63 @@ class Sondage(commands.Cog):
if msg != "help": if msg != "help":
await ctx.message.delete() await ctx.message.delete()
options = msg.split(" | ") options = msg.split(" | ")
time = [x for x in options if x.startswith("time=")]
if time: times = [x for x in options if x.startswith("time=")]
time = time[0]
if time: if times:
options.remove(time) time = int(times[0].strip("time="))
options.remove(times[0])
else:
time = 0
if len(options) <= 1: if len(options) <= 1:
raise commands.errors.MissingRequiredArgument raise commands.errors.MissingRequiredArgument
if len(options) >= 22: if len(options) >= 22:
return await ctx.send(f"{ctx.message.author.mention}> " return await ctx.send(f"{ctx.message.author.mention}> "
f":octagonal_sign: Vous ne pouvez pas " f":octagonal_sign: Vous ne pouvez pas "
f"mettre plus de 20 réponses !") f"mettre plus de 20 réponses !")
if time:
time = int(time.strip("time=")) emoji = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣', '🔟', '0⃣',
else: '🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮']
time = 0
emoji = ['1⃣',
'2⃣',
'3⃣',
'4⃣',
'5⃣',
'6⃣',
'7⃣',
'8⃣',
'9⃣',
'🔟',
'0⃣',
'🇦',
'🇧',
'🇨',
'🇩',
'🇪',
'🇫',
'🇬',
'🇭',
'🇮'
]
to_react = [] to_react = []
confirmation_msg = f"**{options[0].rstrip('?')}?**:\n\n" confirmation_msg = f"**{options[0].rstrip('?')}?**:\n\n"
for idx, option in enumerate(options[1:]): for idx, option in enumerate(options[1:]):
confirmation_msg += "{} - {}\n".format(emoji[idx], option) confirmation_msg += f"{emoji[idx]} - {option}\n"
to_react.append(emoji[idx]) to_react.append(emoji[idx])
confirmation_msg += "*Sondage proposé par* " + \ confirmation_msg += "*Sondage proposé par* " + \
str(ctx.message.author.mention) str(ctx.message.author.mention)
if time == 0: if time == 0:
confirmation_msg += "" confirmation_msg += ""
else: else:
confirmation_msg += "\n\nVous avez {} secondes pour voter!". \ confirmation_msg += f"\n\nVous avez {time} secondes pour voter!"
format(time)
poll_msg = await ctx.send(confirmation_msg) poll_msg = await ctx.send(confirmation_msg)
for emote in to_react: for emote in to_react:
await poll_msg.add_reaction(emote) await poll_msg.add_reaction(emote)
if time != 0: if time != 0:
await asyncio.sleep(time) await asyncio.sleep(time)
if time != 0:
async for message in ctx.message.channel.history(): async for message in ctx.message.channel.history():
if message.id == poll_msg.id: if message.id == poll_msg.id:
poll_msg = message poll_msg = message
results = {} results = {}
for reaction in poll_msg.reactions: for reaction in poll_msg.reactions:
if reaction.emoji in to_react: if reaction.emoji in to_react:
results[reaction.emoji] = reaction.count - 1 results[reaction.emoji] = reaction.count - 1
end_msg = "Le sondage est términé. Les résultats sont:\n\n" end_msg = "Le sondage est términé. Les résultats sont:\n\n"
for result in results: for result in results:
end_msg += "{} {} - {} votes\n". \ end_msg += "{} {} - {} votes\n". \
format(result, format(result,
options[emoji.index(result)+1], options[emoji.index(result)+1],
results[result]) results[result])
top_result = max(results, key=lambda key: results[key]) top_result = max(results, key=lambda key: results[key])
if len([x for x in results if len([x for x in results
if results[x] == results[top_result]]) > 1: if results[x] == results[top_result]]) > 1:
top_results = [] top_results = []

View file

@ -3,7 +3,6 @@ import json
import pytz import pytz
import random import random
import urllib import urllib
import socket
import discord import discord
import requests import requests
@ -178,7 +177,6 @@ class Utility(commands.Cog):
em.set_thumbnail(url = img) em.set_thumbnail(url = img)
await ctx.send(embed=em) await ctx.send(embed=em)
"""---------------------------------------------------------------------""" """---------------------------------------------------------------------"""
@commands.command() @commands.command()
@ -213,8 +211,7 @@ class Utility(commands.Cog):
"""---------------------------------------------------------------------""" """---------------------------------------------------------------------"""
@commands.command(name='iplocalise', pass_context=True) @commands.command(name='iplocalise', pass_context=True)
async def _iplocalise(self, ctx, ipaddress, iptype=""): async def _iplocalise(self, ctx, ipaddress):
realipaddress = ipaddress
"""Recup headers.""" """Recup headers."""
if ipaddress.startswith("http://"): if ipaddress.startswith("http://"):
if ipaddress[-1:] == '/': if ipaddress[-1:] == '/':
@ -225,39 +222,44 @@ class Utility(commands.Cog):
ipaddress = ipaddress[:-1] ipaddress = ipaddress[:-1]
ipaddress = ipaddress.split("https://")[1] ipaddress = ipaddress.split("https://")[1]
if(iptype=="ipv6" or iptype=="v6"):
try:
ipaddress = socket.getaddrinfo(ipaddress, None, socket.AF_INET6)[1][4][0]
except:
await ctx.send("Erreur, cette adresse n'est pas disponible en IPv6.")
return
iploading = await ctx.send("_réfléchis..._") iploading = await ctx.send("_réfléchis..._")
ipapi = urllib.request.urlopen("http://ip-api.com/json/" + ipaddress) ipapi = urllib.request.urlopen("http://ip-api.com/json/" + ipaddress)
ipinfo = json.loads(ipapi.read().decode()) ipinfo = json.loads(ipapi.read().decode())
if ipinfo["status"] != "fail": if ipinfo["status"] != "fail":
if ipinfo['query']:
embed = discord.Embed(title=f"Informations pour ``{realipaddress}`` *`({ipinfo['query']})`*", color=0x5858d7)
if ipinfo['org']: if ipinfo['org']:
embed.add_field(name="Appartient à :", value=ipinfo['org'], inline = False) org = ipinfo['org']
else:
org = 'n/a'
if ipinfo['query']:
ip = ipinfo['query']
else:
ip = 'n/a'
if ipinfo['city']: if ipinfo['city']:
embed.add_field(name="Se situe à :", value=ipinfo['city'], inline = True) city = ipinfo['city']
else:
city = 'n/a'
if ipinfo['country']:
if ipinfo['regionName']: if ipinfo['regionName']:
regionName = ipinfo['regionName'] regionName = ipinfo['regionName']
else: else:
regionName = "N/A" regionName = 'n/a'
embed.add_field(name="Region :", value=f"{regionName} ({ipinfo['country']})", inline = True)
if ipinfo['country']:
country = ipinfo['country']
else:
country = 'n/a'
embed = discord.Embed(title=f"Informations pour {ipaddress} *`({ip})`*", color=0x5858d7)
embed.add_field(name="Appartient à :", value=org, inline = False)
embed.add_field(name="Se situe à :", value=city, inline = True)
embed.add_field(name="Region :", value=f"{regionName} ({country})", inline = True)
embed.set_thumbnail(url=f"https://www.countryflags.io/{ipinfo['countryCode']}/flat/64.png") embed.set_thumbnail(url=f"https://www.countryflags.io/{ipinfo['countryCode']}/flat/64.png")
await ctx.send(embed=embed) await ctx.send(embed=embed)
else: else:
await ctx.send(content=f"Erreur, impossible d'avoir des informations sur l'adresse IP ``{ipinfo['query']}``") await ctx.send(content=f"Erreur, impossible d'avoir des informations sur l'adresse IP {ipinfo['query']}")
await iploading.delete() await iploading.delete()
"""---------------------------------------------------------------------""" """---------------------------------------------------------------------"""
@ -310,7 +312,7 @@ class Utility(commands.Cog):
"""Pour voir mon code""" """Pour voir mon code"""
text = "How tu veux voir mon repos Gitea pour me disséquer ? " \ text = "How tu veux voir mon repos Gitea pour me disséquer ? " \
"Pas de soucis ! Je suis un Bot, je ne ressens pas la " \ "Pas de soucis ! Je suis un Bot, je ne ressens pas la " \
"douleur !\n https://git.gnous.eu/gnous/tuxbot" "douleur !\n https://git.gnous.eu/gnouseu/tuxbot-bot"
em = discord.Embed(title='Repos TuxBot-Bot', description=text, colour=0xE9D460) em = discord.Embed(title='Repos TuxBot-Bot', description=text, colour=0xE9D460)
em.set_author(name='Gnous', icon_url="https://cdn.discordapp.com/" em.set_author(name='Gnous', icon_url="https://cdn.discordapp.com/"
"icons/280805240977227776/" "icons/280805240977227776/"
@ -318,6 +320,41 @@ class Utility(commands.Cog):
".png") ".png")
await ctx.send(embed=em) await ctx.send(embed=em)
"""---------------------------------------------------------------------"""
@commands.command(name='quote', pass_context=True)
async def _quote(self, ctx, quote_id):
global quoted_message
async def get_message(message_id: int):
for channel in ctx.message.guild.channels:
if isinstance(channel, discord.TextChannel):
test_chan = await self.bot.fetch_channel(channel.id)
try:
return await test_chan.fetch_message(message_id)
except discord.NotFound:
pass
return None
quoted_message = await get_message(int(quote_id))
if quoted_message is not None:
embed = discord.Embed(colour=quoted_message.author.colour,
description=quoted_message.clean_content,
timestamp=quoted_message.created_at)
embed.set_author(name=quoted_message.author.display_name,
icon_url=quoted_message.author.avatar_url_as(
format="jpg"))
if len(quoted_message.attachments) >= 1:
embed.set_image(url=quoted_message.attachments[0].url)
embed.add_field(name="**Original**",
value=f"[Go!]({quoted_message.jump_url})")
embed.set_footer(text="#" + quoted_message.channel.name)
await ctx.send(embed=embed)
else:
await ctx.send("Impossible de trouver le message.")
def setup(bot): def setup(bot):
bot.add_cog(Utility(bot)) bot.add_cog(Utility(bot))

View file

@ -2,33 +2,41 @@ from discord.ext import commands
def is_owner_check(message): def is_owner_check(message):
return str(message.author.id) in ['171685542553976832', '269156684155453451'] return str(message.author.id) in ['171685542553976832',
'269156684155453451']
def is_owner(warn=True): def is_owner(warn=True):
def check(ctx, warn): def check(ctx, log):
owner = is_owner_check(ctx.message) owner = is_owner_check(ctx.message)
if not owner and warn: if not owner and log:
print(ctx.message.author.name + " à essayer d'executer " + ctx.message.content + " sur le serveur " + ctx.message.guild.name) print(ctx.message.author.name + " à essayer d'executer " + ctx.message.content + " sur le serveur " + ctx.message.guild.name)
return owner return owner
owner = commands.check(lambda ctx: check(ctx, warn)) owner = commands.check(lambda ctx: check(ctx, warn))
return owner return owner
"""--------------------------------------------------------------------------------------------------------------------------"""
"""-------------------------------------------------------------------------"""
async def check_permissions(ctx, perms, *, check=all): async def check_permissions(ctx, perms, *, check=all):
is_owner = await ctx.bot.is_owner(ctx.author) is_owner = await ctx.bot.is_owner(ctx.author)
if is_owner or is_owner_check(ctx.message) == True: if is_owner or is_owner_check(ctx.message) is True:
return True return True
resolved = ctx.channel.permissions_for(ctx.author) resolved = ctx.channel.permissions_for(ctx.author)
return check(getattr(resolved, name, None) == value for name, value in perms.items()) return check(getattr(resolved, name, None) == value for name, value in
perms.items())
def has_permissions(*, check=all, **perms): def has_permissions(*, check=all, **perms):
async def pred(ctx): async def pred(ctx):
return await check_permissions(ctx, perms, check=check) return await check_permissions(ctx, perms, check=check)
return commands.check(pred) return commands.check(pred)
async def check_guild_permissions(ctx, perms, *, check=all): async def check_guild_permissions(ctx, perms, *, check=all):
is_owner = await ctx.bot.is_owner(ctx.author) is_owner = await ctx.bot.is_owner(ctx.author)
if is_owner: if is_owner:
@ -38,47 +46,61 @@ async def check_guild_permissions(ctx, perms, *, check=all):
return False return False
resolved = ctx.author.guild_permissions resolved = ctx.author.guild_permissions
return check(getattr(resolved, name, None) == value for name, value in perms.items()) return check(getattr(resolved, name, None) == value for name, value in
perms.items())
def has_guild_permissions(*, check=all, **perms): def has_guild_permissions(*, check=all, **perms):
async def pred(ctx): async def pred(ctx):
return await check_guild_permissions(ctx, perms, check=check) return await check_guild_permissions(ctx, perms, check=check)
return commands.check(pred) return commands.check(pred)
# These do not take channel overrides into account # These do not take channel overrides into account
def is_mod(): def is_mod():
async def pred(ctx): async def pred(ctx):
return await check_guild_permissions(ctx, {'manage_guild': True}) return await check_guild_permissions(ctx, {'manage_guild': True})
return commands.check(pred) return commands.check(pred)
def is_admin(): def is_admin():
async def pred(ctx): async def pred(ctx):
return await check_guild_permissions(ctx, {'administrator': True}) return await check_guild_permissions(ctx, {'administrator': True})
return commands.check(pred) return commands.check(pred)
def mod_or_permissions(**perms): def mod_or_permissions(**perms):
perms['manage_guild'] = True perms['manage_guild'] = True
async def predicate(ctx): async def predicate(ctx):
return await check_guild_permissions(ctx, perms, check=any) return await check_guild_permissions(ctx, perms, check=any)
return commands.check(predicate) return commands.check(predicate)
def admin_or_permissions(**perms): def admin_or_permissions(**perms):
perms['administrator'] = True perms['administrator'] = True
async def predicate(ctx): async def predicate(ctx):
return await check_guild_permissions(ctx, perms, check=any) return await check_guild_permissions(ctx, perms, check=any)
return commands.check(predicate) return commands.check(predicate)
def is_in_guilds(*guild_ids): def is_in_guilds(*guild_ids):
def predicate(ctx): def predicate(ctx):
guild = ctx.guild guild = ctx.guild
if guild is None: if guild is None:
return False return False
return guild.id in guild_ids return guild.id in guild_ids
return commands.check(predicate) return commands.check(predicate)
def is_lounge_cpp():
return is_in_guilds(145079846832308224)
def get_user(message, user): def get_user(message, user):
try: try:
@ -94,6 +116,7 @@ def get_user(message, user):
return None return None
return member return member
def check_date(date: str): def check_date(date: str):
if len(date) == 1: if len(date) == 1:
return f"0{date}" return f"0{date}"

View file

@ -16,6 +16,7 @@ class text_colors:
LIGHT_CYAN = '\033[96m' LIGHT_CYAN = '\033[96m'
WHITE = '\033[97m' WHITE = '\033[97m'
class bg_colors: class bg_colors:
BLACK = '\033[40m' BLACK = '\033[40m'
RED = '\033[41m' RED = '\033[41m'
@ -34,10 +35,12 @@ class bg_colors:
LIGHT_CYAN = '\033[106m' LIGHT_CYAN = '\033[106m'
WHITE = '\033[107m' WHITE = '\033[107m'
class text_style: class text_style:
BOLD = '\033[1m' BOLD = '\033[1m'
DIM = '\033[2m' DIM = '\033[2m'
UNDERLINE = '\033[4m' UNDERLINE = '\033[4m'
BLINK = '\033[5m' BLINK = '\033[5m'
ENDC = '\033[0m' ENDC = '\033[0m'

View file

@ -1,245 +0,0 @@
from PIL import Image
from PIL import ImageOps
from PIL import ImageDraw
from PIL import ImageFont
from .checks import check_date
import aiohttp, imghdr, textwrap, math, datetime
async def generate_passport(self, user, theme: str = None):
name = user.name
userid = user.id
avatar = user.avatar_url
def_avatar = user.default_avatar_url
created = datetime.datetime.fromisoformat(str(user.created_at))
nick = user.display_name
discr = user.discriminator
roles = user.roles
top_role_color = str(user.top_role.color)[1:]
user_color = tuple(int(top_role_color[i:i+2], 16) for i in (0, 2 ,4))
user_color += (255,)
color = {
"dark": {
"background": 39,
"question": 220,
"answer": 150
},
"light": {
"background": 216,
"question": 35,
"answer": 61
}
}
user_birth_day = check_date(str(created.day))
user_birth_month = check_date(str(created.month))
formated_user_birth = str(user_birth_day) + "/" + str(user_birth_month) + "/" + str(created.year)
formated_passportdate = "n/a"
roleImages = {}
def draw_underlined_text(draw, pos, text, font, **options):
twidth, theight = draw.textsize(text, font=font)
lx, ly = pos[0], pos[1] + theight
draw.text(pos, text, font=font, **options)
draw.line((lx, ly, lx + twidth, ly), **options)
def break_line(draw, pos, text, font, **options):
lines = text.split("\n")
current_y = pos[1]
for line in lines:
twidth, theight = draw.textsize(line, font=font)
lx, ly = pos[0], current_y
if textwrap.fill(line, 60) == line:
draw.text((lx, ly), textwrap.fill(line, 60), font=font, **options)
current_y += math.floor(theight)
else:
draw.text((lx, ly), textwrap.fill(line, 60), font=font, **options)
current_y += math.floor(theight*2)
for x, role in enumerate(roles):
try:
roleImages[role.name] = Image.open(f"data/images/roles/small/{role.name.lower().replace(' user', '')}.png")
except Exception as e:
next
if avatar == '':
async with aiohttp.ClientSession() as session:
async with session.get(def_avatar) as r:
image = await r.content.read()
else:
async with aiohttp.ClientSession() as session:
async with session.get(avatar) as r:
image = await r.content.read()
with open('data/users/avatars/{}.png'.format(user.id), 'wb') as f:
f.write(image)
checked = False
while checked == False:
checks = 0
isImage = imghdr.what('data/users/avatars/{}.png'.format(user.id))
if checks > 4:
checked = True
if isImage != 'None':
checked = True
else:
checks += 1
av = Image.open('data/users/avatars/{}.png'.format(user.id))
userAvatar = av.resize((128, 128), resample=Image.BILINEAR).convert('RGBA')
maxsize = ( 800, 500)
try:
bg = Image.open('data/users/backgrounds/{0}.png'.format(user.id))
bg_width, bg_height = bg.size
bg = ImageOps.fit(bg,maxsize)
except:
bg = Image.open('data/images/background_default.png')
fontFace = 'data/fonts/{}'.format(self.bot.config.fonts['normal'])
fontFace_bold = 'data/fonts/{}'.format(self.bot.config.fonts['bold'])
fontSize = 18
fontSizeVeryTiny = 16
descSizeQuestion = 10
descSizeAnswer = 10
headerSize = 32
headerSizeTiny = 24
headerSizeVeryTiny = 16
header_font = ImageFont.truetype(fontFace_bold, headerSize)
header_font_tiny = ImageFont.truetype(fontFace_bold, headerSizeTiny)
header_font_very_tiny = ImageFont.truetype(fontFace_bold, headerSizeVeryTiny)
font = ImageFont.truetype(fontFace, fontSize)
font_very_tiny = ImageFont.truetype(fontFace, fontSizeVeryTiny)
desc_font_question = ImageFont.truetype(fontFace_bold, descSizeQuestion)
desc_font_answer = ImageFont.truetype(fontFace, descSizeAnswer)
font_bold = font = ImageFont.truetype(fontFace_bold, fontSize)
answers = None
self.cursor.execute("SELECT os, config, languages, pays, passportdate, theme FROM passport WHERE userid=%s", str(user.id))
answers = self.cursor.fetchone()
if not theme:
if answers:
theme = str(answers[5])
else:
theme = "dark"
cardbg = Image.new('RGBA', (800, 500), (0, 0, 0, 255))
d = ImageDraw.Draw(cardbg)
d.rectangle([(0, 0), 800, 500], fill=(255, 255, 255, 255))
cardbg.paste(bg, (0, 0))
cardfg = Image.new('RGBA', (800, 500), (255, 255, 255, 0))
dd = ImageDraw.Draw(cardfg)
# Info Box Top
dd.rectangle([(60, 60), (600, 191)], fill=(color[theme]["background"], color[theme]["background"], color[theme]["background"], 200))
dd.rectangle([(60, 60), (600, 134)], fill=(color[theme]["background"], color[theme]["background"], color[theme]["background"], 255))
# Avatar box
if user_color == (0, 0, 0, 255):
user_color = (color[theme]["background"], color[theme]["background"], color[theme]["background"], 255)
dd.rectangle([(609, 60), (740, 191)], fill=user_color)
cardfg.paste(userAvatar, (611, 62))
# Profile Information
if textwrap.fill(nick, 25) != nick:
dd.text((70, 70), nick, fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 220), font=header_font_very_tiny)
dd.text((70, 106), '@' + name + '#' + discr, fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 225), font=font_very_tiny)
elif textwrap.fill(nick, 15) != nick:
dd.text((70, 70), nick, fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 220), font=header_font_tiny)
dd.text((70, 106), '@' + name + '#' + discr, fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 225), font=font)
else:
dd.text((70, 64), nick, fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 220), font=header_font)
dd.text((70, 106), '@' + name + '#' + discr, fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 225), font=font)
draw_underlined_text(dd, (380, 75), "Date de parution sur discord :", fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 220), font=desc_font_question)
dd.text((542, 75), formated_user_birth, fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 225), font=desc_font_answer)
# Roles
for idy, ii in enumerate(roleImages):
startx = int((270 - (30 * len(roleImages))) / 2)
cardfg.paste(roleImages[ii], (197 + startx + (30 * idy),152), roleImages[ii])
#Info Box Bottom
dd.rectangle([(60, 200), (740, 450)], fill=(color[theme]["background"], color[theme]["background"], color[theme]["background"], 200))
if answers:
passportdate = datetime.datetime.fromisoformat(answers[4])
passportdate_day = check_date(str(passportdate.day))
passportdate_month = check_date(str(passportdate.month))
formated_passportdate = str(passportdate_day) + "/" + str(passportdate_month) + "/" + str(passportdate.year)
draw_underlined_text(dd, (80, 220), "Système(s) d'exploitation :", desc_font_question, fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 255))
break_line(
dd,
(80, 240),
answers[0],
fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 255),
font=desc_font_answer
)
draw_underlined_text(dd, (80, 300), "Langages de programmation préférés :", desc_font_question, fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 255))
break_line(
dd,
(80, 320),
answers[2],
fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 255),
font=desc_font_answer
)
draw_underlined_text(dd, (80, 380), "Pays :", desc_font_question, fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 255))
break_line(
dd,
(80, 400),
answers[3],
fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 255),
font=desc_font_answer
)
dd.line((400, 220, 400, 430), fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 255))
draw_underlined_text(dd, (410, 220), "Configuration Système :", desc_font_question, fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 255))
break_line(
dd,
(410, 240),
answers[1],
fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 255),
font=desc_font_answer
)
else:
dd.text(
(370, 300),
"Non renseigné.",
fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 255),
font=desc_font_question
)
draw_underlined_text(dd, (380, 100), "Date de création du passeport :", fill=(color[theme]["question"], color[theme]["question"], color[theme]["question"], 220), font=desc_font_question)
dd.text((542, 100), formated_passportdate, fill=(color[theme]["answer"], color[theme]["answer"], color[theme]["answer"], 225), font=desc_font_answer)
card = Image.new('RGBA', (800, 500), (255, 255, 255, 255))
card = Image.alpha_composite(card, cardbg)
card = Image.alpha_composite(card, cardfg)
return card

View file

@ -1,7 +1,7 @@
token = "INSERT TOKEN HERE" token = "INSERT TOKEN HERE"
client_id = "INSERT_CLIENT_ID_HERE" client_id = <INSERT_CLIENT_ID_HERE (in int)>
log_channel_id = "INSERT_LOG_CHANNEL_HERE" log_channel_id = <INSERT_LOG_CHANNEL_HERE (in int)>
main_server_id = "INSERT_MAIN_CHANNEL_ID_HERE" main_server_id = <INSERT_MAIN_CHANNEL_ID_HERE (in int)>
game = "PLAYING_GAME_HERE" game = "PLAYING_GAME_HERE"
prefix = ["."] prefix = ["."]
@ -17,10 +17,4 @@ mysql = {
} }
authorized_id = ['admin ids here'] authorized_id = ['admin ids here']
unkickable_id = ['unkickable ids here']
## Passport settings
fonts = {
"normal": "NotoSansCJK-Regular.ttc",
"bold": "NotoSansCJK-Bold.ttc"
}

BIN
data/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
data/images/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,012 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

View file

@ -1,2 +0,0 @@
-- This file is only for keeping the folder alive in GitHub. --
-- Ce fichier ne sert qu'à garder ce dossier en vie dans GitHub. --