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