Пример #1
0
    async def _quantize(self, ctx, args, canvas, palette):
        """Sends a message containing a quantised version of the image given.

        Arguments:
        ctx - A commands.Context object.
        args - A list of arguments from the user, all strings.
        canvas - The canvas to use, string.
        palette - The palette to quantise to, a list of rgb tuples.

        Returns:
        The discord.Message object returned when ctx.send() is called to send the quantised image.
        """
        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-f", "--faction", default=None, action=FactionAction)

        # Pre-Parsing
        if len(args) == 0:
            name = None
        elif args[0][0] != "-":
            name = args[0]
            args = args[1:]
        else:
            name = None

        try:
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        gid = ctx.guild.id if not args.faction else args.faction.id
        t = ctx.session.query(Template).filter_by(guild_id=gid, name=name).first()

        data = None
        if name:
            if t:
                if t.canvas == canvas:
                    raise IdempotentActionError
                data = await http.get_template(t.url, t.name)
            else:
                raise TemplateNotFoundError(ctx, gid, name)
        else:
            att = await verify_attachment(ctx)
            if att:
                data = io.BytesIO()
                await att.save(data)

        if data:
            template, bad_pixels = await self.bot.loop.run_in_executor(None, render.quantize, data, palette)

            with io.BytesIO() as bio:
                template.save(bio, format="PNG")
                bio.seek(0)
                f = discord.File(bio, "template.png")
                return await ctx.send(ctx.s("canvas.quantize").format(bad_pixels), file=f)
