async def send(self, content=None, *, delete_on_invoke_removed=True, file=None, files=None, **kwargs) -> Message:
        # Case for a too-big message
        if content and len(content) > 1990:
            self.logger.warning("Message content is too big to be sent, putting in a text file for sending.")

            message_file = discord.File(io.BytesIO(content.encode()), filename="message.txt")
            content = None

            if file is not None and files is not None:
                raise InvalidArgument('Cannot pass both file and files parameter to send()')
            elif file is not None:
                files = [message_file, file]
                file = None
            elif files is not None:
                if len(files) == 10:
                    raise InvalidArgument('Content is too big, and too many files were provided')
                else:
                    files = [message_file] + files
            else:
                file = message_file

        message = await super().send(content, file=file, files=files, **kwargs)

        # Message deletion if source is deleted
        if delete_on_invoke_removed:
            asyncio.ensure_future(delete_messages_if_message_removed(self.bot, self.message, message))

        return message
Exemple #2
0
    async def remove_job(self, entry: NickQueueEntry = None, id: int = None):
        """Removes a job from the scheduler for the NickQueueEntry.

        Args:
            entry (NickQueueEntry, optional): The entry to query against. Defaults to None.
            id (int, optional): An ID for an entry to find. Defaults to None.

        Raises:
            InvalidArgument: If neither an entry nor id were presented, or if the ID was invalid.
        """
        if entry is None and id is None:
            raise InvalidArgument(
                "Need to have a valid entry or id to remove a job.")
        elif entry is None:
            nick_queue = await self.nick_queue()
            found = list(filter(lambda entry: (
                id in entry and entry["id"] == id), nick_queue))
            if len(found) == 0:
                raise InvalidArgument("ID was not found.")
            entry = found[0]
        if 'id' not in entry:
            return

        job = scheduler.get_job(str(entry["id"]))
        if job is not None:
            scheduler.remove_job(str(entry["id"]))
Exemple #3
0
    async def send(self,
                   content=None,
                   *,
                   file=None,
                   files=None,
                   **kwargs) -> Message:
        # Case for a too-big message
        if content and len(content) > 1990:
            self.logger.warning(
                "Message content is too big to be sent, putting in a text file for sending."
            )

            message_file = discord.File(io.BytesIO(content.encode()),
                                        filename="message.txt")
            content = None

            if file is not None and files is not None:
                raise InvalidArgument(
                    'Cannot pass both file and files parameter to send()')
            elif file is not None:
                files = [message_file, file]
                file = None
            elif files is not None:
                if len(files) == 10:
                    raise InvalidArgument(
                        'Content is too big, and too many files were provided')
                else:
                    files = [message_file] + files
            else:
                file = message_file

        message = await super().send(content, file=file, files=files, **kwargs)

        return message
Exemple #4
0
    def delete(self, *, reason=None):
        """|maybecoro|

        Deletes this Webhook.

        If the webhook is constructed with a :class:`RequestsWebhookAdapter` then this is
        not a coroutine.

        Parameters
        ------------
        reason: Optional[:class:`str`]
            The reason for deleting this webhook. Shows up on the audit log.

            .. versionadded:: 1.4

        Raises
        -------
        HTTPException
            Deleting the webhook failed.
        NotFound
            This webhook does not exist.
        Forbidden
            You do not have permissions to delete this webhook.
        InvalidArgument
            This webhook does not have a token associated with it.
        """
        if self.token is None:
            raise InvalidArgument('This webhook does not have a token associated with it')

        return self._adapter.delete_webhook(reason=reason)
Exemple #5
0
    def from_url(cls, url, *, adapter):
        """Creates a partial :class:`Webhook` from a webhook URL.

        Parameters
        ------------
        url: :class:`str`
            The URL of the webhook.
        adapter: :class:`WebhookAdapter`
            The webhook adapter to use when sending requests. This is
            typically :class:`AsyncWebhookAdapter` for :doc:`aiohttp <aio:index>` or
            :class:`RequestsWebhookAdapter` for :doc:`req:index`.

        Raises
        -------
        InvalidArgument
            The URL is invalid.

        Returns
        --------
        :class:`Webhook`
            A partial :class:`Webhook`.
            A partial webhook is just a webhook object with an ID and a token.
        """

        m = re.search(r'discord(?:app)?.com/api/webhooks/(?P<id>[0-9]{17,20})/(?P<token>[A-Za-z0-9\.\-\_]{60,68})', url)
        if m is None:
            raise InvalidArgument('Invalid webhook URL given.')
        data = m.groupdict()
        data['type'] = 1
        return cls(data, adapter=adapter)
