Ejemplo n.º 1
0
    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()
Ejemplo n.º 2
0
    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