Compare commits

..

No commits in common. "a702c66603ed0622d1b97cb5378d3f4eadd10054" and "15d95bc78604ddf3bd23f58d12f80f7798649531" have entirely different histories.

4 changed files with 7 additions and 1432 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@

View File

@ -11,7 +11,12 @@ import json
from urllib.parse import quote
from typing import Optional, Dict, Any, Union, TYPE_CHECKING, List
from .errors import * # Import all custom exceptions
from .errors import (
HTTPException,
RateLimitError,
AuthenticationError,
DisagreementException,
)
from . import __version__ # For User-Agent
from .rate_limiter import RateLimiter
from .interactions import InteractionResponsePayload
@ -26,232 +31,6 @@ 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."""
@ -429,17 +208,6 @@ 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}",

View File

@ -1,91 +1,12 @@
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():