mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-21 00:07:51 +00:00
Fix potential conflicts in snowflake keys
This can happen on really old channels with the same ID as the guild ID and having a command with both a role and a channel.
This commit is contained in:
parent
dffd72da58
commit
cdb7b3728e
@ -50,7 +50,7 @@ import re
|
||||
from .enums import AppCommandOptionType, AppCommandType
|
||||
from ..interactions import Interaction
|
||||
from ..enums import ChannelType, try_enum
|
||||
from .models import Choice
|
||||
from .models import AppCommandChannel, AppCommandThread, Choice
|
||||
from .errors import CommandSignatureMismatch, CommandAlreadyRegistered
|
||||
from ..utils import resolve_annotation, MISSING, is_inside_class
|
||||
from ..user import User
|
||||
@ -195,6 +195,8 @@ annotation_to_option_type: Dict[Any, AppCommandOptionType] = {
|
||||
User: AppCommandOptionType.user,
|
||||
Member: AppCommandOptionType.user,
|
||||
Role: AppCommandOptionType.role,
|
||||
AppCommandChannel: AppCommandOptionType.channel,
|
||||
AppCommandThread: AppCommandOptionType.channel,
|
||||
# StageChannel: AppCommandOptionType.channel,
|
||||
# StoreChannel: AppCommandOptionType.channel,
|
||||
# VoiceChannel: AppCommandOptionType.channel,
|
||||
|
@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Tuple
|
||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, NamedTuple, Tuple
|
||||
from ..interactions import Interaction
|
||||
from ..member import Member
|
||||
from ..object import Object
|
||||
@ -32,11 +32,35 @@ from ..role import Role
|
||||
from ..message import Message, Attachment
|
||||
from ..channel import PartialMessageable
|
||||
from .models import AppCommandChannel, AppCommandThread
|
||||
from .enums import AppCommandOptionType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..types.interactions import ResolvedData, ApplicationCommandInteractionDataOption
|
||||
|
||||
|
||||
class ResolveKey(NamedTuple):
|
||||
id: str
|
||||
# CommandOptionType does not use 0 or negative numbers so those can be safe for library
|
||||
# internal use, if necessary. Likewise, only 6, 7, 8, and 11 are actually in use.
|
||||
type: int
|
||||
|
||||
@classmethod
|
||||
def any_with(cls, id: str) -> ResolveKey:
|
||||
return ResolveKey(id=id, type=-1)
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
if not isinstance(o, ResolveKey):
|
||||
return NotImplemented
|
||||
if self.type == -1 or o.type == -1:
|
||||
return self.id == o.id
|
||||
return (self.id, self.type) == (o.id, o.type)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
# Most of the time an ID lookup is all that is necessary
|
||||
# In case of collision then we look up both the ID and the type.
|
||||
return hash(self.id)
|
||||
|
||||
|
||||
class Namespace:
|
||||
"""An object that holds the parameters being passed to a command in a mostly raw state.
|
||||
|
||||
@ -103,47 +127,62 @@ class Namespace:
|
||||
elif opt_type in (6, 7, 8, 9, 11):
|
||||
# Remaining ones should be snowflake based ones with resolved data
|
||||
snowflake: str = option['value'] # type: ignore -- Key is there
|
||||
value = completed.get(snowflake)
|
||||
if opt_type == 9: # Mentionable
|
||||
# Mentionable is User | Role, these do not cause any conflict
|
||||
key = ResolveKey.any_with(snowflake)
|
||||
else:
|
||||
# The remaining keys can conflict, for example, a role and a channel
|
||||
# could end up with the same ID in very old guilds since they used to default
|
||||
# to sharing the guild ID. Old general channels no longer exist, but some old
|
||||
# servers will still have them so this needs to be handled.
|
||||
key = ResolveKey(id=snowflake, type=opt_type)
|
||||
|
||||
value = completed.get(key)
|
||||
self.__dict__[name] = value
|
||||
|
||||
@classmethod
|
||||
def _get_resolved_items(cls, interaction: Interaction, resolved: ResolvedData) -> Dict[str, Any]:
|
||||
completed: Dict[str, Any] = {}
|
||||
def _get_resolved_items(cls, interaction: Interaction, resolved: ResolvedData) -> Dict[ResolveKey, Any]:
|
||||
completed: Dict[ResolveKey, Any] = {}
|
||||
state = interaction._state
|
||||
members = resolved.get('members', {})
|
||||
guild_id = interaction.guild_id
|
||||
guild = (state._get_guild(guild_id) or Object(id=guild_id)) if guild_id is not None else None
|
||||
type = AppCommandOptionType.user.value
|
||||
for (user_id, user_data) in resolved.get('users', {}).items():
|
||||
try:
|
||||
member_data = members[user_id]
|
||||
except KeyError:
|
||||
completed[user_id] = state.create_user(user_data)
|
||||
completed[ResolveKey(id=user_id, type=type)] = state.create_user(user_data)
|
||||
else:
|
||||
member_data['user'] = user_data
|
||||
# Guild ID can't be None in this case.
|
||||
# There's a type mismatch here that I don't actually care about
|
||||
member = Member(state=state, guild=guild, data=member_data) # type: ignore
|
||||
completed[user_id] = member
|
||||
completed[ResolveKey(id=user_id, type=type)] = member
|
||||
|
||||
type = AppCommandOptionType.role.value
|
||||
completed.update(
|
||||
{
|
||||
# The guild ID can't be None in this case.
|
||||
role_id: Role(guild=guild, state=state, data=role_data) # type: ignore
|
||||
ResolveKey(id=role_id, type=type): Role(guild=guild, state=state, data=role_data) # type: ignore
|
||||
for role_id, role_data in resolved.get('roles', {}).items()
|
||||
}
|
||||
)
|
||||
|
||||
type = AppCommandOptionType.channel.value
|
||||
for (channel_id, channel_data) in resolved.get('channels', {}).items():
|
||||
key = ResolveKey(id=channel_id, type=type)
|
||||
if channel_data['type'] in (10, 11, 12):
|
||||
# The guild ID can't be none in this case
|
||||
completed[channel_id] = AppCommandThread(state=state, data=channel_data, guild_id=guild_id) # type: ignore
|
||||
completed[key] = AppCommandThread(state=state, data=channel_data, guild_id=guild_id) # type: ignore
|
||||
else:
|
||||
# The guild ID can't be none in this case
|
||||
completed[channel_id] = AppCommandChannel(state=state, data=channel_data, guild_id=guild_id) # type: ignore
|
||||
completed[key] = AppCommandChannel(state=state, data=channel_data, guild_id=guild_id) # type: ignore
|
||||
|
||||
type = AppCommandOptionType.attachment.value
|
||||
completed.update(
|
||||
{
|
||||
attachment_id: Attachment(data=attachment_data, state=state)
|
||||
ResolveKey(id=attachment_id, type=type): Attachment(data=attachment_data, state=state)
|
||||
for attachment_id, attachment_data in resolved.get('attachments', {}).items()
|
||||
}
|
||||
)
|
||||
@ -157,7 +196,9 @@ class Namespace:
|
||||
channel = guild.get_channel_or_thread(channel_id) or PartialMessageable(state=state, id=channel_id)
|
||||
|
||||
# Type checker doesn't understand this due to failure to narrow
|
||||
completed[message_id] = Message(state=state, channel=channel, data=message_data) # type: ignore
|
||||
message = Message(state=state, channel=channel, data=message_data) # type: ignore
|
||||
key = ResolveKey(id=message_id, type=-1)
|
||||
completed[key] = message
|
||||
|
||||
return completed
|
||||
|
||||
|
@ -27,7 +27,7 @@ import inspect
|
||||
from typing import Callable, Dict, List, Literal, Optional, TYPE_CHECKING, Tuple, Type, Union, overload
|
||||
|
||||
|
||||
from .namespace import Namespace
|
||||
from .namespace import Namespace, ResolveKey
|
||||
from .models import AppCommand
|
||||
from .commands import Command, ContextMenu, Group, _shorten
|
||||
from .enums import AppCommandType
|
||||
@ -532,8 +532,14 @@ class CommandTree:
|
||||
raise CommandNotFound(name, [], AppCommandType(type))
|
||||
|
||||
resolved = Namespace._get_resolved_items(interaction, data.get('resolved', {}))
|
||||
|
||||
target_id = data.get('target_id')
|
||||
# Right now, the only types are message and user
|
||||
# Therefore, there's no conflict with snowflakes
|
||||
|
||||
# This will always work at runtime
|
||||
value = resolved.get(data.get('target_id')) # type: ignore
|
||||
key = ResolveKey.any_with(target_id) # type: ignore
|
||||
value = resolved.get(key)
|
||||
if ctx_menu.type.value != type:
|
||||
raise CommandSignatureMismatch(ctx_menu)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user