async def slash_factoid(self, ctx: ApplicationCommandInteraction, mention: Member = None): if not self.bot.is_supporter(ctx.author) and self.limiter.is_limited( ctx.data.id, ctx.channel_id): logger.debug( f'rate-limited (sc): "{ctx.author}", channel: "{ctx.channel}", factoid: "{ctx.data.name}"' ) return logger.info( f'factoid requested (sc) by: "{ctx.author}", channel: "{ctx.channel}", factoid: "{ctx.data.name}"' ) await self.increment_uses(ctx.data.name) message = self.resolve_variables( self.factoids[ctx.data.name]['message']) embed = None if self.factoids[ctx.data.name]['embed']: embed = Embed(colour=self._factoids_colour, description=message) message = '' if self.factoids[ctx.data.name]['image_url']: embed.set_image(url=self.factoids[ctx.data.name]['image_url']) if mention and isinstance(mention, Member): return await ctx.send(content=f'{mention.mention} {message}', embed=embed) else: return await ctx.send(content=message, embed=embed)
def vip(self, streamer: Streamer, info, mod_action: ModAction, embed: disnake.Embed) -> disnake.Embed: embed = self.set_user_attrs(streamer, info, mod_action, embed) embed.title = embed.title.replace('Vip', 'VIP') #Capitalize VIP for the looks embed.colour = self.colour.green return True, embed
async def sub_unsub_group_helper( self, ctx: Context, func: Callable, action: str ) -> None: """ Helper function for subscribe_group and unsubscribe_group. If `func` is `apply_role`, `role_lst` stores the role(s) which are added to the user If `func` is `remove_role`, `role_lst` stores the role(s) which are removed from the user """ roles = await self.get_roles(ctx) role_lst = [ role.name for role in roles if await func(ctx, role) ] # Applies/Removes the role(s) and stores a list of applied/removed role(s) if role_lst: msg = ( f"{', '.join(role_lst[:-1])} and {role_lst[-1]}" if len(role_lst) > 1 else role_lst[0] ) # Stores a string which tells what role(s) is/are added to or removed from the user embed = Embed( title=f"{Emojis.confirmation_emoji} {action}", description=f"You've {action.lower()} to {ctx.guild}'s {msg}.", color=Colours.green, ) else: embed = Embed( title=f"{Emojis.warning_emoji} Already {action.lower()}", description=f"You're already {action.lower()} to {ctx.guild}'s announcements and polls.", color=Colours.soft_red, ) await ctx.send(content=ctx.author.mention, embed=embed)
async def gurkan_count(self, ctx: Context) -> None: """ Goes through a list of all the members and uses regex to check if the member is a gurkan. Sends the count of total Gurkans in the server,\ and the percentage of the gurkans to the server members. """ members = ctx.guild.members gurkans = sum(gurkan_check(member.display_name) for member in members) rate = round((gurkans / len(members)) * 100) count_emb = Embed() if rate == 100: title = f"Whoa!! All {gurkans} members are gurkans!" color = Colours.green elif rate == 0: title = "No one is a gurkan?! That's lame." color = Colours.soft_red else: rate_m = [RATE_DICT[r] for r in RATE_DICT if rate in r][0] title = f"{Emojis.cucumber_emoji} {gurkans} members" color = Colours.green description = f"About {rate}% ({gurkans}/ {len(members)}) of members are gurkans, that's {rate_m}" count_emb.title = title count_emb.color = color count_emb.description = description await ctx.send(embed=count_emb)
def gen_emoji_page(self, guild: disnake.Guild, page): se = sorted(guild.emojis, key=lambda e: e.name) embed = Embed(color=0x2db1f3) embed.set_author(name=Translator.translate('emoji_server', guild, server=guild.name, page=page + 1, pages=len(guild.emojis) + 1), url=guild.icon.url) if page == 0: for chunk in Utils.chunks(se, 18): embed.add_field(name="\u200b", value=" ".join(str(e) for e in chunk)) animated = set() static = set() for e in guild.emojis: (animated if e.animated else static).add(str(e)) max_emoji = guild.emoji_limit embed.add_field(name=Translator.translate('static_emoji', guild), value=f"{len(static)} / {max_emoji}") embed.add_field(name=Translator.translate('animated_emoji', guild), value=f"{len(animated)} / {max_emoji}") else: self.add_emoji_info(guild, embed, se[page - 1]) return embed
async def is_gurkan(self, ctx: Context, *, user: Optional[Union[Member, str]]) -> None: """ The gurkanrate of the user and whether the user is a gurkan is sent in an embed,\ the color depending on how high the rate is. Can be used on other members, or even text. """ if not isinstance(user, str): user = user.display_name if user else ctx.author.display_name gurk_state = gurkan_check(user) gurk_rate = gurkan_rate(user) rate_embed = Embed(description=f"{user}'s gurk rate is {gurk_rate}%") if not gurk_state: color = Colours.soft_red title = f"{Emojis.invalid_emoji} Not gurkan" else: color = Colours.green title = f"{Emojis.cucumber_emoji} Gurkan" rate_embed.color = color rate_embed.title = title await ctx.send(embed=rate_embed)
async def post_formatted_message( self, actor: User, action: str, *, body: Optional[str] = None, link: Optional[str] = None, colour: int = Colours.green, ) -> None: """Format and post a message to the #log channel.""" logger.trace(f'Creating log "{actor.id} {action}"') embed = Embed( title=( f"{actor} " f"{f'({actor.display_name}) ' if actor.display_name != actor.name else ''}" f"({actor.id}) {action}" ), description=body or "<no additional information provided>", colour=colour, timestamp=datetime.utcnow(), ).set_thumbnail(url=actor.display_avatar.url) if link: embed.url = link await self.post_message(embed=embed)
async def charinfo(self, ctx: Context, *, characters: str) -> None: """Shows you information about up to 50 unicode characters.""" match = re.match(r"<(a?):(\w+):(\d+)>", characters) if match: raise BadArgument( "Only unicode characters can be processed, but a custom Discord emoji " "was found. Please remove it and try again.", ) if len(characters) > 50: raise BadArgument(f"Too many characters ({len(characters)}/50)") def get_info(char: str) -> Tuple[str, str]: digit = f"{ord(char):x}" if len(digit) <= 4: u_code = f"\\u{digit:>04}" else: u_code = f"\\U{digit:>08}" url = f"https://www.compart.com/en/unicode/U+{digit:>04}" name = f"[{unicodedata.name(char, '')}]({url})" info = f"`{u_code.ljust(10)}`: {name} - {utils.escape_markdown(char)}" return info, u_code char_list, raw_list = zip(*(get_info(c) for c in characters)) embed = Embed(title="Character info", colour=Colours.green) if len(characters) > 1: # Maximum length possible is 502 out of 1024, so there's no need to truncate. embed.add_field( name="Full Raw Text", value=f"`{''.join(raw_list)}`", inline=False ) await LinePaginator.paginate( char_list, ctx, embed, max_lines=10, max_size=2000, empty=False )
async def append_reminder(self, timestamp: datetime, ctx: Context, content: str) -> None: """Add reminder to database and schedule it.""" sql = ( "INSERT INTO reminders(jump_url, user_id, channel_id, end_time, content) " "VALUES ($1, $2, $3, $4, $5)RETURNING reminder_id") async with self.bot.db_pool.acquire() as connection: reminder_id = await connection.fetchval( sql, ctx.message.jump_url, ctx.author.id, ctx.channel.id, timestamp, content, ) embed = Embed( title=":white_check_mark: Reminder set", color=Colours.green, description=REMINDER_DESCRIPTION.format( arrive_in=humanize.precisedelta(timestamp - datetime.utcnow(), format="%0.0f"), ), ) embed.set_footer(text=f"ID: {reminder_id}") await ctx.send(embed=embed) self.reminders[reminder_id] = { "reminder_id": reminder_id, "jump_url": ctx.message.jump_url, "user_id": ctx.author.id, "channel_id": ctx.channel.id, "end_time": timestamp, "content": content, } await self.schedule_reminder(self.get_recent_reminder())
def raid(self, streamer: Streamer, info, mod_action: ModAction, embed: disnake.Embed) -> disnake.Embed: embed = self.set_chatroom_attrs(mod_action, embed) embed.add_field( name="Raided Channel", value= f"[{info['args'][0]}](<https://www.twitch.tv/{info['args'][0]}>)", inline=True) return embed
async def unused(self, ctx: Context): embed = Embed(title='Unused Factoids') description = [] for fac in sorted(self.factoids.values(), key=lambda a: a['uses']): if fac['uses'] > 0: break description.append(f'- {fac["name"]}') embed.description = '```{}```'.format('\n'.join(description)) return await ctx.send(embed=embed)
async def startup_greeting(self) -> None: """Announce presence to the devlog channel.""" embed = Embed(description="Connected!") embed.set_author( name="Gurkbot", url=constants.BOT_REPO_URL, icon_url=self.user.display_avatar.url, ) await self.get_channel(constants.Channels.devlog).send(embed=embed)
def followers(self, streamer: Streamer, info, mod_action: ModAction, embed: disnake.Embed) -> disnake.Embed: embed = self.set_chatroom_attrs(mod_action, embed) embed.add_field( name= f"Time Needed to be Following (minute{'' if int(info['args'][0]) == 1 else 's'})", value=f"`{info['args'][0]}`", inline=True) return embed
def slow(self, streamer: Streamer, info, mod_action: ModAction, embed: disnake.Embed) -> disnake.Embed: embed = self.set_chatroom_attrs(mod_action, embed) embed.add_field( name= f"Slow Amount (second{'' if int(info['args'][0]) == 1 else 's'})", value=f"`{info['args'][0]}`", inline=True) return embed
def set_appeals_attrs(self, streamer: Streamer, info, mod_action: ModAction, embed: disnake.Embed) -> disnake.Embed: self.set_user_attrs(streamer, info, mod_action, embed) embed.add_field( name="Moderator Reason", value= f"`{info['moderator_message'] if info['moderator_message'] != '' else 'None Provided'}`", inline=False) return embed
def embed_helper(self, description: str, field: str) -> Embed: """Embed helper function.""" embed = Embed(title="Eval Results", colour=self.GREEN, description=description) embed.add_field( name="Output", value=field, ) return embed
def delete(self, streamer: Streamer, info, mod_action: ModAction, embed: disnake.Embed) -> disnake.Embed: embed = self.set_user_attrs(streamer, info, mod_action, embed) if "`" in info['args'][1]: embed.add_field(name="Message", value=f"```{info['args'][1]}```") else: embed.add_field(name="Message", value=f"`{info['args'][1]}`") # embed.add_field( # name="Message ID", value=f"`{info['args'][2]}`") return embed
async def _send_paginated_embed(ctx: Context, lines: Iterable[str], title: str) -> None: """Send paginated embed.""" embed = Embed() embed.set_author(name=title) await LinePaginator.paginate( [f"{i}. {line}" for i, line in enumerate(lines, start=1)], ctx, embed, allow_empty_lines=True, )
async def bottom(self, ctx: Context): embed = Embed(title='Least used Factoids') description = [ 'Pos - Factoid (uses)', '--------------------------------' ] for pos, fac in enumerate(sorted(self.factoids.values(), key=lambda a: a['uses'])[:10], start=1): description.append(f'{pos:2d}. - {fac["name"]} ({fac["uses"]})') embed.description = '```{}```'.format('\n'.join(description)) return await ctx.send(embed=embed)
def set_user_attrs(self, streamer: Streamer, info, mod_action: ModAction, embed: disnake.Embed) -> disnake.Embed: user = info["target_user_login"] or info['args'][0] user_escaped = user.lower().replace('_', '\_') embed.title = f"Mod {mod_action.value.replace('_', ' ').title()} Action" #embed.description=f"[Review Viewercard for User](<https://www.twitch.tv/popout/{streamer.username}/viewercard/{user.lower()}>)" embed.color = self.colour.red embed.add_field( name="Flagged Account", value= f"[{user_escaped}](<https://www.twitch.tv/popout/{streamer.username}/viewercard/{user_escaped}>)", inline=True) return embed
def add_author_footer(embed: discord.Embed, author: Union[discord.User, discord.Member], set_timestamp=True, additional_text: Union[Iterable[str], None] = None): if set_timestamp: embed.timestamp = datetime.now(tz=timezone.utc) if additional_text is not None: embed.set_footer(icon_url=author.display_avatar.url, text=' | '.join((str(author), *additional_text))) else: embed.set_footer(icon_url=author.display_avatar.url, text=str(author)) return embed
async def listfilters(self, ctx: Context): if not self.bot.is_admin(ctx.author): return if not self.bot.is_private(ctx.channel): return _ban_filters = [] _kick_filters = [] _delete_filters = [] for name, regex in sorted(self.filters.items()): if name in self.bannable: _ban_filters.append(f'* "{name}" - `{regex.pattern}`') elif name in self.kickable: _kick_filters.append(f'* "{name}" - `{regex.pattern}`') else: _delete_filters.append(f'* "{name}" - `{regex.pattern}`') embed = Embed(title='Registered Message Filters') if _ban_filters: embed.add_field(name='Ban Filters', inline=False, value='```\n{}\n```'.format( '\n'.join(_ban_filters))) if _kick_filters: embed.add_field(name='Kick Filters', inline=False, value='```\n{}\n```'.format( '\n'.join(_kick_filters))) if _delete_filters: embed.add_field(name='Delete Filters', inline=False, value='```\n{}\n```'.format( '\n'.join(_delete_filters))) return await ctx.send(embed=embed)
async def status(self, ctx: Context): if not self.bot.is_admin(ctx.author): return embed = Embed(title='OBS Bot Status') embed.add_field( name='Core', inline=False, value=( f'Version: {__version__} - "{__codename__}" Edition\n' f'Uptime: {time.time() - self.bot.start_time:.0f} seconds\n')) mentions = ', '.join(u.mention for u in (self.bot.get_user(_id) for _id in self.bot.admins) if u) embed.add_field(name='Bot admins', inline=False, value=mentions) # get information from other Cogs if possible if fac := self.bot.get_cog('Factoids'): total_uses = sum(i['uses'] for i in fac.factoids.values()) embed.add_field( name='Factoid module', inline=False, value=(f'Factoids: {len(fac.factoids)}\n' f'Aliases: {len(fac.alias_map)}\n' f'Total uses: {total_uses} (since 2018-06-07)'))
async def ask(ctx, text, options, timeout=60): embed = Embed(color=0x68a910, description='\n'.join(f"{option.emoji} {option.text}" for option in options)) message = await ctx.send(text, embed=embed) handlers = dict() for option in options: await message.add_reaction(option.emoji) handlers[str(option.emoji)] = option.handler def check(reaction): return reaction.user_id == ctx.message.author.id and str( reaction.emoji) in handlers.keys( ) and reaction.message_id == message.id try: reaction = await ctx.bot.wait_for('raw_reaction_add', timeout=timeout, check=check) except asyncio.TimeoutError: await MessageUtils.send_to(ctx, "NO", "confirmation_timeout", timeout=30) return else: await handlers[str(reaction.emoji)]() finally: try: await message.delete() except NotFound: pass
async def emoji_roles_remove(self, ctx, emote: disnake.Emoji, roles: Greedy[disnake.Role]): if roles is None: return MessageUtils.send_to(ctx, 'NO', 'roles_no_roles') todo = set() refused = set() for role in roles: (refused if role not in emote.roles else todo).add(role) new_roles = list(emote.roles) for role in todo: new_roles.remove(role) await emote.edit(name=emote.name, roles=new_roles) await asyncio.sleep(1) # sleep so the cache can update embed = Embed(color=0x2db1f3) self.add_emoji_info(ctx, embed, emote) message = MessageUtils.assemble(ctx, "YES", "emoji_roles_remove_success", roles=self.pretty_role_list(todo, ctx)) if len(refused) > 0: message += "\n" + MessageUtils.assemble( ctx, "NO", "emoji_roles_remove_role_not_in_list", roles=self.pretty_role_list(refused, ctx)) await ctx.send(message)
async def on_message(self, message: Message) -> None: """Log DM messages to #dm-logs.""" # If the guild attribute is set it isn't a DM if message.guild: return # Outbound messages shouldn't be logged if message.author.id == self.bot.user.id: return if not self.dm_log_channel: await self.bot.wait_until_ready() self.dm_log_channel = await self.bot.fetch_channel(Channels.dm_log) if not self.dm_log_channel: logger.error( f"Failed to get the #dm-log channel with ID {Channels.dm_log}." ) return await self.dm_log_channel.send( embed=Embed( title=f"Direct message from {message.author}", description=message.content, colour=Colours.green, timestamp=datetime.utcnow(), ).set_thumbnail(url=message.author.display_avatar.url) )
async def create_self_roles(bot, ctx): # create and send pages = gen_role_pages(ctx.guild) embed = Embed(title=Translator.translate("assignable_roles", ctx, server_name=ctx.guild.name, page_num=1, page_count=len(pages)), colour=Colour(0xbffdd), description=pages[0]) message = await ctx.send(embed=embed) # track in redis pipe = bot.redis_pool.pipeline() pipe.sadd(f"self_role:{ctx.guild.id}", message.id) pipe.expire(f"self_role:{ctx.guild.id}", 60 * 60 * 24 * 7) bot.loop.create_task( ReactionManager.register(bot, message.id, ctx.channel.id, "self_role", duration=60 * 60 * 24 * 7, pipe=pipe)) bot.loop.create_task(update_reactions(message, pages[0], len(pages) > 1)) # cleanup bot.loop.create_task(self_cleaner(bot, ctx.guild.id))
async def fetch_xkcd_comics(self, ctx: Context, comic: Optional[str]) -> None: """ Getting an xkcd comic's information along with the image. To get a random comic, don't type any number as an argument. To get the latest, type 'latest'. """ embed = Embed(title=f"XKCD comic '{comic}'") embed.colour = Colours.soft_red if comic and (comic := re.match(COMIC_FORMAT, comic)) is None: embed.description = ( "Comic parameter should either be an integer or 'latest'.") await ctx.send(embed=embed) return
async def ping(self, ctx: commands.Context) -> None: """Ping the bot to see its latency.""" embed = Embed( title="Pong!", description=f"Gateway Latency: {round(self.bot.latency * 1000)}ms", color=Colours.green, ) await ctx.send(content=ctx.author.mention, embed=embed)
async def handle_unexpected_error(self, ctx: commands.Context, error: commands.CommandError) -> None: """Send a generic error message in `ctx` and log the exception as an error with exc_info.""" await ctx.send( f"Sorry, an unexpected error occurred. Please let us know!\n\n" f"```{error.__class__.__name__}: {error}```") push_alert = Embed( title="An unexpected error occurred", color=Colours.soft_red, ) push_alert.add_field( name="User", value=f"id: {ctx.author.id} | username: {ctx.author.mention}", inline=False, ) push_alert.add_field(name="Command", value=ctx.command.qualified_name, inline=False) push_alert.add_field( name="Message & Channel", value= f"Message: [{ctx.message.id}]({ctx.message.jump_url}) | Channel: <#{ctx.channel.id}>", inline=False, ) push_alert.add_field(name="Full Message", value=ctx.message.content, inline=False) dev_alerts = self.bot.get_channel(Channels.devalerts) if dev_alerts is None: logger.info( f"Fetching dev-alerts channel as it wasn't found in the cache (ID: {Channels.devalerts})" ) try: dev_alerts = await self.bot.fetch_channel(Channels.devalerts) except disnake.HTTPException as discord_exc: logger.exception("Fetch failed", exc_info=discord_exc) return # Trigger the logger before trying to use Discord in case that's the issue logger.error( f"Error executing command invoked by {ctx.message.author}: {ctx.message.content}", exc_info=error, ) await dev_alerts.send(embed=push_alert)