Exemple #6
0
 def __init__(self, func, name: str, description='') -> None:
     super().__init__()
     check_coroutine(func)
     self.func = func
     self.name = name
     self.description = description
     f_args = func.__code__.co_varnames[:func.__code__.co_argcount]
     assert len(f_args) >= 2
     self._f_args = f_args[2:]
     self._hints = {k: v for k, v in get_type_hints(func).items() if k in self._f_args}
     self._req_f_args = []
     args = []
     optionals = False
     for a in self._f_args:
         arg = a
         if a in self._hints:
             arg += f': {self._hints[a].__name__}'
         if not a.lower().startswith(self.optional_prefix):
             self._req_f_args.append(a)
             args.append(f'{{{arg}}}')
         elif optionals:
             raise InvalidArgument(
                 f'Non-optional argument {a} found after optional argument in {name} command coroutine')
         else:
             optionals = True
             args.append(f'[{arg[len(self.optional_prefix):]}]')
     self._args_str = ' '.join(args)
Exemple #7
0
    def edit(self, *, reason=None, **kwargs):
        """|maybecoro|

        Edits this Webhook.

        If the webhook is constructed with a :class:`RequestsWebhookAdapter` then this is
        not a coroutine.

        Parameters
        ------------
        name: Optional[:class:`str`]
            The webhook's new default name.
        avatar: Optional[:class:`bytes`]
            A :term:`py:bytes-like object` representing the webhook's new default avatar.
        reason: Optional[:class:`str`]
            The reason for editing this webhook. Shows up on the audit log.

            .. versionadded:: 1.4

        Raises
        -------
        HTTPException
            Editing the webhook failed.
        NotFound
            This webhook does not exist.
        InvalidArgument
            This webhook does not have a token associated with it.
        """
        if self.token is None:
            raise InvalidArgument('This webhook does not have a token associated with it')

        payload = {}

        try:
            name = kwargs['name']
        except KeyError:
            pass
        else:
            if name is not None:
                payload['name'] = str(name)
            else:
                payload['name'] = None

        try:
            avatar = kwargs['avatar']
        except KeyError:
            pass
        else:
            if avatar is not None:
                payload['avatar'] = utils._bytes_to_base64_data(avatar)
            else:
                payload['avatar'] = None

        return self._adapter.edit_webhook(reason=reason, **payload)
Exemple #8
0
    def avatar_url_as(self, *, format=None, size=1024):
        """Returns an :class:`Asset` for the avatar the webhook has.

        If the webhook does not have a traditional avatar, an asset for
        the default avatar is returned instead.

        The format must be one of 'jpeg', 'jpg', or 'png'.
        The size must be a power of 2 between 16 and 1024.

        Parameters
        -----------
        format: Optional[:class:`str`]
            The format to attempt to convert the avatar to.
            If the format is ``None``, then it is equivalent to png.
        size: :class:`int`
            The size of the image to display.

        Raises
        ------
        InvalidArgument
            Bad image format passed to ``format`` or invalid ``size``.

        Returns
        --------
        :class:`Asset`
            The resulting CDN asset.
        """
        if self.avatar is None:
            # Default is always blurple apparently
            return Asset(self._state, '/embed/avatars/0.png')

        if not utils.valid_icon_size(size):
            raise InvalidArgument("size must be a power of 2 between 16 and 1024")

        format = format or 'png'

        if format not in ('png', 'jpg', 'jpeg'):
            raise InvalidArgument("format must be one of 'png', 'jpg', or 'jpeg'.")

        url = '/avatars/{0.id}/{0.avatar}.{1}?size={2}'.format(self, format, size)
        return Asset(self._state, url)
Exemple #9
0
def parse_duration_string(input: str) -> int:
    """Parses a duration string into the number of seconds it composes.

    Args:
        input (str): The input string to parse.

    Returns:
        int: The number of seconds in duration that string is.
    """
    if(re.match("^[\\d]+:[0-5][0-9]:[0-5][0-9]$", input)):
        hours, rest = input.split(':', 1)
        t = strptime(rest, "%M:%S")
        return int(hours) * 60 * 60 + t.tm_min * 60 + t.tm_sec
    elif(re.match("^[0-9]?[0-9]:[0-5][0-9]$", input)):
        t = strptime(input, "%M:%S")
        return t.tm_min * 60 + t.tm_sec
    else:
        raise InvalidArgument("Could not parse the duration.")
