Example #1
0
    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
Example #2
0
    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))
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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 = '…'
Example #6
0
 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))
Example #7
0
    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()
Example #8
0
 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))
Example #9
0
 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)
Example #10
0
 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)
Example #11
0
 def deco_f(*args, **kwargs):
     return strings.natural_truncate(f(*args, **kwargs), max_len,
                                     ellipsis_)