Compare commits
3 Commits
15d95bc786
...
a702c66603
Author | SHA1 | Date | |
---|---|---|---|
a702c66603 | |||
aec0de3e58 | |||
66288ba920 |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
|
@ -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}",
|
||||
|
@ -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():
|
||||
|
Loading…
x
Reference in New Issue
Block a user