Skip to content

Commit 6d28bb4

Browse files
committed
feat: enhance bot functionality with message sending capability and state management
1 parent 85129f8 commit 6d28bb4

3 files changed

Lines changed: 70 additions & 11 deletions

File tree

src/client.rs

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::{Arc, Once};
1+
use std::sync::{Arc, Mutex, Once};
22
use std::future::Future;
33
use std::pin::Pin;
44
use pyo3::{Bound, PyAny, pyclass, pymethods};
@@ -8,16 +8,20 @@ use pyo3_async_runtimes::tokio::{future_into_py_with_locals, get_current_locals,
88
use tokio::runtime;
99
use tokio::time::{Duration, interval};
1010
use wacore::types::events::Event;
11+
use whatsapp_rust::Client;
1112
use whatsapp_rust::bot::Bot;
1213
use whatsapp_rust::store::Backend;
1314
use whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory;
1415
use whatsapp_rust_ureq_http_client::UreqHttpClient;
16+
use waproto::whatsapp::Message as WhatsappMessage;
17+
use prost::Message;
1518
use tokio::signal;
1619
use tracing::{debug, error, info, warn};
1720
use tracing_subscriber::EnvFilter;
1821

1922
use crate::backend::{SqliteBackend, BackendBase};
20-
use crate::events::{Dispatcher, Message, PairingQrCode};
23+
use crate::events::{Dispatcher, Message as WAMessage, PairingQrCode};
24+
use crate::types::JID;
2125

2226
static LOG_INIT: Once = Once::new();
2327

@@ -36,13 +40,15 @@ fn init_logging() {
3640
pub struct Tryx {
3741
backend: Arc<dyn Backend>,
3842
handlers: Py<Dispatcher>,
43+
bot: Arc<Mutex<Option<Arc<Client>>>>,
3944
}
4045

4146
impl Tryx {
4247
async fn run_bot(
4348
backend: Arc<dyn Backend>,
4449
handlers: Py<Dispatcher>,
4550
locals: Option<TaskLocals>,
51+
bot_state: Arc<Mutex<Option<Arc<Client>>>>,
4652
) -> PyResult<()> {
4753
info!("building WhatsApp bot");
4854
let mut bot = Bot::builder()
@@ -113,7 +119,7 @@ impl Tryx {
113119
debug!(handler_index = idx, message_id = %info.id, "calling message Python callback");
114120
let locals = locals.clone();
115121
let py_future = Python::attach(|py| -> PyResult<_> {
116-
let payload = Py::new(py, Message::new(msg.clone(), info.clone()))?;
122+
let payload = Py::new(py, WAMessage::new(msg.clone(), info.clone()))?;
117123
let awaitable = callback.bind(py).call1((py.None(), payload))?;
118124
let fut: Pin<Box<dyn Future<Output = PyResult<Py<PyAny>>> + Send>> = match &locals {
119125
Some(locals) => {
@@ -157,8 +163,15 @@ impl Tryx {
157163
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string())
158164
})?;
159165

160-
info!("bot built successfully, starting run loop");
166+
let client = bot.client();
167+
{
168+
let mut state = bot_state
169+
.lock()
170+
.map_err(|_| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Failed to lock bot state"))?;
171+
*state = Some(client);
172+
}
161173

174+
info!("bot built successfully, starting run loop");
162175
bot.run()
163176
.await
164177
.map_err(|e| {
@@ -195,6 +208,7 @@ impl Tryx {
195208
Ok(Tryx {
196209
backend: Arc::new(store),
197210
handlers: Py::new(py, Dispatcher::empty())?,
211+
bot: Arc::new(Mutex::new(None)),
198212
})
199213
} else {
200214
error!("unsupported backend type passed to Tryx");
@@ -219,10 +233,12 @@ impl Tryx {
219233
info!("starting bot in async mode via Tryx.run");
220234
let backend = self.backend.clone();
221235
let handlers = self.handlers.clone_ref(py);
236+
let bot_state = self.bot.clone();
222237
let locals = get_current_locals(py)?;
223238
future_into_py_with_locals(py, locals.clone(), async move {
224-
Self::run_bot(backend, handlers, Some(locals)).await
239+
Self::run_bot(backend, handlers, Some(locals), bot_state).await
225240
})
241+
226242
}
227243

228244
/// Starts the bot and blocks until it exits.
@@ -234,7 +250,7 @@ impl Tryx {
234250
info!("starting bot in blocking mode via Tryx.run_blocking");
235251
let backend = self.backend.clone();
236252
let handlers = Python::attach(|py| self.handlers.clone_ref(py));
237-
253+
let bot_state = self.bot.clone();
238254
py.detach(move || {
239255
let rt = runtime::Runtime::new()
240256
.map_err(|e| {
@@ -243,7 +259,7 @@ impl Tryx {
243259
})?;
244260

245261
rt.block_on(async {
246-
let mut bot_task = tokio::spawn(Self::run_bot(backend, handlers, None));
262+
let mut bot_task = tokio::spawn(Self::run_bot(backend, handlers, None, bot_state));
247263
let mut signal_tick = interval(Duration::from_millis(200));
248264

249265
loop {
@@ -303,4 +319,38 @@ impl Tryx {
303319
})
304320
})
305321
}
306-
}
322+
323+
fn send_message<'py>(&self, py: Python<'py>, to: Py<JID>, message: Py<PyAny>) -> PyResult<Bound<'py, PyAny>> {
324+
let client = {
325+
let state = self
326+
.bot
327+
.lock()
328+
.map_err(|_| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Failed to lock bot state"))?;
329+
state
330+
.clone()
331+
.ok_or_else(|| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Bot is not running"))?
332+
};
333+
334+
let jid = to.bind(py).borrow().as_whatsapp_jid();
335+
336+
// Python protobuf object -> bytes -> Rust proto
337+
let serialized: Vec<u8> = message
338+
.call_method0(py, "SerializeToString")?
339+
.extract(py)?;
340+
341+
let whatsapp_message = WhatsappMessage::decode(serialized.as_slice()).map_err(|e| {
342+
PyErr::new::<pyo3::exceptions::PyValueError, _>(
343+
format!("Failed to decode WhatsAppMessage proto: {}", e),
344+
)
345+
})?;
346+
347+
let locals = get_current_locals(py)?;
348+
future_into_py_with_locals(py, locals, async move {
349+
let _message_id = client
350+
.send_message(jid, whatsapp_message)
351+
.await
352+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
353+
Ok(())
354+
})
355+
}
356+
}

src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2626
_ => {}
2727
}
2828
})
29-
.build()
30-
.await?;
29+
.build();
30+
let mut bot2 = bot.await?;
31+
let g = bot2.client();
32+
g.send_message(to, message)
3133

3234
// Start the bot
33-
bot.run().await?.await?;
35+
bot2.run().await?.await?;
3436
Ok(())
3537
}

src/types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ use prost::Message;
1111
pub struct JID {
1212
inner: Arc<WhatsAppJID>,
1313
}
14+
15+
impl JID {
16+
pub fn as_whatsapp_jid(&self) -> WhatsAppJID {
17+
(*self.inner).clone()
18+
}
19+
}
20+
1421
#[pymethods]
1522
impl JID {
1623
#[new]

0 commit comments

Comments
 (0)