Exemple #10
0
    def __init__(self, bot: Overlord, priority=None) -> None:
        super().__init__()
        self._bot = bot
        self._enabled = False
        self._async_lock = asyncio.Lock()

        attrs = [
            getattr(self, attr) for attr in dir(self)
            if not attr.startswith('_')
        ]

        # Gather tasks
        self._tasks = [t for t in attrs if isinstance(t, OverlordTask)]
        self._task_instances = []

        # Gather commands
        self._commands = {
            c.name: c
            for c in attrs if isinstance(c, OverlordCommand)
        }
        self._command_handlers = {
            name: c.handler(self)
            for name, c in self._commands.items()
        }

        # Reattach implemented handlers
        handlers = get_coroutine_attrs(
            self, name_filter=lambda x: x.startswith('on_'))
        for h_name, h in handlers.items():
            setattr(self, h_name, self._handler(h))

        # Prioritize
        if priority is not None:
            self.__priority__ = priority
        if self.priority > 63 or self.priority < 0:
            raise InvalidArgument(
                f'priority should be less then 63 and bigger or equal then 0, got: {priority}'
            )
Exemple #11
0
    def edit_message(self, message_id, **fields):
        """|maybecoro|

        Edits a message owned by this webhook.

        This is a lower level interface to :meth:`WebhookMessage.edit` in case
        you only have an ID.

        .. versionadded:: 1.6

        Parameters
        ------------
        message_id: :class:`int`
            The message ID to edit.
        content: Optional[:class:`str`]
            The content to edit the message with or ``None`` to clear it.
        embeds: List[:class:`Embed`]
            A list of embeds to edit the message with.
        embed: Optional[:class:`Embed`]
            The embed to edit the message with. ``None`` suppresses the embeds.
            This should not be mixed with the ``embeds`` parameter.
        allowed_mentions: :class:`AllowedMentions`
            Controls the mentions being processed in this message.
            See :meth:`.abc.Messageable.send` for more information.

        Raises
        -------
        HTTPException
            Editing the message failed.
        Forbidden
            Edited a message that is not yours.
        InvalidArgument
            You specified both ``embed`` and ``embeds`` or the length of
            ``embeds`` was invalid or there was no token associated with
            this webhook.
        """

        payload = {}

        if self.token is None:
            raise InvalidArgument('This webhook does not have a token associated with it')

        try:
            content = fields['content']
        except KeyError:
            pass
        else:
            if content is not None:
                content = str(content)
            payload['content'] = content

        # Check if the embeds interface is being used
        try:
            embeds = fields['embeds']
        except KeyError:
            # Nope
            pass
        else:
            if embeds is None or len(embeds) > 10:
                raise InvalidArgument('embeds has a maximum of 10 elements')
            payload['embeds'] = [e.to_dict() for e in embeds]

        try:
            embed = fields['embed']
        except KeyError:
            pass
        else:
            if 'embeds' in payload:
                raise InvalidArgument('Cannot mix embed and embeds keyword arguments')

            if embed is None:
                payload['embeds'] = []
            else:
                payload['embeds'] = [embed.to_dict()]

        allowed_mentions = fields.pop('allowed_mentions', None)
        previous_mentions = getattr(self._state, 'allowed_mentions', None)

        if allowed_mentions:
            if previous_mentions is not None:
                payload['allowed_mentions'] = previous_mentions.merge(allowed_mentions).to_dict()
            else:
                payload['allowed_mentions'] = allowed_mentions.to_dict()
        elif previous_mentions is not None:
            payload['allowed_mentions'] = previous_mentions.to_dict()

        return self._adapter.edit_webhook_message(message_id, payload=payload)
