from __future__ import annotations from dataclasses import dataclass from datetime import datetime from enum import Enum, IntFlag class FileMime(Enum): AUDIO_OGG = 'audio/ogg' IMAGE_JPEG = 'image/jpeg' IMAGE_PNG = 'image/png' IMAGE_SVG = 'image/svg' JSON = 'application/json' PDF = 'application/pdf' TEXT_CSV = 'text/csv' TEXT_HTML = 'text/html' TEXT_MARKDOWN = 'text/markdown' TEXT_PLAIN = 'text/plain' UNKNOWN = 'application/unknown' VIDEO_MP4 = 'video/mp4' VIDEO_MPEG = 'video/mpeg' VIDEO_WEBM = 'video/webm' ZIP = 'application/zip' @classmethod def _missing_(cls, value): # noqa: ARG003 return FileMime.UNKNOWN class ChannelType(Enum): GUILD_TEXT = 0 DM = 1 GUILD_VOICE = 2 GROUP_DM = 3 GUILD_CATEGORY = 4 GUILD_ANNOUNCEMENT = 5 ANNOUNCEMENT_THREAD = 10 PUBLIC_THREAD = 11 PRIVATE_THREAD = 12 GUILD_STAGE_VOICE = 13 GUILD_DIRECTORY = 14 GUILD_FORUM = 15 GUILD_MEDIA = 16 class ChannelFlags(IntFlag): # this thread is pinned to the top of its parent GUILD_FORUM or GUILD_MEDIA channel PINNED = 1 << 1 # whether a tag is required to be specified when creating a thread in a GUILD_FORUM or a GUILD_MEDIA channel. # Tags are specified in the applied_tags field. REQUIRE_TAG = 1 << 4 # when set hides the embedded media download options. Available only for media channels HIDE_MEDIA_DOWNLOAD_OPTIONS = 1 << 15 class OverwriteType(Enum): ROLE = 0 MEMBER = 1 class Permissions(IntFlag): NONE = 0 # Allows creation of instant invites CREATE_INSTANT_INVITE = 1 # Allows kicking members KICK_MEMBERS = 1 << 1 # Allows banning members BAN_MEMBERS = 1 << 2 # Allows all permissions and bypasses channel permission overwrites ADMINISTRATOR = 1 << 3 # Allows management and editing of channels MANAGE_CHANNELS = 1 << 4 # Allows management and editing of the guild MANAGE_GUILD = 1 << 5 # Allows for adding new reactions to messages. # This permission does not apply to reacting with an existing reaction on a message. ADD_REACTIONS = 1 << 6 # Allows for viewing of audit logs VIEW_AUDIT_LOG = 1 << 7 # Allows for using priority speaker in a voice channel PRIORITY_SPEAKER = 1 << 8 # Allows the user to go live STREAM = 1 << 9 # Allows guild members to view a channel, # which includes reading messages in text channels and joining voice channels VIEW_CHANNEL = 1 << 10 # Allows for sending messages in a channel and creating threads in a forum # (does not allow sending messages in threads) SEND_MESSAGES = 1 << 11 # Allows for sending of /tts messages SEND_TTS_MESSAGES = 1 << 12 # Allows for deletion of other users messages MANAGE_MESSAGES = 1 << 13 # Links sent by users with this permission will be auto-embedded EMBED_LINKS = 1 << 14 # Allows for uploading images and files ATTACH_FILES = 1 << 15 # Allows for reading of message history READ_MESSAGE_HISTORY = 1 << 16 # Allows for using the @everyone tag to notify all users in a channel, # and the @here tag to notify all online users in a channel MENTION_EVERYONE = 1 << 17 # Allows the usage of custom emojis from other servers USE_EXTERNAL_EMOJIS = 1 << 18 # Allows for viewing guild insights VIEW_GUILD_INSIGHTS = 1 << 19 # Allows for joining of a voice channel CONNECT = 1 << 20 # Allows for speaking in a voice channel SPEAK = 1 << 21 # Allows for muting members in a voice channel MUTE_MEMBERS = 1 << 22 # Allows for deafening of members in a voice channel DEAFEN_MEMBERS = 1 << 23 # Allows for moving of members between voice channels MOVE_MEMBERS = 1 << 24 # Allows for using voice-activity-detection in a voice channel USE_VAD = 1 << 25 # Allows for modification of own nickname CHANGE_NICKNAME = 1 << 26 # Allows for modification of other users nicknames MANAGE_NICKNAMES = 1 << 27 # Allows management and editing of roles MANAGE_ROLES = 1 << 28 # Allows management and editing of webhooks MANAGE_WEBHOOKS = 1 << 29 # Allows for editing and deleting emojis, stickers, and soundboard sounds created by all users MANAGE_GUILD_EXPRESSIONS = 1 << 30 # Allows members to use application commands, including slash commands and context menu commands. USE_APPLICATION_COMMANDS = 1 << 31 # Allows for requesting to speak in stage channels. (This permission is under active development # and may be changed or removed.) REQUEST_TO_SPEAK = 1 << 32 # Allows for editing and deleting scheduled events created by all users MANAGE_EVENTS = 1 << 33 # Allows for deleting and archiving threads, and viewing all private threads MANAGE_THREADS = 1 << 34 # Allows for creating public and announcement threads CREATE_PUBLIC_THREADS = 1 << 35 # Allows for creating private threads CREATE_PRIVATE_THREADS = 1 << 36 # Allows the usage of custom stickers from other servers USE_EXTERNAL_STICKERS = 1 << 37 # Allows for sending messages in threads SEND_MESSAGES_IN_THREADS = 1 << 38 # Allows for using Activities (applications with the EMBEDDED flag) USE_EMBEDDED_ACTIVITIES = 1 << 39 # Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, # and from speaking in voice and stage channels MODERATE_MEMBERS = 1 << 40 # Allows for viewing role subscription insights VIEW_CREATOR_MONETIZATION_ANALYTICS = 1 << 41 # Allows for using soundboard in a voice channel USE_SOUNDBOARD = 1 << 42 # Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created # by the current user. Not yet available to developers, see changelog. CREATE_GUILD_EXPRESSIONS = 1 << 43 # Allows for creating scheduled events, and editing and deleting those created by the current user. # Not yet available to developers, see changelog. CREATE_EVENTS = 1 << 44 # Allows the usage of custom soundboard sounds from other servers USE_EXTERNAL_SOUNDS = 1 << 45 # Allows sending voice messages SEND_VOICE_MESSAGES = 1 << 46 # Allows sending polls SEND_POLLS = 1 << 49 # Allows user-installed apps to send public responses. When disabled, users will still be allowed to use their apps # but the responses will be ephemeral. This only applies to apps not also installed to the server. USE_EXTERNAL_APPS = 1 << 50 # Allows pinning and unpinning messages PIN_MESSAGES = 1 << 51 @dataclass class Overwrite: id: int type: OverwriteType allow: Permissions deny: Permissions @staticmethod def from_dict(info: dict) -> Overwrite: return Overwrite( id=int(info['id']), type=OverwriteType(info['type']), allow=Permissions(int(info['allow'])), deny=Permissions(int(info['deny']))) @dataclass class ChannelCategory: id: int guild_id: int position: int permission_overwrites: list[Overwrite] name: str | None parent_id: int | None flags: ChannelFlags @staticmethod def from_dict(info: dict) -> ChannelCategory: parent_id: str | None = info.get('parent_id') return ChannelCategory( id=int(info['id']), guild_id=int(info['guild_id']), position=int(info['position']), permission_overwrites=[Overwrite.from_dict(o) for o in info['permission_overwrites']], name=info.get('name'), parent_id=int(parent_id) if parent_id is not None else None, flags=ChannelFlags(info['flags'])) @dataclass class TextChannel: id: int guild_id: int position: int permission_overwrites: list[Overwrite] name: str | None topic: str | None nsfw: bool last_message_id: int | None rate_limit_per_user: int parent_id: int | None last_pin_timestamp: datetime | None flags: ChannelFlags @staticmethod def from_dict(info: dict) -> TextChannel: parent_id: str | None = info.get('parent_id') last_message_id: str | None = info.get('last_message_id') last_pin_timestamp: str | None = info.get('last_pin_timestamp') return TextChannel( id=int(info['id']), guild_id=int(info['guild_id']), position=int(info['position']), permission_overwrites=[Overwrite.from_dict(o) for o in info['permission_overwrites']], name=info.get('name'), topic=info.get('topic'), nsfw=info['nsfw'], last_message_id=int(last_message_id) if last_message_id is not None else None, rate_limit_per_user=int(info['rate_limit_per_user']), parent_id=int(parent_id) if parent_id is not None else None, last_pin_timestamp=(datetime.fromisoformat(last_pin_timestamp) if last_pin_timestamp is not None else None), flags=ChannelFlags(info['flags'])) @dataclass class User: # TODO : complete attributes id: int username: str discriminator: str global_name: str | None bot: bool @staticmethod def from_dict(info: dict) -> User: return User( id=int(info['id']), username=info['username'], discriminator=info['discriminator'], global_name=info.get('global_name'), bot=info.get('bot', False)) class AttachmentFlags(IntFlag): IS_REMIX = 1 << 2 # this attachment has been edited using the remix feature on mobile @dataclass class Attachment: id: int # attachment id filename: str # name of file attached title: str | None # the title of the file description: str | None # description for the file (max 1024 characters) content_type: str | None # the attachment's media type size: int # size of file in bytes url: str # source url of file proxy_url: str # a proxied url of file height: int | None # height of file (if image) width: int | None # width of file (if image) ephemeral: bool | None # whether this attachment is ephemeral duration_secs: float | None # the duration of the audio file (currently for voice messages) waveform: str | None # base64 encoded bytearray representing a sampled waveform (currently for voice messages) flags: int | None # attachment flags combined as a bitfield @staticmethod def from_dict(info: dict) -> Attachment: height = info.get('height') width = info.get('width') duraction_secs = info.get('duration_secs') flags = info.get('flags') return Attachment( id=int(info['id']), filename=info['filename'], title=info.get('title'), description=info.get('description'), content_type=info.get('content_type'), size=int(info['size']), url=info['url'], proxy_url=info['proxy_url'], height=int(height) if height is not None else None, width=int(width) if width is not None else None, ephemeral=info.get('ephemeral'), duration_secs=float(duraction_secs) if duraction_secs is not None else None, waveform=info.get('waveform'), flags=AttachmentFlags(int(flags)) if flags is not None else None, ) class MessageType(Enum): DEFAULT = 0 RECIPIENT_ADD = 1 RECIPIENT_REMOVE = 2 CALL = 3 CHANNEL_NAME_CHANGE = 4 CHANNEL_ICON_CHANGE = 5 CHANNEL_PINNED_MESSAGE = 6 USER_JOIN = 7 GUILD_BOOST = 8 GUILD_BOOST_TIER_1 = 9 GUILD_BOOST_TIER_2 = 10 GUILD_BOOST_TIER_3 = 11 CHANNEL_FOLLOW_ADD = 12 GUILD_DISCOVERY_DISQUALIFIED = 14 GUILD_DISCOVERY_REQUALIFIED = 15 GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16 GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17 THREAD_CREATED = 18 REPLY = 19 CHAT_INPUT_COMMAND = 20 THREAD_STARTER_MESSAGE = 21 GUILD_INVITE_REMINDER = 22 CONTEXT_MENU_COMMAND = 23 AUTO_MODERATION_ACTION = 24 ROLE_SUBSCRIPTION_PURCHASE = 25 INTERACTION_PREMIUM_UPSELL = 26 STAGE_START = 27 STAGE_END = 28 STAGE_SPEAKER = 29 STAGE_TOPIC = 31 GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32 GUILD_INCIDENT_ALERT_MODE_ENABLED = 36 GUILD_INCIDENT_ALERT_MODE_DISABLED = 37 GUILD_INCIDENT_REPORT_RAID = 38 GUILD_INCIDENT_REPORT_FALSE_ALARM = 39 PURCHASE_NOTIFICATION = 44 POLL_RESULT = 46 NON_DELETABLE_MESSAGE_TYPES = [ MessageType.RECIPIENT_ADD, MessageType.RECIPIENT_REMOVE, MessageType.CALL, MessageType.CHANNEL_NAME_CHANGE, MessageType.CHANNEL_ICON_CHANGE, MessageType.THREAD_STARTER_MESSAGE] class MessageFlags(IntFlag): NONE = 0 CROSSPOSTED = 1 << 0 # this message has been published to subscribed channels (via Channel Following) IS_CROSSPOST = 1 << 1 # this message originated from a message in another channel (via Channel Following) SUPPRESS_EMBEDS = 1 << 2 # do not include any embeds when serializing this message SOURCE_MESSAGE_DELETED = 1 << 3 # the source message for this crosspost has been deleted (via Channel Following) URGENT = 1 << 4 # this message came from the urgent message system HAS_THREAD = 1 << 5 # this message has an associated thread, with the same id as the message EPHEMERAL = 1 << 6 # this message is only visible to the user who invoked the Interaction LOADING = 1 << 7 # this message is an Interaction Response and the bot is "thinking" # this message failed to mention some roles and add their members to the thread FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8 SUPPRESS_NOTIFICATIONS = 1 << 12 # this message will not trigger push and desktop notifications IS_VOICE_MESSAGE = 1 << 13 # this message is a voice message HAS_SNAPSHOT = 1 << 14 # this message has a snapshot (via Message Forwarding) IS_COMPONENTS_V2 = 1 << 15 # allows you to create fully component-driven messages class MessageReferenceType(Enum): DEFAULT = 0 # A standard reference used by replies. FORWARD = 1 # Reference used to point to a message at a point in time. @dataclass class MessageReference: type: MessageReferenceType | None # type of reference. message_id: int | None # id of the originating message channel_id: int | None # id of the originating message's channel guild_id: int | None # id of the originating message's guild # when sending, whether to error if the referenced message doesn't exist # instead of sending as a normal (non-reply) message, default true fail_if_not_exists: bool | None @staticmethod def from_dict(info: dict) -> MessageReference: ref_type: int | None = info.get('type') return MessageReference( type=MessageReferenceType(ref_type) if ref_type is not None else None, message_id=info.get('message_id'), channel_id=info.get('channel_id'), guild_id=info.get('guild_id'), fail_if_not_exists=info.get('fail_if_not_exists')) @dataclass class Message: # TODO : complete attributes id: int channel_id: int author: User content: str timestamp: datetime edited_timestamp: datetime | None attachments: list[Attachment] type: MessageType flags: MessageFlags | None message_reference: MessageReference | None @staticmethod def from_dict(info: dict) -> Message: edited_timestamp: str | None = info.get('edited_timestamp') flags: int | None = info.get('flags') message_reference = info.get('message_reference') return Message( id=int(info['id']), channel_id=int(info['channel_id']), author=(User.from_dict(info['author']) if info.get('webhook_id') is None else User( id=info['webhook_id'], username='webhook', discriminator='webhook', global_name=None, bot=False)), content=info['content'], timestamp=datetime.fromisoformat(info['timestamp']), edited_timestamp=datetime.fromisoformat(edited_timestamp) if edited_timestamp is not None else None, attachments=[Attachment.from_dict(a) for a in info['attachments']], type=MessageType(info['type']), flags=MessageFlags(flags) if flags is not None else None, message_reference=MessageReference.from_dict(message_reference) if message_reference is not None else None) @dataclass class RoleColors: primary_color: int seconday_color: int | None tertiary_color: int | None @staticmethod def from_dict(info: dict) -> RoleColors: seconday_color = info.get('secondary_color') tertiary_color = info.get('tertiary_color') return RoleColors( primary_color=int(info['primary_color']), seconday_color=int(seconday_color) if seconday_color is not None else None, tertiary_color=int(tertiary_color) if tertiary_color is not None else None) class RoleFlags(IntFlag): NONE = 0 # role can be selected by members in an onboarding prompt IN_PROMPT = 1 @dataclass class RoleTags: bot_id: int | None intergration_id: int | None premium_subscriber: bool subcription_listing_id: int | None available_for_purchase: bool guild_connections: bool @staticmethod def from_dict(info: dict) -> RoleTags: bot_id = info.get('bot_id') intergration_id = info.get('intergration_id') subcription_listing_id = info.get('subcription_listing_id') return RoleTags( bot_id=int(bot_id) if bot_id is not None else None, intergration_id=int(intergration_id) if intergration_id is not None else None, premium_subscriber='premium_subscriber' in info, subcription_listing_id=int(subcription_listing_id) if subcription_listing_id is not None else None, available_for_purchase='available_for_purchase' in info, guild_connections='guild_connections' in info) @dataclass class Role: id: int # role id name: str # role name color: int # Deprecated integer representation of hexadecimal color code colors: RoleColors # the role's colors hoist: bool # if this role is pinned in the user listing icon: str | None # role icon hash unicode_emoji: str | None # role unicode emoji position: int # position of this role (roles with the same position are sorted by id) permissions: Permissions # permission bit set managed: bool # whether this role is managed by an integration mentionable: bool # whether this role is mentionable tags: RoleTags | None # the tags this role has flags: int # role flags combined as a bitfield @staticmethod def from_dict(info: dict) -> Role: tags = info.get('tags') return Role( id=int(info['id']), name=info['name'], color=int(info['color']), colors=RoleColors.from_dict(info['colors']), hoist=info['hoist'], icon=info.get('icon'), unicode_emoji=info.get('unicode_emoji'), position=int(info['position']), permissions=Permissions(int(info['permissions'])), managed=info['managed'], mentionable=info['mentionable'], tags=RoleTags.from_dict(tags) if tags is not None else None, flags=RoleFlags(int(info['flags'])) )