Scalable and distributed custom feed generator, ott - on that topic
1use std::str::FromStr;
2use std::sync::Arc;
3
4use anyhow::Result;
5use jacquard::api::app_bsky::actor::get_profiles::GetProfiles;
6use jacquard::client::Agent;
7use jacquard::client::AgentSessionExt;
8use jacquard::client::BasicClient;
9use jacquard::client::MemorySessionStore;
10use jacquard::from_data_owned;
11use jacquard::types::ident::AtIdentifier;
12use jacquard::types::nsid::Nsid;
13use jacquard::xrpc::XrpcExt;
14use jacquard::CowStr;
15use jacquard_api::app_bsky::feed::post::Post;
16use jacquard_api::com_atproto::repo::list_records::ListRecords;
17use tracing::{info, warn};
18use url::Url;
19
20// Super silly, there must be some good traits to use
21type MyAgent = Agent<
22 jacquard::client::credential_session::CredentialSession<
23 MemorySessionStore<
24 (jacquard::types::did::Did<'static>, CowStr<'static>),
25 jacquard::client::AtpSession,
26 >,
27 Agent<
28 jacquard::client::credential_session::CredentialSession<
29 MemorySessionStore<
30 (jacquard::types::did::Did<'static>, CowStr<'static>),
31 jacquard::client::AtpSession,
32 >,
33 jacquard_identity::JacquardResolver,
34 >,
35 >,
36 >,
37>;
38pub struct BskyClient {
39 pub agent: MyAgent,
40 pub base_url: Url,
41}
42
43impl BskyClient {
44 pub async fn new() -> Result<Self> {
45 let app_did = std::env::var("APP_DID").expect("Need to set APP_DID");
46 let app_key = std::env::var("APP_KEY").expect("Need to set APP_KEY");
47 let base = url::Url::parse("https://public.api.bsky.app")?;
48 let session = jacquard::client::credential_session::CredentialSession::new(
49 Arc::new(MemorySessionStore::default()),
50 Arc::new(BasicClient::default()),
51 );
52 session
53 .login(
54 CowStr::from(app_did),
55 CowStr::from(app_key),
56 None,
57 None,
58 None,
59 )
60 .await?;
61 let token = session.access_token().await.unwrap();
62 warn!("{:#?}", token);
63 let agent = Agent::from(session);
64 Ok(Self {
65 agent,
66 base_url: base,
67 })
68 }
69
70 pub async fn get_like(&self, did: &str) -> Result<Post> {
71 let request = ListRecords::new()
72 .collection(Nsid::from_str("bsky.feed.like")?)
73 .limit(1)
74 .repo(AtIdentifier::from_str(did).expect("did to be ok"))
75 .build();
76
77 let response = self
78 .agent
79 .xrpc(self.base_url.clone())
80 .send(&request)
81 .await?;
82 let data = response
83 .into_output()
84 .unwrap()
85 .records
86 .first()
87 .unwrap()
88 .to_owned();
89 let post: Post = from_data_owned(data)?;
90
91 Ok(post)
92 }
93
94 async fn get_profile(&self, did: &str) -> Result<()> {
95 let request = GetProfiles::new()
96 .actors(vec![AtIdentifier::Did(did.parse()?)])
97 .build();
98 let response = self
99 .agent
100 .xrpc(self.base_url.clone())
101 .send(&request)
102 .await?;
103 info!("{:#?}", response.parse());
104 Ok(())
105 }
106
107 async fn get_post(&self, uri: &str) -> Result<()> {
108 let response = self.agent.get_record::<Post>(uri.parse()?).await?;
109 let output = response.into_output()?;
110 info!("{:#?}", output);
111
112 Ok(())
113 }
114}
115
116#[cfg(test)]
117mod test {
118
119 use super::*;
120 use rstest::{fixture, rstest};
121
122 #[fixture]
123 fn setup_tracing() {
124 tracing_subscriber::fmt()
125 .with_max_level(tracing::Level::DEBUG)
126 .try_init()
127 .ok();
128 }
129
130 #[fixture]
131 async fn client() -> BskyClient {
132 BskyClient::new().await.unwrap()
133 }
134
135 #[rstest]
136 #[tokio::test]
137 async fn test_get_latest_like(
138 #[values("did:plc:klugggc44dmpomjkuzyahzjd")] did: &str,
139 #[future] client: BskyClient,
140 _setup_tracing: (),
141 ) {
142 info!("Starting");
143 let client = client.await;
144 let post = client.get_like(did).await;
145 assert!(post.is_ok());
146 }
147
148 #[rstest]
149 #[tokio::test]
150 async fn test_get_profile(
151 #[values("did:plc:klugggc44dmpomjkuzyahzjd", "did:plc:6u4att3krympska2rcfphobc")] did: &str,
152 #[future] client: BskyClient,
153 _setup_tracing: (),
154 ) {
155 info!("Starting");
156 let did = client.await.get_profile(did).await;
157 info!("{:#?}", did);
158
159 assert!(did.is_ok());
160 }
161
162 #[rstest]
163 #[tokio::test]
164 async fn test_get_post(#[future] client: BskyClient) {}
165}