Skip to main content

fluxer/model/
mod.rs

1//! Typed structs for everything the Fluxer API returns -- users, guilds,
2//! channels, messages, embeds, and all the gateway event payloads.
3//!
4//! Most fields are `Option<T>` because the API doesn't always include
5//! everything depending on the endpoint.
6
7pub mod voice;
8use serde::{Deserialize, Serialize};
9
10/// All entity IDs in the Fluxer API are snowflake strings.
11pub type Snowflake = String;
12
13/// A Fluxer user account. Both bot and human accounts share this struct.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct User {
16    pub id: Snowflake,
17    #[serde(default)]
18    pub username: String,
19    pub discriminator: Option<String>,
20    pub avatar: Option<String>,
21    pub banner: Option<String>,
22    #[serde(default)]
23    pub bot: Option<bool>,
24    #[serde(default)]
25    pub system: Option<bool>,
26    pub public_flags: Option<u64>,
27    pub premium: Option<u64>,
28}
29
30/// A guild (server). Full data arrives via `GUILD_CREATE`; [`UnavailableGuild`] is used before then.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Guild {
33    pub id: Snowflake,
34    pub name: Option<String>,
35    pub icon: Option<String>,
36    pub banner: Option<String>,
37    pub splash: Option<String>,
38    pub owner_id: Option<Snowflake>,
39    pub afk_channel_id: Option<Snowflake>,
40    /// AFK timeout in seconds.
41    pub afk_timeout: Option<u64>,
42    pub verification_level: Option<u64>,
43    /// 0 = all messages, 1 = only mentions.
44    pub default_message_notifications: Option<u64>,
45    pub explicit_content_filter: Option<u64>,
46    pub roles: Option<Vec<Role>>,
47    pub emojis: Option<Vec<Emoji>>,
48    pub features: Option<Vec<String>>,
49    pub member_count: Option<u64>,
50    pub max_members: Option<u64>,
51    pub description: Option<String>,
52    pub preferred_locale: Option<String>,
53    pub vanity_url_code: Option<String>,
54}
55
56/// A guild member. Wraps a [`User`] with guild-specific info like nickname and roles.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Member {
59    pub user: Option<User>,
60    pub nick: Option<String>,
61    pub avatar: Option<String>,
62    pub roles: Vec<Snowflake>,
63    pub joined_at: String,
64    pub deaf: Option<bool>,
65    pub mute: Option<bool>,
66    pub pending: Option<bool>,
67    pub permissions: Option<String>,
68    /// ISO 8601 timestamp. If set, the member is timed out until then.
69    pub communication_disabled_until: Option<String>,
70}
71
72impl Member {
73    /// Returns the member's nickname if they have one, otherwise their username.
74    pub fn display_name(&self) -> &str {
75        if let Some(nick) = &self.nick {
76            return nick.as_str();
77        }
78        self.user
79            .as_ref()
80            .map(|u| u.username.as_str())
81            .unwrap_or("")
82    }
83}
84
85/// A guild role.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct Role {
88    pub id: Snowflake,
89    pub name: String,
90    /// Integer color value. 0 means no color.
91    pub color: Option<u64>,
92    /// Whether this role shows up separately in the member list.
93    pub hoist: Option<bool>,
94    pub icon: Option<String>,
95    pub position: Option<i64>,
96    /// Permission bitfield.
97    pub permissions: Option<String>,
98    /// Managed roles are created by integrations (bot roles, booster role, etc).
99    pub managed: Option<bool>,
100    pub mentionable: Option<bool>,
101}
102
103/// Custom or standard emoji. Custom emojis have both `name` and `id`,
104/// standard unicode emojis only have `name`.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct Emoji {
107    pub id: Option<Snowflake>,
108    pub name: Option<String>,
109    pub roles: Option<Vec<Snowflake>>,
110    pub user: Option<User>,
111    pub require_colons: Option<bool>,
112    pub managed: Option<bool>,
113    pub animated: Option<bool>,
114    pub available: Option<bool>,
115}
116
117impl Emoji {
118    /// Formats the emoji for use in reaction endpoints. Returns `"name:id"` for
119    /// custom emojis or just the name for unicode ones.
120    pub fn to_reaction_string(&self) -> String {
121        match (&self.name, &self.id) {
122            (Some(name), Some(id)) => format!("{}:{}", name, id),
123            (Some(name), None) => name.clone(),
124            _ => String::new(),
125        }
126    }
127}
128
129/// A channel — text, voice, DM, category, etc. Check `kind` to tell them apart.
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct Channel {
132    pub id: Snowflake,
133    /// Channel type, see [`ChannelType`] for values.
134    #[serde(rename = "type")]
135    pub kind: Option<u64>,
136    pub guild_id: Option<Snowflake>,
137    pub position: Option<i64>,
138    pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
139    pub name: Option<String>,
140    pub topic: Option<String>,
141    pub nsfw: Option<bool>,
142    pub last_message_id: Option<Snowflake>,
143    /// Bits per second, for voice channels.
144    pub bitrate: Option<u64>,
145    /// 0 = unlimited.
146    pub user_limit: Option<u64>,
147    /// Slowmode, in seconds.
148    pub rate_limit_per_user: Option<u64>,
149    /// Recipients for DM/group DM channels.
150    pub recipients: Option<Vec<User>>,
151    pub icon: Option<String>,
152    pub owner_id: Option<Snowflake>,
153    pub parent_id: Option<Snowflake>,
154    pub last_pin_timestamp: Option<String>,
155}
156
157/// Numeric channel type values that map to [`Channel::kind`].
158#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
159#[repr(u8)]
160pub enum ChannelType {
161    Text = 0,
162    Dm = 1,
163    Voice = 2,
164    GroupDm = 3,
165    Category = 4,
166    Announcement = 5,
167    Stage = 13,
168}
169
170/// Permission overwrite for a channel. `kind` is 0 for role, 1 for member.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct PermissionOverwrite {
173    pub id: Snowflake,
174    #[serde(rename = "type")]
175    pub kind: u8,
176    pub allow: Option<String>,
177    pub deny: Option<String>,
178}
179
180/// A message sent in a channel.
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct Message {
183    pub id: Snowflake,
184    pub channel_id: Option<Snowflake>,
185    pub guild_id: Option<Snowflake>,
186    pub author: User,
187    /// Only present in guild messages.
188    pub member: Option<Member>,
189    pub content: Option<String>,
190    pub timestamp: Option<String>,
191    pub edited_timestamp: Option<String>,
192    pub tts: Option<bool>,
193    pub mention_everyone: Option<bool>,
194    pub mentions: Option<Vec<User>>,
195    pub mention_roles: Option<Vec<Snowflake>>,
196    pub attachments: Option<Vec<Attachment>>,
197    pub embeds: Option<Vec<Embed>>,
198    pub reactions: Option<Vec<Reaction>>,
199    pub pinned: Option<bool>,
200    pub webhook_id: Option<Snowflake>,
201    /// 0 = default, 19 = reply, etc.
202    #[serde(rename = "type")]
203    pub kind: Option<u8>,
204    /// Full message object for the message being replied to, if the server includes it.
205    pub referenced_message: Option<Box<Message>>,
206    /// Reference metadata (message/channel/guild IDs) always present on reply messages.
207    pub message_reference: Option<MessageReference>,
208    pub flags: Option<u64>,
209    pub stickers: Option<Vec<serde_json::Value>>,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct PinnedMessage {
214    pub message: Message,
215    pub pinned_at: Option<String>,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct PinsResponse {
220    pub items: Vec<PinnedMessage>,
221    pub has_more: Option<bool>,
222}
223
224/// A file attached to a message.
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct Attachment {
227    pub id: Snowflake,
228    pub filename: Option<String>,
229    pub description: Option<String>,
230    pub content_type: Option<String>,
231    pub size: Option<u64>,
232    pub url: Option<String>,
233    pub proxy_url: Option<String>,
234    pub height: Option<u64>,
235    pub width: Option<u64>,
236    pub ephemeral: Option<bool>,
237}
238
239/// A file to attach when sending a message. Pass one or more of these to
240/// [`Http::send_files`] or [`Http::send_message_with_files`].
241pub struct AttachmentFile {
242    /// Filename shown in the client, e.g. `"image.png"`.
243    pub filename: String,
244    pub data: Vec<u8>,
245    /// MIME type, e.g. `"image/png"`. If `None`, defaults to `"application/octet-stream"`.
246    pub content_type: Option<String>,
247}
248
249/// Rich embed. Use [`EmbedBuilder`] to construct these.
250#[derive(Debug, Clone, Serialize, Deserialize, Default)]
251pub struct Embed {
252    pub title: Option<String>,
253    #[serde(rename = "type")]
254    pub kind: Option<String>,
255    pub description: Option<String>,
256    pub url: Option<String>,
257    pub timestamp: Option<String>,
258    /// Sidebar color as an integer, e.g. `0xFF0000` for red.
259    pub color: Option<u64>,
260    pub footer: Option<EmbedFooter>,
261    pub image: Option<EmbedMedia>,
262    pub thumbnail: Option<EmbedMedia>,
263    pub video: Option<EmbedMedia>,
264    pub author: Option<EmbedAuthor>,
265    pub fields: Option<Vec<EmbedField>>,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct EmbedFooter {
270    pub text: String,
271    pub icon_url: Option<String>,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct EmbedMedia {
276    pub url: String,
277    pub height: Option<u64>,
278    pub width: Option<u64>,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct EmbedAuthor {
283    pub name: String,
284    pub url: Option<String>,
285    pub icon_url: Option<String>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct EmbedField {
290    pub name: String,
291    pub value: String,
292    /// If true, fields can sit side-by-side.
293    #[serde(default)]
294    pub inline: bool,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct Reaction {
299    pub count: u64,
300    pub me: bool,
301    pub emoji: Emoji,
302}
303
304/// Builder for [`Embed`]. Chain methods and call `.build()` at the end.
305///
306/// ```rust
307/// use fluxer::prelude::*;
308///
309/// let embed = EmbedBuilder::new()
310///     .title("Hello")
311///     .description("World")
312///     .color(0x00FF00)
313///     .build();
314/// ```
315#[derive(Debug, Default)]
316pub struct EmbedBuilder(Embed);
317
318impl EmbedBuilder {
319    pub fn new() -> Self {
320        Self::default()
321    }
322
323    pub fn title(mut self, title: impl Into<String>) -> Self {
324        self.0.title = Some(title.into());
325        self
326    }
327    pub fn description(mut self, desc: impl Into<String>) -> Self {
328        self.0.description = Some(desc.into());
329        self
330    }
331    pub fn url(mut self, url: impl Into<String>) -> Self {
332        self.0.url = Some(url.into());
333        self
334    }
335    pub fn color(mut self, color: u64) -> Self {
336        self.0.color = Some(color);
337        self
338    }
339    pub fn timestamp(mut self, ts: impl Into<String>) -> Self {
340        self.0.timestamp = Some(ts.into());
341        self
342    }
343    pub fn footer(mut self, text: impl Into<String>, icon_url: Option<String>) -> Self {
344        self.0.footer = Some(EmbedFooter { text: text.into(), icon_url });
345        self
346    }
347    pub fn image(mut self, url: impl Into<String>) -> Self {
348        self.0.image = Some(EmbedMedia { url: url.into(), height: None, width: None });
349        self
350    }
351    pub fn thumbnail(mut self, url: impl Into<String>) -> Self {
352        self.0.thumbnail = Some(EmbedMedia { url: url.into(), height: None, width: None });
353        self
354    }
355    pub fn author(mut self, name: impl Into<String>, url: Option<String>, icon_url: Option<String>) -> Self {
356        self.0.author = Some(EmbedAuthor { name: name.into(), url, icon_url });
357        self
358    }
359    pub fn field(mut self, name: impl Into<String>, value: impl Into<String>, inline: bool) -> Self {
360        let fields = self.0.fields.get_or_insert_with(Vec::new);
361        fields.push(EmbedField { name: name.into(), value: value.into(), inline });
362        self
363    }
364    pub fn build(self) -> Embed {
365        self.0
366    }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct Invite {
371    pub code: String,
372    pub guild: Option<PartialGuild>,
373    pub channel: Option<PartialChannel>,
374    pub inviter: Option<User>,
375    pub target_user: Option<User>,
376    pub approximate_member_count: Option<u64>,
377    pub expires_at: Option<String>,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct PartialGuild {
382    pub id: Snowflake,
383    pub name: String,
384    pub icon: Option<String>,
385    pub splash: Option<String>,
386    pub banner: Option<String>,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct PartialChannel {
391    pub id: Snowflake,
392    pub name: Option<String>,
393    #[serde(rename = "type")]
394    pub kind: Option<u8>,
395}
396
397/// A webhook. Use [`Http::execute_webhook`](crate::http::Http::execute_webhook) to post through one.
398#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct Webhook {
400    pub id: Snowflake,
401    #[serde(rename = "type")]
402    pub kind: Option<u8>,
403    pub guild_id: Option<Snowflake>,
404    pub channel_id: Option<Snowflake>,
405    pub user: Option<User>,
406    pub name: Option<String>,
407    pub avatar: Option<String>,
408    pub token: Option<String>,
409    pub url: Option<String>,
410}
411
412// --- Gateway event payloads ---
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct TypingStart {
416    pub channel_id: Option<Snowflake>,
417    pub guild_id: Option<Snowflake>,
418    pub user_id: Snowflake,
419    /// Unix timestamp in seconds.
420    pub timestamp: u64,
421    pub member: Option<Member>,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct ReactionAdd {
426    pub user_id: Snowflake,
427    pub channel_id: Option<Snowflake>,
428    pub message_id: Snowflake,
429    pub guild_id: Option<Snowflake>,
430    pub member: Option<Member>,
431    pub emoji: Emoji,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct ReactionRemove {
436    pub user_id: Snowflake,
437    pub channel_id: Option<Snowflake>,
438    pub message_id: Snowflake,
439    pub guild_id: Option<Snowflake>,
440    pub emoji: Emoji,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct ReactionRemoveAll {
445    pub channel_id: Option<Snowflake>,
446    pub message_id: Snowflake,
447    pub guild_id: Option<Snowflake>,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct ReactionRemoveEmoji {
452    pub channel_id: Option<Snowflake>,
453    pub guild_id: Option<Snowflake>,
454    pub message_id: Snowflake,
455    pub emoji: Emoji,
456}
457
458/// Partial message data from an edit. Only changed fields are populated.
459#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct MessageUpdate {
461    pub id: Snowflake,
462    pub channel_id: Option<Snowflake>,
463    pub guild_id: Option<Snowflake>,
464    pub author: Option<User>,
465    pub content: Option<String>,
466    pub edited_timestamp: Option<String>,
467    pub embeds: Option<Vec<Embed>>,
468    pub attachments: Option<Vec<Attachment>>,
469    pub pinned: Option<bool>,
470    pub flags: Option<u64>,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
474pub struct MessageDelete {
475    pub id: Snowflake,
476    pub channel_id: Option<Snowflake>,
477    pub guild_id: Option<Snowflake>,
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct MessageDeleteBulk {
482    pub ids: Vec<Snowflake>,
483    pub channel_id: Option<Snowflake>,
484    pub guild_id: Option<Snowflake>,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct GuildMemberAdd {
489    pub guild_id: Snowflake,
490    #[serde(flatten)]
491    pub member: Member,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
495pub struct GuildMemberRemove {
496    pub guild_id: Snowflake,
497    pub user: User,
498}
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct GuildMemberUpdate {
502    pub guild_id: Snowflake,
503    pub roles: Vec<Snowflake>,
504    pub user: User,
505    pub nick: Option<String>,
506    pub joined_at: Option<String>,
507    pub pending: Option<bool>,
508    pub communication_disabled_until: Option<String>,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
512pub struct Ban {
513    pub reason: Option<String>,
514    pub user: User,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct GuildBanAdd {
519    pub guild_id: Snowflake,
520    pub user: User,
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct GuildBanRemove {
525    pub guild_id: Snowflake,
526    pub user: User,
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
530pub struct GuildRoleCreate {
531    pub guild_id: Snowflake,
532    pub role: Role,
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct GuildRoleUpdate {
537    pub guild_id: Snowflake,
538    pub role: Role,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct GuildRoleDelete {
543    pub guild_id: Snowflake,
544    pub role_id: Snowflake,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
548pub struct ChannelDelete {
549    pub id: Snowflake,
550    #[serde(rename = "type")]
551    pub kind: Option<u8>,
552    pub guild_id: Option<Snowflake>,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct ChannelPinsUpdate {
557    pub guild_id: Option<Snowflake>,
558    pub channel_id: Option<Snowflake>,
559    pub last_pin_timestamp: Option<String>,
560}
561
562/// Received once after connecting. Contains the bot user, session info for
563/// resuming, and the initial guild list.
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub struct Ready {
566    pub v: Option<u64>,
567    pub session_id: String,
568    pub resume_gateway_url: Option<String>,
569    pub user: User,
570    /// Guilds come in as unavailable first; full data arrives via GUILD_CREATE events.
571    pub guilds: Option<Vec<UnavailableGuild>>,
572    /// `[shard_id, num_shards]`
573    pub shard: Option<[u64; 2]>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct UnavailableGuild {
578    pub id: Snowflake,
579    /// `true` = temporary outage, `false`/absent = bot was removed.
580    pub unavailable: Option<bool>,
581}
582
583#[derive(Debug, Deserialize)]
584pub struct GatewayBotResponse {
585    pub url: String,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct GuildEmojisUpdate {
590    pub guild_id: Snowflake,
591    pub emojis: Vec<serde_json::Value>,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct GuildStickersUpdate {
596    pub guild_id: Snowflake,
597    pub stickers: Vec<serde_json::Value>,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct GuildRoleUpdateBulk {
602    pub guild_id: Snowflake,
603    pub roles: Vec<serde_json::Value>,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
607pub struct ChannelUpdateBulk {
608    pub guild_id: Option<Snowflake>,
609    pub channels: Vec<serde_json::Value>,
610}
611
612#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct InviteCreate {
614    pub channel_id: Option<Snowflake>,
615    pub guild_id: Option<Snowflake>,
616    pub code: Option<String>,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
620pub struct InviteDelete {
621    pub channel_id: Option<Snowflake>,
622    pub guild_id: Option<Snowflake>,
623    pub code: String,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct WebhooksUpdate {
628    pub channel_id: Snowflake,
629    pub guild_id: Option<Snowflake>,
630}
631
632/// Full guild state sync, sent in response to an op 14 subscription request.
633/// Contains the same data as GUILD_CREATE but for already-known guilds.
634#[derive(Debug, Clone, Serialize, Deserialize)]
635pub struct GuildSync {
636    pub id: Snowflake,
637    pub roles: Option<Vec<Role>>,
638    pub channels: Option<Vec<Channel>>,
639    pub emojis: Option<Vec<Emoji>>,
640    pub stickers: Option<Vec<serde_json::Value>>,
641    pub members: Option<Vec<Member>>,
642    pub member_count: Option<u64>,
643    pub online_count: Option<u64>,
644    pub presences: Option<Vec<serde_json::Value>>,
645    pub voice_states: Option<Vec<serde_json::Value>>,
646    pub joined_at: Option<String>,
647    pub unavailable: Option<bool>,
648}
649
650/// Custom status set by a user (the emoji + text shown under their name).
651#[derive(Debug, Clone, Serialize, Deserialize)]
652pub struct CustomStatus {
653    pub text: Option<String>,
654    pub emoji_id: Option<Snowflake>,
655    pub emoji_name: Option<String>,
656    pub expires_at: Option<String>,
657}
658
659/// Fired when a user's presence (status/activity) changes in a guild.
660#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct PresenceUpdate {
662    pub user: User,
663    pub guild_id: Option<Snowflake>,
664    /// `"online"`, `"idle"`, `"dnd"`, or `"offline"`.
665    pub status: Option<String>,
666    pub mobile: Option<bool>,
667    pub afk: Option<bool>,
668    pub custom_status: Option<CustomStatus>,
669    pub activities: Option<Vec<serde_json::Value>>,
670    pub client_status: Option<serde_json::Value>,
671}
672
673/// Multiple presence updates batched together. `guild_id` is set when the
674/// presences all belong to the same guild.
675#[derive(Debug, Clone, Serialize, Deserialize)]
676pub struct PresenceUpdateBulk {
677    pub presences: Vec<PresenceUpdate>,
678    pub guild_id: Option<Snowflake>,
679}
680
681/// Fired when the current user's settings change. Structure varies; use
682/// the raw `data` field to access specific setting keys.
683#[derive(Debug, Clone, Serialize, Deserialize)]
684pub struct UserSettingsUpdate {
685    #[serde(flatten)]
686    pub data: serde_json::Value,
687}
688
689/// Partial user fields sent with USER_UPDATE.
690#[derive(Debug, Clone, Serialize, Deserialize)]
691pub struct UserUpdate {
692    pub id: Snowflake,
693    pub username: String,
694    pub discriminator: Option<String>,
695    pub avatar: Option<String>,
696    pub flags: Option<u64>,
697}
698
699/// Fired when a channel is marked as read from another session.
700#[derive(Debug, Clone, Serialize, Deserialize)]
701pub struct MessageAck {
702    pub channel_id: Option<Snowflake>,
703    pub message_id: Option<Snowflake>,
704}
705
706/// A single session entry inside [`SessionsReplace`].
707#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct SessionEntry {
709    pub session_id: String,
710    /// `"online"`, `"idle"`, `"dnd"`, `"invisible"`, or `"offline"`.
711    pub status: Option<String>,
712    pub mobile: Option<bool>,
713    pub afk: Option<bool>,
714}
715
716/// Replaces all active sessions for the current user. Sent when another
717/// device connects or changes presence.
718#[derive(Debug, Clone, Serialize, Deserialize)]
719pub struct SessionsReplace(pub Vec<SessionEntry>);
720
721/// Relationship types from the Fluxer source (`RelationshipTypes` enum).
722#[repr(u8)]
723#[derive(Debug, Clone, Copy, PartialEq, Eq)]
724pub enum RelationshipType {
725    Friend = 1,
726    Blocked = 2,
727    IncomingRequest = 3,
728    OutgoingRequest = 4,
729}
730
731/// Fired when a relationship is added (friend accepted, request sent, block added, etc).
732#[derive(Debug, Clone, Serialize, Deserialize)]
733pub struct RelationshipAdd {
734    /// The other user's ID.
735    pub id: Snowflake,
736    /// See [`RelationshipType`] for values.
737    #[serde(rename = "type")]
738    pub kind: u8,
739}
740
741/// Fired when an existing relationship changes type (e.g. pending → friend).
742#[derive(Debug, Clone, Serialize, Deserialize)]
743pub struct RelationshipUpdate {
744    pub id: Snowflake,
745    #[serde(rename = "type")]
746    pub kind: u8,
747}
748
749/// Fired when a relationship is removed (unfriend, unblock, request cancelled).
750#[derive(Debug, Clone, Serialize, Deserialize)]
751pub struct RelationshipRemove {
752    pub id: Snowflake,
753}
754
755/// Voice state for a participant inside a DM/group call.
756#[derive(Debug, Clone, Serialize, Deserialize)]
757pub struct CallVoiceState {
758    pub user_id: Snowflake,
759    pub channel_id: Option<Snowflake>,
760    pub session_id: Option<String>,
761    pub self_mute: Option<bool>,
762    pub self_deaf: Option<bool>,
763    pub self_video: Option<bool>,
764    pub self_stream: Option<bool>,
765}
766
767/// A VC was started in a DM or group DM.
768#[derive(Debug, Clone, Serialize, Deserialize)]
769pub struct CallCreate {
770    pub channel_id: Snowflake,
771    pub message_id: Option<Snowflake>,
772    pub region: Option<String>,
773    pub voice_states: Option<Vec<CallVoiceState>>,
774    /// User IDs of people currently being rung.
775    pub ringing: Option<Vec<Snowflake>>,
776}
777
778/// A call's state changed (someone joined/left, region changed, etc).
779#[derive(Debug, Clone, Serialize, Deserialize)]
780pub struct CallUpdate {
781    pub channel_id: Snowflake,
782    pub message_id: Option<Snowflake>,
783    pub region: Option<String>,
784    pub voice_states: Option<Vec<CallVoiceState>>,
785    pub ringing: Option<Vec<Snowflake>>,
786}
787
788/// A VC ended or was declined.
789#[derive(Debug, Clone, Serialize, Deserialize)]
790pub struct CallDelete {
791    pub channel_id: Snowflake,
792}
793
794/// A user was added to a group DM channel.
795#[derive(Debug, Clone, Serialize, Deserialize)]
796pub struct ChannelRecipientAdd {
797    pub channel_id: Snowflake,
798    pub user: User,
799}
800
801/// A user was removed from a group DM channel.
802#[derive(Debug, Clone, Serialize, Deserialize)]
803pub struct ChannelRecipientRemove {
804    pub channel_id: Snowflake,
805    pub user: User,
806}
807
808/// A batch of reactions added to a single message (server side debounce).
809#[derive(Debug, Clone, Serialize, Deserialize)]
810pub struct MessageReactionAddMany {
811    pub channel_id: Option<Snowflake>,
812    pub message_id: Snowflake,
813    pub guild_id: Option<Snowflake>,
814    /// Each entry has `user_id`, `emoji` (`id`/`name`), and optionally `member`.
815    pub reactions: Vec<serde_json::Value>,
816}
817
818/// Lazy passive guild update: channel last-message IDs, voice states, and
819/// channel create/update/delete diffs. Sent for large guilds.
820#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct PassiveUpdates {
822    pub guild_id: Snowflake,
823    /// Map of `channel_id → last_message_id`.
824    pub channels: Option<serde_json::Value>,
825    pub voice_states: Option<Vec<serde_json::Value>>,
826    pub created_channels: Option<Vec<Channel>>,
827    pub updated_channels: Option<Vec<Channel>>,
828    pub deleted_channel_ids: Option<Vec<Snowflake>>,
829}
830
831/// An op inside a [`GuildMemberListUpdate`].
832#[derive(Debug, Clone, Serialize, Deserialize)]
833pub struct MemberListOp {
834    /// `"SYNC"`, `"INSERT"`, `"UPDATE"`, `"DELETE"`, or `"INVALIDATE"`.
835    pub op: String,
836    /// `[start, end]` range, present for SYNC and INVALIDATE.
837    pub range: Option<[u64; 2]>,
838    /// Item index, present for INSERT / UPDATE / DELETE.
839    pub index: Option<u64>,
840    /// Batch of items for SYNC.
841    pub items: Option<Vec<serde_json::Value>>,
842    /// Single item for INSERT / UPDATE / DELETE.
843    pub item: Option<serde_json::Value>,
844}
845
846/// Incremental member list update for a guild channel
847#[derive(Debug, Clone, Serialize, Deserialize)]
848pub struct GuildMemberListUpdate {
849    pub guild_id: Snowflake,
850    /// List identifier (usually matches `channel_id`).
851    pub id: String,
852    pub channel_id: Option<Snowflake>,
853    pub member_count: Option<u64>,
854    pub online_count: Option<u64>,
855    pub groups: Option<Vec<serde_json::Value>>,
856    pub ops: Vec<MemberListOp>,
857}
858
859/// Response to REQUEST_GUILD_MEMBERS (op 8). Contains a chunk of guild members.
860#[derive(Debug, Clone, Serialize, Deserialize)]
861pub struct GuildMembersChunk {
862    pub guild_id: Snowflake,
863    pub members: Vec<Member>,
864    pub chunk_index: u32,
865    pub chunk_count: u32,
866    pub not_found: Option<Vec<Snowflake>>,
867    pub presences: Option<Vec<serde_json::Value>>,
868    pub nonce: Option<String>,
869}
870
871/// A user's voice state changed (joined/left/muted/deafened in a guild voice channel).
872#[derive(Debug, Clone, Serialize, Deserialize)]
873pub struct VoiceStateUpdate {
874    pub user_id: Snowflake,
875    pub guild_id: Option<Snowflake>,
876    pub channel_id: Option<Snowflake>,
877    pub session_id: Option<String>,
878    pub connection_id: Option<String>,
879    pub self_mute: Option<bool>,
880    pub self_deaf: Option<bool>,
881    pub self_video: Option<bool>,
882    pub self_stream: Option<bool>,
883    /// Server-side mute (set by a moderator).
884    pub mute: Option<bool>,
885    /// Server-side deafen (set by a moderator).
886    pub deaf: Option<bool>,
887    pub is_mobile: Option<bool>,
888    pub member: Option<Member>,
889    pub viewer_stream_keys: Option<Vec<String>>,
890    pub version: Option<u64>,
891}
892
893#[derive(Debug, Clone, Serialize, Deserialize)]
894pub struct VoiceServerUpdate {
895    pub token: String,
896    pub guild_id: Option<Snowflake>,
897    pub endpoint: Option<String>,
898}
899
900#[derive(Debug, Clone, Serialize, Deserialize)]
901pub struct Resumed {
902    #[serde(flatten)]
903    pub data: serde_json::Value,
904}
905
906#[derive(Debug, Clone, Serialize, Deserialize)]
907pub struct ChannelPinsAck {
908    pub channel_id: Snowflake,
909    pub timestamp: Option<String>,
910}
911
912#[derive(Debug, Clone, Serialize, Deserialize)]
913pub struct UserPinnedDmsUpdate {
914    pub pinned: Vec<Snowflake>,
915}
916
917#[derive(Debug, Clone, Serialize, Deserialize)]
918pub struct UserNoteUpdate {
919    pub id: Snowflake,
920    pub note: String,
921}
922
923#[derive(Debug, Clone, Serialize, Deserialize)]
924pub struct UserConnectionsUpdate {
925    #[serde(flatten)]
926    pub data: serde_json::Value,
927}
928
929#[derive(Debug, Clone, Serialize, Deserialize)]
930pub struct UserGuildSettingsUpdate {
931    pub guild_id: Option<Snowflake>,
932    #[serde(flatten)]
933    pub settings: serde_json::Value,
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize)]
937pub struct AuthSessionChange {
938    #[serde(flatten)]
939    pub data: serde_json::Value,
940}
941
942#[derive(Debug, Clone, Serialize, Deserialize)]
943pub struct SavedMessageCreate {
944    pub message: Message,
945}
946
947#[derive(Debug, Clone, Serialize, Deserialize)]
948pub struct SavedMessageDelete {
949    pub message_id: Snowflake,
950}
951
952#[derive(Debug, Clone, Serialize, Deserialize)]
953pub struct RecentMentionDelete {
954    pub message_id: Snowflake,
955}
956
957// --- Request payloads ---
958
959/// Attachment metadata entry for the `attachments` field of [`MessageCreatePayload`].
960/// `id` must match the index used in the corresponding `files[N]` multipart part.
961#[derive(Debug, Clone, Serialize)]
962pub struct AttachmentMetadata {
963    pub id: u64,
964    pub filename: String,
965    #[serde(skip_serializing_if = "Option::is_none")]
966    pub description: Option<String>,
967}
968
969/// Payload for sending/editing messages. All fields optional; only set what you need.
970#[derive(Debug, Clone, Serialize, Default)]
971pub struct MessageCreatePayload {
972    #[serde(skip_serializing_if = "Option::is_none")]
973    pub content: Option<String>,
974    #[serde(skip_serializing_if = "Option::is_none")]
975    pub tts: Option<bool>,
976    #[serde(skip_serializing_if = "Option::is_none")]
977    pub embeds: Option<Vec<Embed>>,
978    #[serde(skip_serializing_if = "Option::is_none")]
979    pub flags: Option<u64>,
980    #[serde(skip_serializing_if = "Option::is_none")]
981    pub message_reference: Option<MessageReference>,
982    #[serde(skip_serializing_if = "Option::is_none")]
983    pub referenced_message_id: Option<Snowflake>,
984    /// Required when uploading files. Populated automatically by
985    /// [`Http::send_message_with_files`]; you don't need to set this manually.
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub attachments: Option<Vec<AttachmentMetadata>>,
988}
989
990#[derive(Debug, Clone, Serialize, Deserialize)]
991pub struct MessageReference {
992    pub message_id: Snowflake,
993    pub channel_id: Option<Snowflake>,
994    pub guild_id: Option<Snowflake>,
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub fail_if_not_exists: Option<bool>,
997}
998
999#[derive(Debug, Clone, Serialize, Default)]
1000pub struct ChannelCreatePayload {
1001    pub name: String,
1002    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
1003    pub kind: Option<u8>,
1004    #[serde(skip_serializing_if = "Option::is_none")]
1005    pub topic: Option<String>,
1006    #[serde(skip_serializing_if = "Option::is_none")]
1007    pub bitrate: Option<u64>,
1008    #[serde(skip_serializing_if = "Option::is_none")]
1009    pub user_limit: Option<u64>,
1010    #[serde(skip_serializing_if = "Option::is_none")]
1011    pub rate_limit_per_user: Option<u64>,
1012    #[serde(skip_serializing_if = "Option::is_none")]
1013    pub position: Option<i64>,
1014    #[serde(skip_serializing_if = "Option::is_none")]
1015    pub parent_id: Option<Snowflake>,
1016    #[serde(skip_serializing_if = "Option::is_none")]
1017    pub nsfw: Option<bool>,
1018}
1019
1020/// For nullable fields like `nick`, use `Some(None)` to clear them.
1021#[derive(Debug, Clone, Serialize, Default)]
1022pub struct EditMemberPayload {
1023    #[serde(skip_serializing_if = "Option::is_none")]
1024    pub nick: Option<Option<String>>,
1025    #[serde(skip_serializing_if = "Option::is_none")]
1026    pub roles: Option<Vec<Snowflake>>,
1027    #[serde(skip_serializing_if = "Option::is_none")]
1028    pub mute: Option<bool>,
1029    #[serde(skip_serializing_if = "Option::is_none")]
1030    pub deaf: Option<bool>,
1031    #[serde(skip_serializing_if = "Option::is_none")]
1032    pub channel_id: Option<Option<Snowflake>>,
1033    #[serde(skip_serializing_if = "Option::is_none")]
1034    pub communication_disabled_until: Option<Option<String>>,
1035}
1036
1037#[derive(Debug, Clone, Serialize, Default)]
1038pub struct CreateRolePayload {
1039    pub name: String,
1040    #[serde(skip_serializing_if = "Option::is_none")]
1041    pub permissions: Option<String>,
1042    #[serde(skip_serializing_if = "Option::is_none")]
1043    pub color: Option<u64>,
1044    #[serde(skip_serializing_if = "Option::is_none")]
1045    pub hoist: Option<bool>,
1046    #[serde(skip_serializing_if = "Option::is_none")]
1047    pub mentionable: Option<bool>,
1048}
1049
1050#[derive(Debug, Clone, Serialize, Default)]
1051pub struct EditRolePayload {
1052    #[serde(skip_serializing_if = "Option::is_none")]
1053    pub name: Option<String>,
1054    #[serde(skip_serializing_if = "Option::is_none")]
1055    pub permissions: Option<String>,
1056    #[serde(skip_serializing_if = "Option::is_none")]
1057    pub color: Option<u64>,
1058    #[serde(skip_serializing_if = "Option::is_none")]
1059    pub hoist: Option<bool>,
1060    #[serde(skip_serializing_if = "Option::is_none")]
1061    pub mentionable: Option<bool>,
1062}
1063
1064#[derive(Debug, Clone, Serialize, Default)]
1065pub struct CreateInvitePayload {
1066    /// Seconds. 0 = never expires.
1067    #[serde(skip_serializing_if = "Option::is_none")]
1068    pub max_age: Option<u64>,
1069    /// 0 = unlimited.
1070    #[serde(skip_serializing_if = "Option::is_none")]
1071    pub max_uses: Option<u64>,
1072    /// If true, members joined via this invite get kicked when they disconnect
1073    /// (unless they've been given a role).
1074    #[serde(skip_serializing_if = "Option::is_none")]
1075    pub temporary: Option<bool>,
1076    #[serde(skip_serializing_if = "Option::is_none")]
1077    pub unique: Option<bool>,
1078}
1079
1080/// Query params for fetching messages. Only set one of `before`/`after`/`around`.
1081#[derive(Debug, Clone, Default)]
1082pub struct GetMessagesQuery {
1083    /// 1-100.
1084    pub limit: Option<u8>,
1085    pub before: Option<Snowflake>,
1086    pub after: Option<Snowflake>,
1087    pub around: Option<Snowflake>,
1088}
1089
1090impl GetMessagesQuery {
1091    pub fn to_query_string(&self) -> String {
1092        let mut parts = Vec::new();
1093        if let Some(l) = self.limit {
1094            parts.push(format!("limit={}", l.min(100)));
1095        }
1096        if let Some(ref b) = self.before {
1097            parts.push(format!("before={}", b));
1098        }
1099        if let Some(ref a) = self.after {
1100            parts.push(format!("after={}", a));
1101        }
1102        if let Some(ref ar) = self.around {
1103            parts.push(format!("around={}", ar));
1104        }
1105        if parts.is_empty() {
1106            String::new()
1107        } else {
1108            format!("?{}", parts.join("&"))
1109        }
1110    }
1111}
1112
1113#[derive(Debug, Clone, Serialize, Default)]
1114pub struct EditGuildPayload {
1115    #[serde(skip_serializing_if = "Option::is_none")]
1116    pub name: Option<String>,
1117    #[serde(skip_serializing_if = "Option::is_none")]
1118    pub description: Option<String>,
1119    #[serde(skip_serializing_if = "Option::is_none")]
1120    pub preferred_locale: Option<String>,
1121    #[serde(skip_serializing_if = "Option::is_none")]
1122    pub afk_channel_id: Option<Option<Snowflake>>,
1123    #[serde(skip_serializing_if = "Option::is_none")]
1124    pub afk_timeout: Option<u64>,
1125    #[serde(skip_serializing_if = "Option::is_none")]
1126    pub verification_level: Option<u64>,
1127    #[serde(skip_serializing_if = "Option::is_none")]
1128    pub default_message_notifications: Option<u64>,
1129    #[serde(skip_serializing_if = "Option::is_none")]
1130    pub explicit_content_filter: Option<u64>,
1131}
1132
1133/// Query payload for [`Http::search_messages`]. All fields are optional — set
1134/// only the filters you need. `scope` defaults to `"current"` server side.
1135#[derive(Debug, Clone, Serialize, Default)]
1136pub struct SearchMessagesQuery {
1137    /// Results per page, 1–25.
1138    #[serde(skip_serializing_if = "Option::is_none")]
1139    pub hits_per_page: Option<u8>,
1140    #[serde(skip_serializing_if = "Option::is_none")]
1141    pub page: Option<u32>,
1142    /// Full-text content search.
1143    #[serde(skip_serializing_if = "Option::is_none")]
1144    pub content: Option<String>,
1145    /// Restrict to these channel IDs.
1146    #[serde(skip_serializing_if = "Option::is_none")]
1147    pub channel_id: Option<Vec<Snowflake>>,
1148    /// Restrict to messages by these author IDs.
1149    #[serde(skip_serializing_if = "Option::is_none")]
1150    pub author_id: Option<Vec<Snowflake>>,
1151    /// Filter by attachment/embed type: `"image"`, `"video"`, `"file"`,
1152    /// `"sticker"`, `"embed"`, `"link"`, `"poll"`.
1153    #[serde(skip_serializing_if = "Option::is_none")]
1154    pub has: Option<Vec<String>>,
1155    #[serde(skip_serializing_if = "Option::is_none")]
1156    pub pinned: Option<bool>,
1157    /// `"timestamp"` or `"relevance"`.
1158    #[serde(skip_serializing_if = "Option::is_none")]
1159    pub sort_by: Option<String>,
1160    /// `"asc"` or `"desc"`.
1161    #[serde(skip_serializing_if = "Option::is_none")]
1162    pub sort_order: Option<String>,
1163    /// `"current"`, `"open_dms"`, `"all_dms"`, `"all_guilds"`, or `"all"`.
1164    #[serde(skip_serializing_if = "Option::is_none")]
1165    pub scope: Option<String>,
1166}
1167
1168/// Returned by [`Http::search_messages`].
1169#[derive(Debug, Clone, Deserialize)]
1170pub struct SearchMessagesResponse {
1171    pub messages: Vec<Message>,
1172    pub total: u64,
1173    pub hits_per_page: u8,
1174    pub page: u32,
1175}
1176
1177/// Audit log entry from the gateway.
1178#[derive(Debug, Clone, Serialize, Deserialize)]
1179pub struct GuildAuditLogEntryCreate {
1180    pub id: Snowflake,
1181    pub guild_id: Snowflake,
1182    pub action_type: Option<u64>,
1183    pub user_id: Option<Snowflake>,
1184    pub target_id: Option<Snowflake>,
1185    pub reason: Option<String>,
1186    pub changes: Option<Vec<AuditLogChange>>,
1187    /// Extra context depending on the action e.g. channel name/type for channel creates.
1188    pub options: Option<serde_json::Value>,
1189}
1190
1191/// A single changed field inside an audit log entry.
1192#[derive(Debug, Clone, Serialize, Deserialize)]
1193pub struct AuditLogChange {
1194    pub key: String,
1195    pub new_value: Option<serde_json::Value>,
1196    pub old_value: Option<serde_json::Value>,
1197}
1198
1199/// A single entry for [`Http::ack_bulk`]. Marks `message_id` as the last-read
1200/// message in `channel_id`.
1201#[derive(Debug, Clone, Serialize)]
1202pub struct ReadStateAck {
1203    pub channel_id: Snowflake,
1204    pub message_id: Snowflake,
1205}
1206
1207#[derive(Debug, Clone, Serialize, Default)]
1208pub struct WebhookExecutePayload {
1209    #[serde(skip_serializing_if = "Option::is_none")]
1210    pub content: Option<String>,
1211    /// Override the webhook's name for this message.
1212    #[serde(skip_serializing_if = "Option::is_none")]
1213    pub username: Option<String>,
1214    /// Override the webhook's avatar for this message.
1215    #[serde(skip_serializing_if = "Option::is_none")]
1216    pub avatar_url: Option<String>,
1217    #[serde(skip_serializing_if = "Option::is_none")]
1218    pub tts: Option<bool>,
1219    #[serde(skip_serializing_if = "Option::is_none")]
1220    pub embeds: Option<Vec<Embed>>,
1221    /// Reply to a message in the same channel. Only `message_id` is needed.
1222    #[serde(skip_serializing_if = "Option::is_none")]
1223    pub message_reference: Option<WebhookMessageReference>,
1224}
1225
1226/// Message to reply to when executing a webhook.
1227#[derive(Debug, Clone, Serialize, Default)]
1228pub struct WebhookMessageReference {
1229    pub message_id: Snowflake,
1230}
1231
1232#[derive(Debug, Clone, Serialize, Default)]
1233pub struct WebhookEditPayload {
1234    #[serde(skip_serializing_if = "Option::is_none")]
1235    pub content: Option<String>,
1236    #[serde(skip_serializing_if = "Option::is_none")]
1237    pub embeds: Option<Vec<Embed>>,
1238}