1- use std:: sync:: { Arc , Once } ;
1+ use std:: sync:: { Arc , Mutex , Once } ;
22use std:: future:: Future ;
33use std:: pin:: Pin ;
44use pyo3:: { Bound , PyAny , pyclass, pymethods} ;
@@ -8,16 +8,20 @@ use pyo3_async_runtimes::tokio::{future_into_py_with_locals, get_current_locals,
88use tokio:: runtime;
99use tokio:: time:: { Duration , interval} ;
1010use wacore:: types:: events:: Event ;
11+ use whatsapp_rust:: Client ;
1112use whatsapp_rust:: bot:: Bot ;
1213use whatsapp_rust:: store:: Backend ;
1314use whatsapp_rust_tokio_transport:: TokioWebSocketTransportFactory ;
1415use whatsapp_rust_ureq_http_client:: UreqHttpClient ;
16+ use waproto:: whatsapp:: Message as WhatsappMessage ;
17+ use prost:: Message ;
1518use tokio:: signal;
1619use tracing:: { debug, error, info, warn} ;
1720use tracing_subscriber:: EnvFilter ;
1821
1922use 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
2226static LOG_INIT : Once = Once :: new ( ) ;
2327
@@ -36,13 +40,15 @@ fn init_logging() {
3640pub struct Tryx {
3741 backend : Arc < dyn Backend > ,
3842 handlers : Py < Dispatcher > ,
43+ bot : Arc < Mutex < Option < Arc < Client > > > > ,
3944}
4045
4146impl 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+ }
0 commit comments