Scalable and distributed custom feed generator, ott - on that topic
at main 165 lines 4.7 kB view raw
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}