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())
async def get_commit_messages(self, event_body, brief=False): embed_commits = [] branch = event_body['ref'].split('/', 2)[2] project = event_body['repository']['full_name'] commits = event_body['commits'] if brief and len(commits) > self.config['commit_truncation_limit']: first_hash = commits[0]['id'] last_hash = commits[-2]['id'] compare_url = f'https://github.com/{project}/compare/{first_hash}^...{last_hash}' embed = Embed( title= f'Skipped {len(commits) - 1} commits... (click link for diff)', colour=Colour(self._skipped_commit_colour), url=compare_url) embed_commits.append((embed, None)) commits = commits[-1:] for commit in commits: author_username = commit['author'].get('username', None) author_name = commit['author'].get('name', None) timestamp = dateutil.parser.parse(commit['timestamp']) commit_message = commit['message'].split('\n') embed = Embed(title=commit_message[0], colour=Colour(self._commit_colour), url=commit['url'], timestamp=timestamp) if len(commit_message) > 2 and not brief: commit_body = '\n'.join(commit_message[2:]) embed.description = commit_body[:4096] author = await self.get_author_info(author_username) if author: if author['name'] and author['name'] != author['login']: author_name = f'{author["name"]} ({author["login"]})' else: author_name = author['login'] embed.set_author(name=author_name, url=author['html_url'], icon_url=author['avatar_url']) elif author_name: embed.set_author(name=author_name) else: embed.set_author(name='<No Name>') embed.set_footer(text='Commit') embed.add_field(name='Repository', value=project, inline=True) embed.add_field(name='Branch', value=branch, inline=True) embed_commits.append((embed, commit['id'])) return embed_commits
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
def add_author_footer(embed: disnake.Embed, author: disnake.User, set_timestamp=True, additional_text: Iterable[str] = []): """ Adds footer to the embed with author name and icon from ctx. :param author: author info :param embed: disnake.Embed object :param set_timestamp: bool, should the embed's timestamp be set :param additional_text: Iterable of strings that will be joined with author name by pipe symbol, eg.: "john#2121 | text1 | text2". """ if set_timestamp: embed.timestamp = datetime.now(tz=timezone.utc) embed.set_footer(icon_url=author.display_avatar.url, text=' | '.join((str(author), *additional_text)))
async def fider(self): items = [] async with self.bot.session.get( 'https://ideas.obsproject.com/api/v1/posts?view=recent') as r: if r.status == 200: feed = await r.json() for entry in feed: if entry['id'] <= self.bot.state['fider_last_id']: continue items.append(entry) else: logger.warning( f'Fetching fider posts failed with status code {r.status}') return for item in reversed(items): if item['id'] > self.bot.state['fider_last_id']: self.bot.state['fider_last_id'] = item['id'] url = f'https://ideas.obsproject.com/posts/{item["id"]}/' logger.info(f'Got new Fider post: {url}') description = item['description'] if len(description) > 180: description = description[:180] + ' [...]' embed = Embed(title=f'{item["id"]}: {item["title"]}', colour=Colour(self._fider_colour), url=url, description=description, timestamp=dateutil.parser.parse(item['createdAt'])) embed.set_author( name='Fider', url='https://ideas.obsproject.com/', icon_url='https://cdn.rodney.io/stuff/obsbot/fider.png') embed.set_footer(text='New Idea on Fider') name = 'Anonymous' if not item['user']['name'] else item['user'][ 'name'] embed.add_field(name='Created By', value=name, inline=True) await self.fider_channel.send(embed=embed)
async def get_pr_messages(self, event_body): pr_number = event_body['number'] title = event_body['pull_request']['title'] timestamp = dateutil.parser.parse( event_body['pull_request']['created_at']) embed = Embed(title=f'#{pr_number}: {title}', colour=Colour(self._pull_request_colour), url=event_body['pull_request']['html_url'], timestamp=timestamp) author_name = event_body['pull_request']['user']['login'] author = await self.get_author_info(author_name) if author and author['name'] and author['name'] != author['login']: author_name = f'{author["name"]} ({author["login"]})' embed.set_author( name=author_name, url=event_body['pull_request']['user']['html_url'], icon_url=event_body['pull_request']['user']['avatar_url']) embed.set_footer(text='Pull Request') embed.add_field(name='Repository', value=event_body['repository']['full_name'], inline=True) # create copy without description text for brief channel brief_embed = embed.copy() # filter out comments in template event_body['pull_request']['body'] = '\n'.join( l.strip() for l in event_body['pull_request']['body'].splitlines() if not l.startswith('<!-')) # trim message to discord limits if len(event_body['pull_request']['body']) >= 2048: embed.description = event_body['pull_request'][ 'body'][:2000] + ' [... message trimmed]' else: embed.description = event_body['pull_request']['body'] return brief_embed, embed
async def get_issue_messages(self, event_body): issue_number = event_body['issue']['number'] title = event_body['issue']['title'] timestamp = dateutil.parser.parse(event_body['issue']['created_at']) embed = Embed(title=f'#{issue_number}: {title}', colour=Colour(self._issue_colour), url=event_body['issue']['html_url'], timestamp=timestamp) author_name = event_body['issue']['user']['login'] author = await self.get_author_info(author_name) if author and author['name'] and author['name'] != author['login']: author_name = f'{author["name"]} ({author["login"]})' embed.set_author(name=author_name, url=event_body['issue']['user']['html_url'], icon_url=event_body['issue']['user']['avatar_url']) embed.set_footer(text='Issue') embed.add_field(name='Repository', value=event_body['repository']['full_name'], inline=True) # create copy without description text for brief channel brief_embed = embed.copy() event_body['issue']['body'] = '\n'.join( l.strip() for l in event_body['issue']['body'].splitlines() if not l.startswith('<!-')) issue_text = event_body['issue']['body'] # strip double-newlines from issue forms if '\n\n' in issue_text: issue_text = issue_text.replace('\n\n', '\n') if len(issue_text) >= 2048: embed.description = issue_text[:2000] + ' [... message trimmed]' else: embed.description = issue_text return brief_embed, embed
async def get_discussion_messages(self, event_body): discussion_number = event_body['discussion']['number'] title = event_body['discussion']['title'] category = event_body['discussion']['category']['name'] timestamp = dateutil.parser.parse( event_body['discussion']['created_at']) embed = Embed(title=f'#{discussion_number}: {category} - {title}', colour=Colour(self._discussion_colour), timestamp=timestamp, url=event_body['discussion']['html_url']) author_name = event_body['discussion']['user']['login'] author = await self.get_author_info(author_name) if author and author['name'] and author['name'] != author['login']: author_name = f'{author["name"]} ({author["login"]})' embed.set_author( name=author_name, url=event_body['discussion']['user']['html_url'], icon_url=event_body['discussion']['user']['avatar_url']) embed.set_footer(text='Discussion') embed.add_field(name='Repository', value=event_body['repository']['full_name'], inline=True) # create copy without description text for brief channel brief_embed = embed.copy() event_body['discussion']['body'] = '\n'.join( l.strip() for l in event_body['discussion']['body'].splitlines() if not l.startswith('<!-')) if len(event_body['discussion']['body']) >= 1024: embed.description = event_body['discussion'][ 'body'][:1024] + ' [... message trimmed]' else: embed.description = event_body['discussion']['body'] return brief_embed, embed
async def get_wiki_message(self, event_body): embed = Embed(colour=Colour(self._wiki_colour)) embed.set_footer(text='GitHub Wiki Changes') # All edits in the response are from a single author author_name = event_body['sender']['login'] author = await self.get_author_info(author_name) if author and author['name'] and author['name'] != author['login']: author_name = f'{author["name"]} ({author["login"]})' embed.set_author(name=author_name, url=event_body['sender']['html_url'], icon_url=event_body['sender']['avatar_url']) embed.add_field(name='Repository', value=event_body['repository']['full_name']) body = [] for page in event_body['pages']: diff_url = f'{page["html_url"]}/_compare/{page["sha"]}^...{page["sha"]}' page_url = f'{page["html_url"]}/{page["sha"]}' body.append( f'**{page["action"]}:** [{page["title"]}]({page_url}) [[diff]({diff_url})]' ) embed.description = '\n'.join(body) return embed
async def on_message(self, msg: Message): if not self.filtering_enabled: return # check if channel is in private (these are ignored) if self.bot.is_private(msg.channel): return if self.bot.is_supporter(msg.author): return # go through bannable rules first, then kickable, then just delete for name, regex in self.sorted_filters: m = regex.search(msg.content) if m: break else: # no filter match return try: await msg.delete() deleted = 'Yes' self.bot.state['mod_faster'] += 1 except Exception as e: deleted = f'No, failed with error: {e!r}' finally: self.bot.state['mod_deletes'] += 1 if not self.bot.state['mod_first_delete']: self.bot.state['mod_first_delete'] = time.time() embed = Embed( colour=0xC90000, # title='Message Filter Match', description=f'**Message by** {msg.author.mention} **in** ' f'{msg.channel.mention} **matched filter:**\n' f'```\n{msg.content}\n```') embed.set_footer(text=f'Message ID: {msg.id}') embed.add_field(name='Filter name', value=f'`{name}`', inline=True) embed.add_field(name='Filter regex', value=f'`{regex.pattern}`', inline=True) embed.add_field(name='Regex match', value=f'`{m.group()}`', inline=True) embed.add_field(name='Message deleted?', value=deleted) if name in self.bannable: try: await msg.author.ban(delete_message_days=1, reason=f'Filter rule "{name}" matched.') embed.add_field(name='User banned?', value='Yes') self.bot.state['mod_bans'] += 1 if not self.bot.state['mod_first_ban']: self.bot.state['mod_first_ban'] = time.time() except Exception as e: logger.warning(f'Banning user {msg.author} failed: {e!r}') embed.add_field(name='User banned?', value=f'No, failed with error: {e!r}') else: logger.info( f'Banned user {msg.author.id}; Message {msg.id} matched filter "{name}"' ) elif name in self.kickable: try: await msg.author.kick(reason=f'Filter rule "{name}" matched.') embed.add_field(name='User kicked?', value='Yes') self.bot.state['mod_kicks'] += 1 if not self.bot.state['mod_first_kick']: self.bot.state['mod_first_kick'] = time.time() except Exception as e: logger.warning(f'Banning user {msg.author} failed: {e!r}') embed.add_field(name='User kicked?', value=f'No, failed with error: {e!r}') else: logger.info( f'Kicked user {msg.author.id}; Message {msg.id} matched filter "{name}"' ) else: logger.info( f'Deleted message by {msg.author.id}; Message {msg.id} matched filter "{name}"' ) return await self.log_channel.send(embed=embed)
async def on_message(self, message: disnake.Message): if message.author.bot or message.webhook_id is not None: return if not hasattr(message.channel, "guild") or message.channel.guild is None: return me = message.guild.me if me is None: me = Utils.get_member(self.bot, message.guild, self.bot.user.id) permissions = message.channel.permissions_for(me) if me is None: return if not (permissions.read_messages and permissions.send_messages and permissions.embed_links): return role_list = Configuration.get_var(message.guild.id, "CUSTOM_COMMANDS", "ROLES") role_required = Configuration.get_var(message.guild.id, "CUSTOM_COMMANDS", "ROLE_REQUIRED") channel_list = Configuration.get_var(message.guild.id, "CUSTOM_COMMANDS", "CHANNELS") channels_ignored = Configuration.get_var(message.guild.id, "CUSTOM_COMMANDS", "CHANNELS_IGNORED") mod_bypass = Configuration.get_var(message.guild.id, "CUSTOM_COMMANDS", "MOD_BYPASS") is_mod = message.author is not None and Permissioncheckers.is_mod( message.author) if (message.channel.id in channel_list) is channels_ignored and not ( is_mod and mod_bypass): return has_role = False if message.author is not None and hasattr(message.author, "roles"): for role in message.author.roles: if role.id in role_list: has_role = True break if has_role is not role_required and not (is_mod and mod_bypass): return prefix = Configuration.get_var(message.guild.id, "GENERAL", "PREFIX") if message.content.startswith(prefix, 0) and message.guild.id in self.commands: for trigger in self.commands[message.guild.id]: if message.content.lower() == prefix + trigger or ( message.content.lower().startswith( trigger, len(prefix)) and message.content.lower()[len(prefix + trigger)] == " "): info = self.commands[message.guild.id][trigger] images = IMAGE_MATCHER.findall(info.content) image = None if len(images) == 1: image = images[0] description = info.content.replace(image, "") else: description = info.content embed = Embed(description=description) if info.created_by is not None: creator = await Utils.get_user(info.created_by) embed.set_footer( text= f"Created by {str(creator)} ({info.created_by})", icon_url=creator.avatar.url) if image is not None: embed.set_image(url=image) await message.channel.send(embed=embed) self.bot.custom_command_count += 1
image_url = urL_or_file(image) thumbnail_url = urL_or_file(thumbnail) files = [] if isinstance(image_url, str): embed.set_image(url=image_url) else: embed.set_image(file=image_url) files.append(image_url) if isinstance(thumbnail_url, str): embed.set_thumbnail(url=thumbnail_url) else: embed.set_thumbnail(file=thumbnail_url) files.append(thumbnail_url) embed.set_footer(text=split(footer)) wb.send( content=split(text), embed=embed, files=files, username=data.username, avatar_url=data.avatar_url, ) print(f"\nText and embed sent successfully.") time.sleep(1) delete_lines(11)
async def paginate( cls, lines: t.List[str], ctx: Context, embed: disnake.Embed, prefix: str = "", suffix: str = "", max_lines: t.Optional[int] = None, max_size: int = 500, scale_to_size: int = 2000, empty: bool = False, restrict_to_user: User = None, timeout: int = 300, footer_text: str = None, url: str = None, allow_empty_lines: bool = False, ) -> t.Optional[disnake.Message]: """ Use a paginator and set of reactions to provide pagination over a set of lines. When used, this will send a message using `ctx.send()` and apply the pagination reactions, to control the embed. Pagination will also be removed automatically if no reaction is added for `timeout` seconds. The interaction will be limited to `restrict_to_user` (ctx.author by default) or to any user with a moderation role. Example: >>> people = ["Guido van Rossum", "Linus Torvalds", "Gurkbot", "Bjarne Stroustrup"] >>> e = disnake.Embed() >>> e.set_author(name="Ain't these people just awesome?") >>> await LinePaginator.paginate(people, ctx, e) """ def event_check(reaction_: disnake.Reaction, user_: disnake.Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( # Conditions for a successful pagination: all(( # Reaction is on this message reaction_.message.id == message.id, # Reaction is one of the pagination emotes str(reaction_.emoji) in PAGINATION_EMOJI, # Reaction was not made by the Bot user_.id != ctx.bot.user.id, # The reaction was by a whitelisted user user_.id == restrict_to_user.id, ))) paginator = cls( prefix=prefix, suffix=suffix, max_size=max_size, max_lines=max_lines, scale_to_size=scale_to_size, ) current_page = 0 # If the `restrict_to_user` is empty then set it to the original message author. restrict_to_user = restrict_to_user or ctx.author if not lines: if not allow_empty_lines: logger.exception( "`Empty lines found, raising error as `allow_empty_lines` is `False`." ) raise EmptyPaginatorEmbed("No lines to paginate.") logger.debug( "Empty lines found, `allow_empty_lines` is `True`, adding 'nothing to display' as content." ) lines.append("(nothing to display)") for line in lines: try: paginator.add_line(line, empty=empty) except Exception: logger.exception(f"Failed to add line to paginator: '{line}'.") raise logger.debug(f"Paginator created with {len(paginator.pages)} pages.") # Set embed description to content of current page. embed.description = paginator.pages[current_page] if len(paginator.pages) <= 1: if footer_text: embed.set_footer(text=footer_text) if url: embed.url = url logger.debug("Less than two pages, skipping pagination.") await ctx.send(embed=embed) return else: if footer_text: embed.set_footer( text= f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})" ) else: embed.set_footer( text=f"Page {current_page + 1}/{len(paginator.pages)}") if url: embed.url = url message = await ctx.send(embed=embed) logger.debug("Adding emoji reactions to message...") for emoji in PAGINATION_EMOJI: # Add all the applicable emoji to the message await message.add_reaction(emoji) logger.debug("Successfully added all pagination emojis to message.") while True: try: reaction, user = await ctx.bot.wait_for("reaction_add", timeout=timeout, check=event_check) logger.trace(f"Got reaction: {reaction}.") except asyncio.TimeoutError: logger.debug("Timed out waiting for a reaction.") break # We're done, no reactions for the last 5 minutes if str(reaction.emoji) == DELETE_EMOJI: logger.debug("Got delete reaction.") await message.delete() return if reaction.emoji == FIRST_EMOJI: await message.remove_reaction(reaction.emoji, user) current_page = 0 logger.debug( f"Got first page reaction - changing to page 1/{len(paginator.pages)}." ) embed.description = paginator.pages[current_page] if footer_text: # Current page is zero index based. embed.set_footer( text= f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})" ) else: embed.set_footer( text=f"Page {current_page + 1}/{len(paginator.pages)}") await message.edit(embed=embed) if reaction.emoji == LAST_EMOJI: await message.remove_reaction(reaction.emoji, user) current_page = len(paginator.pages) - 1 logger.debug( f"Got last page reaction - changing to page {current_page + 1}/{len(paginator.pages)}" ) embed.description = paginator.pages[current_page] if footer_text: embed.set_footer( text= f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})" ) else: embed.set_footer( text=f"Page {current_page + 1}/{len(paginator.pages)}") await message.edit(embed=embed) if reaction.emoji == LEFT_EMOJI: await message.remove_reaction(reaction.emoji, user) if current_page <= 0: logger.debug( "Got previous page reaction while they are on the first page, ignoring." ) continue current_page -= 1 logger.debug( f"Got previous page reaction - changing to page {current_page + 1}/{len(paginator.pages)}" ) embed.description = paginator.pages[current_page] if footer_text: embed.set_footer( text= f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})" ) else: embed.set_footer( text=f"Page {current_page + 1}/{len(paginator.pages)}") await message.edit(embed=embed) if reaction.emoji == RIGHT_EMOJI: await message.remove_reaction(reaction.emoji, user) if current_page >= len(paginator.pages) - 1: logger.debug( "Got next page reaction while they are on the last page, ignoring." ) continue current_page += 1 logger.debug( f"Got next page reaction - changing to page {current_page + 1}/{len(paginator.pages)}" ) embed.description = paginator.pages[current_page] if footer_text: embed.set_footer( text= f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})" ) else: embed.set_footer( text=f"Page {current_page + 1}/{len(paginator.pages)}") await message.edit(embed=embed) logger.debug("Ending pagination and clearing reactions.") with suppress(disnake.NotFound): await message.clear_reactions()