From 99ec26765dc37ccb8c369a4b2f2a27431b315bb2 Mon Sep 17 00:00:00 2001 From: Donovan Daniels Date: Thu, 18 Apr 2024 13:24:17 -0500 Subject: [PATCH] Polls (#141) discord/discord-api-docs#6746 --- esm.mjs | 2 + lib/Constants.ts | 571 ++++++++++++++++++---------------- lib/gateway/Shard.ts | 42 ++- lib/index.ts | 1 + lib/routes/Channels.ts | 61 +++- lib/routes/OAuth.ts | 2 +- lib/structures/Guild.ts | 4 +- lib/structures/Interaction.ts | 5 +- lib/structures/Message.ts | 29 +- lib/structures/Poll.ts | 85 +++++ lib/types/applications.d.ts | 2 +- lib/types/channels.d.ts | 96 +++++- lib/types/events.d.ts | 15 +- lib/types/gateway-raw.d.ts | 21 +- lib/types/gateway.d.ts | 10 +- lib/types/interactions.d.ts | 2 +- lib/types/json.d.ts | 17 +- lib/types/webhooks.d.ts | 2 +- lib/util/Routes.ts | 38 +-- lib/util/Util.ts | 52 ++++ 20 files changed, 736 insertions(+), 321 deletions(-) create mode 100644 lib/structures/Poll.ts diff --git a/esm.mjs b/esm.mjs index 9d9f0e6b..1e73f0b2 100644 --- a/esm.mjs +++ b/esm.mjs @@ -49,6 +49,7 @@ const PartialApplication = (await import("./dist/lib/structures/PartialApplicati const Permission = (await import("./dist/lib/structures/Permission.js")).default.default; const PermissionOverwrite = (await import("./dist/lib/structures/PermissionOverwrite.js")).default.default; const PingInteraction = (await import("./dist/lib/structures/PingInteraction.js")).default.default; +const Poll = (await import("./dist/lib/structures/Poll.js")).default.default; const PrivateChannel = (await import("./dist/lib/structures/PrivateChannel.js")).default.default; const PrivateThreadChannel = (await import("./dist/lib/structures/PrivateThreadChannel.js")).default.default; const PublicThreadChannel = (await import("./dist/lib/structures/PublicThreadChannel.js")).default.default; @@ -134,6 +135,7 @@ export { Permission, PermissionOverwrite, PingInteraction, + Poll, PrivateChannel, PrivateThreadChannel, PublicThreadChannel, diff --git a/lib/Constants.ts b/lib/Constants.ts index 325c079d..d909ff72 100644 --- a/lib/Constants.ts +++ b/lib/Constants.ts @@ -512,6 +512,7 @@ export namespace Permissions { export const SEND_VOICE_MESSAGES = 70368744177664n; // 1 << 46 export const USE_CLYDE_AI = 140737488355328n; // 1 << 47 export const SET_VOICE_CHANNEL_STATUS = 281474976710656n; // 1 << 48 + export const SEND_POLLS = 562949953421312n; // 1 << 49 } // bigints can't be used as object keys, so we need to convert them to strings @@ -540,7 +541,8 @@ export const TextPermissions = [ Permissions.USE_EXTERNAL_STICKERS, Permissions.SEND_MESSAGES_IN_THREADS, Permissions.SEND_VOICE_MESSAGES, - Permissions.USE_CLYDE_AI + Permissions.USE_CLYDE_AI, + Permissions.SEND_POLLS ] as const; export const AllTextPermissions = TextPermissions.reduce((all, p) => all | p, 0n); export const AllTextPermissionNames = TextPermissions.map(p => PermissionValueToName[String(p) as `${typeof p}`]); @@ -576,7 +578,8 @@ export const VoicePermissions = [ Permissions.USE_EXTERNAL_SOUNDS, Permissions.SEND_VOICE_MESSAGES, Permissions.USE_CLYDE_AI, - Permissions.SET_VOICE_CHANNEL_STATUS + Permissions.SET_VOICE_CHANNEL_STATUS, + Permissions.SEND_POLLS ] as const; export const AllVoicePermissions = VoicePermissions.reduce((all, p) => all | p, 0n); export const AllVoicePermissionNames = VoicePermissions.map(p => PermissionValueToName[String(p) as `${typeof p}`]); @@ -605,7 +608,8 @@ export const StagePermissions = [ Permissions.MANAGE_EVENTS, Permissions.USE_EXTERNAL_STICKERS, Permissions.SEND_VOICE_MESSAGES, - Permissions.USE_CLYDE_AI + Permissions.USE_CLYDE_AI, + Permissions.SEND_POLLS ] as const; export const AllStagePermissions = StagePermissions.reduce((all, p) => all | p, 0n); export const AllStagePermissionNames = StagePermissions.map(p => PermissionValueToName[String(p) as `${typeof p}`]); @@ -1033,6 +1037,8 @@ export enum Intents { GUILD_SCHEDULED_EVENTS = 1 << 16, AUTO_MODERATION_CONFIGURATION = 1 << 20, AUTO_MODERATION_EXECUTION = 1 << 21, + GUILD_MESSAGE_POLLS = 1 << 24, + DIRECT_MESSAGE_POLLS = 1 << 25, } export type IntentNames = keyof typeof Intents; @@ -1053,7 +1059,9 @@ export const NonPrivilegedIntents = [ Intents.DIRECT_MESSAGE_TYPING, Intents.GUILD_SCHEDULED_EVENTS, Intents.AUTO_MODERATION_CONFIGURATION, - Intents.AUTO_MODERATION_EXECUTION + Intents.AUTO_MODERATION_EXECUTION, + Intents.GUILD_MESSAGE_POLLS, + Intents.DIRECT_MESSAGE_POLLS ] as const; export const AllNonPrivilegedIntents = NonPrivilegedIntents.reduce((all, p) => all | p, 0); export const PrivilegedIntents = [ @@ -1236,282 +1244,293 @@ export enum SKUAccessTypes { PUBLIC = 1, } +export enum PollLayoutType { + DEFAULT = 1, +} + /* eslint-disable @typescript-eslint/no-duplicate-enum-values */ // entries are intentionally not aligned /** The error codes that can be received. See [Discord's Documentation](https://discord.com/developers/docs/topics/opcodes-and-status-codes#json). */ export enum JSONErrorCodes { GENERAL_ERROR = 0, - UNKNOWN_ACCOUNT = 10001, - UNKNOWN_APPLICATION = 10002, - UNKNOWN_CHANNEL = 10003, - UNKNOWN_GUILD = 10004, - UNKNOWN_INTEGRATION = 10005, - UNKNOWN_INVITE = 10006, - UNKNOWN_MEMBER = 10007, - UNKNOWN_MESSAGE = 10008, - UNKNOWN_OVERWRITE = 10009, - UNKNOWN_PROVIDER = 10010, - UNKNOWN_ROLE = 10011, - UNKNOWN_TOKEN = 10012, - UNKNOWN_USER = 10013, - UNKNOWN_EMOJI = 10014, - UNKNOWN_WEBHOOK = 10015, - UNKNOWN_WEBHOOK_SERVICE = 10016, - UNKNOWN_SESSION = 10020, - UNKNOWN_BAN = 10026, - UNKNOWN_SKU = 10027, - UNKNOWN_STORE_LISTING = 10028, - UNKNOWN_ENTITLEMENT = 10029, - UNKNOWN_BUILD = 10030, - UNKNOWN_LOBBY = 10031, - UNKNOWN_BRANCH = 10032, - UNKNOWN_STORE_DIRECTORY_LAYOUT = 10036, - UNKNOWN_REDISTRIBUTABLE = 10037, - UNKNOWN_GIFT_CODE = 10038, - UNKNOWN_STREAM = 10049, - UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN = 10050, - UNKNOWN_GUILD_TEMPLATE = 10057, - UNKNOWN_DISCOVERABLE_SERVER_CATEGORY = 10059, - UNKNOWN_STICKER = 10060, - UNKNOWN_INTERACTION = 10062, - UNKNOWN_APPLICATION_COMMAND = 10063, - UNKNOWN_APPLICATION_COMMAND_PERMISSIONS = 10066, - UNKNOWN_STAGE_INSTANCE = 10067, - UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM = 10068, - UNKNOWN_GUILD_WELCOME_SCREEN = 10069, - UNKNOWN_GUILD_SCHEDULED_EVENT = 10070, - UNKNOWN_GUILD_SCHEDULED_EVENT_USER = 10071, - UNKNOWN_TAG = 10087, - BOT_DISALLOWED = 20001, - BOTS_CANNOT_USE_THIS_ENDPOINT = 20001, - BOT_REQUIRED = 20002, - ONLY_BOTS_CAN_USE_THIS_ENDPOINT = 20002, - RPC_PROXY_DISALLOWED = 20003, - EXPLICIT_CONTENT = 20009, - ACCOUNT_SCHEDULED_FOR_DELETION = 20011, - NOT_AUTHORIZED_FOR_APPLICATION = 20012, - ACCOUNT_DISABLED = 20013, - SLOWMODE_RATE_LIMITED = 20016, - ACCOUNT_OWNER_ONLY = 20018, - CHANNEL_FOLLOWING_EDIT_RATE_LIMITED = 20022, - UNDER_MINIMUM_AGE = 20024, - QUARANTINED = 20026, - CHANNEL_WRITE_RATE_LIMIT = 20028, - GUILD_WRITE_RATE_LIMIT = 20029, - WORDS_NOT_ALLOWED = 20031, - VANITY_URL_REQUIRED_FOR_PUBLISHED_GUILDS = 20040, - VANITY_URL_EMPLOYEE_ONLY_GUILD_DISABLED = 20044, - VANITY_URL_REQUIREMENTS_NOT_MET = 20045, - TOO_MANY_GUILDS = 30001, - TOO_MANY_FRIENDS = 30002, - TOO_MANY_PINS_IN_CHANNEL = 30003, - TOO_MANY_RECIPIENTS = 30004, - TOO_MANY_GUILD_ROLES = 30005, - TOO_MANY_USING_USERNAME = 30006, - TOO_MANY_WEBHOOKS = 30007, - TOO_MANY_EMOJI = 30008, - TOO_MANY_REACTIONS = 30010, - TOO_MANY_GROUP_CHANNELS = 30011, - TOO_MANY_CHANNELS = 30013, - TOO_MANY_ATTACHMENTS = 30015, - TOO_MANY_INVITES = 30016, - TOO_MANY_ANIMATED_EMOJI = 30018, - GUILD_AT_CAPACITY = 30019, - NOT_ENOUGH_GUILD_MEMBERS = 30029, - TOO_MANY_SERVER_CATEGORIES = 30030, - GUILD_ALREADY_HAS_TEMPLATE = 30031, - TOO_MANY_APPLICATION_COMMANDS = 30032, - TOO_MANY_THREAD_MEMBERS = 30033, - TOO_MANY_APPLICATION_COMMAND_CREATES = 30034, - TOO_MANY_BANS_FOR_NON_GUILD_MEMBERS = 30035, - TOO_MANY_BAN_FETCHES = 30037, - TOO_MANY_UNCOMPLETED_GUILD_SCHEDULED_EVENTS = 30038, - TOO_MANY_STICKERS = 30039, - TOO_MANY_PRUNE_REQUESTS = 30040, - TOO_MANY_GUILD_WIDGET_SETTINGS_UPDATES = 30042, - MAXIMUM_NUMBER_OR_EDITS_TO_MESSAGES_OLDER_THAN_1_HOUR = 30046, - TOO_MANY_PINNED_THREADS = 30047, - TOO_MANY_FORUM_TAGS = 30048, - BITRATE_TOO_HIGH = 30052, - TOO_MANY_PREMIUM_EMOJIS = 30056, - TOO_MANY_GUILD_WEBHOOKS = 30058, - TOO_MANY_BLOCKED_USERS = 30059, - TOO_MANY_PUBLISHED_PRODUCT_LISTINGS = 30065, - RESOURCE_RATE_LIMITED = 31002, - UNAUTHORIZED = 40001, - EMAIL_VERIFICATION_REQUIRED = 40002, - RATE_LIMIT_DM_OPEN = 40003, - DIRECT_MESSAGES_RATE_LIMIT = 40003, - SENDING_MESSAGES_TEMPORARILY_DISABLED = 40004, - ENTITY_TOO_LARGE = 40005, - REQUEST_ENTITY_TOO_LARGE = 40005, - ENTITY_EMPTY = 40006, - FEATURE_TEMPORARILY_DISABLED = 40006, - USER_BANNED = 40007, - CONNECTION_REVOKED = 40012, - DELETE_ACCOUNT_TRANSFER_TEAM_OWNERSHIP = 40028, - TARGET_USER_NOT_CONNECTED_TO_VOICE = 40032, - ALREADY_CROSSPOSTED = 40033, - APPLICATION_COMMAND_ALREADY_EXISTS = 40041, - INTERACTION_FAILED_TO_SEND = 40043, - CANNOT_SEND_MESSAGES_IN_FORUM_CHANNEL = 40058, - INTERACTION_ALREADY_ACKNOWLEDGED = 40060, - TAG_NAMES_MUST_BE_UNIQUE = 40061, - SERVICE_RESOURCE_RATE_LIMITED = 40062, - NON_MODERATED_TAG_REQUIRED = 40066, - TAG_REQUIRED = 40067, - USER_QUARANTINED = 40068, - INVITES_DISABLED = 40069, - INVALID_ACCESS = 50001, - MISSING_ACCESS = 50001, - INVALID_ACCOUNT_TYPE = 50002, - INVALID_ACTION_DM = 50003, - INVALID_EMBED_DISABLED = 50004, - INVALID_MESSAGE_AUTHOR = 50005, - INVALID_MESSAGE_EMPTY = 50006, - INVALID_MESSAGE_SEND_USER = 50007, - INVALID_MESSAGE_SEND_NON_TEXT = 50008, - INVALID_MESSAGE_VERIFICATION_LEVEL = 50009, - INVALID_OAUTH_APP_BOT = 50010, - INVALID_OAUTH_APP_LIMIT = 50011, - INVALID_OAUTH_STATE = 50012, - INVALID_PERMISSIONS = 50013, - INVALID_TOKEN = 50014, - INVALID_NOTE = 50015, - INVALID_BULK_DELETE_COUNT = 50016, - INVALID_MFA_LEVEL = 50017, - INVALID_PASSWORD = 50018, - INVALID_PIN_MESSAGE_CHANNEL = 50019, - INVALID_INVITE_CODE = 50020, - CANNOT_EXECUTE_ON_SYSTEM_MESSAGE = 50021, - INVALID_PHONE_NUMBER = 50022, - INVALID_CLIENT_ID = 50023, - INVALID_CHANNEL_TYPE = 50024, - INVALID_OAUTH2_ACCESS_TOKEN = 50025, - INVALID_OAUTH2_MISSING_SCOPE = 50026, - INVALID_WEBHOOK_TOKEN = 50027, - INVALID_ROLE = 50028, - INVALID_RECIPIENTS = 50033, - BULK_DELETE_MESSAGE_TOO_OLD = 50034, - INVALID_FORM_BODY = 50035, - INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT = 50036, - INVALID_ACTIVITY_ACTION = 500039, - INVALID_API_VERSION = 50041, - INVALID_FILE_ASSET_SIZE = 50045, - INVALID_FILE_ASSET = 50046, - INVALID_GIFT_REDEMPTION_EXHAUSTED = 50050, - INVALID_GIFT_REDEMPTION_OWNED = 50051, - INVALID_GIFT_SELF_REDEMPTION = 50054, - INVALID_GUILD = 50055, - INVALID_REQUEST_ORIGIN = 50067, - INVALID_MESSAGE_TYPE = 50068, - PAYMENT_SOURCE_REQUIRED = 50070, - CANNOT_MODIFY_SYSTEM_WEBHOOK = 50073, - CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL = 50074, - CANNOT_EDIT_MESSAGE_STICKERS = 50080, - INVALID_STICKER_SENT = 50081, - THREAD_ARCHIVED = 50083, - INVALID_THREAD_NOTIFICATION_SETTINGS = 50084, - BEFORE_EARLIER_THAN_THREAD_CREATION_DATE = 50085, - COMMUNITY_CHANNELS_MUST_BE_TEXT = 50086, - INVALID_COUNTRY_CODE = 50095, - INVALID_CANNOT_FRIEND_SELF = 50096, - INVALID_GIFT_REDEMPTION_FRAUD_REJECTED = 50097, - MONETIZATION_REQUIRED = 50097, - BOOSTS_REQUIRED = 50101, - INVALID_USER_SETTINGS_DATA = 50105, - INVALID_ACTIVITY_LAUNCH_NO_ACCESS = 50106, - INVALID_ACTIVITY_LAUNCH_PREMIUM_TIER = 50107, - INVALID_ACTIVITY_LAUNCH_CONCURRENT_ACTIVITIES = 50108, - INVALID_JSON = 50109, - OWNER_CANNOT_BE_PENDING_MEMBER = 50131, - OWNERSHIP_CANNOT_BE_TRANSFERRED_TO_BOT = 50132, - INVALID_FILE_ASSET_SIZE_RESIZE_GIF = 50138, - CANNOT_MIX_SUBSCRIPTION_AND_NON_SUBSCRIPTION_ROLES = 50144, - CANNOT_CONVERT_BETWEEN_PREMIUM_AND_NORMAL_EMOJI = 50145, - UPLOADED_FILE_NOT_FOUND = 50146, - INVALID_ACTIVITY_LAUNCH_AFK_CHANNEL = 50148, - VOICE_MESSAGES_DO_NOT_SUPPORT_ADDITIONAL_CONTENT = 50159, - VOICE_MESSAGES_MUST_HAVE_A_SINGLE_AUDIO_ATTACHMENT = 50160, - VOICE_MESSAGES_MUST_HAVE_SUPPORTING_METADATA = 50161, - VOICE_MESSAGES_CANNOT_BE_EDITED = 50162, - CANNOT_DELETE_GUILD_SUBSCRIPTION_INTEGRATION = 50163, - NEW_OWNER_INELIGIBLE_FOR_SERVER_SUBSCRIPTION = 50164, - INVALID_ACTIVITY_LAUNCH_AGE_GATED = 50165, - CANNOT_SEND_VOICE_MESSAGES_IN_CHANNEL = 50173, - USER_MUST_FIRST_BE_VERIFIED = 50178, - INVALID_SKU_ATTACHMENT_NO_ARCHIVES = 50186, - NO_PERMISSION_TO_SEND_STICKER = 50600, - MFA_ENABLED = 60001, - MFA_DISABLED = 60002, - MFA_REQUIRED = 60003, - MFA_UNVERIFIED = 60004, - MFA_INVALID_SECRET = 60005, - MFA_INVALID_TICKET = 60006, - MFA_INVALID_CODE = 60008, - MFA_INVALID_SESSION = 60009, - PHONE_NUMBER_UNABLE_TO_SEND = 70003, - PHONE_VERIFICATION_REQUIRED = 70007, - RELATIONSHIP_INCOMING_DISABLED = 80000, - RELATIONSHIP_INCOMING_BLOCKED = 80001, - RELATIONSHIP_INVALID_USER_BOT = 80002, - RELATIONSHIP_INVALID_SELF = 80003, - RELATIONSHIP_INVALID_DISCORD_TAG = 80004, - RELATIONSHIP_ALREADY_FRIENDS = 80007, - REACTION_BLOCKED = 90001, - USER_CANNOT_USE_BURST_REACTIONS = 90002, - INVALID_GIFT_REDEMPTION_SUBSCRIPTION_MANAGED = 100021, - INVALID_GIFT_REDEMPTION_SUBSCRIPTION_INCOMPATIBLE = 100023, - INVALID_GIFT_REDEMPTION_INVOICE_OPEN = 100024, - INELIGIBLE_FOR_SUBSCRIPTION = 100053, - BILLING_NON_REFUNDABLE_PAYMENT_SOURCE = 100060, - APPLICATION_NOT_AVAILABLE = 110001, - LISTING_ALREADY_JOINED = 120000, - LISTING_TOO_MANY_MEMBERS = 120001, - LISTING_JOIN_BLOCKED = 120002, - API_RESOURCE_IS_CURRENTLY_OVERLOADED = 130000, - STAGE_ALREADY_OPEN = 150006, - CANNOT_REPLY_WITHOUT_READ_MESSAGE_HISTORY = 160002, - THREAD_ALREADY_CREATED_FOR_MESSAGE = 160004, - THREAD_IS_LOCKED = 160005, - TOO_MANY_THREADS = 160006, - TOO_MANY_ANNOUNCEMENT_THREADS = 160007, - INVALID_LOTTIE_JSON = 170001, - UPLOADED_LOTTIE_RASTERIZED = 170002, - STICKER_MAXIMUM_FRAMERATE_EXCEEDED = 170003, - STICKER_FRAME_COUNT_EXCEEDS_MAXIMUM = 170004, - LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED = 170005, - STICKER_FRAME_RATE_TOO_SMALL_OR_LARGE = 170006, - STICKER_ANIMATION_DURATION_TOO_LONG = 170007, - POGGERMODE_TEMPORARILY_DISABLED = 170008, - CANNOT_UPDATE_FINISHED_EVENT = 180000, - FAILED_TO_CREATE_STAGE_INSTANCE = 180002, - AUTOMOD_MESSAGE_BLOCKED = 200000, - AUTOMOD_TITLE_BLOCKED = 200001, - AUTOMOD_INVALID_RUST_SERVICE_RESPONSE = 200002, - MONETIZATION_TERMS_NOT_ACCEPTED = 210003, - TWO_FA_NOT_ENABLED = 210011, - GUILD_PRODUCT_LISTING_CANNOT_PUBLISH_WITHOUT_BENEFIT = 210021, - CREATOR_MONETIZATION_PAYMENT_TEAM_REQUIRED = 210026, - CREATOR_MONETIZATION_PAYMENT_ACCOUNT_VERIFICATION_REQUIRED = 210027, - WEBHOOKS_POSTED_TO_FORUM_CHANNELS_MUST_HAVE_THREAD_NAME_OR_THREAD_ID = 220001, - WEBHOOKS_POSTED_TO_FORUM_CHANNELS_CANNOT_HAVE_BOTH_THREAD_NAME_AND_THREAD_ID = 220002, - WEBHOOKS_CAN_ONLY_CREATE_THREADS_IN_FORUM_CHANNELS = 220003, - WEBHOOK_SERVICES_CANNOT_BE_USED_IN_FORUM_CHANNELS = 220004, - MESSAGE_BLOCKED_BY_HARMFUL_LINKS_FILTER = 220005, - HARMFUL_LINK_MESSAGE_BLOCKED = 240000, - CLYDE_CONSENT_REQUIRED = 310000, - CLYDE_UNSAFE_PERSONALITY = 310003, - USER_LIMITED_ACCESS_DEFAULT = 340000, - USER_FRIEND_REQUEST_LIMITED_ACCESS = 340007, - USER_LIMITED_ACCESS_MAX = 349999, - CANNOT_ENABLE_ONBOARDING_REQUIREMENTS_NOT_MET = 350000, - CANNOT_ENABLE_ONBOARDING_BELOW_REQUIREMENTS = 350001, - GUILD_LIMITED_ACCESS_DEFAULT = 400000, - GUILD_FILE_UPLOAD_RATE_LIMITED_ACCESS = 400001, - GUILD_JOIN_INVITE_LIMITED_ACCESS = 400002, - GUILD_GO_LIVE_LIMITED_ACCESS = 400003, - GUILD_LIMITED_ACCESS_MAX = 409999, - FAILED_TO_BAN_USERS = 500000, + UNKNOWN_ACCOUNT = 10_001, + UNKNOWN_APPLICATION = 10_002, + UNKNOWN_CHANNEL = 10_003, + UNKNOWN_GUILD = 10_004, + UNKNOWN_INTEGRATION = 10_005, + UNKNOWN_INVITE = 10_006, + UNKNOWN_MEMBER = 10_007, + UNKNOWN_MESSAGE = 10_008, + UNKNOWN_OVERWRITE = 10_009, + UNKNOWN_PROVIDER = 10_010, + UNKNOWN_ROLE = 10_011, + UNKNOWN_TOKEN = 10_012, + UNKNOWN_USER = 10_013, + UNKNOWN_EMOJI = 10_014, + UNKNOWN_WEBHOOK = 10_015, + UNKNOWN_WEBHOOK_SERVICE = 10_016, + UNKNOWN_SESSION = 10_020, + UNKNOWN_BAN = 10_026, + UNKNOWN_SKU = 10_027, + UNKNOWN_STORE_LISTING = 10_028, + UNKNOWN_ENTITLEMENT = 10_029, + UNKNOWN_BUILD = 10_030, + UNKNOWN_LOBBY = 10_031, + UNKNOWN_BRANCH = 10_032, + UNKNOWN_STORE_DIRECTORY_LAYOUT = 10_036, + UNKNOWN_REDISTRIBUTABLE = 10_037, + UNKNOWN_GIFT_CODE = 10_038, + UNKNOWN_STREAM = 10_049, + UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN = 10_050, + UNKNOWN_GUILD_TEMPLATE = 10_057, + UNKNOWN_DISCOVERABLE_SERVER_CATEGORY = 10_059, + UNKNOWN_STICKER = 10_060, + UNKNOWN_INTERACTION = 10_062, + UNKNOWN_APPLICATION_COMMAND = 10_063, + UNKNOWN_APPLICATION_COMMAND_PERMISSIONS = 10_066, + UNKNOWN_STAGE_INSTANCE = 10_067, + UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM = 10_068, + UNKNOWN_GUILD_WELCOME_SCREEN = 10_069, + UNKNOWN_GUILD_SCHEDULED_EVENT = 10_070, + UNKNOWN_GUILD_SCHEDULED_EVENT_USER = 10_071, + UNKNOWN_TAG = 10_087, + BOT_DISALLOWED = 20_001, + BOTS_CANNOT_USE_THIS_ENDPOINT = 20_001, + BOT_REQUIRED = 20_002, + ONLY_BOTS_CAN_USE_THIS_ENDPOINT = 20_002, + RPC_PROXY_DISALLOWED = 20_003, + EXPLICIT_CONTENT = 20_009, + ACCOUNT_SCHEDULED_FOR_DELETION = 20_011, + NOT_AUTHORIZED_FOR_APPLICATION = 20_012, + ACCOUNT_DISABLED = 20_013, + SLOWMODE_RATE_LIMITED = 20_016, + ACCOUNT_OWNER_ONLY = 20_018, + CHANNEL_FOLLOWING_EDIT_RATE_LIMITED = 20_022, + UNDER_MINIMUM_AGE = 20_024, + QUARANTINED = 20_026, + CHANNEL_WRITE_RATE_LIMIT = 20_028, + GUILD_WRITE_RATE_LIMIT = 20_029, + WORDS_NOT_ALLOWED = 20_031, + VANITY_URL_REQUIRED_FOR_PUBLISHED_GUILDS = 20_040, + VANITY_URL_EMPLOYEE_ONLY_GUILD_DISABLED = 20_044, + VANITY_URL_REQUIREMENTS_NOT_MET = 20_045, + TOO_MANY_GUILDS = 30_001, + TOO_MANY_FRIENDS = 30_002, + TOO_MANY_PINS_IN_CHANNEL = 30_003, + TOO_MANY_RECIPIENTS = 30_004, + TOO_MANY_GUILD_ROLES = 30_005, + TOO_MANY_USING_USERNAME = 30_006, + TOO_MANY_WEBHOOKS = 30_007, + TOO_MANY_EMOJI = 30_008, + TOO_MANY_REACTIONS = 30_010, + TOO_MANY_GROUP_CHANNELS = 30_011, + TOO_MANY_CHANNELS = 30_013, + TOO_MANY_ATTACHMENTS = 30_015, + TOO_MANY_INVITES = 30_016, + TOO_MANY_ANIMATED_EMOJI = 30_018, + GUILD_AT_CAPACITY = 30_019, + NOT_ENOUGH_GUILD_MEMBERS = 30_029, + TOO_MANY_SERVER_CATEGORIES = 30_030, + GUILD_ALREADY_HAS_TEMPLATE = 30_031, + TOO_MANY_APPLICATION_COMMANDS = 30_032, + TOO_MANY_THREAD_MEMBERS = 30_033, + TOO_MANY_APPLICATION_COMMAND_CREATES = 30_034, + TOO_MANY_BANS_FOR_NON_GUILD_MEMBERS = 30_035, + TOO_MANY_BAN_FETCHES = 30_037, + TOO_MANY_UNCOMPLETED_GUILD_SCHEDULED_EVENTS = 30_038, + TOO_MANY_STICKERS = 30_039, + TOO_MANY_PRUNE_REQUESTS = 30_040, + TOO_MANY_GUILD_WIDGET_SETTINGS_UPDATES = 30_042, + MAXIMUM_NUMBER_OR_EDITS_TO_MESSAGES_OLDER_THAN_1_HOUR = 30_046, + TOO_MANY_PINNED_THREADS = 30_047, + TOO_MANY_FORUM_TAGS = 30_048, + BITRATE_TOO_HIGH = 30_052, + TOO_MANY_PREMIUM_EMOJIS = 30_056, + TOO_MANY_GUILD_WEBHOOKS = 30_058, + TOO_MANY_BLOCKED_USERS = 30_059, + TOO_MANY_PUBLISHED_PRODUCT_LISTINGS = 30_065, + RESOURCE_RATE_LIMITED = 31_002, + UNAUTHORIZED = 40_001, + EMAIL_VERIFICATION_REQUIRED = 40_002, + RATE_LIMIT_DM_OPEN = 40_003, + DIRECT_MESSAGES_RATE_LIMIT = 40_003, + SENDING_MESSAGES_TEMPORARILY_DISABLED = 40_004, + ENTITY_TOO_LARGE = 40_005, + REQUEST_ENTITY_TOO_LARGE = 40_005, + ENTITY_EMPTY = 40_006, + FEATURE_TEMPORARILY_DISABLED = 40_006, + USER_BANNED = 40_007, + CONNECTION_REVOKED = 40_012, + DELETE_ACCOUNT_TRANSFER_TEAM_OWNERSHIP = 40_028, + TARGET_USER_NOT_CONNECTED_TO_VOICE = 40_032, + ALREADY_CROSSPOSTED = 40_033, + APPLICATION_COMMAND_ALREADY_EXISTS = 40_041, + INTERACTION_FAILED_TO_SEND = 40_043, + CANNOT_SEND_MESSAGES_IN_FORUM_CHANNEL = 40_058, + INTERACTION_ALREADY_ACKNOWLEDGED = 40_060, + TAG_NAMES_MUST_BE_UNIQUE = 40_061, + SERVICE_RESOURCE_RATE_LIMITED = 40_062, + NON_MODERATED_TAG_REQUIRED = 40_066, + TAG_REQUIRED = 40_067, + USER_QUARANTINED = 40_068, + INVITES_DISABLED = 40_069, + INVALID_ACCESS = 50_001, + MISSING_ACCESS = 50_001, + INVALID_ACCOUNT_TYPE = 50_002, + INVALID_ACTION_DM = 50_003, + INVALID_EMBED_DISABLED = 50_004, + INVALID_MESSAGE_AUTHOR = 50_005, + INVALID_MESSAGE_EMPTY = 50_006, + INVALID_MESSAGE_SEND_USER = 50_007, + INVALID_MESSAGE_SEND_NON_TEXT = 50_008, + INVALID_MESSAGE_VERIFICATION_LEVEL = 50_009, + INVALID_OAUTH_APP_BOT = 50_010, + INVALID_OAUTH_APP_LIMIT = 50_011, + INVALID_OAUTH_STATE = 50_012, + INVALID_PERMISSIONS = 50_013, + INVALID_TOKEN = 50_014, + INVALID_NOTE = 50_015, + INVALID_BULK_DELETE_COUNT = 50_016, + INVALID_MFA_LEVEL = 50_017, + INVALID_PASSWORD = 50_018, + INVALID_PIN_MESSAGE_CHANNEL = 50_019, + INVALID_INVITE_CODE = 50_020, + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE = 50_021, + INVALID_PHONE_NUMBER = 50_022, + INVALID_CLIENT_ID = 50_023, + INVALID_CHANNEL_TYPE = 50_024, + INVALID_OAUTH2_ACCESS_TOKEN = 50_025, + INVALID_OAUTH2_MISSING_SCOPE = 50_026, + INVALID_WEBHOOK_TOKEN = 50_027, + INVALID_ROLE = 50_028, + INVALID_RECIPIENTS = 50_033, + BULK_DELETE_MESSAGE_TOO_OLD = 50_034, + INVALID_FORM_BODY = 50_035, + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT = 50_036, + INVALID_ACTIVITY_ACTION = 500_039, + INVALID_API_VERSION = 50_041, + INVALID_FILE_ASSET_SIZE = 50_045, + INVALID_FILE_ASSET = 50_046, + INVALID_GIFT_REDEMPTION_EXHAUSTED = 50_050, + INVALID_GIFT_REDEMPTION_OWNED = 50_051, + INVALID_GIFT_SELF_REDEMPTION = 50_054, + INVALID_GUILD = 50_055, + INVALID_REQUEST_ORIGIN = 50_067, + INVALID_MESSAGE_TYPE = 50_068, + PAYMENT_SOURCE_REQUIRED = 50_070, + CANNOT_MODIFY_SYSTEM_WEBHOOK = 50_073, + CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL = 50_074, + CANNOT_EDIT_MESSAGE_STICKERS = 50_080, + INVALID_STICKER_SENT = 50_081, + THREAD_ARCHIVED = 50_083, + INVALID_THREAD_NOTIFICATION_SETTINGS = 50_084, + BEFORE_EARLIER_THAN_THREAD_CREATION_DATE = 50_085, + COMMUNITY_CHANNELS_MUST_BE_TEXT = 50_086, + INVALID_COUNTRY_CODE = 50_095, + INVALID_CANNOT_FRIEND_SELF = 50_096, + INVALID_GIFT_REDEMPTION_FRAUD_REJECTED = 50_097, + MONETIZATION_REQUIRED = 50_097, + BOOSTS_REQUIRED = 50_101, + INVALID_USER_SETTINGS_DATA = 50_105, + INVALID_ACTIVITY_LAUNCH_NO_ACCESS = 50_106, + INVALID_ACTIVITY_LAUNCH_PREMIUM_TIER = 50_107, + INVALID_ACTIVITY_LAUNCH_CONCURRENT_ACTIVITIES = 50_108, + INVALID_JSON = 50_109, + OWNER_CANNOT_BE_PENDING_MEMBER = 50_131, + OWNERSHIP_CANNOT_BE_TRANSFERRED_TO_BOT = 50_132, + INVALID_FILE_ASSET_SIZE_RESIZE_GIF = 50_138, + CANNOT_MIX_SUBSCRIPTION_AND_NON_SUBSCRIPTION_ROLES = 50_144, + CANNOT_CONVERT_BETWEEN_PREMIUM_AND_NORMAL_EMOJI = 50_145, + UPLOADED_FILE_NOT_FOUND = 50_146, + INVALID_ACTIVITY_LAUNCH_AFK_CHANNEL = 50_148, + VOICE_MESSAGES_DO_NOT_SUPPORT_ADDITIONAL_CONTENT = 50_159, + VOICE_MESSAGES_MUST_HAVE_A_SINGLE_AUDIO_ATTACHMENT = 50_160, + VOICE_MESSAGES_MUST_HAVE_SUPPORTING_METADATA = 50_161, + VOICE_MESSAGES_CANNOT_BE_EDITED = 50_162, + CANNOT_DELETE_GUILD_SUBSCRIPTION_INTEGRATION = 50_163, + NEW_OWNER_INELIGIBLE_FOR_SERVER_SUBSCRIPTION = 50_164, + INVALID_ACTIVITY_LAUNCH_AGE_GATED = 50_165, + CANNOT_SEND_VOICE_MESSAGES_IN_CHANNEL = 50_173, + USER_MUST_FIRST_BE_VERIFIED = 50_178, + INVALID_SKU_ATTACHMENT_NO_ARCHIVES = 50_186, + NO_PERMISSION_TO_SEND_STICKER = 50_600, + MFA_ENABLED = 60_001, + MFA_DISABLED = 60_002, + MFA_REQUIRED = 60_003, + MFA_UNVERIFIED = 60_004, + MFA_INVALID_SECRET = 60_005, + MFA_INVALID_TICKET = 60_006, + MFA_INVALID_CODE = 60_008, + MFA_INVALID_SESSION = 60_009, + PHONE_NUMBER_UNABLE_TO_SEND = 70_003, + PHONE_VERIFICATION_REQUIRED = 70_007, + RELATIONSHIP_INCOMING_DISABLED = 80_000, + RELATIONSHIP_INCOMING_BLOCKED = 80_001, + RELATIONSHIP_INVALID_USER_BOT = 80_002, + RELATIONSHIP_INVALID_SELF = 80_003, + RELATIONSHIP_INVALID_DISCORD_TAG = 80_004, + RELATIONSHIP_ALREADY_FRIENDS = 80_007, + REACTION_BLOCKED = 90_001, + USER_CANNOT_USE_BURST_REACTIONS = 90_002, + INVALID_GIFT_REDEMPTION_SUBSCRIPTION_MANAGED = 100_021, + INVALID_GIFT_REDEMPTION_SUBSCRIPTION_INCOMPATIBLE = 100_023, + INVALID_GIFT_REDEMPTION_INVOICE_OPEN = 100_024, + INELIGIBLE_FOR_SUBSCRIPTION = 100_053, + BILLING_NON_REFUNDABLE_PAYMENT_SOURCE = 100_060, + APPLICATION_NOT_AVAILABLE = 110_001, + LISTING_ALREADY_JOINED = 120_000, + LISTING_TOO_MANY_MEMBERS = 120_001, + LISTING_JOIN_BLOCKED = 120_002, + API_RESOURCE_IS_CURRENTLY_OVERLOADED = 130_000, + STAGE_ALREADY_OPEN = 150_006, + CANNOT_REPLY_WITHOUT_READ_MESSAGE_HISTORY = 160_002, + THREAD_ALREADY_CREATED_FOR_MESSAGE = 160_004, + THREAD_IS_LOCKED = 160_005, + TOO_MANY_THREADS = 160_006, + TOO_MANY_ANNOUNCEMENT_THREADS = 160_007, + INVALID_LOTTIE_JSON = 170_001, + UPLOADED_LOTTIE_RASTERIZED = 170_002, + STICKER_MAXIMUM_FRAMERATE_EXCEEDED = 170_003, + STICKER_FRAME_COUNT_EXCEEDS_MAXIMUM = 170_004, + LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED = 170_005, + STICKER_FRAME_RATE_TOO_SMALL_OR_LARGE = 170_006, + STICKER_ANIMATION_DURATION_TOO_LONG = 170_007, + POGGERMODE_TEMPORARILY_DISABLED = 170_008, + CANNOT_UPDATE_FINISHED_EVENT = 180_000, + FAILED_TO_CREATE_STAGE_INSTANCE = 180_002, + AUTOMOD_MESSAGE_BLOCKED = 200_000, + AUTOMOD_TITLE_BLOCKED = 200_001, + AUTOMOD_INVALID_RUST_SERVICE_RESPONSE = 200_002, + MONETIZATION_TERMS_NOT_ACCEPTED = 210_003, + TWO_FA_NOT_ENABLED = 210_011, + GUILD_PRODUCT_LISTING_CANNOT_PUBLISH_WITHOUT_BENEFIT = 210_021, + CREATOR_MONETIZATION_PAYMENT_TEAM_REQUIRED = 210_026, + CREATOR_MONETIZATION_PAYMENT_ACCOUNT_VERIFICATION_REQUIRED = 210_027, + WEBHOOKS_POSTED_TO_FORUM_CHANNELS_MUST_HAVE_THREAD_NAME_OR_THREAD_ID = 220_001, + WEBHOOKS_POSTED_TO_FORUM_CHANNELS_CANNOT_HAVE_BOTH_THREAD_NAME_AND_THREAD_ID = 220_002, + WEBHOOKS_CAN_ONLY_CREATE_THREADS_IN_FORUM_CHANNELS = 220_003, + WEBHOOK_SERVICES_CANNOT_BE_USED_IN_FORUM_CHANNELS = 220_004, + MESSAGE_BLOCKED_BY_HARMFUL_LINKS_FILTER = 220_005, + HARMFUL_LINK_MESSAGE_BLOCKED = 240_000, + CLYDE_CONSENT_REQUIRED = 310_000, + CLYDE_UNSAFE_PERSONALITY = 310_003, + USER_LIMITED_ACCESS_DEFAULT = 340_000, + USER_FRIEND_REQUEST_LIMITED_ACCESS = 340_007, + USER_LIMITED_ACCESS_MAX = 349_999, + CANNOT_ENABLE_ONBOARDING_REQUIREMENTS_NOT_MET = 350_000, + CANNOT_ENABLE_ONBOARDING_BELOW_REQUIREMENTS = 350_001, + GUILD_LIMITED_ACCESS_DEFAULT = 400_000, + GUILD_FILE_UPLOAD_RATE_LIMITED_ACCESS = 400_001, + GUILD_JOIN_INVITE_LIMITED_ACCESS = 400_002, + GUILD_GO_LIVE_LIMITED_ACCESS = 400_003, + GUILD_LIMITED_ACCESS_MAX = 409_999, + FAILED_TO_BAN_USERS = 500_000, + POLL_VOTING_BLOCKED = 520_000, + POLL_EXPIRED = 520_001, + INVALID_CHANNEL_TYPE_FOR_POLL_CREATION = 520_002, + CANNOT_EDIT_POLL_MESSAGE = 520_003, + CANNOT_USE_AN_EMOJI_INCLUDED_WITH_THE_POLL = 520_004, + CANNOT_EXPIRE_A_NON_POLL_MESSAGE = 520_006, + POLL_IS_ALREADY_EXPIRED = 520_007, } /* eslint-enable @typescript-eslint/no-duplicate-enum-values */ diff --git a/lib/gateway/Shard.ts b/lib/gateway/Shard.ts index 87f27e0b..3e64d0d1 100644 --- a/lib/gateway/Shard.ts +++ b/lib/gateway/Shard.ts @@ -36,7 +36,8 @@ import type { ThreadMember, ThreadParentChannel, UncachedThreadMember, - AnyVoiceChannel + AnyVoiceChannel, + PollAnswer } from "../types/channels"; import type TextChannel from "../structures/TextChannel"; import type { JSONAnnouncementThreadChannel } from "../types/json"; @@ -713,6 +714,42 @@ export default class Shard extends TypedEmitter { break; } + case "MESSAGE_POLL_VOTE_ADD": { + const user = this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; + const channel = this.client.getChannel(packet.d.channel_id) ?? { id: packet.d.channel_id }; + const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined; + const message = (channel instanceof Channel ? channel.messages.get(packet.d.message_id) : undefined) ?? { channel, channelID: channel.id, guild, guildID: guild?.id, id: packet.d.message_id }; + let answer: PollAnswer | { answerID: number; } = { answerID: packet.d.answer_id }; + if (message instanceof Message && message.poll !== undefined) { + const pollAnswer = message.poll.answers.find(a => a.answerID === packet.d.answer_id); + if (pollAnswer) { + answer = pollAnswer; + } + + this.client.util.updatePollAnswer(message.poll, packet.d.answer_id, 1, packet.d.user_id); + } + this.client.emit("messagePollVoteAdd", message, user, answer); + break; + } + + case "MESSAGE_POLL_VOTE_REMOVE": { + const user = this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; + const channel = this.client.getChannel(packet.d.channel_id) ?? { id: packet.d.channel_id }; + const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined; + const message = (channel instanceof Channel ? channel.messages.get(packet.d.message_id) : undefined) ?? { channel, channelID: channel.id, guild, guildID: guild?.id, id: packet.d.message_id }; + let answer: PollAnswer | { answerID: number; } = { answerID: packet.d.answer_id }; + if (message instanceof Message && message.poll !== undefined) { + const pollAnswer = message.poll.answers.find(a => a.answerID === packet.d.answer_id); + if (pollAnswer) { + answer = pollAnswer; + } + + this.client.util.updatePollAnswer(message.poll, packet.d.answer_id, -1, packet.d.user_id); + } + this.client.emit("messagePollVoteRemove", message, user, answer); + break; + } + case "MESSAGE_REACTION_ADD": { const channel = this.client.getChannel(packet.d.channel_id); const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined; @@ -755,7 +792,8 @@ export default class Shard extends TypedEmitter { guild, guildID: packet.d.guild_id, id: packet.d.message_id , - author: packet.d.message_author_id === undefined ? undefined : guild?.members.get(packet.d.message_author_id) ?? this.client.users.get(packet.d.message_author_id) ?? { id: packet.d.message_author_id } + // @TODO (1.11.0): Convert this to only be User, and add a member field + author: packet.d.message_author_id === undefined ? undefined : guild?.members.get(packet.d.user_id) ?? this.client.users.get(packet.d.user_id) ?? { id: packet.d.message_author_id } }, reactor, packet.d.emoji, packet.d.burst); break; } diff --git a/lib/index.ts b/lib/index.ts index 42b441c1..8143235b 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -52,6 +52,7 @@ export { default as PartialApplication } from "./structures/PartialApplication"; export { default as Permission } from "./structures/Permission"; export { default as PermissionOverwrite } from "./structures/PermissionOverwrite"; export { default as PingInteraction } from "./structures/PingInteraction"; +export { default as Poll } from "./structures/Poll"; export { default as PrivateChannel } from "./structures/PrivateChannel"; export { default as PrivateThreadChannel } from "./structures/PrivateThreadChannel"; export { default as PublicThreadChannel } from "./structures/PublicThreadChannel"; diff --git a/lib/routes/Channels.ts b/lib/routes/Channels.ts index 98c274f4..6b22ae75 100644 --- a/lib/routes/Channels.ts +++ b/lib/routes/Channels.ts @@ -43,7 +43,8 @@ import type { GetThreadMembersOptions, AnyGuildChannel, GetChannelMessagesIteratorOptions, - MessagesIterator + MessagesIterator, + GetPollAnswerUsersOptions } from "../types/channels"; import * as Routes from "../util/Routes"; import type Message from "../structures/Message"; @@ -177,7 +178,10 @@ export default class Channels { method: "POST", path: Routes.CHANNEL_MESSAGES(channelID), json: { - allowed_mentions: this.#manager.client.util.formatAllowedMentions(options.allowedMentions), + // HACK: currently, allowed_mentions cannot be sent along with a poll. Due to how this previously worked, allowed mentions was ALWAYS sent. + // We check if poll is not present (or if allowedMentions IS present, for future proofing), and don't send allowed_mentions + // ^ If fixed make sure to remove the note from the "poll" property in the CreateMessageOptions interface + allowed_mentions: options.poll === undefined || options.allowedMentions ? this.#manager.client.util.formatAllowedMentions(options.allowedMentions) : undefined, attachments: options.attachments, components: options.components ? this.#manager.client.util.componentsToRaw(options.components) : undefined, content: options.content, @@ -190,6 +194,15 @@ export default class Channels { guild_id: options.messageReference.guildID, message_id: options.messageReference.messageID } : undefined, + poll: options.poll ? { + allow_multiselect: options.poll.allowMultiselect, + answers: options.poll.answers.map(a => ({ + poll_media: a.pollMedia + })), + duration: options.poll.duration, + layout_type: options.poll.layoutType, + question: options.poll.question + } : undefined, tts: options.tts }, files @@ -534,6 +547,19 @@ export default class Channels { }).then(data => new StageInstance(data, this.#manager.client)); } + /** + * End a poll now. + * @param channelID The ID of the channel the poll is in. + * @param messageID The ID of the message the poll is on. + * @caching This method **does not** cache its result. + */ + async expirePoll(channelID: string, messageID: string): Promise { + await this.#manager.authRequest({ + method: "POST", + path: Routes.POLL_EXPIRE(channelID, messageID) + }); + } + /** * Follow an announcement channel. * @param channelID The ID of the channel to follow announcements from. @@ -774,6 +800,37 @@ export default class Channels { }).then(data => data.map(d => this.#manager.client.util.updateMessage(d))); } + /** + * Get the users that voted on a poll answer. + * @param channelID The ID of the channel the poll is in. + * @param messageID The ID of the message the poll is on. + * @param answerID The ID of the poll answer to get voters for. + * @param options The options for getting the voters. + * @caching This method **does** cache its result. + * @caches {@link Client#users | Client#users} + */ + async getPollAnswerUsers(channelID: string, messageID: string, answerID: number, options?: GetPollAnswerUsersOptions): Promise> { + const qs = new URLSearchParams(); + if (options?.after !== undefined) { + qs.set("before", options.after); + } + if (options?.limit !== undefined) { + qs.set("limit", options.limit.toString()); + } + return this.#manager.authRequest<{ users: Array; }>({ + method: "GET", + path: Routes.POLL_ANSWER_USERS(channelID, messageID, answerID), + query: qs + }).then(data => { + const users = data.users.map(user => this.#manager.client.users.update(user)); + const message = this.#manager.client.getChannel(channelID)?.messages.get(messageID); + if (message?.poll) { + this.#manager.client.util.replacePollAnswer(message.poll, answerID, users.length, users.map(u => u.id)); + } + return users; + }); + } + /** * Get the private archived threads in a channel. * @param channelID The ID of the channel to get the archived threads from. diff --git a/lib/routes/OAuth.ts b/lib/routes/OAuth.ts index 0f068224..b6ee7641 100644 --- a/lib/routes/OAuth.ts +++ b/lib/routes/OAuth.ts @@ -104,7 +104,7 @@ export default class OAuth { * Get information about the current authorization. * * Note: OAuth only. Bots cannot use this. - * @caching This method **does** cache part its result. + * @caching This method **does** cache part of its result. * @caches {@link Client#users | Client#users} */ async getCurrentAuthorizationInformation(): Promise { diff --git a/lib/structures/Guild.ts b/lib/structures/Guild.ts index 2d0315b2..11562dd9 100644 --- a/lib/structures/Guild.ts +++ b/lib/structures/Guild.ts @@ -213,7 +213,7 @@ export default class Guild extends Base { publicUpdatesChannel?: AnyTextableGuildChannel | null; /** The id of the channel where notices from Discord are received. Only present in guilds with the `COMMUNITY` feature. */ publicUpdatesChannelID: string | null; - /** @deprecated The region of this guild.*/ + /** @deprecated The region of this guild. */ region?: string | null; /** The roles in this guild. */ roles: TypedCollection; @@ -622,7 +622,7 @@ export default class Guild extends Base { } } - /** The client's member for this guild. This will throw an error if the member is not cached.*/ + /** The client's member for this guild. This will throw an error if the member is not cached. */ get clientMember(): Member { this._clientMember ??= this.client["_user"] === undefined ? undefined : this.members.get(this.client["_user"].id); if (!this._clientMember) { diff --git a/lib/structures/Interaction.ts b/lib/structures/Interaction.ts index 593c9040..21ee6f52 100644 --- a/lib/structures/Interaction.ts +++ b/lib/structures/Interaction.ts @@ -29,7 +29,7 @@ export default class Interaction extends Base { /** The ID of the application this interaction is for. */ applicationID: string; /** The token of this interaction. */ - token: string; + token!: string; /** The [type](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-type) of this interaction. */ type: InteractionTypes; /** Read-only property, always `1` */ @@ -39,7 +39,7 @@ export default class Interaction extends Base { this.acknowledged = false; this.application = client["_application"] && client.application.id === data.application_id ? client.application : undefined; this.applicationID = data.application_id; - this.token = data.token; + Object.defineProperty(this, "token", { value: data.token, enumerable: false }); this.type = data.type; this.version = data.version; } @@ -97,7 +97,6 @@ export default class Interaction extends Base { return { ...super.toJSON(), applicationID: this.applicationID, - token: this.token, type: this.type, version: this.version }; diff --git a/lib/structures/Message.ts b/lib/structures/Message.ts index 74df68f2..37dcaafa 100644 --- a/lib/structures/Message.ts +++ b/lib/structures/Message.ts @@ -12,6 +12,7 @@ import type PublicThreadChannel from "./PublicThreadChannel"; import type TextChannel from "./TextChannel"; import GuildChannel from "./GuildChannel"; import type PrivateChannel from "./PrivateChannel"; +import Poll from "./Poll"; import type Client from "../Client"; import TypedCollection from "../util/TypedCollection"; import { BASE_URL, type MessageTypes } from "../Constants"; @@ -34,7 +35,8 @@ import type { MessageActionRow, AnyThreadChannel, RoleSubscriptionData, - MessageInteractionMetadata + MessageInteractionMetadata, + GetPollAnswerUsersOptions } from "../types/channels"; import type { RawMember } from "../types/guilds"; import type { DeleteWebhookMessageOptions, EditWebhookMessageOptions } from "../types/webhooks"; @@ -108,6 +110,8 @@ export default class Message(this.webhookID, token, this.id, options); } + /** End this The poll on this message now. */ + async expire(): Promise { + if (this.poll === undefined) { + throw new TypeError("Message does not have a poll."); + } + + await this.poll.expire(); + } + + /** + * Get the users that voted on a poll answer. + * @param answerID The ID of the poll answer to get voters for. + * @param options The options for getting the voters. + */ + async getPollAnswerUsers(answerID: number, options?: GetPollAnswerUsersOptions): Promise> { + if (this.poll === undefined) { + throw new TypeError("Message does not have a poll."); + } + + return this.poll.getAnswerUsers(answerID, options); + } + /** * Get the users who reacted with a specific emoji on this message. * @param emoji The reaction to remove from the message. `name:id` for custom emojis, and the unicode codepoint for default emojis. diff --git a/lib/structures/Poll.ts b/lib/structures/Poll.ts new file mode 100644 index 00000000..93261357 --- /dev/null +++ b/lib/structures/Poll.ts @@ -0,0 +1,85 @@ +import type Message from "./Message"; +import type User from "./User"; +import type Client from "../Client"; +import type { PollLayoutType } from "../Constants"; +import type { GetPollAnswerUsersOptions, PollAnswer, PollResults, RawPoll } from "../types/channels"; +import type { JSONPoll, PollQuestion } from "../types"; + +export default class Poll { + allowMultiselect: boolean; + answers: Array; + client!: Client; + expiry: Date; + layoutType: PollLayoutType; + message: Message; + question: PollQuestion; + results: PollResults; + constructor(data: RawPoll, client: Client, message: Message) { + Object.defineProperty(this, "client", { + value: client, + enumerable: false, + writable: false, + configurable: false + }); + this.allowMultiselect = data.allow_multiselect; + this.answers = data.answers.map(a => ({ + answerID: a.answer_id, + pollMedia: a.poll_media + })); + this.expiry = new Date(data.expiry); + this.layoutType = data.layout_type; + this.message = message; + this.question = data.question; + const res = data.results || { answer_counts: [], is_finalized: false }; + this.results = { + answerCounts: res.answer_counts.map(a => ({ + count: a.count, + id: a.id, + meVoted: a.me_voted, + users: [] + })), + isFinalized: res.is_finalized + }; + // this makes working with this much easier as a developer. We still have systems in place to insert missing answerCounts, if needs be + for (const answer of data.answers) { + if (!this.results.answerCounts.some(a => a.id === answer.answer_id)) { + this.results.answerCounts.push({ + count: 0, + id: answer.answer_id, + meVoted: false, + users: [] + }); + } + } + } + + /** The user that created this poll. */ + get creator(): User { + return this.message.author; + } + + /** End this poll now. */ + async expire(): Promise { + await this.client.rest.channels.expirePoll.call(this.client.rest.channels, this.message.channelID, this.message.id); + } + + /** + * Get the users that voted on a poll answer. + * @param answerID The ID of the poll answer to get voters for. + * @param options The options for getting the voters. + */ + async getAnswerUsers(answerID: number, options?: GetPollAnswerUsersOptions): Promise> { + return this.client.rest.channels.getPollAnswerUsers.call(this.client.rest.channels, this.message.channelID, this.message.id, answerID, options); + } + + toJSON(): JSONPoll { + return { + allowMultiselect: this.allowMultiselect, + answers: this.answers, + expiry: this.expiry.toISOString(), + layoutType: this.layoutType, + question: this.question, + results: this.results + }; + } +} diff --git a/lib/types/applications.d.ts b/lib/types/applications.d.ts index 21fd565b..1a1e4174 100644 --- a/lib/types/applications.d.ts +++ b/lib/types/applications.d.ts @@ -372,7 +372,7 @@ export type Locale = export type LocaleMap = Partial>; export interface CreateTestEntitlementOptions { - /** The ID of the owner of the test entitlement, a user or guild.*/ + /** The ID of the owner of the test entitlement, a user or guild. */ ownerID: string; /** The type of the owner of the entitlement. */ ownerType: EntitlementOwnerTypes; diff --git a/lib/types/channels.d.ts b/lib/types/channels.d.ts index 92661278..54026aff 100644 --- a/lib/types/channels.d.ts +++ b/lib/types/channels.d.ts @@ -38,7 +38,8 @@ import type { InviteChannelTypes, ImplementedChannelTypes, ThreadOnlyChannelTypes, - ReactionType + ReactionType, + PollLayoutType } from "../Constants"; import type Member from "../structures/Member"; import type AnnouncementChannel from "../structures/AnnouncementChannel"; @@ -169,7 +170,7 @@ export interface ThreadMember extends UncachedThreadMember { export interface GetThreadMembersOptions { /** Get members after this member id. */ after?: string; - /** The maximum number of thread members returned, defaults to 100.*/ + /** The maximum number of thread members returned, defaults to 100. */ limit?: number; /** If the results should include a `member` object. This also enables pagination. */ withMember?: boolean; @@ -288,6 +289,7 @@ export interface AddGroupRecipientOptions { userID: string; } +// make sure to add to ExecuteWebhookOptions & InteractionContent export interface CreateMessageOptions { /** An object that specifies the allowed mentions in this message. */ allowedMentions?: AllowedMentions; @@ -305,6 +307,14 @@ export interface CreateMessageOptions { flags?: number; /** Reply to a message. */ messageReference?: MessageReference; + /** + * A poll to send. + * @note As of [3/22/24](https://github.com/discord/discord-api-docs/pull/6746): + * * content, components, and many other fields cannot be sent alongside a poll. + * * * This means we cannot set `allowedMentions`. If in the future `content` can be sent alongside a poll, allowedMentions will not be set automatically, and must be set manually. + * * Messages with a poll cannot be edited. + */ + poll?: MessagePollOptions; /** The IDs of up to 3 stickers from the current guild to send. */ stickerIDs?: Array; /** If the message should be spoken aloud. */ @@ -682,6 +692,7 @@ export interface RawMessage { message_reference?: RawMessageReference; nonce?: number | string; pinned: boolean; + poll?: RawPoll; position?: number; reactions?: Array; referenced_message?: RawMessage | null; @@ -1148,3 +1159,84 @@ export interface RoleSubscriptionData { tierName: string; totalMonthsSubscribed: number; } + +export interface MessagePollOptions { + /** If multiple votes can be selected. */ + allowMultiselect: boolean; + /** The answers for this poll. Up to 10 can be specified. */ + answers: Array; + /** The duration of this poll in hours. Up to 7 days (168 hours) */ + duration: number; + layoutType?: PollLayoutType; + /** The question for the poll. */ + question: PollQuestion; +} + +export interface GetPollAnswerUsersOptions { + /** Get members after this member id. */ + after?: string; + /** The maximum number of users returned, defaults to 100. */ + limit?: number; +} + +export interface RawPoll { + allow_multiselect: boolean; + answers: Array; + expiry: string; + layout_type: PollLayoutType; + question: PollQuestion; + results?: RawPollResults; +} + +export interface RawPollAnswer { + answer_id: number; + poll_media: PollMedia; +} + +export interface RawPollResults { + answer_counts: Array; + is_finalized: boolean; +} + +export interface PollMedia { + /** The emoji to dispaly. */ + emoji?: NullablePartialEmoji; + // @FIXME: Discord has said this is currently always nonnull, but not to depend on that in the future (https://github.com/discord/discord-api-docs/pull/6746) + /** The text. */ + text: string; +} + +export interface PollQuestion extends Pick {} + +export interface PollAnswer { + /** The id of this answer in relation to `results.answerCounts.id`. */ + answerID: number; + /** The media object for this answer. */ + pollMedia: PollMedia; +} + +export interface PollAnswerOption extends Pick {} + +export interface RawPollAnswerCount { + count: number; + id: number; + me_voted: boolean; +} + +export interface PollResults { + /** The counts for each answer. */ + answerCounts: Array; + /** If the poll has been finalized. */ + isFinalized: boolean; +} + +export interface PollAnswerCount { + /** The count for this answer. */ + count: number; + /** The id of this answer in relation to `answers.answerID`. */ + id: number; + /** If the current user has voted for this answer. */ + meVoted: boolean; + /** The IDs of the users that voted. This will always be out of sync unless you either {@link Poll#getAnswerUsers|fetch the answer voters over REST}, or if the poll was created after the bot started, and stays in the cache. */ + users: Array; +} diff --git a/lib/types/events.d.ts b/lib/types/events.d.ts index b6aff774..8b0a2d2b 100644 --- a/lib/types/events.d.ts +++ b/lib/types/events.d.ts @@ -10,7 +10,8 @@ import type { PossiblyUncachedThread, ThreadMember, UncachedThreadMember, - AnyVoiceChannel + AnyVoiceChannel, + PollAnswer } from "./channels"; import type { RawRequest } from "./request-handler"; import type { AutoModerationActionExecution, DeletedPrivateChannel, VoiceChannelEffect } from "./gateway"; @@ -130,7 +131,7 @@ export interface ClientEvents { guildMemberChunk: [members: Array]; /** @event Emitted when a member leaves a guild. Requires the `GUILD_MEMBERS` intent. If the member is uncached, the first parameter will be a user. If the guild is uncached, the first parameter will be a user, and the second will be an object with only an `id`. */ guildMemberRemove: [member: Member | User, guild: Guild | Uncached]; - /** @event Emitted when a guild member is updates. Requires the `GUILD_MEMBERS` intent.*/ + /** @event Emitted when a guild member is updates. Requires the `GUILD_MEMBERS` intent. */ guildMemberUpdate: [member: Member, oldMember: JSONMember | null]; /** @event Emitted when a role is created. Requires the `GUILDS` intent. */ guildRoleCreate: [role: Role]; @@ -174,6 +175,10 @@ export interface ClientEvents { messageDelete: [message: PossiblyUncachedMessage]; /** @event Emitted when messages are bulk deleted. Requires the `GUILD_MESSAGES` intent. The `MESSAGE_CONTENT` intent is required for `content`, `embeds`, and similar to be present on most messages. */ messageDeleteBulk: [messages: Array]; + /** @event Emitted when a vote is added to a poll. Requires the `GUILD_MESSAGE_POLLS` for guild messages, and `DIRECT_MESSAGE_POLLS` for direct messages. */ + messagePollVoteAdd: [message: PossiblyUncachedMessage, user: User | Uncached, answer: PollAnswer | { answerID: number; }]; + /** @event Emitted when a vote is added to a poll. Requires the `GUILD_MESSAGE_POLLS` for guild messages, and `DIRECT_MESSAGE_POLLS` for direct messages. */ + messagePollVoteRemove: [message: PossiblyUncachedMessage, user: User | Uncached, answer: PollAnswer | { answerID: number; }]; /** @event Emitted when a reaction is added to a message. For uncached messages, `author` will not be present if the reaction was added to a webhook message. Requires the `GUILD_MESSAGE_REACTIONS` for guild messages, and `DIRECT_MESSAGE_REACTIONS` for direct messages. */ messageReactionAdd: [message: PossiblyUncachedMessage & { author?: Member | User | Uncached; }, reactor: Member | User | Uncached, reaction: PartialEmoji, burst: boolean]; /** @event Emitted when a reaction is removed from a message. Requires the `GUILD_MESSAGE_REACTIONS` for guild messages, and `DIRECT_MESSAGE_REACTIONS` for direct messages. */ @@ -192,7 +197,7 @@ export interface ClientEvents { ready: []; /** @event Emitted when a request is made. */ request: [rawRequest: RawRequest]; - /** @event Emitted when this shard disconnects.*/ + /** @event Emitted when this shard disconnects. */ shardDisconnect: [err: Error | undefined, id: number]; /** @event Emitted when this shard has processed the READY packet from Discord. */ shardPreReady: [id: number]; @@ -220,7 +225,7 @@ export interface ClientEvents { threadUpdate: [thread: AnnouncementThreadChannel, oldThread: JSONAnnouncementThreadChannel | null] | [thread: PublicThreadChannel, oldThread: JSONPublicThreadChannel | null] | [thread: PrivateThreadChannel, oldThread: JSONPrivateThreadChannel | null]; /** @event Emitted when a user starts typing. Requires the `GUILD_MESSAGE_TYPING` for guilds, and `DIRECT_MESSAGE_TYPING` for direct messages. */ typingStart: [channel: PrivateChannel | Uncached, user: User | Uncached, startTimestamp: Date] | [channel: AnyTextableGuildChannel | Uncached, member: Member, startTimestamp: Date]; - /** @event Emitted when a guild is created, but is unavailable. Requires the `GUILDS` intent.*/ + /** @event Emitted when a guild is created, but is unavailable. Requires the `GUILDS` intent. */ unavailableGuildCreate: [guild: UnavailableGuild]; /** @event Emitted when a user is updated. */ userUpdate: [user: User, oldUser: JSONUser | null]; @@ -245,7 +250,7 @@ export interface ClientEvents { export interface ShardEvents { /** @event Emitted with various information for debugging. */ debug: [info: string]; - /** @event Emitted when this shard disconnects.*/ + /** @event Emitted when this shard disconnects. */ disconnect: [err?: Error]; /** @event Emitted when an error happens. If an error is emitted and no handlers are present, the error will be thrown. */ error: [info: Error | string]; diff --git a/lib/types/gateway-raw.d.ts b/lib/types/gateway-raw.d.ts index 5829ac69..3ce7b519 100644 --- a/lib/types/gateway-raw.d.ts +++ b/lib/types/gateway-raw.d.ts @@ -11,7 +11,13 @@ import type { RawSticker } from "./guilds"; import type { RawExtendedUser, RawUser } from "./users"; -import type { PresenceUpdate, RawAutoModerationActionExecution, RawDeletedPrivateChannel, RawVoiceChannelEffect } from "./gateway"; +import type { + PresenceUpdate, + RawAutoModerationActionExecution, + RawDeletedPrivateChannel, + RawMessagePollVote, + RawVoiceChannelEffect +} from "./gateway"; import type { RawGuildApplicationCommandPermissions, RawEntitlement, @@ -551,6 +557,16 @@ export interface EntitlementDeletePacket extends BaseDispatchPacket { t: "ENTITLEMENT_DELETE"; } +export interface MessagePollVoteAdd extends BaseDispatchPacket { + d: RawMessagePollVote; + t: "MESSAGE_POLL_VOTE_ADD"; +} + +export interface MessagePollVoteRemove extends BaseDispatchPacket { + d: RawMessagePollVote; + t: "MESSAGE_POLL_VOTE_REMOVE"; +} + export type AnyDispatchPacket = PresenceUpdatePacket | ReadyPacket | ResumedPacket | GuildCreatePacket | GuildDeletePacket | GuildUpdatePacket | ApplicationCommandPermissionsUpdatePacket | GuildAuditLogEntryCreatePacket | AutoModerationRuleCreatePacket | AutoModerationRuleDeletePacket | AutoModerationRuleUpdatePacket | AutoModerationActionExecutionPacket | @@ -564,4 +580,5 @@ IntegrationCreatePacket | IntegrationDeletePacket | IntegrationUpdatePacket | InviteCreatePacket | InviteDeletePacket | MessageCreatePacket | MessageDeletePacket | MessageDeleteBulkPacket | MessageUpdatePacket | MessageReactionAddPacket | MessageReactionRemovePacket | MessageReactionRemoveAllPacket | MessageReactionRemoveEmojiPacket | TypingStartPacket | UserUpdatePacket | VoiceStateUpdatePacket | VoiceChannelEffectSendPacket | VoiceChannelStatusUpdatePacket | VoiceServerUpdatePacket | WebhooksUpdatePacket | InteractionCreatePacket | StageInstanceCreatePacket | StageInstanceDeletePacket | StageInstanceUpdatePacket | -EntitlementCreatePacket | EntitlementUpdatePacket | EntitlementDeletePacket; +EntitlementCreatePacket | EntitlementUpdatePacket | EntitlementDeletePacket | +MessagePollVoteAdd | MessagePollVoteRemove; diff --git a/lib/types/gateway.d.ts b/lib/types/gateway.d.ts index fc660e48..0bc246ea 100644 --- a/lib/types/gateway.d.ts +++ b/lib/types/gateway.d.ts @@ -113,7 +113,7 @@ interface GatewayOptions { */ reconnectDelay?: ReconnectDelayFunction; /** - * If a check should be made before connecting, which will remove any disallowed intents. This requires making a request to {@link REST/MApplications.getCurrent | `/applications/@me`}. Any removed intents will be emitted via the `warn` event. + * If a check should be made before connecting, which will remove any disallowed intents. This requires making a request to {@link REST/Applications.getCurrent | `/applications/@me`}. Any removed intents will be emitted via the `warn` event. * @defaultValue false */ removeDisallowedIntents?: boolean; @@ -336,3 +336,11 @@ export interface VoiceChannelEffect { animationID?: number; animationType?: AnimationTypes; } + +export interface RawMessagePollVote { + answer_id: number; + channel_id: string; + guild_id?: string; + message_id: string; + user_id: string; +} diff --git a/lib/types/interactions.d.ts b/lib/types/interactions.d.ts index 86d1116e..32120a7c 100644 --- a/lib/types/interactions.d.ts +++ b/lib/types/interactions.d.ts @@ -45,7 +45,7 @@ import type Guild from "../structures/Guild"; import type Permission from "../structures/Permission"; import type ModalSubmitInteractionComponentsWrapper from "../util/interactions/ModalSubmitInteractionComponentsWrapper"; -export interface InteractionContent extends Pick {} +export interface InteractionContent extends Pick {} export interface InitialInteractionContent extends Omit {} export type InteractionResponse = PingInteractionResponse | MessageInteractionResponse | DeferredInteractionResponse | AutocompleteInteractionResponse | ModalSubmitInteractionResponse | PremiumRequiredResponse; diff --git a/lib/types/json.d.ts b/lib/types/json.d.ts index 29f340ba..e56d2565 100644 --- a/lib/types/json.d.ts +++ b/lib/types/json.d.ts @@ -39,7 +39,10 @@ import type { TextableGuildChannels, ThreadChannels, VoiceChannels, - ThreadOnlyChannels + ThreadOnlyChannels, + PollQuestion, + PollResults, + PollAnswer } from "./channels"; import type { ScheduledEventEntityMetadata } from "./scheduled-events"; import type { Uncached } from "./shared"; @@ -73,7 +76,8 @@ import type { ForumLayoutTypes, EntitlementTypes, ApplicationIntegrationTypes, - InteractionContextTypes + InteractionContextTypes, + PollLayoutType } from "../Constants"; export interface JSONAnnouncementChannel extends JSONThreadableChannel { @@ -360,7 +364,6 @@ export interface JSONIntegration extends JSONBase { } export interface JSONInteraction extends JSONBase { applicationID: string; - token: string; type: InteractionTypes; version: 1; } @@ -544,6 +547,14 @@ export interface JSONPermissionOverwrite extends JSONBase { export interface JSONPingInteraction extends JSONInteraction { type: InteractionTypes.PING; } +export interface JSONPoll { + allowMultiselect: boolean; + answers: Array; + expiry: string; + layoutType: PollLayoutType; + question: PollQuestion; + results: PollResults; +} export interface JSONPrivateChannel extends JSONChannel { lastMessageID: string | null; messages: Array; diff --git a/lib/types/webhooks.d.ts b/lib/types/webhooks.d.ts index 3d225121..7a3f5782 100644 --- a/lib/types/webhooks.d.ts +++ b/lib/types/webhooks.d.ts @@ -46,7 +46,7 @@ export interface EditWebhookOptions extends EditWebhookTokenOptions { reason?: string; } -export interface ExecuteWebhookOptions extends Pick { +export interface ExecuteWebhookOptions extends Pick { /** The url of an avatar to use. */ avatarURL?: string; /** The id of the thread to send the message to. */ diff --git a/lib/util/Routes.ts b/lib/util/Routes.ts index 5d39f789..391c2200 100644 --- a/lib/util/Routes.ts +++ b/lib/util/Routes.ts @@ -78,20 +78,23 @@ export const GROUP_RECIPIENT = (channelID: string, userI export const VOICE_REGIONS = "/voice/regions" as const; export const GUILD_VOICE_REGIONS = (guildID: string) => `/guilds/${guildID}/regions` as const; export const VOICE_STATUS = (channelID: string) => `/channels/${channelID}/voice-status` as const; +export const POLL_ANSWER_USERS = (channelID: string, pollID: string, answerID: number) => `/channels/${channelID}/polls/${pollID}/answers/${answerID}` as const; +export const POLL_EXPIRE = (channelID: string, pollID: string) => `/channels/${channelID}/polls/${pollID}/expire` as const; // OAuth -export const OAUTH_APPLICATION = "/oauth2/applications/@me" as const; -export const OAUTH_AUTHORIZE = "/oauth2/authorize" as const; -export const OAUTH_INFO = "/oauth2/@me" as const; -export const OAUTH_CURRENT_USER = "/users/@me" as const; -export const OAUTH_CHANNELS = "/users/@me/channels" as const; -export const OAUTH_CONNECTIONS = "/users/@me/connections" as const; -export const OAUTH_GUILD = (guildID: string) => `/users/@me/guilds/${guildID}` as const; -export const OAUTH_GUILD_MEMBER = (guildID: string) => `${OAUTH_GUILD(guildID)}/member` as const; -export const OAUTH_GUILDS = "/users/@me/guilds" as const; -export const OAUTH_TOKEN = "/oauth2/token" as const; -export const OAUTH_TOKEN_REVOKE = "/oauth2/token/revoke" as const; -export const OAUTH_ROLE_CONNECTION = (applicationID: string) => `/users/@me/applications/${applicationID}/role-connection` as const; +export const OAUTH_APPLICATION = "/oauth2/applications/@me" as const; +export const OAUTH_AUTHORIZE = "/oauth2/authorize" as const; +export const OAUTH_INFO = "/oauth2/@me" as const; +export const OAUTH_CURRENT_USER = "/users/@me" as const; +export const OAUTH_CHANNELS = "/users/@me/channels" as const; +export const OAUTH_CONNECTIONS = "/users/@me/connections" as const; +export const OAUTH_GUILD = (guildID: string) => `/users/@me/guilds/${guildID}` as const; +export const OAUTH_GUILD_MEMBER = (guildID: string) => `${OAUTH_GUILD(guildID)}/member` as const; +export const OAUTH_GUILDS = "/users/@me/guilds" as const; +export const OAUTH_TOKEN = "/oauth2/token" as const; +export const OAUTH_TOKEN_REVOKE = "/oauth2/token/revoke" as const; +export const OAUTH_ROLE_CONNECTION = (applicationID: string) => `/users/@me/applications/${applicationID}/role-connection` as const; +export const ROLE_CONNECTIONS_METADATA = (applicationID: string) => `/applications/${applicationID}/role-connections/metadata` as const; // Images export const ACHIEVEMENT_ICON = (applicationID: string, achievementID: string, hash: string) => `/app-assets/${applicationID}/achievements/${achievementID}/icons/${hash}` as const; @@ -114,7 +117,7 @@ export const TEAM_ICON = (teamID: string, hash: string) => `/t export const USER_AVATAR = (userID: string, hash: string) => `/avatars/${userID}/${hash}` as const; export const USER_AVATAR_DECORATION = (userID: string, hash: string) => `/avatar-decorations/${userID}/${hash}` as const; -// Application Commands +// Applications export const APPLICATION_COMMAND = (applicationID: string, commandID: string) => `/applications/${applicationID}/commands/${commandID}` as const; export const APPLICATION_COMMANDS = (applicationID: string) => `/applications/${applicationID}/commands` as const; export const GUILD_APPLICATION_COMMAND = (applicationID: string, guildID: string, commandID: string) => `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}` as const; @@ -122,6 +125,10 @@ export const GUILD_APPLICATION_COMMANDS = (applicationID: string, gui export const GUILD_APPLICATION_COMMAND_PERMISSION = (applicationID: string, guildID: string, commandID: string) => `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}/permissions` as const; export const GUILD_APPLICATION_COMMAND_PERMISSIONS = (applicationID: string, guildID: string) => `/applications/${applicationID}/guilds/${guildID}commands/permissions` as const; export const INTERACTION_CALLBACK = (interactionID: string, interactionToken: string) => `/interactions/${interactionID}/${interactionToken}/callback` as const; +export const APPLICATION = "/applications/@me" as const; +export const ENTITLEMENTS = (applicationID: string) => `/applications/${applicationID}/entitlements` as const; +export const ENTITLEMENT = (applicationID: string, entitlementID: string) => `/applications/${applicationID}/entitlements/${entitlementID}` as const; +export const SKUS = (applicationID: string) => `/applications/${applicationID}/skus` as const; // Misc export const GATEWAY = "/gateway" as const; @@ -132,8 +139,3 @@ export const STICKER_PACKS = "/sticker-packs" as const; export const INVITE = (code: string) => `/invites/${code}` as const; export const STAGE_INSTANCES = "/stage-instances" as const; export const STAGE_INSTANCE = (channelID: string) => `/stage-instances/${channelID}` as const; -export const ROLE_CONNECTIONS_METADATA = (applicationID: string) => `/applications/${applicationID}/role-connections/metadata` as const; -export const APPLICATION = "/applications/@me" as const; -export const ENTITLEMENTS = (applicationID: string) => `/applications/${applicationID}/entitlements` as const; -export const ENTITLEMENT = (applicationID: string, entitlementID: string) => `/applications/${applicationID}/entitlements/${entitlementID}` as const; -export const SKUS = (applicationID: string) => `/applications/${applicationID}/skus` as const; diff --git a/lib/util/Util.ts b/lib/util/Util.ts index daee9b8c..c2f10ce5 100644 --- a/lib/util/Util.ts +++ b/lib/util/Util.ts @@ -63,6 +63,7 @@ import type { import Message from "../structures/Message"; import Entitlement from "../structures/Entitlement"; import TestEntitlement from "../structures/TestEntitlement"; +import type Poll from "../structures/Poll"; /** A general set of utilities. These are intentionally poorly documented, as they serve almost no usefulness to outside developers. */ export default class Util { @@ -492,6 +493,25 @@ export default class Util { } as RawApplicationCommandOption; } + /** @internal */ + replacePollAnswer(poll: Poll, answerID: number, count: number, users?: Array): void { + let answerCount = poll.results.answerCounts.find(a => a.id === answerID); + if (!answerCount) { + answerCount = { + count, + id: answerID, + users: [], + meVoted: false + }; + } + + answerCount.count = count; + if (users) { + answerCount.users = users; + answerCount.meVoted = (this.#client["_user"] && users.includes(this.#client["_user"]?.id)) ?? false; + } + } + updateChannel(channelData: RawChannel): T { guild: if (channelData.guild_id) { const guild = this.#client.guilds.get(channelData.guild_id); @@ -549,6 +569,38 @@ export default class Util { return new Message(data, this.#client); } + /** @internal */ + updatePollAnswer(poll: Poll, answerID: number, count: number, user?: string): void { + let answerCount = poll.results.answerCounts.find(a => a.id === answerID); + if (!answerCount) { + if (count === -1) { + return; + } + + answerCount = { + count, + id: answerID, + users: user ? [user] : [], + meVoted: user === this.#client["_user"]?.id + }; + poll.results.answerCounts.push(answerCount); + return; + } + + answerCount.count += count; + if (user) { + if (count === 1 && !answerCount.users.includes(user)) { + answerCount.users.push(user); + answerCount.meVoted = user === this.#client["_user"]?.id; + } else if (count === -1 && answerCount.users.includes(user)) { + answerCount.users.splice(answerCount.users.indexOf(user), 1); + if (user === this.#client["_user"]?.id) { + answerCount.meVoted = false; + } + } + } + } + /** @internal */ updateThread(threadData: RawThreadChannel): T { const guild = this.#client.guilds.get(threadData.guild_id);