import openai # from openai import api_key import discord from discord import Intents from discord.commands import slash_command, option from discord.ext import commands import re import os import asyncio import logging import datetime import base64 import requests import zipfile from zipfile import ZipFile from dotenv import load_dotenv load_dotenv() use_images = os.getenv("USE_IMAGES") cooldown = os.getenv("COOLDOWN") if use_images != "No": import imagesGeneration logging.basicConfig(level=logging.INFO) imageint = "" if use_images != "No": imageint = "To add an image illustration , use ![bg left:50% 70%](a-long-detailed-description-of-the-image.png) at the beginning of the slide, just after \"---\". Use only .png. It's not possible to add technical images but only illustrations. The images are generated by an ai, the name of the file should be a detailed quite long description of the image wanted. For example \" ![bg left:50% 100%](a-man-wearing-a hat-ryding-a-bicicle.png)\" but don't need to show a person necessairly." intstructions = f'''Here is a presentation with marp. It's not possible to make slides longer than 200 characters. to separate slides, " --- " then go at the line. The presentatio should be for everybody, all technical words and concepts, explained. {imageint} The presentation is minimum 20 slides long. You can use bulletpoints. Use markdown formatting (titles, etc...). The presentation has also a conclusion.''' bot = discord.Bot() styles = ["default", "gaia", "uncover", "default-dark", "gaia-dark", "uncover-dark", "olive"] languages = ["english", "french", "spanish", "german", "italian", "portuguese", "russian", "chinese", "japanese", "korean", "arabic"] darkstyles = ["default-dark", "gaia-dark", "uncover-dark"] customstyles = ["olive"] async def get_style(ctx: discord.AutocompleteContext): """Returns a list of colors that begin with the characters entered so far.""" return [style for style in styles if style.startswith(ctx.value.lower())] async def get_ln(ctx: discord.AutocompleteContext): return [language for language in languages if language.startswith(ctx.value.lower())] @bot.slash_command(name="private_present", description="Generate a presentation with marp, private command for user 707196665668436019") @option(name="subject", description="The subject of the presentation", required=True) @option(name="style", description="The style of the presentation", required=False, autocomplete=get_style) @option(name="center", description="Center the text", required=False) @option(name="language", description="The language of the presentation", required=False, autocomplete=get_ln) @option(name="indications", description="The indications for the presentation", required=False) #command wprks only in dm and only for user 707196665668436019 @commands.is_owner() async def private_present(ctx: discord.ApplicationContext, subject: str, style: str = "default", center: bool = True, language: str = "english", indications: str = ""): await present(ctx, subject, style, language, indications) @bot.slash_command(name="present", description="Generate a presentation with marp") #we create a function that takes the subject of the presentation and the style of the presentation as arguments, and that @option(name="subject", description="The subject of the presentation", required=True) @option(name="style", description="The style of the presentation", required=False, autocomplete=get_style) @option(name="center", description="Center the text", required=False) @option(name="language", description="The language of the presentation", required=False, autocomplete=get_ln) @option(name="indications", description="The indications for the presentation", required=False) # a cooldown of duration cooldown seconds, except if the user is 707196665668436019 #@commands.cooldown(1, int(cooldown), commands.BucketType.user) @commands.cooldown(1, int(cooldown), commands.BucketType.guild) async def normal_present(ctx: discord.ApplicationContext, subject: str, style: str = "default", center: bool = True, language: str = "english", indications: str = ""): await present(ctx, subject, style, language, indications) async def present(ctx: discord.ApplicationContext, subject: str, style: str = "default", center: bool = True, language: str = "english", indications: str = ""): await ctx.defer() date = datetime.datetime.now() date = date.strftime("%Y-%m-%d-%H-%M-%S") #if the style is dark dark = False if style in darkstyles: style = style.replace("-dark", "") dark = True marp = f'''--- marp: true theme: {styles[styles.index(style)]} class: ''' if dark: marp = marp + f" - invert\n---" if center: marp = marp + " - lead\n---" else: marp = marp + "\n---" # if style in customstyles: # marp = f"/* @theme {style} */\n" + marp # print(marp) prompt = f"{intstructions} {indications} The subject of the presentation is: {subject} The Language is: {language} <|endofprompt|> \n {marp}" subject2 = subject forbidden = ["\\", "/", "?", "!", ":", ";", "(", ")", "[", "]", "{", "}", "'", '"', "=", "+", "*", "&", "^", "%", "$", "#", "@", "`", "~", "|", "<", ">", ",", ".", "?", " "] for i in forbidden: if i in subject: subject = subject.replace(i, "-") #we save teh subject in base64 in a variable #if dosen't exist, create a directory called "userid" where the userid is the id of the user who called the command uid = str(ctx.author.id) if not os.path.exists("./data/"+uid): os.mkdir("./data/"+uid) datenow = datetime.datetime.now() datenow = datenow.strftime("%Y-%m-%d-%H-%M-%S") response = await openai.Completion.acreate( engine="text-davinci-003", prompt=prompt, temperature=0.6, max_tokens=1024, top_p=1, frequency_penalty=0, presence_penalty=0, stop=["<|endofprompt|>"] ) #we save the output in a variable output = response["choices"][0]["text"] present = marp + output ##we save the output in a file called "subject.md" matches = re.finditer(r'!\[.*?\]\((.*?)\)', present) image_filenames = [] for match in matches: image_filenames.append(match.group(1)) #we create a text file with the image names and a md file for the presentation with utf8 encoding if len(subject) > 15: subject = subject[:15] b64 = base64.urlsafe_b64encode(subject.encode("utf-8")) os.mkdir(f"./data/{uid}/{b64}{datenow}") path = f"./data/{uid}/{b64}{datenow}" with open(f"{path}/{subject}-images.txt", "w", encoding="utf8") as f: for image in image_filenames: f.write(image + "\n") with open(f"{path}/{subject}.md", "w", encoding="utf8") as f: f.write(present) if len(image_filenames) > 0 and use_images!="no": #now we first remove the extension from the image filenames by removing the last 4 characters image_filenames = [image[:-4] for image in image_filenames] print(image_filenames) for images in image_filenames: print ("generating image " + images + "with " + str(use_images)) r = await imagesGeneration.generate(images, f"{os.getcwd()}\\data\\{uid}\\{b64}{datenow}\\", str(use_images), apikey) if str(use_images) == "sd": os.rename(f"{os.getcwd()}\\.\\data\\{uid}\\{b64}{datenow}\\{images}_0.png", f"{os.getcwd()}\\data\\{uid}\\{b64}{datenow}\\{images}.png") if str(use_images) == "dalle": image_url = r['data'][0]['url'] img_data = requests.get(image_url).content with open(f'{path}/{images}.png', 'wb') as handler: handler.write(img_data) await asyncio.sleep(15) #wait 15 seconds to avoid rate limiting cmd = f"--pdf --allow-local-files {path}/{subject}.md" if style in customstyles: cmd = cmd + f" --theme ./themes/{style}.css" if os.path.exists("./marp.exe"): os.system(f"marp.exe {cmd}") else: cmd = cmd.replace("'", "\\'") os.system(f"./marp {cmd}") cmd = f" --image png -o {path}/{subject}.png --allow-local-files {path}/{subject}.md" if style in customstyles: cmd = cmd + f" --theme ./themes/{style}.css" if os.path.exists("./marp.exe"): os.system(f"marp.exe {cmd}") else: cmd = cmd.replace("'", "\\'") os.system(f"./marp {cmd}") cmd = f" --html --allow-local-files {path}/{subject}.md" if style in customstyles: cmd = cmd + f" --theme ./themes/{style}.css" if os.path.exists("./marp.exe"): os.system(f"marp.exe {cmd}") else: cmd = cmd.replace("'", "\\'") os.system(f"./marp {cmd}") cmd = f" --pptx --allow-local-files {path}/{subject}.md" if style in customstyles: cmd = cmd + f" --theme ./themes/{style}.css" if os.path.exists("./marp.exe"): os.system(f"marp.exe {cmd}") else: cmd = cmd.replace("'", "\\'") os.system(f"./marp {cmd}") #now, we create a zip file with all the files zipObj = ZipFile(f"{path}/{subject}.zip", 'w') zipObj.write(f"{path}/{subject}.md", f"{subject}.md") zipObj.write(f"{path}/{subject}.html", f"{subject}.html") zipObj.write(f"{path}/{subject}.pdf", f"{subject}.pdf") zipObj.write(f"{path}/{subject}.png", f"{subject}.png") zipObj.write(f"{path}/{subject}.pptx", f"{subject}.pptx") with open(f"{path}/{subject}-images.txt", "r", encoding="utf8") as f: for image in f.readlines(): zipObj.write(f"{path}/{image.strip()}", f"{image.strip()}") zipObj.close() embed = discord.Embed(title=subject2, description="Thanks for using presentator bot. You will find your presentation in the attached zip file in the following formats: markdown, html, pdf, pptx, and the presentation' images. If you want to modify your presentation you can use the markdown file. More information about how to modify the file [HERE](https://marp.app).", color=discord.Color.brand_red()) files = [discord.File(f"{path}/{subject}.zip"), discord.File(f"{path}/{subject}.png")] embed.set_image(url=f"attachment://{subject}.png") await ctx.respond(embed=embed, files=files) @bot.slash_command(name="list", description="List all the presentations you have created") async def list(ctx: discord.ApplicationContext): embed = discord.Embed(title="Presentations", description="Here is the list of all the presentations you have created. You can download the presentation in different formats (pdf, markdown, html) by doing `/get` \"*presentation id*\". The images are generated by an ai. If you want to modify your presentation you can use the markdown file. More information about how to modify the file [HERE](https://marp.app).", color=0x00ff00) liste = await get_presentations(str(ctx.author.id)) for key in liste: embed.add_field(name=f"{liste[key]}", value=f" `{key}`", inline=False) await ctx.respond(embed=embed, ephemeral=True) async def get_presentations(uid): folders = os.listdir(f"./data/{uid}") names = {} for folder in folders: name = base64.urlsafe_b64decode(folder[2:-20]).decode("utf-8") names[folder] = name return names @bot.slash_command(name="get", description="Get a presentation") @option(name="pid", description="The id of the presentation", required=True) async def get(ctx: discord.ApplicationContext, pid: str): uid = str(ctx.author.id) liste = await get_presentations(uid) if pid in liste: files = [discord.File(f"./data/{uid}/{pid}/{liste[pid]}.pdf"), discord.File(f"./data/{uid}/{pid}/{liste[pid]}.md"), discord.File(f"./data/{uid}/{pid}/{liste[pid]}.html")] await ctx.respond(files=files, ephemeral=True) #when the bot is ready we print a message @bot.event async def on_ready(): print("Bot is ready") if not os.path.exists("data"): os.mkdir("data") @bot.event async def on_application_command_error(ctx, error): #if there is an error we send a message to the user await ctx.respond(f"An error occured: {error}", ephemeral=True) #get the openai key drom he key.env file token = os.getenv("TOKEN") apikey = os.getenv("OPENAI") openai.api_key = apikey bot.run(token)