#![cfg(target_os = "android")] #![allow(non_snake_case)] use std::ffi::{CStr, CString}; use std::future::Future; use grammers_client::{Client, Config}; use grammers_client::types::{Dialog, LoginToken}; use grammers_session::{PackedChat, Session}; use jni::JNIEnv; use jni::objects::{JObject, JString}; use jni::sys::{jboolean, jint, jlong, jstring}; use log; use log::{error, info, Level}; use once_cell::sync::OnceCell; use tokio::runtime; use tokio::runtime::Runtime; const LOG_MIN_LEVEL: Level = Level::Trace; const LOG_TAG: &str = ".native.talari"; const API_ID: i32 = 0; const API_HASH: &str = ""; type Result = std::result::Result>; static RUNTIME: OnceCell = OnceCell::new(); static CLIENT: OnceCell = OnceCell::new(); fn block_on(future: F) -> F::Output { if RUNTIME.get().is_none() { RUNTIME .set( runtime::Builder::new_current_thread() .enable_all() .build() .unwrap(), ) .ok(); } RUNTIME.get().unwrap().block_on(future) } async fn init_client() -> Result<()> { android_logger::init_once( android_logger::Config::default() .with_min_level(LOG_MIN_LEVEL) .with_tag(LOG_TAG), ); if CLIENT.get().is_some() { info!("Client is already initialized"); return Ok(()); } info!("Connecting to Telegram..."); let client = Client::connect(Config { session: Session::new(), api_id: API_ID, api_hash: API_HASH.to_string(), params: Default::default(), }) .await?; info!("Connected!"); CLIENT .set(client) .map_err(|_| "Client was already initialized")?; Ok(()) } async fn need_login() -> Result { let client = CLIENT.get().ok_or("Client not initialized")?; Ok(client.is_authorized().await?) } async fn request_login_code(phone: &str) -> Result { let client = CLIENT.get().ok_or("Client not initialized")?; let token = client.request_login_code(&phone, API_ID, API_HASH).await?; Ok(token) } async fn sign_in(token: LoginToken, code: &str) -> Result<()> { let client = CLIENT.get().ok_or("Client not initialized")?; client.sign_in(&token, &code).await?; Ok(()) } async fn get_dialogs() -> Result> { let client = CLIENT.get().ok_or("Client not initialized")?; let mut result = Vec::new(); let mut dialogs = client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { result.push(dialog); } Ok(result) } async fn send_message(chat: PackedChat, text: &str) -> Result<()> { let client = CLIENT.get().ok_or("Client not initialized")?; client.send_message(chat, text).await?; Ok(()) } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_initClient(_: JNIEnv, _: JObject) { match block_on(init_client()) { Ok(_) => info!("Client initialized"), Err(e) => error!("Failed to initialize client: {}", e), } } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_needLogin( _: JNIEnv, _: JObject, ) -> jboolean { match block_on(need_login()) { Ok(login) => login.into(), Err(e) => { error!("Failed to check if user is authorized: {}", e); false.into() } } } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_requestLoginCode( env: JNIEnv, _: JObject, phone: JString, ) -> jlong { let phone = CString::from(CStr::from_ptr(env.get_string(phone).unwrap().as_ptr())); match block_on(request_login_code(phone.to_str().unwrap())) { Ok(token) => Box::into_raw(Box::new(token)) as jlong, Err(e) => { error!("Failed to request login code: {}", e); 0 as jlong } } } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_signIn( env: JNIEnv, _: JObject, token_ptr: jlong, code: JString, ) { let token = *Box::from_raw(token_ptr as *mut LoginToken); let code = CString::from(CStr::from_ptr(env.get_string(code).unwrap().as_ptr())); match block_on(sign_in(token, code.to_str().unwrap())) { Ok(_) => info!("Sign in success"), Err(e) => error!("Failed to sign in: {}", e), } } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_getDialogs( _: JNIEnv, _: JObject, ) -> jlong { match block_on(get_dialogs()) { Ok(dialogs) => Box::into_raw(Box::new(dialogs)) as jlong, Err(e) => { error!("Failed to get dialogs: {}", e); 0 as jlong } } } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogCount( _: JNIEnv, _: JObject, dialogs_ptr: jlong, ) -> jint { let dialogs = Box::leak(Box::from_raw(dialogs_ptr as *mut Vec)); dialogs.len() as jint } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogPacked( env: JNIEnv, _: JObject, dialogs_ptr: jlong, index: jint, ) -> jstring { let dialogs = Box::leak(Box::from_raw(dialogs_ptr as *mut Vec)); let packed = dialogs[index as usize].chat().pack().to_hex(); let output = env.new_string(packed).unwrap(); output.into_inner() } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogTitle( env: JNIEnv, _: JObject, dialogs_ptr: jlong, index: jint, ) -> jstring { let dialogs = Box::leak(Box::from_raw(dialogs_ptr as *mut Vec)); let title = dialogs[index as usize].chat().name(); let output = env.new_string(title).unwrap(); output.into_inner() } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_freeDialogs( _: JNIEnv, _: JObject, dialogs_ptr: jlong, ) { let _ = Box::from_raw(dialogs_ptr as *mut Vec); } #[no_mangle] pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_sendMessage( env: JNIEnv, _: JObject, packed: JString, text: JString, ) { let packed = CString::from(CStr::from_ptr(env.get_string(packed).unwrap().as_ptr())); let text = CString::from(CStr::from_ptr(env.get_string(text).unwrap().as_ptr())); let packed = PackedChat::from_hex(packed.to_str().unwrap()).unwrap(); match block_on(send_message(packed, text.to_str().unwrap())) { Ok(_) => info!("Message sent"), Err(e) => error!("Failed to send message: {}", e), } }