Exemple #12
0
    def send(self, content=None, *, wait=False, username=None, avatar_url=None, tts=False,
                                    file=None, files=None, embed=None, embeds=None, allowed_mentions=None):
        """|maybecoro|

        Sends a message using the webhook.

        If the webhook is constructed with a :class:`RequestsWebhookAdapter` then this is
        not a coroutine.

        The content must be a type that can convert to a string through ``str(content)``.

        To upload a single file, the ``file`` parameter should be used with a
        single :class:`File` object.

        If the ``embed`` parameter is provided, it must be of type :class:`Embed` and
        it must be a rich embed type. You cannot mix the ``embed`` parameter with the
        ``embeds`` parameter, which must be a :class:`list` of :class:`Embed` objects to send.

        Parameters
        ------------
        content: :class:`str`
            The content of the message to send.
        wait: :class:`bool`
            Whether the server should wait before sending a response. This essentially
            means that the return type of this function changes from ``None`` to
            a :class:`WebhookMessage` if set to ``True``.
        username: :class:`str`
            The username to send with this message. If no username is provided
            then the default username for the webhook is used.
        avatar_url: Union[:class:`str`, :class:`Asset`]
            The avatar URL to send with this message. If no avatar URL is provided
            then the default avatar for the webhook is used.
        tts: :class:`bool`
            Indicates if the message should be sent using text-to-speech.
        file: :class:`File`
            The file to upload. This cannot be mixed with ``files`` parameter.
        files: List[:class:`File`]
            A list of files to send with the content. This cannot be mixed with the
            ``file`` parameter.
        embed: :class:`Embed`
            The rich embed for the content to send. This cannot be mixed with
            ``embeds`` parameter.
        embeds: List[:class:`Embed`]
            A list of embeds to send with the content. Maximum of 10. This cannot
            be mixed with the ``embed`` parameter.
        allowed_mentions: :class:`AllowedMentions`
            Controls the mentions being processed in this message.

            .. versionadded:: 1.4

        Raises
        --------
        HTTPException
            Sending the message failed.
        NotFound
            This webhook was not found.
        Forbidden
            The authorization token for the webhook is incorrect.
        InvalidArgument
            You specified both ``embed`` and ``embeds`` or the length of
            ``embeds`` was invalid or there was no token associated with
            this webhook.

        Returns
        ---------
        Optional[:class:`WebhookMessage`]
            The message that was sent.
        """

        payload = {}
        if self.token is None:
            raise InvalidArgument('This webhook does not have a token associated with it')
        if files is not None and file is not None:
            raise InvalidArgument('Cannot mix file and files keyword arguments.')
        if embeds is not None and embed is not None:
            raise InvalidArgument('Cannot mix embed and embeds keyword arguments.')

        if embeds is not None:
            if len(embeds) > 10:
                raise InvalidArgument('embeds has a maximum of 10 elements.')
            payload['embeds'] = [e.to_dict() for e in embeds]

        if embed is not None:
            payload['embeds'] = [embed.to_dict()]

        if content is not None:
            payload['content'] = str(content)

        payload['tts'] = tts
        if avatar_url:
            payload['avatar_url'] = str(avatar_url)
        if username:
            payload['username'] = username

        previous_mentions = getattr(self._state, 'allowed_mentions', None)

        if allowed_mentions:
            if previous_mentions is not None:
                payload['allowed_mentions'] = previous_mentions.merge(allowed_mentions).to_dict()
            else:
                payload['allowed_mentions'] = allowed_mentions.to_dict()
        elif previous_mentions is not None:
            payload['allowed_mentions'] = previous_mentions.to_dict()

        return self._adapter.execute_webhook(wait=wait, file=file, files=files, payload=payload)
Exemple #13
0
    async def send(self,
                   content=None,
                   *,
                   delete_on_invoke_removed=True,
                   file=None,
                   files=None,
                   reply=False,
                   **kwargs) -> Message:
        # Case for a too-big message
        if content and len(content) > 1990:
            self.logger.warning(
                "Message content is too big to be sent, putting in a text file for sending."
            )

            message_file = discord.File(io.BytesIO(content.encode()),
                                        filename="message.txt")
            content = None

            if file is not None and files is not None:
                raise InvalidArgument(
                    'Cannot pass both file and files parameter to send()')
            elif file is not None:
                files = [message_file, file]
                file = None
            elif files is not None:
                if len(files) == 10:
                    raise InvalidArgument(
                        'Content is too big, and too many files were provided')
                else:
                    files = [message_file] + files
            else:
                file = message_file

        if reply:
            db_user = await get_from_db(self.author, as_user=True)
            try:
                message = await super().reply(
                    content,
                    file=file,
                    files=files,
                    allowed_mentions=discord.AllowedMentions(
                        replied_user=db_user.ping_friendly),
                    **kwargs)
            except discord.errors.HTTPException:
                # Can't reply, probably that the message we are replying to was deleted.
                message = await super().send(content,
                                             file=file,
                                             files=files,
                                             **kwargs)
        else:
            message = await super().send(content,
                                         file=file,
                                         files=files,
                                         **kwargs)

        # Message deletion if source is deleted
        if delete_on_invoke_removed:
            asyncio.ensure_future(
                delete_messages_if_message_removed(self.bot, self.message,
                                                   message))

        return message
