From eeafc9363f0497e2faebdf331375f0a456300d97 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 15 Jan 2021 20:26:03 +1000 Subject: [PATCH 001/171] [commands] Add PartialMessageConverter --- discord/ext/commands/converter.py | 48 ++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index a3392482..aff96aac 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -37,6 +37,7 @@ __all__ = ( 'MemberConverter', 'UserConverter', 'MessageConverter', + 'PartialMessageConverter', 'TextChannelConverter', 'InviteConverter', 'RoleConverter', @@ -251,7 +252,38 @@ class UserConverter(IDConverter): return result -class MessageConverter(Converter): +class PartialMessageConverter(Converter): + """Converts to a :class:`discord.PartialMessage`. + + .. versionadded:: 1.7 + + The creation strategy is as follows (in order): + + 1. By "{channel ID}-{message ID}" (retrieved by shift-clicking on "Copy ID") + 2. By message ID (The message is assumed to be in the context channel.) + 3. By message URL + """ + def _get_id_matches(self, argument): + id_regex = re.compile(r'(?:(?P[0-9]{15,21})-)?(?P[0-9]{15,21})$') + link_regex = re.compile( + r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/' + r'(?:[0-9]{15,21}|@me)' + r'/(?P[0-9]{15,21})/(?P[0-9]{15,21})/?$' + ) + match = id_regex.match(argument) or link_regex.match(argument) + if not match: + raise MessageNotFound(argument) + channel_id = match.group("channel_id") + return int(match.group("message_id")), int(channel_id) if channel_id else None + + async def convert(self, ctx, argument): + message_id, channel_id = self._get_id_matches(argument) + channel = ctx.bot.get_channel(channel_id) if channel_id else ctx.channel + if not channel: + raise ChannelNotFound(channel_id) + return discord.PartialMessage(channel=channel, id=message_id) + +class MessageConverter(PartialMessageConverter): """Converts to a :class:`discord.Message`. .. versionadded:: 1.1 @@ -266,21 +298,11 @@ class MessageConverter(Converter): Raise :exc:`.ChannelNotFound`, :exc:`.MessageNotFound` or :exc:`.ChannelNotReadable` instead of generic :exc:`.BadArgument` """ async def convert(self, ctx, argument): - id_regex = re.compile(r'(?:(?P[0-9]{15,21})-)?(?P[0-9]{15,21})$') - link_regex = re.compile( - r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/' - r'(?:[0-9]{15,21}|@me)' - r'/(?P[0-9]{15,21})/(?P[0-9]{15,21})/?$' - ) - match = id_regex.match(argument) or link_regex.match(argument) - if not match: - raise MessageNotFound(argument) - message_id = int(match.group("message_id")) - channel_id = match.group("channel_id") + message_id, channel_id = self._get_id_matches(argument) message = ctx.bot._connection._get_message(message_id) if message: return message - channel = ctx.bot.get_channel(int(channel_id)) if channel_id else ctx.channel + channel = ctx.bot.get_channel(channel_id) if channel_id else ctx.channel if not channel: raise ChannelNotFound(channel_id) try: From 69bdc3a1849407f64ef93bd2e5119e54d6cd2208 Mon Sep 17 00:00:00 2001 From: Nihaal Sangha Date: Fri, 15 Jan 2021 10:28:11 +0000 Subject: [PATCH 002/171] Change copyright year to present --- LICENSE | 2 +- discord/__init__.py | 4 ++-- discord/__main__.py | 2 +- discord/abc.py | 2 +- discord/activity.py | 2 +- discord/appinfo.py | 2 +- discord/asset.py | 4 ++-- discord/audit_logs.py | 2 +- discord/backoff.py | 2 +- discord/calls.py | 2 +- discord/channel.py | 2 +- discord/client.py | 2 +- discord/colour.py | 2 +- discord/context_managers.py | 2 +- discord/embeds.py | 2 +- discord/emoji.py | 2 +- discord/enums.py | 2 +- discord/errors.py | 2 +- discord/ext/commands/__init__.py | 2 +- discord/ext/commands/_types.py | 2 +- discord/ext/commands/bot.py | 2 +- discord/ext/commands/cog.py | 2 +- discord/ext/commands/context.py | 2 +- discord/ext/commands/converter.py | 2 +- discord/ext/commands/cooldowns.py | 2 +- discord/ext/commands/core.py | 2 +- discord/ext/commands/errors.py | 2 +- discord/ext/commands/help.py | 2 +- discord/ext/commands/view.py | 2 +- discord/ext/tasks/__init__.py | 2 +- discord/file.py | 2 +- discord/flags.py | 3 +-- discord/gateway.py | 2 +- discord/guild.py | 2 +- discord/http.py | 2 +- discord/integrations.py | 2 +- discord/invite.py | 2 +- discord/iterators.py | 4 ++-- discord/member.py | 2 +- discord/mentions.py | 2 +- discord/message.py | 2 +- discord/mixins.py | 2 +- discord/object.py | 2 +- discord/oggparse.py | 2 +- discord/opus.py | 4 ++-- discord/partial_emoji.py | 2 +- discord/permissions.py | 2 +- discord/player.py | 2 +- discord/raw_models.py | 2 +- discord/reaction.py | 2 +- discord/relationship.py | 2 +- discord/role.py | 2 +- discord/shard.py | 2 +- discord/state.py | 2 +- discord/sticker.py | 2 +- discord/team.py | 2 +- discord/template.py | 2 +- discord/user.py | 2 +- discord/utils.py | 2 +- discord/voice_client.py | 2 +- discord/webhook.py | 2 +- discord/widget.py | 2 +- docs/conf.py | 2 +- docs/locale/ja/LC_MESSAGES/intents.po | 3 +-- 64 files changed, 68 insertions(+), 70 deletions(-) diff --git a/LICENSE b/LICENSE index eb8cdf65..700c21b6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/__init__.py b/discord/__init__.py index 48f98fea..eba52408 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -6,7 +6,7 @@ Discord API Wrapper A basic wrapper for the Discord API. -:copyright: (c) 2015-2020 Rapptz +:copyright: (c) 2015-present Rapptz :license: MIT, see LICENSE for more details. """ @@ -14,7 +14,7 @@ A basic wrapper for the Discord API. __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' -__copyright__ = 'Copyright 2015-2020 Rapptz' +__copyright__ = 'Copyright 2015-present Rapptz' __version__ = '1.6.0' __path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/discord/__main__.py b/discord/__main__.py index 70854748..47504b7f 100644 --- a/discord/__main__.py +++ b/discord/__main__.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/abc.py b/discord/abc.py index e45312b6..cf4b8ab4 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/activity.py b/discord/activity.py index 91e094e2..c230154d 100644 --- a/discord/activity.py +++ b/discord/activity.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/appinfo.py b/discord/appinfo.py index 4ff76d39..97337b91 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/asset.py b/discord/asset.py index 29a86769..ea457297 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -165,7 +165,7 @@ class Asset: format = 'gif' if emoji.animated else static_format return cls(state, '/emojis/{0.id}.{1}'.format(emoji, format)) - + def __str__(self): return self.BASE + self._url if self._url is not None else '' diff --git a/discord/audit_logs.py b/discord/audit_logs.py index c316f146..90c14986 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/backoff.py b/discord/backoff.py index b602ec64..0f49d155 100644 --- a/discord/backoff.py +++ b/discord/backoff.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/calls.py b/discord/calls.py index a1b6ab09..80fc8954 100644 --- a/discord/calls.py +++ b/discord/calls.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/channel.py b/discord/channel.py index 2bac8b1a..9ab5d226 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/client.py b/discord/client.py index 6f9cb80a..22c25db7 100644 --- a/discord/client.py +++ b/discord/client.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/colour.py b/discord/colour.py index a9e87f3e..c6e3dd67 100644 --- a/discord/colour.py +++ b/discord/colour.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/context_managers.py b/discord/context_managers.py index 279b224d..e4fde6b3 100644 --- a/discord/context_managers.py +++ b/discord/context_managers.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/embeds.py b/discord/embeds.py index aa1563e4..e93e16f2 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/emoji.py b/discord/emoji.py index 733980e9..735d508c 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/enums.py b/discord/enums.py index fff7a153..721dbe47 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/errors.py b/discord/errors.py index be3015cc..fbdff0aa 100644 --- a/discord/errors.py +++ b/discord/errors.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/__init__.py b/discord/ext/commands/__init__.py index 669a49a9..6f356c5d 100644 --- a/discord/ext/commands/__init__.py +++ b/discord/ext/commands/__init__.py @@ -6,7 +6,7 @@ discord.ext.commands An extension module to facilitate creation of bot commands. -:copyright: (c) 2015-2020 Rapptz +:copyright: (c) 2015-present Rapptz :license: MIT, see LICENSE for more details. """ diff --git a/discord/ext/commands/_types.py b/discord/ext/commands/_types.py index 664f7519..36d0efc9 100644 --- a/discord/ext/commands/_types.py +++ b/discord/ext/commands/_types.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index 2fc08fa2..ed3ae078 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/cog.py b/discord/ext/commands/cog.py index 774f5317..bea69c04 100644 --- a/discord/ext/commands/cog.py +++ b/discord/ext/commands/cog.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index d129e819..155e4797 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index aff96aac..27991eca 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/cooldowns.py b/discord/ext/commands/cooldowns.py index c530bb12..cd7d67ea 100644 --- a/discord/ext/commands/cooldowns.py +++ b/discord/ext/commands/cooldowns.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index 2d2291f4..b62eadb5 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index 541a9833..aa4c585b 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/help.py b/discord/ext/commands/help.py index 5d567325..459b108d 100644 --- a/discord/ext/commands/help.py +++ b/discord/ext/commands/help.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/commands/view.py b/discord/ext/commands/view.py index 36c89037..7f74c700 100644 --- a/discord/ext/commands/view.py +++ b/discord/ext/commands/view.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index d2db5df8..9dbc7f9c 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/file.py b/discord/file.py index 9d1a6ff1..30b22422 100644 --- a/discord/file.py +++ b/discord/file.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/flags.py b/discord/flags.py index cd8557a3..be90fa5c 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -938,4 +938,3 @@ class MemberCacheFlags(BaseFlags): @property def _online_only(self): return self.value == 1 - diff --git a/discord/gateway.py b/discord/gateway.py index d82e552f..9b85b866 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/guild.py b/discord/guild.py index f78a7315..3b12eeea 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/http.py b/discord/http.py index b90c6b85..edfe031c 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/integrations.py b/discord/integrations.py index 37fda5e9..e2d05575 100644 --- a/discord/integrations.py +++ b/discord/integrations.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/invite.py b/discord/invite.py index 2f7c273d..627e90ef 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/iterators.py b/discord/iterators.py index 51431651..a9575d49 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -101,7 +101,7 @@ class _ChunkedAsyncIterator(_AsyncIterator): def __init__(self, iterator, max_size): self.iterator = iterator self.max_size = max_size - + async def next(self): ret = [] n = 0 diff --git a/discord/member.py b/discord/member.py index afce4c34..38c2d0df 100644 --- a/discord/member.py +++ b/discord/member.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/mentions.py b/discord/mentions.py index 14fcecc5..2cabc4c6 100644 --- a/discord/mentions.py +++ b/discord/mentions.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/message.py b/discord/message.py index f47a45f3..ae405fd3 100644 --- a/discord/message.py +++ b/discord/message.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/mixins.py b/discord/mixins.py index e115e844..afdbc1d0 100644 --- a/discord/mixins.py +++ b/discord/mixins.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/object.py b/discord/object.py index 1ca96c79..d349caa3 100644 --- a/discord/object.py +++ b/discord/object.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/oggparse.py b/discord/oggparse.py index 5dd34345..a7b9dcd7 100644 --- a/discord/oggparse.py +++ b/discord/oggparse.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/opus.py b/discord/opus.py index 1f2a03f3..28b37ae0 100644 --- a/discord/opus.py +++ b/discord/opus.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -374,7 +374,7 @@ class Decoder(_OpusStruct): def _set_gain(self, adjustment): """Configures decoder gain adjustment. - + Scales the decoded output by a factor specified in Q8 dB units. This has a maximum range of -32768 to 32767 inclusive, and returns OPUS_BAD_ARG (-1) otherwise. The default is zero indicating no adjustment. diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 346ccafe..e174a513 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/permissions.py b/discord/permissions.py index aee76fe6..0a4e9438 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/player.py b/discord/player.py index d5540a56..413e5de6 100644 --- a/discord/player.py +++ b/discord/player.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/raw_models.py b/discord/raw_models.py index dd4d5e5d..dd8ce9e3 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/reaction.py b/discord/reaction.py index 1d30b483..437c0fe9 100644 --- a/discord/reaction.py +++ b/discord/reaction.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/relationship.py b/discord/relationship.py index cd58e4b0..83866f4e 100644 --- a/discord/relationship.py +++ b/discord/relationship.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/role.py b/discord/role.py index bc50fdbb..7c6e5223 100644 --- a/discord/role.py +++ b/discord/role.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/shard.py b/discord/shard.py index ebc27b0d..84ba8880 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/state.py b/discord/state.py index 8005fb95..1784fc5f 100644 --- a/discord/state.py +++ b/discord/state.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/sticker.py b/discord/sticker.py index 845940b4..d94d1644 100644 --- a/discord/sticker.py +++ b/discord/sticker.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/team.py b/discord/team.py index e59c122c..63cd0c07 100644 --- a/discord/team.py +++ b/discord/team.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/template.py b/discord/template.py index 0dc54e4f..1c333380 100644 --- a/discord/template.py +++ b/discord/template.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/user.py b/discord/user.py index 690cbce9..53cf81df 100644 --- a/discord/user.py +++ b/discord/user.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/utils.py b/discord/utils.py index fd24eaee..775907ff 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/voice_client.py b/discord/voice_client.py index 675fdae9..c276c3e4 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/webhook.py b/discord/webhook.py index 51e62fc3..bdbc8eff 100644 --- a/discord/webhook.py +++ b/discord/webhook.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/discord/widget.py b/discord/widget.py index 0fcf7ec0..b76df877 100644 --- a/discord/widget.py +++ b/discord/widget.py @@ -3,7 +3,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2020 Rapptz +Copyright (c) 2015-present Rapptz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/docs/conf.py b/docs/conf.py index d15ad1be..35cd2d8e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -77,7 +77,7 @@ master_doc = 'index' # General information about the project. project = u'discord.py' -copyright = u'2015-2021, Rapptz' +copyright = u'2015-present, Rapptz' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/locale/ja/LC_MESSAGES/intents.po b/docs/locale/ja/LC_MESSAGES/intents.po index 5a4cbd7a..a3d6d75e 100644 --- a/docs/locale/ja/LC_MESSAGES/intents.po +++ b/docs/locale/ja/LC_MESSAGES/intents.po @@ -1,5 +1,5 @@ # SOME DESCRIPTIVE TITLE. -# Copyright (C) 2015-2020, Rapptz +# Copyright (C) 2015-present, Rapptz # This file is distributed under the same license as the discord.py package. # FIRST AUTHOR , 2020. # @@ -427,4 +427,3 @@ msgid "" "If you truly dislike the direction Discord is going with their API, you " "can contact them via `support `_" msgstr "" - From 73a783cd6b2b9f02d7071947e70da7eb5bc7d2b8 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 15 Jan 2021 20:34:05 +1000 Subject: [PATCH 003/171] Strip both - and _ from newcog class names --- discord/__main__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/__main__.py b/discord/__main__.py index 47504b7f..02803cf0 100644 --- a/discord/__main__.py +++ b/discord/__main__.py @@ -250,8 +250,9 @@ def newcog(parser, args): name = args.class_name else: name = str(directory.stem) - if '-' in name: - name = name.replace('-', ' ').title().replace(' ', '') + if '-' in name or '_' in name: + translation = str.maketrans('-_', ' ') + name = name.translate(translation).title().replace(' ', '') else: name = name.title() From b9a99238e8bd976be825f725fd57012268790c66 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Fri, 15 Jan 2021 06:00:28 -0500 Subject: [PATCH 004/171] [commands] Add Command/Cog.has_error_handler This allows querying the state without relying on internal undocumented attributes. --- discord/ext/commands/cog.py | 7 +++++++ discord/ext/commands/core.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/discord/ext/commands/cog.py b/discord/ext/commands/cog.py index bea69c04..57c78b4e 100644 --- a/discord/ext/commands/cog.py +++ b/discord/ext/commands/cog.py @@ -296,6 +296,13 @@ class Cog(metaclass=CogMeta): return func return decorator + def has_error_handler(self): + """:class:`bool`: Checks whether the cog has an error handler. + + .. versionadded:: 1.7 + """ + return hasattr(self.cog_command_error.__func__, '__cog_special_method__') + @_cog_special_method def cog_unload(self): """A special method that is called when the cog gets removed. diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index b62eadb5..b17f878c 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -904,6 +904,13 @@ class Command(_BaseCommand): self.on_error = coro return coro + def has_error_handler(self): + """:class:`bool`: Checks whether the command has an error handler registered. + + .. versionadded:: 1.7 + """ + return hasattr(self, 'on_error') + def before_invoke(self, coro): """A decorator that registers a coroutine as a pre-invoke hook. From 941e1efcb66a3e9dcb29d092ed81cf380d00308a Mon Sep 17 00:00:00 2001 From: Anurag Singh <30367300+anurag-7@users.noreply.github.com> Date: Fri, 15 Jan 2021 17:40:20 +0530 Subject: [PATCH 005/171] PartialMessage.edit returns a full Message --- discord/message.py | 100 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/discord/message.py b/discord/message.py index ae405fd3..51d77d50 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1351,7 +1351,6 @@ class PartialMessage(Hashable): _exported_names = ( 'jump_url', 'delete', - 'edit', 'publish', 'pin', 'unpin', @@ -1416,3 +1415,102 @@ class PartialMessage(Hashable): data = await self._state.http.get_message(self.channel.id, self.id) return self._state.create_message(channel=self.channel, data=data) + + async def edit(self, **fields): + """|coro| + + Edits the message. + + The content must be able to be transformed into a string via ``str(content)``. + + .. versionchanged:: 1.7 + :class:`discord.Message` is returned instead of ``None`` if an edit took place. + + Parameters + ----------- + content: Optional[:class:`str`] + The new content to replace the message with. + Could be ``None`` to remove the content. + embed: Optional[:class:`Embed`] + The new embed to replace the original with. + Could be ``None`` to remove the embed. + suppress: :class:`bool` + Whether to suppress embeds for the message. This removes + all the embeds if set to ``True``. If set to ``False`` + this brings the embeds back if they were suppressed. + Using this parameter requires :attr:`~.Permissions.manage_messages`. + delete_after: Optional[:class:`float`] + If provided, the number of seconds to wait in the background + before deleting the message we just edited. If the deletion fails, + then it is silently ignored. + allowed_mentions: Optional[:class:`~discord.AllowedMentions`] + Controls the mentions being processed in this message. If this is + passed, then the object is merged with :attr:`~discord.Client.allowed_mentions`. + The merging behaviour only overrides attributes that have been explicitly passed + to the object, otherwise it uses the attributes set in :attr:`~discord.Client.allowed_mentions`. + If no object is passed at all then the defaults given by :attr:`~discord.Client.allowed_mentions` + are used instead. + + Raises + ------- + NotFound + The message was not found. + HTTPException + Editing the message failed. + Forbidden + Tried to suppress a message without permissions or + edited a message's content or embed that isn't yours. + + Returns + --------- + Optional[:class:`Message`] + The message that was edited. + """ + + try: + content = fields['content'] + except KeyError: + pass + else: + if content is not None: + fields['content'] = str(content) + + try: + embed = fields['embed'] + except KeyError: + pass + else: + if embed is not None: + fields['embed'] = embed.to_dict() + + try: + suppress = fields.pop('suppress') + except KeyError: + pass + else: + flags = MessageFlags._from_value(0) + flags.suppress_embeds = suppress + fields['flags'] = flags.value + + delete_after = fields.pop('delete_after', None) + + try: + allowed_mentions = fields.pop('allowed_mentions') + except KeyError: + pass + else: + if allowed_mentions is not None: + if self._state.allowed_mentions is not None: + allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions).to_dict() + else: + allowed_mentions = allowed_mentions.to_dict() + fields['allowed_mentions'] = allowed_mentions + + if fields: + data = await self._state.http.edit_message(self.channel.id, self.id, **fields) + + if delete_after is not None: + await self.delete(delay=delete_after) + + if fields: + return self._state.create_message(channel=self.channel, data=data) From c72dbf28baf46f0f575d143dbbce41caccbe0560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20N=C3=B8rgaard?= Date: Sat, 16 Jan 2021 10:36:08 +0000 Subject: [PATCH 006/171] [commands] Fix Command duplicates in `HelpCommand.get_bot_mapping` --- discord/ext/commands/help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ext/commands/help.py b/discord/ext/commands/help.py index 459b108d..82708571 100644 --- a/discord/ext/commands/help.py +++ b/discord/ext/commands/help.py @@ -376,7 +376,7 @@ class HelpCommand: cog: cog.get_commands() for cog in bot.cogs.values() } - mapping[None] = [c for c in bot.all_commands.values() if c.cog is None] + mapping[None] = [c for c in bot.commands if c.cog is None] return mapping @property From c5a44d83033cc9b9166e8b16e1f65ef6e37ecd74 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sat, 16 Jan 2021 05:41:16 -0500 Subject: [PATCH 007/171] Version bump for development --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index eba52408..342c0578 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -15,7 +15,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '1.6.0' +__version__ = '1.7.0a' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -62,6 +62,6 @@ from .sticker import Sticker VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial') -version_info = VersionInfo(major=1, minor=6, micro=0, releaselevel='final', serial=0) +version_info = VersionInfo(major=1, minor=7, micro=0, releaselevel='alpha', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From d7b41e0a21721407e0941a2fe8ebffc5f78caa55 Mon Sep 17 00:00:00 2001 From: Lucas <13612932+LucasCoderT@users.noreply.github.com> Date: Sat, 16 Jan 2021 22:09:15 -0700 Subject: [PATCH 008/171] Fix User public flags not updating --- discord/member.py | 6 +++--- discord/user.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/discord/member.py b/discord/member.py index 38c2d0df..ec9e4e4e 100644 --- a/discord/member.py +++ b/discord/member.py @@ -290,12 +290,12 @@ class Member(discord.abc.Messageable, _BaseUser): def _update_inner_user(self, user): u = self._user - original = (u.name, u.avatar, u.discriminator) + original = (u.name, u.avatar, u.discriminator, u._public_flags) # These keys seem to always be available - modified = (user['username'], user['avatar'], user['discriminator']) + modified = (user['username'], user['avatar'], user['discriminator'], user.get('public_flags', 0)) if original != modified: to_return = User._copy(self._user) - u.name, u.avatar, u.discriminator = modified + u.name, u.avatar, u.discriminator, u._public_flags = modified # Signal to dispatch on_user_update return to_return, u diff --git a/discord/user.py b/discord/user.py index 53cf81df..dfdbb56e 100644 --- a/discord/user.py +++ b/discord/user.py @@ -120,6 +120,7 @@ class BaseUser(_BaseUser): self.avatar = user.avatar self.bot = user.bot self._state = user._state + self._public_flags = user._public_flags return self From b7c7200f4d791bc6bdc74c0f731cd644b60c55b5 Mon Sep 17 00:00:00 2001 From: PikalaxALT Date: Sun, 17 Jan 2021 00:15:36 -0500 Subject: [PATCH 009/171] [commands] Add linesep kwarg to Paginator --- discord/ext/commands/help.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/discord/ext/commands/help.py b/discord/ext/commands/help.py index 82708571..2dd94fcd 100644 --- a/discord/ext/commands/help.py +++ b/discord/ext/commands/help.py @@ -79,18 +79,22 @@ class Paginator: The suffix appended at the end of every page. e.g. three backticks. max_size: :class:`int` The maximum amount of codepoints allowed in a page. + linesep: :class:`str` + The character string inserted between lines. e.g. a newline character. + .. versionadded:: 1.7 """ - def __init__(self, prefix='```', suffix='```', max_size=2000): + def __init__(self, prefix='```', suffix='```', max_size=2000, linesep='\n'): self.prefix = prefix self.suffix = suffix self.max_size = max_size + self.linesep = linesep self.clear() def clear(self): """Clears the paginator to have no pages.""" if self.prefix is not None: self._current_page = [self.prefix] - self._count = len(self.prefix) + 1 # prefix + newline + self._count = len(self.prefix) + self._linesep_len # prefix + newline else: self._current_page = [] self._count = 0 @@ -104,6 +108,10 @@ class Paginator: def _suffix_len(self): return len(self.suffix) if self.suffix else 0 + @property + def _linesep_len(self): + return len(self.linesep) + def add_line(self, line='', *, empty=False): """Adds a line to the current page. @@ -122,29 +130,29 @@ class Paginator: RuntimeError The line was too big for the current :attr:`max_size`. """ - max_page_size = self.max_size - self._prefix_len - self._suffix_len - 2 + max_page_size = self.max_size - self._prefix_len - self._suffix_len - 2 * self._linesep_len if len(line) > max_page_size: raise RuntimeError('Line exceeds maximum page size %s' % (max_page_size)) - if self._count + len(line) + 1 > self.max_size - self._suffix_len: + if self._count + len(line) + self._linesep_len > self.max_size - self._suffix_len: self.close_page() - self._count += len(line) + 1 + self._count += len(line) + self._linesep_len self._current_page.append(line) if empty: self._current_page.append('') - self._count += 1 + self._count += self._linesep_len def close_page(self): """Prematurely terminate a page.""" if self.suffix is not None: self._current_page.append(self.suffix) - self._pages.append('\n'.join(self._current_page)) + self._pages.append(self.linesep.join(self._current_page)) if self.prefix is not None: self._current_page = [self.prefix] - self._count = len(self.prefix) + 1 # prefix + newline + self._count = len(self.prefix) + self._linesep_len # prefix + linesep else: self._current_page = [] self._count = 0 @@ -162,7 +170,7 @@ class Paginator: return self._pages def __repr__(self): - fmt = '' + fmt = '' return fmt.format(self) def _not_overriden(f): From fce67c32c93cb157dae2ed2c9af3146444ea58eb Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 17 Jan 2021 15:18:10 +1000 Subject: [PATCH 010/171] Add spoiler attribute to File --- discord/file.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/file.py b/discord/file.py index 30b22422..442931c3 100644 --- a/discord/file.py +++ b/discord/file.py @@ -58,7 +58,7 @@ class File: Whether the attachment is a spoiler. """ - __slots__ = ('fp', 'filename', '_original_pos', '_owner', '_closer') + __slots__ = ('fp', 'filename', 'spoiler', '_original_pos', '_owner', '_closer') def __init__(self, fp, filename=None, *, spoiler=False): self.fp = fp @@ -92,6 +92,8 @@ class File: if spoiler and self.filename is not None and not self.filename.startswith('SPOILER_'): self.filename = 'SPOILER_' + self.filename + self.spoiler = spoiler or (self.filename is not None and self.filename.startswith('SPOILER_')) + def reset(self, *, seek=True): # The `seek` parameter is needed because # the retry-loop is iterated over multiple times From d75291699530c46c270ab5b63480787b9709811f Mon Sep 17 00:00:00 2001 From: z03h Date: Sat, 16 Jan 2021 21:21:00 -0800 Subject: [PATCH 011/171] Add MessageReference.jump_url --- discord/message.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/discord/message.py b/discord/message.py index 51d77d50..d024c4b3 100644 --- a/discord/message.py +++ b/discord/message.py @@ -335,6 +335,15 @@ class MessageReference: """Optional[:class:`~discord.Message`]: The cached message, if found in the internal message cache.""" return self._state._get_message(self.message_id) + @property + def jump_url(self): + """:class:`str`: Returns a URL that allows the client to jump to the referenced message. + + .. versionadded:: 1.7 + """ + guild_id = self.guild_id if self.guild_id is not None else '@me' + return 'https://discord.com/channels/{0}/{1.channel_id}/{1.message_id}'.format(guild_id, self) + def __repr__(self): return ''.format(self) From 050bf74f5db0046c0010c2b7c3ac0c5fd0a500f9 Mon Sep 17 00:00:00 2001 From: ChristopherJHart Date: Mon, 18 Jan 2021 04:55:59 -0500 Subject: [PATCH 012/171] Clarify on_raw_message_edit cached message nature --- discord/raw_models.py | 3 ++- docs/api.rst | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/discord/raw_models.py b/discord/raw_models.py index dd8ce9e3..bc19aad5 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -97,7 +97,8 @@ class RawMessageUpdateEvent(_RawReprMixin): data: :class:`dict` The raw data given by the `gateway `_ cached_message: Optional[:class:`Message`] - The cached message, if found in the internal message cache. + The cached message, if found in the internal message cache. Represents the message before + it is modified by the data in :attr:`RawMessageUpdateEvent.data`. """ __slots__ = ('message_id', 'channel_id', 'data', 'cached_message') diff --git a/docs/api.rst b/docs/api.rst index b88ccd5d..9990d036 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -448,7 +448,10 @@ to handle it, which defaults to print a traceback and ignoring the exception. regardless of the state of the internal message cache. If the message is found in the message cache, - it can be accessed via :attr:`RawMessageUpdateEvent.cached_message` + it can be accessed via :attr:`RawMessageUpdateEvent.cached_message`. The cached message represents + the message before it has been edited. For example, if the content of a message is modified and + triggers the :func:`on_raw_message_edit` coroutine, the :attr:`RawMessageUpdateEvent.cached_message` + will return a :class:`Message` object that represents the message before the content was modified. Due to the inherently raw nature of this event, the data parameter coincides with the raw data given by the `gateway `_. From e0e60a2f625002fd01887f0a1ae37e993acb5944 Mon Sep 17 00:00:00 2001 From: Sebastian Law <44045823+SebbyLaw@users.noreply.github.com> Date: Sun, 24 Jan 2021 02:16:32 -0800 Subject: [PATCH 013/171] [commands] document PartialMessageConverter --- docs/ext/commands/api.rst | 3 +++ docs/ext/commands/commands.rst | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index a022a4e3..86ceeb2a 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -281,6 +281,9 @@ Converters .. autoclass:: discord.ext.commands.MessageConverter :members: +.. autoclass:: discord.ext.commands.PartialMessageConverter + :members: + .. autoclass:: discord.ext.commands.TextChannelConverter :members: diff --git a/docs/ext/commands/commands.rst b/docs/ext/commands/commands.rst index c2396f38..1008d637 100644 --- a/docs/ext/commands/commands.rst +++ b/docs/ext/commands/commands.rst @@ -378,6 +378,7 @@ A lot of discord models work out of the gate as a parameter: - :class:`CategoryChannel` - :class:`Role` - :class:`Message` (since v1.1) +- :class:`PartialMessage` (since v1.7) - :class:`Invite` - :class:`Game` - :class:`Emoji` @@ -397,6 +398,8 @@ converter is given below: +--------------------------+-------------------------------------------------+ | :class:`Message` | :class:`~ext.commands.MessageConverter` | +--------------------------+-------------------------------------------------+ +| :class:`PartialMessage` | :class:`~ext.commands.PartialMessageConveter` | ++--------------------------+-------------------------------------------------+ | :class:`User` | :class:`~ext.commands.UserConverter` | +--------------------------+-------------------------------------------------+ | :class:`TextChannel` | :class:`~ext.commands.TextChannelConverter` | From fb9aa2486dd59303ffd3ae3de44ef3358483d9c6 Mon Sep 17 00:00:00 2001 From: z03h Date: Fri, 22 Jan 2021 02:38:44 -0800 Subject: [PATCH 014/171] Add PartialEmoji.url_as --- discord/partial_emoji.py | 41 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index e174a513..a9b9eae9 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -149,10 +149,43 @@ class PartialEmoji(_EmojiTag): @property def url(self): - """:class:`Asset`: Returns an asset of the emoji, if it is custom.""" + """:class:`Asset`: Returns the asset of the emoji, if it is custom. + + This is equivalent to calling :meth:`url_as` with + the default parameters (i.e. png/gif detection). + """ + return self.url_as(format=None) + + def url_as(self, *, format=None, static_format="png"): + """Returns an :class:`Asset` for the emoji's url, if it is custom. + + The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif'. + 'gif' is only valid for animated emojis. + + .. versionadded:: 1.7 + + Parameters + ----------- + format: Optional[:class:`str`] + The format to attempt to convert the emojis to. + If the format is ``None``, then it is automatically + detected as either 'gif' or static_format, depending on whether the + emoji is animated or not. + static_format: Optional[:class:`str`] + Format to attempt to convert only non-animated emoji's to. + Defaults to 'png' + + Raises + ------- + InvalidArgument + Bad image format passed to ``format`` or ``static_format``. + + Returns + -------- + :class:`Asset` + The resulting CDN asset. + """ if self.is_unicode_emoji(): return Asset(self._state) - _format = 'gif' if self.animated else 'png' - url = "/emojis/{0.id}.{1}".format(self, _format) - return Asset(self._state, url) + return Asset._from_emoji(self._state, self, format=format, static_format=static_format) From 0d8ac415511d9d5cfaf9cbd8d354cbf38ae6294d Mon Sep 17 00:00:00 2001 From: Jonas Bohmann Date: Sat, 23 Jan 2021 17:20:08 +0100 Subject: [PATCH 015/171] [commands] Fix documented type of `Context.cog` --- discord/ext/commands/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 155e4797..c4691155 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -204,7 +204,7 @@ class Context(discord.abc.Messageable): @property def cog(self): - """:class:`.Cog`: Returns the cog associated with this context's command. None if it does not exist.""" + """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. None if it does not exist.""" if self.command is None: return None From 61884dd9acd1ed982ef5759f2c91f3c1319eb7cb Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 24 Jan 2021 05:21:13 -0500 Subject: [PATCH 016/171] Use member provided data in typing_start event if not in cache Apparently Discord had this all along. ref: #5965, #5983 --- discord/state.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/state.py b/discord/state.py index 1784fc5f..8e302fe3 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1053,6 +1053,11 @@ class ConnectionState: member = channel.recipient elif isinstance(channel, TextChannel) and guild is not None: member = guild.get_member(user_id) + if member is None: + member_data = data.get('member') + if member_data: + member = Member(data=member_data, state=self, guild=guild) + elif isinstance(channel, GroupChannel): member = utils.find(lambda x: x.id == user_id, channel.recipients) From 0d1e15bbc2aab339b121a1d500cbbc34b5fbde74 Mon Sep 17 00:00:00 2001 From: Zomatree <39768508+Zomatree@users.noreply.github.com> Date: Wed, 27 Jan 2021 04:35:52 +0000 Subject: [PATCH 017/171] Add versionchanged to guild.create_role --- discord/guild.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/discord/guild.py b/discord/guild.py index 3b12eeea..a13c3336 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1733,6 +1733,9 @@ class Guild(Hashable): You must have the :attr:`~Permissions.manage_roles` permission to do this. + ..versionchanged:: 1.6 + Can now pass ``int`` to ``colour`` keyword-only parameter. + Parameters ----------- name: :class:`str` From 30c06bc55f08cb5d7c56fdfa7e6c708d80ea7f9d Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 2 Feb 2021 03:01:12 -0500 Subject: [PATCH 018/171] Always inject the HTML builder even in RTD builds --- docs/extensions/builder.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/extensions/builder.py b/docs/extensions/builder.py index 792f370e..3f2b2594 100644 --- a/docs/extensions/builder.py +++ b/docs/extensions/builder.py @@ -58,11 +58,13 @@ def add_custom_jinja2(app): def add_builders(app): """This is necessary because RTD injects their own for some reason.""" + app.set_translator('html', DPYHTML5Translator, override=True) + app.add_builder(DPYStandaloneHTMLBuilder, override=True) + try: original = app.registry.builders['readthedocs'] except KeyError: - app.set_translator('html', DPYHTML5Translator, override=True) - app.add_builder(DPYStandaloneHTMLBuilder, override=True) + pass else: injected_mro = tuple(base if base is not StandaloneHTMLBuilder else DPYStandaloneHTMLBuilder for base in original.mro()[1:]) From 82edc252e0b315134a482195553ca21ab8ec2890 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 2 Feb 2021 03:46:20 -0500 Subject: [PATCH 019/171] Switch issue templates to use the new issue forms alpha --- .github/ISSUE_TEMPLATE/bug_report.md | 39 ------------- .github/ISSUE_TEMPLATE/bug_report.yml | 67 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 27 --------- .github/ISSUE_TEMPLATE/feature_request.yml | 40 +++++++++++++ 4 files changed, 107 insertions(+), 66 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8139c361..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Bug Report -about: Report broken or incorrect behaviour -labels: bug ---- - -## Summary - - - -## Reproduction Steps - - - -## Expected Results - - - -## Actual Results - - - -## Intents - - - -## Checklist - - - -- [ ] I have searched the open issues for duplicates. -- [ ] I have shown the entire traceback, if possible. -- [ ] I have removed my token from display, if visible. -- [ ] I have provided the intents that my bot is using. - -## System Information - - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..54b2e8a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,67 @@ +name: Bug Report +about: Report broken or incorrect behaviour +labels: bug +issue_body: true +inputs: + - type: description + attributes: + value: > + Thanks for taking the time to fill out a bug. + If you want real-time support, consider joining our Discord instead at https://discord.gg/r3sSKJJ instead. + + Please note that this form is for bugs only! + - type: input + attributes: + label: Summary + description: A simple summary of your bug report + required: true + - type: textarea + attributes: + label: Reproduction Steps + required: true + description: > + What you did to make it happen. + Ideally there should be a short code snippet in this section to help reproduce the bug. + - type: textarea + attributes: + label: Expected Results + required: true + description: > + What did you expect to happen? + - type: textarea + attributes: + label: Actual Results + required: true + description: > + What actually happened? + - type: input + attributes: + label: Intents + required: true + description: > + What intents are you using for your bot? + This is the `discord.Intents` class you pass to the client. + - type: textarea + attributes: + label: System Information + required: true + description: > + Run `python -m discord -v` and paste this information below. + + This command required v1.1.0 or higher of the library. If this errors out then show some basic + information involving your system such as operating system and Python version. + - type: checkboxes + attributes: + label: Checklist + required: true + description: > + Let's make sure you've properly done due dilligence when reporting this issue! + choices: + - label: I have searched the open issues for duplicates. + required: true + - label: I have shown the entire traceback, if possible. + required: true + - label: I have removed my token from display, if visible. + required: true + - label: I have provided the intents that my bot is using. + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 4badd49e..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Feature Request -about: Suggest a feature for this library -labels: feature request ---- - -## The Problem - - - -## The Ideal Solution - - - -## The Current Solution - - - -## Summary - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..598ac705 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,40 @@ +name: Feature Request +about: Suggest a feature for this library +labels: feature request +issue_body: true +inputs: + - type: input + attributes: + label: Summary + description: > + A short summary of what your feature request is. + required: true + - type: dropdown + attributes: + label: What is the feature request for? + required: true + choices: + - The core library + - discord.ext.commands + - discord.ext.tasks + - The documentation + - type: textarea + attributes: + label: The Problem + description: > + What problem is your feature trying to solve? + What becomes easier or possible when this feature is implemented? + required: true + - type: textarea + attributes: + label: The Ideal Solution + description: > + What is your ideal solution to the problem? + What would you like this feature to do? + required: true + - type: textarea + attributes: + label: The Current Solution + description: > + What is the current solution to the problem, if any? + required: false From 3030f8ce0d030dcf974fb458a915110f46ba2eee Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 2 Feb 2021 03:49:06 -0500 Subject: [PATCH 020/171] Fix some validation errors with the template yaml --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 54b2e8a8..5fca7b23 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -53,7 +53,6 @@ inputs: - type: checkboxes attributes: label: Checklist - required: true description: > Let's make sure you've properly done due dilligence when reporting this issue! choices: @@ -63,5 +62,3 @@ inputs: required: true - label: I have removed my token from display, if visible. required: true - - label: I have provided the intents that my bot is using. - required: true From adae90400f9db9bec3cdeaa251f743cf7c6c727c Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 2 Feb 2021 03:57:09 -0500 Subject: [PATCH 021/171] Some grammar nits --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5fca7b23..779f065c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,7 +7,7 @@ inputs: attributes: value: > Thanks for taking the time to fill out a bug. - If you want real-time support, consider joining our Discord instead at https://discord.gg/r3sSKJJ instead. + If you want real-time support, consider joining our Discord at https://discord.gg/r3sSKJJ instead. Please note that this form is for bugs only! - type: input diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5336b869..7934e4a8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,5 +4,5 @@ contact_links: about: Ask questions and discuss with other users of the library. url: https://github.com/Rapptz/discord.py/discussions - name: Discord Server - about: Use our official Discord server to ask help and questions as well. + about: Use our official Discord server to ask for help and questions as well. url: https://discord.gg/r3sSKJJ From af6725694916c14187270d6dbc84a8fd8661a8bc Mon Sep 17 00:00:00 2001 From: Peter Delevoryas Date: Sat, 6 Feb 2021 02:42:22 -0800 Subject: [PATCH 022/171] Ensure Message.call is None by default `Message` has an attribute `call` which is claimed to have type `Optional[CallMessage]`. But `Message` doesn't actually ensure that `call` is initialized to a value in `__init__`. This commit fixes that inconsistency. --- discord/message.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/message.py b/discord/message.py index d024c4b3..7df7f6c9 100644 --- a/discord/message.py +++ b/discord/message.py @@ -504,6 +504,7 @@ class Message(Hashable): self.application = data.get('application') self.activity = data.get('activity') self.channel = channel + self.call = None self._edited_timestamp = utils.parse_time(data['edited_timestamp']) self.type = try_enum(MessageType, data['type']) self.pinned = data['pinned'] From 68eb844d48be4aaeb55a264944b1a7164329ee75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20N=C3=B8rgaard?= Date: Sun, 7 Feb 2021 10:32:33 +0000 Subject: [PATCH 023/171] [commands] Add discord.Guild converter and GuildNotFound error * Add discord.Guild converter and GuildNotFound error * note for lack of disambiguation in Guilds with duplicate names, and removed the possibility of returning None * edited converter to use `utils.get` over `utils.find` and docs edited with Converter and Exception. --- discord/ext/commands/converter.py | 29 ++++++++++++++++++++++++++++- discord/ext/commands/errors.py | 17 +++++++++++++++++ docs/ext/commands/api.rst | 6 ++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 27991eca..db686a26 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -40,6 +40,7 @@ __all__ = ( 'PartialMessageConverter', 'TextChannelConverter', 'InviteConverter', + 'GuildConverter', 'RoleConverter', 'GameConverter', 'ColourConverter', @@ -282,7 +283,7 @@ class PartialMessageConverter(Converter): if not channel: raise ChannelNotFound(channel_id) return discord.PartialMessage(channel=channel, id=message_id) - + class MessageConverter(PartialMessageConverter): """Converts to a :class:`discord.Message`. @@ -524,6 +525,32 @@ class InviteConverter(Converter): except Exception as exc: raise BadInviteArgument() from exc +class GuildConverter(IDConverter): + """Converts to a :class:`~discord.Guild`. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by name. (There is no disambiguation for Guilds with multiple matching names). + + .. versionadded:: 1.7 + """ + + async def convert(self, ctx, argument): + match = self._get_id_match(argument) + result = None + + if match is not None: + guild_id = int(match.group(1)) + result = ctx.bot.get_guild(guild_id) + + if result is None: + result = discord.utils.get(ctx.bot.guilds, name=argument) + + if result is None: + raise GuildNotFound(argument) + return result + class EmojiConverter(IDConverter): """Converts to a :class:`~discord.Emoji`. diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index aa4c585b..d533d96a 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -45,6 +45,7 @@ __all__ = ( 'NotOwner', 'MessageNotFound', 'MemberNotFound', + 'GuildNotFound', 'UserNotFound', 'ChannelNotFound', 'ChannelNotReadable', @@ -230,6 +231,22 @@ class MemberNotFound(BadArgument): self.argument = argument super().__init__('Member "{}" not found.'.format(argument)) +class GuildNotFound(BadArgument): + """Exception raised when the guild provided was not found in the bot's cache. + + This inherits from :exc:`BadArgument` + + .. versionadded:: 1.7 + + Attributes + ----------- + argument: :class:`str` + The guild supplied by the called that was not found + """ + def __init__(self, argument): + self.argument = argument + super().__init__('Guild "{}" not found.'.format(argument)) + class UserNotFound(BadArgument): """Exception raised when the user provided was not found in the bot's cache. diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 86ceeb2a..f66c2958 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -296,6 +296,9 @@ Converters .. autoclass:: discord.ext.commands.InviteConverter :members: +.. autoclass:: discord.ext.commands.GuildConverter + :members: + .. autoclass:: discord.ext.commands.RoleConverter :members: @@ -410,6 +413,9 @@ Exceptions .. autoexception:: discord.ext.commands.MemberNotFound :members: +.. autoexception:: discord.ext.commands.GuildNotFound + :members: + .. autoexception:: discord.ext.commands.UserNotFound :members: From a0404807d5e20ad6a5fae52e5d3a51cbba8e0c63 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 7 Feb 2021 07:18:56 -0500 Subject: [PATCH 024/171] [commands] Add support for rgb function in ColourConverter This also adds support for 3 digit hex. Fixes #6374 --- discord/ext/commands/converter.py | 71 +++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index db686a26..89b87f80 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -449,29 +449,76 @@ class ColourConverter(Converter): - ``0x`` - ``#`` - ``0x#`` + - ``rgb(, , )`` - Any of the ``classmethod`` in :class:`Colour` - The ``_`` in the name can be optionally replaced with spaces. + Like CSS, ```` can be either 0-255 or 0-100% and ```` can be + either a 6 digit hex number or a 3 digit hex shortcut (e.g. #fff). + .. versionchanged:: 1.5 Raise :exc:`.BadColourArgument` instead of generic :exc:`.BadArgument` - """ - async def convert(self, ctx, argument): - arg = argument.replace('0x', '').lower() - if arg[0] == '#': - arg = arg[1:] + .. versionchanged:: 1.7 + Added support for ``rgb`` function and 3-digit hex shortcuts + """ + + RGB_REGEX = re.compile(r'rgb\s*\((?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*\)') + + def parse_hex_number(self, argument): + arg = ''.join(i * 2 for i in argument) if len(argument) == 3 else argument try: value = int(arg, base=16) if not (0 <= value <= 0xFFFFFF): - raise BadColourArgument(arg) - return discord.Colour(value=value) + raise BadColourArgument(argument) except ValueError: - arg = arg.replace(' ', '_') - method = getattr(discord.Colour, arg, None) - if arg.startswith('from_') or method is None or not inspect.ismethod(method): - raise BadColourArgument(arg) - return method() + raise BadColourArgument(argument) + else: + return discord.Color(value=value) + + def parse_rgb_number(self, argument, number): + if number[-1] == '%': + value = int(number[:-1]) + if not (0 <= value <= 100): + raise BadColourArgument(argument) + return round(255 * (value / 100)) + + value = int(number) + if not (0 <= value <= 255): + raise BadColourArgument(argument) + return value + + def parse_rgb(self, argument, *, regex=RGB_REGEX): + match = regex.match(argument) + if match is None: + raise BadColourArgument(argument) + + red = self.parse_rgb_number(argument, match.group('r')) + green = self.parse_rgb_number(argument, match.group('g')) + blue = self.parse_rgb_number(argument, match.group('b')) + return discord.Color.from_rgb(red, green, blue) + + async def convert(self, ctx, argument): + if argument[0] == '#': + return self.parse_hex_number(argument[1:]) + + if argument[0:2] == '0x': + rest = argument[2:] + # Legacy backwards compatible syntax + if rest.startswith('#'): + return self.parse_hex_number(rest[1:]) + return self.parse_hex_number(rest) + + arg = argument.lower() + if arg[0:3] == 'rgb': + return self.parse_rgb(arg) + + arg = arg.replace(' ', '_') + method = getattr(discord.Colour, arg, None) + if arg.startswith('from_') or method is None or not inspect.ismethod(method): + raise BadColourArgument(arg) + return method() ColorConverter = ColourConverter From 491b4262054b9fde45f302fc51dbfff469d26b7e Mon Sep 17 00:00:00 2001 From: SuzuZusu <79049600+SuzuZusu@users.noreply.github.com> Date: Wed, 17 Feb 2021 04:16:38 -0500 Subject: [PATCH 025/171] Fix documentation with reference in GroupChannel.permissions_for --- discord/channel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index 9ab5d226..db4b566d 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1248,8 +1248,8 @@ class GroupChannel(discord.abc.Messageable, Hashable): This returns all the Text related permissions set to ``True`` except: - - send_tts_messages: You cannot send TTS messages in a DM. - - manage_messages: You cannot delete others messages in a DM. + - :attr:`~Permissions.send_tts_messages`: You cannot send TTS messages in a DM. + - :attr:`~Permissions.manage_messages`: You cannot delete others messages in a DM. This also checks the kick_members permission if the user is the owner. From 08d45cc2abba781245e3f5a2c356048e343f2335 Mon Sep 17 00:00:00 2001 From: Michael H Date: Wed, 17 Feb 2021 07:33:17 -0500 Subject: [PATCH 026/171] Update docs to be clearer about discord limitations --- docs/api.rst | 8 ++++++++ docs/intents.rst | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 9990d036..0421d5a9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -478,6 +478,14 @@ to handle it, which defaults to print a traceback and ignoring the exception. This requires :attr:`Intents.reactions` to be enabled. + .. note:: + + This doesn't require :attr:`Intents.members` within a guild context, + but due to Discord not providing updated user information in a direct message + it's required for direct messages to receive this event. + Consider using :func:`on_raw_reaction_add` if you need this and do not otherwise want + to enable the members intent. + :param reaction: The current state of the reaction. :type reaction: :class:`Reaction` :param user: The user who added the reaction. diff --git a/docs/intents.rst b/docs/intents.rst index 8ba036c9..38264b19 100644 --- a/docs/intents.rst +++ b/docs/intents.rst @@ -118,8 +118,9 @@ It should be noted that certain things do not need a member cache since Discord - :func:`on_message` will have :attr:`Message.author` be a member even if cache is disabled. - :func:`on_voice_state_update` will have the ``member`` parameter be a member even if cache is disabled. -- :func:`on_reaction_add` will have the ``user`` parameter be a member even if cache is disabled. -- :func:`on_raw_reaction_add` will have :attr:`RawReactionActionEvent.member` be a member even if cache is disabled. +- :func:`on_reaction_add` will have the ``user`` parameter be a member when in a guild even if cache is disabled. +- :func:`on_raw_reaction_add` will have :attr:`RawReactionActionEvent.member` be a member when in a guild even if cache is disabled. +- The reaction add events do not contain additional information when in direct messages. This is a Discord limitation. - The reaction removal events do not have the member information. This is a Discord limitation. Other events that take a :class:`Member` will require the use of the member cache. If absolute accuracy over the member cache is desirable, then it is advisable to have the :attr:`Intents.members` intent enabled. From 16e0adb315ff378b19ab32940ce26be3d78ac0d6 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Wed, 17 Feb 2021 22:48:09 -0600 Subject: [PATCH 027/171] [commands] Handle positional-only parameters in bot commands --- discord/ext/commands/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index b17f878c..baf2dca5 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -523,7 +523,7 @@ class Command(_BaseCommand): # The greedy converter is simple -- it keeps going until it fails in which case, # it undos the view ready for the next parameter to use instead if type(converter) is converters._Greedy: - if param.kind == param.POSITIONAL_OR_KEYWORD: + if param.kind == param.POSITIONAL_OR_KEYWORD or param.kind == param.POSITIONAL_ONLY: return await self._transform_greedy_pos(ctx, param, required, converter.converter) elif param.kind == param.VAR_POSITIONAL: return await self._transform_greedy_var_pos(ctx, param, converter.converter) @@ -693,7 +693,7 @@ class Command(_BaseCommand): raise discord.ClientException(fmt.format(self)) for name, param in iterator: - if param.kind == param.POSITIONAL_OR_KEYWORD: + if param.kind == param.POSITIONAL_OR_KEYWORD or param.kind == param.POSITIONAL_ONLY: transformed = await self.transform(ctx, param) args.append(transformed) elif param.kind == param.KEYWORD_ONLY: From 0cd1a88316a9d35d54da04b6992cb26e7b0e7527 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Thu, 18 Feb 2021 00:23:58 -0500 Subject: [PATCH 028/171] Clarify Message.nonce documentation Fix #2451 --- discord/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/message.py b/discord/message.py index 7df7f6c9..bdb9e05d 100644 --- a/discord/message.py +++ b/discord/message.py @@ -406,7 +406,7 @@ class Message(Hashable): The actual contents of the message. nonce The value used by the discord guild and the client to verify that the message is successfully sent. - This is typically non-important. + This is not stored long term within Discord's servers and is only used ephemerally. embeds: List[:class:`Embed`] A list of embeds the message has. channel: Union[:class:`abc.Messageable`] From 88c23125ffd462e62084f2e1a1dd0f446bf127fc Mon Sep 17 00:00:00 2001 From: Rapptz Date: Thu, 18 Feb 2021 00:43:19 -0500 Subject: [PATCH 029/171] Allow CustomActivity emoji to be constructed as documented. Fix #4049 --- discord/activity.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/discord/activity.py b/discord/activity.py index c230154d..cf5192ad 100644 --- a/discord/activity.py +++ b/discord/activity.py @@ -693,8 +693,14 @@ class CustomActivity(BaseActivity): if emoji is None: self.emoji = emoji - else: + elif isinstance(emoji, dict): self.emoji = PartialEmoji.from_dict(emoji) + elif isinstance(emoji, str): + self.emoji = PartialEmoji(name=emoji) + elif isinstance(emoji, PartialEmoji): + self.emoji = emoji + else: + raise TypeError('Expected str, PartialEmoji, or None, received {0!r} instead.'.format(type(emoji))) @property def type(self): From fafc3d91854d4db615637344593c6979a12fa375 Mon Sep 17 00:00:00 2001 From: Riley Shaw <30989490+ShineyDev@users.noreply.github.com> Date: Thu, 18 Feb 2021 11:53:08 +0000 Subject: [PATCH 030/171] Fix ZeroDivisionError in DiscordVoiceWebSocket.average_latency --- discord/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/gateway.py b/discord/gateway.py index 9b85b866..f13276a1 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -876,7 +876,7 @@ class DiscordVoiceWebSocket: def average_latency(self): """:class:`list`: Average of last 20 HEARTBEAT latencies.""" heartbeat = self._keep_alive - if heartbeat is None: + if heartbeat is None or not heartbeat.recent_ack_latencies: return float('inf') return sum(heartbeat.recent_ack_latencies) / len(heartbeat.recent_ack_latencies) From 272339d5e6f36aa39b44ba350e82df4f05d2160d Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 28 Jul 2020 20:30:25 -0400 Subject: [PATCH 031/171] Add support for the new permission serialization scheme. --- discord/abc.py | 8 ++++---- discord/role.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index cf4b8ab4..05130487 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -168,15 +168,15 @@ class _Overwrites: def __init__(self, **kwargs): self.id = kwargs.pop('id') - self.allow = kwargs.pop('allow', 0) - self.deny = kwargs.pop('deny', 0) + self.allow = int(kwargs.pop('allow_new', 0)) + self.deny = int(kwargs.pop('deny_new', 0)) self.type = sys.intern(kwargs.pop('type')) def _asdict(self): return { 'id': self.id, - 'allow': self.allow, - 'deny': self.deny, + 'allow': str(self.allow), + 'deny': str(self.deny), 'type': self.type, } diff --git a/discord/role.py b/discord/role.py index 7c6e5223..735ad12b 100644 --- a/discord/role.py +++ b/discord/role.py @@ -188,7 +188,7 @@ class Role(Hashable): def _update(self, data): self.name = data['name'] - self._permissions = data.get('permissions', 0) + self._permissions = int(data.get('permissions_new', 0)) self.position = data.get('position', 0) self._colour = data.get('color', 0) self.hoist = data.get('hoist', False) @@ -340,7 +340,7 @@ class Role(Hashable): payload = { 'name': fields.get('name', self.name), - 'permissions': fields.get('permissions', self.permissions).value, + 'permissions': str(fields.get('permissions', self.permissions).value), 'color': colour.value, 'hoist': fields.get('hoist', self.hoist), 'mentionable': fields.get('mentionable', self.mentionable) From a8f44174bafed3989ec2959a62b89006f4a9e9a1 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Thu, 18 Feb 2021 07:28:37 -0500 Subject: [PATCH 032/171] Add Permissions.use_slash_commands --- discord/permissions.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/discord/permissions.py b/discord/permissions.py index 0a4e9438..68d2a496 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -403,9 +403,13 @@ class Permissions(BaseFlags): """:class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis.""" return 1 << 30 - # 1 unused + @flag_value + def use_slash_commands(self): + """:class:`bool`: Returns ``True`` if a user can use slash commands. - # after these 32 bits, there's 21 more unused ones technically + .. versionadded:: 1.7 + """ + return 1 << 31 def augment_from_permissions(cls): cls.VALID_NAMES = set(Permissions.VALID_FLAGS) From 0203d0339b2e09ea78455b4c46e4651f7afe57da Mon Sep 17 00:00:00 2001 From: Kowlin Date: Sat, 20 Feb 2021 00:49:49 +0100 Subject: [PATCH 033/171] Update issue templates for migration --- .github/ISSUE_TEMPLATE/bug_report.yml | 22 ++++++++++++++-------- .github/ISSUE_TEMPLATE/feature_request.yml | 12 +++++++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 779f065c..adebc90b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -2,8 +2,8 @@ name: Bug Report about: Report broken or incorrect behaviour labels: bug issue_body: true -inputs: - - type: description +body: + - type: markdown attributes: value: > Thanks for taking the time to fill out a bug. @@ -14,48 +14,54 @@ inputs: attributes: label: Summary description: A simple summary of your bug report + validations: required: true - type: textarea attributes: label: Reproduction Steps - required: true description: > What you did to make it happen. Ideally there should be a short code snippet in this section to help reproduce the bug. + validations: + required: true - type: textarea attributes: label: Expected Results - required: true description: > What did you expect to happen? + validations: + required: true - type: textarea attributes: label: Actual Results - required: true description: > What actually happened? + validations: + required: true - type: input attributes: label: Intents - required: true description: > What intents are you using for your bot? This is the `discord.Intents` class you pass to the client. + validations: + required: true - type: textarea attributes: label: System Information - required: true description: > Run `python -m discord -v` and paste this information below. This command required v1.1.0 or higher of the library. If this errors out then show some basic information involving your system such as operating system and Python version. + validations: + required: true - type: checkboxes attributes: label: Checklist description: > Let's make sure you've properly done due dilligence when reporting this issue! - choices: + options: - label: I have searched the open issues for duplicates. required: true - label: I have shown the entire traceback, if possible. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 598ac705..1e338978 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -2,28 +2,32 @@ name: Feature Request about: Suggest a feature for this library labels: feature request issue_body: true -inputs: +body: - type: input attributes: label: Summary description: > A short summary of what your feature request is. + validations: required: true - type: dropdown attributes: + multiple: false label: What is the feature request for? - required: true - choices: + options: - The core library - discord.ext.commands - discord.ext.tasks - The documentation + validations: + required: true - type: textarea attributes: label: The Problem description: > What problem is your feature trying to solve? What becomes easier or possible when this feature is implemented? + validations: required: true - type: textarea attributes: @@ -31,10 +35,12 @@ inputs: description: > What is your ideal solution to the problem? What would you like this feature to do? + validations: required: true - type: textarea attributes: label: The Current Solution description: > What is the current solution to the problem, if any? + validations: required: false From 27c7fb6aed4b05fc9f5d9548c6cf371f884f9bed Mon Sep 17 00:00:00 2001 From: sudosnok Date: Sun, 21 Feb 2021 00:29:19 +0000 Subject: [PATCH 034/171] Add User.mutual_guilds --- discord/user.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/discord/user.py b/discord/user.py index dfdbb56e..c0095035 100644 --- a/discord/user.py +++ b/discord/user.py @@ -707,6 +707,18 @@ class User(BaseUser, discord.abc.Messageable): """ return self._state._get_private_channel_by_user(self.id) + @property + def mutual_guilds(self): + """List[:class:`Guild`]: The guilds that the user shares with the client. + + .. note:: + + This will only return mutual guilds within the client's internal cache. + + .. versionadded:: 1.7 + """ + return [guild for guild in self._state._guilds.values() if guild.get_member(self.id)] + async def create_dm(self): """Creates a :class:`DMChannel` with this user. From b86073fac3776cfc64f9bce07cbd51368ca2c836 Mon Sep 17 00:00:00 2001 From: Sebastian Law <44045823+SebbyLaw@users.noreply.github.com> Date: Sat, 20 Feb 2021 17:47:29 -0800 Subject: [PATCH 035/171] Document behavior of on_disconnect --- docs/api.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 0421d5a9..35da0571 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -200,11 +200,11 @@ to handle it, which defaults to print a traceback and ignoring the exception. .. function:: on_disconnect() - Called when the client has disconnected from Discord. This could happen either through - the internet being disconnected, explicit calls to logout, or Discord terminating the connection - one way or the other. + Called when the client has disconnected from Discord, or a connection attempt to Discord has failed. + This could happen either through the internet being disconnected, explicit calls to logout, + or Discord terminating the connection one way or the other. - This function can be called many times. + This function can be called many times without a corresponding :func:`on_connect` call. .. function:: on_shard_disconnect(shard_id) From 66b834b3323c8d9403b2cb8f29b2e00d633bb1cc Mon Sep 17 00:00:00 2001 From: Maya <17090652+XuaTheGrate@users.noreply.github.com> Date: Sun, 21 Feb 2021 18:12:30 +1300 Subject: [PATCH 036/171] Document BanEntry --- discord/guild.py | 19 ++++++------------- docs/api.rst | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index a13c3336..caba7376 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1346,9 +1346,7 @@ class Guild(Hashable): async def fetch_ban(self, user): """|coro| - Retrieves the :class:`BanEntry` for a user, which is a namedtuple - with a ``user`` and ``reason`` field. See :meth:`bans` for more - information. + Retrieves the :class:`BanEntry` for a user. You must have the :attr:`~Permissions.ban_members` permission to get this information. @@ -1369,8 +1367,8 @@ class Guild(Hashable): Returns ------- - BanEntry - The BanEntry object for the specified user. + :class:`BanEntry` + The :class:`BanEntry` object for the specified user. """ data = await self._state.http.get_ban(user.id, self.id) return BanEntry( @@ -1381,12 +1379,7 @@ class Guild(Hashable): async def bans(self): """|coro| - Retrieves all the users that are banned from the guild. - - This coroutine returns a :class:`list` of BanEntry objects, which is a - namedtuple with a ``user`` field to denote the :class:`User` - that got banned along with a ``reason`` field specifying - why the user was banned that could be set to ``None``. + Retrieves all the users that are banned from the guild as a :class:`list` of :class:`BanEntry`. You must have the :attr:`~Permissions.ban_members` permission to get this information. @@ -1400,8 +1393,8 @@ class Guild(Hashable): Returns -------- - List[BanEntry] - A list of BanEntry objects. + List[:class:`BanEntry`] + A list of :class:`BanEntry` objects. """ data = await self._state.http.get_bans(self.id) diff --git a/docs/api.rst b/docs/api.rst index 35da0571..9146b318 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2867,6 +2867,22 @@ Guild .. automethod:: audit_logs :async-for: +.. class:: BanEntry + + A namedtuple which represents a ban returned from :meth:`~Guild.bans`. + + .. attribute:: reason + + The reason this user was banned. + + :type: Optional[:class:`str`] + .. attribute:: user + + The :class:`User` that was banned. + + :type: :class:`User` + + Integration ~~~~~~~~~~~~ From 6c08f3d3441cf660de910b0f3c49c3385f4469f4 Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Sun, 21 Feb 2021 05:16:37 +0000 Subject: [PATCH 037/171] Add "Secret" channel/emoji example --- examples/secret.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 examples/secret.py diff --git a/examples/secret.py b/examples/secret.py new file mode 100644 index 00000000..1e90d8f5 --- /dev/null +++ b/examples/secret.py @@ -0,0 +1,96 @@ +import typing + +import discord +from discord.ext import commands + +bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!") + +# the `hidden` keyword argument hides it from the help command. +@bot.group(hidden=True) +async def secret(ctx: commands.Context): + """What is this "secret" you speak of?""" + if ctx.invoked_subcommand is None: + await ctx.send('Shh!', delete_after=5) + +def create_overwrites(ctx, *objects): + """This is just a helper function that creates the overwrites for the + voice/text channels. + + A `discord.PermissionOverwrite` allows you to determine the permissions + of an object, whether it be a `discord.Role` or a `discord.Member`. + + In this case, the `view_channel` permission is being used to hide the channel + from being viewed by whoever does not meet the criteria, thus creating a + secret channel. + """ + + # a dict comprehension is being utilised here to set the same permission overwrites + # for each `discord.Role` or `discord.Member`. + overwrites = { + obj: discord.PermissionOverwrite(view_channel=True) + for obj in objects + } + + # prevents the default role (@everyone) from viewing the channel + # if it isn't already allowed to view the channel. + overwrites.setdefault(ctx.guild.default_role, discord.PermissionOverwrite(view_channel=False)) + + # makes sure the client is always allowed to view the channel. + overwrites[ctx.guild.me] = discord.PermissionOverwrite(view_channel=True) + + return overwrites + +# since these commands rely on guild related features, +# it is best to lock it to be guild-only. +@secret.command() +@commands.guild_only() +async def text(ctx: commands.Context, name: str, *objects: typing.Union[discord.Role, discord.Member]): + """This makes a text channel with a specified name + that is only visible to roles or members that are specified. + """ + + overwrites = create_overwrites(ctx, *objects) + + await ctx.guild.create_text_channel( + name, + overwrites=overwrites, + topic='Top secret text channel. Any leakage of this channel may result in serious trouble.', + reason='Very secret business.', + ) + +@secret.command() +@commands.guild_only() +async def voice(ctx: commands.Context, name: str, *objects: typing.Union[discord.Role, discord.Member]): + """This does the same thing as the `text` subcommand + but instead creates a voice channel. + """ + + overwrites = create_overwrites(ctx, *objects) + + await ctx.guild.create_voice_channel( + name, + overwrites=overwrites, + reason='Very secret business.' + ) + +@secret.command() +@commands.guild_only() +async def emoji(ctx: commands.Context, emoji: discord.PartialEmoji, *roles: discord.Role): + """This clones a specified emoji that only specified roles + are allowed to use. + """ + + # fetch the emoji asset and read it as bytes. + emoji_bytes = await emoji.url.read() + + # the key parameter here is `roles`, which controls + # what roles are able to use the emoji. + await ctx.guild.create_custom_emoji( + name=emoji.name, + image=emoji_bytes, + roles=roles, + reason='Very secret business.' + ) + + +bot.run('token') From ceab8ff638b373cb7769ce6fa9708bd350d7a71d Mon Sep 17 00:00:00 2001 From: Sebastian Law <44045823+SebbyLaw@users.noreply.github.com> Date: Sun, 21 Feb 2021 04:19:10 -0800 Subject: [PATCH 038/171] [tasks] make __call__ actually appear in the docs --- discord/ext/tasks/__init__.py | 2 +- docs/ext/tasks/index.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 9dbc7f9c..94917ca0 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -161,7 +161,7 @@ class Loop: return self._next_iteration async def __call__(self, *args, **kwargs): - """|coro| + r"""|coro| Calls the internal callback that the task holds. diff --git a/docs/ext/tasks/index.rst b/docs/ext/tasks/index.rst index 13dfbc5e..55f60932 100644 --- a/docs/ext/tasks/index.rst +++ b/docs/ext/tasks/index.rst @@ -139,5 +139,6 @@ API Reference .. autoclass:: discord.ext.tasks.Loop() :members: + :special-members: __call__ .. autofunction:: discord.ext.tasks.loop From a0c1d6f6c5bdc44c144a650dfdeea85ee59f627a Mon Sep 17 00:00:00 2001 From: Sebastian Law <44045823+SebbyLaw@users.noreply.github.com> Date: Sun, 21 Feb 2021 04:32:11 -0800 Subject: [PATCH 039/171] Fix backslashes showing up in the docs --- discord/utils.py | 4 ++-- docs/api.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/utils.py b/discord/utils.py index 775907ff..5c8e4960 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -492,8 +492,8 @@ def escape_markdown(text, *, as_needed=False, ignore_links=True): as_needed: :class:`bool` Whether to escape the markdown characters as needed. This means that it does not escape extraneous characters if it's - not necessary, e.g. ``**hello**`` is escaped into ``\*\*hello**`` - instead of ``\*\*hello\*\*``. Note however that this can open + not necessary, e.g. ``**hello**`` is escaped into ``**hello**`` + instead of ``**hello**``. Note however that this can open you up to some clever syntax abuse. Defaults to ``False``. ignore_links: :class:`bool` Whether to leave links alone when escaping markdown. For example, diff --git a/docs/api.rst b/docs/api.rst index 9146b318..dc016fba 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -250,7 +250,7 @@ to handle it, which defaults to print a traceback and ignoring the exception. :param shard_id: The shard ID that has resumed. :type shard_id: :class:`int` -.. function:: on_error(event, \*args, \*\*kwargs) +.. function:: on_error(event, *args, **kwargs) Usually when an event raises an uncaught exception, a traceback is printed to stderr and the exception is ignored. If you want to From abfc07f968dc777257c2c2ee509bf129e006539d Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 21 Feb 2021 07:35:31 -0500 Subject: [PATCH 040/171] Fix up previous PR mistake with intentional backslashes --- discord/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/utils.py b/discord/utils.py index 5c8e4960..775907ff 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -492,8 +492,8 @@ def escape_markdown(text, *, as_needed=False, ignore_links=True): as_needed: :class:`bool` Whether to escape the markdown characters as needed. This means that it does not escape extraneous characters if it's - not necessary, e.g. ``**hello**`` is escaped into ``**hello**`` - instead of ``**hello**``. Note however that this can open + not necessary, e.g. ``**hello**`` is escaped into ``\*\*hello**`` + instead of ``\*\*hello\*\*``. Note however that this can open you up to some clever syntax abuse. Defaults to ``False``. ignore_links: :class:`bool` Whether to leave links alone when escaping markdown. For example, From cc55a28be51b69c298614158ef49e93d132021ca Mon Sep 17 00:00:00 2001 From: Sebastian Law <44045823+SebbyLaw@users.noreply.github.com> Date: Mon, 22 Feb 2021 05:51:34 -0800 Subject: [PATCH 041/171] Fix typo in Guild.fetch_member docs --- discord/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/guild.py b/discord/guild.py index caba7376..cd4dde98 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1317,7 +1317,7 @@ class Guild(Hashable): async def fetch_member(self, member_id): """|coro| - Retreives a :class:`Member` from a guild ID, and a member ID. + Retrieves a :class:`Member` from a guild ID, and a member ID. .. note:: From 1cbc5377348e1ca1d685b5bb821d4198c13f53c1 Mon Sep 17 00:00:00 2001 From: Kaylynn Morgan <51037748+kaylynn234@users.noreply.github.com> Date: Tue, 23 Feb 2021 19:29:04 +1100 Subject: [PATCH 042/171] [commands] Allow relative paths when handling extensions --- discord/ext/commands/bot.py | 40 ++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index ed3ae078..c9ec03a6 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -624,7 +624,13 @@ class BotBase(GroupMixin): else: self.__extensions[key] = lib - def load_extension(self, name): + def _resolve_name(self, name, package): + try: + return importlib.util.resolve_name(name, package) + except ImportError: + raise errors.ExtensionNotFound(name) + + def load_extension(self, name, *, package=None): """Loads an extension. An extension is a python module that contains commands, cogs, or @@ -640,11 +646,19 @@ class BotBase(GroupMixin): The extension name to load. It must be dot separated like regular Python imports if accessing a sub-module. e.g. ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when loading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 Raises -------- ExtensionNotFound The extension could not be imported. + This is also raised if the name of the extension could not + be resolved using the provided ``package`` parameter. ExtensionAlreadyLoaded The extension is already loaded. NoEntryPointError @@ -653,6 +667,7 @@ class BotBase(GroupMixin): The extension or its setup function had an execution error. """ + name = self._resolve_name(name, package) if name in self.__extensions: raise errors.ExtensionAlreadyLoaded(name) @@ -662,7 +677,7 @@ class BotBase(GroupMixin): self._load_from_module_spec(spec, name) - def unload_extension(self, name): + def unload_extension(self, name, *, package=None): """Unloads an extension. When the extension is unloaded, all commands, listeners, and cogs are @@ -679,13 +694,23 @@ class BotBase(GroupMixin): The extension name to unload. It must be dot separated like regular Python imports if accessing a sub-module. e.g. ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when unloading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 Raises ------- + ExtensionNotFound + The name of the extension could not + be resolved using the provided ``package`` parameter. ExtensionNotLoaded The extension was not loaded. """ + name = self._resolve_name(name, package) lib = self.__extensions.get(name) if lib is None: raise errors.ExtensionNotLoaded(name) @@ -693,7 +718,7 @@ class BotBase(GroupMixin): self._remove_module_references(lib.__name__) self._call_module_finalizers(lib, name) - def reload_extension(self, name): + def reload_extension(self, name, *, package=None): """Atomically reloads an extension. This replaces the extension with the same extension, only refreshed. This is @@ -707,6 +732,12 @@ class BotBase(GroupMixin): The extension name to reload. It must be dot separated like regular Python imports if accessing a sub-module. e.g. ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when reloading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 Raises ------- @@ -714,12 +745,15 @@ class BotBase(GroupMixin): The extension was not loaded. ExtensionNotFound The extension could not be imported. + This is also raised if the name of the extension could not + be resolved using the provided ``package`` parameter. NoEntryPointError The extension does not have a setup function. ExtensionFailed The extension setup function had an execution error. """ + name = self._resolve_name(name, package) lib = self.__extensions.get(name) if lib is None: raise errors.ExtensionNotLoaded(name) From 427e387a2f487cf27d4a7ed5b49cc4c4bcae58ed Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Tue, 23 Feb 2021 08:36:37 +0000 Subject: [PATCH 043/171] Deprecate non-bot methods --- discord/calls.py | 29 +++++++++++++++++++---- discord/channel.py | 11 ++++++++- discord/client.py | 5 ++++ discord/guild.py | 3 +++ discord/message.py | 6 +++++ discord/relationship.py | 9 +++++++ discord/user.py | 52 +++++++++++++++++++++++++++++++++++++++++ docs/api.rst | 16 +++++++++++++ 8 files changed, 126 insertions(+), 5 deletions(-) diff --git a/discord/calls.py b/discord/calls.py index 80fc8954..8cc4cdcb 100644 --- a/discord/calls.py +++ b/discord/calls.py @@ -36,6 +36,8 @@ class CallMessage: This is only received in cases where the message type is equivalent to :attr:`MessageType.call`. + .. deprecated:: 1.7 + Attributes ----------- ended_timestamp: Optional[:class:`datetime.datetime`] @@ -53,12 +55,18 @@ class CallMessage: @property def call_ended(self): - """:class:`bool`: Indicates if the call has ended.""" + """:class:`bool`: Indicates if the call has ended. + + .. deprecated:: 1.7 + """ return self.ended_timestamp is not None @property def channel(self): - r""":class:`GroupChannel`\: The private channel associated with this message.""" + r""":class:`GroupChannel`\: The private channel associated with this message. + + .. deprecated:: 1.7 + """ return self.message.channel @property @@ -68,6 +76,8 @@ class CallMessage: If the call has not ended then the current duration will be returned. + .. deprecated:: 1.7 + Returns --------- :class:`datetime.timedelta` @@ -83,6 +93,8 @@ class GroupCall: This is accompanied with a :class:`CallMessage` denoting the information. + .. deprecated:: 1.7 + Attributes ----------- call: :class:`CallMessage` @@ -122,7 +134,10 @@ class GroupCall: @property def connected(self): - """List[:class:`User`]: A property that returns all users that are currently in this call.""" + """List[:class:`User`]: A property that returns all users that are currently in this call. + + .. deprecated:: 1.7 + """ ret = [u for u in self.channel.recipients if self.voice_state_for(u) is not None] me = self.channel.me if self.voice_state_for(me) is not None: @@ -132,15 +147,21 @@ class GroupCall: @property def channel(self): - r""":class:`GroupChannel`\: Returns the channel the group call is in.""" + r""":class:`GroupChannel`\: Returns the channel the group call is in. + + .. deprecated:: 1.7 + """ return self.call.channel + @utils.deprecated() def voice_state_for(self, user): """Retrieves the :class:`VoiceState` for a specified :class:`User`. If the :class:`User` has no voice state then this function returns ``None``. + .. deprecated:: 1.7 + Parameters ------------ user: :class:`User` diff --git a/discord/channel.py b/discord/channel.py index db4b566d..7a5b9548 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -347,7 +347,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): bulk: :class:`bool` If ``True``, use bulk delete. Setting this to ``False`` is useful for mass-deleting a bot's own messages without :attr:`Permissions.manage_messages`. When ``True``, will - fall back to single delete if current account is a user bot, or if messages are + fall back to single delete if current account is a user bot (now deprecated), or if messages are older than two weeks. Raises @@ -1274,6 +1274,7 @@ class GroupChannel(discord.abc.Messageable, Hashable): return base + @utils.deprecated() async def add_recipients(self, *recipients): r"""|coro| @@ -1284,6 +1285,8 @@ class GroupChannel(discord.abc.Messageable, Hashable): add a recipient to the group, you must have a relationship with the user of type :attr:`RelationshipType.friend`. + .. deprecated:: 1.7 + Parameters ----------- \*recipients: :class:`User` @@ -1301,11 +1304,14 @@ class GroupChannel(discord.abc.Messageable, Hashable): for recipient in recipients: await req(self.id, recipient.id) + @utils.deprecated() async def remove_recipients(self, *recipients): r"""|coro| Removes recipients from this group. + .. deprecated:: 1.7 + Parameters ----------- \*recipients: :class:`User` @@ -1323,11 +1329,14 @@ class GroupChannel(discord.abc.Messageable, Hashable): for recipient in recipients: await req(self.id, recipient.id) + @utils.deprecated() async def edit(self, **fields): """|coro| Edits the group. + .. deprecated:: 1.7 + Parameters ----------- name: Optional[:class:`str`] diff --git a/discord/client.py b/discord/client.py index 22c25db7..85035535 100644 --- a/discord/client.py +++ b/discord/client.py @@ -495,6 +495,8 @@ class Client: Keyword argument that specifies if the account logging on is a bot token or not. + .. deprecated:: 1.7 + Raises ------ :exc:`.LoginFailure` @@ -1385,11 +1387,14 @@ class Client: data = await self.http.get_user(user_id) return User(state=self._connection, data=data) + @utils.deprecated() async def fetch_user_profile(self, user_id): """|coro| Gets an arbitrary user's profile. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. diff --git a/discord/guild.py b/discord/guild.py index cd4dde98..c51a4631 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1974,6 +1974,7 @@ class Guild(Hashable): payload['max_age'] = 0 return Invite(state=self._state, data=payload) + @utils.deprecated() def ack(self): """|coro| @@ -1981,6 +1982,8 @@ class Guild(Hashable): The user must not be a bot user. + .. deprecated:: 1.7 + Raises ------- HTTPException diff --git a/discord/message.py b/discord/message.py index bdb9e05d..535a401f 100644 --- a/discord/message.py +++ b/discord/message.py @@ -415,6 +415,9 @@ class Message(Hashable): call: Optional[:class:`CallMessage`] The call that the message refers to. This is only applicable to messages of type :attr:`MessageType.call`. + + .. deprecated:: 1.7 + reference: Optional[:class:`~discord.MessageReference`] The message that this message references. This is only applicable to messages of type :attr:`MessageType.pins_add`, crossposted messages created by a @@ -1244,6 +1247,7 @@ class Message(Hashable): """ await self._state.http.clear_reactions(self.channel.id, self.id) + @utils.deprecated() async def ack(self): """|coro| @@ -1251,6 +1255,8 @@ class Message(Hashable): The user must not be a bot user. + .. deprecated:: 1.7 + Raises ------- HTTPException diff --git a/discord/relationship.py b/discord/relationship.py index 83866f4e..0a9fffda 100644 --- a/discord/relationship.py +++ b/discord/relationship.py @@ -25,6 +25,7 @@ DEALINGS IN THE SOFTWARE. """ from .enums import RelationshipType, try_enum +from . import utils class Relationship: """Represents a relationship in Discord. @@ -32,6 +33,8 @@ class Relationship: A relationship is like a friendship, a person who is blocked, etc. Only non-bot accounts can have relationships. + .. deprecated:: 1.7 + Attributes ----------- user: :class:`User` @@ -50,11 +53,14 @@ class Relationship: def __repr__(self): return ''.format(self) + @utils.deprecated() async def delete(self): """|coro| Deletes the relationship. + .. deprecated:: 1.7 + Raises ------ HTTPException @@ -63,12 +69,15 @@ class Relationship: await self._state.http.remove_relationship(self.user.id) + @utils.deprecated() async def accept(self): """|coro| Accepts the relationship request. e.g. accepting a friend request. + .. deprecated:: 1.7 + Raises ------- HTTPException diff --git a/discord/user.py b/discord/user.py index c0095035..0770109a 100644 --- a/discord/user.py +++ b/discord/user.py @@ -33,6 +33,7 @@ from .enums import DefaultAvatar, RelationshipType, UserFlags, HypeSquadHouse, P from .errors import ClientException from .colour import Colour from .asset import Asset +from .utils import deprecated class Profile(namedtuple('Profile', 'flags user mutual_guilds connected_accounts premium_since')): __slots__ = () @@ -321,14 +322,22 @@ class ClientUser(BaseUser): Specifies if the user is a verified account. email: Optional[:class:`str`] The email the user used when registering. + + .. deprecated:: 1.7 + locale: Optional[:class:`str`] The IETF language tag used to identify the language the user is using. mfa_enabled: :class:`bool` Specifies if the user has MFA turned on and working. premium: :class:`bool` Specifies if the user is a premium user (e.g. has Discord Nitro). + + .. deprecated:: 1.7 + premium_type: Optional[:class:`PremiumType`] Specifies the type of premium a user has (e.g. Nitro or Nitro Classic). Could be None if the user is not premium. + + .. deprecated:: 1.7 """ __slots__ = BaseUser.__slots__ + \ ('email', 'locale', '_flags', 'verified', 'mfa_enabled', @@ -353,9 +362,12 @@ class ClientUser(BaseUser): self.premium = data.get('premium', False) self.premium_type = try_enum(PremiumType, data.get('premium_type', None)) + @deprecated() def get_relationship(self, user_id): """Retrieves the :class:`Relationship` if applicable. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -376,6 +388,8 @@ class ClientUser(BaseUser): def relationships(self): """List[:class:`User`]: Returns all the relationships that the user has. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -386,6 +400,8 @@ class ClientUser(BaseUser): def friends(self): r"""List[:class:`User`]: Returns all the users that the user is friends with. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -396,6 +412,8 @@ class ClientUser(BaseUser): def blocked(self): r"""List[:class:`User`]: Returns all the users that the user has blocked. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -410,6 +428,10 @@ class ClientUser(BaseUser): If a bot account is used then a password field is optional, otherwise it is required. + .. warning:: + + The user account-only fields are deprecated. + .. note:: To upload an avatar, a :term:`py:bytes-like object` must be passed in that @@ -501,6 +523,7 @@ class ClientUser(BaseUser): self._update(data) + @deprecated() async def create_group(self, *recipients): r"""|coro| @@ -508,6 +531,8 @@ class ClientUser(BaseUser): provided. These recipients must be have a relationship of type :attr:`RelationshipType.friend`. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -541,11 +566,14 @@ class ClientUser(BaseUser): data = await self._state.http.start_group(self.id, users) return GroupChannel(me=self, data=data, state=self._state) + @deprecated() async def edit_settings(self, **kwargs): """|coro| Edits the client user's settings. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -748,11 +776,14 @@ class User(BaseUser, discord.abc.Messageable): """ return self._state.user.get_relationship(self.id) + @deprecated() async def mutual_friends(self): """|coro| Gets all mutual friends of this user. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -773,9 +804,12 @@ class User(BaseUser, discord.abc.Messageable): mutuals = await state.http.get_mutual_friends(self.id) return [User(state=state, data=friend) for friend in mutuals] + @deprecated() def is_friend(self): """:class:`bool`: Checks if the user is your friend. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -785,9 +819,12 @@ class User(BaseUser, discord.abc.Messageable): return False return r.type is RelationshipType.friend + @deprecated() def is_blocked(self): """:class:`bool`: Checks if the user is blocked. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -797,11 +834,14 @@ class User(BaseUser, discord.abc.Messageable): return False return r.type is RelationshipType.blocked + @deprecated() async def block(self): """|coro| Blocks the user. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -816,11 +856,14 @@ class User(BaseUser, discord.abc.Messageable): await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value) + @deprecated() async def unblock(self): """|coro| Unblocks the user. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -834,11 +877,14 @@ class User(BaseUser, discord.abc.Messageable): """ await self._state.http.remove_relationship(self.id) + @deprecated() async def remove_friend(self): """|coro| Removes the user as a friend. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -852,11 +898,14 @@ class User(BaseUser, discord.abc.Messageable): """ await self._state.http.remove_relationship(self.id) + @deprecated() async def send_friend_request(self): """|coro| Sends the user a friend request. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. @@ -870,11 +919,14 @@ class User(BaseUser, discord.abc.Messageable): """ await self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator) + @deprecated() async def profile(self): """|coro| Gets the user's profile. + .. deprecated:: 1.7 + .. note:: This can only be used by non-bot accounts. diff --git a/docs/api.rst b/docs/api.rst index dc016fba..05b08909 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -894,6 +894,8 @@ to handle it, which defaults to print a traceback and ignoring the exception. Called when a :class:`Relationship` is added or removed from the :class:`ClientUser`. + .. deprecated:: 1.7 + :param relationship: The relationship that was added or removed. :type relationship: :class:`Relationship` @@ -902,6 +904,8 @@ to handle it, which defaults to print a traceback and ignoring the exception. Called when a :class:`Relationship` is updated, e.g. when you block a friend or a friendship is accepted. + .. deprecated:: 1.7 + :param before: The previous relationship status. :type before: :class:`Relationship` :param after: The updated relationship status. @@ -937,6 +941,8 @@ Profile A namedtuple representing a user's Discord public profile. + .. deprecated:: 1.7 + .. attribute:: user The :class:`User` the profile belongs to. @@ -1892,6 +1898,8 @@ of :class:`enum.Enum`. Specifies the type of :class:`Relationship`. + .. deprecated:: 1.7 + .. note:: This only applies to users, *not* bots. @@ -1918,6 +1926,8 @@ of :class:`enum.Enum`. Represents the options found in ``Settings > Privacy & Safety > Safe Direct Messaging`` in the Discord client. + .. deprecated:: 1.7 + .. note:: This only applies to users, *not* bots. @@ -1940,6 +1950,8 @@ of :class:`enum.Enum`. Represents the options found in ``Settings > Privacy & Safety > Who Can Add You As A Friend`` in the Discord client. + .. deprecated:: 1.7 + .. note:: This only applies to users, *not* bots. @@ -1969,6 +1981,8 @@ of :class:`enum.Enum`. Represents the user's Discord Nitro subscription type. + .. deprecated:: 1.7 + .. note:: This only applies to users, *not* bots. @@ -1986,6 +2000,8 @@ of :class:`enum.Enum`. Represents the theme synced across all Discord clients. + .. deprecated:: 1.7 + .. note:: This only applies to users, *not* bots. From 1afc12745813ba20e817a51bed73bcfbd246c655 Mon Sep 17 00:00:00 2001 From: Sebastian Law <44045823+SebbyLaw@users.noreply.github.com> Date: Mon, 22 Feb 2021 05:17:13 -0800 Subject: [PATCH 044/171] [commands] Add Context.invoked_parents --- discord/ext/commands/context.py | 11 +++++++++++ discord/ext/commands/core.py | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index c4691155..45d96f2b 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -57,6 +57,14 @@ class Context(discord.abc.Messageable): invoked_with: :class:`str` The command name that triggered this invocation. Useful for finding out which alias called the command. + invoked_parents: List[:class:`str`] + The command names of the parents that triggered this invocation. Useful for + finding out which aliases called the command. + + For example in commands ``?a b c test``, the invoked parents are ``['a', 'b', 'c']``. + + .. versionadded:: 1.7 + invoked_subcommand: :class:`Command` The subcommand that was invoked. If no valid subcommand was invoked then this is equal to ``None``. @@ -79,6 +87,7 @@ class Context(discord.abc.Messageable): self.command = attrs.pop('command', None) self.view = attrs.pop('view', None) self.invoked_with = attrs.pop('invoked_with', None) + self.invoked_parents = attrs.pop('invoked_parents', []) self.invoked_subcommand = attrs.pop('invoked_subcommand', None) self.subcommand_passed = attrs.pop('subcommand_passed', None) self.command_failed = attrs.pop('command_failed', False) @@ -180,6 +189,7 @@ class Context(discord.abc.Messageable): to_call = cmd.root_parent or cmd view.index = len(self.prefix) view.previous = 0 + self.invoked_parents = [] view.get_word() # advance to get the root command else: to_call = cmd @@ -192,6 +202,7 @@ class Context(discord.abc.Messageable): view.previous = previous self.invoked_with = invoked_with self.invoked_subcommand = invoked_subcommand + self.invoked_parents = invoked_parents self.subcommand_passed = subcommand_passed @property diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index baf2dca5..9cef19e5 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -1342,6 +1342,8 @@ class Group(GroupMixin, Command): injected = hooked_wrapped_callback(self, ctx, self.callback) await injected(*ctx.args, **ctx.kwargs) + ctx.invoked_parents.append(ctx.invoked_with) + if trigger and ctx.invoked_subcommand: ctx.invoked_with = trigger await ctx.invoked_subcommand.invoke(ctx) @@ -1380,6 +1382,8 @@ class Group(GroupMixin, Command): if call_hooks: await self.call_after_hooks(ctx) + ctx.invoked_parents.append(ctx.invoked_with) + if trigger and ctx.invoked_subcommand: ctx.invoked_with = trigger await ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks) From 48b748e340c75b4e93f29a12ff44d3e516b55ba1 Mon Sep 17 00:00:00 2001 From: Sebastian Law <44045823+SebbyLaw@users.noreply.github.com> Date: Mon, 22 Feb 2021 05:34:19 -0800 Subject: [PATCH 045/171] [commands] properly assign ctx.invoked_with with ctx. resolves #6461 --- discord/ext/commands/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 45d96f2b..3bd27ea8 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -190,7 +190,7 @@ class Context(discord.abc.Messageable): view.index = len(self.prefix) view.previous = 0 self.invoked_parents = [] - view.get_word() # advance to get the root command + self.invoked_with = view.get_word() # advance to get the root command else: to_call = cmd From 7f05f7f6c06d728fbcdb33e58656da1c2971a09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20N=C3=B8rgaard?= Date: Tue, 23 Feb 2021 08:54:31 +0000 Subject: [PATCH 046/171] Add converter example --- examples/converters.py | 113 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 examples/converters.py diff --git a/examples/converters.py b/examples/converters.py new file mode 100644 index 00000000..74b9c3c4 --- /dev/null +++ b/examples/converters.py @@ -0,0 +1,113 @@ +# This example requires the 'members' privileged intent to use the Member converter. + +import typing + +import discord +from discord.ext import commands + +intents = discord.Intents.default() +intents.members = True + +bot = commands.Bot('!', intents=intents) + + +@bot.command() +async def userinfo(ctx: commands.Context, user: discord.User): + # In the command signature above, you can see that the `user` + # parameter is typehinted to `discord.User`. This means that + # during command invocation we will attempt to convert + # the value passed as `user` to a `discord.User` instance. + # The documentation notes what can be converted, in the case of `discord.User` + # you pass an ID, mention or username (discrim optional) + # E.g. 80088516616269824, @Danny or Danny#0007 + + # NOTE: typehinting acts as a converter within the `commands` framework only. + # In standard Python, it is use for documentation and IDE assistance purposes. + + # If the conversion is successful, we will have a `discord.User` instance + # and can do the following: + user_id = user.id + username = user.name + avatar = user.avatar_url + await ctx.send('User found: {} -- {}\n{}'.format(user_id, username, avatar)) + +@userinfo.error +async def userinfo_error(ctx: commands.Context, error: commands.CommandError): + # if the conversion above fails for any reason, it will raise `commands.BadArgument` + # so we handle this in this error handler: + if isinstance(error, commands.BadArgument): + return await ctx.send('Couldn\'t find that user.') + +# Custom Converter here +class ChannelOrMemberConverter(commands.Converter): + async def convert(self, ctx: commands.Context, argument: str): + # In this example we have made a custom converter. + # This checks if an input is convertible to a + # `discord.Member` or `discord.TextChannel` instance from the + # input the user has given us using the pre-existing converters + # that the library provides. + + member_converter = commands.MemberConverter() + try: + # Try and convert to a Member instance. + # If this fails, then an exception is raised. + # Otherwise, we just return the converted member value. + member = await member_converter.convert(ctx, argument) + except commands.MemberNotFound: + pass + else: + return member + + # Do the same for TextChannel... + textchannel_converter = commands.TextChannelConverter() + try: + channel = await textchannel_converter.convert(ctx, argument) + except commands.ChannelNotFound: + pass + else: + return channel + + # If the value could not be converted we can raise an error + # so our error handlers can deal with it in one place. + # The error has to be CommandError derived, so BadArgument works fine here. + raise commands.BadArgument('No Member or TextChannel could be converted from "{}"'.format(argument)) + + + +@bot.command() +async def notify(ctx: commands.Context, target: ChannelOrMemberConverter): + # This command signature utilises the custom converter written above + # What will happen during command invocation is that the `target` above will be passed to + # the `argument` parameter of the `ChannelOrMemberConverter.convert` method and + # the conversion will go through the process defined there. + + await target.send('Hello, {}!'.format(target.name)) + +@bot.command() +async def ignore(ctx: commands.Context, target: typing.Union[discord.Member, discord.TextChannel]): + # This command signature utilises the `typing.Union` typehint. + # The `commands` framework attempts a conversion of each type in this Union *in order*. + # So, it will attempt to convert whatever is passed to `target` to a `discord.Member` instance. + # If that fails, it will attempt to convert it to a `discord.TextChannel` instance. + # See: https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html#typing-union + # NOTE: If a Union typehint converter fails it will raise `commands.BadUnionArgument` + # instead of `commands.BadArgument`. + + # To check the resulting type, `isinstance` is used + if isinstance(target, discord.Member): + await ctx.send('Member found: {}, adding them to the ignore list.'.format(target.mention)) + elif isinstance(target, discord.TextChannel): # this could be an `else` but for completeness' sake. + await ctx.send('Channel found: {}, adding it to the ignore list.'.format(target.mention)) + +# Built-in type converters. +@bot.command() +async def multiply(ctx: commands.Context, number: int, maybe: bool): + # We want an `int` and a `bool` parameter here. + # `bool` is a slightly special case, as shown here: + # See: https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html#bool + + if maybe is True: + return await ctx.send(number * 2) + await ctx.send(number * 5) + +bot.run('token') From 6f748e5da59e5c98504c14d52fcc00a8cce7041a Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Tue, 23 Feb 2021 08:57:11 +0000 Subject: [PATCH 047/171] Add remaining v6 message types --- discord/enums.py | 31 ++++++++++++++++++------------- discord/message.py | 15 +++++++++++++++ docs/api.rst | 29 +++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index 721dbe47..93194788 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -163,19 +163,24 @@ class ChannelType(Enum): return self.name class MessageType(Enum): - default = 0 - recipient_add = 1 - recipient_remove = 2 - call = 3 - channel_name_change = 4 - channel_icon_change = 5 - pins_add = 6 - new_member = 7 - premium_guild_subscription = 8 - premium_guild_tier_1 = 9 - premium_guild_tier_2 = 10 - premium_guild_tier_3 = 11 - channel_follow_add = 12 + default = 0 + recipient_add = 1 + recipient_remove = 2 + call = 3 + channel_name_change = 4 + channel_icon_change = 5 + pins_add = 6 + new_member = 7 + premium_guild_subscription = 8 + premium_guild_tier_1 = 9 + premium_guild_tier_2 = 10 + premium_guild_tier_3 = 11 + channel_follow_add = 12 + guild_stream = 13 + guild_discovery_disqualified = 14 + guild_discovery_requalified = 15 + guild_discovery_grace_period_initial_warning = 16 + guild_discovery_grace_period_final_warning = 17 class VoiceRegion(Enum): us_west = 'us-west' diff --git a/discord/message.py b/discord/message.py index 535a401f..d9f9bccb 100644 --- a/discord/message.py +++ b/discord/message.py @@ -923,6 +923,21 @@ class Message(Hashable): if self.type is MessageType.channel_follow_add: return '{0.author.name} has added {0.content} to this channel'.format(self) + + if self.type is MessageType.guild_stream: + return '{0.author.name} is live! Now streaming {0.author.activity.name}'.format(self) + + if self.type is MessageType.guild_discovery_disqualified: + return 'This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details.' + + if self.type is MessageType.guild_discovery_requalified: + return 'This server is eligible for Server Discovery again and has been automatically relisted!' + + if self.type is MessageType.guild_discovery_grace_period_initial_warning: + return 'This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery.' + + if self.type is MessageType.guild_discovery_grave_period_final_warning: + return 'This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.' async def delete(self, *, delay=None): """|coro| diff --git a/docs/api.rst b/docs/api.rst index 05b08909..7c1c5f3c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1125,6 +1125,35 @@ of :class:`enum.Enum`. The system message denoting that an announcement channel has been followed. .. versionadded:: 1.3 + .. attribute:: guild_stream + + The system message denoting that a member is streaming in the guild. + + .. versionadded:: 1.7 + .. attribute:: guild_discovery_disqualified + + The system message denoting that the guild is no longer eligible for Server + Discovery. + + .. versionadded:: 1.7 + .. attribute:: guild_discovery_requalified + + The system message denoting that the guild has become eligible again for Server + Discovery. + + .. versionadded:: 1.7 + .. attribute:: guild_discovery_grace_period_initial_warning + + The system message denoting that the guild has failed to meet the Server + Discovery requirements for one week. + + .. versionadded:: 1.7 + .. attribute:: guild_discovery_grace_period_final_warning + + The system message denoting that the guild has failed to meet the Server + Discovery requirements for 3 weeks in a row. + + .. versionadded:: 1.7 .. class:: ActivityType From fb773dc1dd46f7921d5ac41d503cecb685ea39a6 Mon Sep 17 00:00:00 2001 From: Nadir Chowdhury Date: Tue, 23 Feb 2021 08:58:03 +0000 Subject: [PATCH 048/171] Add remaining template endpoints --- discord/guild.py | 53 +++++++++++++++++++++++++ discord/http.py | 22 +++++++++++ discord/template.py | 94 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 162 insertions(+), 7 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index c51a4631..697537af 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1460,6 +1460,29 @@ class Guild(Hashable): data = await self._state.http.prune_members(self.id, days, compute_prune_count=compute_prune_count, roles=roles, reason=reason) return data['pruned'] + + async def templates(self): + """|coro| + + Gets the list of templates from this guild. + + Requires :attr:`~.Permissions.manage_guild` permissions. + + .. versionadded:: 1.7 + + Raises + ------- + Forbidden + You don't have permissions to get the templates. + + Returns + -------- + List[:class:`Template`] + The templates for this guild. + """ + from .template import Template + data = await self._state.http.guild_templates(self.id) + return [Template(data=d, state=self._state) for d in data] async def webhooks(self): """|coro| @@ -1546,6 +1569,36 @@ class Guild(Hashable): result.append(Invite(state=self._state, data=invite)) return result + + async def create_template(self, *, name, description=None): + """|coro| + + Creates a template for the guild. + + You must have the :attr:`~Permissions.manage_guild` permission to + do this. + + .. versionadded:: 1.7 + + Parameters + ----------- + name: :class:`str` + The name of the template. + description: Optional[:class:`str`] + The description of the template. + """ + from .template import Template + + payload = { + 'name': name + } + + if description: + payload['description'] = description + + data = await self._state.http.create_template(self.id, payload) + + return Template(state=self._state, data=data) async def create_integration(self, *, type, id): """|coro| diff --git a/discord/http.py b/discord/http.py index edfe031c..f151188d 100644 --- a/discord/http.py +++ b/discord/http.py @@ -670,6 +670,28 @@ class HTTPClient: def get_template(self, code): return self.request(Route('GET', '/guilds/templates/{code}', code=code)) + def guild_templates(self, guild_id): + return self.request(Route('GET', '/guilds/{guild_id}/templates', guild_id=guild_id)) + + def create_template(self, guild_id, payload): + return self.request(Route('POST', '/guilds/{guild_id}/templates', guild_id=guild_id), json=payload) + + def sync_template(self, guild_id, code): + return self.request(Route('PUT', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code)) + + def edit_template(self, guild_id, code, payload): + valid_keys = ( + 'name', + 'description', + ) + payload = { + k: v for k, v in payload.items() if k in valid_keys + } + return self.request(Route('PATCH', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code), json=payload) + + def delete_template(self, guild_id, code): + return self.request(Route('DELETE', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code)) + def create_from_template(self, code, name, region, icon): payload = { 'name': name, diff --git a/discord/template.py b/discord/template.py index 1c333380..f528120c 100644 --- a/discord/template.py +++ b/discord/template.py @@ -105,7 +105,9 @@ class Template: def __init__(self, *, state, data): self._state = state - + self._store(data) + + def _store(self, data): self.code = data['code'] self.uses = data['usage_count'] self.name = data['name'] @@ -117,11 +119,16 @@ class Template: self.updated_at = parse_time(data.get('updated_at')) id = _get_as_snowflake(data, 'source_guild_id') - source_serialised = data['serialized_source_guild'] - source_serialised['id'] = id - state = _PartialTemplateState(state=self._state) - self.source_guild = Guild(data=source_serialised, state=state) + guild = self._state._get_guild(id) + + if guild is None: + source_serialised = data['serialized_source_guild'] + source_serialised['id'] = id + state = _PartialTemplateState(state=self._state) + guild = Guild(data=source_serialised, state=state) + + self.source_guild = guild def __repr__(self): return '