lmk if things need to move/be changed/etc i am Not Great At Rust™ lol
+58
crates/jacquard/src/client.rs
+58
crates/jacquard/src/client.rs
···
1048
1048
Self::unauthenticated()
1049
1049
}
1050
1050
}
1051
+
1052
+
/// MemoryCredentialSession: credential session with in memory store and identity resolver
1053
+
pub type MemoryCredentialSession = CredentialSession<
1054
+
MemorySessionStore<SessionKey, AtpSession>,
1055
+
jacquard_identity::PublicResolver,
1056
+
>;
1057
+
1058
+
impl MemoryCredentialSession {
1059
+
/// Create an unauthenticated MemoryCredentialSession.
1060
+
///
1061
+
/// Uses an in memory store and a public resolver.
1062
+
/// Equivalent to a BasicClient that isn't wrapped in Agent
1063
+
fn unauthenticated() -> Self {
1064
+
use std::sync::Arc;
1065
+
let http = reqwest::Client::new();
1066
+
let resolver = jacquard_identity::PublicResolver::new(http, Default::default());
1067
+
let store = MemorySessionStore::default();
1068
+
CredentialSession::new(Arc::new(store), Arc::new(resolver))
1069
+
}
1070
+
1071
+
/// Create a MemoryCredentialSession and authenticate with the provided details
1072
+
///
1073
+
/// - `identifier`: handle (preferred), DID, or `https://` PDS base URL.
1074
+
/// - `session_id`: optional session label; defaults to "session".
1075
+
/// - Persists and activates the session, and updates the base endpoint to the user's PDS.
1076
+
///
1077
+
/// # Example
1078
+
/// ```no_run
1079
+
/// # use jacquard::client::BasicClient;
1080
+
/// # use jacquard::types::string::AtUri;
1081
+
/// # use jacquard_api::app_bsky::feed::post::Post;
1082
+
/// use crate::jacquard::client::{Agent, AgentSessionExt};
1083
+
/// # #[tokio::main]
1084
+
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1085
+
/// let (session, _) = MemoryCredentialSession::authenticated(identifier, password, None);
1086
+
/// let agent = Agent::from(session);
1087
+
/// let output = agent.create_record::<Post>(post, None).await?;
1088
+
/// # Ok(())
1089
+
/// # }
1090
+
/// ```
1091
+
async fn authenticated(
1092
+
identifier: CowStr<'_>,
1093
+
password: CowStr<'_>,
1094
+
session_id: Option<CowStr<'_>>,
1095
+
) -> Result<(Self, AtpSession), ClientError> {
1096
+
let session = MemoryCredentialSession::unauthenticated();
1097
+
let auth = session
1098
+
.login(identifier, password, session_id, None, None)
1099
+
.await?;
1100
+
Ok((session, auth))
1101
+
}
1102
+
}
1103
+
1104
+
impl Default for MemoryCredentialSession {
1105
+
fn default() -> Self {
1106
+
MemoryCredentialSession::unauthenticated()
1107
+
}
1108
+
}
+27
-6
crates/jacquard/Cargo.toml
+27
-6
crates/jacquard/Cargo.toml
···
17
17
# Minimal API bindings
18
18
api = ["jacquard-api/minimal"]
19
19
# Bluesky API bindings
20
-
api_bluesky = ["api", "jacquard-api/bluesky" ]
20
+
api_bluesky = ["api", "jacquard-api/bluesky"]
21
21
# Bluesky API bindings, plus a curated selection of community lexicons
22
-
api_full = ["api", "jacquard-api/bluesky", "jacquard-api/other", "jacquard-api/lexicon_community"]
22
+
api_full = [
23
+
"api",
24
+
"jacquard-api/bluesky",
25
+
"jacquard-api/other",
26
+
"jacquard-api/lexicon_community",
27
+
]
23
28
# All captured generated lexicon API bindings
24
29
api_all = ["api_full", "jacquard-api/ufos"]
25
30
26
31
# Propagate loopback to oauth (server + browser helper)
27
32
loopback = ["jacquard-oauth/loopback", "jacquard-oauth/browser-open"]
28
33
# Enable tracing instrumentation
29
-
tracing = ["dep:tracing", "jacquard-common/tracing", "jacquard-oauth/tracing", "jacquard-identity/tracing"]
34
+
tracing = [
35
+
"dep:tracing",
36
+
"jacquard-common/tracing",
37
+
"jacquard-oauth/tracing",
38
+
"jacquard-identity/tracing",
39
+
]
30
40
dns = ["jacquard-identity/dns"]
31
41
streaming = ["jacquard-common/streaming"]
32
42
websocket = ["jacquard-common/websocket"]
···
83
93
path = "../../examples/streaming_download.rs"
84
94
required-features = ["api_bluesky", "streaming"]
85
95
96
+
[[example]]
97
+
name = "app_password_create_post"
98
+
path = "../../examples/app_password_create_post.rs"
99
+
required-features = ["api_bluesky"]
100
+
86
101
87
102
[dependencies]
88
103
jacquard-api = { version = "0.5", path = "../jacquard-api" }
89
-
jacquard-common = { version = "0.5", path = "../jacquard-common", features = ["reqwest-client"] }
104
+
jacquard-common = { version = "0.5", path = "../jacquard-common", features = [
105
+
"reqwest-client",
106
+
] }
90
107
jacquard-oauth = { version = "0.5", path = "../jacquard-oauth" }
91
108
jacquard-derive = { version = "0.5", path = "../jacquard-derive", optional = true }
92
109
jacquard-identity = { version = "0.5", path = "../jacquard-identity" }
···
112
129
tracing = { workspace = true, optional = true }
113
130
114
131
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
115
-
reqwest = { workspace = true, features = ["http2", "system-proxy", "rustls-tls"] }
132
+
reqwest = { workspace = true, features = [
133
+
"http2",
134
+
"system-proxy",
135
+
"rustls-tls",
136
+
] }
116
137
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] }
117
138
118
139
[target.'cfg(target_family = "wasm")'.dependencies]
···
123
144
miette = { workspace = true, features = ["fancy"] }
124
145
125
146
[package.metadata.docs.rs]
126
-
features = [ "api_all", "derive", "dns", "loopback" ]
147
+
features = ["api_all", "derive", "dns", "loopback"]
+49
examples/app_password_create_post.rs
+49
examples/app_password_create_post.rs
···
1
+
use clap::Parser;
2
+
use jacquard::CowStr;
3
+
use jacquard::api::app_bsky::feed::post::Post;
4
+
use jacquard::client::{Agent, AgentSessionExt, MemoryCredentialSession};
5
+
use jacquard::types::string::Datetime;
6
+
7
+
#[derive(Parser, Debug)]
8
+
#[command(author, version, about = "Create a simple post")]
9
+
struct Args {
10
+
/// Handle (e.g., alice.bsky.social) or DID
11
+
input: CowStr<'static>,
12
+
13
+
/// App Password
14
+
password: CowStr<'static>,
15
+
16
+
/// Post text
17
+
#[arg(short, long)]
18
+
text: String,
19
+
}
20
+
21
+
#[tokio::main]
22
+
async fn main() -> miette::Result<()> {
23
+
let args = Args::parse();
24
+
25
+
let (session, auth) =
26
+
MemoryCredentialSession::authenticated(args.input, args.password, None).await?;
27
+
println!("Signed in as {}", auth.handle);
28
+
29
+
let agent: Agent<_> = Agent::from(session);
30
+
31
+
// Create a simple text post using the Agent convenience method
32
+
let post = Post {
33
+
text: CowStr::from(args.text),
34
+
created_at: Datetime::now(),
35
+
embed: None,
36
+
entities: None,
37
+
facets: None,
38
+
labels: None,
39
+
langs: None,
40
+
reply: None,
41
+
tags: None,
42
+
extra_data: Default::default(),
43
+
};
44
+
45
+
let output = agent.create_record(post, None).await?;
46
+
println!("✓ Created post: {}", output.uri);
47
+
48
+
Ok(())
49
+
}