diff --git a/app/build.gradle b/app/build.gradle index 31e6835..38553ac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,6 +57,7 @@ dependencies { implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" implementation 'androidx.compose.material:material:1.2.1' implementation "androidx.navigation:navigation-compose:2.5.2" + implementation "net.java.dev.jna:jna:5.12.0@aar" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' @@ -68,7 +69,7 @@ dependencies { // See https://github.com/mozilla/rust-android-gradle for other targets and required toolchains cargo { module = "../native" - libname = "talaria" + libname = "uniffi_talaria" targets = ["arm64"] profile = 'release' } @@ -78,3 +79,14 @@ tasks.whenTaskAdded { task -> task.dependsOn 'cargoBuild' } } + +// See https://mozilla.github.io/uniffi-rs/kotlin/gradle.html for more details on this snippet +android.applicationVariants.all { variant -> + def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) { + workingDir "${project.projectDir}" + commandLine 'uniffi-bindgen', 'generate', '../native/src/talaria.udl', '--language', 'kotlin', '--no-format', '--out-dir', "${buildDir}/generated/source/uniffi/${variant.name}/java" + } + variant.javaCompileProvider.get().dependsOn(t) + def sourceSet = variant.sourceSets.find { it.name == variant.name } + sourceSet.java.srcDir new File(buildDir, "generated/source/uniffi/${variant.name}/java") +} diff --git a/app/src/main/java/dev/lonami/talaria/MainActivity.kt b/app/src/main/java/dev/lonami/talaria/MainActivity.kt index ff1df47..74f9591 100644 --- a/app/src/main/java/dev/lonami/talaria/MainActivity.kt +++ b/app/src/main/java/dev/lonami/talaria/MainActivity.kt @@ -7,8 +7,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.ui.Modifier -import dev.lonami.talaria.bindings.Native import dev.lonami.talaria.ui.theme.TalariaTheme +import uniffi.talaria.initClient +import uniffi.talaria.initDatabase class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -24,7 +25,7 @@ class MainActivity : ComponentActivity() { } } - Native.initDatabase(getDatabasePath("talaria.db").path) - Native.initClient() + initDatabase(getDatabasePath("talaria.db").path) + initClient() } } diff --git a/app/src/main/java/dev/lonami/talaria/TalariaApp.kt b/app/src/main/java/dev/lonami/talaria/TalariaApp.kt index 74c4e51..d8189e9 100644 --- a/app/src/main/java/dev/lonami/talaria/TalariaApp.kt +++ b/app/src/main/java/dev/lonami/talaria/TalariaApp.kt @@ -18,11 +18,11 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import dev.lonami.talaria.bindings.Native import dev.lonami.talaria.ui.screens.ChatScreen import dev.lonami.talaria.ui.screens.DialogScreen import dev.lonami.talaria.ui.screens.LoginScreen import kotlinx.coroutines.launch +import uniffi.talaria.needLogin enum class TalariaScreen(@StringRes val title: Int) { Login(title = R.string.app_name), @@ -148,7 +148,7 @@ fun TalariaApp(modifier: Modifier = Modifier) { val currentScreen = TalariaScreen.valueOf(backStackEntry?.destination?.route ?: TalariaScreen.Login.name) - val loggedIn by remember { mutableStateOf(!Native.needLogin()) } + val loggedIn by remember { mutableStateOf(!needLogin()) } var selectedDialog by remember { mutableStateOf("") } val drawerState = rememberDrawerState(DrawerValue.Closed) diff --git a/app/src/main/java/dev/lonami/talaria/bindings/Native.kt b/app/src/main/java/dev/lonami/talaria/bindings/Native.kt deleted file mode 100644 index b65436b..0000000 --- a/app/src/main/java/dev/lonami/talaria/bindings/Native.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.lonami.talaria.bindings - -object Native { - init { - System.loadLibrary("talaria") - } - - external fun initDatabase(path: String) - external fun initClient() - external fun needLogin(): Boolean - external fun requestLoginCode(phone: String): Long - external fun signIn(tokenPtr: Long, code: String) - external fun getDialogs(): Long - external fun dialogCount(dialogsPtr: Long): Int - external fun dialogPacked(dialogsPtr: Long, index: Int): String - external fun dialogTitle(dialogsPtr: Long, index: Int): String - external fun dialogSender(dialogsPtr: Long, index: Int): String - external fun dialogText(dialogsPtr: Long, index: Int): String - external fun dialogTime(dialogsPtr: Long, index: Int): String - external fun dialogAck(dialogsPtr: Long, index: Int): Int - external fun dialogPin(dialogsPtr: Long, index: Int): Int - external fun freeDialogs(dialogsPtr: Long) - external fun sendMessage(packed: String, text: String) -} diff --git a/app/src/main/java/dev/lonami/talaria/data/DialogRepository.kt b/app/src/main/java/dev/lonami/talaria/data/DialogRepository.kt index 791f8b6..bdcdcd2 100644 --- a/app/src/main/java/dev/lonami/talaria/data/DialogRepository.kt +++ b/app/src/main/java/dev/lonami/talaria/data/DialogRepository.kt @@ -1,9 +1,9 @@ package dev.lonami.talaria.data -import dev.lonami.talaria.bindings.Native import dev.lonami.talaria.models.Dialog import dev.lonami.talaria.models.MessageAck import dev.lonami.talaria.models.MessagePreview +import uniffi.talaria.* import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -15,34 +15,34 @@ class NativeDialogRepository : DialogRepository { override fun loadDialogs(): List { val dialogs = mutableListOf() - val dialogPtr = Native.getDialogs() + val dialogPtr = getDialogs() try { - val dialogCount = Native.dialogCount(dialogPtr) - for (i in 0 until dialogCount) { + val dialogCount = dialogCount(dialogPtr) + for (i in 0U until dialogCount) { dialogs.add( Dialog( - id = Native.dialogPacked(dialogPtr, i), - title = Native.dialogTitle(dialogPtr, i), + id = dialogPacked(dialogPtr, i), + title = dialogTitle(dialogPtr, i), lastMessage = MessagePreview( - sender = Native.dialogSender(dialogPtr, i), - text = Native.dialogText(dialogPtr, i), + sender = dialogSender(dialogPtr, i), + text = dialogText(dialogPtr, i), date = LocalDateTime.parse( - Native.dialogTime(dialogPtr, i), + dialogTime(dialogPtr, i), DateTimeFormatter.ISO_OFFSET_DATE_TIME ), - ack = when (Native.dialogAck(dialogPtr, i)) { - 0 -> MessageAck.RECEIVED - 1 -> MessageAck.SENT - 2 -> MessageAck.SEEN + ack = when (dialogAck(dialogPtr, i)) { + 0U -> MessageAck.RECEIVED + 1U -> MessageAck.SENT + 2U -> MessageAck.SEEN else -> MessageAck.RECEIVED } ), - pinned = Native.dialogPin(dialogPtr, i) != 0 + pinned = dialogPin(dialogPtr, i) != 0U ) ) } } finally { - Native.freeDialogs(dialogPtr) + freeDialogs(dialogPtr) } return dialogs diff --git a/app/src/main/java/dev/lonami/talaria/ui/screens/LoginScreen.kt b/app/src/main/java/dev/lonami/talaria/ui/screens/LoginScreen.kt index f401f0e..edcf7b8 100644 --- a/app/src/main/java/dev/lonami/talaria/ui/screens/LoginScreen.kt +++ b/app/src/main/java/dev/lonami/talaria/ui/screens/LoginScreen.kt @@ -18,8 +18,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import dev.lonami.talaria.R -import dev.lonami.talaria.bindings.Native import dev.lonami.talaria.ui.theme.TalariaTheme +import uniffi.talaria.requestLoginCode +import uniffi.talaria.signIn enum class LoginStage { ASK_PHONE, @@ -105,7 +106,7 @@ fun LoginScreen(onConfirmOtp: () -> Unit, modifier: Modifier = Modifier) { var phone by remember { mutableStateOf("") } var otp by remember { mutableStateOf("") } - var tokenPtr by remember { mutableStateOf(0L) } + var tokenPtr by remember { mutableStateOf(0UL) } Column( modifier = modifier @@ -127,7 +128,7 @@ fun LoginScreen(onConfirmOtp: () -> Unit, modifier: Modifier = Modifier) { phone, onPhoneChanged = { phone = it }, onSendCode = { - tokenPtr = Native.requestLoginCode(phone) + tokenPtr = requestLoginCode(phone) stage = LoginStage.ASK_CODE } ) @@ -135,7 +136,7 @@ fun LoginScreen(onConfirmOtp: () -> Unit, modifier: Modifier = Modifier) { otp, onOtpChanged = { otp = it }, onConfirmOtp = { - Native.signIn(tokenPtr, otp) + signIn(tokenPtr, otp) onConfirmOtp() } ) diff --git a/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt b/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt index cd77f4e..1cabbd9 100644 --- a/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt +++ b/app/src/main/java/dev/lonami/talaria/ui/state/ChatViewModel.kt @@ -1,7 +1,6 @@ package dev.lonami.talaria.ui.state import androidx.lifecycle.ViewModel -import dev.lonami.talaria.bindings.Native import dev.lonami.talaria.data.MessageRepository import dev.lonami.talaria.models.Message import kotlinx.coroutines.flow.MutableStateFlow @@ -18,7 +17,7 @@ class ChatViewModel : ViewModel() { } fun sendMessage(dialog: String, message: String) { - Native.sendMessage(dialog, message) + sendMessage(dialog, message) _uiState.update { state -> state.messages.add(Message("You", message)) state diff --git a/build.gradle b/build.gradle index 5f07436..afe7c91 100644 --- a/build.gradle +++ b/build.gradle @@ -4,8 +4,10 @@ buildscript { } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.3.0' apply false - id 'com.android.library' version '7.3.0' apply false + // uniffi-recommended Gradle script fails with Android plugin 7.3.1. + // See https://github.com/mozilla/uniffi-rs/issues/1386 for details. + id 'com.android.application' version '7.2.2' apply false + id 'com.android.library' version '7.2.2' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" } diff --git a/native/Cargo.toml b/native/Cargo.toml index 684c92d..b2449c5 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -4,11 +4,12 @@ version = "0.1.0" edition = "2021" [lib] -name = "talaria" +name = "uniffi_talaria" crate-type = ["cdylib"] [dependencies] jni = { version = "0.10.2", default-features = false } +uniffi = "0.21.0" # v0.4 of grammers-* is currently unreleased; clone the project and use path dependencies grammers-client = { version = "0.4.1" } grammers-tl-types = { version = "0.4.0" } @@ -19,5 +20,8 @@ android_logger = "0.11.1" once_cell = "1.15.0" sqlite = "0.27.0" +[build-dependencies] +uniffi_build = "0.21.0" + [profile.release] lto = true diff --git a/native/build.rs b/native/build.rs new file mode 100644 index 0000000..2d91551 --- /dev/null +++ b/native/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("src/talaria.udl").unwrap(); +} diff --git a/native/src/lib.rs b/native/src/lib.rs index 70be889..42605d0 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -7,14 +7,10 @@ use grammers_client::types::{Dialog, LoginToken}; use grammers_client::{Client, Config, InitParams}; use grammers_session::{PackedChat, Session, UpdateState}; use grammers_tl_types as tl; -use jni::objects::{JObject, JString}; -use jni::sys::{jboolean, jint, jlong, jstring}; -use jni::JNIEnv; use log; use log::{error, info, Level}; use once_cell::sync::OnceCell; use std::collections::HashMap; -use std::ffi::{CStr, CString}; use std::future::Future; use std::net::SocketAddr; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -22,6 +18,8 @@ use std::sync::Mutex; use tokio::runtime; use tokio::runtime::Runtime; +include!(concat!(env!("OUT_DIR"), "/talaria.uniffi.rs")); + type Result = std::result::Result>; const LOG_MIN_LEVEL: Level = Level::Trace; @@ -211,37 +209,26 @@ async fn send_message(chat: PackedChat, text: &str) -> Result<()> { Ok(()) } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_initDatabase( - env: JNIEnv, - _: JObject, - path: JString, -) { +pub fn initDatabase(path: String) { let mut guard = DATABASE.lock().unwrap(); if guard.is_some() { info!("Database is already initialized"); } - let path = CString::from(CStr::from_ptr(env.get_string(path).unwrap().as_ptr())); - match db::init_connection(path.to_str().unwrap()) { + match db::init_connection(&path) { Ok(conn) => *guard = Some(conn), Err(e) => error!("Failed to initialize database: {}", e), } } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_initClient(_: JNIEnv, _: JObject) { +pub fn initClient() { 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 { +pub fn needLogin() -> bool { match block_on(need_login()) { Ok(login) => login.into(), Err(e) => { @@ -251,101 +238,56 @@ pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_needLogin( } } -#[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, +pub fn requestLoginCode(phone: String) -> u64 { + match block_on(request_login_code(&phone)) { + Ok(token) => Box::into_raw(Box::new(token)) as u64, Err(e) => { error!("Failed to request login code: {}", e); - 0 as jlong + 0 as u64 } } } -#[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())); +pub fn signIn(token_ptr: u64, code: String) { + let token = unsafe { *Box::from_raw(token_ptr as *mut LoginToken) }; - match block_on(sign_in(token, code.to_str().unwrap())) { + match block_on(sign_in(token, &code)) { 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 { +pub fn getDialogs() -> u64 { match block_on(get_dialogs()) { - Ok(dialogs) => Box::into_raw(Box::new(dialogs)) as jlong, + Ok(dialogs) => Box::into_raw(Box::new(dialogs)) as u64, Err(e) => { error!("Failed to get dialogs: {}", e); - 0 as jlong + 0 as u64 } } } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogCount( - _: JNIEnv, - _: JObject, - dialogs_ptr: jlong, -) -> jint { - let dialogs = &mut *(dialogs_ptr as *mut Vec); - dialogs.len() as jint +pub fn dialogCount(dialogsPtr: u64) -> u32 { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; + dialogs.len() as u32 } -#[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 = &mut *(dialogs_ptr as *mut Vec); +pub fn dialogPacked(dialogsPtr: u64, index: u32) -> String { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; - let packed = dialogs[index as usize].chat().pack().to_hex(); - let output = env.new_string(packed).unwrap(); - output.into_inner() + dialogs[index as usize].chat().pack().to_hex() } -#[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 = &mut *(dialogs_ptr as *mut Vec); +pub fn dialogTitle(dialogsPtr: u64, index: u32) -> String { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; - let title = dialogs[index as usize].chat().name(); - let output = env.new_string(title).unwrap(); - output.into_inner() + dialogs[index as usize].chat().name().to_string() } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogSender( - env: JNIEnv, - _: JObject, - dialogs_ptr: jlong, - index: jint, -) -> jstring { - let dialogs = &mut *(dialogs_ptr as *mut Vec); +pub fn dialogSender(dialogsPtr: u64, index: u32) -> String { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; - let sender = if let Some(msg) = dialogs[index as usize].last_message.as_ref() { + if let Some(msg) = dialogs[index as usize].last_message.as_ref() { if let Some(sender) = msg.sender() { sender.name().to_string() } else { @@ -353,58 +295,34 @@ pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogSender( } } else { String::new() - }; - let output = env.new_string(sender).unwrap(); - output.into_inner() + } } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogText( - env: JNIEnv, - _: JObject, - dialogs_ptr: jlong, - index: jint, -) -> jstring { - let dialogs = &mut *(dialogs_ptr as *mut Vec); +pub fn dialogText(dialogsPtr: u64, index: u32) -> String { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; - let text = if let Some(msg) = dialogs[index as usize].last_message.as_ref() { - msg.text() + if let Some(msg) = dialogs[index as usize].last_message.as_ref() { + msg.text().to_string() } else { - "" - }; - let output = env.new_string(text).unwrap(); - output.into_inner() + "".to_string() + } } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogTime( - env: JNIEnv, - _: JObject, - dialogs_ptr: jlong, - index: jint, -) -> jstring { - let dialogs = &mut *(dialogs_ptr as *mut Vec); +pub fn dialogTime(dialogsPtr: u64, index: u32) -> String { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; - let time = if let Some(msg) = dialogs[index as usize].last_message.as_ref() { + if let Some(msg) = dialogs[index as usize].last_message.as_ref() { msg.date().to_rfc3339().to_string() } else { String::new() - }; - let output = env.new_string(time).unwrap(); - output.into_inner() + } } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogAck( - _: JNIEnv, - _: JObject, - dialogs_ptr: jlong, - index: jint, -) -> jint { - let dialogs = &mut *(dialogs_ptr as *mut Vec); +pub fn dialogAck(dialogsPtr: u64, index: u32) -> u32 { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; let dialog = &dialogs[index as usize]; - let ack = if let Some(msg) = dialog.last_message.as_ref() { + if let Some(msg) = dialog.last_message.as_ref() { if msg.outgoing() { match &dialog.dialog { tl::enums::Dialog::Dialog(d) => { @@ -421,47 +339,26 @@ pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogAck( } } else { 0 - }; - ack + } } -#[no_mangle] -pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogPin( - _: JNIEnv, - _: JObject, - dialogs_ptr: jlong, - index: jint, -) -> jint { - let dialogs = &mut *(dialogs_ptr as *mut Vec); +pub fn dialogPin(dialogsPtr: u64, index: u32) -> u32 { + let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec) }; let pinned = match &dialogs[index as usize].dialog { tl::enums::Dialog::Dialog(d) => d.pinned, tl::enums::Dialog::Folder(f) => f.pinned, }; - pinned as jint + pinned as u32 } -#[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); +pub fn freeDialogs(dialogsPtr: u64) { + let _ = unsafe { Box::from_raw(dialogsPtr 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())) { +pub fn sendMessage(packed: String, text: String) { + let packed = PackedChat::from_hex(&packed).unwrap(); + match block_on(send_message(packed, &text)) { Ok(_) => info!("Message sent"), Err(e) => error!("Failed to send message: {}", e), } diff --git a/native/src/talaria.udl b/native/src/talaria.udl new file mode 100644 index 0000000..ae6144b --- /dev/null +++ b/native/src/talaria.udl @@ -0,0 +1,18 @@ +namespace talaria { + void initDatabase(string path); + void initClient(); + boolean needLogin(); + u64 requestLoginCode(string phone); + void signIn(u64 tokenPtr, string code); + u64 getDialogs(); + u32 dialogCount(u64 dialogsPtr); + string dialogPacked(u64 dialogsPtr, u32 index); + string dialogTitle(u64 dialogsPtr, u32 index); + string dialogSender(u64 dialogsPtr, u32 index); + string dialogText(u64 dialogsPtr, u32 index); + string dialogTime(u64 dialogsPtr, u32 index); + u32 dialogAck(u64 dialogsPtr, u32 index); + u32 dialogPin(u64 dialogsPtr, u32 index); + void freeDialogs(u64 dialogsPtr); + void sendMessage(string packed, string text); +};