From 3c638d17ced197917bc202ba885893ce453fd4f6 Mon Sep 17 00:00:00 2001 From: Slipstream Date: Wed, 11 Jun 2025 15:13:37 -0600 Subject: [PATCH] Adds latency display and improves bot configuration Adds millisecond latency property to gateway and client for more precise monitoring. Enhances ping command to display gateway latency for better debugging. Configures message content intent and mention replies for improved bot functionality. Adds dotenv support for cleaner environment variable management. Formats error classes for better code consistency and readability. --- README.md | 11 +- disagreement/client.py | 7 + disagreement/errors.py | 229 +++++++++++++++++++++++++++++++- disagreement/gateway.py | 7 + examples/example_from_readme.py | 41 ++++++ tests/test_errors.py | 1 + 6 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 examples/example_from_readme.py diff --git a/README.md b/README.md index a99e865..1a9c5ed 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ import os import disagreement from disagreement.ext import commands +from dotenv import load_dotenv +load_dotenv() class Basics(commands.Cog): @@ -46,18 +48,17 @@ class Basics(commands.Cog): @commands.command() async def ping(self, ctx: commands.CommandContext) -> None: - await ctx.reply("Pong!") + await ctx.reply(f"Pong! Gateway Latency: {self.client.latency_ms} ms.") # type: ignore (latency is None during static analysis) token = os.getenv("DISCORD_BOT_TOKEN") if not token: raise RuntimeError("DISCORD_BOT_TOKEN environment variable not set") -client = disagreement.Client(token=token, command_prefix="!") -client.add_cog(Basics(client)) - - +intents = disagreement.GatewayIntent.default() | disagreement.GatewayIntent.MESSAGE_CONTENT +client = disagreement.Client(token=token, command_prefix="!", intents=intents, mention_replies=True) async def main() -> None: + client.add_cog(Basics(client)) await client.run() diff --git a/disagreement/client.py b/disagreement/client.py index 03fb37b..49f3437 100644 --- a/disagreement/client.py +++ b/disagreement/client.py @@ -412,6 +412,13 @@ class Client: return self._gateway.latency return None + @property + def latency_ms(self) -> Optional[float]: + """Returns the gateway latency in milliseconds, or ``None`` if unavailable.""" + if self._gateway: + return self._gateway.latency_ms + return None + async def wait_until_ready(self) -> None: """|coro| Waits until the client is fully connected to Discord and the initial state is processed. diff --git a/disagreement/errors.py b/disagreement/errors.py index 051231c..b09b59c 100644 --- a/disagreement/errors.py +++ b/disagreement/errors.py @@ -91,1120 +91,1347 @@ 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): +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/gateway.py b/disagreement/gateway.py index 6499607..2cc8a81 100644 --- a/disagreement/gateway.py +++ b/disagreement/gateway.py @@ -621,6 +621,13 @@ class GatewayClient: return None return self._last_heartbeat_ack - self._last_heartbeat_sent + @property + def latency_ms(self) -> Optional[float]: + """Returns the latency between heartbeat and ACK in milliseconds.""" + if self._last_heartbeat_sent is None or self._last_heartbeat_ack is None: + return None + return (self._last_heartbeat_ack - self._last_heartbeat_sent) * 1000 + @property def last_heartbeat_sent(self) -> Optional[float]: return self._last_heartbeat_sent diff --git a/examples/example_from_readme.py b/examples/example_from_readme.py new file mode 100644 index 0000000..acdb411 --- /dev/null +++ b/examples/example_from_readme.py @@ -0,0 +1,41 @@ +# Example from the README file of the disagreement library +# This example demonstrates a simple bot that responds to the "!ping" command with "Pong!". + +import asyncio +import os + +import disagreement +from disagreement.ext import commands +from dotenv import load_dotenv + +load_dotenv() + + +class Basics(commands.Cog): + def __init__(self, client: disagreement.Client) -> None: + super().__init__(client) + + @commands.command() + async def ping(self, ctx: commands.CommandContext) -> None: + await ctx.reply(f"Pong! Gateway Latency: {self.client.latency_ms} ms.") # type: ignore (latency is None during static analysis) + + +token = os.getenv("DISCORD_BOT_TOKEN") +if not token: + raise RuntimeError("DISCORD_BOT_TOKEN environment variable not set") + +intents = ( + disagreement.GatewayIntent.default() | disagreement.GatewayIntent.MESSAGE_CONTENT +) +client = disagreement.Client( + token=token, command_prefix="!", intents=intents, mention_replies=True +) + + +async def main() -> None: + client.add_cog(Basics(client)) + await client.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/test_errors.py b/tests/test_errors.py index 42ffe0d..e948c11 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -19,6 +19,7 @@ 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():