pdsmigration_common/
agent.rs

1use crate::errors::PdsError;
2use crate::CreateAccountRequest;
3use bsky_sdk::api::agent::atp_agent::AtpSession;
4use bsky_sdk::api::agent::Configure;
5use bsky_sdk::api::app::bsky::actor::defs::Preferences;
6use bsky_sdk::api::com::atproto::identity::sign_plc_operation::InputData;
7use bsky_sdk::api::com::atproto::repo::list_missing_blobs::RecordBlob;
8use bsky_sdk::api::types::string::{Cid, Did, Handle, Nsid};
9use bsky_sdk::api::types::Unknown;
10use bsky_sdk::BskyAgent;
11use ipld_core::ipld::Ipld;
12
13pub type GetAgentResult = Result<BskyAgent, Box<dyn std::error::Error>>;
14pub type RecommendedDidOutputData =
15    bsky_sdk::api::com::atproto::identity::get_recommended_did_credentials::OutputData;
16
17#[tracing::instrument(skip(agent, token))]
18pub async fn login_helper(
19    agent: &BskyAgent,
20    pds_host: &str,
21    did: &str,
22    token: &str,
23) -> Result<AtpSession, PdsError> {
24    use bsky_sdk::api::com::atproto::server::create_session::OutputData;
25    agent.configure_endpoint(pds_host.to_string());
26    match agent
27        .resume_session(AtpSession {
28            data: OutputData {
29                access_jwt: token.to_string(),
30                active: Some(true),
31                did: Did::new(did.to_string()).unwrap(),
32                did_doc: None,
33                email: None,
34                email_auth_factor: None,
35                email_confirmed: None,
36                handle: Handle::new("anothermigration.bsky.social".to_string()).unwrap(),
37                refresh_jwt: "".to_string(),
38                status: None,
39            },
40            extra_data: Ipld::Null,
41        })
42        .await
43    {
44        Ok(_) => {
45            tracing::info!("Successfully logged in");
46            Ok(agent.get_session().await.unwrap())
47        }
48        Err(e) => {
49            tracing::error!("Error logging in: {:?}", e);
50            Err(PdsError::Login)
51        }
52    }
53}
54
55#[tracing::instrument(skip(agent))]
56pub async fn describe_server(
57    agent: &BskyAgent,
58) -> Result<bsky_sdk::api::com::atproto::server::describe_server::OutputData, String> {
59    let result = agent.api.com.atproto.server.describe_server().await;
60    match result {
61        Ok(output) => {
62            tracing::info!("{:?}", output);
63            Ok(output.data)
64        }
65        Err(e) => {
66            tracing::error!("{:?}", e);
67            Err(String::from("Error"))
68        }
69    }
70}
71
72#[tracing::instrument(skip(agent))]
73pub async fn missing_blobs(agent: &BskyAgent) -> Result<Vec<RecordBlob>, PdsError> {
74    use bsky_sdk::api::com::atproto::repo::list_missing_blobs::{Parameters, ParametersData};
75    let result = agent
76        .api
77        .com
78        .atproto
79        .repo
80        .list_missing_blobs(Parameters {
81            data: ParametersData {
82                cursor: None,
83                limit: None,
84            },
85            extra_data: Ipld::Null,
86        })
87        .await;
88    match result {
89        Ok(output) => {
90            tracing::info!("{:?}", output);
91            Ok(output.blobs.clone())
92        }
93        Err(e) => {
94            tracing::error!("{:?}", e);
95            Err(PdsError::Validation)
96        }
97    }
98}
99
100#[tracing::instrument(skip(agent))]
101pub async fn get_blob(agent: &BskyAgent, cid: Cid, did: Did) -> Result<Vec<u8>, ()> {
102    use bsky_sdk::api::com::atproto::sync::get_blob::{Parameters, ParametersData};
103    let result = agent
104        .api
105        .com
106        .atproto
107        .sync
108        .get_blob(Parameters {
109            data: ParametersData {
110                cid,
111                did: did.parse().unwrap(),
112            },
113            extra_data: Ipld::Null,
114        })
115        .await;
116    match result {
117        Ok(output) => {
118            tracing::debug!("Successfully fetched blob: {:?}", output);
119            Ok(output.clone())
120        }
121        Err(e) => {
122            tracing::error!("Failed to fetch blob: {:?}", e);
123            Err(())
124        }
125    }
126}
127
128#[tracing::instrument(skip(agent, input))]
129pub async fn upload_blob(agent: &BskyAgent, input: Vec<u8>) -> Result<(), PdsError> {
130    let result = agent.api.com.atproto.repo.upload_blob(input).await;
131    match result {
132        Ok(output) => {
133            tracing::info!("Successfully uploaded blob");
134            tracing::debug!("{:?}", output);
135            Ok(())
136        }
137        Err(e) => {
138            tracing::error!("Failed to upload blob: {:?}", e);
139            Err(PdsError::Validation)
140        }
141    }
142}
143
144#[tracing::instrument(skip(agent))]
145pub async fn export_preferences(agent: &BskyAgent) -> Result<Preferences, PdsError> {
146    use bsky_sdk::api::app::bsky::actor::get_preferences::{Parameters, ParametersData};
147    let result = agent
148        .api
149        .app
150        .bsky
151        .actor
152        .get_preferences(Parameters {
153            data: ParametersData {},
154            extra_data: Ipld::Null,
155        })
156        .await;
157    match result {
158        Ok(output) => {
159            tracing::info!("Successfully exported preferences");
160            tracing::debug!("{:?}", output);
161            Ok(output.preferences.clone())
162        }
163        Err(e) => {
164            tracing::error!("Failed to export preferences: {:?}", e);
165            Err(PdsError::Validation)
166        }
167    }
168}
169
170#[tracing::instrument(skip(agent))]
171pub async fn import_preferences(
172    agent: &BskyAgent,
173    preferences: Preferences,
174) -> Result<(), PdsError> {
175    use bsky_sdk::api::app::bsky::actor::put_preferences::{Input, InputData};
176    let result = agent
177        .api
178        .app
179        .bsky
180        .actor
181        .put_preferences(Input {
182            data: InputData { preferences },
183            extra_data: Ipld::Null,
184        })
185        .await;
186    match result {
187        Ok(output) => {
188            tracing::info!("Successfully imported preferences");
189            tracing::debug!("{:?}", output);
190            Ok(())
191        }
192        Err(e) => {
193            tracing::error!("Failed to import preferences: {:?}", e);
194            Err(PdsError::Validation)
195        }
196    }
197}
198
199#[tracing::instrument(skip(agent))]
200pub async fn recommended_plc(agent: &BskyAgent) -> Result<RecommendedDidOutputData, PdsError> {
201    let result = agent
202        .api
203        .com
204        .atproto
205        .identity
206        .get_recommended_did_credentials()
207        .await;
208    match result {
209        Ok(output) => {
210            tracing::info!("Successfully imported preferences");
211            tracing::debug!("{:?}", output);
212            Ok(output.data)
213        }
214        Err(e) => {
215            tracing::error!("Failed to import preferences: {:?}", e);
216            Err(PdsError::Validation)
217        }
218    }
219}
220
221#[tracing::instrument(skip(agent))]
222pub async fn get_service_auth(agent: &BskyAgent, aud: &str) -> Result<String, PdsError> {
223    use bsky_sdk::api::com::atproto::server::get_service_auth::{Parameters, ParametersData};
224    let result = agent
225        .api
226        .com
227        .atproto
228        .server
229        .get_service_auth(Parameters {
230            data: ParametersData {
231                aud: aud.parse().unwrap(),
232                exp: None,
233                lxm: Some(Nsid::new("com.atproto.server.createAccount".to_string()).unwrap()),
234            },
235            extra_data: Ipld::Null,
236        })
237        .await;
238    match result {
239        Ok(output) => {
240            tracing::info!("Successfully requested service auth");
241            tracing::debug!("{:?}", output);
242            Ok(output.token.clone())
243        }
244        Err(e) => {
245            tracing::error!("Failed to request service auth: {:?}", e);
246            Err(PdsError::Runtime)
247        }
248    }
249}
250
251#[tracing::instrument(skip(agent))]
252pub async fn sign_plc(agent: &BskyAgent, plc_input_data: InputData) -> Result<Unknown, PdsError> {
253    use bsky_sdk::api::com::atproto::identity::sign_plc_operation::Input;
254    let result = agent
255        .api
256        .com
257        .atproto
258        .identity
259        .sign_plc_operation(Input {
260            data: plc_input_data,
261            extra_data: Ipld::Null,
262        })
263        .await;
264    match result {
265        Ok(output) => {
266            tracing::info!("Successfully signed token");
267            tracing::debug!("{:?}", output);
268            Ok(output.operation.clone())
269        }
270        Err(e) => {
271            tracing::error!("Failed to sign token: {:?}", e);
272            Err(PdsError::Validation)
273        }
274    }
275}
276
277#[tracing::instrument(skip(agent))]
278pub async fn account_import(agent: &BskyAgent, filepath: &str) -> Result<(), PdsError> {
279    let result = agent
280        .api
281        .com
282        .atproto
283        .repo
284        .import_repo(tokio::fs::read(filepath).await.unwrap())
285        .await;
286    match result {
287        Ok(_) => {
288            tracing::info!("Successfully signed token");
289            Ok(())
290        }
291        Err(e) => {
292            eprintln!("Error importing: {:?}", e);
293            Err(PdsError::AccountImport)
294        }
295    }
296}
297
298#[tracing::instrument(skip(agent))]
299pub async fn account_export(agent: &BskyAgent, did: &Did) -> Result<(), PdsError> {
300    use bsky_sdk::api::com::atproto::sync::get_repo::{Parameters, ParametersData};
301    let result = agent
302        .api
303        .com
304        .atproto
305        .sync
306        .get_repo(Parameters {
307            data: ParametersData {
308                did: did.clone(),
309                since: None,
310            },
311            extra_data: Ipld::Null,
312        })
313        .await;
314    match result {
315        Ok(output) => {
316            tokio::fs::write(did.as_str().to_string() + ".car", output)
317                .await
318                .unwrap();
319            tracing::info!("write success");
320            Ok(())
321        }
322        Err(e) => {
323            tracing::error!("Error exporting: {:?}", e);
324            Err(PdsError::AccountExport)
325        }
326    }
327}
328
329#[tracing::instrument(skip(agent))]
330pub async fn deactivate_account(agent: &BskyAgent) -> Result<(), PdsError> {
331    use bsky_sdk::api::com::atproto::server::deactivate_account::{Input, InputData};
332    let result = agent
333        .api
334        .com
335        .atproto
336        .server
337        .deactivate_account(Input {
338            data: InputData { delete_after: None },
339            extra_data: Ipld::Null,
340        })
341        .await;
342    match result {
343        Ok(output) => {
344            tracing::info!("Successfully deactivated account");
345            tracing::debug!("{:?}", output);
346            Ok(())
347        }
348        Err(e) => {
349            tracing::error!("Failed to deactivate account: {:?}", e);
350            Err(PdsError::Validation)
351        }
352    }
353}
354
355#[tracing::instrument(skip(agent))]
356pub async fn activate_account(agent: &BskyAgent) -> Result<(), PdsError> {
357    let result = agent.api.com.atproto.server.activate_account().await;
358    match result {
359        Ok(output) => {
360            tracing::info!("Successfully activated account");
361            tracing::debug!("{:?}", output);
362            Ok(())
363        }
364        Err(e) => {
365            tracing::error!("Failed to activate account: {:?}", e);
366            Err(PdsError::Validation)
367        }
368    }
369}
370
371#[tracing::instrument(skip(agent))]
372pub async fn submit_plc(agent: &BskyAgent, signed_plc: Unknown) -> Result<(), PdsError> {
373    use bsky_sdk::api::com::atproto::identity::submit_plc_operation::{Input, InputData};
374    let result = agent
375        .api
376        .com
377        .atproto
378        .identity
379        .submit_plc_operation(Input {
380            data: InputData {
381                operation: signed_plc,
382            },
383            extra_data: Ipld::Null,
384        })
385        .await;
386    match result {
387        Ok(output) => {
388            tracing::info!("Successfully submitted PLC Operation");
389            tracing::debug!("{:?}", output);
390            Ok(())
391        }
392        Err(e) => {
393            tracing::error!("Failed to submitted PLC Operation: {:?}", e);
394            Err(PdsError::Validation)
395        }
396    }
397}
398
399#[tracing::instrument(skip(agent))]
400async fn account_status(agent: &BskyAgent) -> Result<(), PdsError> {
401    match agent.api.com.atproto.server.check_account_status().await {
402        Ok(output) => {
403            tracing::info!("Successfully Got Account Status");
404            tracing::debug!("{:?}", output);
405            Ok(())
406        }
407        Err(e) => {
408            tracing::error!("{:?}", e);
409            Err(PdsError::AccountStatus)
410        }
411    }
412}
413
414#[tracing::instrument(skip(agent))]
415pub async fn request_token(agent: &BskyAgent) -> Result<(), PdsError> {
416    let result = agent
417        .api
418        .com
419        .atproto
420        .identity
421        .request_plc_operation_signature()
422        .await;
423    match result {
424        Ok(_) => Ok(()),
425        Err(e) => {
426            tracing::error!("{:?}", e);
427            Err(PdsError::Validation)
428        }
429    }
430}
431
432#[tracing::instrument(skip(account_request))]
433pub async fn create_account(
434    pds_host: &str,
435    account_request: &CreateAccountRequest,
436) -> Result<(), PdsError> {
437    use bsky_sdk::api::com::atproto::server::create_account::{Input, InputData};
438    let client = reqwest::Client::new();
439    let x = serde_json::to_string(&Input {
440        data: InputData {
441            did: Some(account_request.did.clone()),
442            email: account_request.email.clone(),
443            handle: account_request.handle.parse().unwrap(),
444            invite_code: account_request.invite_code.clone(),
445            password: account_request.password.clone(),
446            plc_op: None,
447            recovery_key: account_request.recovery_key.clone(),
448            verification_code: account_request.verification_code.clone(),
449            verification_phone: account_request.verification_phone.clone(),
450        },
451        extra_data: Ipld::Null,
452    })
453    .unwrap();
454    let result = client
455        .post(pds_host.to_string() + "/xrpc/com.atproto.server.createAccount")
456        .body(x)
457        .header("Content-Type", "application/json")
458        .bearer_auth(account_request.token.clone())
459        .send()
460        .await;
461    match result {
462        Ok(output) => match output.status() {
463            reqwest::StatusCode::OK => {
464                tracing::info!("Successfully created account");
465            }
466            _ => {
467                tracing::error!("Error creating account: {:?}", output);
468                tracing::error!("More: {:?}", output.text().await);
469                return Err(PdsError::Validation);
470            }
471        },
472        Err(e) => {
473            tracing::error!("Error creating account: {:?}", e);
474            return Err(PdsError::Validation);
475        }
476    }
477    Ok(())
478}