async def multi_user_markov(self, ctx: Context, *users: discord.User): """Generate a markov chain based off a list of users messages. `users`: The list of users who's messages should be used to generate the markov chain. """ if len(set(users)) < 2: raise commands.BadArgument( "You need to specify at least two users.") is_nsfw = ctx.channel.is_nsfw() if ctx.guild is not None else False async with ctx.typing(): async with ctx.db as connection: coros = [] for user in users: if user == ctx.author: await OptInStatus.is_opted_in(connection, ctx) else: await OptInStatus.is_public(connection, ctx, user) coros.append( MessageLog.get_user_log(connection, user, is_nsfw)) query = ("mum", is_nsfw, 3) + tuple(user.id for user in users) model = await self.get_model(query, *coros, order=3) await self.send_markov(ctx, model, 3)
async def status_pie( self, ctx: Context, user: Optional[discord.User] = None, *, flags: StatusPieOptions, ): """Display a status pie. `user`: The user who's status log to look at, defaults to you. `show_totals`: Sets whether status percentages should be shown, defaults to True. """ user = cast(discord.User, user or ctx.author) if flags.num_days < MIN_DAYS: raise commands.BadArgument(f"You must display at least {MIN_DAYS} days.") async with ctx.typing(): async with ctx.db as connection: await OptInStatus.is_public(connection, ctx, user) data = await get_status_totals(connection, user, days=flags.num_days) if not data: raise commands.BadArgument( f'User "{user}" currently has no status log data, please try again later.' ) avatar_fp = BytesIO() await user.avatar.replace(format="png", size=IMAGE_SIZE // 2).save(avatar_fp) draw_call = partial(draw_status_pie, data, avatar_fp, show_totals=flags.show_totals) image = await self.bot.loop.run_in_executor(None, draw_call) await ctx.send(file=discord.File(image, f"{user.id}_status_{ctx.message.created_at}.png"))
async def status_log( self, ctx: Context, user: Optional[discord.User] = None, *, flags: StatusLogOptions, ): """Display a status log. `user`: The user who's status log to look at, defaults to you. `--labels`: Sets whether date and time labels should be shown, defaults to False. `--timezone`: The timezone offset to use in hours, defaults to the users set timezone or UTC+0. `--days`: The number of days to fetch status log data for. Defaults to 30. """ if not flags._square and not await commands.is_owner().predicate(ctx): raise commands.BadArgument("Only the bot owner can set this flag.") user = cast(discord.User, user or ctx.author) timezone_offset = flags.timezone if timezone_offset is not None and not -14 < timezone_offset < 14: raise commands.BadArgument("Invalid timezone offset passed.") async with ctx.db as connection: if timezone_offset is None: timezone = await TimeZones.get_timezone(connection, user) or datetime.timezone.utc else: timezone = datetime.timezone(datetime.timedelta(hours=timezone_offset)) if flags.num_days < MIN_DAYS: raise commands.BadArgument(f"You must display at least {MIN_DAYS} days.") async with ctx.typing(): await OptInStatus.is_public(connection, ctx, user) data = await get_status_log(connection, user, days=flags.num_days) if not data: raise commands.BadArgument( f'User "{user}" currently has no status log data, please try again later.' ) delta = (ctx.message.created_at - data[0].start).days days = max(min(flags.num_days, delta), MIN_DAYS) draw_call = partial( draw_status_log, data, timezone=timezone, show_labels=flags.show_labels, num_days=days, square=flags._square, ) image = await self.bot.loop.run_in_executor(None, draw_call) await ctx.send(file=discord.File(image, f"{user.id}_status_{ctx.message.created_at}.png"))
async def code_guild_markov(self, ctx: Context): """Generate a markov chain code block.""" async with ctx.typing(): async with ctx.db as connection: is_nsfw = ctx.channel.is_nsfw() if ctx.guild is not None else False query = ("cgm", is_nsfw, 2, ctx.guild.id) coro = MessageLog.get_guild_log(connection, ctx.guild, is_nsfw) model = await self.get_model(query, coro, order=2) await self.send_markov(ctx, model, 2, callable=make_code)
async def guild_markov(self, ctx: Context): """Generate a markov chain based off messages in the server.""" async with ctx.typing(): async with ctx.db as connection: is_nsfw = ctx.channel.is_nsfw() if ctx.guild is not None else False query = ("gm", is_nsfw, 3, ctx.guild.id) coro = MessageLog.get_guild_log(connection, ctx.guild, is_nsfw) model = await self.get_model(query, coro, order=3) await self.send_markov(ctx, model, 3)
async def seeded_guild_markov(self, ctx: Context, *, seed: str): """Generate a markov chain based off messages in the server which starts with a given seed. `seed`: The string to attempt to seed the markov chain with. """ async with ctx.typing(): async with ctx.db as connection: is_nsfw = ctx.channel.is_nsfw() if ctx.guild is not None else False query = ("gm", is_nsfw, 3, ctx.guild.id) coro = MessageLog.get_guild_log(connection, ctx.guild, is_nsfw, False) model = await self.get_model(query, coro, order=3) await self.send_markov(ctx, model, 3, seed=seed.lower())
async def code_user_markov(self, ctx: Context, user: Optional[discord.User] = None): """Generate a markov chain code block.""" user = cast(discord.User, user or ctx.author) async with ctx.typing(): async with ctx.db as connection: await OptInStatus.is_public(connection, ctx, user) is_nsfw = ctx.channel.is_nsfw() if ctx.guild is not None else False query = ("cum", is_nsfw, 2, user.id) coro = MessageLog.get_user_log(connection, user, is_nsfw) model = await self.get_model(query, coro, order=2) await self.send_markov(ctx, model, 2, callable=make_code)
async def user_markov(self, ctx: Context, *, user: Optional[discord.User] = None): """Generate a markov chain based off a users messages. `user`: The user who's messages should be used to generate the markov chain, defaults to you. """ user = cast(discord.User, user or ctx.author) async with ctx.typing(): async with ctx.db as connection: await OptInStatus.is_public(connection, ctx, user) is_nsfw = ctx.channel.is_nsfw() if ctx.guild is not None else False query = ("um", is_nsfw, 2, user.id) coro = MessageLog.get_user_log(connection, user, is_nsfw) model = await self.get_model(query, coro, order=2) await self.send_markov(ctx, model, 2)
async def gameboy(self, ctx: Context, *, game_name: str = "red") -> None: # type: ignore if self.game is not None: raise commands.BadArgument("You are already playing!") game = ROOT_DIR / f"{game_name}.gb" if not game.exists(): game = ROOT_DIR / f"{game_name}.gbc" if not game.exists(): raise commands.BadArgument(f"{game_name} is not a game!") async with ctx.typing(): self.game = GameBoyView(self, str(game), ctx.author) image_url = await self.game.render() await ctx.send(embed=self.game.embed.set_image(url=image_url), view=self.game) # type: ignore
async def tags(self, ctx: Context, *, options: TagOptions): def check(message: discord.Message): if message.channel != ctx.channel or message.author != CONFIG.R_DANNY: return False if len(message.attachments ) != 1 or message.attachments[0].filename != "tags.txt": return False return True try: await ctx.send( f"{PAUSE_BUTTON} Use `@{CONFIG.R_DANNY.name}#{CONFIG.R_DANNY.discriminator} tag all --text` to retrieve the tag list.", delete_after=15, ) message: discord.Message = await ctx.bot.wait_for("message", check=check, timeout=30) prefix = message.content[:-14] except asyncio.TimeoutError: return await ctx.send(f"{LOADING_BUTTON} Processing...", delete_after=15) async with ctx.typing(): contents = await message.attachments[0].read() contents = contents.decode("utf-8") sep, contents = contents.split("\n", 1) key, contents = contents.split("\n", 1) all_tags: dict[str, Tag] = {} tag_owners: dict[int, list[str]] = defaultdict(list) for match in TAG_FILE_REGEX.finditer(contents): tag, owner_id, uses, can_delete, is_alias = match.groups() tag = tag.rstrip() owner_id = int(owner_id) uses = int(uses) can_delete = can_delete == "True" is_alias = is_alias == "True" all_tags[tag] = Tag(tag, owner_id, uses, can_delete, is_alias, match.group()) tag_owners[owner_id].append(tag) orphaned_tags: list[str] = [] for user_id, tags in tag_owners.items(): if ctx.guild.get_member(user_id) is None: orphaned_tags.extend(tags) tag_file = io.BytesIO() if options.claim: for tag in sorted(orphaned_tags, key=lambda t: all_tags[t].uses, reverse=True): tag_file.write( (f"{prefix}tag claim {tag}\n").encode("utf-8")) else: tag_file.write((sep + "\n").encode("utf-8")) tag_file.write((key + "\n").encode("utf-8")) tag_file.write((sep + "\n").encode("utf-8")) for tag in sorted(orphaned_tags, key=lambda t: all_tags[t].uses, reverse=True): tag_file.write( (all_tags[tag].match + "\n").encode("utf-8")) tag_file.write((sep + "\n").encode("utf-8")) tag_file.seek(0) await ctx.send(file=discord.File(tag_file, "available_tags.txt"))