Talaria/native/src/lib.rs

366 lines
10 KiB
Rust

#![cfg(target_os = "android")]
#![allow(non_snake_case)]
mod db;
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 log;
use log::{error, info, Level};
use once_cell::sync::OnceCell;
use std::collections::HashMap;
use std::future::Future;
use std::net::SocketAddr;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::Mutex;
use tokio::runtime;
use tokio::runtime::Runtime;
include!(concat!(env!("OUT_DIR"), "/talaria.uniffi.rs"));
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
const LOG_MIN_LEVEL: Level = Level::Trace;
const LOG_TAG: &str = ".native.talari";
const API_ID: i32 = {
let mut index = 0;
let mut value = 0;
let api_id = env!("TALARIA_API_ID");
let bytes = api_id.as_bytes();
while index < bytes.len() {
match bytes[index] {
b @ b'0'..=b'9' => value = value * 10 + (b - b'0') as i32,
_ => panic!("non-digit character found in API ID"),
}
index += 1
}
value
};
const API_HASH: &str = env!("TALARIA_API_HASH");
const SERVER_ADDR: &str = env!("TALARIA_SERVER_ADDR");
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
static CLIENT: OnceCell<Client> = OnceCell::new();
static DATABASE: Mutex<Option<sqlite::Connection>> = Mutex::new(None);
fn block_on<F: Future>(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(());
}
let guard = DATABASE.lock().unwrap();
let conn = match guard.as_ref() {
Some(c) => c,
None => {
return Err("Database was not initialized".into());
}
};
info!("Connecting to Telegram...");
let session = Session::new();
let sessions = db::get_sessions(conn)?;
if let Some(s) = sessions.get(0) {
match (s.user_id, s.dc_id, s.bot) {
(Some(id), Some(dc), Some(bot)) => session.set_user(id, dc, bot),
_ => {}
}
match (s.pts, s.qts, s.seq, s.date) {
(Some(pts), Some(qts), Some(seq), Some(date)) => session.set_state(UpdateState {
pts,
qts,
seq,
date,
channels: HashMap::new(),
}),
_ => {}
}
match (s.dc_id, s.dc_addr.as_ref(), s.dc_port, s.dc_auth) {
(Some(id), Some(addr), Some(port), Some(auth)) => {
session.insert_dc(id, SocketAddr::new(addr.parse().unwrap(), port), auth)
}
_ => {}
}
}
let client = Client::connect(Config {
session,
api_id: API_ID,
api_hash: API_HASH.to_string(),
params: InitParams {
server_addr: if SERVER_ADDR.is_empty() {
None
} else {
Some(SERVER_ADDR.parse().unwrap())
},
..Default::default()
},
})
.await?;
info!("Connected!");
CLIENT
.set(client)
.map_err(|_| "Client was already initialized")?;
Ok(())
}
async fn need_login() -> Result<bool> {
let client = CLIENT.get().ok_or("Client not initialized")?;
Ok(client.is_authorized().await?)
}
async fn request_login_code(phone: &str) -> Result<LoginToken> {
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?;
let guard = DATABASE.lock().unwrap();
let conn = match guard.as_ref() {
Some(c) => c,
None => {
return Err("Database was not initialized".into());
}
};
let mut session = db::create_session(conn)?;
let s = client.session();
if let Some(user) = s.get_user() {
session.user_id = Some(user.id);
session.dc_id = Some(user.dc);
session.bot = Some(user.bot);
}
if let Some(state) = s.get_state() {
session.pts = Some(state.pts);
session.qts = Some(state.qts);
session.seq = Some(state.seq);
session.date = Some(state.date);
}
if let Some(dc_id) = session.dc_id {
for dc in s.get_dcs() {
if dc.id == dc_id {
if let Some(ipv4) = dc.ipv4 {
session.dc_addr = Some(Ipv4Addr::from(ipv4.to_le_bytes()).to_string())
} else if let Some(ipv6) = dc.ipv6 {
session.dc_addr = Some(Ipv6Addr::from(ipv6).to_string())
}
session.dc_port = Some(dc.port as u16);
session.dc_auth = dc.auth.map(|b| b.try_into().unwrap());
break;
}
}
}
db::update_session(conn, &session)?;
Ok(())
}
async fn get_dialogs() -> Result<Vec<Dialog>> {
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(())
}
pub fn initDatabase(path: String) {
let mut guard = DATABASE.lock().unwrap();
if guard.is_some() {
info!("Database is already initialized");
}
match db::init_connection(&path) {
Ok(conn) => *guard = Some(conn),
Err(e) => error!("Failed to initialize database: {}", e),
}
}
pub fn initClient() {
match block_on(init_client()) {
Ok(_) => info!("Client initialized"),
Err(e) => error!("Failed to initialize client: {}", e),
}
}
pub fn needLogin() -> bool {
match block_on(need_login()) {
Ok(login) => login.into(),
Err(e) => {
error!("Failed to check if user is authorized: {}", e);
false.into()
}
}
}
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 u64
}
}
}
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)) {
Ok(_) => info!("Sign in success"),
Err(e) => error!("Failed to sign in: {}", e),
}
}
pub fn getDialogs() -> u64 {
match block_on(get_dialogs()) {
Ok(dialogs) => Box::into_raw(Box::new(dialogs)) as u64,
Err(e) => {
error!("Failed to get dialogs: {}", e);
0 as u64
}
}
}
pub fn dialogCount(dialogsPtr: u64) -> u32 {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
dialogs.len() as u32
}
pub fn dialogPacked(dialogsPtr: u64, index: u32) -> String {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
dialogs[index as usize].chat().pack().to_hex()
}
pub fn dialogTitle(dialogsPtr: u64, index: u32) -> String {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
dialogs[index as usize].chat().name().to_string()
}
pub fn dialogSender(dialogsPtr: u64, index: u32) -> String {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
if let Some(msg) = dialogs[index as usize].last_message.as_ref() {
if let Some(sender) = msg.sender() {
sender.name().to_string()
} else {
"unknown".to_string()
}
} else {
String::new()
}
}
pub fn dialogText(dialogsPtr: u64, index: u32) -> String {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
if let Some(msg) = dialogs[index as usize].last_message.as_ref() {
msg.text().to_string()
} else {
"".to_string()
}
}
pub fn dialogTime(dialogsPtr: u64, index: u32) -> String {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
if let Some(msg) = dialogs[index as usize].last_message.as_ref() {
msg.date().to_rfc3339().to_string()
} else {
String::new()
}
}
pub fn dialogAck(dialogsPtr: u64, index: u32) -> u32 {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
let dialog = &dialogs[index as usize];
if let Some(msg) = dialog.last_message.as_ref() {
if msg.outgoing() {
match &dialog.dialog {
tl::enums::Dialog::Dialog(d) => {
if msg.id() <= d.read_inbox_max_id {
2
} else {
1
}
}
tl::enums::Dialog::Folder(_) => 0,
}
} else {
0
}
} else {
0
}
}
pub fn dialogPin(dialogsPtr: u64, index: u32) -> u32 {
let dialogs = unsafe { &mut *(dialogsPtr as *mut Vec<Dialog>) };
let pinned = match &dialogs[index as usize].dialog {
tl::enums::Dialog::Dialog(d) => d.pinned,
tl::enums::Dialog::Folder(f) => f.pinned,
};
pinned as u32
}
pub fn freeDialogs(dialogsPtr: u64) {
let _ = unsafe { Box::from_raw(dialogsPtr as *mut Vec<Dialog>) };
}
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),
}
}