From 89826aa0a609086a3efeb5a409a5c71d24b64de3 Mon Sep 17 00:00:00 2001 From: Timothy Quilling Date: Mon, 22 Dec 2025 14:14:04 -0500 Subject: [PATCH] feat: update lexica --- lexica/src/app_bsky/feed.rs | 57 +++++++++ lexica/src/app_bsky/graph.rs | 176 +++++++++++++++++++++++++++- lexica/src/app_bsky/mod.rs | 1 + lexica/src/app_bsky/notification.rs | 145 +++++++++++++++++++++++ 4 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 lexica/src/app_bsky/notification.rs diff --git a/lexica/src/app_bsky/feed.rs b/lexica/src/app_bsky/feed.rs index e8f32cec..2fa76077 100644 --- a/lexica/src/app_bsky/feed.rs +++ b/lexica/src/app_bsky/feed.rs @@ -241,3 +241,60 @@ pub enum SkeletonReason { #[serde(rename = "app.bsky.feed.defs#skeletonReasonRepost")] Repost { repost: String }, } + +// ============================================================================ +// Interaction Types +// ============================================================================ + +/// Interaction event types for feed items +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum InteractionEvent { + #[serde(rename = "app.bsky.feed.defs#requestLess")] + RequestLess, + #[serde(rename = "app.bsky.feed.defs#requestMore")] + RequestMore, + #[serde(rename = "app.bsky.feed.defs#clickthroughItem")] + ClickthroughItem, + #[serde(rename = "app.bsky.feed.defs#clickthroughAuthor")] + ClickthroughAuthor, + #[serde(rename = "app.bsky.feed.defs#clickthroughReposter")] + ClickthroughReposter, + #[serde(rename = "app.bsky.feed.defs#clickthroughEmbed")] + ClickthroughEmbed, + #[serde(rename = "app.bsky.feed.defs#interactionSeen")] + InteractionSeen, + #[serde(rename = "app.bsky.feed.defs#interactionLike")] + InteractionLike, + #[serde(rename = "app.bsky.feed.defs#interactionRepost")] + InteractionRepost, + #[serde(rename = "app.bsky.feed.defs#interactionReply")] + InteractionReply, + #[serde(rename = "app.bsky.feed.defs#interactionQuote")] + InteractionQuote, + #[serde(rename = "app.bsky.feed.defs#interactionShare")] + InteractionShare, + #[serde(untagged)] + Other(String), +} + +/// Interaction with a feed item +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Interaction { + /// URI of the item being interacted with + pub item: String, + + /// The type of interaction event + pub event: InteractionEvent, + + /// Context originally supplied by the feed generator + #[serde(skip_serializing_if = "Option::is_none")] + pub feed_context: Option, +} + +/// Request body for sendInteractions endpoint +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SendInteractionsRequest { + pub interactions: Vec, +} diff --git a/lexica/src/app_bsky/graph.rs b/lexica/src/app_bsky/graph.rs index 95670428..5519787a 100644 --- a/lexica/src/app_bsky/graph.rs +++ b/lexica/src/app_bsky/graph.rs @@ -60,7 +60,8 @@ pub struct ListView { pub indexed_at: DateTime, } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ListItemView { pub uri: String, pub subject: ProfileView, @@ -92,7 +93,7 @@ impl FromStr for ListPurpose { } } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct StarterPackView { pub uri: String, @@ -158,3 +159,174 @@ pub struct NotFoundActor { pub actor: String, pub not_found: bool, } + +// ============================================================================ +// Request/Response Types for Graph Endpoints +// ============================================================================ + +/// Parameters for getRelationships +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetRelationshipsParams { + pub actor: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub others: Option>, +} + +/// Response from getRelationships +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetRelationshipsResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub actor: Option, + pub relationships: Vec, +} + +/// Parameters for getFollows +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetFollowsParams { + pub actor: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, +} + +/// Response from getFollows +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetFollowsResponse { + pub subject: ProfileView, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + pub follows: Vec, +} + +/// Parameters for getFollowers +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetFollowersParams { + pub actor: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, +} + +/// Response from getFollowers +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetFollowersResponse { + pub subject: ProfileView, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + pub followers: Vec, +} + +/// Parameters for getList +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetListParams { + pub list: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, +} + +/// Response from getList +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetListResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + pub list: ListView, + pub items: Vec, +} + +/// Parameters for getLists +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetListsParams { + pub actor: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, +} + +/// Response from getLists +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetListsResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + pub lists: Vec, +} + +/// Parameters for getBlocks +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetBlocksParams { + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, +} + +/// Response from getBlocks +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetBlocksResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + pub blocks: Vec, +} + +/// Parameters for getMutes +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetMutesParams { + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, +} + +/// Response from getMutes +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetMutesResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + pub mutes: Vec, +} + +/// Parameters for getStarterPack +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetStarterPackParams { + pub starter_pack: String, +} + +/// Response from getStarterPack +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetStarterPackResponse { + pub starter_pack: StarterPackView, +} + +/// Parameters for getStarterPacks +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetStarterPacksParams { + pub uris: Vec, +} + +/// Response from getStarterPacks +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetStarterPacksResponse { + pub starter_packs: Vec, +} diff --git a/lexica/src/app_bsky/mod.rs b/lexica/src/app_bsky/mod.rs index d4dbba27..16d6b449 100644 --- a/lexica/src/app_bsky/mod.rs +++ b/lexica/src/app_bsky/mod.rs @@ -6,6 +6,7 @@ pub mod embed; pub mod feed; pub mod graph; pub mod labeler; +pub mod notification; pub mod richtext; pub mod unspecced; diff --git a/lexica/src/app_bsky/notification.rs b/lexica/src/app_bsky/notification.rs new file mode 100644 index 00000000..da330367 --- /dev/null +++ b/lexica/src/app_bsky/notification.rs @@ -0,0 +1,145 @@ +//! app.bsky.notification types and definitions +//! +//! Notification system for the Bluesky social application + +use crate::app_bsky::actor::ProfileView; +use crate::com_atproto::label::Label; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +// ============================================================================ +// Notification Types +// ============================================================================ + +/// Notification reason type +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum NotificationReason { + Like, + Repost, + Follow, + Mention, + Reply, + Quote, + #[serde(rename = "starterpack-joined")] + StarterpackJoined, + Verified, + Unverified, + #[serde(untagged)] + Other(String), +} + +/// A single notification +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Notification { + pub uri: String, + pub cid: String, + pub author: ProfileView, + pub reason: NotificationReason, + + #[serde(skip_serializing_if = "Option::is_none")] + pub reason_subject: Option, + + /// The record that triggered the notification (like, repost, follow, etc) + pub record: serde_json::Value, + + pub is_read: bool, + pub indexed_at: DateTime, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub labels: Vec