From 15716f8ea9d667f313d668d1ab03f0e3c397c0e7 Mon Sep 17 00:00:00 2001 From: Mylloon Date: Sun, 29 Nov 2020 11:39:41 +0100 Subject: [PATCH] premier commit --- .gitignore | 5 + cogs/fun.py | 108 +++++++++ cogs/games.py | 93 ++++++++ cogs/help.py | 67 ++++++ cogs/internet.py | 139 ++++++++++++ cogs/music.py | 565 +++++++++++++++++++++++++++++++++++++++++++++++ cogs/utils.py | 337 ++++++++++++++++++++++++++++ main.py | 180 +++++++++++++++ requirements.txt | 6 + 9 files changed, 1500 insertions(+) create mode 100644 .gitignore create mode 100644 cogs/fun.py create mode 100644 cogs/games.py create mode 100644 cogs/help.py create mode 100644 cogs/internet.py create mode 100644 cogs/music.py create mode 100644 cogs/utils.py create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f18c9a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +UPDATE/ +.dockerignore +Dockerfile +setup.py +cogs/music_old.py \ No newline at end of file diff --git a/cogs/fun.py b/cogs/fun.py new file mode 100644 index 0000000..70bd96f --- /dev/null +++ b/cogs/fun.py @@ -0,0 +1,108 @@ +import discord +from discord.ext import commands +from random import randint, choice + +def setup(client): + client.add_cog(Fun(client)) + +class Fun(commands.Cog): + """Commandes plutôt fun.""" + + def __init__(self, client): + self.client = client + + @commands.command(name='iq') + async def _iq(self, ctx, *, user = '0'): + """Calcule ton IQ.\n ➡ Syntaxe: .iq [user]⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + if user == '0': + user = ctx.author + await ctx.message.add_reaction(emoji = '✅') + return await ctx.send(f"T'as {randint(randint(-100,0),220)} IQ {user.mention} !") + else: + try: + user2 = user + user2 = user2[2:-1] + user2 = user2.replace("!","") + user2 = int(user2) + user2 = self.client.get_user(user2) + KassouBot = self.client.get_user(740140888373854269) + if user2.id == KassouBot.id: + await ctx.message.add_reaction(emoji = '✅') + return await ctx.send(f"Bah... pas ouf... j'ai juste 100000 IQ :/") + else: + await ctx.message.add_reaction(emoji = '✅') + message = await ctx.send("...") + return await message.edit(content = f"{user2.mention} a {randint(randint(-100,0),220)} IQ !") + except: + await ctx.message.add_reaction(emoji = '✅') + message = await ctx.send("...") + return await message.edit(content = f"{user} a {randint(randint(-100,0),220)} IQ !") + + @commands.command(name='love') + async def _love(self, ctx, *users: discord.Member): + """Découvre la probabilité que ces deux personnes se mettent en couple.\n ➡ Syntaxe: .love """ + if len(users) == 2 or len(users) == 1: + UneDemande = False + if len(users) == 1: + U = users + users = [] + users.append(U[0]) + users.append(ctx.author) + UneDemande = True + if users[0] == users[1]: + await ctx.message.add_reaction(emoji = '✅') + return await ctx.send("Je suis sûr que cette personne s'aime ! :angry:") + if users[0].nick: + user1 = list(users[0].nick) + else: + user1 = list(users[0].name) + if users[1].nick: + user2 = list(users[1].nick) + else: + user2 = list(users[1].name) + user1_CALC = self._retirerDoublons([x.lower() for x in user1]) + user2_CALC = self._retirerDoublons([x.lower() for x in user2]) + coef_amour = 0 + if len(user1_CALC) > len(user2_CALC): + taille_du_pls_grand = len(user1_CALC) + taille_du_ms_grand = len(user2_CALC) + else: + taille_du_pls_grand = len(user2_CALC) + taille_du_ms_grand = len(user1_CALC) + coef_amour = round(float(len(list(set(user1_CALC).intersection(user2_CALC))) / taille_du_pls_grand),1) * 100 + ((taille_du_pls_grand-taille_du_ms_grand) * 1.5) * 1.7 + if coef_amour > 100: + coef_amour = 100 + if UneDemande == True: + await ctx.message.add_reaction(emoji = '✅') + return await ctx.send(f"Tu as {coef_amour}% de chance de te mettre en couple avec {''.join(user1)}") + await ctx.message.add_reaction(emoji = '✅') + await ctx.send(f"{''.join(user1)} et {''.join(user2)} ont {coef_amour}% de chance de se mettre en couple !") + else: + await ctx.message.add_reaction(emoji = '❌') + await ctx.send("Erreur! Syntaxe : `.love [User2]`\n") + def _retirerDoublons(self, liste): + Newliste = [] + for element in liste: + if element not in Newliste: + Newliste.append(element) + return Newliste + @_love.error + async def _love_error(self, ctx, error): + await ctx.send(str(error).replace('Member "', "Le membre **").replace('" not found', "** n'as pas été trouvé.")) + + @commands.command(name='8ball', aliases=['8b', '8balls']) + async def _8ball(self, ctx, *, question): + """Répond à ta question 🔮.\n ➡ Syntaxe: .8ball/8b ⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + reponses=["c'est sûr.","il en est décidément ainsi.","incontestablement.","oui sans aucun doute.","tu peux t'y fier.","tel que je le vois, oui.","c'est le plus probable.", + "cela montre de bonnes perspectives.","certes.","les signes indiquent que oui.","ma réponse est oui.","ta question est trop floue, réessaie.","redemandes plus tard stp.", + "je ferais mieux de pas te le dire maintenant...","je ne peux pas le prédire actuellement :/","concentre-toi et redemande.","n'y comptes pas trop.","ma réponse est non.", + "mes sources disent que non.", "les perspectives ne sont pas si bonnes...","c'est très douteux."] + await ctx.send(f"{ctx.author.mention}, {choice(reponses)}") + @_8ball.error + async def _8ball_error(self, ctx, error): + if str(error) == "question is a required argument that is missing.": + await ctx.send("Mauvaise syntaxe : `.8ball/8b/8balls `.") + + @commands.command(name='pileouface', aliases=['pf']) + async def _pileouface(self, ctx): + return await ctx.send(f"{'Pile' if randint(0,1) == 1 else 'Face'} !") \ No newline at end of file diff --git a/cogs/games.py b/cogs/games.py new file mode 100644 index 0000000..7140457 --- /dev/null +++ b/cogs/games.py @@ -0,0 +1,93 @@ +import discord +from discord.ext import commands +from random import randint, choice +import asyncio + +def setup(client): + client.add_cog(Games(client)) + +class Games(commands.Cog): + """Commandes relatives aux jeux.""" + + def __init__(self, client): + self.client = client + self.guessing_game = {} + + @commands.command(name='chifumi', aliases = ["shifumi", "ppc"]) + async def _chifumi(self, ctx, *, choix): + """Un simple Chifumi contre le bot.\n ➡ Syntaxe: .chifumi/shifumi/ppc """ + + choix_jeu = ["Pierre ✊", "Papier 🧻", "Ciseaux ✂"] + orditxt = choice(choix_jeu) + ordi = choix_jeu.index(orditxt) + + PIERRE = 0 + PAPIER = 1 + CISEAUX = 2 + + choix = choix.lower() + if choix == "pierre": + choix = PIERRE + if choix == "papier" or choix == "feuille": + choix = PAPIER + if choix == "ciseaux" or choix == "ciseau": + choix = CISEAUX + + description = (f"{choix_jeu[choix][:-1]} VS {choix_jeu[ordi][:-1]}\n\n**" + f"{('Égalité !', 'Tu as perdu !', 'Tu as gagné !')[(choix != ordi) + ((choix > ordi and ordi +1 == choix) or (choix < ordi and choix + ordi == 2))]}**") + + embed = discord.Embed(title = f"{choix_jeu[choix][-1:]}VS {choix_jeu[ordi][-1:]}", description = description) + embed.set_author(name = ctx.author.name, icon_url = ctx.author.avatar_url) + await ctx.send(embed = embed) + await ctx.message.add_reaction(emoji = '✅') + @_chifumi.error + async def _chifumi_error(self, ctx, error): + await ctx.send("Mauvaise syntaxe : `.chifumi/shifumi/ppc `.") + + + @commands.command(name='plusoumoins', aliases = ['+ou-', '+-']) + async def _plusoumoins(self, ctx): + """Un plus ou moins entre 1 et 100.\n ➡ Syntaxe: .plusoumoins/+ou-/+-⁢⁢⁢⁢⁢""" + if str(ctx.author.id) in self.guessing_game: + return await ctx.send("Tu es déjà en partie.") + guess = 5 + self.guessing_game[str(ctx.author.id)] = guess + number = randint(1,100) + message = f"Choisis un nombre entre 1 et 100 {ctx.author.mention}." + await ctx.send(message) + while self.guessing_game[str(ctx.author.id)] != 0: + try: + def check(message): + if message.author.bot == False: + return str(message.author.id) in self.guessing_game + msg = await self.client.wait_for('message', check = check, timeout = 30) + except asyncio.TimeoutError: + del self.guessing_game[str(ctx.author.id)] + return await ctx.send(f"Tu as mis trop de temps a répondre {ctx.author.mention}. La réponse était {number}.") + if msg.author == ctx.author: + if msg.content == "stop": + del self.guessing_game[str(ctx.author.id)] + return await ctx.send(f"Fin du plus ou moins {ctx.author.mention}. La réponse était {number}.") + try: + attempt = int(msg.content) + if attempt > number: + if guess-1 != 0: + await ctx.send(f"J'pense que c'est moins {ctx.author.mention}... Il te reste {guess-1} essai{'s' if guess-1>1 else ''}.") + guess -= 1 + self.guessing_game[str(ctx.author.id)] = guess + if guess != 0: + await ctx.send(message) + elif attempt < number: + if guess-1 != 0: + await ctx.send(f"J'pense que c'est plus {ctx.author.mention}... Il te reste {guess-1} essai{'s' if guess-1>1 else ''}.") + guess -=1 + self.guessing_game[str(ctx.author.id)] = guess + if guess != 0: + await ctx.send(message) + elif attempt == number: + del self.guessing_game[str(ctx.author.id)] + return await ctx.send(f"Tu as trouvé {ctx.author.mention}, bien joué !") + except: + await ctx.send(f"Erreur dans la réponse {ctx.author.mention}, merci de n'écrire qu'un nombre. Tapez `stop` pour arrêter le jeu.") + del self.guessing_game[str(ctx.author.id)] + await ctx.send(f"T'as pas trouvé {ctx.author.mention}... dommage, c'était {number}.") \ No newline at end of file diff --git a/cogs/help.py b/cogs/help.py new file mode 100644 index 0000000..a413ec4 --- /dev/null +++ b/cogs/help.py @@ -0,0 +1,67 @@ +import discord +from discord.ext import commands +from random import randint + +def setup(client): + client.add_cog(Help(client)) + +class Help(commands.Cog): + """Listes des commandes et/ou catégories.""" + + def __init__(self, client): + self.client = client + self.client.remove_command("help") + + @commands.command(name='help') + async def _help(self, ctx, *cog): + """Affiche toutes les commandes du bot.\n ➡ Syntaxe: .help [catégorie]⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + if not cog: + """Liste des Cog""" + halp=discord.Embed(title = 'Liste des catégories et commandes sans catégorie', + description = f'Utilisez `{ctx.prefix}help [catégorie]` pour en savoir plus sur elles et leur commande.', + color = randint(0, 0xFFFFFF)) + for name_cog in self.client.cogs: + liste_cmds = "" + nb_cmds = 0 + for cmds in self.client.get_cog(name_cog).get_commands(): + if not cmds.hidden: + liste_cmds += f", `{ctx.prefix}{cmds.name}`" + nb_cmds += 1 + if name_cog != "Help": + halp.add_field(name = f'**{name_cog} — {nb_cmds}**', value = liste_cmds[2:], inline = False) + cmds_desc = '' + for y in self.client.walk_commands(): + if not y.cog_name and not y.hidden: + cmds_desc += (f'{ctx.prefix}{y.name} - {y.help}\n ⁢⁢⁢⁢⁢ ') + + if len(cmds_desc) > 1: + halp.add_field(name = 'Commandes sans catégorie', value = cmds_desc[0:len(cmds_desc)-1], inline = False) + await ctx.message.add_reaction(emoji = '✅') + await ctx.send(embed = halp) + else: + """Avertissement si il y a trop d'arguments dans la variable cog""" + if len(cog) > 1: + halp = discord.Embed(title = 'Erreur !', description = "Tu as renseigné trop d'arguments !", color = 0xC41B1B) + await ctx.send(embed = halp) + else: + """Liste des commandes avec cogs.""" + cog = [item.capitalize() for item in cog] + found = False + for x in self.client.cogs: + for y in cog: + if x == y: + halp = discord.Embed(title = f'{cog[0]} - Liste des commandes', description = self.client.cogs[cog[0]].__doc__, color = randint(0, 0xFFFFFF)) + for c in self.client.get_cog(y).get_commands(): + if not c.hidden: + cmds_help = str(c.help).split("\n") + del cmds_help[0] + backslash = '\n' + halp.add_field(name = f"`{ctx.prefix}{c.name}` - {str(c.help).split(backslash)[0]}", value = f"{''.join(cmds_help)}\u200b", inline = False) + found = True + if not found: + """Rappel si le cog n'existe pas.""" + await ctx.message.add_reaction(emoji = '❌') + halp = discord.Embed(title = 'Erreur !', description = f"Qu'est ce que {cog[0]} ?", color = 0xC41B1B) + else: + await ctx.message.add_reaction(emoji = '✅') + await ctx.send('', embed = halp) \ No newline at end of file diff --git a/cogs/internet.py b/cogs/internet.py new file mode 100644 index 0000000..8bdaefd --- /dev/null +++ b/cogs/internet.py @@ -0,0 +1,139 @@ +import discord, praw, json, requests, datetime +from discord.ext import commands +from random import randint, choice +import time + +def setup(client): + client.add_cog(Internet(client)) + +class Internet(commands.Cog): + """Commandes relatives à ce qui provient d'internet.""" + + + def __init__(self, client): + self.client = client + + @commands.Cog.listener() + async def on_message(self, message): + if message.channel.id == 770805818487865404 or message.channel.id == 772239638240165928: # Le groupe de l'amour ❤❤ -- channel chien/chat + chiens = ["dog", "chien", "potichien"] + chats = ["kat", "mace", "kater", "katze", "sinta", "minoos", "cat", "qitt", "besseh", "katu", "caun", "kazh", + "bisig", "moggy", "kotka", "maow", "gat", "we'sa", "guigna", "kodkod", "mao", "koyangi", "ghjattu", "míw", "pussi", + "gato", "gata", "kato", "kass", "domadh", "demmat", "kissa", "chat", "minou", "piscín", "cath", "k'at'a", "muca", "gali", + "gatos", "popoki", "kike", "chatul", "chatula", "billa", "kat poes", "macska", "cica", "kutjing", "kucing", "köttur", + "gatto", "gattina", "neko", "chma", "pising", "feles", "felix", "kakis", "katé", "qattus", "qattusa", "ngeru", "miz", "felino", + "felina", "muur", "katt", "shimii", "billi", "gorbe", "pusa", "kot", "giat", "pisica", "koshka", "pusi", "macka", "mizhu", + "kotsur", "bisad", "büsi", "chatz", "paka", "muc", "poonai", "puunay", "kocour", "kocka", "maa-oh", "kedi", "kit", "con mêo", + "tchèt", "mouss", "ologbo", "kats", "猫", "кот", "고양이", "poticha", "😼"] + if message.content.lower() in chiens: + await self._dog(await self.client.get_context(message)) + if message.content.lower() in chats: + await self._cat(await self.client.get_context(message)) + + @commands.command(name='memes', aliases = ['meme']) + async def _memes(self, ctx, *, args = ""): + """Envois un meme de reddit.\n ➡ Syntaxe: .memes/meme [subreddit]⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + try: + reddit = praw.Reddit(client_id = 'nHPaCR8L_jlmwQ', client_secret = 'tSCjb4QvdiNyCYKmW35SEWhjV8w', user_agent = 'disreddit /u/mylloon, http://localhost:8080') + + if args != "": # si il y a un arg différent d'un meme + subredditchoix = args + + else: # si il n'y a pas d'arguments + subredditchoix = choice(['memes', 'anime_irl', 'goodanimemes', 'BikiniclienttomTwitter', 'dankmemes', 'DeepFriedMemes', + 'educationalmemes', 'funny', 'marvelmemes', 'me_irl', 'meme', 'MemeEconomy', 'Memes_Of_The_Dank', 'MinecraftMemes', + 'physicsmemes', 'reactiongifs', 'blackpeopletwitter', 'metal_me_irl', 'bee_irl', '195', + 'shittyadviceanimals', 'meirl', '2meirl4meirl', 'AdviceAnimals', 'weirdmemes']) + + memes_submissions = reddit.subreddit(subredditchoix).hot() + post_to_pick = randint(1, 10) + for i in range(0, post_to_pick): # i pas important + i = i #retire l'erreur sur vscode + submission = next(x for x in memes_submissions if not x.stickied) + + image = ["png", "jpg", "jpeg", "bmp", "gif"] + if submission.url[-3:] in image: + embed = discord.Embed(title = f"r/{subredditchoix} pour {ctx.author.name}", color = randint(0, 0xFFFFFF), description = f"[lien du meme]({submission.url})") + embed.set_footer(text = f"Meme de Reddit") + embed.set_image(url = submission.url) + message = await ctx.send(embed = embed) + else: + await ctx.send(f"```r/{subredditchoix} pour {ctx.author.name}```\n{submission.url}") + message = await ctx.send("```Meme de Reddit```") + await ctx.message.add_reaction(emoji = '✅') + await message.add_reaction('👍') + return await message.add_reaction('👎') + + except Exception as error: + print(f"args: {args}, subreddit: {subredditchoix}, error: {error}") + await ctx.message.add_reaction(emoji = '❌') + return await ctx.send(f"Ce subreddit est interdit, mis en quarantaine ou n'existe pas. ({subredditchoix})") + + def _random_image(self, link): + temps_requete = int(round(time.time() * 1000)) + try: + request_data = requests.get(link) + except Exception as e: + raise Exception(f"Une erreur s'est produite lors de la tentative de demande de l'API {link} : {e}") + + if not request_data.status_code == 200: + raise Exception(f"Code HTTP {request_data.status_code} au lieu de HTTP 200 à l'appel de {link} : {request_data.text}") + + try: + json_data = json.loads(request_data.text) + except Exception as e: + raise Exception(f"Erreur lors de la transformation les données de {link} en json : {e}") + + temps_requete = int(round(time.time() * 1000)) - temps_requete + return (json_data, temps_requete) + + @commands.command(name='cat', aliases = ['chat']) + async def _cat(self, ctx): + """Te montre un magnifique chat\n ➡ Syntaxe: .cat/chat""" + + if ctx.author.nick: + name = f"{ctx.author.nick} ({ctx.author.name}#{ctx.author.discriminator})" + else: + name = f"{ctx.author.name}" + embed = discord.Embed(title = f"Poticha pour {name}", colour = randint(0, 0xFFFFFF)) + cat = self._random_image("http://aws.random.cat/meow") + embed.set_image(url = cat[0]['file']) + embed.set_footer(text = f"random.cat a pris {cat[1]} ms.") + await ctx.message.add_reaction(emoji = '✅') + message = await ctx.send(embed=embed) + return await message.add_reaction('❤️') + + @commands.command(name='dog', aliases = ['chien']) + async def _dog(self, ctx): + """Te montre un magnifique chien\n ➡ Syntaxe: .dog/chien""" + + if ctx.author.nick: + name = f"{ctx.author.nick} ({ctx.author.name}#{ctx.author.discriminator})" + else: + name = f"{ctx.author.name}" + embed = discord.Embed(title = f"Potichien pour {name}", colour = randint(0, 0xFFFFFF)) + dog = self._random_image("https://dog.ceo/api/breeds/image/random") + embed.set_image(url = dog[0]['message']) + embed.set_footer(text = f"dog.ceo a pris {dog[1]} ms.") + await ctx.message.add_reaction(emoji = '✅') + message = await ctx.send(embed=embed) + return await message.add_reaction('❤️') + + @commands.command(name='sexe', aliases=['sexes', 'nude', 'nudes', 'nsfw']) + async def _sexe(self, ctx, *, choice_of_nsfw = None): + """Envois une image coquine. (NSFW)\n ➡ Syntaxe: .sexe/sexes/nude/nudes [butts/boobs]⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + liste_hot = ['butts', 'boobs'] + if choice_of_nsfw in liste_hot: + pass + else: + choice_of_nsfw = choice(liste_hot) + if ctx.channel.is_nsfw(): + embed = discord.Embed(title = f"{choice_of_nsfw.capitalize()} pour {ctx.author.name}", colour = randint(0, 0xFFFFFF)) + nsfw = self._random_image(f'http://api.o{choice_of_nsfw}.ru/noise/') + embed.set_image(url = f"http://media.o{choice_of_nsfw}.ru/{nsfw[0][0]['preview']}") + embed.set_footer(text = f"o{choice_of_nsfw}.ru a pris {nsfw[1]} ms.") + await ctx.message.add_reaction(emoji = '✅') + await ctx.send(embed = embed) + else: + await ctx.message.add_reaction(emoji = '❌') + await ctx.send(f"Désolé mais je n'envois ce genre de message seulement dans les salons NSFW !") \ No newline at end of file diff --git a/cogs/music.py b/cogs/music.py new file mode 100644 index 0000000..28290cc --- /dev/null +++ b/cogs/music.py @@ -0,0 +1,565 @@ +# -*- coding: utf-8 -*- + +""" +Copyright (c) 2019 Valentin B. + +https://gist.github.com/vbe0201/ade9b80f2d3b64643d854938d40a0a2d +""" + +import asyncio +import functools +import itertools +import math +import random + +import discord +import youtube_dl +from async_timeout import timeout +from discord.ext import commands + +from random import randint +import lyricsgenius +import time + +# Genius API +genius = lyricsgenius.Genius("gmAB9NLNoDACxvcf5IjJKVVgbYx3UJ8CZmdQkFOitRfyFewI6qvZFe62x6EUETpK") + +# Silence useless bug reports messages +youtube_dl.utils.bug_reports_message = lambda: '' + +def setup(client): + client.add_cog(Music(client)) + +class VoiceError(Exception): + pass + + +class YTDLError(Exception): + pass + + +class YTDLSource(discord.PCMVolumeTransformer): + YTDL_OPTIONS = { + 'format': 'bestaudio/best', + 'extractaudio': True, + 'audioformat': 'mp3', + 'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s', + 'restrictfilenames': True, + 'noplaylist': True, + 'nocheckcertificate': True, + 'ignoreerrors': False, + 'logtostderr': False, + 'quiet': True, + 'no_warnings': True, + 'default_search': 'auto', + 'source_address': '0.0.0.0', + } + + FFMPEG_OPTIONS = { + 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', + 'options': '-vn', + } + + ytdl = youtube_dl.YoutubeDL(YTDL_OPTIONS) + + def __init__(self, ctx: commands.Context, source: discord.FFmpegPCMAudio, *, data: dict, volume: float = 0.5): + super().__init__(source, volume) + + self.requester = ctx.author + self.channel = ctx.channel + self.data = data + + self.uploader = data.get('uploader') + self.uploader_url = data.get('uploader_url') + date = data.get('upload_date') + self.upload_date = date[6:8] + '.' + date[4:6] + '.' + date[0:4] + self.title = data.get('title') + self.thumbnail = data.get('thumbnail') + self.description = data.get('description') + self.duration = self.parse_duration(int(data.get('duration'))) + self.tags = data.get('tags') + self.url = data.get('webpage_url') + self.views = data.get('view_count') + self.likes = data.get('like_count') + self.dislikes = data.get('dislike_count') + self.stream_url = data.get('url') + + def __str__(self): + return f"**{self.title}** de **{self.uploader}**" + + @classmethod + async def create_source(cls, ctx: commands.Context, search: str, *, loop: asyncio.BaseEventLoop = None): + loop = loop or asyncio.get_event_loop() + + partial = functools.partial(cls.ytdl.extract_info, search, download=False, process=False) + data = await loop.run_in_executor(None, partial) + + if data is None: + raise YTDLError(f"Je n'ai rien trouvé qui corresponde à `{search}`") + + if 'entries' not in data: + process_info = data + else: + process_info = None + for entry in data['entries']: + if entry: + process_info = entry + break + + if process_info is None: + raise YTDLError(f"Je n'ai rien trouvé qui corresponde à `{search}`") + + webpage_url = process_info['webpage_url'] + partial = functools.partial(cls.ytdl.extract_info, webpage_url, download=False) + processed_info = await loop.run_in_executor(None, partial) + + if processed_info is None: + raise YTDLError(f"Impossible d'obtenir `{webpage_url}`") + + if "entries" not in processed_info: + info = processed_info + else: + info = None + while info is None: + try: + info = processed_info['entries'].pop(0) + except IndexError: + raise YTDLError(f"Aucune correspondances pour `{webpage_url}`") + + return cls(ctx, discord.FFmpegPCMAudio(info['url'], **cls.FFMPEG_OPTIONS), data=info) + + @staticmethod + def parse_duration(duration: int): + minutes, seconds = divmod(duration, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + + duration = [] + if days > 0: + duration.append(f"{days} jours,{'' if days <= 1 else 's'}") + if hours > 0: + duration.append(f"{hours} H,{'' if hours <= 1 else 's'}") + if minutes > 0: + duration.append(f"{minutes} min{'' if minutes <= 1 else 's'}") + if seconds > 0: + duration.append(f"{seconds} sec{'' if seconds <= 1 else 's'}") + + return ' '.join(duration) + + +class Song: + __slots__ = ('source', 'requester') + + def __init__(self, source: YTDLSource): + self.source = source + self.requester = source.requester + + def create_embed(self): + embed = (discord.Embed(title="Joue", + description=f"\n[{self.source.title}]({self.source.url})\n", + color=randint(0, 0xFFFFFF)) + .add_field(name="Durée", value=self.source.duration) + .add_field(name="Demandé par", value=self.requester.mention) + .add_field(name="Chaîne", value=f"[{self.source.uploader}]({self.source.uploader_url})") + .set_thumbnail(url=self.source.thumbnail)) + + return embed + + def title(self): + return self.source.title + + +class SongQueue(asyncio.Queue): + def __getitem__(self, item): + if isinstance(item, slice): + return list(itertools.islice(self._queue, item.start, item.stop, item.step)) + else: + return self._queue[item] + + def __iter__(self): + return iter(self._queue.__iter__()) + + def __len__(self): + return self.qsize() + + def clear(self): + self._queue.clear() + + def shuffle(self): + random.shuffle(self._queue) + + def remove(self, index: int): + del self._queue[index] + + +class VoiceState: + def __init__(self, client: commands.bot, ctx: commands.Context): + self.client = client + self._ctx = ctx + + self.current = None + self.voice = None + self.next = asyncio.Event() + self.songs = SongQueue() + + self._loop = False + self._volume = 1.0 + + self.audio_player = client.loop.create_task(self.audio_player_task()) + + def __del__(self): + self.audio_player.cancel() + + @property + def loop(self): + return self._loop + + @loop.setter + def loop(self, value: bool): + self._loop = value + + @property + def volume(self): + return self._volume + + @volume.setter + def volume(self, value: float): + self._volume = value + + @property + def is_playing(self): + return self.voice and self.current + + async def audio_player_task(self): + while True: + self.next.clear() + + if not self.loop: + # Try to get the next song within 3 minutes. + # If no song will be added to the queue in time, + # the player will disconnect due to performance + # reasons. + try: + async with timeout(180): # 3 minutes + self.current = await self.songs.get() + except asyncio.TimeoutError: + self.client.loop.create_task(self.stop()) + return + + self.current.source.volume = self._volume + self.voice.play(self.current.source, after=self.play_next_song) + await self.current.source.channel.send(embed=self.current.create_embed()) + + await self.next.wait() + + def play_next_song(self, error=None): + if error: + raise VoiceError(str(error)) + + self.next.set() + + def skip(self): + if self.is_playing: + self.voice.stop() + + async def stop(self): + self.songs.clear() + + if self.voice: + await self.voice.disconnect() + self.voice = None + + +class Music(commands.Cog): + """Commandes relatives à la musique - © vbe0201.""" + def __init__(self, client: commands.bot): + self.client = client + self.voice_states = {} + + def get_voice_state(self, ctx: commands.Context): + state = self.voice_states.get(ctx.guild.id) + if not state: + state = VoiceState(self.client, ctx) + self.voice_states[ctx.guild.id] = state + + return state + + def cog_unload(self): + for state in self.voice_states.values(): + self.client.loop.create_task(state.stop()) + + def cog_check(self, ctx: commands.Context): + if not ctx.guild: + raise commands.NoPrivateMessage("Je ne fais pas de musiques en DM.") + + return True + + async def cog_before_invoke(self, ctx: commands.Context): + ctx.voice_state = self.get_voice_state(ctx) + + async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): + await ctx.send(f"Une erreur est survenue : {str(error)}") + + @commands.command(name='join', aliases=['j'], invoke_without_subcommand=True) + async def _summon(self, ctx: commands.Context, *, channel: discord.VoiceChannel = None): + """Se connecte au salon vocal.\n ➡ Syntaxe: .connect/join⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + + if not channel and not ctx.author.voice: + await ctx.send("Aucun channel à rejoindre. Connecte toi dans un vocal ou donne-moi son id.") + raise VoiceError("Vous n'êtes pas connecté à un channel vocal et n'avez spécifié aucun channel à rejoindre.") + + destination = channel or ctx.author.voice.channel + await ctx.send(f":thumbsup: **Connecté à __{destination}__**") + if ctx.voice_state.voice: + await ctx.voice_state.voice.move_to(destination) + return + + ctx.voice_state.voice = await destination.connect() + + @commands.command(name='stop', aliases=['disconnect', 'dc']) + async def _leave(self, ctx: commands.Context): + """Arrête la chanson en cours de lecture et quitte le salon vocal.\n ➡ Syntaxe: .disconnect/dc/stop⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + + if not ctx.voice_state.voice: + embed = discord.Embed(description = "Tape `.play ` pour jouer quelque chose ou `.join [id]` pour me connecter à un salon vocal.", color = 0xC41B1B) + embed.set_author(name = "Je ne suis connecté à aucun vocal.") + return await ctx.send(embed = embed) + + await ctx.voice_state.stop() + del self.voice_states[ctx.guild.id] + await ctx.send("📭 **Déconnecté du salon**") + + @commands.command(name='volume', aliases=['vol']) + async def _volume(self, ctx: commands.Context, *, volume: int = False): + """Modifie le volume du bot (entre 1 et 100).\n ➡ Syntaxe: .volume/vol [1;100]⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + + if not ctx.voice_state.is_playing: + return await ctx.send("Rien n'est joué pour le moment.") + + if not volume: + return await ctx.send(f"Le volume est à **{ctx.voice_state.volume * 100}%**") + + if 0 > volume > 100: + return await ctx.send("Le volume doit être compris entre 0 et 100.") + + ctx.voice_state.volume = volume / 100 + await ctx.send(f"Volume réglé sur **{volume}%**") + + @commands.command(name='now', aliases=['current', 'playing', 'np']) + async def _now(self, ctx: commands.Context): + """Affiche des informations sur la chanson en cours de lecture.\n ➡ Syntaxe: .now/current⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢/playing/np""" + + await ctx.send(embed=ctx.voice_state.current.create_embed()) + + @commands.command(name='pause') + async def _pause(self, ctx: commands.Context): + """Mets en pause de la chanson en cours de lecture.⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + + if not ctx.voice_state.is_playing and ctx.voice_state.voice.is_playing(): + ctx.voice_state.voice.pause() + await ctx.message.add_reaction('⏯') + await ctx.send(f"**`{ctx.author}`** met en pause la chanson en cours.") + else: + embed = discord.Embed(description = "Tape `.play ` pour jouer quelque chose.", color = 0xC41B1B) + embed.set_author(name = "Je ne joue rien en ce moment !") + return await ctx.send(embed = embed) + + @commands.command(name='resume') + async def _resume(self, ctx: commands.Context): + """Reprends la chanson en pause.""" + + if not ctx.voice_state.is_playing and ctx.voice_state.voice.is_paused(): + ctx.voice_state.voice.resume() + await ctx.message.add_reaction('⏯') + await ctx.send(f"**`{ctx.author}`** relance la chanson.") + else: + if ctx.voice_state.is_playing: + embed = discord.Embed(description = "Tape `.pause` pour mettre en pause la chanson.", color = 0xC41B1B) + embed.set_author(name = "Je suis déjà en lecture !") + return await ctx.send(embed = embed) + else: + embed = discord.Embed(description = "Tape `.play ` pour jouer quelque chose ou `.join [id]` pour me connecter à un salon vocal.", color = 0xC41B1B) + embed.set_author(name = "Je ne suis connecté à aucun vocal.") + return await ctx.send(embed = embed) + + @commands.command(name='skip', aliases=['s']) + async def _skip(self, ctx: commands.Context): + """Passe la chanson.\n ➡ Syntaxe: .skip/s""" + + if not ctx.voice_state.is_playing: + embed = discord.Embed(description = "Tape `.play ` pour jouer quelque chose.", color = 0xC41B1B) + embed.set_author(name = "Je ne joue rien en ce moment !") + return await ctx.send(embed = embed) + + await ctx.message.add_reaction('⏭') + ctx.voice_state.skip() + await ctx.send(f"**`{ctx.author}`**: Passe la chanson !") + + @commands.command(name='queue', aliases=['q', 'playlist']) + async def _queue(self, ctx: commands.Context, *, page: int = 1): + """Affiche la file d'attente des chansons à venir.\n ➡ Syntaxe: .queue/q⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢/playlist [page]""" + + if len(ctx.voice_state.songs) == 0: + embed = discord.Embed(description = "Tape `.play ` pour jouer quelque chose.", color = 0xC41B1B) + embed.set_author(name = "Il n'y a plus de chanson à venir dans la playlist.") + return await ctx.send(embed = embed) + + items_per_page = 10 + pages = math.ceil(len(ctx.voice_state.songs) / items_per_page) + + if page > pages: + embed = discord.Embed(description = "Tape `.play ` pour rajouter encore de la musique.", color = 0xC41B1B) + embed.set_author(name = "Il n'y a pas autant de pages") + return await ctx.send(embed = embed) + + start = (page - 1) * items_per_page + end = start + items_per_page + + queue = '' + for i, song in enumerate(ctx.voice_state.songs[start:end], start=start): + queue += f"`{i + 1}.` [**{song.source.title}**]({song.source.url})\n" + + embed = (discord.Embed(description=f"**{len(ctx.voice_state.songs)} piste{'s' if len(ctx.voice_state.songs)>1 else ''} :**\n\n{queue}", color = randint(0, 0xFFFFFF)) + .set_footer(text=f"Page {page}/{pages}")) + await ctx.send(embed=embed) + + @commands.command(name='shuffle') + async def _shuffle(self, ctx: commands.Context): + """Mélange la file d'attente.""" + + if len(ctx.voice_state.songs) == 0: + embed = discord.Embed(description = "Tape `.play ` pour jouer quelque chose.", color = 0xC41B1B) + embed.set_author(name = "La file est vide.") + return await ctx.send(embed = embed) + + ctx.voice_state.songs.shuffle() + await ctx.message.add_reaction('✅') + + @commands.command(name='remove') + async def _remove(self, ctx: commands.Context, index: int): + """Supprime une chanson de la file d'attente.""" + + if len(ctx.voice_state.songs) == 0: + return await ctx.send("File vide.") + + ctx.voice_state.songs.remove(index - 1) + await ctx.message.add_reaction('✅') + await ctx.send("Chanson sélectionnée supprimée de la file d'attente.") + + @commands.command(name='loop', aliases=['repeat']) + async def _loop(self, ctx: commands.Context): + """Répète la chanson actuellement en lecture.\n ➡ Syntaxe: .loop/repeat""" + + if not ctx.voice_state.is_playing: + embed = discord.Embed(description = "Tape `.play ` pour jouer quelque chose.", color = 0xC41B1B) + embed.set_author(name = "Je ne joue rien en ce moment !") + return await ctx.send(embed = embed) + + # Inverse boolean value to loop and unloop. + ctx.voice_state.loop = not ctx.voice_state.loop + await ctx.message.add_reaction('✅') + await ctx.send("La chanson change d'état.") + + @commands.command(name='play', aliases=['p']) + async def _play(self, ctx: commands.Context, *, search: str): + """Recherche une chanson sur les sites compatibles avec YoutubeDL si aucun URL n'est donné et l'ajoute à la file d'attente.\n ➡ Syntaxe: .play/p⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + + if not ctx.voice_state.voice: + await ctx.invoke(self._summon) + + async with ctx.typing(): + try: + source = await YTDLSource.create_source(ctx, search, loop=self.client.loop) + except YTDLError as e: + await ctx.send(f"Une erreur s'est produite lors du traitement de cette demande : {str(e)}") + else: + song = Song(source) + + await ctx.voice_state.songs.put(song) + await ctx.send(f"En file d'attente {str(source)}") + + @_summon.before_invoke + @_play.before_invoke + async def ensure_voice_state(self, ctx: commands.Context): + if not ctx.author.voice or not ctx.author.voice.channel: + raise commands.CommandError("Vous n'êtes connecté à aucun channel vocal.") + + if ctx.voice_client: + if ctx.voice_client.channel != ctx.author.voice.channel: + raise commands.CommandError("Le bot est déjà dans un channel vocal.") + + @commands.command(name='lyrics', aliases = ['l', 'lyric']) + async def _lyrics(self, ctx, *, song: str = None): + """Affiche les paroles de la musique en cours, ou de la chanson spécifiée.\n ➡ Syntaxe: .lyrics/lyric/l (musique)⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + if song or ctx.voice_state.is_playing: + if not song: + song = f"{ctx.voice_state.current.title()}" + if " romanized" in song: + message = await ctx.send(f":mag: **Cherche les paroles romanisées de ** `{song.replace(' romanized', '')}`") + else: + message = await ctx.send(f":mag: **Cherche les paroles de ** `{song}`") + temps_requete = int(round(time.time() * 1000)) + song_genius = genius.search_song(song) + couleur_embed = randint(0, 0xFFFFFF) + try: + paroles = song_genius.lyrics + except: + await ctx.message.add_reaction(emoji = '❌') + return await message.edit(content = f"Pas de résultats trouvés pour `{song}`.") + lignetotal = "" + premierembed = True + if len(paroles) > 7500: + await ctx.message.add_reaction(emoji = '❌') + return await message.edit(content = f"Désolé, les paroles sont trop longues pour être affichés (lien vers la page des paroles : {song_genius.url}).") + title_first_embed = f"Paroles de {song_genius.title} par {song_genius.artist}." + desc_first_embed = f"[Lien vers les paroles sur le site]({song_genius.url})" + type_de_comptage = "\n\n" if paroles.count("\n\n") > 2 else "\n" + for ligne in paroles.split(type_de_comptage): + if len(ligne) >= 2048: + type_de_comptage = "\n" + for ligne in paroles.split(type_de_comptage): + if len(f"{lignetotal}{type_de_comptage}{ligne}") < 1900: + lignetotal = f"{lignetotal}{type_de_comptage}{self.ligne_formatage(ligne)}" + else: + if premierembed == True: + premierembed = False + embed = discord.Embed(title = title_first_embed, description = f"{desc_first_embed}{lignetotal}", color = couleur_embed) + embed.set_thumbnail(url = song_genius.song_art_image_url) + await message.edit(embed = embed) + else: + embed = discord.Embed(description = lignetotal, color = couleur_embed) + await ctx.send(embed = embed) + lignetotal = f"{self.ligne_formatage(ligne)}" + + temps_requete = int(round(time.time() * 1000)) - temps_requete + footer_embed = f"Pour {self.user_or_nick(ctx.author)} par Genius en {round(temps_requete / 1000, 2)} s." + await ctx.message.add_reaction(emoji = '✅') + if premierembed == True: + premierembed = False + embed = discord.Embed(title = title_first_embed, description = f"{desc_first_embed}{lignetotal}", color = couleur_embed) + embed.set_footer(icon_url = ctx.author.avatar_url, text = footer_embed) + return await message.edit(embed = embed) + else: + embed = discord.Embed(description = lignetotal, color = couleur_embed) + embed.set_footer(icon_url = ctx.author.avatar_url, text = footer_embed) + return await ctx.send(embed = embed) + else: + await ctx.message.add_reaction(emoji = '❌') + await ctx.send("Aucune musique demandé... `.lyrics/l/lyrics `.") + def ligne_formatage(self, ligne): + liste_balise = [ + ('[Hook', '[Accroche'), ('[Verse', '[Couplet'), ('[Chorus', '[Chœur'), + ('[Bridge', '[Pont'),('[Pre-Chorus', '[Pré-chœur'), ('[Post-Chorus', '[Post-chœur') + ] + for balises in liste_balise: + ligne = ligne.replace(balises[0], balises[1]) + return ligne + def user_or_nick(self, user): + if user.nick: + return f"{user.nick} ({user.name}#{user.discriminator})" + else: + return f"{user.name}#{user.discriminator}" + + @commands.command(name='lyricsromanized', aliases = ['lr', 'lyricromanized'], hidden = True) + async def _lyricsromanized(self, ctx, *, song: str = None): + await ctx.invoke(self.client.get_command("lyrics"), song = f"{song} romanized" if song else song) \ No newline at end of file diff --git a/cogs/utils.py b/cogs/utils.py new file mode 100644 index 0000000..ba4af36 --- /dev/null +++ b/cogs/utils.py @@ -0,0 +1,337 @@ +import discord, pytz, time +from discord.ext import commands +from random import randint, shuffle +from datetime import datetime +from pytz import timezone +import re +import asyncio + +def setup(client): + client.add_cog(Utils(client)) + +class Utils(commands.Cog): + """Commandes essentielles.""" + + def __init__(self, client): + self.client = client + + + @commands.command(name='ping') + async def _ping(self, ctx, *, question = '0'): + """Affiche mon ping.⁢⁢⁢⁢⁢\n ➡ Syntaxe: .ping [help]⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + if question == 'help': + return await ctx.send(embed = discord.Embed(color = randint(0, 0xFFFFFF), description = ":hourglass: correspond au temps entre deux battements de cœurs (en millisecondes)\n\n:stopwatch: correspond au temps que met le client a calculer le ping (en millisecondes)\n\n:heartbeat: correspond au temps que met le client a réagir au messages (en millisecondes)")) + else: + now = int(round(time.time() * 1000)) + ping = now - int(round(ctx.message.created_at.timestamp() * 1000)) + embed = discord.Embed(description = 'Pinging...') + message = await ctx.send(embed = embed) + ping2 = int(round(time.time() * 1000)) - now + await message.edit(embed = discord.Embed(color = randint(0, 0xFFFFFF), description = f':hourglass: {round(self.client.latency * 1000)}ms\n\n:stopwatch: {ping2}ms\n\n:heartbeat: {ping}ms')) + await ctx.message.add_reaction(emoji = '✅') + + @commands.command(name='avatar') + async def _avatar(self, ctx, *, user = '0'): + """Affiche ton avatar ou celui que tu mentionnes.\n ➡ Syntaxe: .avatar [user]""" + if user == '0': + user = ctx.author + else: + user = self.client.get_user(int(user[2:-1].replace("!",""))) + await ctx.message.add_reaction(emoji = '✅') + embed = discord.Embed(description = f"[lien vers la photo de profil]({user.avatar_url}) de {user.mention}", color = randint(0, 0xFFFFFF)) + embed.set_author(name = f"Photo de profil de {user.name}") + embed.set_image(url = user.avatar_url) + await ctx.send(embed = embed) + + @commands.command(name='calc') + async def _calc(self, ctx, *, msg): + """Calculatrice.\n ➡ Syntaxe: .calc ⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + equation = msg.replace('^', '**').replace('x', '*').replace('×', '*').replace('÷', '/').replace('≥', '>=').replace('≤', '<=') + try: + try: + if '=' in equation: + if '<' in equation: + left = eval(equation.split("<=")[0]) + right = eval(equation.split("<=")[1]) + answer = str(left <= right) + elif '>' in equation: + left = eval(equation.split(">=")[0]) + right = eval(equation.split(">=")[1]) + answer = str(left >= right) + else: + left = eval(equation.split("=")[0]) + right = eval(equation.split("=")[1]) + answer = str(left == right) + else: + answer = str(eval(equation)) + except ZeroDivisionError: + return await ctx.send("Tu ne peux pas divisé par 0.") + except TypeError: + return await ctx.send("Requête de calcul invalide.") + if '.' in answer: + aftercomma = answer.split(".")[1] + if len(aftercomma) > 2: + answer = str(round(float(answer),2)) + equation = f"'{equation}' arrondi à 2" + equation = equation.replace('*', '×').replace('/', '÷').replace('>=', '≥').replace('<=', '≤') + embed = discord.Embed(color = randint(0, 0xFFFFFF), title = 'Calculatrice') + embed.set_footer(text = ctx.author) + + embed.add_field(name = 'Calcul :', value = equation, inline = False) + embed.add_field(name = 'Réponse :', value = answer.replace('False', 'Faux').replace('True', 'Vrai'), inline = False) + await ctx.message.add_reaction(emoji = '✅') + await ctx.send(content = None, embed = embed) + @_calc.error + async def _calc_error(self, ctx, error): + await ctx.send("Tu n'as pas spécifié de calcul.") + + @commands.command(name='syntax') + async def _syntax(self, ctx): + """Informations pour bien éditer son texte.⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + syntaxe = "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("```Js\n") + syntaxe += discord.utils.escape_markdown("//code en js (possible de remplacer 'js' par d'autres languages . adaptez le !)\n") + syntaxe += discord.utils.escape_markdown('console.log("hi");\n') + syntaxe += discord.utils.escape_markdown("```\n") + syntaxe += "```Js\n" + syntaxe += "//code en js (possible de remplacer 'js' par d'autres languages . adaptez le !)\n" + syntaxe += 'console.log("hi");\n' + syntaxe += "```\n" + syntaxe += "Si ton code est trop long, mets le sur \n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("`code sur une seule ligne`\n") + syntaxe += "`code sur une seule ligne`\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("*texte en italique*\n") + syntaxe += "*texte en italique*\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("**text en gras**\n") + syntaxe += "**text en gras**\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("<>\n") + syntaxe += "Un lien entre crochet, ça empêche Discord de rajouté son intégration automatique (mais le lien fonctionnera toujours).\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("__texte souligné__\n") + syntaxe += "__texte souligné__\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("~~texte barré~~\n") + syntaxe += "~~texte barré~~\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("~~__***text en italique-gras-souligné-barré***__~~\n") + syntaxe += "~~__***text en italique-gras-souligné-barré***__~~\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("\:joy: <- l'emoji ne va pas fonctionné grâce au \ \n") + syntaxe += "\:joy: <- l'emoji ne va pas fonctionné grâce au \ \n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown("> cette ligne est cité\npas celle là\n") + syntaxe += "> cette ligne est cité\npas celle là\n" + syntaxe += "-----------------------------------------------------\n" + syntaxe += discord.utils.escape_markdown(">>> cette ligne est cité\ncelle là aussi (et elles le seront toutes!)\n") + syntaxe += ">>> cette ligne est cité\ncelle là aussi (et elles le seront toutes!)\n" + await ctx.message.add_reaction(emoji = '✅') + await ctx.send(syntaxe) + + @commands.command(name='memo', aliases = ['note']) + async def _memo(self, ctx, *, text): + """T'envoie un petit memo par message privé.\n ➡ Syntaxe: .memo/note ⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + if len(text) <= 5: + await ctx.message.add_reaction(emoji = '❌') + return await ctx.send("Ta note doit au moins faire 5 caractères.") + elif len(text) >= 2048: + await ctx.message.add_reaction(emoji = '❌') + return await ctx.send("Ta note doit faire moins de 2048 caractères.") + else: + await ctx.message.delete() + embed = discord.Embed(description = text, color = randint(0, 0xFFFFFF)) + embed.set_author(name = "Mémo", icon_url = ctx.author.avatar_url) + embed.set_footer(text = f'📝 le {datetime.now(pytz.timezone("Europe/Paris")).strftime("%d/%m/%Y à %H:%M:%S")}') + await ctx.author.send(embed = embed) + return await ctx.send("Tu viens de recevoir ton mémo !", delete_after = 5) + @_memo.error + async def _note_error(self, ctx, error): + if str(error) == "text is a required argument that is missing.": + await ctx.send("Vous devez renseigner un message : `.note/memo ⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢`.") + + @commands.command(name='infos', aliases = ['info']) + async def _infos(self, ctx): + """Donne des infos sur le bot.\n ➡ Syntaxe: .infos/info⁢""" + appinfo = await self.client.application_info() + + embed = discord.Embed(color = randint(0, 0xFFFFFF)) + + embed.set_author(name = appinfo.name, icon_url = self.client.user.avatar_url) + + total_online = len({m.id for m in self.client.get_all_members() if m.status is discord.Status.online}) + total_unique = len(self.client.users) + + voice_channels = [] + text_channels = [] + for guild in self.client.guilds: + voice_channels.extend(guild.voice_channels) + text_channels.extend(guild.text_channels) + + text = len(text_channels) + voice = len(voice_channels) + + embed.add_field(name = "Dev", value = f"[{appinfo.owner}](https://github.com/Mylloon)") + embed.add_field(name = "Serveurs", value = len(self.client.guilds)) + embed.add_field(name = "Membres", value = f"{total_unique} au total\n{total_online} en ligne") + embed.add_field(name = "Channels", value = f"{text} textuelles\n{voice} vocales") + embed.set_footer(text = f"Basé sur discord.py {discord.__version__}") + await ctx.message.add_reaction(emoji = '✅') + await ctx.send(embed = embed) + + def _map_list_among_us(self, map): + maps = {} + maps["skeld"] = ["skeld", "the skeld", "theskeld"] + maps["mira"] = ["mira", "mira hq", "mirahq"] + maps["polus"] = ["polus"] + if map == "all": + return maps["skeld"] + maps["mira"] + maps["polus"] + return maps[map] + + @commands.command(name='among', hidden = True) + async def _among(self, ctx, *, args = ""): + if not args == "": + args = args.split() + del args[0] + args = " ".join(args) + if args.lower() in self._map_list_among_us("all"): + await ctx.invoke(self.client.get_command("amongus"), map=args) + else: + await ctx.invoke(self.client.get_command("amongus")) + else: + await ctx.message.add_reaction(emoji = '❓') + + @commands.command(name='amongus') + async def _amongus(self, ctx, *, map = "0"): + """Affiche la carte voulue d'Among Us.⁢⁢⁢⁢⁢\n ➡ Syntaxe: .amongus ⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + if map.lower() in self._map_list_among_us("mira"): + image = "https://i.imgur.com/6ijrH1h.jpg" + embed = discord.Embed(title = f"Map Mira HQ d'Among Us", color = randint(0, 0xFFFFFF), description = f"[lien de l'image]({image})") + embed.set_image(url = image) + await ctx.send(embed = embed) + await ctx.message.add_reaction(emoji = '✅') + elif map.lower() in self._map_list_among_us("polus"): + image = "https://i.imgur.com/mhFmcw3.jpg" + embed = discord.Embed(title = f"Map Polus d'Among Us", color = randint(0, 0xFFFFFF), description = f"[lien de l'image]({image})") + embed.set_image(url = image) + await ctx.send(embed = embed) + await ctx.message.add_reaction(emoji = '✅') + elif map.lower() in self._map_list_among_us("skeld"): + image = "https://i.imgur.com/OSXI4Zv.jpg" + embed = discord.Embed(title = f"Map The Skeld d'Among Us", color = randint(0, 0xFFFFFF), description = f"[lien de l'image]({image})") + embed.set_image(url = image) + await ctx.send(embed = embed) + await ctx.message.add_reaction(emoji = '✅') + else: + await ctx.send("`.amongus `") + + @commands.command(name='whois') + async def _whois(self, ctx, *user: discord.Member): + """Affiche les infos sur l'utilisateur.⁢⁢⁢⁢⁢\n ➡ Syntaxe: .whois [user]⁢⁢⁢⁢⁢⁢⁢⁢⁢⁢""" + if len(user) <= 1: + if user == (): + user = [ctx.author] + nom = f"{user[0].name}#{user[0].discriminator}" + if user[0].nick: + nom = f"{user[0].nick} ({user[0].name}#{user[0].discriminator})" + embed = discord.Embed(color = randint(0, 0xFFFFFF)).set_author(name = nom, icon_url = user[0].avatar_url) + + embed.add_field(name = "ID", value = user[0].id) + + value = str(user[0].created_at.astimezone(timezone('Europe/Paris')))[:-13].replace('-', '/').split() + embed.add_field(name = "Compte créé le", value = f"{value[0][8:]}/{value[0][5:-3]}/{value[0][:4]} à {value[1]}") + + embed.add_field(name = "Âge du compte", value = self._age_layout(self._get_age(user[0].created_at))) + + embed.add_field(name = "Mention", value = user[0].mention) + + value = str(user[0].joined_at.astimezone(timezone('Europe/Paris')))[:-13].replace('-', '/').split() + embed.add_field(name = "Serveur rejoint le", value = f"{value[0][8:]}/{value[0][5:-3]}/{value[0][:4]} à {value[1]}") + + embed.add_field(name = "Est sur le serveur depuis", value = self._age_layout(self._get_age(user[0].joined_at))) + await ctx.message.add_reaction(emoji = '✅') + return await ctx.send(embed = embed) + await ctx.send("Tu mentionnes trop d'utilisateurs : `.whois [@Membre]`") + def _get_age(self, date): + joursRestants = datetime.now() - date + years = joursRestants.total_seconds() / (365.242 * 24 * 3600) + months = (years - int(years)) * 12 + days = (months - int(months)) * (365.242 / 12) + hours = (days - int(days)) * 24 + minutes = (hours - int(hours)) * 60 + seconds = (minutes - int(minutes)) * 60 + return (int(years), int(months), int(days), int(hours), int(minutes), int(seconds)) + def _age_layout(self, tuple): + time = {} + time[0], time[1], time[2], time[3], time[4], time[5] = "an", "mois", "jour", "heure", "minute", "seconde" + for i in range(len(tuple)): + if tuple[i] > 1 and i != 1: + time[i] = time[i] + "s" + message = "" + if tuple[5] > 0: # pour les secondes + affichage = [5] # on affiche que : seconde + if tuple[4] > 0: + affichage = [4, 5] # on affiche : minute + seconde + if tuple[3] > 0: + affichage = [3, 4, 5] # on affiche : heure + minute + seconde + if tuple[2] > 0: + affichage = [2, 3, 4] # on affiche : jour + heure + minute + if tuple[1] > 0: + affichage = [1, 2, 3] # on affiche : mois + jour + heure + if tuple[0] > 0: + affichage = [0, 1, 3] # on affiche : an + mois + heure + for i in affichage: + message = message + f", {tuple[i]} {time[i]}" + return message[2:] + + @commands.command(name='sondage') + async def _sondage(self, ctx, *args): + """Fais un sondage.⁢⁢⁢⁢⁢\n ➡ Syntaxe: .sondage "" "" "" "" """ + args = list(args) + if len(args) > 2: + question = args[0].replace("<@!", "").replace(">", "").replace("<@", "") + for i in re.findall(r'\d+', question): + ii = self.user_or_nick(ctx.author.guild.get_member(int(i))) + try: + question = question.replace(i, ii) + except: + pass + propositions = args[1:] + if len(propositions) <= 20: + message = "" + emojis = {} + emojis[0] = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'] + emojis[1] = [ + '🟤', '🔴', '🟠', '🟡', '🟢', '🔵', '🟣', '🔘', '❤', '💜', + '🟫', '🟥', '🟧', '🟨', '🟩', '🟦', '🟪', '🔳', '🧡', '💙' + ] + mixable = True + if len(propositions) <= 10: + emojis_chosen = emojis[randint(0, len(emojis) - 1)] + emojis_chosen = emojis_chosen[:10] + if len(propositions) <= 8: + emojis_chosen = emojis_chosen[:8] + else: + emojis_chosen = emojis[randint(1, len(emojis) - 1)] + if emojis[0][0] in emojis_chosen: # rajouter ici les listes qui ne doivent pas être mélanger + mixable = False + if mixable: + shuffle(emojis_chosen) + for i in range(len(args[1:])): + message += f"{emojis_chosen[i]} -> {propositions[i]}\n" + embed = discord.Embed(title = question, description = message,color = randint(0, 0xFFFFFF)).set_footer(text = self.user_or_nick(ctx.author), icon_url = ctx.author.avatar_url) + sondage = await ctx.send(embed = embed) + for i in range(len(args[1:])): + await sondage.add_reaction(emoji = emojis_chosen[i]) + return await ctx.message.add_reaction(emoji = '✅') + else: + return await ctx.send(f"Désolé, mais tu as mis trop de possibilités (maximum : 20)") + else: + return await ctx.send(f'Désolé, mais il manque des arguments : `.sondage "" "" "" ""`') + def user_or_nick(self, user): + if user.nick: + return f"{user.nick} ({user.name}#{user.discriminator})" + else: + return f"{user.name}#{user.discriminator}" \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..2c67a65 --- /dev/null +++ b/main.py @@ -0,0 +1,180 @@ +print("Connexion à Discord...") + +import discord, re, pytz +from discord.ext import commands +from random import randint, choice +from datetime import datetime, timedelta +from pytz import timezone +from setup import token + +client = commands.Bot(command_prefix = ".", case_insensitive = True, intents = discord.Intents.all()) + +@client.event +async def on_connect(): + print(f"Connecté avec le token : {token}.") + print("Chargement des extensions & librairie...") + client.load_extension("cogs.help") + client.load_extension("cogs.utils") + client.load_extension("cogs.internet") + client.load_extension("cogs.music") + client.load_extension("cogs.games") + client.load_extension("cogs.fun") + +@client.event +async def on_ready(): + await client.change_presence(status = discord.Status.online, activity = discord.Activity(name = ".help", type = discord.ActivityType.playing)) + print("Bot prêt.") + channel = client.get_channel(742564480050790512) + await channel.send("Le bot a bien démarré.") + +@client.event +async def on_member_join(member): + if member.guild.id == 441208120644075520: # Confrérie du Kassoulait + if member.bot == True: + role = discord.utils.get(member.guild.roles, name = "Bot") + else: + role = discord.utils.get(member.guild.roles, name = "Copain") + await member.add_roles(role) + try: + await member.send(f"Coucou **{member.name}** sur {member.guild.name} ! 🥰\n\nTu as le rôle **{role}** 💖!") + except: + pass + channel = client.get_channel(741639570172674120) # salons des arrivées + switch = { + 0: f"Bienvenue, {member.mention}. On espère que tu as apporté de la pizza.", + 1: f"C'est un plaisir de te voir, {member.mention}.", + 2: f"{member.mention} vient juste d'arriver !", + 3: f"{member.mention} vient juste d'atterrir.", + 4: f"{member.mention} vient de se glisser dans le serveur.", + 5: f"{member.mention} a bondi dans le serveur.", + 6: f"Contents de te voir, {member.mention}.", + 7: f"{member.mention} est arrivé(e).", + 8: f"Tout le monde, accueillez comme il se doit {member.mention} !", + 9: f"Youhou, tu as réussi, {member.mention} !", + 10: f"{member.mention} a rejoint le groupe.", + } + message = await channel.send("...") # évite d'envoyer une notification + await message.edit(content = choice(switch)) + +@client.event +async def on_member_remove(member): + if member.guild.id == 441208120644075520: # Confrérie du Kassoulait + channel = client.get_channel(741639570172674120) # salons des arrivées + await channel.send(f"{member.mention} vient de quitter le serveur.") + +@client.event +async def on_raw_reaction_add(payload): + if payload.message_id == 644922358745792512: # Règles de la Confrérie du Kassoulait + if payload.emoji.name == '✅': + role = discord.utils.get(payload.member.guild.roles, name="règles-acceptés") + await payload.member.add_roles(role) + +@client.event +async def on_raw_reaction_remove(payload): + if payload.message_id == 644922358745792512: # Règles de la Confrérie du Kassoulait + if payload.emoji.name == '✅': + guild = discord.utils.find(lambda g : g.id == payload.guild_id, client.guilds) + member = discord.utils.find(lambda m: m.id == payload.user_id, guild.members) + role = discord.utils.get(guild.roles, name="règles-acceptés") + await member.remove_roles(role) + +@client.event +async def on_command_error(ctx, error): + if not ctx.invoked_with.startswith('.'): + await ctx.message.add_reaction(emoji = '❓') + print(error) + +@client.event +async def on_message(message): + await client.process_commands(message) + + + if message.author == client.user: + return + + if client.user.mention == message.content.replace("!",""): + ctx = await client.get_context(message) + prefix = await client.get_prefix(message) + await ctx.send(f">>> Coucou !\nMon préfix est `{prefix}` et ma commande d'aide est `{prefix}help`") + + urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', message.content) + for i in range(len(urls)): + if urls[i].startswith("https://discordapp.com/channels/") or urls[i].startswith("https://discord.com/channels/") or urls[i].startswith("https://ptb.discordapp.com/"): + link = urls[i] + linkURL = link + if link.startswith("https://discord.com/channels/"): + link = f'000{link}' + if link.startswith("https://ptb.discordapp.com/"): + link = link[4:] + if "@me" in urls[i]: + return await message.channel.send("Je ne cite pas les messages privés.", delete_after = 5) + try: + if int(link[32:-38]) == message.guild.id: + msgID = await client.get_channel(int(link[51:-19])).fetch_message(int(link[70:])) + if len(msgID.content) > 0: + embed = discord.Embed(description = msgID.content) + else: + return # si le message ne contient pas de mots (image? vidéo? intégration?) + embed.add_field(name = "Auteur", value = msgID.author.mention, inline=True) + embed.add_field(name = "Channel", value = msgID.channel.mention, inline=True) + embed.add_field(name = "Message", value = f"[Aller au message]({linkURL})", inline=True) + embed.set_author(name = "Citation", icon_url = msgID.author.avatar_url) + + icon_url = message.author.avatar_url + + date_1 = str(msgID.created_at.astimezone(timezone('Europe/Paris')))[:-13].replace('-', '/').split() + edit = "" + if msgID.edited_at: + date_edit = str(msgID.edited_at.astimezone(timezone('Europe/Paris')))[:-13].replace('-', '/').split() + edit = f"(Dernier edit : {date_edit[0][8:]}/{date_edit[0][5:-3]}/{date_edit[0][:4]} à {date_edit[1]})" + message_1 = f"Date : {date_1[0][8:]}/{date_1[0][5:-3]}/{date_1[0][:4]} à {date_1[1]} {edit}" + + date_2 = str(message.created_at.astimezone(timezone('Europe/Paris')))[:-13].replace('-', '/').split() + date_2 = f"{date_2[0][8:]}/{date_2[0][5:-3]}/{date_2[0][:4]} à {date_2[1]}" + + embed.set_footer(icon_url = icon_url, text = f"{message_1}\nCité par {user_or_nick(message.author)} le {date_2}") + await message.channel.send(embed = embed) + if message.content == linkURL.replace(' ',''): + await message.delete() + except Exception as e: + e = str(e) + if not "invalid literal for int() with base 10:" in e or not "404 Not Found (error code: 10008)" in e: # faute de frappe / message supprimé + print(e) + +@client.event +async def on_message_delete(message): + if message.author.guild.id == 441208120644075520: # Confrérie du Kassoulait + prefix = await client.get_prefix(message) + if not ( + message.content.startswith(f"{prefix}note") or + message.content.startswith(f"{prefix}memo") or + len(re.findall(".com/channels/", message.content)) != 0 or + client.user.id is message.author.id + ): + user_suppressed = None + + async for entry in message.guild.audit_logs(limit=1): + if (datetime.now() - entry.created_at).seconds < 5 and str(entry.action) == 'AuditLogAction.message_delete': + user_suppressed = entry.user + + channel = client.get_channel(742588187456831659) + embed = discord.Embed(description = f"{message.content}") + + embed.set_author(name = user_or_nick(message.author), icon_url = message.author.avatar_url) + + if not user_suppressed: + embed.set_footer(text = f"Channel: #{message.channel.name} | Date : {str(message.created_at.astimezone(timezone('Europe/Paris')))[:-13].replace('-', '/').replace(' ', ' à ')}\nSupprimé le {datetime.now(pytz.timezone('Europe/Paris')).strftime('%d/%m/%Y à %H:%M:%S')}") + else: + embed.set_footer(icon_url = user_suppressed.avatar_url, text = f"Channel: #{message.channel.name} | Date : {str(message.created_at.astimezone(timezone('Europe/Paris')))[:-13].replace('-', '/').replace(' ', ' à ')}\nSupprimé par {user_or_nick(user_suppressed)} le {datetime.now(pytz.timezone('Europe/Paris')).strftime('%d/%m/%Y à %H:%M:%S')}") + + await channel.send(embed = embed) + # ne fonctionne pas quand un message a été supprimé avant que le bot ai démarré + # info sur la personne qui a supprimé ne fonctionne pas si il a supprimé un message auparavant (les logs se rajoute a un log deja existant) + +def user_or_nick(user): + if user.nick: + return f"{user.nick} ({user.name}#{user.discriminator})" + else: + return f"{user.name}#{user.discriminator}" + +client.run(token) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1f7e665 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +discord.py[voice]==1.5.1 # discord +async-timeout==3.0.1 # discord +pytz==2020.4 # timezone +praw==7.1.0 # reddit +youtube-dl==2020.11.17 # music +git+https://github.com/johnwmillr/LyricsGenius.git # lyrics \ No newline at end of file