async def on_command_error(ctx, error): if isinstance(error, commands.CommandOnCooldown): await ctx.send(getlang(ctx.guild.id, "bot.error.command_on_cooldown").format(error.retry_after)) return if isinstance(error, commands.CommandNotFound): # await ctx.send(getlang(ctx.guild.id, "bot.error.command_not_found").format(get_prefix(bot, ctx.message))) return if isinstance(error, commands.MissingRequiredArgument): pages = bot.formatter.format_help_for(ctx, ctx.command) print(pages) for p in pages: await ctx.send(p) return if isinstance(error, commands.BadArgument): pages = bot.formatter.format_help_for(ctx, ctx.command) for p in pages: await ctx.send(p) return if isinstance(error, NoPermission): await ctx.send(getlang(ctx.guild.id, "bot.error.no_permission")) return if isinstance(error, commands.NoPrivateMessage): await ctx.send(getlang(ctx.guild.id, "bot.error.no_private_message")) return if isinstance(error, commands.CommandInvokeError) and isinstance(error.original, discord.HTTPException) \ and error.original.code == 50013: return cname = ctx.command.qualified_name if ctx.command is not None else "None" await channel_logger.log_to_channel("An error occurred while executing command `{0}` in server **{1.name}** " "(ID: `{1.id}`):".format(cname, ctx.guild)) await channel_logger.log_to_channel("```{}```".format(error)) log.error("An error occurred while executing command {}: {}\n{}" .format(cname, error, ''.join(traceback.format_exception(None, error, error.__traceback__)))) await ctx.send(getlang(ctx.guild.id, "bot.error.unhandled_command_error"))
async def parse_diff(ctx, coords): if len(ctx.message.attachments) < 1: await ctx.send( getlang(ctx.guild.id, "bot.error.missing_attachment")) return att = ctx.message.attachments[0] if att.filename[-4:].lower() != ".png": if att.filename[-4:].lower() == ".jpg" or att.filename[-5:].lower( ) == ".jpeg": await ctx.send(getlang(ctx.guild.id, "bot.error.jpeg")) return await ctx.send(getlang(ctx.guild.id, "bot.error.no_png")) return if att.width is None or att.height is None: await ctx.send( getlang(ctx.guild.id, "bot.error.bad_png").format( sql.get_guild_prefix(ctx.guild.id), getlang(ctx.guild.id, "command.quantize"))) return m = re.search('\(?(-?\d+), ?(-?\d+)\)?\s?#?(\d+)?', coords) if m is not None: x = int(m.group(1)) y = int(m.group(2)) zoom = int(m.group(3)) if m.group(3) is not None else 1 zoom = max(1, min(zoom, 400 // att.width, 400 // att.height)) return ctx, x, y, att, zoom
async def diff(ctx, x, y, att, zoom, fetch, palette): async with ctx.typing(): with io.BytesIO() as bio: await att.save(bio) template = Image.open(bio).convert('RGBA') log.debug("X:{0} | Y:{1} | Dim: {2}x{3} | Zoom: {4}".format( x, y, template.width, template.height, zoom)) if template.width * template.height > 600000: await ctx.send(getlang(ctx.guild.id, "render.large_template")) diff_img = await fetch(x, y, template.width, template.height) tot = 0 # Total non-transparent pixels in template err = 0 # Number of errors bad = 0 # Number of pixels in the template that are not in the color palette for py in range(template.height): for px in range(template.width): tp = template.getpixel((px, py)) dp = diff_img.getpixel((px, py)) if tp[3] is not 0: # All non-transparent pixels count to the total tot += 1 if 0 < tp[3] < 255 or (tp[3] is 255 and tp[:3] not in palette): # All non-opaque and non-transparent pixels, and opaque pixels of bad colors, are bad pixel = (0, 0, 255, 255) err += 1 bad += 1 elif tp[3] is 255 and (tp[0] is not dp[0] or tp[1] is not dp[1] or tp[2] is not dp[2]): # All pixels that are valid and opaque but do not match the canvas are wrong pixel = (255, 0, 0, 255) err += 1 else: # Render all correct/irrelevant pixels in greyscale avg = round(dp[0] * 0.3 + dp[1] * 0.52 + dp[2] * 0.18) pixel = (avg, avg, avg, 255) diff_img.putpixel((px, py), pixel) if zoom > 1: diff_img = diff_img.resize(tuple(zoom * x for x in diff_img.size), Image.NEAREST) if bad > 0: content = getlang(ctx.guild.id, "render.diff_bad_color")\ .format(tot - err, tot, err, bad, 100 * (tot - err) / tot) else: content = getlang(ctx.guild.id, "render.diff").format(tot - err, tot, err, 100 * (tot - err) / tot) with io.BytesIO() as bio: diff_img.save(bio, format="PNG") bio.seek(0) f = discord.File(bio, "diff.png") await ctx.send(content=content, file=f)
async def register(self, ctx): if not sql.is_user_animote_user(ctx.author.id): sql.add_animote_user(ctx.author.id) message = getlang(ctx.guild.id, "animotes.member_opt_in") else: sql.delete_animote_user(ctx.author.id) message = getlang(ctx.guild.id, "animotes.member_opt_out") await ctx.message.author.send(content=message)
async def autoscan(self, ctx): if not ctx.author.permissions_in(ctx.channel).administrator: raise NoPermission if sql.select_guild_by_id(ctx.guild.id)['autoscan'] == 0: sql.update_guild(ctx.guild.id, autoscan=1) await ctx.send( getlang(ctx.guild.id, "configuration.autoscan_enabled")) else: sql.update_guild(ctx.guild.id, autoscan=0) await ctx.send( getlang(ctx.guild.id, "configuration.autoscan_disabled"))
async def check_attachment(ctx): if len(ctx.message.attachments) < 1: await ctx.send( getlang(ctx.guild.id, "bot.error.missing_attachment")) return False filename = ctx.message.attachments[0].filename if filename[-4:].lower() != ".png": if filename[-4:].lower() == ".jpg" or filename[-5:].lower( ) == ".jpeg": await ctx.send(getlang(ctx.guild.id, "bot.error.jpeg")) return False await ctx.send(getlang(ctx.guild.id, "bot.error.no_png")) return False return True
async def setdefaultcanvas_pxlsspace(self, ctx): if not ctx.author.permissions_in(ctx.channel).administrator: raise NoPermission sql.update_guild(ctx.guild.id, default_canvas="pxlsspace") await ctx.send( getlang(ctx.guild.id, "configuration.default_canvas_set").format("Pxls.space"))
async def alertchannel_set(self, ctx, channel: TextChannel): if not ctx.author.permissions_in(ctx.channel).administrator: raise NoPermission sql.update_guild(ctx.guild.id, alert_channel=channel.id) await ctx.send( getlang(ctx.guild.id, "configuration.alert_channel_set").format(channel.mention))
async def registerguild(self, ctx): if not ctx.author.permissions_in(ctx.channel).manage_emojis: raise NoPermission if not sql.is_server_emojishare_server(ctx.guild.id): sql.update_guild(ctx.guild.id, emojishare=1) self.log.info("Guild {0.name} (ID: {0.id}) has opted in to emoji sharing.".format(ctx.guild)) await self.channel_logger.log_to_channel("Guild **{0.name}** (ID: `{0.id}`) has opted in to emoji sharing." .format(ctx.guild)) message = getlang(ctx.guild.id, "animotes.guild_opt_in") else: sql.update_guild(ctx.guild.id, emojishare=0) self.log.info("Guild {0.name} (ID: {0.id}) has opted out of emoji sharing.".format(ctx.guild)) await self.channel_logger.log_to_channel("Guild **{0.name}** (ID: `{0.id}`) has opted out of emoji sharing." .format(ctx.guild)) message = getlang(ctx.guild.id, "animotes.guild_opt_out") await ctx.send(message)
async def setprefix(self, ctx, prefix): if not ctx.author.permissions_in(ctx.channel).administrator: raise NoPermission if len(prefix) > 10: raise commands.BadArgument sql.update_guild(ctx.guild.id, prefix=prefix) await ctx.send( getlang(ctx.guild.id, "configuration.prefix_set").format(prefix))
def add_localized_subcommands_to_page(self, max_width, commands): gid = self.context.guild.id for name, command in commands: if name in command.aliases: # skip aliases continue short_doc = getlang( gid, "brief." + command.qualified_name.replace(' ', '.')) entry = ' {0:<{width}} - {1}'.format(name, short_doc, width=max_width) shortened = self.shorten(entry) self._paginator.add_line(shortened)
async def on_ready(): log.info("Performing guild check...") if sql.get_version() is None: sql.init_version(VERSION) new_ver_alert = False else: new_ver_alert = sql.get_version() != VERSION and sql.get_version() is not None if new_ver_alert: sql.update_version(VERSION) for g in bot.guilds: log.info("Servicing guild '{0.name}' (ID: {0.id})".format(g)) row = sql.select_guild_by_id(g.id) if row is not None: prefix = row['prefix'] if row['prefix'] is not None else cfg.prefix if g.name != row['name']: await channel_logger.log_to_channel("Guild ID `{0.id}` changed name from **{1}** to **{0.name}** since " "last bot start".format(g, row['name'])) sql.update_guild(g.id, name=g.name) alert_channel_id = row['alert_channel'] if row['alert_channel'] is not None else 0 if new_ver_alert and alert_channel_id is not 0: alert_channel = next((x for x in g.channels if x.id == alert_channel_id), None) if alert_channel is not None: await alert_channel.send(getlang(g.id, "bot.alert_update").format(VERSION, prefix)) log.info("Sent update message to guild {0.name} (ID: {0.id})") else: log.info("Could not send update message to guild {0.name} (ID: {0.id}): " "Alert channel could not be found.") else: await channel_logger.log_to_channel("Joined guild **{0.name}** (ID: `{0.id}`) between sessions at `{1}`" .format(g, g.me.joined_at.isoformat(' '))) sql.add_guild(g.id, g.name, int(g.me.joined_at.timestamp())) await print_welcome_message(g) db_guilds = sql.get_all_guilds() if len(bot.guilds) != len(db_guilds): for g in db_guilds: if not any(x for x in bot.guilds if x.id == g['id']): log.info("Kicked from guild '{0}' (ID: {1}) between sessions".format(g['name'], g['id'])) await channel_logger.log_to_channel("Kicked from guild **{0}** (ID: `{1}`)".format(g['name'], g['id'])) sql.delete_guild(g['id']) for extension in extensions: try: bot.load_extension(extension) except Exception as e: log.error("Failed to load extension {}\n{}: {}".format(extension, type(e).__name__, e)) print("I am ready!") log.info('I am ready!') await channel_logger.log_to_channel("I am ready!")
async def quantize(ctx, att, palette): with io.BytesIO() as bio: await att.save(bio) template = Image.open(bio).convert('RGBA') log.debug("Dim: {0}x{1}".format(template.width, template.height)) bad_pixels = template.height * template.width for py in range(template.height): for px in range(template.width): pix = template.getpixel((px, py)) if pix[3] == 0: # Ignore fully transparent pixels bad_pixels -= 1 continue if pix[3] < 30: # Make barely visible pixels transparent template.putpixel((px, py), (0, 0, 0, 0)) continue dist = 450 best_fit = (0, 0, 0) for c in palette: if pix[:3] == c: # If pixel matches exactly, break best_fit = c if pix[3] == 255: # Pixel is only not bad if it's fully opaque bad_pixels -= 1 break tmp = sqrt( pow(pix[0] - c[0], 2) + pow(pix[1] - c[1], 2) + pow(pix[2] - c[2], 2)) if tmp < dist: dist = tmp best_fit = c template.putpixel((px, py), best_fit + (255, )) with io.BytesIO() as bio: template.save(bio, format="PNG") bio.seek(0) f = discord.File(bio, "template.png") await ctx.send(getlang(ctx.guild.id, "render.quantize").format(bad_pixels), file=f)
async def ping(ctx): ping_start = time() ping_msg = await ctx.send(getlang(ctx.guild.id, "bot.ping")) ping_time = time() - ping_start await ping_msg.edit(content=getlang(ctx.guild.id, "bot.pong").format(ping_time))
async def setdefaultcanvas(self, ctx): await ctx.send(getlang(ctx.guild.id, "bot.error.no_subcommand"))
async def version(ctx): await ctx.send(getlang(ctx.guild.id, "bot.version").format(VERSION))
async def alertchannel_clear(self, ctx): if not ctx.author.permissions_in(ctx.channel).administrator: raise NoPermission sql.update_guild(ctx.guild.id, alert_channel=0) await ctx.send( getlang(ctx.guild.id, "configuration.alert_channel_cleared"))
async def suggest(ctx, *, suggestion: str): await channel_logger.log_to_channel("New suggestion from **{0.name}#{0.discriminator}** (ID: `{0.id}`) in guild " "**{1.name}** (ID: `{1.id}`):".format(ctx.author, ctx.guild)) await channel_logger.log_to_channel("> `{}`".format(suggestion)) await ctx.send(getlang(ctx.guild.id, "bot.suggest"))
async def alertchannel(self, ctx): await ctx.send(getlang(ctx.guild.id, "bot.error.no_subcommand"))
async def ditherchart(ctx): await ctx.send(getlang(ctx.guild.id, "bot.error.no_subcommand"))
def get_localized_ending_note(self): gid = self.context.guild.id command_name = self.context.invoked_with return getlang(gid, "bot.help_ending_note").format(self.clean_prefix, command_name)
async def diff(self, ctx): await ctx.send(getlang(ctx.guild.id, "bot.error.no_subcommand"))
def format(self): self._paginator = Paginator() gid = self.context.guild.id if self.is_bot(): self._paginator.add_line(inspect.cleandoc( getlang(gid, "bot.description").format(cfg.name)), empty=True) elif self.is_cog(): # self._paginator.add_line(getlang(gid, )) pass # TODO: HELP!! elif isinstance(self.command, Command): self._paginator.add_line(getlang( gid, "brief." + self.command.qualified_name.replace(' ', '.')), empty=True) # TODO: Translate signatures # <signature portion> signature = self.get_command_signature() self._paginator.add_line(signature, empty=True) # <long doc> section long_doc = getlang( gid, "help." + self.command.qualified_name.replace(' ', '.')) if long_doc: self._paginator.add_line(inspect.cleandoc(long_doc), empty=True) # end it here if it's just a regular command if not self.has_subcommands(): self._paginator.close_page() return self._paginator.pages max_width = self.max_name_size def category(tup): cog = tup[1].cog_name # we insert the zero width space there to give it approximate # last place sorting position. return cog + ':' if cog is not None else '\u200bNo Category:' filtered = yield from self.filter_command_list() if self.is_bot(): data = sorted(filtered, key=category) for category, commands in itertools.groupby(data, key=category): # there simply is no prettier way of doing this. commands = sorted(commands) if len(commands) > 0: self._paginator.add_line(category) self.add_localized_subcommands_to_page(max_width, commands) else: filtered = sorted(filtered) if filtered: self._paginator.add_line('Commands:') self.add_localized_subcommands_to_page(max_width, filtered) # add the ending note self._paginator.add_line() ending_note = self.get_localized_ending_note() self._paginator.add_line(ending_note) return self._paginator.pages
async def repeat(self, ctx): async for msg in ctx.history(limit=50, before=ctx.message): log_msg = "repeated by {0.name}#{0.discriminator} (ID: {0.id}) in {1.name} (ID: {1.id})" \ .format(ctx.author, ctx.guild) regex = ctx.prefix \ + '(diff|preview) ((?:pixel(?:canvas|zio|zone))|pxlsspace)(?: (-?\d+), ?(-?\d+)/?\s?#?(\d+)?)' match = re.match(regex, msg.content) if match: cmd = match.group(1) sub_cmd = match.group(2) x = int(match.group(3)) y = int(match.group(4)) zoom = int(match.group(5)) if match.group(5) is not None else 1 if cmd == "diff" and len( msg.attachments ) > 0 and msg.attachments[0].filename[-4:].lower() == ".png": att = msg.attachments[0] if att.width is None or att.height is None: await ctx.send( getlang(ctx.guild.id, "bot.error.bad_png").format( sql.get_guild_prefix(ctx.guild.id), getlang(ctx.guild.id, "command.quantize"))) return zoom = max(1, min(zoom, 400 // att.width, 400 // att.height)) if sub_cmd == "pixelcanvas": log.debug("Pixelcanvas diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pixelcanvas, colors.pixelcanvas) return elif sub_cmd == "pixelzio": log.debug("Pixelzio diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pixelzio, colors.pixelzio) return elif sub_cmd == "pixelzone": log.debug("Pixelzone diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pixelzone, colors.pixelzone) return elif sub_cmd == "pxlsspace": log.debug("Pxlsspace diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pxlsspace, colors.pxlsspace) return if cmd == "preview": zoom = max(1, min(16, zoom)) if sub_cmd == "pixelcanvas": log.debug("Pixelcanvas preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelcanvas) return elif sub_cmd == "pixelzio": log.debug("Pixelzio preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelzio) return elif sub_cmd == "pixelzone": log.debug("Pixelzone preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelzone) return elif sub_cmd == "pxlsspace": log.debug("Pxlsspace preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pxlsspace) return default_canvas = sql.select_guild_by_id( ctx.guild.id)['default_canvas'] pc_match = re.search( '(?:pixelcanvas\.io/)@(-?\d+),(-?\d+)/?(?:\s?#?(\d+))?', msg.content) pzio_match = re.search( '(?:pixelz\.io/)@(-?\d+),(-?\d+)(?:\s?#?(\d+))?', msg.content) pzone_match = re.search( '(?:pixelzone\.io/)\?p=(-?\d+),(-?\d+)(?:,(\d+))?(?:\s?#?(\d+))?', msg.content) pxlsp_match = re.search( 'pxls\.space/#x=(\d+)&y=(\d+)(?:&scale=(\d+))?\s?#?(\d+)?', msg.content) prev_match = re.search('@\(?(-?\d+), ?(-?\d+)\)?(?: ?#(\d+))?', msg.content) diff_match = re.search('\(?(-?\d+), ?(-?\d+)\)?(?: ?#(\d+))?', msg.content) if pc_match is not None: x = int(pc_match.group(1)) y = int(pc_match.group(2)) zoom = int( pc_match.group(3)) if pc_match.group(3) is not None else 1 zoom = max(min(zoom, 16), 1) log.debug("Pixelcanvas preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelcanvas) return if pzio_match is not None: x = int(pzio_match.group(1)) y = int(pzio_match.group(2)) zoom = int(pzio_match.group(3)) if pzio_match.group( 3) is not None else 1 zoom = max(min(zoom, 16), 1) log.debug("Pixelzio preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelzio) return if pzone_match is not None: x = int(pzone_match.group(1)) y = int(pzone_match.group(2)) if pzone_match.group(4) is not None: zoom = int(pzone_match.group(4)) elif pzone_match.group(3) is not None: zoom = int(pzio_match.group(3)) else: zoom = 1 zoom = max(min(zoom, 16), 1) log.debug("Pixelzone preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelzone) return if pxlsp_match is not None: x = int(pxlsp_match.group(1)) y = int(pxlsp_match.group(2)) if pxlsp_match.group(4) is not None: zoom = int(pxlsp_match.group(4)) elif pxlsp_match.group(3) is not None: zoom = int(pxlsp_match.group(3)) else: zoom = 1 zoom = max(min(zoom, 16), 1) log.debug("Pxlsspace preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pxlsspace) return if prev_match is not None: x = int(prev_match.group(1)) y = int(prev_match.group(2)) zoom = int(prev_match.group(3)) if prev_match.group( 3) is not None else 1 zoom = max(min(zoom, 16), 1) if default_canvas == "pixelcanvas": log.debug("Pixelcanvas preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelcanvas) elif default_canvas == "pixelz": log.debug("Pixelzio preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelzio) elif default_canvas == "pixelzone": log.debug("Pixelzone preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pixelzone) elif default_canvas == "pxlsspace": log.debug("Pxlsspace preview " + log_msg) await render.preview(ctx, x, y, zoom, render.fetch_pxlsspace) return if diff_match is not None and len(msg.attachments) > 0 \ and msg.attachments[0].filename[-4:].lower() == ".png": att = msg.attachments[0] if att.width is None or att.height is None: await ctx.send( getlang(ctx.guild.id, "bot.error.bad_png").format( sql.get_guild_prefix(ctx.guild.id), getlang(ctx.guild.id, "command.quantize"))) return x = int(diff_match.group(1)) y = int(diff_match.group(2)) zoom = int(diff_match.group(3)) if diff_match.group( 3) is not None else 1 zoom = max(1, min(zoom, 400 // att.width, 400 // att.height)) if default_canvas == "pixelcanvas": log.debug("Pixelcanvas diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pixelcanvas, colors.pixelcanvas) elif default_canvas == "pixelz": log.debug("Pixelzio diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pixelzio, colors.pixelzio) elif default_canvas == "pixelzone": log.debug("Pixelzone diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pixelzone, colors.pixelzone) elif default_canvas == "pxlsspace": log.debug("Pxlsspace diff " + log_msg) await render.diff(ctx, x, y, att, zoom, render.fetch_pxlsspace, colors.pxlsspace) return ctx.send(getlang(ctx.guild.id, "render.repeat_not_found"))