def __init__(self, *, auto_truncate=False, repeat_header=True, repeat_footer=False, repeat_image=True, title=EmptyEmbed, description=EmptyEmbed, **kwargs): kwargs = kwargs.copy() self.auto_truncate = auto_truncate self._embeds = [] self._field_cache = [] self.template = None self.repeat_header = repeat_header self.repeat_footer = repeat_footer self.repeat_image = repeat_image self.cur_num_fields = 0 if title is not EmptyEmbed and len(title) > Limits.EMBED_TITLE: if not self.auto_truncate: raise ValueError("Title too long") title = natural_truncate(title, maxlen=Limits.EMBED_TITLE) if description is not EmptyEmbed and len(description) > Limits.EMBED_DESC: if not self.auto_truncate: raise ValueError("Description too long") description = natural_truncate(description, maxlen=Limits.EMBED_DESC) self.template = Embed(title=title, description=description, **kwargs) self._cur_embed = None
async def send_spotlight_info(self, destination: discord.Object, app: SpotlightApp) -> None: """ Sends a discord.Embed object containing human-readable formatted spotlight_data to the given destination. :param destination: The destination as a Discord object (often a :cls:`discord.Channel`) :param app: the array of spotlight data to send :return: None, or a :cls:`discord.HTTPException` class if sending fails (this is already logged and communicated over Discord, provided for informational purposes/further handling) """ index = self.applications.index(app) + 1 logger.info("Displaying spotlight data for: {!s}".format(app)) try: s_user_id = extract_user_id(app.user_id) except discord.InvalidArgument: author_value = "{} (invalid ID)".format(app.user_name_only) else: author_value = "{} ({})".format(user_mention(s_user_id), app.user_name_only) em = discord.Embed(color=0x80AAFF) em.add_field(name="Project Name", value=app.project, inline=True) em.add_field(name="Author", value=author_value, inline=True) # if app.is_filled(app.user_reddit): # em.add_field(name="Reddit", value="/u/" + app.user_reddit[:128], inline=True) em.add_field(name="Elevator Pitch", value=natural_truncate(app.pitch, Limits.EMBED_FIELD_VALUE) or "None", inline=False) if app.is_filled(app.mature): em.add_field(name="Mature & Controversial Issues", value=natural_truncate(app.mature, Limits.EMBED_FIELD_VALUE), inline=False) em.add_field(name="Keywords", value=natural_truncate(app.keywords, Limits.EMBED_FIELD_VALUE) or "None", inline=False) if app.is_filled(app.art_url): em.add_field(name="Project Art", value=natural_truncate(app.art_url, Limits.EMBED_FIELD_VALUE), inline=True) if app.is_filled(app.additional_info_url): em.add_field(name="Additional Content", value=natural_truncate(app.additional_info_url, Limits.EMBED_FIELD_VALUE), inline=True) await self.bot.send_message(destination, embed=em) await self.bot.say("Spotlight ID #{:d}: {!s}".format(index, app))
async def list(self, ctx: commands.Context): """!kazhelp brief: List all configured sticky messages. description: | List all configured sticky messages. """ sorted_data = sorted(self.cog_state.messages.values(), key=lambda v: v.channel.name) if sorted_data: es = EmbedSplitter(title="Channel Sticky Messages") else: es = EmbedSplitter(title="Channel Sticky Messages", description="None.") for data in sorted_data: try: jump_url = ' *([link]({}))*'.format( get_jump_url(await data.get_posted_message(self.bot))) except (AttributeError, discord.NotFound): jump_url = '' delay_string = ' (delay: {})'.format(format_timedelta( data.delay)) if data.delay else '' es.add_field(name="#{}{}".format(data.channel.name, delay_string), value="{}{}".format( natural_truncate(data.message, 512), jump_url), inline=False) await self.send_message(ctx.message.channel, embed=es)
async def on_message(self, message: discord.Message): """ Message handler. Check all non-mod messages for filtered words. """ is_mod = check_role( self.config.discord.get("mod_roles", []) + self.config.discord.get("admin_roles", []), message) is_pm = isinstance(message.channel, discord.PrivateChannel) if not is_mod and not is_pm: message_string = str(message.content) del_match = self.engines['delete'].check_message(message_string) warn_match = self.engines['warn'].check_message(message_string) # logging if del_match or warn_match: if del_match: log_fmt = "Found filter match [auto-delete] '{1}' in {0}" else: # is_warn log_fmt = "Found filter match (auto-warn) '{2}' in {0}" logger.info( log_fmt.format(message_log_str(message), del_match, warn_match)) # delete if del_match: logger.debug("Deleting message") await self.bot.delete_message(message) # warn if del_match or warn_match: logger.debug("Preparing and sending filter warning") filter_type = 'delete' if del_match else 'warn' match_text = del_match if del_match else warn_match em = discord.Embed(color=self.match_warn_color[filter_type]) em.set_author(name=self.match_headings[filter_type]) em.add_field(name="User", value=message.author.mention, inline=True) em.add_field(name="Channel", value=message.channel.mention, inline=True) em.add_field(name="Timestamp", value=format_timestamp(message), inline=True) em.add_field(name="Match Text", value=match_text, inline=True) em.add_field(name="Message Link", value='[Message link]({})'.format( get_jump_url(message)), inline=True) em.add_field(name="Content", value=natural_truncate(message_string, Limits.EMBED_FIELD_VALUE), inline=False) await self.bot.send_message(self.channel_current, embed=em)
def _add_split_field(self, name, value, inline): """ Split a field into multiple fields if the value is too long. """ value_rem = value while value_rem: # add current field new_val = natural_truncate( value_rem, maxlen=Limits.EMBED_FIELD_VALUE, ellipsis_='' ) self._field_cache.append({'name': name, 'value': new_val, 'inline': inline}) # next iter value_rem = value_rem[len(new_val):] name = '…'
async def wikichannel(self, ctx: commands.Context, channel: discord.Channel, subreddit: str, page_name: str): """!kazhelp brief: Set or modify a wiki channel. description: | Set or change the wiki page that a channel mirrors. parameters: - name: channel type: channel name description: Discord channel to update. - name: subreddit type: string description: Name of subreddit of wiki page. - name: page_name type: string description: Name of wiki page. examples: - command: ".wikichannel #rules mysubreddit rules" description: set #rules """ logger.info("Configuring wikichannel for #{}...".format(channel.name)) sr = await self.reddit.subreddit(subreddit ) # type: reddit.models.Subreddit page = await sr.wiki.get_page(page_name ) # type: reddit.models.WikiPage page_preview = natural_truncate(page.content_md, 128) try: with self.cog_state as state: state.channels[channel].subreddit = subreddit state.channels[channel].wikipage = page_name state.channels[channel].last_revision = 0 state.channels[channel].channel = channel # don't overwrite messages - still want to delete discord messages on update logger.debug("Updated channel data.") except KeyError: with self.cog_state as state: state.channels[channel] = WikiChannelData(subreddit=subreddit, wikipage=page_name, last_revision=0, channel=channel, messages=tuple()) logger.debug( "Channel not previously configured: added channel data.") await self.send_message( ctx.message.channel, ctx.message.author.mention + " " + ("Configured channel {} to mirror wiki page /r/{}/wiki/{}. Use `.wikichannel refresh` " "to update the channel with the new page text. Contents preview:\n\n```{}```" ).format(channel.mention, subreddit, page_name, page_preview))
def add_field_no_break(self, *, name, value, inline=True): """ Add field to the embed(s). This method does not allow for breaking (splitting) into a new embed AFTER this new field (it may allow it before, if :meth:`~.add_field` was used). A field value that exceeds the max length will split the field if auto_truncate is enabled, or raise a ValueError otherwise. :raise ValueError: Field name or value too long (and auto_truncate disabled) """ if len(name) > Limits.EMBED_FIELD_NAME: if not self.auto_truncate: raise ValueError("Field name too long") name = natural_truncate(name, maxlen=Limits.EMBED_FIELD_NAME) elif not name.strip(): raise ValueError("Empty name field for embed titled {!r}".format(self.template.title)) if len(value) > Limits.EMBED_FIELD_VALUE: if not self.auto_truncate: raise ValueError("Field value too long") self._add_split_field(name, value, inline) elif not value.strip(): raise ValueError("Empty value for field named {!r}".format(name)) else: self._field_cache.append({'name': name, 'value': value, 'inline': inline}) cur_len = sum(len(f['name']) + len(f['value']) for f in self._field_cache) try: if not self.repeat_footer: # always reserve footer space, just 'cause it's simpler cur_len += len(self.template.footer.text) except TypeError: # self.template.footer.text is EmptyEmbed pass # if the current field cache does not fit, make new embed # cache is not flushed into the embed, so this will keep currently cached fields together if self._is_too_long(cur_len) or self._num_fields() > Limits.EMBED_FIELD_NUM: self._start_new_embed()
def log_submission(submission: reddit.models.Submission) -> str: """ Format submission info in short form for logs. """ return "{0.id} on {0.subreddit.display_name} (\"{1}\")".format( submission, natural_truncate(submission.title, 50))
def set_footer(self, *, text: str=EmptyEmbed, icon_url: str=EmptyEmbed): if len(text) > Limits.EMBED_FOOTER: if not self.auto_truncate: raise ValueError("Footer too long") text = natural_truncate(text, maxlen=Limits.EMBED_FOOTER) self.template.set_footer(text=text, icon_url=icon_url)
def set_author(self, *, name: str, url: str=EmptyEmbed, icon_url: str=EmptyEmbed): if len(name) > Limits.EMBED_AUTHOR: if not self.auto_truncate: raise ValueError("Author too long") name = natural_truncate(name, maxlen=Limits.EMBED_AUTHOR) self.template.set_author(name=name, url=url, icon_url=icon_url)
def deco_f(*args, **kwargs): return strings.natural_truncate(f(*args, **kwargs), max_len, ellipsis_)