api-surface #1
16 changed files with 914 additions and 3 deletions
feat(model): webhook/invite/audit/automod/scheduled/poll/application/team + retypes
commit
9c8c8de498
56
lib/dexcord/model/application_info.ex
Normal file
56
lib/dexcord/model/application_info.ex
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
defmodule Dexcord.InstallParams do
|
||||
@moduledoc "OAuth2 install parameters for an application."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :scopes, {:list, :string}, default: []
|
||||
field :permissions, {:flags, Dexcord.Permissions}, wire_string: true
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.ApplicationInfo do
|
||||
@moduledoc """
|
||||
A Discord application object.
|
||||
|
||||
Named `ApplicationInfo` (not `Application`) to avoid colliding with the OTP
|
||||
application module `Dexcord.Application`.
|
||||
|
||||
https://docs.discord.com/developers/resources/application
|
||||
"""
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :name, :string
|
||||
field :icon, :string
|
||||
field :description, :string
|
||||
field :rpc_origins, {:list, :string}, default: []
|
||||
field :bot_public, :boolean
|
||||
field :bot_require_code_grant, :boolean
|
||||
field :bot, {:struct, Dexcord.User}
|
||||
field :terms_of_service_url, :string
|
||||
field :privacy_policy_url, :string
|
||||
field :owner, {:struct, Dexcord.User}
|
||||
field :verify_key, :string
|
||||
field :team, {:struct, Dexcord.Team}
|
||||
field :guild_id, :snowflake
|
||||
field :guild, {:struct, Dexcord.PartialGuild}
|
||||
field :primary_sku_id, :snowflake
|
||||
field :slug, :string
|
||||
field :cover_image, :string
|
||||
field :flags, {:flags, Dexcord.ApplicationFlags}
|
||||
field :approximate_guild_count, :integer
|
||||
field :approximate_user_install_count, :integer
|
||||
field :approximate_user_authorization_count, :integer
|
||||
field :redirect_uris, {:list, :string}, default: []
|
||||
field :interactions_endpoint_url, :string
|
||||
field :role_connections_verification_url, :string
|
||||
field :event_webhooks_url, :string
|
||||
field :event_webhooks_status, {:enum, Dexcord.EventWebhooksStatus}
|
||||
field :event_webhooks_types, {:list, :string}, default: []
|
||||
field :tags, {:list, :string}, default: []
|
||||
field :install_params, {:struct, Dexcord.InstallParams}
|
||||
field :integration_types_config, :raw
|
||||
field :custom_install_url, :string
|
||||
end
|
||||
end
|
||||
71
lib/dexcord/model/audit_log.ex
Normal file
71
lib/dexcord/model/audit_log.ex
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
defmodule Dexcord.AuditLogChange do
|
||||
@moduledoc "A single changed key in an audit-log entry."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :new_value, :raw
|
||||
field :old_value, :raw
|
||||
field :key, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.AuditLogEntryInfo do
|
||||
@moduledoc """
|
||||
Optional extra info for certain audit-log actions.
|
||||
|
||||
Several numeric-looking fields (`count`, `delete_member_days`,
|
||||
`members_removed`) are typed `:string` on purpose — Discord genuinely
|
||||
sends them as strings on the wire.
|
||||
"""
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :application_id, :snowflake
|
||||
field :auto_moderation_rule_name, :string
|
||||
field :auto_moderation_rule_trigger_type, :string
|
||||
field :channel_id, :snowflake
|
||||
field :count, :string
|
||||
field :delete_member_days, :string
|
||||
field :id, :snowflake
|
||||
field :members_removed, :string
|
||||
field :message_id, :snowflake
|
||||
field :role_name, :string
|
||||
field :type, :string
|
||||
field :integration_type, :string
|
||||
field :status, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.AuditLogEntry do
|
||||
@moduledoc "A single audit-log entry. https://docs.discord.com/developers/resources/audit-log"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
# String, not snowflake — can hold non-snowflake target ids.
|
||||
field :target_id, :string
|
||||
field :changes, {:list, {:struct, Dexcord.AuditLogChange}}, default: []
|
||||
field :user_id, :snowflake
|
||||
field :id, :snowflake
|
||||
field :action_type, {:enum, Dexcord.AuditLogEvent}
|
||||
field :options, {:struct, Dexcord.AuditLogEntryInfo}
|
||||
field :reason, :string
|
||||
# Gateway extra from GUILD_AUDIT_LOG_ENTRY_CREATE.
|
||||
field :guild_id, :snowflake
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.AuditLog do
|
||||
@moduledoc "A guild audit log. https://docs.discord.com/developers/resources/audit-log"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :application_commands, {:list, {:struct, Dexcord.ApplicationCommand}}, default: []
|
||||
field :audit_log_entries, {:list, {:struct, Dexcord.AuditLogEntry}}, default: []
|
||||
field :auto_moderation_rules, {:list, {:struct, Dexcord.AutoModerationRule}}, default: []
|
||||
field :guild_scheduled_events, {:list, {:struct, Dexcord.GuildScheduledEvent}}, default: []
|
||||
field :integrations, {:list, {:struct, Dexcord.Integration}}, default: []
|
||||
field :threads, {:list, {:struct, Dexcord.Channel}}, default: []
|
||||
field :users, {:list, {:struct, Dexcord.User}}, default: []
|
||||
field :webhooks, {:list, {:struct, Dexcord.Webhook}}, default: []
|
||||
end
|
||||
end
|
||||
53
lib/dexcord/model/auto_moderation.ex
Normal file
53
lib/dexcord/model/auto_moderation.ex
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
defmodule Dexcord.AutoModerationTriggerMetadata do
|
||||
@moduledoc "Additional data used to determine whether an auto-moderation rule triggers."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :keyword_filter, {:list, :string}, default: []
|
||||
field :regex_patterns, {:list, :string}, default: []
|
||||
field :presets, {:list, {:enum, Dexcord.KeywordPresetType}}, default: []
|
||||
field :allow_list, {:list, :string}, default: []
|
||||
field :mention_total_limit, :integer
|
||||
field :mention_raid_protection_enabled, :boolean
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.AutoModerationActionMetadata do
|
||||
@moduledoc "Additional data used when an auto-moderation action is executed."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :channel_id, :snowflake
|
||||
field :duration_seconds, :integer
|
||||
field :custom_message, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.AutoModerationAction do
|
||||
@moduledoc "An action taken when an auto-moderation rule is triggered."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :type, {:enum, Dexcord.AutoModerationActionType}
|
||||
field :metadata, {:struct, Dexcord.AutoModerationActionMetadata}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.AutoModerationRule do
|
||||
@moduledoc "An auto-moderation rule. https://docs.discord.com/developers/resources/auto-moderation"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :guild_id, :snowflake
|
||||
field :name, :string
|
||||
field :creator_id, :snowflake
|
||||
field :event_type, {:enum, Dexcord.AutoModerationEventType}
|
||||
field :trigger_type, {:enum, Dexcord.AutoModerationTriggerType}
|
||||
field :trigger_metadata, {:struct, Dexcord.AutoModerationTriggerMetadata}
|
||||
field :actions, {:list, {:struct, Dexcord.AutoModerationAction}}, default: []
|
||||
field :enabled, :boolean
|
||||
field :exempt_roles, {:list, :snowflake}, default: []
|
||||
field :exempt_channels, {:list, :snowflake}, default: []
|
||||
end
|
||||
end
|
||||
|
|
@ -184,6 +184,7 @@ defmodule Dexcord.Thread do
|
|||
field :member, {:struct, Dexcord.ThreadMember}
|
||||
field :applied_tags, {:list, :snowflake}, default: []
|
||||
field :last_pin_timestamp, :datetime
|
||||
field :newly_created, :boolean, default: false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ defmodule Dexcord.ThreadMember do
|
|||
field :join_timestamp, :datetime
|
||||
field :flags, :integer
|
||||
field :member, {:struct, Dexcord.Member}
|
||||
field :guild_id, :snowflake
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ defmodule Dexcord.Guild do
|
|||
field :max_stage_video_channel_users, :integer
|
||||
field :approximate_member_count, :integer
|
||||
field :approximate_presence_count, :integer
|
||||
field :welcome_screen, :raw
|
||||
field :welcome_screen, {:struct, Dexcord.WelcomeScreen}
|
||||
field :nsfw_level, {:enum, Dexcord.GuildNsfwLevel}
|
||||
field :stickers, {:list, {:struct, Dexcord.Sticker}}, default: []
|
||||
field :premium_progress_bar_enabled, :boolean
|
||||
|
|
@ -54,7 +54,7 @@ defmodule Dexcord.Guild do
|
|||
field :threads, {:list, {:struct, Dexcord.Channel}}, default: []
|
||||
field :presences, {:list, {:struct, Dexcord.Presence}}, default: []
|
||||
field :stage_instances, {:list, :raw}, default: []
|
||||
field :guild_scheduled_events, {:list, :raw}, default: []
|
||||
field :guild_scheduled_events, {:list, {:struct, Dexcord.GuildScheduledEvent}}, default: []
|
||||
field :soundboard_sounds, {:list, :raw}, default: []
|
||||
end
|
||||
end
|
||||
|
|
|
|||
149
lib/dexcord/model/guild_extras.ex
Normal file
149
lib/dexcord/model/guild_extras.ex
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
defmodule Dexcord.WelcomeScreenChannel do
|
||||
@moduledoc "A channel shown on a guild's welcome screen."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :channel_id, :snowflake
|
||||
field :description, :string
|
||||
field :emoji_id, :snowflake
|
||||
field :emoji_name, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.WelcomeScreen do
|
||||
@moduledoc "A guild welcome screen."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :description, :string
|
||||
field :welcome_channels, {:list, {:struct, Dexcord.WelcomeScreenChannel}}, default: []
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Ban do
|
||||
@moduledoc "A guild ban."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :reason, :string
|
||||
field :user, {:struct, Dexcord.User}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.GuildWidgetSettings do
|
||||
@moduledoc "A guild's widget settings."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :enabled, :boolean
|
||||
field :channel_id, :snowflake
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.GuildWidget do
|
||||
@moduledoc "A guild widget."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :name, :string
|
||||
field :instant_invite, :string
|
||||
field :channels, {:list, :raw}, default: []
|
||||
field :members, {:list, :raw}, default: []
|
||||
field :presence_count, :integer
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.OnboardingPromptOption do
|
||||
@moduledoc "An option within an onboarding prompt."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :channel_ids, {:list, :snowflake}, default: []
|
||||
field :role_ids, {:list, :snowflake}, default: []
|
||||
field :emoji, {:struct, Dexcord.PartialEmoji}
|
||||
field :emoji_id, :snowflake
|
||||
field :emoji_name, :string
|
||||
field :emoji_animated, :boolean
|
||||
field :title, :string
|
||||
field :description, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.OnboardingPrompt do
|
||||
@moduledoc "A guild onboarding prompt."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :type, {:enum, Dexcord.OnboardingPromptType}
|
||||
field :options, {:list, {:struct, Dexcord.OnboardingPromptOption}}, default: []
|
||||
field :title, :string
|
||||
field :single_select, :boolean
|
||||
field :required, :boolean
|
||||
field :in_onboarding, :boolean
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.GuildOnboarding do
|
||||
@moduledoc "A guild's onboarding flow. https://docs.discord.com/developers/resources/guild"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :guild_id, :snowflake
|
||||
field :prompts, {:list, {:struct, Dexcord.OnboardingPrompt}}, default: []
|
||||
field :default_channel_ids, {:list, :snowflake}, default: []
|
||||
field :enabled, :boolean
|
||||
field :mode, {:enum, Dexcord.OnboardingMode}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.IntegrationAccount do
|
||||
@moduledoc "An integration account. Note: `id` is a plain string, NOT a snowflake."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :string
|
||||
field :name, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.IntegrationApplication do
|
||||
@moduledoc "The bot/OAuth2 application associated with an integration."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :name, :string
|
||||
field :icon, :string
|
||||
field :description, :string
|
||||
field :bot, {:struct, Dexcord.User}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Integration do
|
||||
@moduledoc "A guild integration. https://docs.discord.com/developers/resources/guild"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :name, :string
|
||||
field :type, :string
|
||||
field :enabled, :boolean
|
||||
field :syncing, :boolean
|
||||
field :role_id, :snowflake
|
||||
field :enable_emoticons, :boolean
|
||||
field :expire_behavior, {:enum, Dexcord.IntegrationExpireBehavior}
|
||||
field :expire_grace_period, :integer
|
||||
field :user, {:struct, Dexcord.User}
|
||||
field :account, {:struct, Dexcord.IntegrationAccount}
|
||||
field :synced_at, :datetime
|
||||
field :subscriber_count, :integer
|
||||
field :revoked, :boolean
|
||||
field :application, {:struct, Dexcord.IntegrationApplication}
|
||||
field :scopes, {:list, :string}, default: []
|
||||
# Gateway extra from INTEGRATION_CREATE/UPDATE.
|
||||
field :guild_id, :snowflake
|
||||
end
|
||||
end
|
||||
48
lib/dexcord/model/invite.ex
Normal file
48
lib/dexcord/model/invite.ex
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
defmodule Dexcord.Model.InviteShared do
|
||||
@moduledoc false
|
||||
# Field group shared by Dexcord.Invite and Dexcord.InviteMetadata
|
||||
# (InviteMetadata is an Invite plus the extra metadata fields).
|
||||
|
||||
def __included_fields__ do
|
||||
[
|
||||
{:type, {:enum, Dexcord.InviteType}, []},
|
||||
{:code, :string, []},
|
||||
{:guild, {:struct, Dexcord.PartialGuild}, []},
|
||||
{:channel, :raw, []},
|
||||
{:inviter, {:struct, Dexcord.User}, []},
|
||||
{:target_type, {:enum, Dexcord.InviteTargetType}, []},
|
||||
{:target_user, {:struct, Dexcord.User}, []},
|
||||
{:target_application, {:struct, Dexcord.ApplicationInfo}, []},
|
||||
{:approximate_presence_count, :integer, []},
|
||||
{:approximate_member_count, :integer, []},
|
||||
{:expires_at, :datetime, []},
|
||||
{:stage_instance, :raw, []},
|
||||
{:guild_scheduled_event, {:struct, Dexcord.GuildScheduledEvent}, []},
|
||||
{:flags, {:flags, Dexcord.InviteFlags}, []},
|
||||
{:roles, {:list, :raw}, [default: []]}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Invite do
|
||||
@moduledoc "A guild/group invite. https://docs.discord.com/developers/resources/invite"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
include_fields Dexcord.Model.InviteShared
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.InviteMetadata do
|
||||
@moduledoc "An invite plus extra metadata (uses, max_uses, max_age, temporary, created_at)."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
include_fields Dexcord.Model.InviteShared
|
||||
field :uses, :integer
|
||||
field :max_uses, :integer
|
||||
field :max_age, :integer
|
||||
field :temporary, :boolean
|
||||
field :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
|
@ -4,6 +4,7 @@ defmodule Dexcord.Model.MemberShared do
|
|||
|
||||
def __included_fields__ do
|
||||
[
|
||||
{:guild_id, :snowflake, []},
|
||||
{:nick, :string, []},
|
||||
{:avatar, :string, []},
|
||||
{:banner, :string, []},
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ defmodule Dexcord.Message do
|
|||
field :position, :integer
|
||||
field :role_subscription_data, :raw
|
||||
field :resolved, {:struct, Dexcord.ResolvedData}
|
||||
field :poll, :raw
|
||||
field :poll, {:struct, Dexcord.Poll}
|
||||
field :call, {:struct, Dexcord.MessageCall}
|
||||
field :shared_client_theme, :raw
|
||||
end
|
||||
|
|
|
|||
67
lib/dexcord/model/misc.ex
Normal file
67
lib/dexcord/model/misc.ex
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
defmodule Dexcord.FollowedChannel do
|
||||
@moduledoc "A followed announcement channel."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :channel_id, :snowflake
|
||||
field :webhook_id, :snowflake
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.VoiceRegion do
|
||||
@moduledoc "A voice region. https://docs.discord.com/developers/resources/voice"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :string
|
||||
field :name, :string
|
||||
field :optimal, :boolean
|
||||
field :deprecated, :boolean
|
||||
field :custom, :boolean
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Connection do
|
||||
@moduledoc "A user connection to an external service."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :string
|
||||
field :name, :string
|
||||
field :type, :string
|
||||
field :revoked, :boolean
|
||||
field :integrations, :raw
|
||||
field :verified, :boolean
|
||||
field :friend_sync, :boolean
|
||||
field :show_activity, :boolean
|
||||
field :two_way_link, :boolean
|
||||
field :visibility, {:enum, Dexcord.ConnectionVisibility}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Activity do
|
||||
@moduledoc "A presence activity. https://docs.discord.com/developers/topics/gateway-events#activity-object"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :name, :string
|
||||
field :type, {:enum, Dexcord.ActivityType}
|
||||
field :url, :string
|
||||
# Unix milliseconds — NOT an ISO8601 datetime.
|
||||
field :created_at, :integer
|
||||
field :timestamps, :raw
|
||||
field :application_id, :snowflake
|
||||
field :status_display_type, {:enum, Dexcord.StatusDisplayType}
|
||||
field :details, :string
|
||||
field :details_url, :string
|
||||
field :state, :string
|
||||
field :state_url, :string
|
||||
field :emoji, :raw
|
||||
field :party, :raw
|
||||
field :assets, :raw
|
||||
field :secrets, :raw
|
||||
field :instance, :boolean
|
||||
field :flags, {:flags, Dexcord.ActivityFlags}
|
||||
field :buttons, {:list, :string}
|
||||
end
|
||||
end
|
||||
55
lib/dexcord/model/poll.ex
Normal file
55
lib/dexcord/model/poll.ex
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
defmodule Dexcord.PollMedia do
|
||||
@moduledoc "The text/emoji content of a poll question or answer."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :text, :string
|
||||
field :emoji, {:struct, Dexcord.PartialEmoji}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.PollAnswer do
|
||||
@moduledoc "A single poll answer."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :answer_id, :integer
|
||||
field :poll_media, {:struct, Dexcord.PollMedia}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.PollAnswerCount do
|
||||
@moduledoc "The vote tally for one poll answer."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :integer
|
||||
field :count, :integer
|
||||
field :me_voted, :boolean
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.PollResults do
|
||||
@moduledoc "The tallied results of a poll."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :is_finalized, :boolean
|
||||
field :answer_counts, {:list, {:struct, Dexcord.PollAnswerCount}}, default: []
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Poll do
|
||||
@moduledoc "A message poll. https://docs.discord.com/developers/resources/poll"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :question, {:struct, Dexcord.PollMedia}
|
||||
field :answers, {:list, {:struct, Dexcord.PollAnswer}}, default: []
|
||||
field :expiry, :datetime
|
||||
field :allow_multiselect, :boolean
|
||||
field :layout_type, {:enum, Dexcord.PollLayoutType}
|
||||
# Tristate: absence does NOT mean "no votes" — Discord genuinely omits it.
|
||||
field :results, {:struct, Dexcord.PollResults}, tristate: true
|
||||
end
|
||||
end
|
||||
73
lib/dexcord/model/scheduled_event.ex
Normal file
73
lib/dexcord/model/scheduled_event.ex
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
defmodule Dexcord.GuildScheduledEvent.EntityMetadata do
|
||||
@moduledoc "Additional metadata for a guild scheduled event's entity (e.g. external location)."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :location, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.GuildScheduledEvent.NWeekday do
|
||||
@moduledoc "An (n, weekday) pair for a monthly recurrence rule."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :n, :integer
|
||||
field :day, {:enum, Dexcord.RecurrenceRuleWeekday}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.GuildScheduledEvent.RecurrenceRule do
|
||||
@moduledoc "The recurrence rule for a repeating guild scheduled event."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :start, :datetime
|
||||
# The JSON key is "end"; Atom.to_string(:end) == "end", so no special handling.
|
||||
field :end, :datetime
|
||||
field :frequency, {:enum, Dexcord.RecurrenceRuleFrequency}
|
||||
field :interval, :integer
|
||||
field :by_weekday, {:list, {:enum, Dexcord.RecurrenceRuleWeekday}}, default: []
|
||||
field :by_n_weekday, {:list, {:struct, Dexcord.GuildScheduledEvent.NWeekday}}, default: []
|
||||
field :by_month, {:list, {:enum, Dexcord.RecurrenceRuleMonth}}, default: []
|
||||
field :by_month_day, {:list, :integer}, default: []
|
||||
field :by_year_day, {:list, :integer}, default: []
|
||||
field :count, :integer
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.GuildScheduledEvent do
|
||||
@moduledoc "A guild scheduled event. https://docs.discord.com/developers/resources/guild-scheduled-event"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :guild_id, :snowflake
|
||||
field :channel_id, :snowflake
|
||||
field :creator_id, :snowflake
|
||||
field :name, :string
|
||||
field :description, :string
|
||||
field :scheduled_start_time, :datetime
|
||||
field :scheduled_end_time, :datetime
|
||||
field :privacy_level, {:enum, Dexcord.GuildScheduledEventPrivacyLevel}
|
||||
field :status, {:enum, Dexcord.GuildScheduledEventStatus}
|
||||
field :entity_type, {:enum, Dexcord.GuildScheduledEventEntityType}
|
||||
field :entity_id, :snowflake
|
||||
field :entity_metadata, {:struct, Dexcord.GuildScheduledEvent.EntityMetadata}
|
||||
field :creator, {:struct, Dexcord.User}
|
||||
field :user_count, :integer
|
||||
field :image, :string
|
||||
field :recurrence_rule, {:struct, Dexcord.GuildScheduledEvent.RecurrenceRule}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.GuildScheduledEventUser do
|
||||
@moduledoc "A user subscribed to a guild scheduled event."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :guild_scheduled_event_id, :snowflake
|
||||
field :user, {:struct, Dexcord.User}
|
||||
field :member, {:struct, Dexcord.Member}
|
||||
end
|
||||
end
|
||||
24
lib/dexcord/model/team.ex
Normal file
24
lib/dexcord/model/team.ex
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
defmodule Dexcord.TeamMember do
|
||||
@moduledoc "A member of a developer team."
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :membership_state, {:enum, Dexcord.TeamMembershipState}
|
||||
field :team_id, :snowflake
|
||||
field :user, {:struct, Dexcord.User}
|
||||
field :role, :string
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dexcord.Team do
|
||||
@moduledoc "A developer team. https://docs.discord.com/developers/topics/teams"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :icon, :string
|
||||
field :id, :snowflake
|
||||
field :members, {:list, {:struct, Dexcord.TeamMember}}, default: []
|
||||
field :name, :string
|
||||
field :owner_user_id, :snowflake
|
||||
end
|
||||
end
|
||||
19
lib/dexcord/model/webhook.ex
Normal file
19
lib/dexcord/model/webhook.ex
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Dexcord.Webhook do
|
||||
@moduledoc "A webhook. https://docs.discord.com/developers/resources/webhook"
|
||||
use Dexcord.Struct
|
||||
|
||||
discord_struct do
|
||||
field :id, :snowflake
|
||||
field :type, {:enum, Dexcord.WebhookType}
|
||||
field :guild_id, :snowflake
|
||||
field :channel_id, :snowflake
|
||||
field :user, {:struct, Dexcord.User}
|
||||
field :name, :string
|
||||
field :avatar, :string
|
||||
field :token, :string
|
||||
field :application_id, :snowflake
|
||||
field :source_guild, {:struct, Dexcord.PartialGuild}
|
||||
field :source_channel, :raw
|
||||
field :url, :string
|
||||
end
|
||||
end
|
||||
293
test/dexcord/model_resources_test.exs
Normal file
293
test/dexcord/model_resources_test.exs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
defmodule Dexcord.ModelResourcesTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
describe "Dexcord.Webhook (fetched-by-token shape has no user)" do
|
||||
test "decodes a webhook without a user" do
|
||||
map = %{
|
||||
"id" => "100000000000000001",
|
||||
"type" => 1,
|
||||
"guild_id" => "200000000000000002",
|
||||
"channel_id" => "300000000000000003",
|
||||
"name" => "Spidey Bot",
|
||||
"avatar" => "abc123",
|
||||
"token" => "secrettoken",
|
||||
"application_id" => nil
|
||||
}
|
||||
|
||||
assert %Dexcord.Webhook{
|
||||
id: 100_000_000_000_000_001,
|
||||
type: :incoming,
|
||||
guild_id: 200_000_000_000_000_002,
|
||||
channel_id: 300_000_000_000_000_003,
|
||||
name: "Spidey Bot",
|
||||
user: nil,
|
||||
application_id: nil
|
||||
} = Dexcord.Webhook.from_map(map)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Dexcord.Invite and Dexcord.InviteMetadata (shared fields)" do
|
||||
test "invite decodes the shared fields with typed enums" do
|
||||
map = %{
|
||||
"type" => 0,
|
||||
"code" => "abc123",
|
||||
"target_type" => 1,
|
||||
"approximate_member_count" => 42,
|
||||
"flags" => 1
|
||||
}
|
||||
|
||||
assert %Dexcord.Invite{
|
||||
type: :guild,
|
||||
code: "abc123",
|
||||
target_type: :stream,
|
||||
approximate_member_count: 42
|
||||
} = Dexcord.Invite.from_map(map)
|
||||
end
|
||||
|
||||
test "invite metadata carries both invite fields and the metadata fields" do
|
||||
map = %{
|
||||
"code" => "abc123",
|
||||
"type" => 0,
|
||||
"uses" => 5,
|
||||
"max_uses" => 100,
|
||||
"max_age" => 3600,
|
||||
"temporary" => false,
|
||||
"created_at" => "2026-07-05T00:00:00.000000+00:00"
|
||||
}
|
||||
|
||||
meta = Dexcord.InviteMetadata.from_map(map)
|
||||
assert meta.code == "abc123"
|
||||
assert meta.type == :guild
|
||||
assert meta.uses == 5
|
||||
assert meta.max_uses == 100
|
||||
assert meta.max_age == 3600
|
||||
assert meta.temporary == false
|
||||
assert %DateTime{} = meta.created_at
|
||||
end
|
||||
end
|
||||
|
||||
describe "Dexcord.AuditLogEntry (string-typed numeric options)" do
|
||||
test "options.count stays a string (Discord sends these numbers as strings)" do
|
||||
map = %{
|
||||
"id" => "100000000000000001",
|
||||
"target_id" => "200000000000000002",
|
||||
"user_id" => "300000000000000003",
|
||||
"action_type" => 21,
|
||||
"guild_id" => "400000000000000004",
|
||||
"options" => %{
|
||||
"count" => "15",
|
||||
"delete_member_days" => "7",
|
||||
"members_removed" => "4"
|
||||
},
|
||||
"changes" => [
|
||||
%{"key" => "name", "old_value" => "old", "new_value" => "new"}
|
||||
]
|
||||
}
|
||||
|
||||
entry = Dexcord.AuditLogEntry.from_map(map)
|
||||
|
||||
assert %Dexcord.AuditLogEntry{
|
||||
id: 100_000_000_000_000_001,
|
||||
target_id: "200000000000000002",
|
||||
user_id: 300_000_000_000_000_003,
|
||||
action_type: :member_prune,
|
||||
guild_id: 400_000_000_000_000_004
|
||||
} = entry
|
||||
|
||||
assert %Dexcord.AuditLogEntryInfo{
|
||||
count: "15",
|
||||
delete_member_days: "7",
|
||||
members_removed: "4"
|
||||
} = entry.options
|
||||
|
||||
assert [%Dexcord.AuditLogChange{key: "name", old_value: "old", new_value: "new"}] =
|
||||
entry.changes
|
||||
end
|
||||
end
|
||||
|
||||
describe "Dexcord.Poll (results is tristate)" do
|
||||
test "absent results decodes to :absent, not an empty struct" do
|
||||
map = %{
|
||||
"question" => %{"text" => "Best hero?"},
|
||||
"answers" => [
|
||||
%{"answer_id" => 1, "poll_media" => %{"text" => "Spider-Man"}},
|
||||
%{"answer_id" => 2, "poll_media" => %{"text" => "Iron Man"}}
|
||||
],
|
||||
"allow_multiselect" => false,
|
||||
"layout_type" => 1
|
||||
}
|
||||
|
||||
poll = Dexcord.Poll.from_map(map)
|
||||
|
||||
assert %Dexcord.Poll{
|
||||
question: %Dexcord.PollMedia{text: "Best hero?"},
|
||||
layout_type: :default,
|
||||
results: :absent
|
||||
} = poll
|
||||
|
||||
assert [
|
||||
%Dexcord.PollAnswer{
|
||||
answer_id: 1,
|
||||
poll_media: %Dexcord.PollMedia{text: "Spider-Man"}
|
||||
},
|
||||
%Dexcord.PollAnswer{answer_id: 2}
|
||||
] = poll.answers
|
||||
end
|
||||
|
||||
test "present results decodes to a PollResults struct" do
|
||||
map = %{
|
||||
"question" => %{"text" => "Best hero?"},
|
||||
"results" => %{
|
||||
"is_finalized" => true,
|
||||
"answer_counts" => [%{"id" => 1, "count" => 3, "me_voted" => true}]
|
||||
}
|
||||
}
|
||||
|
||||
poll = Dexcord.Poll.from_map(map)
|
||||
|
||||
assert %Dexcord.PollResults{
|
||||
is_finalized: true,
|
||||
answer_counts: [%Dexcord.PollAnswerCount{id: 1, count: 3, me_voted: true}]
|
||||
} = poll.results
|
||||
end
|
||||
end
|
||||
|
||||
describe "Dexcord.GuildScheduledEvent (recurrence rule with enum lists)" do
|
||||
test "recurrence rule decodes by_weekday as an enum list and honors the :end key" do
|
||||
map = %{
|
||||
"id" => "100000000000000001",
|
||||
"guild_id" => "200000000000000002",
|
||||
"name" => "Weekly Standup",
|
||||
"privacy_level" => 2,
|
||||
"status" => 1,
|
||||
"entity_type" => 2,
|
||||
"entity_metadata" => %{"location" => "The Void"},
|
||||
"recurrence_rule" => %{
|
||||
"start" => "2026-07-05T00:00:00.000000+00:00",
|
||||
"end" => "2026-12-05T00:00:00.000000+00:00",
|
||||
"frequency" => 2,
|
||||
"interval" => 1,
|
||||
"by_weekday" => [0, 2, 4],
|
||||
"by_month" => [7]
|
||||
}
|
||||
}
|
||||
|
||||
event = Dexcord.GuildScheduledEvent.from_map(map)
|
||||
|
||||
assert %Dexcord.GuildScheduledEvent{
|
||||
privacy_level: :guild_only,
|
||||
status: :scheduled,
|
||||
entity_type: :voice,
|
||||
entity_metadata: %Dexcord.GuildScheduledEvent.EntityMetadata{location: "The Void"}
|
||||
} = event
|
||||
|
||||
rule = event.recurrence_rule
|
||||
assert %Dexcord.GuildScheduledEvent.RecurrenceRule{frequency: :weekly, interval: 1} = rule
|
||||
assert rule.by_weekday == [:monday, :wednesday, :friday]
|
||||
assert rule.by_month == [:july]
|
||||
assert %DateTime{} = rule.start
|
||||
assert %DateTime{} = Map.fetch!(rule, :end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Dexcord.Integration (string account id)" do
|
||||
test "account.id stays a string, not a snowflake" do
|
||||
map = %{
|
||||
"id" => "100000000000000001",
|
||||
"name" => "Twitch",
|
||||
"type" => "twitch",
|
||||
"enabled" => true,
|
||||
"expire_behavior" => 1,
|
||||
"guild_id" => "200000000000000002",
|
||||
"account" => %{"id" => "twitch_user_123", "name" => "streamer"}
|
||||
}
|
||||
|
||||
integration = Dexcord.Integration.from_map(map)
|
||||
|
||||
assert %Dexcord.Integration{
|
||||
id: 100_000_000_000_000_001,
|
||||
type: "twitch",
|
||||
expire_behavior: :kick,
|
||||
guild_id: 200_000_000_000_000_002,
|
||||
account: %Dexcord.IntegrationAccount{id: "twitch_user_123", name: "streamer"}
|
||||
} = integration
|
||||
end
|
||||
end
|
||||
|
||||
describe "Dexcord.Activity (created_at is unix ms, not a datetime)" do
|
||||
test "created_at stays an integer" do
|
||||
map = %{"name" => "Coding", "type" => 0, "created_at" => 1_720_000_000_000}
|
||||
activity = Dexcord.Activity.from_map(map)
|
||||
|
||||
assert %Dexcord.Activity{name: "Coding", type: :playing, created_at: 1_720_000_000_000} =
|
||||
activity
|
||||
end
|
||||
end
|
||||
|
||||
describe "retypes" do
|
||||
test "Guild.welcome_screen decodes to a WelcomeScreen struct" do
|
||||
map = %{
|
||||
"welcome_screen" => %{
|
||||
"description" => "Welcome!",
|
||||
"welcome_channels" => [
|
||||
%{"channel_id" => "100000000000000001", "description" => "rules"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
guild = Dexcord.Guild.from_map(map)
|
||||
|
||||
assert %Dexcord.WelcomeScreen{
|
||||
description: "Welcome!",
|
||||
welcome_channels: [
|
||||
%Dexcord.WelcomeScreenChannel{channel_id: 100_000_000_000_000_001}
|
||||
]
|
||||
} = guild.welcome_screen
|
||||
end
|
||||
|
||||
test "Guild.guild_scheduled_events decodes to a list of GuildScheduledEvent" do
|
||||
map = %{
|
||||
"guild_scheduled_events" => [
|
||||
%{"id" => "100000000000000001", "name" => "Event", "entity_type" => 3}
|
||||
]
|
||||
}
|
||||
|
||||
guild = Dexcord.Guild.from_map(map)
|
||||
|
||||
assert [%Dexcord.GuildScheduledEvent{entity_type: :external}] =
|
||||
guild.guild_scheduled_events
|
||||
end
|
||||
|
||||
test "Message.poll decodes to a Poll struct" do
|
||||
map = %{"poll" => %{"question" => %{"text" => "Q?"}, "layout_type" => 1}}
|
||||
message = Dexcord.Message.from_map(map)
|
||||
assert %Dexcord.Poll{layout_type: :default} = message.poll
|
||||
end
|
||||
|
||||
test "Member gains guild_id (GUILD_MEMBER_ADD carries it)" do
|
||||
map = %{
|
||||
"guild_id" => "100000000000000001",
|
||||
"user" => %{"id" => "200000000000000002", "username" => "peter"},
|
||||
"nick" => "Spidey"
|
||||
}
|
||||
|
||||
assert %Dexcord.Member{
|
||||
guild_id: 100_000_000_000_000_001,
|
||||
nick: "Spidey",
|
||||
user: %Dexcord.User{username: "peter"}
|
||||
} = Dexcord.Member.from_map(map)
|
||||
end
|
||||
|
||||
test "Thread gains newly_created" do
|
||||
map = %{"id" => "100000000000000001", "type" => 11, "newly_created" => true}
|
||||
assert %Dexcord.Thread{newly_created: true} = Dexcord.Thread.from_map(map)
|
||||
end
|
||||
|
||||
test "ThreadMember gains guild_id" do
|
||||
map = %{"id" => "100000000000000001", "guild_id" => "200000000000000002"}
|
||||
|
||||
assert %Dexcord.ThreadMember{guild_id: 200_000_000_000_000_002} =
|
||||
Dexcord.ThreadMember.from_map(map)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue