use std::collections::HashMap; use serde::{Deserialize, Deserializer, Serialize}; #[derive(Serialize)] pub struct IdentityInfo { pub pubkey: String, pub display_name: String, } #[derive(Serialize, Deserialize)] pub struct ProfileInfo { pub pubkey: String, pub display_name: Option, pub avatar_url: Option, pub about: Option, pub nip05_handle: Option, pub owner_pubkey: Option, } #[derive(Serialize, Deserialize)] pub struct UserProfileSummaryInfo { pub display_name: Option, pub avatar_url: Option, pub nip05_handle: Option, pub owner_pubkey: Option, #[serde(default)] pub is_agent: bool, } #[derive(Serialize, Deserialize)] pub struct UsersBatchResponse { pub profiles: HashMap, pub missing: Vec, } #[derive(Serialize, Deserialize)] pub struct UserSearchResultInfo { pub pubkey: String, pub display_name: Option, pub avatar_url: Option, pub nip05_handle: Option, pub owner_pubkey: Option, #[serde(default)] pub is_agent: bool, } #[derive(Serialize, Deserialize)] pub struct SearchUsersResponse { pub users: Vec, } #[derive(Serialize, Deserialize)] pub struct UserNoteInfo { pub id: String, pub pubkey: String, pub created_at: i64, pub content: String, pub tags: Vec>, } #[derive(Serialize, Deserialize)] pub struct NoteReactionSummary { pub note_id: String, pub emoji: String, pub count: usize, pub pubkeys: Vec, } #[derive(Serialize, Deserialize)] pub struct UserNotesCursor { pub before: i64, pub before_id: String, } #[derive(Serialize, Deserialize)] pub struct UserNotesResponse { pub notes: Vec, pub next_cursor: Option, } #[derive(Serialize, Deserialize)] pub struct ChannelInfo { pub id: String, pub name: String, pub channel_type: String, pub visibility: String, #[serde(deserialize_with = "deserialize_null_string_as_empty")] pub description: String, pub topic: Option, pub purpose: Option, pub member_count: i64, #[serde(default)] pub member_pubkeys: Vec, pub last_message_at: Option, pub archived_at: Option, #[serde(default)] pub participants: Vec, #[serde(default)] pub participant_pubkeys: Vec, #[serde(default = "default_true")] pub is_member: bool, pub ttl_seconds: Option, pub ttl_deadline: Option, } #[derive(Serialize, Deserialize)] pub struct ChannelDetailInfo { pub id: String, pub name: String, pub channel_type: String, pub visibility: String, #[serde(deserialize_with = "deserialize_null_string_as_empty")] pub description: String, pub topic: Option, pub topic_set_by: Option, pub topic_set_at: Option, pub purpose: Option, pub purpose_set_by: Option, pub purpose_set_at: Option, pub created_by: String, pub created_at: String, pub updated_at: String, pub archived_at: Option, pub member_count: i64, pub topic_required: bool, pub max_members: Option, pub nip29_group_id: Option, pub ttl_seconds: Option, pub ttl_deadline: Option, } #[derive(Serialize, Deserialize)] pub struct ChannelMemberInfo { pub pubkey: String, pub role: String, #[serde(default)] pub is_agent: bool, /// Optional — kind:39002 events do not carry per-member join timestamps, /// so this is `None` when populated from a NIP-29 members event. #[serde(default)] pub joined_at: Option, pub display_name: Option, } #[derive(Serialize, Deserialize)] pub struct ChannelMembersResponse { pub members: Vec, pub next_cursor: Option, } #[derive(Serialize, Deserialize)] pub struct FeedItemInfo { pub id: String, pub kind: u32, pub pubkey: String, pub content: String, pub created_at: u64, pub channel_id: Option, pub channel_name: String, #[serde(default)] pub channel_type: Option, pub tags: Vec>, pub category: String, } #[derive(Serialize, Deserialize)] pub struct FeedSections { pub mentions: Vec, pub needs_action: Vec, pub activity: Vec, pub agent_activity: Vec, } #[derive(Serialize, Deserialize)] pub struct FeedMeta { pub since: i64, pub total: u64, pub generated_at: i64, } #[derive(Serialize, Deserialize)] pub struct FeedResponse { pub feed: FeedSections, pub meta: FeedMeta, } #[derive(Serialize, Deserialize)] pub struct SearchHitInfo { pub event_id: String, pub content: String, pub kind: u32, pub pubkey: String, pub channel_id: Option, pub channel_name: Option, pub created_at: u64, pub score: f64, } #[derive(Serialize, Deserialize)] pub struct SearchResponse { pub hits: Vec, pub found: u64, } #[derive(Serialize, Deserialize)] pub struct SendChannelMessageResponse { pub event_id: String, pub parent_event_id: Option, pub root_event_id: Option, pub depth: u32, pub created_at: i64, } #[derive(Serialize, Deserialize)] pub struct ThreadSummary { pub reply_count: u32, pub descendant_count: u32, pub last_reply_at: Option, pub participants: Vec, } #[derive(Serialize, Deserialize)] pub struct ForumMessageInfo { pub event_id: String, pub pubkey: String, pub content: String, pub kind: u32, pub created_at: i64, pub channel_id: String, pub tags: Vec>, #[serde(default)] pub thread_summary: Option, #[serde(default)] pub reactions: serde_json::Value, } #[derive(Serialize, Deserialize)] pub struct ForumPostsResponse { pub messages: Vec, pub next_cursor: Option, } #[derive(Serialize, Deserialize)] pub struct ForumThreadReplyInfo { pub event_id: String, pub pubkey: String, pub content: String, pub kind: u32, pub created_at: i64, pub channel_id: String, pub tags: Vec>, pub parent_event_id: Option, pub root_event_id: Option, pub depth: u32, pub broadcast: bool, #[serde(default)] pub reactions: serde_json::Value, } #[derive(Serialize, Deserialize)] pub struct ForumThreadResponse { pub root: ForumMessageInfo, pub replies: Vec, pub total_replies: u32, pub next_cursor: Option, } fn deserialize_null_string_as_empty<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { Ok(Option::::deserialize(deserializer)?.unwrap_or_default()) } fn default_true() -> bool { true } // ── Social / Contact list ─────────────────────────────────────────────────── #[derive(Serialize, Deserialize)] pub struct ContactListResponse { pub id: String, pub pubkey: String, pub created_at: i64, pub tags: Vec>, pub content: String, } #[derive(Serialize, Deserialize)] pub struct ContactEntry { pub pubkey: String, #[serde(default)] pub relay_url: Option, #[serde(default)] pub petname: Option, }