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
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"]))
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
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)
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)
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)
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)
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)
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.")
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}' )
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)
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)
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
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
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