async def run(self, ctx, language, *, code=''): """Execute code in a given programming language""" # Powered by tio.run options = {'stats': False, 'wrapped': False} lang = language.strip('`').lower() code = code.split(' ') for i, option in enumerate(options): if f'--{option}' in code[:len(options) - i]: options[option] = True code.remove(f'--{option}') code = ' '.join(code) compilerFlags = [] commandLineOptions = [] args = [] inputs = [] lines = code.split('\n') code = [] for line in lines: if line.startswith('input '): inputs.append(' '.join(line.split(' ')[1:]).strip('`')) elif line.startswith('compiler-flags '): compilerFlags.extend(line[15:].strip('`').split(' ')) elif line.startswith('command-line-options '): commandLineOptions.extend(line[21:].strip('`').split(' ')) elif line.startswith('arguments '): args.extend(line[10:].strip('`').split(' ')) else: code.append(line) inputs = '\n'.join(inputs) code = '\n'.join(code) text = None async with ctx.typing(): if ctx.message.attachments: # Code in file file = ctx.message.attachments[0] if file.size > 20000: return await ctx.send("File must be smaller than 20 kio.") buffer = BytesIO() await ctx.message.attachments[0].save(buffer) text = buffer.read().decode('utf-8') elif code.split(' ')[-1].startswith('link='): # Code in a webpage base_url = urllib.parse.quote_plus( code.split(' ')[-1][5:].strip('/'), safe=';/?:@&=$,><-[]') url = get_raw(base_url) async with aiohttp.ClientSession() as client_session: async with client_session.get(url) as response: if response.status == 404: return await ctx.send( 'Nothing found. Check your link') elif response.status != 200: return await ctx.send( f'An error occurred (status code: {response.status}). Retry later.' ) text = await response.text() if len(text) > 20000: return await ctx.send( 'Code must be shorter than 20,000 characters.') elif code.strip('`'): # Code in message text = code.strip('`') firstLine = text.splitlines()[0] if re.fullmatch(r'( |[0-9A-z]*)\b', firstLine): text = text[len(firstLine) + 1:] if text is None: # Ensures code isn't empty after removing options raise commands.MissingRequiredArgument( ctx.command.clean_params['code']) # common identifiers, also used in highlight.js and thus discord codeblocks quickmap = { 'asm': 'assembly', 'c#': 'cs', 'c++': 'cpp', 'csharp': 'cs', 'f#': 'fs', 'fsharp': 'fs', 'js': 'javascript', 'nimrod': 'nim', 'py': 'python', 'q#': 'qs', 'rs': 'rust', 'sh': 'bash', } if lang in quickmap: lang = quickmap[lang] if lang in self.bot.default: lang = self.bot.default[lang] if not lang in self.bot.languages: matches = '\n'.join([ language for language in self.bot.languages if lang in language ][:10]) message = f"`{lang}` not available." if matches: message = message + f" Did you mean:\n{matches}" return await ctx.send(message) if options['wrapped']: if not (any( map(lambda x: lang.split('-')[0] == x, self.wrapping))) or lang in ('cs-mono-shell', 'cs-csi'): return await ctx.send(f'`{lang}` cannot be wrapped') for beginning in self.wrapping: if lang.split('-')[0] == beginning: text = self.wrapping[beginning].replace('code', text) break tio = Tio(lang, text, compilerFlags=compilerFlags, inputs=inputs, commandLineOptions=commandLineOptions, args=args) result = await tio.send() if not options['stats']: try: start = result.rindex("Real time: ") end = result.rindex("%\nExit code: ") result = result[:start] + result[end + 2:] except ValueError: # Too much output removes this markers pass if len(result) > 1991 or result.count('\n') > 40: # If it exceeds 2000 characters (Discord longest message), counting ` and ph\n characters # Or if it floods with more than 40 lines # Create a hastebin and send it back link = await paste(result) if link is None: return await ctx.send( "Your output was too long, but I couldn't make an online bin out of it" ) return await ctx.send( f'Output was too long (more than 2000 characters or 40 lines) so I put it here: {link}' ) zero = '\N{zero width space}' result = re.sub('```', f'{zero}`{zero}`{zero}`{zero}', result) # ph, as placeholder, prevents Discord from taking the first line # as a language identifier for markdown and remove it returned = await ctx.send(f'```ph\n{result}```') await returned.add_reaction('🗑') returnedID = returned.id def check(reaction, user): return user == ctx.author and str( reaction.emoji) == '🗑' and reaction.message.id == returnedID try: await self.bot.wait_for('reaction_add', timeout=60.0, check=check) except asyncio.TimeoutError: pass else: await returned.delete()
async def run(self, ctx, *, payload=''): """Execute code in a given programming language""" if not payload: emb = discord.Embed( title='SyntaxError', description= f"Command `run` missing a required argument: `language`", colour=0xff0000) return await ctx.send(embed=emb) no_rerun = True language = payload lang = None # to override in 2 first cases if ctx.message.attachments: # Code in file file = ctx.message.attachments[0] if file.size > 20000: return await ctx.send("File must be smaller than 20 kio.") buffer = BytesIO() await ctx.message.attachments[0].save(buffer) text = buffer.read().decode('utf-8') lang = re.split(r'\s+', payload, maxsplit=1)[0] elif payload.split(' ')[-1].startswith('link='): # Code in a webpage base_url = urllib.parse.quote_plus( payload.split(' ')[-1][5:].strip('/'), safe=';/?:@&=$,><-[]') url = get_raw(base_url) async with self.bot.session.get(url) as response: if response.status == 404: return await ctx.send('Nothing found. Check your link') elif response.status != 200: return await ctx.send( f'An error occurred (status code: {response.status}). Retry later.' ) text = await response.text() if len(text) > 20000: return await ctx.send( 'Code must be shorter than 20,000 characters.') lang = re.split(r'\s+', payload, maxsplit=1)[0] else: no_rerun = False language, text, errored = prepare_payload( payload ) # we call it text but it's an embed if it errored #JustDynamicTypingThings if errored: return await ctx.send(embed=text) async with ctx.typing(): if lang: language = lang output = await execute_run(self.bot, language, text) view = Refresh(self.bot, no_rerun) try: returned = await ctx.reply(output, view=view) buttons = True except discord.HTTPException: # message deleted returned = await ctx.send(output, view=view) buttons = False if buttons: await view.wait() try: await returned.edit(view=None) view.stop() except: # We deleted the message pass