use std::sync::Arc;
use crate::error::SymbiosError;
use bevy::prelude::*;
use proto_blue_oauth::OAuthSession;
use serde::Deserialize;
#[derive(Deserialize)]
struct GetServiceAuthResponse {
token: String,
}
#[derive(Resource, Clone)]
pub struct AtprotoSession {
pub did: String,
pub handle: String,
pub pds_url: String,
pub session: Arc<OAuthSession>,
}
impl AtprotoSession {
pub fn xrpc_url(&self, nsid: &str) -> String {
format!("{}/xrpc/{}", self.pds_url.trim_end_matches('/'), nsid)
}
}
pub async fn get_service_auth(session: &AtprotoSession, aud: &str) -> Result<String, SymbiosError> {
let url = format!(
"{}?aud={}",
session.xrpc_url("com.atproto.server.getServiceAuth"),
urlencode(aud),
);
let resp = session
.session
.get(&url)
.await
.map_err(|e| SymbiosError::AuthFailed(format!("getServiceAuth: {e}")))?;
let status = resp.status();
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
return Err(SymbiosError::AuthFailed(format!(
"getServiceAuth returned {status}: {body}"
)));
}
let parsed: GetServiceAuthResponse = resp
.json()
.await
.map_err(|e| SymbiosError::AuthFailed(format!("getServiceAuth decode: {e}")))?;
Ok(parsed.token)
}
fn urlencode(s: &str) -> String {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let mut out = String::with_capacity(s.len());
for b in s.bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
out.push(b as char);
}
_ => {
out.push('%');
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0f) as usize] as char);
}
}
}
out
}