Пример #2
0
    async def gridify(self, ctx, *args):
        log.info(f"g!gridify run in {ctx.guild.name} with args: {args}")

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        parser.add_argument("-c",
                            "--color",
                            default=0x808080,
                            action=ColorAction)
        parser.add_argument("-z", "--zoom", type=int, default=1)

        # Pre-Parsing
        if len(args) == 0:
            name = None
            a = args
        elif args[0][0] != "-":
            name = args[0]
            a = args[1:]
        else:
            name = None
            a = args

        try:
            a = vars(parser.parse_args(a))
        except TypeError:
            return

        faction = a["faction"]
        color = a["color"]
        zoom = a["zoom"]

        gid = ctx.guild.id if not faction else faction.id
        t = sql.template_get_by_name(gid, name)

        if name:
            if t:
                log.info("(T:{} | GID:{})".format(t.name, t.gid))
                data = await http.get_template(t.url, t.name)
                max_zoom = int(math.sqrt(4000000 // (t.width * t.height)))
                zoom = max(1, min(zoom, max_zoom))
                template = await render.gridify(data, color, zoom)
            else:
                raise TemplateNotFoundError(gid, name)
        else:
            att = await verify_attachment(ctx)
            data = io.BytesIO()
            await att.save(data)
            max_zoom = int(math.sqrt(4000000 // (att.width * att.height)))
            zoom = max(1, min(zoom, max_zoom))
            template = await render.gridify(data, color, zoom)

        with io.BytesIO() as bio:
            template.save(bio, format="PNG")
            bio.seek(0)
            f = discord.File(bio, "gridded.png")
            await ctx.send(file=f)
Пример #3
0
def dither_argparse(ctx, args):
    parser = GlimmerArgumentParser(ctx)
    parser.add_argument(
        "-d",
        "--ditherType",
        choices=["b", "bayer", "y", "yliluoma", "fs", "floyd-steinberg"],
        default="bayer")
    parser.add_argument("-t",
                        "--threshold",
                        type=int,
                        choices=[2, 4, 8, 16, 32, 64, 128, 256, 512])
    parser.add_argument("-o", "--order", type=int, choices=[2, 4, 8, 16])
    try:
        a = parser.parse_args(args)
    except TypeError:
        raise discord.ext.commands.BadArgument

    def default(value, default):
        return value if value is not None else default

    names = {"b": "bayer", "y": "yliluoma", "fs": "floyd-steinberg"}
    default_thresholds = {
        "bayer": 256,
    }
    default_orders = {"bayer": 4, "yliluoma": 8, "floyd-steinberg": 2}

    dither_type = default(names.get(a.ditherType, None), a.ditherType)
    dither_type = dither_type if dither_type is not None else "bayer"  # Incase they select an invalid option for this
    threshold = default(a.threshold, default_thresholds.get(dither_type))
    order = order = default(a.order, default_orders.get(dither_type))

    return dither_type, threshold, order
Пример #4
0
async def _preview(ctx, args, fetch):
    """Sends a preview of the image provided.

    Arguments:
    ctx - A commands.Context object.
    args - A list of arguments from the user, all strings.
    fetch - The current state of all pixels that the template/specified area covers, PIL Image object.
    """
    async with ctx.typing():
        # Order Parsing
        try:
            x, y = args[0], args[1]
        except TypeError:
            await ctx.send("Error: no arguments were provided.")
            return

        if re.match("-\D+", x) != None:
            x, y = args[-2], args[-1]
            args = args[:-2]
        else:
            args = args[2:]

        # X and Y Cleanup
        try:
            #cleans up x and y by removing all spaces and chars that aren't 0-9 or the minus sign using regex. Then makes em ints
            x = int(re.sub('[^0-9-]', '', x))
            y = int(re.sub('[^0-9-]', '', y))
        except ValueError:
            await ctx.send(ctx.s("canvas.invalid_input"))
            return

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-z", "--zoom", type=int, default=1)
        try:
            a = vars(parser.parse_args(args))
        except TypeError:
            return

        zoom = a["zoom"]
        zoom = max(min(zoom, 16), -8)

        preview_img = await render.preview(x, y, zoom, fetch)

        with io.BytesIO() as bio:
            preview_img.save(bio, format="PNG")
            bio.seek(0)
            f = discord.File(bio, "preview.png")
            await ctx.send(file=f)
Пример #5
0
    async def template(self, ctx, *args):
        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-p", "--page", type=int, default=1)
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        try:
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        gid = ctx.guild.id
        if args.faction is not None:
            gid = args.faction.id

        templates = ctx.session.query(TemplateDb).filter_by(
            guild_id=gid).order_by(TemplateDb.name).all()
        if len(templates) < 1:
            raise NoTemplatesError()

        template_menu = menus.MenuPages(source=TemplateSource(templates),
                                        clear_reactions_after=True,
                                        timeout=300.0)
        template_menu.current_page = max(
            min(args.page - 1, template_menu.source.get_max_pages()), 0)
        try:
            await template_menu.start(ctx, wait=True)
            template_menu.source.embed.set_footer(text=ctx.s("bot.timeout"))
            await template_menu.message.edit(embed=template_menu.source.embed)
        except discord.NotFound:
            await ctx.send(ctx.s("bot.menu_deleted"))
Пример #6
0
    async def gridify(self, ctx, *args):
        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-f", "--faction", default=None, action=FactionAction)
        parser.add_argument("-c", "--color", default=0x808080, action=ColorAction)
        parser.add_argument("-z", "--zoom", type=int, default=1)

        # Pre-Parsing
        if len(args) == 0:
            name = None
            a = args
        elif args[0][0] != "-":
            name = args[0]
            a = args[1:]
        else:
            name = None
            a = args

        try:
            a = parser.parse_args(a)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {a}")

        gid = ctx.guild.id if not a.faction else a.faction.id
        t = ctx.session.query(Template).filter_by(guild_id=gid, name=name).first()

        if name:
            if t:
                max_zoom = int(math.sqrt(4000000 // (t.width * t.height)))
                data = await http.get_template(t.url, t.name)
                zoom = max(1, min(a.zoom, max_zoom))
                template = render.gridify(data, a.color, zoom)
            else:
                raise TemplateNotFoundError(ctx, gid, name)
        else:
            att = await verify_attachment(ctx)
            data = io.BytesIO()
            await att.save(data)
            max_zoom = int(math.sqrt(4000000 // (att.width * att.height)))
            zoom = max(1, min(a.zoom, max_zoom))
            template = render.gridify(data, a.color, zoom)

        with io.BytesIO() as bio:
            template.save(bio, format="PNG")
            bio.seek(0)
            f = discord.File(bio, "gridded.png")
            await ctx.send(file=f)
Пример #7
0
    async def recent(self, ctx, *args):
        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-p", "--page", type=int, default=1)
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        try:
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        gid = ctx.guild.id
        if args.faction is not None:
            gid = args.faction.id

        templates = ctx.session.query(TemplateDb).filter_by(guild_id=gid).all()
        checker_templates = [
            t for t in self.templates if t.id in [t_.id for t_ in templates]
        ]
        pixels = [
            p for t in checker_templates for p in t.current_pixels
            if p.fixed is False
        ]
        pixels.sort(key=lambda p: p.recieved, reverse=True)

        if not pixels:
            await ctx.send(ctx.s("alerts.no_recent_errors"))
            return

        checker_menu = menus.MenuPages(source=CheckerSource(
            pixels, checker_templates),
                                       clear_reactions_after=True,
                                       timeout=300.0)
        checker_menu.current_page = max(
            min(args.page - 1, checker_menu.source.get_max_pages()), 0)
        try:
            await checker_menu.start(ctx, wait=True)
            checker_menu.source.embed.set_footer(text=ctx.s("bot.timeout"))
            await checker_menu.message.edit(embed=checker_menu.source.embed)
        except discord.NotFound:
            await ctx.send(ctx.s("bot.menu_deleted"))
Пример #8
0
    async def template_update_pixelcanvas(self, ctx, *args):
        try:
            name = args[0]
        except TypeError:
            await ctx.send(ctx.s("error.missing_argument"))
            return

        skip = False
        for arg in args:
            if any(h == arg for h in ["--help", "-h"]):
                args = ["--help"]
                skip = True

        if not skip:
            if re.match(r"-\D+", name) is not None:
                name = args[-1]
                args = args[:-1]
            else:
                args = args[1:]

            orig_template = ctx.session.query(TemplateDb).filter_by(
                guild_id=ctx.guild.id, name=name).first()
            if not orig_template:
                raise TemplateNotFoundError(ctx, ctx.guild.id, name)

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-n", "--newName", nargs="?", default=None)
        parser.add_argument("-x", nargs="?", default=None)
        parser.add_argument("-y", nargs="?", default=None)
        # if -i not present, False
        # if no value after -i, True
        # if value after -i, capture
        parser.add_argument("-i",
                            "--image",
                            nargs="?",
                            const=True,
                            default=None)
        try:
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        out = []

        # Image is done first since I'm using the build_template method to update stuff,
        # and I don't want anything to have changed in orig_template before I use it
        if args.image:
            # Update image
            url = None
            if not isinstance(args.image, bool):
                url = args.image
            url = await select_url_update(ctx, url, out)
            if url is None:
                return  # Sending the end is handled in select_url_update if it fails

            try:
                t = await build_template(ctx, orig_template.name,
                                         orig_template.x, orig_template.y, url,
                                         "pixelcanvas")
            except TemplateHttpError:
                out.append(
                    ctx.s("template.url_access").format(
                        ctx.s("template.update_file")))
                return await send_end(ctx, out)
            except NoJpegsError:
                out.append(
                    "Updating file failed: Seriously? A JPEG? Gross! Please create a PNG template instead."
                )
                return await send_end(ctx, out)
            except NotPngError:
                out.append(
                    "Updating file failed: That command requires a PNG image.")
                return await send_end(ctx, out)
            except (PilImageError, UrlError):
                out.append("{0}.".format(ctx.s("template.err.update_file")))
                return await send_end(ctx, out)

            if t is None:
                out.append("{0}.".format(ctx.s("template.err.update_file")))
                return await send_end(ctx, out)

            # update template data
            ctx.session.query(TemplateDb)\
                .filter_by(guild_id=ctx.guild.id, name=name)\
                .update({
                    "url": t.url,
                    "md5": t.md5,
                    "width": t.width,
                    "height": t.height,
                    "size": t.size,
                    "date_modified": t.date_modified
                })
            ctx.session.commit()
            out.append("File updated.")

        if args.x:
            orig_x = copy.copy(orig_template.x)

            try:
                x = int(re.sub('[^0-9-]', '', args.x))
            except ValueError:
                out.append(
                    "Updating x failed, value provided was not a number.")
                return await send_end(ctx, out)

            ctx.session.query(TemplateDb)\
                .filter_by(guild_id=ctx.guild.id, name=name)\
                .update({
                    "x": x,
                    "date_modified": int(time.time())
                })
            ctx.session.commit()
            out.append(f"X coordinate changed from {orig_x} to {x}.")

        if args.y:
            orig_y = copy.copy(orig_template.y)

            try:
                y = int(re.sub('[^0-9-]', '', args.y))
            except ValueError:
                out.append(
                    "Updating y failed, value provided was not a number.")
                return await send_end(ctx, out)

            ctx.session.query(TemplateDb)\
                .filter_by(guild_id=ctx.guild.id, name=name)\
                .update({
                    "y": y,
                    "date_modified": int(time.time())
                })
            ctx.session.commit()
            out.append(f"Y coordinate changed from {orig_y} to {y}.")

        if args.newName:
            dup_check = ctx.session.query(TemplateDb.name).filter_by(
                guild_id=ctx.guild.id, name=args.newName).first()
            if dup_check is not None:
                out.append(
                    f"Updating name failed, the name {args.newName} is already in use."
                )
                return await send_end(ctx, out)
            if len(args.newName) > config.MAX_TEMPLATE_NAME_LENGTH:
                out.append("Updating name failed: {}".format(
                    ctx.s("template.err.name_too_long").format(
                        config.MAX_TEMPLATE_NAME_LENGTH)))
                return await send_end(ctx, out)
            if args.newName[0] == "-":
                out.append(
                    "Updating name failed: Names cannot begin with hyphens.")
                return await send_end(ctx, out)
            try:
                _ = int(args.newName)
                out.append("Updating name failed: Names cannot be numbers.")
                return await send_end(ctx, out)
            except ValueError:
                pass

            ctx.session.query(TemplateDb)\
                .filter_by(guild_id=ctx.guild.id, name=name)\
                .update({
                    "name": args.newName,
                    "date_modified": int(time.time())
                })
            ctx.session.commit()
            out.append(f"Nickname changed from {name} to {args.newName}.")

        await send_end(ctx, out)
Пример #9
0
    async def template_info(self, ctx, *args):
        # Order Parsing
        try:
            name = args[0]
        except TypeError:
            await ctx.send("Error: no arguments were provided.")
            return

        if re.match("-\D+", name) != None:
            name = args[-1]
            args = args[:-1]
        else:
            args = args[1:]

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-r", "--raw", action="store_true")
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        parser.add_argument("-z", "--zoom", default=1)
        try:
            args = vars(parser.parse_args(args))
        except TypeError:
            return

        image_only = args["raw"]
        f = args["faction"]
        try:
            gid, faction = f.id, f
        except AttributeError:
            gid, faction = ctx.guild.id, sql.guild_get_by_id(ctx.guild.id)
        zoom = args["zoom"]

        t = sql.template_get_by_name(gid, name)
        if not t:
            raise TemplateNotFoundError(gid, name)

        if image_only:
            try:
                if type(zoom) is not int:
                    if zoom.startswith("#"):
                        zoom = zoom[1:]
                    zoom = int(zoom)
            except ValueError:
                zoom = 1
            max_zoom = int(math.sqrt(4000000 // (t.width * t.height)))
            zoom = max(1, min(zoom, max_zoom))

            img = render.zoom(await http.get_template(t.url, t.name), zoom)

            with io.BytesIO() as bio:
                img.save(bio, format="PNG")
                bio.seek(0)
                f = discord.File(bio, t.name + ".png")
                await ctx.send(file=f)
            return

        canvas_name = canvases.pretty_print[t.canvas]
        coords = "{}, {}".format(t.x, t.y)
        dimensions = "{} x {}".format(t.width, t.height)
        size = t.size
        visibility = ctx.s("bot.private") if bool(
            t.private) else ctx.s("bot.public")
        owner = self.bot.get_user(t.owner_id)
        if owner is None:
            added_by = ctx.s("error.account_deleted")
        else:
            added_by = owner.name + "#" + owner.discriminator
        date_added = datetime.date.fromtimestamp(
            t.date_created).strftime("%d %b, %Y")
        date_modified = datetime.date.fromtimestamp(
            t.date_updated).strftime("%d %b, %Y")
        color = faction.faction_color
        description = "[__{}__]({})".format(
            ctx.s("template.link_to_canvas"),
            canvases.url_templates[t.canvas].format(*t.center()))

        if size == 0:
            t.size = await render.calculate_size(await http.get_template(
                t.url, t.name))
            sql.template_update(t)

        e = discord.Embed(title=t.name, color=color, description=description) \
            .set_image(url=t.url) \
            .add_field(name=ctx.s("bot.canvas"), value=canvas_name, inline=True) \
            .add_field(name=ctx.s("bot.coordinates"), value=coords, inline=True) \
            .add_field(name=ctx.s("bot.dimensions"), value=dimensions, inline=True) \
            .add_field(name=ctx.s("bot.size"), value=size, inline=True) \
            .add_field(name=ctx.s("bot.visibility"), value=visibility, inline=True) \
            .add_field(name=ctx.s("bot.added_by"), value=added_by, inline=True) \
            .add_field(name=ctx.s("bot.date_added"), value=date_added, inline=True) \
            .add_field(name=ctx.s("bot.date_modified"), value=date_modified, inline=True)

        if faction.id != ctx.guild.id and faction.faction_name:
            e = e.set_author(name=faction.faction_name,
                             icon_url=faction.faction_emblem
                             or discord.Embed.Empty)

        await ctx.send(embed=e)
Пример #10
0
    async def template(self, ctx, *args):
        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-p", "--page", type=int, default=1)
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        try:
            args = vars(parser.parse_args(args))
        except TypeError:
            return

        page = args["page"]
        faction = args["faction"]

        gid = ctx.guild.id
        if faction != None:
            gid = faction.id

        templates = sql.template_get_all_by_guild_id(gid)
        if len(templates) < 1:
            raise NoTemplatesError()

        # Find number of pages given there are 25 templates per page.
        pages = int(math.ceil(len(templates) / 25))
        # Makes sure page is in the range (1 <= page <= pages).
        page = min(max(page, 0), pages)
        page_index = page - 1

        embed = Template.build_table(ctx, page_index, pages, templates)
        message = await ctx.send(embed=embed)
        await message.add_reaction('◀')
        await message.add_reaction('▶')

        def is_valid(reaction, user):
            return reaction.message.id == message.id and (
                reaction.emoji == '◀'
                or reaction.emoji == '▶') and user.id != discord.ClientUser.id

        _5_minutes_in_future = (datetime.datetime.today() +
                                datetime.timedelta(minutes=5.0))
        try:
            while _5_minutes_in_future > datetime.datetime.today():
                add_future = asyncio.ensure_future(
                    self.bot.wait_for("reaction_add",
                                      timeout=300.0,
                                      check=is_valid))
                remove_future = asyncio.ensure_future(
                    self.bot.wait_for("reaction_remove",
                                      timeout=300.0,
                                      check=is_valid))
                reaction, _user = None, None
                while True:
                    if remove_future.done() == True:
                        reaction, _user = remove_future.result()
                        break
                    if add_future.done() == True:
                        reaction, _user = add_future.result()
                        break
                    await asyncio.sleep(0.1)

                if reaction.emoji == '◀':
                    if page_index != 0:
                        #not on first page, scroll left
                        page_index -= 1
                        embed = Template.build_table(ctx, page_index, pages,
                                                     templates)
                        await message.edit(embed=embed)
                elif reaction.emoji == '▶':
                    if page_index != pages - 1:
                        #not on last page, scroll right
                        page_index += 1
                        embed = Template.build_table(ctx, page_index, pages,
                                                     templates)
                        await message.edit(embed=embed)
        except asyncio.TimeoutError:
            pass
        await message.edit(content=ctx.s("bot.timeout"), embed=embed)
Пример #11
0
    async def template_update_pixelcanvas(self, ctx, *args):
        log.info(f"g!t update run in {ctx.guild.name} with args: {args}")

        try:
            name = args[0]
        except TypeError:
            await ctx.send(
                "Template not updated as no arguments were provided.")
            return

        if re.match("-\D+", name) != None:
            name = args[-1]
            args = args[:-1]
        else:
            args = args[1:]

        orig_template = sql.template_get_by_name(ctx.guild.id, name)
        if not orig_template:
            raise TemplateNotFoundError(ctx.guild.id, name)

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-n", "--newName", nargs="?", default=None)
        parser.add_argument("-x", nargs="?", default=None)
        parser.add_argument("-y", nargs="?", default=None)
        # if -i not present, False
        # if no value after -i, True
        # if value after -i, capture
        parser.add_argument("-i",
                            "--image",
                            nargs="?",
                            const=True,
                            default=None)
        try:
            args = vars(parser.parse_args(args))
        except TypeError:
            return

        new_name = args["newName"]
        x = args["x"]
        y = args["y"]
        image = args["image"]

        out = []
        """Image is done first since I'm using the build_template method to update stuff,
        and I don't want anything to have changed in orig_template before I use it"""
        if image:
            # Update image
            url = None
            if not isinstance(image, bool):
                url = image
            url = await Template.select_url_update(ctx, url, out)
            if url is None:
                return  # Sending the end is handled in select_url_update if it fails

            try:
                t = await Template.build_template(ctx, orig_template.name,
                                                  orig_template.x,
                                                  orig_template.y, url,
                                                  "pixelcanvas")
            except TemplateHttpError:
                out.append(
                    f"Updating file failed: Could not access URL for template."
                )
                await Template.send_end(ctx, out)
                return
            except NoJpegsError:
                out.append(
                    f"Updating file failed: Seriously? A JPEG? Gross! Please create a PNG template instead."
                )
                await Template.send_end(ctx, out)
                return
            except NotPngError:
                out.append(
                    f"Updating file failed: That command requires a PNG image."
                )
                await Template.send_end(ctx, out)
                return
            except (PilImageError, UrlError):
                out.append(f"Updating file failed.")
                await Template.send_end(ctx, out)
                return

            if t is None:
                out.append(f"Updating file failed.")
                await Template.send_end(ctx, out)
                return

            # Could check for md5 duplicates here, maybe implement that later
            sql.template_kwarg_update(ctx.guild.id,
                                      orig_template.name,
                                      url=t.url,
                                      md5=t.md5,
                                      w=t.width,
                                      h=t.height,
                                      size=t.size,
                                      date_modified=int(time.time()))
            out.append(f"File updated.")

        if x:
            # Update x coord
            try:
                x = int(re.sub('[^0-9-]', '', x))
            except ValueError:
                out.append(
                    "Updating x failed, value provided was not a number.")
                await Template.send_end(ctx, out)
                return

            sql.template_kwarg_update(ctx.guild.id,
                                      orig_template.name,
                                      x=x,
                                      date_modified=int(time.time()))
            out.append(f"X coordinate changed from {orig_template.x} to {x}.")

        if y:
            # Update y coord
            try:
                y = int(re.sub('[^0-9-]', '', y))
            except ValueError:
                out.append(
                    "Updating y failed, value provided was not a number.")
                await Template.send_end(ctx, out)
                return

            sql.template_kwarg_update(ctx.guild.id,
                                      orig_template.name,
                                      y=y,
                                      date_modified=int(time.time()))
            out.append(f"Y coordinate changed from {orig_template.y} to {y}.")

        if new_name:
            # Check if new name is already in use
            dup_check = sql.template_get_by_name(ctx.guild.id, new_name)
            if dup_check != None:
                out.append(
                    f"Updating name failed, the name {new_name} is already in use."
                )
                await Template.send_end(ctx, out)
                return
            # Check if new name is too long
            if len(new_name) > config.MAX_TEMPLATE_NAME_LENGTH:
                out.append("Updating name failed: " +
                           ctx.s("template.err.name_too_long").format(
                               config.MAX_TEMPLATE_NAME_LENGTH))
                await Template.send_end(ctx, out)
                return
            # Check if new name begins with a '-'
            if new_name[0] == "-":
                out.append(
                    "Updating name failed: Names cannot begin with hyphens.")
                await Template.send_end(ctx, out)
                return
            # Make sure the name isn't a number
            try:
                c = int(new_name)
                out.append("Updating name failed: Names cannot be numbers.")
                await Template.send_end(ctx, out)
                return
            except ValueError:
                pass

            # None with new nick, update template
            sql.template_kwarg_update(ctx.guild.id,
                                      orig_template.name,
                                      new_name=new_name,
                                      date_modified=int(time.time()))
            out.append(f"Nickname changed from {name} to {new_name}.")

        await Template.send_end(ctx, out)
Пример #12
0
async def _quantize(ctx, args, canvas, palette):
    """Sends a message containing a quantised version of the image given.

    Arguments:
    ctx - A commands.Context object.
    args - A list of arguments from the user, all strings.
    canvas - The canvas to use, string.
    palette - The palette to quantise to, a list of rgb tuples.

    Returns:
    The discord.Message object returned when ctx.send() is called to send the quantised image.
    """
    # Argument Parsing
    parser = GlimmerArgumentParser(ctx)
    parser.add_argument("-f", "--faction", default=None, action=FactionAction)
    parser.add_argument("-z", "--zoom", type=int, default=1)

    # Pre-Parsing
    if len(args) == 0:
        name = None
    elif args[0][0] != "-":
        name = args[0]
        args = args[1:]
    else:
        name = None

    try:
        args = vars(parser.parse_args(args))
    except TypeError:
        return

    faction = args["faction"]
    zoom = args["zoom"]

    gid = ctx.guild.id if not faction else faction.id
    t = sql.template_get_by_name(gid, name)

    data = None
    if name:
        if t:
            log.info("(T:{} | GID:{})".format(t.name, t.gid))
            if t.canvas == canvas:
                raise IdempotentActionError
            data = await http.get_template(t.url, t.name)
        else:
            raise TemplateNotFoundError(gid, name)
    else:
        att = await verify_attachment(ctx)
        if att:
            data = io.BytesIO()
            await att.save(data)

    if data:
        template, bad_pixels = await render.quantize(data, palette)

        with io.BytesIO() as bio:
            template.save(bio, format="PNG")
            bio.seek(0)
            f = discord.File(bio, "template.png")
            return await ctx.send(ctx.s("canvas.quantize").format(bad_pixels),
                                  file=f)
Пример #13
0
    async def _pre_diff(self, ctx, args, name=None, canvas=None, fetch=None, palette=None, help=False):
        for arg in args:
            if any(h == arg for h in ["--help", "-h"]):
                help = True

        if not help and not name:
            att = await verify_attachment(ctx)

            # Order Parsing
            try:
                x, y = args[0], args[1]
            except IndexError:
                await ctx.send("Error: not enough arguments were provided.")
                return

            if re.match(r"-\D+", x) is not None:
                x, y = args[-2], args[-1]
                args = args[:-2]
            else:
                args = args[2:]

            # X and Y Cleanup
            try:
                # cleans up x and y by removing all spaces and chars that aren't 0-9 or the minus sign using regex. Then makes em ints
                x = int(re.sub('[^0-9-]', '', x))
                y = int(re.sub('[^0-9-]', '', y))
            except ValueError:
                await ctx.send(ctx.s("canvas.invalid_input"))
                return

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-e", "--errors", action='store_true')
        parser.add_argument("-s", "--snapshot", action='store_true')
        parser.add_argument("-c", "--highlightCorrect", action='store_true')
        parser.add_argument("-cb", "--colorBlind", action='store_true')
        parser.add_argument("-z", "--zoom", type=int, default=1)
        parser.add_argument("-t", "--excludeTarget", action='store_true')
        colorFilters = parser.add_mutually_exclusive_group()
        colorFilters.add_argument("-ec", "--excludeColors", nargs="+", type=int, default=None)
        colorFilters.add_argument("-oc", "--onlyColors", nargs="+", type=int, default=None)

        if name:
            parser.add_argument("-f", "--faction", default=None, action=FactionAction)

        try:
            if help:
                args = ["--help"]
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        if name:
            gid = ctx.guild.id if not args.faction else args.faction.id
            t = ctx.session.query(Template).filter_by(guild_id=gid, name=name).first()
            if t:
                data = await http.get_template(t.url, t.name)
                await self._diff(
                    ctx, args, t.x, t.y, t.width, t.height,
                    t.canvas, self.bot.fetchers[t.canvas],
                    colors.by_name[t.canvas], data)
            else:
                raise TemplateNotFoundError(ctx, gid, name)
        else:
            data = io.BytesIO()
            await att.save(data)
            await self._diff(
                ctx, args, x, y, att.width, att.height,
                canvas, fetch, palette, data)
Пример #14
0
    async def diff(self, ctx, *args):
        log.info(f"g!diff run in {ctx.guild.name} with args: {args}")

        # Order Parsing
        try:
            name = args[0]
        except TypeError:
            await ctx.send("Error: no arguments were provided.")
            return

        if re.match("-\D+", name) != None:
            name = args[-1]
            args = args[:-1]
        else:
            args = args[1:]

        if re.match("-{0,1}\d+",
                    name) != None:  # Skip to coords + image parsing
            await ctx.invoke_default("diff")
            return

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-e", "--errors", action='store_true')
        parser.add_argument("-s", "--snapshot", action='store_true')
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        parser.add_argument("-z", "--zoom", type=int, default=1)
        parser.add_argument("-t", "--excludeTarget", action='store_true')
        colorFilters = parser.add_mutually_exclusive_group()
        colorFilters.add_argument("-ec",
                                  "--excludeColors",
                                  nargs="+",
                                  type=int,
                                  default=None)
        colorFilters.add_argument("-oc",
                                  "--onlyColors",
                                  nargs="+",
                                  type=int,
                                  default=None)
        try:
            a = vars(parser.parse_args(args))
        except TypeError:
            return

        list_pixels = a["errors"]
        create_snapshot = a["snapshot"]
        faction = a["faction"]
        zoom = a["zoom"]
        exclude_target = a["excludeTarget"]
        exclude_colors = a["excludeColors"]
        only_colors = a["onlyColors"]

        gid = ctx.guild.id if not faction else faction.id
        t = sql.template_get_by_name(gid, name)

        if t:
            async with ctx.typing():
                log.info("(T:{} | GID:{})".format(t.name, t.gid))
                data = await http.get_template(t.url, t.name)
                max_zoom = int(math.sqrt(4000000 // (t.width * t.height)))
                zoom = max(1, min(zoom, max_zoom))

                fetchers = {
                    'pixelcanvas': render.fetch_pixelcanvas,
                    'pixelzone': render.fetch_pixelzone,
                    'pxlsspace': render.fetch_pxlsspace
                }

                diff_img, tot, err, bad, err_list \
                    = await render.diff(t.x, t.y, data, zoom, fetchers[t.canvas], colors.by_name[t.canvas], create_snapshot)

                done = tot - err
                perc = done / tot
                if perc < 0.00005 and done > 0:
                    perc = ">0.00%"
                elif perc >= 0.99995 and err > 0:
                    perc = "<100.00%"
                else:
                    perc = "{:.2f}%".format(perc * 100)
                out = ctx.s("canvas.diff") if bad == 0 else ctx.s(
                    "canvas.diff_bad_color")
                out = out.format(done, tot, err, perc, bad=bad)

                with io.BytesIO() as bio:
                    diff_img.save(bio, format="PNG")
                    bio.seek(0)
                    f = discord.File(bio, "diff.png")
                    await ctx.send(content=out, file=f)

                if list_pixels and len(err_list) > 0:
                    error_list = []
                    for x, y, current, target in err_list:
                        # Color Filtering
                        c = current if not exclude_target else target
                        if exclude_colors:
                            if c in exclude_colors:
                                continue
                        elif only_colors:
                            if not c in only_colors:
                                continue

                        # The current x,y are in terms of the template area, add to template start coords so they're in terms of canvas
                        x += t.x
                        y += t.y
                        error_list.append(Pixel(current, target, x, y))

                    checker = Checker(self.bot, ctx, t.canvas, error_list)
                    checker.connect_websocket()
        else:
            # No template found
            raise TemplateNotFoundError(gid, name)
Пример #15
0
    async def check(self, ctx, *args):

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-e", "--onlyErrors", action='store_true')
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        parser.add_argument("-s",
                            "--sort",
                            default="name_az",
                            choices=[
                                "name_az", "name_za", "errors_az", "errors_za",
                                "percent_az", "percent_za"
                            ])
        try:
            a = vars(parser.parse_args(args))
        except TypeError:
            return

        only_errors = a["onlyErrors"]
        faction = a["faction"]
        sort = a["sort"]

        if faction:
            templates = sql.template_get_all_by_guild_id(faction.id)
        else:
            templates = sql.template_get_all_by_guild_id(ctx.guild.id)

        if len(templates) < 1:
            ctx.command.parent.reset_cooldown(ctx)
            raise NoTemplatesError(False)

        msg = None

        # Calc info + send temp msg
        for canvas, canvas_ts in itertools.groupby(templates,
                                                   lambda tx: tx.canvas):
            ct = list(canvas_ts)
            msg = await check_canvas(ctx, ct, canvas, msg=msg)

        # Delete temp msg and send final report
        await msg.delete()

        ts = [t for t in templates
              if t.errors != 0] if only_errors else templates

        if sort == "name_az" or sort == "name_za":
            ts = sorted(ts, key=lambda t: t.name, reverse=(sort == "name_za"))
        elif sort == "errors_az" or sort == "errors_za":
            ts = sorted(ts,
                        key=lambda t: t.errors,
                        reverse=(sort == "errors_za"))
        elif sort == "percent_az" or sort == "percent_za":
            ts = sorted(ts,
                        key=lambda t: (t.size - t.errors) / t.size,
                        reverse=(sort == "percent_za"))

        ts = sorted(ts, key=lambda t: t.canvas)

        # Find number of pages given there are 25 templates per page.
        pages = int(math.ceil(len(ts) / 25))
        await build_template_report(ctx, ts, None, pages)
Пример #16
0
    async def preview(self, ctx, *args):
        log.info(f"g!preview run in {ctx.guild.name} with args: {args}")

        # Order Parsing
        try:
            name = args[0]
        except TypeError:
            await ctx.send("Error: no arguments were provided.")
            return

        if re.match("-\D+", name) != None:
            name = args[-1]
            args = args[:-1]
        else:
            args = args[1:]

        if re.match("-{0,1}\d+",
                    name) != None:  # Skip to coords + image parsing
            await ctx.invoke_default("preview")
            return

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-t", "--templateRegion", action='store_true')
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        parser.add_argument("-z", "--zoom", type=int, default=1)
        try:
            a = vars(parser.parse_args(args))
        except TypeError:
            return

        preview_template_region = a["templateRegion"]
        faction = a["faction"]
        zoom = a["zoom"]

        gid = ctx.guild.id if not faction else faction.id
        t = sql.template_get_by_name(gid, name)

        if t:
            async with ctx.typing():
                log.info("(T:{} | GID:{})".format(t.name, t.gid))
                max_zoom = int(math.sqrt(4000000 // (t.width * t.height)))
                zoom = max(-8, min(zoom, max_zoom))

                fetchers = {
                    'pixelcanvas': render.fetch_pixelcanvas,
                    'pixelzone': render.fetch_pixelzone,
                    'pxlsspace': render.fetch_pxlsspace
                }

                if preview_template_region:
                    preview_img = await render.preview(*t.center(), zoom,
                                                       fetchers[t.canvas])
                else:
                    preview_img = await render.preview_template(
                        t, zoom, fetchers[t.canvas])

                with io.BytesIO() as bio:
                    preview_img.save(bio, format="PNG")
                    bio.seek(0)
                    f = discord.File(bio, "preview.png")
                    await ctx.send(file=f)
                return

        # No template found
        raise TemplateNotFoundError(gid, name)
Пример #17
0
    async def _preview(self, ctx, args, name=None, fetch=None, help=False):
        """Sends a preview of the image or template provided.

        Arguments:
        ctx - A commands.Context object.
        args - A list of arguments from the user, all strings.

        Keyword Arguments:
        name - The name of the template to preview.
        fetch - A function to fetch from a specific canvas.
        """
        for arg in args:
            if any(h == arg for h in ["--help", "-h"]):
                help = True

        if not help and not name:
            # Order Parsing
            try:
                x, y = args[0], args[1]
            except IndexError:
                await ctx.send("Error: no arguments were provided.")
                return

            if re.match(r"-\D+", x) is not None:
                x, y = args[-2], args[-1]
                args = args[:-2]
            else:
                args = args[2:]

            # X and Y Cleanup
            try:
                # Remove all spaces and chars that aren't 0-9 or the minus sign.
                x = int(re.sub('[^0-9-]', '', x))
                y = int(re.sub('[^0-9-]', '', y))
            except ValueError:
                await ctx.send(ctx.s("canvas.invalid_input"))
                return

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-z", "--zoom", type=int, default=1)

        if name:
            parser.add_argument("-t", "--templateRegion", action='store_true')
            parser.add_argument("-f", "--faction", default=None, action=FactionAction)

        try:
            if help:
                args = ["--help"]
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        t = None
        if name:
            gid = ctx.guild.id if not args.faction else args.faction.id
            t = ctx.session.query(Template).filter_by(guild_id=gid, name=name).first()

            if not t:
                raise TemplateNotFoundError(ctx, gid, name)

            fetch = self.bot.fetchers[t.canvas]
            if args.templateRegion:
                x, y = t.center

        async with ctx.typing():
            zoom = max(min(args.zoom, 16), -8)

            if t:
                preview_img = await render.preview_template(self.bot, t, zoom, fetch)
            else:
                preview_img = await render.preview(self.bot, x, y, zoom, fetch)

            with io.BytesIO() as bio:
                preview_img.save(bio, format="PNG")
                bio.seek(0)
                f = discord.File(bio, "preview.png")
                await ctx.send(file=f)
Пример #18
0
    async def template_info(self, ctx, *args):
        # Order Parsing
        try:
            name = args[0]
        except IndexError:
            return await ctx.send(ctx.s("error.missing_argument"))

        skip = False
        for arg in args:
            if any(h == arg for h in ["--help", "-h"]):
                args = ["--help"]
                skip = True

        if not skip:
            if re.match(r"-\D+", name) is not None:
                name = args[-1]
                args = args[:-1]
            else:
                args = args[1:]

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-r", "--raw", action="store_true")
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        parser.add_argument("-z", "--zoom", default=1)
        try:
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        try:
            gid, faction = args.faction.id, args.faction
        except AttributeError:
            gid, faction = ctx.guild.id, ctx.session.query(Guild).get(
                ctx.guild.id)

        t = ctx.session.query(TemplateDb).filter_by(guild_id=gid,
                                                    name=name).first()
        if not t:
            raise TemplateNotFoundError(ctx, gid, name)

        if args.raw:
            try:
                zoom = int(args.zoom)
            except ValueError:
                zoom = 1
            max_zoom = int(math.sqrt(4000000 // (t.width * t.height)))
            zoom = max(1, min(zoom, max_zoom))

            img = render.zoom(await http.get_template(t.url, t.name), zoom)

            with io.BytesIO() as bio:
                img.save(bio, format="PNG")
                bio.seek(0)
                f = discord.File(bio, t.name + ".png")
                await ctx.send(file=f)
            return

        canvas_name = canvases.pretty_print[t.canvas]
        coords = "{}, {}".format(t.x, t.y)
        dimensions = "{} x {}".format(t.width, t.height)
        size = t.size
        owner = self.bot.get_user(t.owner)
        if owner is None:
            added_by = ctx.s("error.account_deleted")
        else:
            added_by = owner.name + "#" + owner.discriminator
        date_added = datetime.date.fromtimestamp(
            t.date_added).strftime("%d %b, %Y")
        date_modified = datetime.date.fromtimestamp(
            t.date_modified).strftime("%d %b, %Y")
        color = faction.faction_color
        description = "[__{}__]({})".format(
            ctx.s("template.link_to_canvas"),
            canvases.url_templates[t.canvas].format(*t.center))

        e = discord.Embed(title=t.name, color=color, description=description) \
            .set_image(url=t.url) \
            .add_field(name=ctx.s("bot.canvas"), value=canvas_name, inline=True) \
            .add_field(name=ctx.s("bot.coordinates"), value=coords, inline=True) \
            .add_field(name=ctx.s("bot.dimensions"), value=dimensions, inline=True) \
            .add_field(name=ctx.s("bot.size"), value=size, inline=True) \
            .add_field(name=ctx.s("bot.added_by"), value=added_by, inline=True) \
            .add_field(name=ctx.s("bot.date_added"), value=date_added, inline=True) \
            .add_field(name=ctx.s("bot.date_modified"), value=date_modified, inline=True)

        if t.alert_id:
            channel = self.bot.get_channel(t.alert_id)
            e.add_field(name=ctx.s("bot.alert_channel"),
                        value=channel.mention,
                        inline=True)

        if faction.id != ctx.guild.id and faction.faction_name:
            e = e.set_author(name=faction.faction_name,
                             icon_url=faction.faction_emblem
                             or discord.Embed.Empty)

        await ctx.send(embed=e)
Пример #19
0
    async def check(self, ctx, *args):

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-e", "--onlyErrors", action='store_true')
        parser.add_argument("-f", "--faction", default=None, action=FactionAction)
        parser.add_argument("-s", "--sort", default="name_az", choices=[
            "name_az", "name_za", "errors_az", "errors_za", "percent_az", "percent_za"])
        parser.add_argument("-p", "--page", default=1, type=int)
        try:
            a = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {a}")

        if a.faction:
            templates = ctx.session.query(Template).filter_by(guild_id=a.faction.id).all()
        else:
            templates = ctx.session.query(Template).filter_by(guild_id=ctx.guild.id).all()

        if len(templates) < 1:
            ctx.command.reset_cooldown(ctx)
            raise NoTemplatesError(False)

        msg = None

        # Calc info + send temp msg
        for canvas, canvas_ts in itertools.groupby(templates, lambda tx: tx.canvas):
            ct = list(canvas_ts)
            msg = await self.check_canvas(ctx, ct, canvas, msg=msg)

        # Delete temp msg and send final report
        await msg.delete()

        ts = [t for t in templates if t.errors != 0] if a.onlyErrors else templates

        if a.sort == "name_az" or a.sort == "name_za":
            ts = sorted(ts, key=lambda t: t.name, reverse=(a.sort == "name_za"))
        elif a.sort == "errors_az" or a.sort == "errors_za":
            ts = sorted(ts, key=lambda t: t.errors, reverse=(a.sort == "errors_za"))
        elif a.sort == "percent_az" or a.sort == "percent_za":
            ts = sorted(ts, key=lambda t: (t.size - t.errors) / t.size, reverse=(a.sort == "percent_za"))

        ts = sorted(ts, key=lambda t: t.canvas)

        check_menu = menus.MenuPages(
            source=CheckSource(ts),
            clear_reactions_after=True,
            timeout=300.0)
        check_menu.current_page = max(min(a.page - 1, check_menu.source.get_max_pages()), 0)
        try:
            await check_menu.start(ctx, wait=True)
            check_menu.source.embed.set_footer(text=ctx.s("bot.timeout"))
            await check_menu.message.edit(embed=check_menu.source.embed)
        except discord.NotFound:
            await ctx.send(ctx.s("bot.menu_deleted"))
Пример #20
0
    async def send_stats(self, ctx, args, canvas):
        parser = GlimmerArgumentParser(ctx)
        output = parser.add_mutually_exclusive_group()
        output.add_argument(
            "-t", "--type",
            default="hexbin",
            choices=["color-pie", "hexbin", "online-line", "2dhist", "placement-hist"])
        output.add_argument("-r", "--raw", default=False, choices=["placement", "online"])
        parser.add_argument(
            "-d", "--duration",
            default=DurationAction.get_duration(ctx, "1d"),
            action=DurationAction)
        parser.add_argument("-c", "--center", nargs=2, type=int)
        parser.add_argument("-a", "--radius", type=int, default=500)
        parser.add_argument("--nooverlay", action="store_true")
        parser.add_argument("--bins", default="log", choices=["log", "count"])
        parser.add_argument("--mean", action="store_true")
        parser.add_argument(
            "--colormap",
            default="plasma",
            choices=[cmap for cmap in cmaps.keys() if not cmap.endswith("_r")],
            help="See: https://matplotlib.org/tutorials/colors/colormaps.html for visualisations.")

        try:
            args = parser.parse_args(args)
        except TypeError:
            return

        # Verify coordinate info.
        if args.center:
            center = args.center
        else:
            center = canvases.center[canvas]

        if args.radius > 1000:
            return await ctx.send(ctx.s(canvas.radius_toolarge).format(1000))

        start_x, start_y = center[0] - args.radius, center[1] - args.radius
        end_x, end_y = center[0] + args.radius, center[1] + args.radius

        axes = [start_x, end_x, start_y, end_y]

        start = args.duration.start
        end = args.duration.end

        start_str = args.duration.start.strftime("%d %b %Y %H:%M:%S UTC")
        end_str = args.duration.end.strftime("%d %b %Y %H:%M:%S UTC")

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        # NOTE: Could definitely think about using processes rather than threads
        # for both the collection+processing and plotting of our data here.
        # I'm pretty sure none of these functions are actually sharing sqlalchemy
        # objects across (minus the session, but we can just ditch that and access
        # the db via the engine directly after calling engine.dispose()).
        # Threading means we don't block the event loop, but it's gonna for sure slow
        # stuff down.

        if args.raw:
            process_func = partial(plot.process_raw, ctx, canvas, args.duration.start, args.duration.end, args.raw)
            buf = await self.bot.loop.run_in_executor(None, process_func)

            content = ctx.s(f"canvas.csv_{args.raw}").format(
                start_str, end_str, canvases.pretty_print[canvas])
            file = discord.File(buf, "{0}-from-{1}-to-{2}.csv".format(
                canvas,
                int(args.duration.start.timestamp()),
                int(args.duration.end.timestamp())))
            await ctx.send(content, file=file)
            return

        if args.type == "color-pie":
            process_func = partial(plot.process_color_pie, canvas, args.duration.start, args.duration.end)
            data = await self.bot.loop.run_in_executor(None, process_func)

            plot_func = partial(plot.color_pie, data, canvas)
            image = await self.bot.loop.run_in_executor(None, plot_func)

            content = ctx.s("canvas.pie_color_title").format(
                canvases.pretty_print[canvas], start_str, end_str)
        elif args.type == "hexbin":
            preview_img = not args.nooverlay
            if not args.nooverlay:
                t = MockTemplate(axes)
                fetch = self.bot.fetchers[canvas]
                preview_img = await render.preview_template(self.bot, t, 1, fetch)

            process_func = partial(plot.process_histogram, canvas, start, end, axes)
            x_values, y_values = await self.bot.loop.run_in_executor(None, process_func)

            plot_func = partial(plot.hexbin_placement_density, ctx, x_values, y_values, args.colormap, args.bins, axes, center, overlay=preview_img)
            image = await self.bot.loop.run_in_executor(None, plot_func)
            content = ctx.s("canvas.hexbin_title").format(
                canvases.pretty_print[canvas], start_str, end_str)
        elif args.type == "2dhist":
            preview_img = not args.nooverlay
            if not args.nooverlay:
                t = MockTemplate(axes)
                fetch = self.bot.fetchers[canvas]
                preview_img = await render.preview_template(self.bot, t, 1, fetch)

            process_func = partial(plot.process_histogram, canvas, start, end, axes)
            x_values, y_values = await self.bot.loop.run_in_executor(None, process_func)

            axes = start_x, end_x, end_y, start_y
            plot_func = partial(plot.histogram_2d_placement_density, ctx, x_values, y_values, args.colormap, axes, center, overlay=preview_img)
            image = await self.bot.loop.run_in_executor(None, plot_func)
            content = ctx.s("canvas.hist2d_title").format(
                canvases.pretty_print[canvas], start_str, end_str)
        elif args.type == "online-line":
            process_func = partial(
                plot.process_online_line,
                ctx, canvas, start, end)
            x_values, y_values = await self.bot.loop.run_in_executor(None, process_func)

            plot_func = partial(
                plot.online_line,
                ctx, x_values, y_values, args.duration,
                mean=y_values.mean() if args.mean else args.mean)
            image = await self.bot.loop.run_in_executor(None, plot_func)
            content = ctx.s("canvas.online_line_title").format(
                canvases.pretty_print[canvas], start_str, end_str)
        elif args.type == "placement-hist":
            process_func = partial(plot.process_placement_hist, ctx, canvas, args.duration)
            times = await self.bot.loop.run_in_executor(None, process_func)

            plot_func = partial(plot.placement_hist, ctx, times, args.duration, args.bins)
            image = await self.bot.loop.run_in_executor(None, plot_func)
            content = "Histogram of placements on {0} from `{1}` to `{2}`".format(
                canvases.pretty_print[canvas], start_str, end_str)

        await ctx.send(content, file=discord.File(image, "stats.png"))
Пример #21
0
    async def alert_stats(self, ctx, *args):
        try:
            name = args[0]
        except IndexError:
            await ctx.send(ctx.s("error.missing_argument"))
            return

        skip = False
        for arg in args:
            if any(h == arg for h in ["--help", "-h"]):
                args = ["--help"]
                skip = True

        if not skip:
            if re.match(r"-\D+", name) is not None:
                name = args[-1]
                args = args[:-1]
            else:
                args = args[1:]

        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-f",
                            "--faction",
                            default=None,
                            action=FactionAction)
        parser.add_argument("-d",
                            "--duration",
                            default=DurationAction.get_duration(ctx, "1d"),
                            action=DurationAction)
        parser.add_argument("-t",
                            "--type",
                            default="comparision",
                            choices=["comparision", "gain"])
        try:
            args = parser.parse_args(args)
        except TypeError:
            return

        log.debug(f"[uuid:{ctx.uuid}] Parsed arguments: {args}")

        gid = ctx.guild.id
        if args.faction is not None:
            gid = args.faction.id

        template = ctx.session.query(TemplateDb)\
            .filter_by(guild_id=gid, name=name).first()
        if not template:
            raise TemplateNotFoundError(ctx, gid, name)

        alert_template = None
        for t in self.templates:
            if t.id == template.id:
                alert_template = t
                break

        if not alert_template:
            await ctx.send(
                "Error fetching data, is that template an alert template? (See `{0}help alert` for more info)."
                .format(ctx.prefix))
            return

        start = args.duration.start
        end = args.duration.end

        sq = ctx.session.query(
            Canvas.id).filter_by(nick=template.canvas).subquery()
        q = ctx.session.query(PixelDb).filter(
            PixelDb.placed.between(start, end), PixelDb.canvas_id.in_(sq),
            PixelDb.x.between(template.x, template.x + template.width - 1),
            PixelDb.y.between(template.y, template.y + template.height - 1))
        q = q.order_by(PixelDb.placed)
        pixels = q.all()

        if not len(pixels):
            raise NotEnoughDataError

        async with ctx.typing():
            process_func = partial(self.process, pixels, alert_template,
                                   args.duration.days)
            x_data, y_data = await self.bot.loop.run_in_executor(
                None, process_func)

            plot_types = {
                "comparision": plot.alert_comparision,
                "gain": plot.alert_gain
            }

            plot_func = partial(plot_types.get(args.type), ctx, x_data,
                                y_data[:, 0], y_data[:, 1], args.duration)
            image = await self.bot.loop.run_in_executor(None, plot_func)

            if args.type == "comparision":
                out = ctx.s("alerts.comparision_title").format(
                    template.name, args.duration.days)
            elif args.type == "gain":
                out = ctx.s("alerts.gain_title").format(
                    template.name, args.duration.days)

            await ctx.send(out, file=discord.File(image, "stats.png"))
Пример #22
0
async def _diff(self, ctx, args, canvas, fetch, palette):
    """Sends a diff on the image provided.

    Arguments:
    ctx - commands.Context object.
    args - A list of arguments from the user, all strings.
    canvas - The name of the canvas to look at, string.
    fetch - The fetch function to use, points to a fetch function from render.py.
    palette - The palette in use on this canvas, a list of rgb tuples.
    """
    async with ctx.typing():
        att = await verify_attachment(ctx)

        # Order Parsing
        try:
            x, y = args[0], args[1]
        except TypeError:
            await ctx.send("Error: no arguments were provided.")
            return

        if re.match("-\D+", x) != None:
            x, y = args[-2], args[-1]
            args = args[:-2]
        else:
            args = args[2:]

        # X and Y Cleanup
        try:
            #cleans up x and y by removing all spaces and chars that aren't 0-9 or the minus sign using regex. Then makes em ints
            x = int(re.sub('[^0-9-]', '', x))
            y = int(re.sub('[^0-9-]', '', y))
        except ValueError:
            await ctx.send(ctx.s("canvas.invalid_input"))
            return

        # Argument Parsing
        parser = GlimmerArgumentParser(ctx)
        parser.add_argument("-e", "--errors", action='store_true')
        parser.add_argument("-s", "--snapshot", action='store_true')
        parser.add_argument("-z", "--zoom", type=int, default=1)
        parser.add_argument("-t", "--excludeTarget", action='store_true')
        colorFilters = parser.add_mutually_exclusive_group()
        colorFilters.add_argument("-ec",
                                  "--excludeColors",
                                  nargs="+",
                                  type=int,
                                  default=None)
        colorFilters.add_argument("-oc",
                                  "--onlyColors",
                                  nargs="+",
                                  type=int,
                                  default=None)
        try:
            a = vars(parser.parse_args(args))
        except TypeError:
            return

        list_pixels = a["errors"]
        create_snapshot = a["snapshot"]
        zoom = a["zoom"]
        exclude_target = a["excludeTarget"]
        exclude_colors = a["excludeColors"]
        only_colors = a["onlyColors"]

        data = io.BytesIO()
        await att.save(data)
        max_zoom = int(math.sqrt(4000000 // (att.width * att.height)))
        zoom = max(1, min(zoom, max_zoom))
        diff_img, tot, err, bad, err_list = await render.diff(
            x, y, data, zoom, fetch, palette, create_snapshot)

        done = tot - err
        perc = done / tot
        if perc < 0.00005 and done > 0:
            perc = ">0.00%"
        elif perc >= 0.99995 and err > 0:
            perc = "<100.00%"
        else:
            perc = "{:.2f}%".format(perc * 100)
        out = ctx.s("canvas.diff") if bad == 0 else ctx.s(
            "canvas.diff_bad_color")
        out = out.format(done, tot, err, perc, bad=bad)

        with io.BytesIO() as bio:
            diff_img.save(bio, format="PNG")
            bio.seek(0)
            f = discord.File(bio, "diff.png")
            await ctx.send(content=out, file=f)

        if list_pixels and len(err_list) > 0:
            error_list = []
            for x, y, current, target in err_list:
                # Color Filtering
                c = current if not exclude_target else target
                if exclude_colors:
                    if c in exclude_colors:
                        continue
                elif only_colors:
                    if not c in only_colors:
                        continue

                # The current x,y are in terms of the template area, add to template start coords so they're in terms of canvas
                x += t.x
                y += t.y
                error_list.append(Pixel(current, target, x, y))

            checker = Checker(self.bot, ctx, t.canvas, error_list)
            checker.connect_websocket()