1pub use simploxide_ffi_core::{
10 CallError, DbOpts, DefaultUser, Event as CoreEvent, InitError as CoreInitError, RawClient,
11 Result as CoreResult, SimplexVersion, VersionError, WorkerConfig,
12};
13
14use simploxide_api_types::{
15 Preferences, Profile,
16 client_api::{ExtractResponse as _, FfiResponseShape},
17 events::{Event, EventKind},
18};
19use simploxide_core::{MAX_SUPPORTED_VERSION, MIN_SUPPORTED_VERSION};
20
21use std::sync::Arc;
22
23use crate::{
24 BadResponseError, ClientApi, ClientApiError, EventParser,
25 bot::{BotName, BotProfileSettings, BotSettings},
26 id::UserId,
27 preview::ImagePreview,
28 util,
29};
30
31pub type EventResult = CoreResult<CoreEvent>;
32pub type EventStream = crate::EventStream<EventResult>;
33pub type ClientResult<T = ()> = ::std::result::Result<T, ClientError>;
34
35#[cfg(not(feature = "xftp"))]
36pub type Bot = crate::bot::Bot<Client>;
37
38#[cfg(feature = "xftp")]
39pub type Bot = crate::bot::Bot<crate::xftp::XftpClient<Client>>;
40
41#[cfg(feature = "farm")]
42pub type FarmBot = crate::bot::farm::FarmBot<Client>;
43
44#[cfg(feature = "farm")]
45pub type InitFarm = crate::bot::farm::InitFarm<Client, EventResult>;
46
47#[cfg(feature = "farm")]
48pub type RunningFarm = crate::bot::farm::RunningFarm<Client, EventResult>;
49
50pub async fn init(
51 default_user: DefaultUser,
52 db_opts: DbOpts,
53) -> Result<(Client, EventStream), InitError> {
54 init_with_config(default_user, db_opts, WorkerConfig::default()).await
55}
56
57pub async fn init_with_config(
58 default_user: DefaultUser,
59 db_opts: DbOpts,
60 config: WorkerConfig,
61) -> Result<(Client, EventStream), InitError> {
62 let (raw_client, raw_event_queue) =
63 simploxide_ffi_core::init_with_config(default_user, db_opts, config).await?;
64
65 let version = raw_client
66 .version()
67 .await
68 .map_err(InitError::VersionError)?;
69
70 if !version.is_supported() {
71 return Err(InitError::VersionMismatch(version));
72 }
73
74 Ok((
75 Client::from(raw_client),
76 EventStream::from(raw_event_queue.into_receiver()),
77 ))
78}
79
80#[derive(Clone)]
82pub struct Client {
83 inner: RawClient,
84}
85
86impl From<RawClient> for Client {
87 fn from(inner: RawClient) -> Self {
88 Self { inner }
89 }
90}
91
92impl Client {
95 pub fn version(&self) -> impl Future<Output = Result<SimplexVersion, VersionError>> {
96 self.inner.version()
97 }
98
99 pub fn disconnect(self) -> impl Future<Output = ()> {
102 self.inner.disconnect()
103 }
104}
105
106impl ClientApi for Client {
107 type ResponseShape<'de, T>
108 = FfiResponseShape<T>
109 where
110 T: 'de + serde::Deserialize<'de>;
111
112 type Error = ClientError;
113
114 async fn send_raw(&self, command: String) -> Result<String, Self::Error> {
115 self.inner
116 .send(command)
117 .await
118 .map_err(ClientError::FfiFailure)
119 }
120}
121
122impl EventParser for EventResult {
123 type Error = ClientError;
124
125 fn parse_kind(&self) -> Result<EventKind, Self::Error> {
126 match parse_data::<util::TypeField<'_>>(self) {
127 Ok(f) => Ok(EventKind::from_type_str(f.typ)),
128 Err(ClientError::BadResponse(BadResponseError::ChatError(_))) => {
131 Ok(EventKind::ChatError)
132 }
133 Err(ClientError::BadResponse(BadResponseError::Undocumented(_))) => {
134 Ok(EventKind::Undocumented)
135 }
136 Err(e) => Err(e),
137 }
138 }
139
140 fn parse_user_id(&self) -> Result<Option<UserId>, Self::Error> {
141 match parse_data::<util::UserField>(self) {
142 Ok(f) => Ok(UserId::try_from(f.user.user_id).ok()),
143 Err(ClientError::BadResponse(_)) => Ok(None),
144 Err(e) => Err(e),
145 }
146 }
147
148 fn parse_event(&self) -> Result<Event, Self::Error> {
149 match parse_data(self) {
150 Ok(ev) => Ok(ev),
151 Err(ClientError::BadResponse(BadResponseError::ChatError(err))) => Ok(
154 Event::ChatError(Arc::new(simploxide_api_types::events::ChatError {
155 chat_error: err.as_ref().clone(),
156 undocumented: Default::default(),
157 })),
158 ),
159 Err(ClientError::BadResponse(BadResponseError::Undocumented(json))) => {
160 Ok(Event::Undocumented(json))
161 }
162 Err(e) => Err(e),
163 }
164 }
165}
166
167fn parse_data<'de, 'r: 'de, D: 'de + serde::Deserialize<'de>>(
168 result: &'r EventResult,
169) -> Result<D, ClientError> {
170 result
171 .as_ref()
172 .map_err(|e| ClientError::FfiFailure(e.clone()))
173 .and_then(|ev| {
174 serde_json::from_str::<FfiResponseShape<D>>(ev)
175 .map_err(BadResponseError::InvalidJson)
176 .and_then(|shape| shape.extract_response())
177 .map_err(ClientError::BadResponse)
178 })
179}
180
181#[derive(Clone)]
183pub struct BotBuilder {
184 display_name: BotName,
185 db_opts: DbOpts,
186 default_user: Option<DefaultUser>,
187 auto_accept: Option<String>,
188 profile: Option<Profile>,
189 preferences: Option<Preferences>,
190 avatar: Option<ImagePreview>,
191 bio: Option<String>,
192 description: Option<String>,
193 worker_config: WorkerConfig,
194}
195
196impl BotBuilder {
197 pub fn new(name: impl Into<BotName>, db_opts: DbOpts) -> Self {
199 Self {
200 display_name: name.into(),
201 db_opts,
202 default_user: None,
203 auto_accept: None,
204 profile: None,
205 preferences: None,
206 avatar: None,
207 bio: None,
208 description: None,
209 worker_config: WorkerConfig::default(),
210 }
211 }
212
213 pub fn with_default_user(mut self, user: DefaultUser) -> Self {
218 self.default_user = Some(user);
219 self
220 }
221
222 pub fn auto_accept(mut self) -> Self {
224 self.auto_accept = Some(String::default());
225 self
226 }
227
228 pub fn auto_accept_with(mut self, welcome_message: impl Into<String>) -> Self {
230 self.auto_accept = Some(welcome_message.into());
231 self
232 }
233
234 pub fn with_avatar(mut self, avatar: ImagePreview) -> Self {
236 self.avatar = Some(avatar);
237 self
238 }
239
240 pub fn with_bio(mut self, bio: impl Into<String>) -> Self {
242 self.bio = Some(bio.into());
243 self
244 }
245
246 pub fn with_description(mut self, description: impl Into<String>) -> Self {
248 self.description = Some(description.into());
249 self
250 }
251
252 pub fn with_profile(mut self, profile: Profile) -> Self {
254 self.profile = Some(profile);
255 self
256 }
257
258 pub fn with_preferences(mut self, prefs: Preferences) -> Self {
260 self.preferences = Some(prefs);
261 self
262 }
263
264 pub fn with_worker_config(mut self, config: WorkerConfig) -> Self {
266 self.worker_config = config;
267 self
268 }
269
270 pub async fn launch(self) -> Result<(Bot, EventStream), BotInitError> {
272 let default_user = self
273 .default_user
274 .unwrap_or_else(|| DefaultUser::bot(self.display_name.current()));
275
276 let (client, events) = init_with_config(default_user, self.db_opts, self.worker_config)
277 .await
278 .map_err(BotInitError::Init)?;
279
280 #[cfg(feature = "xftp")]
281 let (client, events) = events.hook_xftp(client);
282
283 let settings = BotSettings {
284 display_name: self.display_name,
285 auto_accept: self.auto_accept,
286 profile_settings: match (self.profile, self.preferences) {
287 (Some(mut profile), Some(preferences)) => {
288 profile.preferences = Some(preferences);
289 Some(BotProfileSettings::FullProfile(profile))
290 }
291 (Some(profile), None) => Some(BotProfileSettings::FullProfile(profile)),
292 (None, Some(preferences)) => Some(BotProfileSettings::Preferences(preferences)),
293 (None, None) => None,
294 },
295 avatar: self.avatar,
296 bio: self.bio,
297 description: self.description,
298 };
299
300 let bot = Bot::init(client, settings).await?;
301
302 let mut events = events;
303 events.set_owner(bot.user_id());
304
305 Ok((bot, events))
306 }
307}
308
309#[cfg(feature = "farm")]
310#[derive(Clone)]
311pub struct BotFarmBuilder {
312 display_name: String,
313 db_opts: DbOpts,
314 default_user: Option<DefaultUser>,
315 worker_config: WorkerConfig,
316}
317
318#[cfg(feature = "farm")]
319impl BotFarmBuilder {
320 pub fn new(name: impl Into<String>, db_opts: DbOpts) -> Self {
321 Self {
322 display_name: name.into(),
323 db_opts,
324 default_user: None,
325 worker_config: WorkerConfig::default(),
326 }
327 }
328
329 pub fn with_default_user(mut self, user: DefaultUser) -> Self {
334 self.default_user = Some(user);
335 self
336 }
337
338 pub fn with_worker_config(mut self, config: WorkerConfig) -> Self {
340 self.worker_config = config;
341 self
342 }
343
344 pub async fn launch(self) -> Result<InitFarm, BotInitError> {
346 let default_user = self
347 .default_user
348 .unwrap_or_else(|| DefaultUser::bot(&self.display_name));
349
350 let (client, events) = init_with_config(default_user, self.db_opts, self.worker_config)
351 .await
352 .map_err(BotInitError::Init)?;
353
354 let bot = crate::bot::BotFarm::init(self.display_name, client, events).await?;
355 Ok(bot)
356 }
357}
358
359#[derive(Debug)]
364pub enum ClientError {
365 FfiFailure(Arc<CallError>),
366 BadResponse(BadResponseError),
367}
368
369impl std::error::Error for ClientError {
370 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
371 match self {
372 Self::FfiFailure(error) => Some(error),
373 Self::BadResponse(error) => Some(error),
374 }
375 }
376}
377
378impl std::fmt::Display for ClientError {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 match self {
381 ClientError::FfiFailure(err) => writeln!(f, "FFI error: {err}"),
382 ClientError::BadResponse(err) => err.fmt(f),
383 }
384 }
385}
386
387impl From<BadResponseError> for ClientError {
388 fn from(err: BadResponseError) -> Self {
389 Self::BadResponse(err)
390 }
391}
392
393impl ClientApiError for ClientError {
394 fn bad_response(&self) -> Option<&BadResponseError> {
395 if let Self::BadResponse(resp) = self {
396 Some(resp)
397 } else {
398 None
399 }
400 }
401
402 fn bad_response_mut(&mut self) -> Option<&mut BadResponseError> {
403 if let Self::BadResponse(resp) = self {
404 Some(resp)
405 } else {
406 None
407 }
408 }
409}
410
411#[derive(Debug)]
412pub enum InitError {
413 Ffi(CoreInitError),
415 VersionError(VersionError),
417 VersionMismatch(SimplexVersion),
419}
420
421impl InitError {
422 pub fn is_ffi(&self) -> bool {
423 matches!(self, Self::Ffi(_))
424 }
425
426 pub fn is_version_mismatch(&self) -> bool {
427 matches!(self, Self::VersionMismatch(_))
428 }
429}
430
431impl From<CoreInitError> for InitError {
432 fn from(value: CoreInitError) -> Self {
433 Self::Ffi(value)
434 }
435}
436
437impl std::fmt::Display for InitError {
438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 match self {
440 Self::Ffi(error) => write!(f, "Cannot initialize the FFI backend: {error}"),
441 Self::VersionError(error) => write!(f, "Cannot get FFI version {error}"),
442 Self::VersionMismatch(v) => write!(
443 f,
444 "Version {v} is unsupported by the current client. Supported versions are {MIN_SUPPORTED_VERSION}..{MAX_SUPPORTED_VERSION}"
445 ),
446 }
447 }
448}
449
450impl std::error::Error for InitError {
451 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
452 match self {
453 Self::Ffi(error) => Some(error),
454 Self::VersionError(error) => Some(error),
455 Self::VersionMismatch(_) => None,
456 }
457 }
458}
459
460#[derive(Debug)]
462pub enum BotInitError {
463 Init(InitError),
464 Api(ClientError),
465}
466
467impl std::fmt::Display for BotInitError {
468 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469 match self {
470 Self::Init(e) => write!(f, "SimpleX FFI init failed: {e}"),
471 Self::Api(e) => write!(f, "SimpleX API error during init: {e}"),
472 }
473 }
474}
475
476impl std::error::Error for BotInitError {
477 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
478 match self {
479 Self::Init(e) => Some(e),
480 Self::Api(e) => Some(e),
481 }
482 }
483}
484
485impl From<ClientError> for BotInitError {
486 fn from(e: ClientError) -> Self {
487 Self::Api(e)
488 }
489}