From 4f539b710f38a80a35fdb448d0e43b53d219c455 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sat, 16 Aug 2025 05:21:19 -0400 Subject: [PATCH] Fix type errors in all examples --- discord/ui/action_row.py | 2 +- examples/advanced_startup.py | 1 - examples/app_commands/basic.py | 30 ++++++++++++++++++++++++--- examples/app_commands/transformers.py | 3 +++ examples/background_task.py | 8 ++++++- examples/background_task_asyncio.py | 9 +++++++- examples/basic_bot.py | 9 +++++++- examples/basic_voice.py | 9 ++++---- examples/converters.py | 12 ++++++++--- examples/deleted.py | 3 +++ examples/edits.py | 3 +++ examples/guessing_game.py | 3 +++ examples/modals/basic.py | 3 +++ examples/new_member.py | 3 +++ examples/reaction_roles.py | 6 ++++++ examples/reply.py | 3 +++ examples/secret.py | 7 +++++++ examples/views/confirm.py | 2 ++ examples/views/counter.py | 3 +++ examples/views/dropdown.py | 3 +++ examples/views/dynamic_counter.py | 3 +++ examples/views/ephemeral.py | 3 +++ examples/views/layout.py | 3 +++ examples/views/link.py | 3 +++ examples/views/persistent.py | 3 +++ examples/views/tic_tac_toe.py | 3 +++ 26 files changed, 124 insertions(+), 16 deletions(-) diff --git a/discord/ui/action_row.py b/discord/ui/action_row.py index 9af19d10d..7d2fc64a9 100644 --- a/discord/ui/action_row.py +++ b/discord/ui/action_row.py @@ -68,7 +68,7 @@ if TYPE_CHECKING: SelectCallbackDecorator = Callable[[ItemCallbackType['S', BaseSelectT]], BaseSelectT] -S = TypeVar('S', bound='ActionRow', covariant=True) +S = TypeVar('S', bound=Union['ActionRow', 'LayoutView'], covariant=True) V = TypeVar('V', bound='LayoutView', covariant=True) __all__ = ('ActionRow',) diff --git a/examples/advanced_startup.py b/examples/advanced_startup.py index 4a452188d..82d0a96d3 100644 --- a/examples/advanced_startup.py +++ b/examples/advanced_startup.py @@ -5,7 +5,6 @@ import asyncio import logging import logging.handlers -import os from typing import List, Optional diff --git a/examples/app_commands/basic.py b/examples/app_commands/basic.py index f646643d0..7dc46c657 100644 --- a/examples/app_commands/basic.py +++ b/examples/app_commands/basic.py @@ -8,6 +8,9 @@ MY_GUILD = discord.Object(id=0) # replace with your guild id class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self, *, intents: discord.Intents): super().__init__(intents=intents) # A CommandTree is a special type that holds all the application command @@ -72,10 +75,17 @@ async def send(interaction: discord.Interaction, text_to_send: str): async def joined(interaction: discord.Interaction, member: Optional[discord.Member] = None): """Says when a member joined.""" # If no member is explicitly provided then we use the command user here - member = member or interaction.user + user = member or interaction.user + + # Tell the type checker that this is a Member + assert isinstance(user, discord.Member) # The format_dt function formats the date time into a human readable representation in the official client - await interaction.response.send_message(f'{member} joined {discord.utils.format_dt(member.joined_at)}') + # Joined at can be None in very bizarre cases so just handle that as well + if user.joined_at is None: + await interaction.response.send_message(f'{user} has no join date.') + else: + await interaction.response.send_message(f'{user} joined {discord.utils.format_dt(user.joined_at)}') # A Context Menu command is an app command that can be run on a member or on a message by @@ -86,7 +96,12 @@ async def joined(interaction: discord.Interaction, member: Optional[discord.Memb @client.tree.context_menu(name='Show Join Date') async def show_join_date(interaction: discord.Interaction, member: discord.Member): # The format_dt function formats the date time into a human readable representation in the official client - await interaction.response.send_message(f'{member} joined at {discord.utils.format_dt(member.joined_at)}') + # Joined at can be None in very bizarre cases so just handle that as well + + if member.joined_at is None: + await interaction.response.send_message(f'{member} has no join date.') + else: + await interaction.response.send_message(f'{member} joined at {discord.utils.format_dt(member.joined_at)}') # This context menu command only works on messages @@ -97,9 +112,18 @@ async def report_message(interaction: discord.Interaction, message: discord.Mess f'Thanks for reporting this message by {message.author.mention} to our moderators.', ephemeral=True ) + # Make sure that we're inside a guild + if interaction.guild is None: + await interaction.response.send_message('This command can only be used in a server.', ephemeral=True) + return + # Handle report by sending it into a log channel log_channel = interaction.guild.get_channel(0) # replace with your channel id + if log_channel is None or not isinstance(log_channel, discord.abc.Messageable): + await interaction.response.send_message('Log channel not found or not messageable.', ephemeral=True) + return + embed = discord.Embed(title='Reported Message') if message.content: embed.description = message.content diff --git a/examples/app_commands/transformers.py b/examples/app_commands/transformers.py index 2fb1231c4..a8275ad07 100644 --- a/examples/app_commands/transformers.py +++ b/examples/app_commands/transformers.py @@ -12,6 +12,9 @@ MY_GUILD = discord.Object(id=0) # replace with your guild id class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): super().__init__(intents=discord.Intents.default()) self.tree = app_commands.CommandTree(self) diff --git a/examples/background_task.py b/examples/background_task.py index 657aeb34b..40d543c51 100644 --- a/examples/background_task.py +++ b/examples/background_task.py @@ -4,6 +4,9 @@ import discord class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -21,8 +24,11 @@ class MyClient(discord.Client): @tasks.loop(seconds=60) # task runs every 60 seconds async def my_background_task(self): channel = self.get_channel(1234567) # channel ID goes here + # Tell the type checker that this is a messageable channel + assert isinstance(channel, discord.abc.Messageable) + self.counter += 1 - await channel.send(self.counter) + await channel.send(str(self.counter)) @my_background_task.before_loop async def before_my_task(self): diff --git a/examples/background_task_asyncio.py b/examples/background_task_asyncio.py index 8e9f3ce65..33b19bb38 100644 --- a/examples/background_task_asyncio.py +++ b/examples/background_task_asyncio.py @@ -3,6 +3,9 @@ import asyncio class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -18,9 +21,13 @@ class MyClient(discord.Client): await self.wait_until_ready() counter = 0 channel = self.get_channel(1234567) # channel ID goes here + + # Tell the type checker that this is a messageable channel + assert isinstance(channel, discord.abc.Messageable) + while not self.is_closed(): counter += 1 - await channel.send(counter) + await channel.send(str(counter)) await asyncio.sleep(60) # task runs every 60 seconds diff --git a/examples/basic_bot.py b/examples/basic_bot.py index 6e6aee182..738ae291a 100644 --- a/examples/basic_bot.py +++ b/examples/basic_bot.py @@ -18,6 +18,9 @@ bot = commands.Bot(command_prefix='?', description=description, intents=intents) @bot.event async def on_ready(): + # Tell the type checker that User is filled up at this point + assert bot.user is not None + print(f'Logged in as {bot.user} (ID: {bot.user.id})') print('------') @@ -57,7 +60,11 @@ async def repeat(ctx, times: int, content='repeating...'): @bot.command() async def joined(ctx, member: discord.Member): """Says when a member joined.""" - await ctx.send(f'{member.name} joined {discord.utils.format_dt(member.joined_at)}') + # Joined at can be None in very bizarre cases so just handle that as well + if member.joined_at is None: + await ctx.send(f'{member} has no join date.') + else: + await ctx.send(f'{member} joined {discord.utils.format_dt(member.joined_at)}') @bot.group() diff --git a/examples/basic_voice.py b/examples/basic_voice.py index c0759e2a0..e21dd684b 100644 --- a/examples/basic_voice.py +++ b/examples/basic_voice.py @@ -25,10 +25,6 @@ ytdl_format_options = { 'source_address': '0.0.0.0', # bind to ipv4 since ipv6 addresses cause issues sometimes } -ffmpeg_options = { - 'options': '-vn', -} - ytdl = youtube_dl.YoutubeDL(ytdl_format_options) @@ -51,7 +47,7 @@ class YTDLSource(discord.PCMVolumeTransformer): data = data['entries'][0] filename = data['url'] if stream else ytdl.prepare_filename(data) - return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) + return cls(discord.FFmpegPCMAudio(filename, options='-vn'), data=data) class Music(commands.Cog): @@ -138,6 +134,9 @@ bot = commands.Bot( @bot.event async def on_ready(): + # Tell the type checker that User is filled up at this point + assert bot.user is not None + print(f'Logged in as {bot.user} (ID: {bot.user.id})') print('------') diff --git a/examples/converters.py b/examples/converters.py index f8cae5675..c1809692e 100644 --- a/examples/converters.py +++ b/examples/converters.py @@ -83,8 +83,14 @@ class ChannelOrMemberConverter(commands.Converter): raise commands.BadArgument(f'No Member or TextChannel could be converted from "{argument}"') +# Make it so the converter is friendly to type checkers +# The first parameter of typing.Annotated is the type we tell the type checker +# The second parameter is what converter the library uses +ChannelOrMember = typing.Annotated[typing.Union[discord.Member, discord.TextChannel], ChannelOrMemberConverter] + + @bot.command() -async def notify(ctx: commands.Context, target: ChannelOrMemberConverter): +async def notify(ctx: commands.Context, target: ChannelOrMember): # 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 @@ -118,8 +124,8 @@ async def multiply(ctx: commands.Context, number: int, maybe: bool): # 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) + return await ctx.send(str(number * 2)) + await ctx.send(str(number * 5)) bot.run('token') diff --git a/examples/deleted.py b/examples/deleted.py index 97dac4631..1ac179643 100644 --- a/examples/deleted.py +++ b/examples/deleted.py @@ -4,6 +4,9 @@ import discord class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + async def on_ready(self): print(f'Logged in as {self.user} (ID: {self.user.id})') print('------') diff --git a/examples/edits.py b/examples/edits.py index 5b089109c..422502a6e 100644 --- a/examples/edits.py +++ b/examples/edits.py @@ -5,6 +5,9 @@ import asyncio class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + async def on_ready(self): print(f'Logged in as {self.user} (ID: {self.user.id})') print('------') diff --git a/examples/guessing_game.py b/examples/guessing_game.py index dd6c26ca9..ff2a0bfc8 100644 --- a/examples/guessing_game.py +++ b/examples/guessing_game.py @@ -6,6 +6,9 @@ import asyncio class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + async def on_ready(self): print(f'Logged in as {self.user} (ID: {self.user.id})') print('------') diff --git a/examples/modals/basic.py b/examples/modals/basic.py index edde8435b..27215b669 100644 --- a/examples/modals/basic.py +++ b/examples/modals/basic.py @@ -9,6 +9,9 @@ TEST_GUILD = discord.Object(0) class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self) -> None: # Just default intents and a `discord.Client` instance # We don't need a `commands.Bot` instance because we are not diff --git a/examples/new_member.py b/examples/new_member.py index 7cc84251e..60fe94b20 100644 --- a/examples/new_member.py +++ b/examples/new_member.py @@ -4,6 +4,9 @@ import discord class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + async def on_ready(self): print(f'Logged in as {self.user} (ID: {self.user.id})') print('------') diff --git a/examples/reaction_roles.py b/examples/reaction_roles.py index 99c4d17b6..2ab7e9f65 100644 --- a/examples/reaction_roles.py +++ b/examples/reaction_roles.py @@ -4,6 +4,9 @@ import discord class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -36,6 +39,9 @@ class MyClient(discord.Client): # Make sure the role still exists and is valid. return + # Tell the type checker that RawReactionActionEvent.member is not none during REACTION_ADD + assert payload.member is not None + try: # Finally, add the role. await payload.member.add_roles(role) diff --git a/examples/reply.py b/examples/reply.py index f2ccb4a7c..94154894a 100644 --- a/examples/reply.py +++ b/examples/reply.py @@ -4,6 +4,9 @@ import discord class MyClient(discord.Client): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + async def on_ready(self): print(f'Logged in as {self.user} (ID: {self.user.id})') print('------') diff --git a/examples/secret.py b/examples/secret.py index 434b5f0aa..1d7e55877 100644 --- a/examples/secret.py +++ b/examples/secret.py @@ -52,6 +52,9 @@ async def text(ctx: commands.Context, name: str, *objects: typing.Union[discord. overwrites = create_overwrites(ctx, *objects) + # Tell the type checker that our Guild is not None + assert ctx.guild is not None + await ctx.guild.create_text_channel( name, overwrites=overwrites, @@ -69,6 +72,8 @@ async def voice(ctx: commands.Context, name: str, *objects: typing.Union[discord overwrites = create_overwrites(ctx, *objects) + assert ctx.guild is not None + await ctx.guild.create_voice_channel( name, overwrites=overwrites, @@ -86,6 +91,8 @@ async def emoji(ctx: commands.Context, emoji: discord.PartialEmoji, *roles: disc # fetch the emoji asset and read it as bytes. emoji_bytes = await emoji.read() + assert ctx.guild is not None + # the key parameter here is `roles`, which controls # what roles are able to use the emoji. await ctx.guild.create_custom_emoji( diff --git a/examples/views/confirm.py b/examples/views/confirm.py index 5500dc2a6..7695aac70 100644 --- a/examples/views/confirm.py +++ b/examples/views/confirm.py @@ -6,6 +6,8 @@ import discord class Bot(commands.Bot): + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True diff --git a/examples/views/counter.py b/examples/views/counter.py index e3cd40e81..6d18c3be5 100644 --- a/examples/views/counter.py +++ b/examples/views/counter.py @@ -6,6 +6,9 @@ import discord class CounterBot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True diff --git a/examples/views/dropdown.py b/examples/views/dropdown.py index eb055eebd..d02921b0a 100644 --- a/examples/views/dropdown.py +++ b/examples/views/dropdown.py @@ -38,6 +38,9 @@ class DropdownView(discord.ui.View): class Bot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True diff --git a/examples/views/dynamic_counter.py b/examples/views/dynamic_counter.py index cfb02ee5d..848b569a6 100644 --- a/examples/views/dynamic_counter.py +++ b/examples/views/dynamic_counter.py @@ -70,6 +70,9 @@ class DynamicCounter( class DynamicCounterBot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() super().__init__(command_prefix=commands.when_mentioned, intents=intents) diff --git a/examples/views/ephemeral.py b/examples/views/ephemeral.py index 5a5fbffab..3864e945d 100644 --- a/examples/views/ephemeral.py +++ b/examples/views/ephemeral.py @@ -6,6 +6,9 @@ import discord class EphemeralCounterBot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True diff --git a/examples/views/layout.py b/examples/views/layout.py index 70effc30c..a5d161329 100644 --- a/examples/views/layout.py +++ b/examples/views/layout.py @@ -6,6 +6,9 @@ import discord class Bot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True diff --git a/examples/views/link.py b/examples/views/link.py index 3838fb72f..13394891c 100644 --- a/examples/views/link.py +++ b/examples/views/link.py @@ -7,6 +7,9 @@ from urllib.parse import quote_plus class GoogleBot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True diff --git a/examples/views/persistent.py b/examples/views/persistent.py index 14b267190..90050ea03 100644 --- a/examples/views/persistent.py +++ b/examples/views/persistent.py @@ -64,6 +64,9 @@ class DynamicButton(discord.ui.DynamicItem[discord.ui.Button], template=r'button class PersistentViewBot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True diff --git a/examples/views/tic_tac_toe.py b/examples/views/tic_tac_toe.py index eff638ae3..f016c72ca 100644 --- a/examples/views/tic_tac_toe.py +++ b/examples/views/tic_tac_toe.py @@ -121,6 +121,9 @@ class TicTacToe(discord.ui.View): class TicTacToeBot(commands.Bot): + # Suppress error on the User attribute being None since it fills up later + user: discord.ClientUser + def __init__(self): intents = discord.Intents.default() intents.message_content = True