Exemple #14
0
    async def _send(self,
                    content=None,
                    *,
                    tts=False,
                    embed=None,
                    file=None,
                    files=None,
                    delete_after=None,
                    nonce=None,
                    static_channel=None):
        # If we want to set a static channel to the context and send content there
        channel = await self._get_channel()
        if static_channel:
            channel = static_channel

        state = self._state
        content = str(content) if content is not None else None
        if embed is not None:
            embed = embed.to_dict()

        if file is not None and files is not None:
            raise InvalidArgument(
                'cannot pass both file and files parameter to send()')

        if file is not None:
            if not isinstance(file, File):
                raise InvalidArgument('file parameter must be File')

            try:
                data = await state.http.send_files(channel.id,
                                                   files=[file],
                                                   content=content,
                                                   tts=tts,
                                                   embed=embed,
                                                   nonce=nonce)
            finally:
                file.close()

        elif files is not None:
            if len(files) > 10:
                raise InvalidArgument(
                    'files parameter must be a list of up to 10 elements')
            elif not all(isinstance(file, File) for file in files):
                raise InvalidArgument('files parameter must be a list of File')

            try:
                data = await state.http.send_files(channel.id,
                                                   files=files,
                                                   content=content,
                                                   tts=tts,
                                                   embed=embed,
                                                   nonce=nonce)
            finally:
                for f in files:
                    f.close()
        else:
            data = await state.http.send_message(channel.id,
                                                 content,
                                                 tts=tts,
                                                 embed=embed,
                                                 nonce=nonce)

        ret = state.create_message(channel=channel, data=data)
        if delete_after is not None:
            await ret.delete(delay=delete_after)
        return ret
Exemple #15
0
    async def send(self,
                   content=None,
                   *,
                   tts=False,
                   embed=None,
                   file=None,
                   files=None,
                   reason=None,
                   delete_after=None,
                   filter=True):
        """Sends a message to the destination with the content given."""
        channel = await self._get_channel()
        state = self._state
        if content:
            content = str(content)
            if filter:
                if not self.bot.selfbot:
                    content = content.replace('@everyone',
                                              '@\u200beveryone').replace(
                                                  '@here', '@\u200bhere')
            if len(content) > 2000:
                truncate_msg = '**... (truncated)**'
                if '```' in content:
                    truncate_msg = '```' + truncate_msg
                content = content[:2000 - len(truncate_msg)] + truncate_msg
            elif len(content) <= 1999:
                if self.bot.selfbot:
                    if filter:
                        content += '\u200b'

        if embed is not None:
            embed = embed.to_dict()

        if file is not None and files is not None:
            raise InvalidArgument(
                'cannot pass both file and files parameter to send()')

        if file is not None:
            if not isinstance(file, File):
                raise InvalidArgument('file parameter must be File')

            try:
                data = await state.http.send_files(channel.id,
                                                   files=[(file.open_file(),
                                                           file.filename)],
                                                   content=content,
                                                   tts=tts,
                                                   embed=embed)
            finally:
                file.close()

        elif files is not None:
            if len(files) < 2 or len(files) > 10:
                raise InvalidArgument(
                    'files parameter must be a list of 2 to 10 elements')

            try:
                param = [(f.open_file(), f.filename) for f in files]
                data = await state.http.send_files(channel.id,
                                                   files=param,
                                                   content=content,
                                                   tts=tts,
                                                   embed=embed)
            finally:
                for f in files:
                    f.close()
        else:
            data = await state.http.send_message(channel.id,
                                                 content,
                                                 tts=tts,
                                                 embed=embed)

        ret = state.create_message(channel=channel, data=data)
        if delete_after is not None:

            async def delete():
                await asyncio.sleep(delete_after, loop=state.loop)
                try:
                    await ret.delete(reason=reason)
                except:
                    pass

            state.loop.create_task(delete())
        return ret