From 66288ba9209f399274dde4bcdf6b98aac2422d9e Mon Sep 17 00:00:00 2001 From: Slipstream Date: Wed, 11 Jun 2025 14:47:23 -0600 Subject: [PATCH] Adds comprehensive Discord API error code mapping Implements specific exception classes for all Discord API error codes to provide more granular error handling and better debugging experience. Creates individual exception classes for each Discord error code (10001-520006) with descriptive names and documentation strings that include the official error messages and codes. Updates HTTP client to automatically map Discord error codes to their corresponding exception classes, allowing developers to catch specific error types rather than generic HTTP exceptions. Includes comprehensive test coverage for the new error code mapping functionality to ensure correct exception raising based on API responses. --- disagreement/errors.py | 1113 ++++++++++++++++++++++++++++++++++++++++ disagreement/http.py | 244 ++++++++- tests/test_errors.py | 81 ++- 3 files changed, 1431 insertions(+), 7 deletions(-) diff --git a/disagreement/errors.py b/disagreement/errors.py index 7395779..051231c 100644 --- a/disagreement/errors.py +++ b/disagreement/errors.py @@ -91,6 +91,1119 @@ class Forbidden(HTTPException): pass +class GeneralError(HTTPException): + """General error (such as a malformed request body, amongst other things) (Code: 0)""" + pass + + +class UnknownAccount(HTTPException): + """Unknown account (Code: 10001)""" + pass + + +class UnknownApplication(HTTPException): + """Unknown application (Code: 10002)""" + pass + + +class UnknownChannel(HTTPException): + """Unknown channel (Code: 10003)""" + pass + + +class UnknownGuild(HTTPException): + """Unknown guild (Code: 10004)""" + pass + + +class UnknownIntegration(HTTPException): + """Unknown integration (Code: 10005)""" + pass + + +class UnknownInvite(HTTPException): + """Unknown invite (Code: 10006)""" + pass + + +class UnknownMember(HTTPException): + """Unknown member (Code: 10007)""" + pass + + +class UnknownMessage(HTTPException): + """Unknown message (Code: 10008)""" + pass + + +class UnknownPermissionOverwrite(HTTPException): + """Unknown permission overwrite (Code: 10009)""" + pass + + +class UnknownProvider(HTTPException): + """Unknown provider (Code: 10010)""" + pass + + +class UnknownRole(HTTPException): + """Unknown role (Code: 10011)""" + pass + + +class UnknownToken(HTTPException): + """Unknown token (Code: 10012)""" + pass + + +class UnknownUser(HTTPException): + """Unknown user (Code: 10013)""" + pass + + +class UnknownEmoji(HTTPException): + """Unknown emoji (Code: 10014)""" + pass + + +class UnknownWebhook(HTTPException): + """Unknown webhook (Code: 10015)""" + pass + + +class UnknownWebhookService(HTTPException): + """Unknown webhook service (Code: 10016)""" + pass + + +class UnknownSession(HTTPException): + """Unknown session (Code: 10020)""" + pass + + +class UnknownAsset(HTTPException): + """Unknown Asset (Code: 10021)""" + pass + + +class UnknownBan(HTTPException): + """Unknown ban (Code: 10026)""" + pass + + +class UnknownSKU(HTTPException): + """Unknown SKU (Code: 10027)""" + pass + + +class UnknownStoreListing(HTTPException): + """Unknown Store Listing (Code: 10028)""" + pass + + +class UnknownEntitlement(HTTPException): + """Unknown entitlement (Code: 10029)""" + pass + + +class UnknownBuild(HTTPException): + """Unknown build (Code: 10030)""" + pass + + +class UnknownLobby(HTTPException): + """Unknown lobby (Code: 10031)""" + pass + + +class UnknownBranch(HTTPException): + """Unknown branch (Code: 10032)""" + pass + + +class UnknownStoreDirectoryLayout(HTTPException): + """Unknown store directory layout (Code: 10033)""" + pass + + +class UnknownRedistributable(HTTPException): + """Unknown redistributable (Code: 10036)""" + pass + + +class UnknownGiftCode(HTTPException): + """Unknown gift code (Code: 10038)""" + pass + + +class UnknownStream(HTTPException): + """Unknown stream (Code: 10049)""" + pass + + +class UnknownPremiumServerSubscribeCooldown(HTTPException): + """Unknown premium server subscribe cooldown (Code: 10050)""" + pass + + +class UnknownGuildTemplate(HTTPException): + """Unknown guild template (Code: 10057)""" + pass + + +class UnknownDiscoverableServerCategory(HTTPException): + """Unknown discoverable server category (Code: 10059)""" + pass + + +class UnknownSticker(HTTPException): + """Unknown sticker (Code: 10060)""" + pass + + +class UnknownStickerPack(HTTPException): + """Unknown sticker pack (Code: 10061)""" + pass + + +class UnknownInteraction(HTTPException): + """Unknown interaction (Code: 10062)""" + pass + + +class UnknownApplicationCommand(HTTPException): + """Unknown application command (Code: 10063)""" + pass + + +class UnknownVoiceState(HTTPException): + """Unknown voice state (Code: 10065)""" + pass + + +class UnknownApplicationCommandPermissions(HTTPException): + """Unknown application command permissions (Code: 10066)""" + pass + + +class UnknownStageInstance(HTTPException): + """Unknown Stage Instance (Code: 10067)""" + pass + + +class UnknownGuildMemberVerificationForm(HTTPException): + """Unknown Guild Member Verification Form (Code: 10068)""" + pass + + +class UnknownGuildWelcomeScreen(HTTPException): + """Unknown Guild Welcome Screen (Code: 10069)""" + pass + + +class UnknownGuildScheduledEvent(HTTPException): + """Unknown Guild Scheduled Event (Code: 10070)""" + pass + + +class UnknownGuildScheduledEventUser(HTTPException): + """Unknown Guild Scheduled Event User (Code: 10071)""" + pass + + +class UnknownTag(HTTPException): + """Unknown Tag (Code: 10087)""" + pass + + +class UnknownSound(HTTPException): + """Unknown sound (Code: 10097)""" + pass + + +class BotsCannotUseThisEndpoint(HTTPException): + """Bots cannot use this endpoint (Code: 20001)""" + pass + + +class OnlyBotsCanUseThisEndpoint(HTTPException): + """Only bots can use this endpoint (Code: 20002)""" + pass + + +class ExplicitContentCannotBeSentToTheDesiredRecipients(HTTPException): + """Explicit content cannot be sent to the desired recipient(s) (Code: 20009)""" + pass + + +class NotAuthorizedToPerformThisActionOnThisApplication(HTTPException): + """You are not authorized to perform this action on this application (Code: 20012)""" + pass + + +class ActionCannotBePerformedDueToSlowmodeRateLimit(HTTPException): + """This action cannot be performed due to slowmode rate limit (Code: 20016)""" + pass + + +class OnlyTheOwnerOfThisAccountCanPerformThisAction(HTTPException): + """Only the owner of this account can perform this action (Code: 20018)""" + pass + + +class MessageCannotBeEditedDueToAnnouncementRateLimits(HTTPException): + """This message cannot be edited due to announcement rate limits (Code: 20022)""" + pass + + +class UnderMinimumAge(HTTPException): + """Under minimum age (Code: 20024)""" + pass + + +class ChannelHitWriteRateLimit(HTTPException): + """The channel you are writing has hit the write rate limit (Code: 20028)""" + pass + + +class ServerHitWriteRateLimit(HTTPException): + """The write action you are performing on the server has hit the write rate limit (Code: 20029)""" + pass + + +class DisallowedWordsInStageTopicOrNames(HTTPException): + """Your Stage topic, server name, server description, or channel names contain words that are not allowed (Code: 20031)""" + pass + + +class GuildPremiumSubscriptionLevelTooLow(HTTPException): + """Guild premium subscription level too low (Code: 20035)""" + pass + + +class MaximumNumberOfGuildsReached(HTTPException): + """Maximum number of guilds reached (100) (Code: 30001)""" + pass + + +class MaximumNumberOfFriendsReached(HTTPException): + """Maximum number of friends reached (1000) (Code: 30002)""" + pass + + +class MaximumNumberOfPinsReached(HTTPException): + """Maximum number of pins reached for the channel (50) (Code: 30003)""" + pass + + +class MaximumNumberOfRecipientsReached(HTTPException): + """Maximum number of recipients reached (10) (Code: 30004)""" + pass + + +class MaximumNumberOfGuildRolesReached(HTTPException): + """Maximum number of guild roles reached (250) (Code: 30005)""" + pass + + +class MaximumNumberOfWebhooksReached(HTTPException): + """Maximum number of webhooks reached (15) (Code: 30007)""" + pass + + +class MaximumNumberOfEmojisReached(HTTPException): + """Maximum number of emojis reached (Code: 30008)""" + pass + + +class MaximumNumberOfReactionsReached(HTTPException): + """Maximum number of reactions reached (20) (Code: 30010)""" + pass + + +class MaximumNumberOfGroupDMsReached(HTTPException): + """Maximum number of group DMs reached (10) (Code: 30011)""" + pass + + +class MaximumNumberOfGuildChannelsReached(HTTPException): + """Maximum number of guild channels reached (500) (Code: 30013)""" + pass + + +class MaximumNumberOfAttachmentsInAMessageReached(HTTPException): + """Maximum number of attachments in a message reached (10) (Code: 30015)""" + pass + + +class MaximumNumberOfInvitesReached(HTTPException): + """Maximum number of invites reached (1000) (Code: 30016)""" + pass + + +class MaximumNumberOfAnimatedEmojisReached(HTTPException): + """Maximum number of animated emojis reached (Code: 30018)""" + pass + + +class MaximumNumberOfServerMembersReached(HTTPException): + """Maximum number of server members reached (Code: 30019)""" + pass + + +class MaximumNumberOfServerCategoriesReached(HTTPException): + """Maximum number of server categories has been reached (5) (Code: 30030)""" + pass + + +class GuildAlreadyHasATemplate(HTTPException): + """Guild already has a template (Code: 30031)""" + pass + + +class MaximumNumberOfApplicationCommandsReached(HTTPException): + """Maximum number of application commands reached (Code: 30032)""" + pass + + +class MaximumNumberOfThreadParticipantsReached(HTTPException): + """Maximum number of thread participants has been reached (1000) (Code: 30033)""" + pass + + +class MaximumNumberOfDailyApplicationCommandCreatesReached(HTTPException): + """Maximum number of daily application command creates has been reached (200) (Code: 30034)""" + pass + + +class MaximumNumberOfBansForNonGuildMembersExceeded(HTTPException): + """Maximum number of bans for non-guild members have been exceeded (Code: 30035)""" + pass + + +class MaximumNumberOfBansFetchesReached(HTTPException): + """Maximum number of bans fetches has been reached (Code: 30037)""" + pass + + +class MaximumNumberOfUncompletedGuildScheduledEventsReached(HTTPException): + """Maximum number of uncompleted guild scheduled events reached (100) (Code: 30038)""" + pass + + +class MaximumNumberOfStickersReached(HTTPException): + """Maximum number of stickers reached (Code: 30039)""" + pass + + +class MaximumNumberOfPruneRequestsReached(HTTPException): + """Maximum number of prune requests has been reached. Try again later (Code: 30040)""" + pass + + +class MaximumNumberOfGuildWidgetSettingsUpdatesReached(HTTPException): + """Maximum number of guild widget settings updates has been reached. Try again later (Code: 30042)""" + pass + + +class MaximumNumberOfSoundboardSoundsReached(HTTPException): + """Maximum number of soundboard sounds reached (Code: 30045)""" + pass + + +class MaximumNumberOfEditsToMessagesOlderThan1HourReached(HTTPException): + """Maximum number of edits to messages older than 1 hour reached. Try again later (Code: 30046)""" + pass + + +class MaximumNumberOfPinnedThreadsInAForumChannelReached(HTTPException): + """Maximum number of pinned threads in a forum channel has been reached (Code: 30047)""" + pass + + +class MaximumNumberOfTagsInAForumChannelReached(HTTPException): + """Maximum number of tags in a forum channel has been reached (Code: 30048)""" + pass + + +class BitrateIsTooHighForChannelOfThisType(HTTPException): + """Bitrate is too high for channel of this type (Code: 30052)""" + pass + + +class MaximumNumberOfPremiumEmojisReached(HTTPException): + """Maximum number of premium emojis reached (25) (Code: 30056)""" + pass + + +class MaximumNumberOfWebhooksPerGuildReached(HTTPException): + """Maximum number of webhooks per guild reached (1000) (Code: 30058)""" + pass + + +class MaximumNumberOfChannelPermissionOverwritesReached(HTTPException): + """Maximum number of channel permission overwrites reached (1000) (Code: 30061)""" + pass + + +class TheChannelsForThisGuildAreTooLarge(HTTPException): + """The channels for this guild are too large (Code: 30061)""" + pass + + +class Unauthorized(HTTPException): + """Unauthorized. Provide a valid token and try again (Code: 40001)""" + pass + + +class YouNeedToVerifyYourAccount(HTTPException): + """You need to verify your account in order to perform this action (Code: 40002)""" + pass + + +class YouAreOpeningDirectMessagesTooFast(HTTPException): + """You are opening direct messages too fast (Code: 40003)""" + pass + + +class SendMessagesHasBeenTemporarilyDisabled(HTTPException): + """Send messages has been temporarily disabled (Code: 40004)""" + pass + + +class RequestEntityTooLarge(HTTPException): + """Request entity too large. Try sending something smaller in size (Code: 40005)""" + pass + + +class ThisFeatureHasBeenTemporarilyDisabledServerSide(HTTPException): + """This feature has been temporarily disabled server-side (Code: 40006)""" + pass + + +class TheUserIsBannedFromThisGuild(HTTPException): + """The user is banned from this guild (Code: 40007)""" + pass + + +class ConnectionHasBeenRevoked(HTTPException): + """Connection has been revoked (Code: 40012)""" + pass + + +class OnlyConsumableSKUsCanBeConsumed(HTTPException): + """Only consumable SKUs can be consumed (Code: 40018)""" + pass + + +class YouCanOnlyDeleteSandboxEntitlements(HTTPException): + """You can only delete sandbox entitlements. (Code: 40019)""" + pass + + +class TargetUserIsNotConnectedToVoice(HTTPException): + """Target user is not connected to voice (Code: 40032)""" + pass + + +class ThisMessageHasAlreadyBeenCrossposted(HTTPException): + """This message has already been crossposted (Code: 40033)""" + pass + + +class AnApplicationCommandWithThatNameAlreadyExists(HTTPException): + """An application command with that name already exists (Code: 40041)""" + pass + + +class ApplicationInteractionFailedToSend(HTTPException): + """Application interaction failed to send (Code: 40043)""" + pass + + +class CannotSendAMessageInAForumChannel(HTTPException): + """Cannot send a message in a forum channel (Code: 40058)""" + pass + + +class InteractionHasAlreadyBeenAcknowledged(HTTPException): + """Interaction has already been acknowledged (Code: 40060)""" + pass + + +class TagNamesMustBeUnique(HTTPException): + """Tag names must be unique (Code: 40061)""" + pass + + +class ServiceResourceIsBeingRateLimited(HTTPException): + """Service resource is being rate limited (Code: 40062)""" + pass + + +class ThereAreNoTagsAvailableThatCanBeSetByNonModerators(HTTPException): + """There are no tags available that can be set by non-moderators (Code: 40066)""" + pass + + +class ATagIsRequiredToCreateAForumPostInThisChannel(HTTPException): + """A tag is required to create a forum post in this channel (Code: 40067)""" + pass + + +class AnEntitlementHasAlreadyBeenGrantedForThisResource(HTTPException): + """An entitlement has already been granted for this resource (Code: 40074)""" + pass + + +class ThisInteractionHasHitTheMaximumNumberOfFollowUpMessages(HTTPException): + """This interaction has hit the maximum number of follow up messages (Code: 40094)""" + pass + + +class CloudflareIsBlockingYourRequest(HTTPException): + """Cloudflare is blocking your request. This can often be resolved by setting a proper User Agent (Code: 40333)""" + pass + + +class MissingAccess(HTTPException): + """Missing access (Code: 50001)""" + pass + + +class InvalidAccountType(HTTPException): + """Invalid account type (Code: 50002)""" + pass + + +class CannotExecuteActionOnADMChannel(HTTPException): + """Cannot execute action on a DM channel (Code: 50003)""" + pass + + +class GuildWidgetDisabled(HTTPException): + """Guild widget disabled (Code: 50004)""" + pass + + +class CannotEditAMessageAuthoredByAnotherUser(HTTPException): + """Cannot edit a message authored by another user (Code: 50005)""" + pass + + +class CannotSendAnEmptyMessage(HTTPException): + """Cannot send an empty message (Code: 50006)""" + pass + + +class CannotSendMessagesToThisUser(HTTPException): + """Cannot send messages to this user (Code: 50007)""" + pass + + +class CannotSendMessagesInANonTextChannel(HTTPException): + """Cannot send messages in a non-text channel (Code: 50008)""" + pass + + +class ChannelVerificationLevelIsTooHighForYouToGainAccess(HTTPException): + """Channel verification level is too high for you to gain access (Code: 50009)""" + pass + + +class OAuth2ApplicationDoesNotHaveABot(HTTPException): + """OAuth2 application does not have a bot (Code: 50010)""" + pass + + +class OAuth2ApplicationLimitReached(HTTPException): + """OAuth2 application limit reached (Code: 50011)""" + pass + + +class InvalidOAuth2State(HTTPException): + """Invalid OAuth2 state (Code: 50012)""" + pass + + +class YouLackPermissionsToPerformThatAction(HTTPException): + """You lack permissions to perform that action (Code: 50013)""" + pass + + +class InvalidAuthenticationTokenProvided(HTTPException): + """Invalid authentication token provided (Code: 50014)""" + pass + + +class NoteWasTooLong(HTTPException): + """Note was too long (Code: 50015)""" + pass + + +class ProvidedTooFewOrTooManyMessagesToDelete(HTTPException): + """Provided too few or too many messages to delete. Must provide at least 2 and fewer than 100 messages to delete (Code: 50016)""" + pass + + +class InvalidMFALevel(HTTPException): + """Invalid MFA Level (Code: 50017)""" + pass + + +class AMessageCanOnlyBePinnedToTheChannelItWasSentIn(HTTPException): + """A message can only be pinned to the channel it was sent in (Code: 50019)""" + pass + + +class InviteCodeWasEitherInvalidOrTaken(HTTPException): + """Invite code was either invalid or taken (Code: 50020)""" + pass + + +class CannotExecuteActionOnASystemMessage(HTTPException): + """Cannot execute action on a system message (Code: 50021)""" + pass + + +class CannotExecuteActionOnThisChannelType(HTTPException): + """Cannot execute action on this channel type (Code: 50024)""" + pass + + +class InvalidOAuth2AccessTokenProvided(HTTPException): + """Invalid OAuth2 access token provided (Code: 50025)""" + pass + + +class MissingRequiredOAuth2Scope(HTTPException): + """Missing required OAuth2 scope (Code: 50026)""" + pass + + +class InvalidWebhookTokenProvided(HTTPException): + """Invalid webhook token provided (Code: 50027)""" + pass + + +class InvalidRole(HTTPException): + """Invalid role (Code: 50028)""" + pass + + +class InvalidRecipients(HTTPException): + """Invalid Recipient(s) (Code: 50033)""" + pass + + +class AMessageProvidedWasTooOldToBulkDelete(HTTPException): + """A message provided was too old to bulk delete (Code: 50034)""" + pass + + +class InvalidFormBody(HTTPException): + """Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided (Code: 50035)""" + pass + + +class AnInviteWasAcceptedToAGuildTheApplicationBotIsNotIn(HTTPException): + """An invite was accepted to a guild the application's bot is not in (Code: 50036)""" + pass + + +class InvalidActivityAction(HTTPException): + """Invalid Activity Action (Code: 50039)""" + pass + + +class InvalidAPIVersionProvided(HTTPException): + """Invalid API version provided (Code: 50041)""" + pass + + +class FileUploadedExceedsTheMaximumSize(HTTPException): + """File uploaded exceeds the maximum size (Code: 50045)""" + pass + + +class InvalidFileUploaded(HTTPException): + """Invalid file uploaded (Code: 50046)""" + pass + + +class CannotSelfRedeemThisGift(HTTPException): + """Cannot self-redeem this gift (Code: 50054)""" + pass + + +class InvalidGuild(HTTPException): + """Invalid Guild (Code: 50055)""" + pass + + +class InvalidSKU(HTTPException): + """Invalid SKU (Code: 50057)""" + pass + + +class InvalidRequestOrigin(HTTPException): + """Invalid request origin (Code: 50067)""" + pass + + +class InvalidMessageType(HTTPException): + """Invalid message type (Code: 50068)""" + pass + + +class PaymentSourceRequiredToRedeemGift(HTTPException): + """Payment source required to redeem gift (Code: 50070)""" + pass + + +class CannotModifyASystemWebhook(HTTPException): + """Cannot modify a system webhook (Code: 50073)""" + pass + + +class CannotDeleteAChannelRequiredForCommunityGuilds(HTTPException): + """Cannot delete a channel required for Community guilds (Code: 50074)""" + pass + + +class CannotEditStickersWithinAMessage(HTTPException): + """Cannot edit stickers within a message (Code: 50080)""" + pass + + +class InvalidStickerSent(HTTPException): + """Invalid sticker sent (Code: 50081)""" + pass + + +class TriedToPerformAnOperationOnAnArchivedThread(HTTPException): + """Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread (Code: 50083)""" + pass + + +class InvalidThreadNotificationSettings(HTTPException): + """Invalid thread notification settings (Code: 50085)""" + pass + + +class BeforeValueIsEarlierThanTheThreadCreationDate(HTTPException): + """before value is earlier than the thread creation date (Code: 50086)""" + pass + + +class CommunityServerChannelsMustBeTextChannels(HTTPException): + """Community server channels must be text channels (Code: 50086)""" + pass + + +class TheEntityTypeOfTheEventIsDifferentFromTheEntityYouAreTryingToStartTheEventFor(HTTPException): + """The entity type of the event is different from the entity you are trying to start the event for (Code: 50091)""" + pass + + +class ThisServerIsNotAvailableInYourLocation(HTTPException): + """This server is not available in your location (Code: 50095)""" + pass + + +class ThisServerNeedsMonetizationEnabledInOrderToPerformThisAction(HTTPException): + """This server needs monetization enabled in order to perform this action (Code: 50097)""" + pass + + +class ThisServerNeedsMoreBoostsToPerformThisAction(HTTPException): + """This server needs more boosts to perform this action (Code: 50101)""" + pass + + +class TheRequestBodyContainsInvalidJSON(HTTPException): + """The request body contains invalid JSON. (Code: 50109)""" + pass + + +class TheProvidedFileIsInvalid(HTTPException): + """The provided file is invalid. (Code: 50110)""" + pass + + +class TheProvidedFileTypeIsInvalid(HTTPException): + """The provided file type is invalid. (Code: 50123)""" + pass + + +class TheProvidedFileDurationExceedsMaximumOf52Seconds(HTTPException): + """The provided file duration exceeds maximum of 5.2 seconds. (Code: 50124)""" + pass + + +class OwnerCannotBePendingMember(HTTPException): + """Owner cannot be pending member (Code: 50131)""" + pass + + +class OwnershipCannotBeTransferredToABotUser(HTTPException): + """Ownership cannot be transferred to a bot user (Code: 50132)""" + pass + + +class FailedToResizeAssetBelowTheMaximumSize(HTTPException): + """Failed to resize asset below the maximum size: 262144 (Code: 50138)""" + pass + + +class CannotMixSubscriptionAndNonSubscriptionRolesForAnEmoji(HTTPException): + """Cannot mix subscription and non subscription roles for an emoji (Code: 50144)""" + pass + + +class CannotConvertBetweenPremiumEmojiAndNormalEmoji(HTTPException): + """Cannot convert between premium emoji and normal emoji (Code: 50145)""" + pass + + +class UploadedFileNotFound(HTTPException): + """Uploaded file not found. (Code: 50146)""" + pass + + +class TheSpecifiedEmojiIsInvalid(HTTPException): + """The specified emoji is invalid (Code: 50151)""" + pass + + +class VoiceMessagesDoNotSupportAdditionalContent(HTTPException): + """Voice messages do not support additional content. (Code: 50159)""" + pass + + +class VoiceMessagesMustHaveASingleAudioAttachment(HTTPException): + """Voice messages must have a single audio attachment. (Code: 50160)""" + pass + + +class VoiceMessagesMustHaveSupportingMetadata(HTTPException): + """Voice messages must have supporting metadata. (Code: 50161)""" + pass + + +class VoiceMessagesCannotBeEdited(HTTPException): + """Voice messages cannot be edited. (Code: 50162)""" + pass + + +class CannotDeleteGuildSubscriptionIntegration(HTTPException): + """Cannot delete guild subscription integration (Code: 50163)""" + pass + + +class YouCannotSendVoiceMessagesInThisChannel(HTTPException): + """You cannot send voice messages in this channel. (Code: 50173)""" + pass + + +class TheUserAccountMustFirstBeVerified(HTTPException): + """The user account must first be verified (Code: 50178)""" + pass + + +class TheProvidedFileDoesNotHaveAValidDuration(HTTPException): + """The provided file does not have a valid duration. (Code: 50192)""" + pass + + +class YouDoNotHavePermissionToSendThisSticker(HTTPException): + """You do not have permission to send this sticker. (Code: 50600)""" + pass + + +class TwoFactorIsRequiredForThisOperation(HTTPException): + """Two factor is required for this operation (Code: 60003)""" + pass + + +class NoUsersWithDiscordTagExist(HTTPException): + """No users with DiscordTag exist (Code: 80004)""" + pass + + +class ReactionWasBlocked(HTTPException): + """Reaction was blocked (Code: 90001)""" + pass + + +class UserCannotUseBurstReactions(HTTPException): + """User cannot use burst reactions (Code: 90002)""" + pass + + +class ApplicationNotYetAvailable(HTTPException): + """Application not yet available. Try again later (Code: 110001)""" + pass + + +class APIResourceIsCurrentlyOverloaded(HTTPException): + """API resource is currently overloaded. Try again a little later (Code: 130000)""" + pass + + +class TheStageIsAlreadyOpen(HTTPException): + """The Stage is already open (Code: 150006)""" + pass + + +class CannotReplyWithoutPermissionToReadMessageHistory(HTTPException): + """Cannot reply without permission to read message history (Code: 160002)""" + pass + + +class AThreadHasAlreadyBeenCreatedForThisMessage(HTTPException): + """A thread has already been created for this message (Code: 160004)""" + pass + + +class ThreadIsLocked(HTTPException): + """Thread is locked (Code: 160005)""" + pass + + +class MaximumNumberOfActiveThreadsReached(HTTPException): + """Maximum number of active threads reached (Code: 160006)""" + pass + + +class MaximumNumberOfActiveAnnouncementThreadsReached(HTTPException): + """Maximum number of active announcement threads reached (Code: 160007)""" + pass + + +class InvalidJSONForUploadedLottieFile(HTTPException): + """Invalid JSON for uploaded Lottie file (Code: 170001)""" + pass + + +class UploadedLottiesCannotContainRasterizedImages(HTTPException): + """Uploaded Lotties cannot contain rasterized images such as PNG or JPEG (Code: 170002)""" + pass + + +class StickerMaximumFramerateExceeded(HTTPException): + """Sticker maximum framerate exceeded (Code: 170003)""" + pass + + +class StickerFrameCountExceedsMaximumOf1000Frames(HTTPException): + """Sticker frame count exceeds maximum of 1000 frames (Code: 170004)""" + pass + + +class LottieAnimationMaximumDimensionsExceeded(HTTPException): + """Lottie animation maximum dimensions exceeded (Code: 170005)""" + pass + + +class StickerFrameRateIsEitherTooSmallOrTooLarge(HTTPException): + """Sticker frame rate is either too small or too large (Code: 170006)""" + pass + + +class StickerAnimationDurationExceedsMaximumOf5Seconds(HTTPException): + """Sticker animation duration exceeds maximum of 5 seconds (Code: 170007)""" + pass + + +class CannotUpdateAFinishedEvent(HTTPException): + """Cannot update a finished event (Code: 180000)""" + pass + + +class FailedToCreateStageNeededForStageEvent(HTTPException): + """Failed to create stage needed for stage event (Code: 180002)""" + pass + + +class MessageWasBlockedByAutomaticModeration(HTTPException): + """Message was blocked by automatic moderation (Code: 200000)""" + pass + + +class TitleWasBlockedByAutomaticModeration(HTTPException): + """Title was blocked by automatic moderation (Code: 200001)""" + pass + + +class WebhooksPostedToForumChannelsMustHaveAThreadNameOrThreadId(HTTPException): + """Webhooks posted to forum channels must have a thread_name or thread_id (Code: 220001)""" + pass + + +class WebhooksPostedToForumChannelsCannotHaveBothAThreadNameAndThreadId(HTTPException): + """Webhooks posted to forum channels cannot have both a thread_name and thread_id (Code: 220002)""" + pass + + +class WebhooksCanOnlyCreateThreadsInForumChannels(HTTPException): + """Webhooks can only create threads in forum channels (Code: 220003)""" + pass + + +class WebhookServicesCannotBeUsedInForumChannels(HTTPException): + """Webhook services cannot be used in forum channels (Code: 220004)""" + pass + + +class MessageBlockedByHarmfulLinksFilter(HTTPException): + """Message blocked by harmful links filter (Code: 240000)""" + pass + + +class CannotEnableOnboardingRequirementsAreNotMet(HTTPException): + """Cannot enable onboarding, requirements are not met (Code: 350000)""" + pass + + +class CannotUpdateOnboardingWhileBelowRequirements(HTTPException): + """Cannot update onboarding while below requirements (Code: 350001)""" + pass + + +class FailedToBanUsers(HTTPException): + """Failed to ban users (Code: 500000)""" + pass + + +class PollVotingBlocked(HTTPException): + """Poll voting blocked (Code: 520000)""" + pass + + +class PollExpired(HTTPException): + """Poll expired (Code: 520001)""" + pass + + +class InvalidChannelTypeForPollCreation(HTTPException): + """Invalid channel type for poll creation (Code: 520002)""" + pass + + +class CannotEditAPollMessage(HTTPException): + """Cannot edit a poll message (Code: 520003)""" + pass + + +class CannotUseAnEmojiIncludedWithThePoll(HTTPException): + """Cannot use an emoji included with the poll (Code: 520004)""" + pass + + +class CannotExpireANonPollMessage(HTTPException): + """Cannot expire a non-poll message (Code: 520006)""" + pass class AppCommandError(DisagreementException): """Base exception for application command related errors.""" diff --git a/disagreement/http.py b/disagreement/http.py index 3a81990..2f190e2 100644 --- a/disagreement/http.py +++ b/disagreement/http.py @@ -11,12 +11,7 @@ import json from urllib.parse import quote from typing import Optional, Dict, Any, Union, TYPE_CHECKING, List -from .errors import ( - HTTPException, - RateLimitError, - AuthenticationError, - DisagreementException, -) +from .errors import * # Import all custom exceptions from . import __version__ # For User-Agent from .rate_limiter import RateLimiter from .interactions import InteractionResponsePayload @@ -31,6 +26,232 @@ API_BASE_URL = "https://discord.com/api/v10" # Using API v10 logger = logging.getLogger(__name__) +DISCORD_ERROR_CODE_TO_EXCEPTION = { + 0: GeneralError, + 10001: UnknownAccount, + 10002: UnknownApplication, + 10003: UnknownChannel, + 10004: UnknownGuild, + 10005: UnknownIntegration, + 10006: UnknownInvite, + 10007: UnknownMember, + 10008: UnknownMessage, + 10009: UnknownPermissionOverwrite, + 10010: UnknownProvider, + 10011: UnknownRole, + 10012: UnknownToken, + 10013: UnknownUser, + 10014: UnknownEmoji, + 10015: UnknownWebhook, + 10016: UnknownWebhookService, + 10020: UnknownSession, + 10021: UnknownAsset, + 10026: UnknownBan, + 10027: UnknownSKU, + 10028: UnknownStoreListing, + 10029: UnknownEntitlement, + 10030: UnknownBuild, + 10031: UnknownLobby, + 10032: UnknownBranch, + 10033: UnknownStoreDirectoryLayout, + 10036: UnknownRedistributable, + 10038: UnknownGiftCode, + 10049: UnknownStream, + 10050: UnknownPremiumServerSubscribeCooldown, + 10057: UnknownGuildTemplate, + 10059: UnknownDiscoverableServerCategory, + 10060: UnknownSticker, + 10061: UnknownStickerPack, + 10062: UnknownInteraction, + 10063: UnknownApplicationCommand, + 10065: UnknownVoiceState, + 10066: UnknownApplicationCommandPermissions, + 10067: UnknownStageInstance, + 10068: UnknownGuildMemberVerificationForm, + 10069: UnknownGuildWelcomeScreen, + 10070: UnknownGuildScheduledEvent, + 10071: UnknownGuildScheduledEventUser, + 10087: UnknownTag, + 10097: UnknownSound, + 20001: BotsCannotUseThisEndpoint, + 20002: OnlyBotsCanUseThisEndpoint, + 20009: ExplicitContentCannotBeSentToTheDesiredRecipients, + 20012: NotAuthorizedToPerformThisActionOnThisApplication, + 20016: ActionCannotBePerformedDueToSlowmodeRateLimit, + 20018: OnlyTheOwnerOfThisAccountCanPerformThisAction, + 20022: MessageCannotBeEditedDueToAnnouncementRateLimits, + 20024: UnderMinimumAge, + 20028: ChannelHitWriteRateLimit, + 20029: ServerHitWriteRateLimit, + 20031: DisallowedWordsInStageTopicOrNames, + 20035: GuildPremiumSubscriptionLevelTooLow, + 30001: MaximumNumberOfGuildsReached, + 30002: MaximumNumberOfFriendsReached, + 30003: MaximumNumberOfPinsReached, + 30004: MaximumNumberOfRecipientsReached, + 30005: MaximumNumberOfGuildRolesReached, + 30007: MaximumNumberOfWebhooksReached, + 30008: MaximumNumberOfEmojisReached, + 30010: MaximumNumberOfReactionsReached, + 30011: MaximumNumberOfGroupDMsReached, + 30013: MaximumNumberOfGuildChannelsReached, + 30015: MaximumNumberOfAttachmentsInAMessageReached, + 30016: MaximumNumberOfInvitesReached, + 30018: MaximumNumberOfAnimatedEmojisReached, + 30019: MaximumNumberOfServerMembersReached, + 30030: MaximumNumberOfServerCategoriesReached, + 30031: GuildAlreadyHasATemplate, + 30032: MaximumNumberOfApplicationCommandsReached, + 30033: MaximumNumberOfThreadParticipantsReached, + 30034: MaximumNumberOfDailyApplicationCommandCreatesReached, + 30035: MaximumNumberOfBansForNonGuildMembersExceeded, + 30037: MaximumNumberOfBansFetchesReached, + 30038: MaximumNumberOfUncompletedGuildScheduledEventsReached, + 30039: MaximumNumberOfStickersReached, + 30040: MaximumNumberOfPruneRequestsReached, + 30042: MaximumNumberOfGuildWidgetSettingsUpdatesReached, + 30045: MaximumNumberOfSoundboardSoundsReached, + 30046: MaximumNumberOfEditsToMessagesOlderThan1HourReached, + 30047: MaximumNumberOfPinnedThreadsInAForumChannelReached, + 30048: MaximumNumberOfTagsInAForumChannelReached, + 30052: BitrateIsTooHighForChannelOfThisType, + 30056: MaximumNumberOfPremiumEmojisReached, + 30058: MaximumNumberOfWebhooksPerGuildReached, + 30061: MaximumNumberOfChannelPermissionOverwritesReached, + 30062: TheChannelsForThisGuildAreTooLarge, + 40001: Unauthorized, + 40002: YouNeedToVerifyYourAccount, + 40003: YouAreOpeningDirectMessagesTooFast, + 40004: SendMessagesHasBeenTemporarilyDisabled, + 40005: RequestEntityTooLarge, + 40006: ThisFeatureHasBeenTemporarilyDisabledServerSide, + 40007: TheUserIsBannedFromThisGuild, + 40012: ConnectionHasBeenRevoked, + 40018: OnlyConsumableSKUsCanBeConsumed, + 40019: YouCanOnlyDeleteSandboxEntitlements, + 40032: TargetUserIsNotConnectedToVoice, + 40033: ThisMessageHasAlreadyBeenCrossposted, + 40041: AnApplicationCommandWithThatNameAlreadyExists, + 40043: ApplicationInteractionFailedToSend, + 40058: CannotSendAMessageInAForumChannel, + 40060: InteractionHasAlreadyBeenAcknowledged, + 40061: TagNamesMustBeUnique, + 40062: ServiceResourceIsBeingRateLimited, + 40066: ThereAreNoTagsAvailableThatCanBeSetByNonModerators, + 40067: ATagIsRequiredToCreateAForumPostInThisChannel, + 40074: AnEntitlementHasAlreadyBeenGrantedForThisResource, + 40094: ThisInteractionHasHitTheMaximumNumberOfFollowUpMessages, + 40333: CloudflareIsBlockingYourRequest, + 50001: MissingAccess, + 50002: InvalidAccountType, + 50003: CannotExecuteActionOnADMChannel, + 50004: GuildWidgetDisabled, + 50005: CannotEditAMessageAuthoredByAnotherUser, + 50006: CannotSendAnEmptyMessage, + 50007: CannotSendMessagesToThisUser, + 50008: CannotSendMessagesInANonTextChannel, + 50009: ChannelVerificationLevelIsTooHighForYouToGainAccess, + 50010: OAuth2ApplicationDoesNotHaveABot, + 50011: OAuth2ApplicationLimitReached, + 50012: InvalidOAuth2State, + 50013: YouLackPermissionsToPerformThatAction, + 50014: InvalidAuthenticationTokenProvided, + 50015: NoteWasTooLong, + 50016: ProvidedTooFewOrTooManyMessagesToDelete, + 50017: InvalidMFALevel, + 50019: AMessageCanOnlyBePinnedToTheChannelItWasSentIn, + 50020: InviteCodeWasEitherInvalidOrTaken, + 50021: CannotExecuteActionOnASystemMessage, + 50024: CannotExecuteActionOnThisChannelType, + 50025: InvalidOAuth2AccessTokenProvided, + 50026: MissingRequiredOAuth2Scope, + 50027: InvalidWebhookTokenProvided, + 50028: InvalidRole, + 50033: InvalidRecipients, + 50034: AMessageProvidedWasTooOldToBulkDelete, + 50035: InvalidFormBody, + 50036: AnInviteWasAcceptedToAGuildTheApplicationBotIsNotIn, + 50039: InvalidActivityAction, + 50041: InvalidAPIVersionProvided, + 50045: FileUploadedExceedsTheMaximumSize, + 50046: InvalidFileUploaded, + 50054: CannotSelfRedeemThisGift, + 50055: InvalidGuild, + 50057: InvalidSKU, + 50067: InvalidRequestOrigin, + 50068: InvalidMessageType, + 50070: PaymentSourceRequiredToRedeemGift, + 50073: CannotModifyASystemWebhook, + 50074: CannotDeleteAChannelRequiredForCommunityGuilds, + 50080: CannotEditStickersWithinAMessage, + 50081: InvalidStickerSent, + 50083: TriedToPerformAnOperationOnAnArchivedThread, + 50085: InvalidThreadNotificationSettings, + 50086: BeforeValueIsEarlierThanTheThreadCreationDate, + 50087: CommunityServerChannelsMustBeTextChannels, + 50091: TheEntityTypeOfTheEventIsDifferentFromTheEntityYouAreTryingToStartTheEventFor, + 50095: ThisServerIsNotAvailableInYourLocation, + 50097: ThisServerNeedsMonetizationEnabledInOrderToPerformThisAction, + 50101: ThisServerNeedsMoreBoostsToPerformThisAction, + 50109: TheRequestBodyContainsInvalidJSON, + 50110: TheProvidedFileIsInvalid, + 50123: TheProvidedFileTypeIsInvalid, + 50124: TheProvidedFileDurationExceedsMaximumOf52Seconds, + 50131: OwnerCannotBePendingMember, + 50132: OwnershipCannotBeTransferredToABotUser, + 50138: FailedToResizeAssetBelowTheMaximumSize, + 50144: CannotMixSubscriptionAndNonSubscriptionRolesForAnEmoji, + 50145: CannotConvertBetweenPremiumEmojiAndNormalEmoji, + 50146: UploadedFileNotFound, + 50151: TheSpecifiedEmojiIsInvalid, + 50159: VoiceMessagesDoNotSupportAdditionalContent, + 50160: VoiceMessagesMustHaveASingleAudioAttachment, + 50161: VoiceMessagesMustHaveSupportingMetadata, + 50162: VoiceMessagesCannotBeEdited, + 50163: CannotDeleteGuildSubscriptionIntegration, + 50173: YouCannotSendVoiceMessagesInThisChannel, + 50178: TheUserAccountMustFirstBeVerified, + 50192: TheProvidedFileDoesNotHaveAValidDuration, + 50600: YouDoNotHavePermissionToSendThisSticker, + 60003: TwoFactorIsRequiredForThisOperation, + 80004: NoUsersWithDiscordTagExist, + 90001: ReactionWasBlocked, + 90002: UserCannotUseBurstReactions, + 110001: ApplicationNotYetAvailable, + 130000: APIResourceIsCurrentlyOverloaded, + 150006: TheStageIsAlreadyOpen, + 160002: CannotReplyWithoutPermissionToReadMessageHistory, + 160004: AThreadHasAlreadyBeenCreatedForThisMessage, + 160005: ThreadIsLocked, + 160006: MaximumNumberOfActiveThreadsReached, + 160007: MaximumNumberOfActiveAnnouncementThreadsReached, + 170001: InvalidJSONForUploadedLottieFile, + 170002: UploadedLottiesCannotContainRasterizedImages, + 170003: StickerMaximumFramerateExceeded, + 170004: StickerFrameCountExceedsMaximumOf1000Frames, + 170005: LottieAnimationMaximumDimensionsExceeded, + 170006: StickerFrameRateIsEitherTooSmallOrTooLarge, + 170007: StickerAnimationDurationExceedsMaximumOf5Seconds, + 180000: CannotUpdateAFinishedEvent, + 180002: FailedToCreateStageNeededForStageEvent, + 200000: MessageWasBlockedByAutomaticModeration, + 200001: TitleWasBlockedByAutomaticModeration, + 220001: WebhooksPostedToForumChannelsMustHaveAThreadNameOrThreadId, + 220002: WebhooksPostedToForumChannelsCannotHaveBothAThreadNameAndThreadId, + 220003: WebhooksCanOnlyCreateThreadsInForumChannels, + 220004: WebhookServicesCannotBeUsedInForumChannels, + 240000: MessageBlockedByHarmfulLinksFilter, + 350000: CannotEnableOnboardingRequirementsAreNotMet, + 350001: CannotUpdateOnboardingWhileBelowRequirements, + 500000: FailedToBanUsers, + 520000: PollVotingBlocked, + 520001: PollExpired, + 520002: InvalidChannelTypeForPollCreation, + 520003: CannotEditAPollMessage, + 520004: CannotUseAnEmojiIncludedWithThePoll, + 520006: CannotExpireANonPollMessage, +} + class HTTPClient: """Handles HTTP requests to the Discord API.""" @@ -208,6 +429,17 @@ class HTTPClient: discord_error_code = ( data.get("code") if isinstance(data, dict) else None ) + + if discord_error_code in DISCORD_ERROR_CODE_TO_EXCEPTION: + exc_class = DISCORD_ERROR_CODE_TO_EXCEPTION[discord_error_code] + raise exc_class( + response, + f"API Error on {method} {endpoint}: {error_text}", + status=response.status, + text=error_text, + error_code=discord_error_code, + ) + raise HTTPException( response, f"API Error on {method} {endpoint}: {error_text}", diff --git a/tests/test_errors.py b/tests/test_errors.py index 7a104ba..42ffe0d 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,12 +1,91 @@ import pytest - +import aiohttp +from unittest.mock import MagicMock, patch from disagreement.errors import ( HTTPException, RateLimitError, AppCommandOptionConversionError, Forbidden, NotFound, + UnknownAccount, + MaximumNumberOfGuildsReached, ) +from disagreement.http import HTTPClient + + +# A fixture to provide an HTTPClient with a mocked session +@pytest.fixture +def http_client(): + # Using a real session and patching the request method is more robust + client = HTTPClient(token="fake_token") + yield client + # Cleanup: close the session after the test + # This requires making the fixture async or running this in an event loop + async def close_session(): + if client._session: + await client.close() + + import asyncio + + try: + loop = asyncio.get_running_loop() + loop.run_until_complete(close_session()) + except RuntimeError: + asyncio.run(close_session()) + + +# Mock aiohttp response +class MockAiohttpResponse: + def __init__(self, status, json_data, headers=None): + self.status = status + self._json_data = json_data + self.headers = headers or {"Content-Type": "application/json"} + + async def json(self): + return self._json_data + + async def text(self): + return str(self._json_data) + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + pass + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "error_code, error_message, expected_exception", + [ + (10001, "Unknown account", UnknownAccount), + (30001, "Maximum number of guilds reached", MaximumNumberOfGuildsReached), + ], +) +async def test_error_code_mapping_raises_correct_exception( + http_client, error_code, error_message, expected_exception +): + """ + Tests if the HTTP client correctly raises a specific exception + based on the Discord error code. + """ + mock_response = MockAiohttpResponse( + status=400, json_data={"code": error_code, "message": error_message} + ) + + # Patch the session object to control the response + with patch("aiohttp.ClientSession") as mock_session_class: + mock_session_instance = mock_session_class.return_value + mock_session_instance.request.return_value = mock_response + + # Assert that the correct exception is raised + with pytest.raises(expected_exception) as excinfo: + await http_client.request("GET", "/test-endpoint") + + # Optionally, check the exception details + assert excinfo.value.status == 400 + assert excinfo.value.error_code == error_code + assert error_message in str(excinfo.value) def test_http_exception_message():