diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index efa3f03e9..6a1b60c4a 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -5,6 +5,7 @@ export * from "./commands"; export * from "./guild"; export * from "./messages"; export * from "./misc"; +export * from "./quests"; export * from "./settings"; export * from "./user"; export * from "./voice"; diff --git a/packages/discord-types/enums/quests.ts b/packages/discord-types/enums/quests.ts new file mode 100644 index 000000000..8de76b3a8 --- /dev/null +++ b/packages/discord-types/enums/quests.ts @@ -0,0 +1,129 @@ +export const enum QuestRewardType { + REWARD_CODE = 1, + IN_GAME = 2, + COLLECTIBLE = 3, + VIRTUAL_CURRENCY = 4, + FRACTIONAL_PREMIUM = 5, +} + +export const enum QuestPlatform { + CROSS_PLATFORM = 0, + XBOX = 1, + PLAYSTATION = 2, + SWITCH = 3, + PC = 4, +} + +export const enum QuestTargetedContent { + GIFT_INVENTORY_SETTINGS_BADGE = 0, + QUEST_BAR = 1, + QUEST_INVENTORY_CARD = 2, + QUESTS_EMBED = 3, + ACTIVITY_PANEL = 4, + QUEST_LIVE_STREAM = 5, + MEMBERS_LIST = 6, + QUEST_BADGE = 7, + GIFT_INVENTORY_FOR_YOU = 8, + GIFT_INVENTORY_OTHER = 9, + QUEST_BAR_V2 = 10, + QUEST_HOME_DESKTOP = 11, + QUEST_HOME_MOBILE = 12, + QUEST_BAR_MOBILE = 13, + THIRD_PARTY_APP = 14, + QUEST_BOTTOM_SHEET = 15, + QUEST_EMBED_MOBILE = 16, + QUEST_HOME_MOVE_CALLOUT = 17, + DISCOVERY_SIDEBAR = 18, + QUEST_SHARE_LINK = 19, + CONNECTIONS_MODAL = 20, + DISCOVERY_COMPASS = 21, + TROPHY_CASE_CARD = 22, + VIDEO_MODAL = 23, + VIDEO_MODAL_END_CARD = 24, + REWARD_MODAL = 25, + EXCLUDED_QUEST_EMBED = 26, + VIDEO_MODAL_MOBILE = 27, + ORBS_ANNOUNCEMENT_MODAL = 28, + ORBS_BALANCE_MENU = 29, + QUEST_ENROLLMENT_BLOCKED_BOTTOM_SHEET = 30, + ORBS_SHOP_HERO_CTA = 31, + QUEST_ENROLLMENT_BLOCKED_MODAL = 32, + INTERNAL_PREVIEW_TOOL = 33, + ORBS_REHEAT_COACHMARK_CTA = 34, + INVALID_QUEST_EMBED = 35, + NOT_SHAREABLE_QUEST_EMBED = 36, + QUEST_HOME_MOVE_CALLOUT_DISCOVER = 37, + SPONSORED_QUEST_SHEET = 38, + MOBILE_ORBS_ONBOARDING_DC = 39, + RUNNING_ACTIVITY = 40, + VIDEO_MODAL_PRIMARY_CTA = 41, + QUEST_HOME_TAKEOVER = 42, + USER_PROFILE_ACTIVITY = 43, +} + +export const enum QuestPlacement { + INVALID_PLACEMENT = 0, + DESKTOP_ACCOUNT_PANEL_AREA = 1, + MOBILE_HOME_DOCK_AREA = 2, +} + +export const enum QuestRewardAssignmentMethod { + ALL = 1, + TIERED = 2, +} + +export const enum QuestRewardExpirationMode { + NORMAL = 1, + PREMIUM_EXTENSION = 2, + PREMIUM_PERMANENT = 3, +} + +export const enum QuestFeature { + POST_ENROLLMENT_CTA = 1, + QUEST_BAR_V2 = 3, + EXCLUDE_RUSSIA = 5, + IN_HOUSE_CONSOLE_QUEST = 6, + MOBILE_CONSOLE_QUEST = 7, + START_QUEST_CTA = 8, + REWARD_HIGHLIGHTING = 9, + FRACTIONS_QUEST = 10, + ADDITIONAL_REDEMPTION_INSTRUCTIONS = 11, + PACING_V2 = 12, + DISMISSAL_SURVEY = 13, + MOBILE_QUEST_DOCK = 14, + QUESTS_CDN = 15, + PACING_CONTROLLER = 16, + QUEST_HOME_FORCE_STATIC_IMAGE = 17, + VIDEO_QUEST_FORCE_HLS_VIDEO = 18, + VIDEO_QUEST_FORCE_END_CARD_CTA_SWAP = 19, + EXPERIMENTAL_TARGETING_TRAITS = 20, + DO_NOT_DISPLAY = 21, + EXTERNAL_DIALOG = 22, + MOBILE_ONLY_QUEST_PUSH_TO_MOBILE = 23, + MANUAL_HEARTBEAT_INITIALIZATION = 24, + CLOUD_GAMING_ACTIVITY = 25, + NON_GAMING_PLAY_QUEST = 26, + ACTIVITY_QUEST_AUTO_ENROLLMENT = 27, + PACKAGE_ACTION_ADVENTURE = 28, + PACKAGE_RPG_MMO = 29, + PACKAGE_RACING_SPORTS = 30, + PACKAGE_SANDBOX_CREATIVE = 31, + PACKAGE_FAMILY_FRIENDLY = 32, + PACKAGE_HOLIDAY_SEASON = 33, + PACKAGE_NEW_YEARS = 34, +} + +export const enum QuestDismissibleContentFlags { + GIFT_INVENTORY_SETTINGS_BADGE = 1, + QUEST_BAR = 2, + ACTIVITY_PANEL = 4, + QUEST_LIVE_STREAM = 8, +} + +export const enum QuestUserQuestStatus { + UNACCEPTED = 0, + ACCEPTED = 1, + IN_PROGRESS = 2, + COMPLETED = 3, + CLAIMED = 4, +} diff --git a/packages/discord-types/src/stores/QuestStore.d.ts b/packages/discord-types/src/stores/QuestStore.d.ts new file mode 100644 index 000000000..2948c44d3 --- /dev/null +++ b/packages/discord-types/src/stores/QuestStore.d.ts @@ -0,0 +1,992 @@ +import { + QuestDismissibleContentFlags, + QuestFeature, + QuestPlatform, + QuestPlacement, + QuestRewardAssignmentMethod, + QuestRewardExpirationMode, + QuestRewardType, + QuestTargetedContent, +} from "../../enums"; +import { FluxStore } from "./FluxStore"; + +/** Task types for quest completion requirements. */ +export const enum QuestTaskType { + STREAM_ON_DESKTOP = "STREAM_ON_DESKTOP", + PLAY_ON_DESKTOP = "PLAY_ON_DESKTOP", + PLAY_ON_XBOX = "PLAY_ON_XBOX", + PLAY_ON_PLAYSTATION = "PLAY_ON_PLAYSTATION", + PLAY_ON_DESKTOP_V2 = "PLAY_ON_DESKTOP_V2", + WATCH_VIDEO = "WATCH_VIDEO", + WATCH_VIDEO_ON_MOBILE = "WATCH_VIDEO_ON_MOBILE", + PLAY_ACTIVITY = "PLAY_ACTIVITY", + ACHIEVEMENT_IN_GAME = "ACHIEVEMENT_IN_GAME", + ACHIEVEMENT_IN_ACTIVITY = "ACHIEVEMENT_IN_ACTIVITY", +} + +/** Operator for combining multiple quest tasks. */ +export const enum QuestTaskJoinOperator { + /** All tasks must be completed. */ + AND = "and", + /** Any one task must be completed. */ + OR = "or", +} + +/** Error types returned from quest API operations. */ +export const enum QuestErrorType { + GENERIC = "generic", + RATE_LIMITED = "rate_limited", +} + +/** Platform mode for quest task selection. */ +export const enum QuestPlatformMode { + DESKTOP = "desktop", + CONSOLE = "console", + /** User must select a platform. */ + SELECT = "select", +} + +/** Reasons for pausing quest video playback. */ +export const enum QuestVideoPauseReason { + PAUSE_BUTTON = "PAUSE_BUTTON", + LOST_FOCUS = "LOST_FOCUS", + MODAL_CLOSED = "MODAL_CLOSED", +} + +/** Placement types for quest home takeover. */ +export const enum QuestHomePlacement { + QUEST_HOME_BANNER = "quest_home_banner", +} + +/** Client platforms for quest targeting. */ +export const enum QuestClientPlatform { + IOS = "ios", + ANDROID = "android", + DESKTOP = "desktop", + WEB = "web", + WEB_MOBILE = "web_mobile", + WEB_TABLET = "web_tablet", +} + +/** Quest sharing policy. */ +export const enum QuestSharePolicy { + SHAREABLE_EVERYWHERE = "shareable_everywhere", + NOT_SHAREABLE = "not_shareable", +} + +/** Quest bar UI states. */ +export const enum QuestBarState { + COLLAPSED = "collapsed", + EXPANDED = "expanded", + CLOSED = "closed", + SOFT_DISMISSED = "soft-dismissed", + RESET_TO_PREVIOUS = "reset-to-previous", +} + +/** Reasons for quest embed fallback display. */ +export const enum QuestEmbedFallbackReason { + EXCLUDED_QUEST = "excluded_quest", + UNKNOWN_QUEST = "unknown_quest", + NOT_SHAREABLE_QUEST = "not_shareable_quest", +} + +/** Console platform selection for quest tasks. */ +export const enum QuestConsolePlatformSelection { + CONSOLE = "CONSOLE", + DESKTOP = "DESKTOP", +} + +/** Call-to-action types for quest UI interactions. */ +export const enum QuestContentCTA { + LEARN_MORE = "LEARN_MORE", + SHOW_REWARD = "SHOW_REWARD", + CLAIM_REWARD = "CLAIM_REWARD", + GET_REWARD_CODE = "GET_REWARD_CODE", + COPY_REWARD_CODE = "COPY_REWARD_CODE", + ACCEPT_QUEST = "ACCEPT_QUEST", + COPY_QUEST_URL = "COPY_QUEST_URL", + MOBILE_SHARESHEET = "MOBILE_SHARESHEET", + TRACK_PROGRESS = "TRACK_PROGRESS", + CONNECT_CONSOLE = "CONNECT_CONSOLE", + CONNECT_CONSOLE_LINK = "CONNECT_CONSOLE_LINK", + VIEW_CONSOLE_CONNECTIONS = "VIEW_CONSOLE_CONNECTION", + VIEW_CONSOLE_CONNECTIONS_LINK = "VIEW_CONSOLE_CONNECTIONS_LINK", + VIEW_REQUIREMENTS = "VIEW_REQUIREMENTS", + SELECT_CONSOLE_PLATFORM = "SELECT_CONSOLE_PLATFORM", + SELECT_DESKTOP_PLATFORM = "SELECT_DESKTOP_PLATFORM", + DESELECT_PLATFORM = "DESELECT_PLATFORM", + DEFIBRILLATOR = "DEFIBRILLATOR", + DEFIBRILLATOR_RECONNECT_CONSOLE = "DEFIBRILLATOR_RECONNECT_CONSOLE", + OPEN_DISCLOSURE = "OPEN_DISCLOSURE", + WATCH_STREAM = "WATCH_STREAM", + WATCH_STREAM_CONFIRM = "WATCH_STREAM_CONFIRM", + REWARD_LEARN_MORE = "REWARD_LEARN_MORE", + OPEN_GAME_LINK = "OPEN_GAME_LINK", + OPEN_CONTEXT_MENU = "OPEN_CONTEXT_MENU", + OPEN_QUEST_HOME = "OPEN_QUEST_HOME", + QUEST_BAR_COPY_LINK = "QUEST_BAR.COPY_LINK", + CONTEXT_MENU_COPY_LINK = "CONTEXT_MENU.COPY_LINK", + REWARD_MODAL_COPY_LINK = "REWARD_MODAL.COPY_LINK", + CONTEXT_MENU_HIDE_CONTENT = "CONTEXT_MENU.HIDE_CONTENT", + CONTEXT_MENU_OPEN_GAME_LINK = "CONTEXT_MENU.OPEN_GAME_LINK", + CONTEXT_MENU_OPEN_DISCLOSURE = "CONTEXT_MENU.OPEN_DISCLOSURE", + CONTEXT_MENU_LEARN_MORE = "CONTEXT_MENU.LEARN_MORE", + HOW_TO_HELP_ARTICLE_XBOX = "HOW_TO_HELP_ARTICLE_XBOX", + HOW_TO_HELP_ARTICLE_PLAYSTATION = "HOW_TO_HELP_ARTICLE_PLAYSTATION", + VIEW_QUESTS = "VIEW_QUESTS", + EXPAND = "EXPAND", + COLLAPSE = "COLLAPSE", + START_QUEST = "START_QUEST", + TRANSCRIPT_ENABLE = "TRANSCRIPT_ENABLE", + TRANSCRIPT_DISABLE = "TRANSCRIPT_DISABLE", + CLOSED_CAPTIONING_ENABLE = "CLOSED_CAPTIONING_ENABLE", + CLOSED_CAPTIONING_DISABLE = "CLOSED_CAPTIONING_DISABLE", + SEEK_BACKWARD = "SEEK_BACKWARD", + SEEK_FORWARD = "SEEK_FORWARD", + WATCH_VIDEO = "WATCH_VIDEO", + QUEST_BAR_VIDEO_QUEST_PREVIEW = "QUEST_BAR_VIDEO_QUEST_PREVIEW", + QUEST_HOME_TILE_HEADER_WATCH_VIDEO = "QUEST_HOME_TILE_HEADER_WATCH_VIDEO", + REDEEM_REWARD = "REDEEM_REWARD", + VISIT_REDEMPTION_LINK = "VISIT_REDEMPTION_LINK", + SPONSORED_QUEST_SHEET = "SPONSORED_QUEST_SHEET", + GAME_PROFILE_OPEN = "GAME_PROFILE_OPEN", + GAME_STORE_OPEN_GAME_LINK = "GAME_STORE_OPEN_GAME_LINK", + MOBILE_ORBS_ONBOARDING_DC = "MOBILE_ORBS_ONBOARDING_DC", +} + +/** Sort methods for quest list display. */ +export const enum QuestSortMethod { + /** Default sorting by relevance. */ + SUGGESTED = "suggested", + MOST_RECENT = "most_recent", + EXPIRING_SOON = "expiring_soon", + RECENTLY_ENROLLED = "recently_enrolled", +} + +/** Filter types for quest tasks. */ +export const enum QuestTaskFilter { + VIDEO = "task_video", + PLAY = "task_play", +} + +/** Filter types for quest rewards. */ +export const enum QuestRewardFilter { + VIRTUAL_CURRENCY = "reward_virtual_currency", + COLLECTIBLE = "reward_collectible", + IN_GAME = "reward_in_game", +} + +/** Video playback progress states. */ +export const enum QuestVideoProgressState { + UNKNOWN = "UNKNOWN", + NOT_STARTED = "NOT_STARTED", + IN_PROGRESS = "IN_PROGRESS", + COMPLETED = "COMPLETED", +} + +/** Transcript fetch operation states. */ +export const enum QuestTranscriptFetchStatus { + NONE = "NONE", + FETCHING = "FETCHING", + SUCCESS = "SUCCESS", + FAILURE = "FAILURE", +} + +/** Result types from quest enrollment attempts. */ +export const enum QuestEnrollmentResult { + SUCCESS = "success", + CAPTCHA_FAILED = "captcha_failed", + UNKNOWN_ERROR = "unknown_error", + /** Enrollment already in progress. */ + PREVIOUS_IN_FLIGHT_REQUEST = "previous_in_flight_request", +} + +/** Reasons why a quest embed was not found. */ +export const enum QuestEmbedNotFoundReason { + NOT_FOUND = "not_found", + MOBILE_ONLY = "mobile_only", +} + +/** Tab filter types for quest list. */ +export const enum QuestFilterTab { + ALL = "all", + CLAIMED = "claimed", + /** Internal preview tool only. */ + PREVIEW_TOOL = "preview_tool", +} + +/** URL parameter types for quest filtering. */ +export const enum QuestFilterParamType { + TAB = "tab", + QUEST_ID = "quest_id", + SORT = "sort", + FILTER = "filter", +} + +/** Location identifiers for quest-related UI and tracking. */ +export const enum QuestLocation { + ACTIVITY_PANEL = "quests_bar_activity_panel", + QUESTS_MANAGER = "quests_manager", + QUESTS_CONSOLE_OPTIMISTIC_UPDATES_MANAGER = "quests_console_optimistic_updates_manager", + USER_SETTINGS_GIFT_INVENTORY = "user_settings_gift_inventory", + USER_SETTINGS_SEARCH_GIFT_INVENTORY = "user_settings_search_gift_inventory", + USE_QUESTS = "use_quests", + STREAM_SOURCE_SELECT = "stream_source_select", + MEMBERS_LIST = "members_list", + QUESTS_BAR = "quests_bar", + QUESTS_BAR_MOBILE = "quests_bar_mobile", + REWARD_CODE_MODAL = "reward_code_modal", + INGAME_REWARD_MODAL = "ingame_reward_modal", + COLLECTIBLE_REWARD_MODAL = "collectible_reward_modal", + ORBS_REWARD_MODAL = "orbs_reward_modal", + QUEST_PREVIEW_TOOL = "quest_preview_tool", + QUEST_PREVIEW_TOOL_2 = "quest_preview_tool_2", + QUESTS_MINOR_REWARD_CAPPING_CONFIG = "QUESTS_MINOR_REWARD_CAPPING_CONFIG", + QUESTS_CARD = "quests_card", + QUESTS_STORE = "quests_store", + QUEST_CHANNEL_CALL_HEADER = "quests_channel_call_header", + QUEST_HOME_DESKTOP = "quest_home_desktop", + QUEST_HOME_MOBILE = "quest_home_mobile", + QUEST_PROGRESS_BAR = "quest_progress_bar", + EMBED_MOBILE = "embed_mobile", + EMBED_DESKTOP = "embed_desktop", + QUEST_CONTEXT_MENU = "context_menu", + CODED_LINK = "coded_link", + QUEST_DISCLOSURE_MODAL = "quest_disclosure_modal", + DISCOVERY_SIDEBAR = "discovery_sidebar", + DISCOVERY_COMPASS = "discovery_compass", + BADGE = "badge", + COLLECTIBLES_SHOP_HEADER_BAR = "collectibles_shop_header_bar", + ORBS_ANNOUNCEMENT_MODAL = "orbs_announcement_modal", + CONFLICT_CHECKS = "conflict_checks", + VIDEO_MODAL = "video_modal", + VIDEO_MODAL_MOBILE = "video_modal_mobile", + GAME_WIDGETS_POPOVER = "game_widgets_popover", + PRIVATE_CHANNELS_LIST = "private_channels_list", + INTERNAL_TOOLING = "internal_tooling", + QUEST_HOME_MOVED_CALLOUT = "quest_home_moved_callout", + IN_APP_NAVIGATION = "in_app_navigation", + NAVIGATE_TO_QUEST_HOME_UTIL = "navigate_to_quest_home_util", + QUEST_DEEP_LINK_UTIL = "quest_deep_link_util", + YOU_TAB_PROFILE_HEADER = "you_tab_profile_header", +} + +/** Video asset for quest tasks. */ +export interface QuestVideoAsset { + /** Video URL path relative to CDN. */ + url: string; + /** Width in pixels. */ + width: number; + /** Height in pixels. */ + height: number; + /** Thumbnail image URL. */ + thumbnail: string; + /** VTT caption file URL. */ + caption?: string; + /** Transcript file URL. */ + transcript?: string; +} + +/** Video assets for video-based quest tasks. */ +export interface QuestTaskVideoAssets { + /** Full resolution video (1080p). */ + video: QuestVideoAsset; + /** Low resolution video (720p). */ + videoLowRes?: QuestVideoAsset; + /** HLS streaming video (.m3u8). */ + videoHls?: QuestVideoAsset; +} + +/** Display messages for video-based quest tasks. */ +export interface QuestTaskVideoMessages { + videoTitle: string; +} + +/** Display messages for achievement-based quest tasks. */ +export interface QuestTaskAchievementMessages { + taskTitle: string; + taskDescription: string; +} + +/** Application reference in quest tasks. */ +export interface QuestTaskApplication { + /** Discord application ID. */ + id: string; +} + +/** Base properties shared by all quest task types. */ +interface QuestTaskBase { + /** Target value to complete (seconds for play tasks, seconds for video tasks). */ + target: number; +} + +/** Play on desktop quest task. */ +export interface QuestTaskPlayOnDesktop extends QuestTaskBase { + type: QuestTaskType.PLAY_ON_DESKTOP; + /** Applications that qualify for completion. */ + applications: QuestTaskApplication[]; +} + +/** Play on desktop v2 quest task with enhanced tracking. */ +export interface QuestTaskPlayOnDesktopV2 extends QuestTaskBase { + type: QuestTaskType.PLAY_ON_DESKTOP_V2; + applications: QuestTaskApplication[]; +} + +/** Stream on desktop quest task. */ +export interface QuestTaskStreamOnDesktop extends QuestTaskBase { + type: QuestTaskType.STREAM_ON_DESKTOP; + applications: QuestTaskApplication[]; +} + +/** Play Discord activity quest task. */ +export interface QuestTaskPlayActivity extends QuestTaskBase { + type: QuestTaskType.PLAY_ACTIVITY; + applications: QuestTaskApplication[]; +} + +/** Play on Xbox quest task. */ +export interface QuestTaskPlayOnXbox extends QuestTaskBase { + type: QuestTaskType.PLAY_ON_XBOX; + /** Xbox title IDs. */ + externalIds: string[]; + applications: QuestTaskApplication[]; +} + +/** Play on PlayStation quest task. */ +export interface QuestTaskPlayOnPlayStation extends QuestTaskBase { + type: QuestTaskType.PLAY_ON_PLAYSTATION; + /** PlayStation title IDs (e.g., "PPSA09016_00"). */ + externalIds: string[]; + applications: QuestTaskApplication[]; +} + +/** Watch video quest task for desktop. */ +export interface QuestTaskWatchVideo extends QuestTaskBase { + type: QuestTaskType.WATCH_VIDEO; + assets: QuestTaskVideoAssets; + messages: QuestTaskVideoMessages; +} + +/** Watch video quest task for mobile. */ +export interface QuestTaskWatchVideoOnMobile extends QuestTaskBase { + type: QuestTaskType.WATCH_VIDEO_ON_MOBILE; + assets: QuestTaskVideoAssets; + messages: QuestTaskVideoMessages; +} + +/** Achievement in game quest task. */ +export interface QuestTaskAchievementInGame extends QuestTaskBase { + type: QuestTaskType.ACHIEVEMENT_IN_GAME; + /** Event name dispatched when achievement is unlocked. */ + eventName: string; + messages: QuestTaskAchievementMessages; + applications: QuestTaskApplication[]; +} + +/** Achievement in Discord activity quest task. */ +export interface QuestTaskAchievementInActivity extends QuestTaskBase { + type: QuestTaskType.ACHIEVEMENT_IN_ACTIVITY; + eventName: string; + messages: QuestTaskAchievementMessages; + applications: QuestTaskApplication[]; +} + +/** Union of all quest task types. */ +export type QuestTask = + | QuestTaskPlayOnDesktop + | QuestTaskPlayOnDesktopV2 + | QuestTaskStreamOnDesktop + | QuestTaskPlayActivity + | QuestTaskPlayOnXbox + | QuestTaskPlayOnPlayStation + | QuestTaskWatchVideo + | QuestTaskWatchVideoOnMobile + | QuestTaskAchievementInGame + | QuestTaskAchievementInActivity; + +/** Task configuration for a quest. */ +export interface QuestTaskConfig { + /** Map of task type to task configuration. */ + tasks: Partial>; + /** How tasks are combined. Default is "or" (any task completes the quest). */ + joinOperator: QuestTaskJoinOperator; +} + +/** Display messages for quest rewards. */ +export interface QuestRewardMessages { + /** Redemption instructions keyed by {@link QuestPlatform}. */ + redemptionInstructionsByPlatform: Partial>; + /** Reward display name (e.g., "700 Orbs"). */ + name: string; + /** Name with article (e.g., "700 Orbs"). */ + nameWithArticle: string; +} + +/** Base properties shared by all quest reward types. */ +interface QuestRewardBase { + /** SKU ID for the reward entitlement. */ + skuId: string; + messages: QuestRewardMessages; +} + +/** Reward code reward type. */ +export interface QuestRewardCode extends QuestRewardBase { + type: QuestRewardType.REWARD_CODE; + /** Reward image URL. */ + asset?: string; + /** Reward video URL. */ + assetVideo?: string | null; + /** Approximate codes remaining, null if unlimited. */ + approximateCount?: number | null; + /** External URL to redeem the code. */ + redemptionLink?: string | null; +} + +/** In-game reward type. */ +export interface QuestRewardInGame extends QuestRewardBase { + type: QuestRewardType.IN_GAME; + asset?: string; + assetVideo?: string | null; +} + +/** Collectible avatar decoration reward type. */ +export interface QuestRewardCollectible extends QuestRewardBase { + type: QuestRewardType.COLLECTIBLE; + asset?: string; + assetVideo?: string | null; + /** ISO timestamp when collectible expires. */ + expiresAt?: string; + /** Expiration behavior. */ + expirationMode?: QuestRewardExpirationMode; + /** Extended expiration for Nitro users. */ + expiresAtPremium?: string | null; +} + +/** Virtual currency (Orbs) reward type. */ +export interface QuestRewardVirtualCurrency extends QuestRewardBase { + type: QuestRewardType.VIRTUAL_CURRENCY; + /** Number of orbs awarded. */ + orbQuantity: number; +} + +/** Fractional Nitro premium time reward type. */ +export interface QuestRewardFractionalPremium extends QuestRewardBase { + type: QuestRewardType.FRACTIONAL_PREMIUM; + asset?: string; + assetVideo?: string | null; + /** Premium time quantity. */ + quantity: number; +} + +/** Union of all quest reward types. */ +export type QuestReward = + | QuestRewardCode + | QuestRewardInGame + | QuestRewardCollectible + | QuestRewardVirtualCurrency + | QuestRewardFractionalPremium; + +/** Rewards configuration for a quest. */ +export interface QuestRewardsConfig { + /** How rewards are distributed to users. */ + assignmentMethod: QuestRewardAssignmentMethod; + /** Available reward options. */ + rewards: QuestReward[]; + /** ISO timestamp when rewards can no longer be claimed. */ + rewardsExpireAt: string; + /** Platforms where rewards can be claimed. */ + platforms: QuestPlatform[]; +} + +/** Visual assets for quest display. */ +export interface QuestAssets { + /** Hero banner image URL. */ + hero: string; + /** Hero banner video URL. */ + heroVideo: string | null; + /** Quest bar hero image URL. */ + questBarHero: string; + /** Quest bar hero video URL. */ + questBarHeroVideo: string | null; + /** Game tile image URL. */ + gameTile: string; + /** Game logotype image URL. */ + logotype: string; + /** Light theme game tile URL. */ + gameTileLight: string; + /** Dark theme game tile URL. */ + gameTileDark: string; + /** Light theme logotype URL. */ + logotypeLight: string; + /** Dark theme logotype URL. */ + logotypeDark: string; +} + +/** Color scheme for quest UI theming. */ +export interface QuestColors { + /** Primary hex color (e.g., "#4752C4"). */ + primary: string; + /** Secondary hex color. */ + secondary: string; +} + +/** Display messages for quest UI. */ +export interface QuestMessages { + /** Quest display name. */ + questName: string; + /** Game title. */ + gameTitle: string; + /** Game publisher name. */ + gamePublisher: string; +} + +/** Application reference in quest configuration. */ +export interface QuestApplication { + /** Discord application ID. */ + id: string; + /** Application display name. */ + name: string; +} + +/** Platform-specific app store configuration. */ +export interface QuestCtaPlatformConfig { + /** Android package name. */ + androidAppId?: string; + /** iOS App Store ID. */ + iosAppId?: string; +} + +/** Call-to-action button configuration. */ +export interface QuestCtaConfig { + /** CTA destination URL. */ + link: string; + /** Button label text. */ + buttonLabel: string; + /** Optional subtitle text. */ + subtitle?: string; + /** Android-specific configuration. */ + android?: QuestCtaPlatformConfig; + /** iOS-specific configuration. */ + ios?: QuestCtaPlatformConfig; +} + +/** Co-sponsor branding metadata for partnered quests. */ +export interface QuestCosponsorMetadata { + /** Co-sponsor company name. */ + name: string; + /** Logotype image URL. */ + logotype: string; + /** Redemption instructions text. */ + redemptionInstructions: string; + /** Light theme logotype URL. */ + logotypeLight: string; + /** Dark theme logotype URL. */ + logotypeDark: string; +} + +/** Complete quest configuration. */ +export interface QuestConfig { + /** Quest ID (snowflake). */ + id: string; + /** Configuration version number. */ + configVersion: number; + /** ISO timestamp when quest becomes available. */ + startsAt: string; + /** ISO timestamp when quest expires. */ + expiresAt: string; + /** Enabled feature flags for this quest. */ + features: QuestFeature[]; + /** Associated Discord application. */ + application: QuestApplication; + /** Visual assets. */ + assets: QuestAssets; + /** Color scheme. */ + colors: QuestColors; + /** Display messages. */ + messages: QuestMessages; + /** Task requirements configuration. */ + taskConfigV2: QuestTaskConfig; + /** Rewards configuration. */ + rewardsConfig: QuestRewardsConfig; + /** Optional co-sponsor branding. */ + cosponsorMetadata?: QuestCosponsorMetadata; + /** Sharing policy. */ + sharePolicy: QuestSharePolicy; + /** Call-to-action configuration. */ + ctaConfig: QuestCtaConfig; +} + +/** Heartbeat tracking for quest progress. Updated via QUESTS_SEND_HEARTBEAT_SUCCESS. */ +export interface QuestProgressHeartbeat { + /** ISO timestamp of last heartbeat. */ + lastBeatAt: string; + /** ISO timestamp when heartbeat tracking expires. */ + expiresAt: string | null; +} + +/** Progress tracking for a single quest task. Updated via QUESTS_USER_STATUS_UPDATE. */ +export interface QuestTaskProgress { + /** Task event name matching {@link QuestTaskType}. */ + eventName: string; + /** Current progress value (seconds played/watched). */ + value: number; + /** ISO timestamp of last progress update. */ + updatedAt: string; + /** ISO timestamp when task was completed, null if incomplete. */ + completedAt: string | null; + /** Heartbeat tracking for active progress. */ + heartbeat: QuestProgressHeartbeat | null; +} + +/** User's status for a specific quest. Updated via QUESTS_USER_STATUS_UPDATE. */ +export interface QuestUserStatus { + /** User ID (snowflake). */ + userId: string; + /** Quest ID (snowflake). */ + questId: string; + /** ISO timestamp when user enrolled. */ + enrolledAt: string; + /** ISO timestamp when quest was completed, null if incomplete. */ + completedAt: string | null; + /** ISO timestamp when reward was claimed, null if unclaimed. */ + claimedAt: string | null; + /** Claimed reward tier for tiered rewards, null otherwise. */ + claimedTier: number | null; + /** ISO timestamp of last stream heartbeat. */ + lastStreamHeartbeatAt: string | null; + /** Total stream progress in seconds. */ + streamProgressSeconds: number; + /** Bitmask of dismissed UI content. */ + dismissedQuestContent: QuestDismissibleContentFlags; + /** Progress by task event name. */ + progress: Record; +} + +/** Quest object with configuration and user status. */ +export interface Quest { + /** Quest ID (snowflake). */ + id: string; + /** Whether this is a preview/test quest. */ + preview: boolean; + /** Quest configuration. */ + config: QuestConfig; + /** User's status, null if not enrolled. */ + userStatus: QuestUserStatus | null; + /** UI placements where this quest should appear. */ + targetedContent: QuestTargetedContent[]; +} + +/** Reference to an excluded quest. */ +export interface ExcludedQuest { + /** Excluded quest ID. */ + id: string; + /** Replacement quest ID to show instead. */ + replacementId: string; +} + +/** Ad tracking identifiers for quest delivery. */ +export interface QuestAdIdentifiers { + ad_id?: string; + adset_id?: string; + ad_set_id?: string; + campaign_id?: string; + creative_id?: string; + creative_type?: string; + /** Request ID from ad decision. */ + decision_id?: string; + /** Whether quest was served via targeting. */ + is_targeted: boolean; +} + +/** Quest delivery information for a placement. */ +export interface QuestDeliveryInfo { + /** Quest to deliver. */ + quest: Quest; + /** Ad tracking data. */ + adDecisionData: QuestAdIdentifiers; + /** Ad context for analytics. */ + adContext: string | null; + /** Raw metadata for ad attribution. */ + metadataRaw: string | null; + /** Sealed metadata for verification. */ + metadataSealed: string | null; +} + +/** Ad decision cache entry for quest delivery. */ +export interface QuestAdDecision { + /** Quest ID or null if no quest to deliver. */ + questId: string | null; + /** Unix timestamp when decision was fetched. */ + fetchedAt: number; + /** Cache TTL in milliseconds. Default 6 hours. */ + ttlMillis: number; + /** Ad tracking data. */ + adDecisionData: QuestAdIdentifiers; + adContext: string | null; + metadataRaw: string | null; + metadataSealed: string | null; +} + +/** Stream heartbeat failure tracking. Updated via QUESTS_SEND_HEARTBEAT_FAILURE. */ +export interface StreamHeartbeatFailure { + /** Quest ID. */ + questId: string; + /** Stream identifier. */ + streamKey: string; + /** Unix timestamp of first failure. */ + firstFailedAt: number; +} + +/** Asset for quest home takeover display. */ +export interface QuestHomeTakeoverAsset { + /** Accessibility text. */ + altText: string; + /** Asset type identifier. */ + assetType: string; + /** Asset URL. */ + url: string; +} + +/** Sponsor CTA for quest home takeover. */ +export interface QuestHomeTakeoverCtaSponsor { + ctaType: string; + title: string; + /** Sponsor destination URL. */ + url: string; +} + +/** Quest CTA for quest home takeover. */ +export interface QuestHomeTakeoverCtaQuest { + ctaType: string; + title: string; + /** Quest ID to navigate to. */ + questId: string; +} + +/** Quest home takeover banner configuration. */ +export interface QuestHomeTakeoverConfig { + placementType: QuestHomePlacement; + /** Campaign identifier. */ + campaignId: string; + labelTitle: string; + labelSubtitle: string; + /** Hero banner asset. */ + assetHeroImage: QuestHomeTakeoverAsset; + /** Sponsor logo asset. */ + assetSponsorImage: QuestHomeTakeoverAsset; + /** Sponsor link CTA. */ + ctaSponsorUrl: QuestHomeTakeoverCtaSponsor; + /** Quest navigation CTAs. */ + ctaQuests: QuestHomeTakeoverCtaQuest[]; + /** ISO timestamp when takeover starts. */ + startsAt: string; + /** ISO timestamp when takeover expires. */ + expiresAt: string; +} + +/** Claimed reward code details. Returned by QUESTS_FETCH_REWARD_CODE_SUCCESS. */ +export interface QuestClaimedRewardCode { + userId: string; + questId: string; + /** The reward code string. */ + code: string; + /** Platform the code is for. */ + platform: QuestPlatform; + /** ISO timestamp when claimed. */ + claimedAt: string; + /** Reward tier if applicable. */ + tier: number | null; +} + +/** Entitlement item from reward claim. */ +export interface QuestEntitlementItem { + /** SKU ID. */ + skuId: string; + /** Metadata containing reward details. */ + tenantMetadata: { + questRewards?: { + reward: { + tag: QuestRewardType; + rewardCode?: QuestClaimedRewardCode; + }; + }; + } | null; + /** Whether entitlement has been consumed. */ + consumed: boolean; +} + +/** Error from reward claim operation. */ +export interface QuestEntitlementError { + code?: string; + message?: string; +} + +/** Response from claiming quest rewards. Returned by QUESTS_CLAIM_REWARD_SUCCESS. */ +export interface QuestEntitlements { + /** ISO timestamp when claimed. */ + claimedAt: string; + /** Entitlement items received. */ + items: QuestEntitlementItem[]; + /** Errors encountered during claim. */ + errors: QuestEntitlementError[]; +} + +/** Video progress tracking state. */ +export interface QuestVideoProgress { + /** Current playback position in seconds. */ + timestampSec: number; + /** Total video duration in seconds. */ + duration: number; + /** Maximum position reached in seconds. */ + maxTimestampSec: number; +} + +/** Transcript asset for quest videos. */ +export interface QuestTranscriptAsset { + questId: string; + fetchStatus: QuestTranscriptFetchStatus; + /** Transcript text content when loaded. */ + text?: string; + /** Transcript file URL. */ + url?: string; +} + +/** Error hint from console quest operations. */ +export interface QuestConsoleErrorHint { + type: QuestErrorType; + /** User-facing error message. */ + message: string; + /** Connected account ID that caused the error. */ + connected_account_id: string; + /** Account type (e.g., "xbox", "playstation"). */ + connected_account_type: string; +} + +/** Computed task details for display. */ +export interface QuestTaskDetails { + taskType: QuestTaskType; + /** Target completion time in minutes. */ + targetMinutes: number; + /** Progress as decimal (0.0 to 1.0). */ + percentComplete: number; + /** Applications for this task. */ + applications?: QuestTaskApplication[]; +} + +/** Third-party task details for external quests. */ +export interface QuestThirdPartyTaskDetails { + description: string; + /** Current progress value. */ + progress: number; + /** Target completion value. */ + target: number; + /** Progress as decimal (0.0 to 1.0). */ + percentComplete: number; + title?: string; +} + +/** Store for Discord Quests data. Manages quest configs, user progress, rewards, and delivery. */ +export class QuestStore extends FluxStore { + /** All quests keyed by quest ID. Updated via QUESTS_FETCH_CURRENT_QUESTS_SUCCESS. */ + get quests(): Map; + + /** Quests the user is excluded from. Updated via QUESTS_FETCH_CURRENT_QUESTS_SUCCESS. */ + get excludedQuests(): Map; + + /** Quests with claimed rewards. Updated via QUESTS_FETCH_CLAIMED_QUESTS_SUCCESS. */ + get claimedQuests(): Map; + + /** Whether fetching current quests. Set via QUESTS_FETCH_CURRENT_QUESTS_BEGIN. */ + get isFetchingCurrentQuests(): boolean; + + /** Whether fetching claimed quests. Set via QUESTS_FETCH_CLAIMED_QUESTS_BEGIN. */ + get isFetchingClaimedQuests(): boolean; + + /** Unix timestamp of last current quests fetch. Default 0. */ + get lastFetchedCurrentQuests(): number; + + /** Unix timestamp of last quest-to-deliver fetch. Default 0. */ + get lastFetchedQuestToDeliver(): number; + + /** Whether fetching quest to deliver. Set via QUESTS_FETCH_QUEST_TO_DELIVER_BEGIN. */ + get isFetchingQuestToDeliver(): boolean; + + /** Override quest for delivery testing. Set via QUESTS_DELIVERY_OVERRIDE. */ + get questDeliveryOverride(): Quest | undefined; + + /** Quest delivery info keyed by {@link QuestPlacement}. Updated via QUESTS_FETCH_QUEST_TO_DELIVER_SUCCESS. */ + get questToDeliverForPlacement(): Map; + + /** Date until enrollment is blocked, null if not blocked. Set via QUESTS_ENROLL_FAILURE. */ + get questEnrollmentBlockedUntil(): Date | null; + + /** Ad decision cache keyed by {@link QuestPlacement}. Updated via QUESTS_FETCH_QUEST_TO_DELIVER_SUCCESS. */ + get questAdDecisionByPlacement(): Map; + + /** Quest configs keyed by quest ID. */ + get questConfigs(): Map; + + /** Whether fetching preview for a quest. */ + isFetchingQuestPreview(questId: string): boolean; + + /** Whether fetching quest to deliver for a placement. */ + isFetchingQuestToDeliverByPlacement(placement: QuestPlacement): boolean; + + /** Gets preview fetch error for a quest. */ + getFetchQuestPreviewError(questId: string): QuestErrorType | undefined; + + /** Whether user is currently enrolling in a quest. Set via QUESTS_ENROLL_BEGIN. */ + isEnrolling(questId: string): boolean; + + /** Whether user is currently claiming reward for a quest. Set via QUESTS_CLAIM_REWARD_BEGIN. */ + isClaimingReward(questId: string): boolean; + + /** Whether fetching reward code for a quest. Set via QUESTS_FETCH_REWARD_CODE_BEGIN. */ + isFetchingRewardCode(questId: string): boolean; + + /** Whether dismissing content for a quest. Set via QUESTS_DISMISS_CONTENT_BEGIN. */ + isDismissingContent(questId: string): boolean; + + /** Gets claimed reward code for a quest. Updated via QUESTS_FETCH_REWARD_CODE_SUCCESS. */ + getRewardCode(questId: string): QuestClaimedRewardCode | undefined; + + /** Gets entitlement items for a quest. Updated via QUESTS_CLAIM_REWARD_SUCCESS. */ + getRewards(questId: string): QuestEntitlementItem[] | undefined; + + /** Gets stream heartbeat failure info. Cleared via STREAM_CLOSE. */ + getStreamHeartbeatFailure(streamKey: string): StreamHeartbeatFailure | undefined; + + /** Gets a quest by ID. */ + getQuest(questId: string): Quest | undefined; + + /** Gets quest config by ID. */ + getQuestConfig(questId: string): QuestConfig | undefined; + + /** Whether quest has active desktop progress. Updated via QUESTS_SEND_HEARTBEAT_SUCCESS. */ + isProgressingOnDesktop(questId: string): boolean; + + /** Gets selected task platform for a quest. Set via QUESTS_SELECT_TASK_PLATFORM. */ + selectedTaskPlatform(questId: string): QuestPlatformMode | null; + + /** Gets optimistic progress for a task. Set via QUESTS_UPDATE_OPTIMISTIC_PROGRESS, cleared via QUESTS_RESET_OPTIMISTIC_PROGRESS. */ + getOptimisticProgress(questId: string, taskEventName: string): number | undefined; + + /** Gets map of quest IDs to expired status. Computed from quest config expiresAt. */ + getExpiredQuestsMap(): Map; + + /** Whether a quest has expired. Computed from quest config expiresAt. */ + isQuestExpired(questId: string): boolean; + + /** Gets quest loaded via preview tool. Updated via QUESTS_FETCH_PREVIEW_SUCCESS. */ + getQuestLoadedViaPreview(questId: string): Quest | undefined; + + /** Whether fetching quest home takeover config. Set via QUESTS_FETCH_QUEST_HOME_TAKEOVER_BEGIN. */ + isFetchingQuestHomeTakeover(): boolean; + + /** Gets quest home takeover config. Updated via QUESTS_FETCH_QUEST_HOME_TAKEOVER_SUCCESS. */ + getQuestHomeTakeoverConfig(): QuestHomeTakeoverConfig | null; + + /** Gets Unix timestamp of last takeover fetch, null if never fetched. */ + getLastFetchedQuestHomeTakeover(): number | null; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index d46a3cbd5..019efb38e 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -28,6 +28,7 @@ export * from "./OverridePremiumTypeStore"; export * from "./PermissionStore"; export * from "./PopoutWindowStore"; export * from "./PresenceStore"; +export * from "./QuestStore"; export * from "./ReadStateStore"; export * from "./RelationshipStore"; export * from "./RTCConnectionStore"; diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index fc54d6948..d622f9f53 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -81,6 +81,7 @@ export let SoundboardStore: t.SoundboardStore; export let PopoutWindowStore: t.PopoutWindowStore; export let ApplicationCommandIndexStore: t.ApplicationCommandIndexStore; export let EditMessageStore: t.EditMessageStore; +export let QuestStore: t.QuestStore; /** * @see jsdoc of {@link t.useStateFromStores} @@ -134,6 +135,7 @@ waitForStore("SoundboardStore", m => SoundboardStore = m); waitForStore("PopoutWindowStore", m => PopoutWindowStore = m); waitForStore("ApplicationCommandIndexStore", m => ApplicationCommandIndexStore = m); waitForStore("EditMessageStore", m => EditMessageStore = m); +waitForStore("QuestStore", m => QuestStore = m); waitForStore("ThemeStore", m => { ThemeStore = m; // Importing this directly causes all webpack commons to be imported, which can easily cause circular dependencies.