diff --git a/Cargo.lock b/Cargo.lock index 2852f2f..af289e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,9 +379,9 @@ checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "async-trait" -version = "0.1.48" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf" +checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48" dependencies = [ "proc-macro2", "quote", @@ -873,9 +873,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.0.2" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" +checksum = "639891fde0dbea823fc3d798a0fdf9d2f9440a42d64a78ab3488b0ca025117b3" dependencies = [ "byteorder", "digest", @@ -1295,7 +1295,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", "winapi", ] @@ -1344,9 +1344,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" +checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" dependencies = [ "futures-channel", "futures-core", @@ -1359,9 +1359,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" dependencies = [ "futures-core", "futures-sink", @@ -1369,15 +1369,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" +checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" [[package]] name = "futures-executor" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" +checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" dependencies = [ "futures-core", "futures-task", @@ -1386,9 +1386,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" +checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" [[package]] name = "futures-lite" @@ -1407,9 +1407,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" +checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -1419,21 +1419,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" +checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" [[package]] name = "futures-task" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" +checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" [[package]] name = "futures-util" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" dependencies = [ "futures-channel", "futures-core", @@ -1507,6 +1507,41 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +[[package]] +name = "glib" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c685013b7515e668f1b57a165b009d4d28cb139a8a989bbd699c10dad29d0c5" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", +] + +[[package]] +name = "glib-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" +dependencies = [ + "anyhow", + "heck", + "itertools", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "glib-sys" version = "0.10.1" @@ -1536,6 +1571,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gobject-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -1926,9 +1972,9 @@ dependencies = [ [[package]] name = "mailparse" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31de1f9043c582efde7dbd93de56600df12b6c4488a67eeaefa74ea364019b22" +checksum = "30286c2f0c485ae66dc96864ec64cd0b60497d8e0bc2073ead6999eff5c7b7a6" dependencies = [ "base64 0.12.3", "charset", @@ -2223,11 +2269,17 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", "smallvec", "winapi", ] +[[package]] +name = "paste" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -2406,6 +2458,39 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check 0.9.3", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.3", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2431,17 +2516,34 @@ dependencies = [ name = "purple-plugin-delta" version = "0.1.0" dependencies = [ + "async-std", "deltachat", - "glib-sys", "lazy_static", - "libc", - "purple-sys", + "log", + "purple-rs", ] [[package]] -name = "purple-sys" -version = "0.0.5" -source = "git+https://github.com/lupine/libpurple-rust?branch=with-flared#517054727b28079e67c66136be410eefbd637467" +name = "purple-rs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e506ad6d399bcbc5fcf62c4c2a846a5a9f354f2a479fc88395e809415b53399" +dependencies = [ + "glib", + "glib-sys", + "lazy_static", + "libc", + "log", + "paste", + "purple-sys-flared", + "serde", +] + +[[package]] +name = "purple-sys-flared" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f303166d1606c13323914c863ab57b0e3c4127500a9bf7de20689513210b91" dependencies = [ "bindgen", "glib-sys", @@ -2589,9 +2691,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" dependencies = [ "bitflags", ] @@ -3212,7 +3314,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "rand 0.8.3", - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", "remove_dir_all", "winapi", ] @@ -3321,9 +3423,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" +checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg 1.0.1", "pin-project-lite 0.2.6", @@ -3700,9 +3802,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ "curve25519-dalek", "rand_core 0.5.1", diff --git a/Cargo.toml b/Cargo.toml index e021b35..fba35b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,21 +4,20 @@ version = "0.1.0" authors = ["Nick Thomas "] [lib] -name = "delta" -path = "src/delta.rs" +name = "purple_delta" +path = "src/lib.rs" crate-type = ["dylib"] [dependencies] -libc = "*" -glib-sys = "*" -purple-sys = { git = "https://github.com/lupine/libpurple-rust", branch="with-flared" } deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="v1.51.0" } lazy_static = "1.4.0" +log = "0.4.8" +purple-rs = "*" -[profile.dev] -debug = 0 +# Keep in sync with deltachat-core-rust +[dependencies.async-std] +version = "~1.8" +features = ["unstable"] [profile.release] lto = true - - diff --git a/src/delta.rs b/src/delta.rs deleted file mode 100644 index 0ce9888..0000000 --- a/src/delta.rs +++ /dev/null @@ -1,188 +0,0 @@ -extern crate purple_sys; -extern crate glib_sys; -extern crate libc; -#[macro_use] -extern crate lazy_static; - -/* -mod pointer; -mod server; -mod user; -mod chatroom; -mod message; -*/ -use std::os::raw::{c_void, c_char}; -use std::ptr::null_mut; -use std::boxed::Box; -use std::ffi::CString; // CStr -//use std::sync::RwLock; - -use purple_sys::*; -use purple_sys::PurpleType; -//use purple_sys::PurpleConnectionState; -use purple_sys::PurpleStatusPrimitive; -//use purple_sys::PurpleRoomlistFieldType; -//use message::*; -//use pointer::Pointer; -//use server::ACCOUNT; -//use server::{send_im, send_chat, find_blist_chat, find_chat_token}; - -use glib_sys::{GHashTable, GList}; - -const TRUE: i32 = 1; -const FALSE: i32 = 0; - -lazy_static!{ - // static ref PLUGIN: RwLock = RwLock::new(Pointer::new()); - static ref ICON_FILE: CString = CString::new("delta").unwrap(); - // static ref DELTA_CATEGORY: CString = CString::new("Delta Chat").unwrap(); -} - -fn append_item(list: *mut GList, item: *mut c_void) -> *mut GList { - unsafe { - glib_sys::g_list_append(list as *mut glib_sys::GList, item as *mut libc::c_void) as - *mut GList - } -} - -extern "C" fn list_icon(_: *mut PurpleAccount, _: *mut PurpleBuddy) -> *const c_char { - ICON_FILE.as_ptr() -} - -extern "C" fn status_types(_: *mut PurpleAccount) -> *mut GList { - - let mut list: *mut GList = null_mut(); - - let available = CString::new("available").unwrap(); - let available_name = CString::new("Available").unwrap(); - let offline = CString::new("offline").unwrap(); - let offline_name = CString::new("Offline").unwrap(); - let nick = CString::new("nick").unwrap(); - - let status = unsafe { - purple_status_type_new_with_attrs( - PurpleStatusPrimitive::PURPLE_STATUS_AVAILABLE, - available.as_ptr(), - available_name.as_ptr(), - TRUE, - TRUE, - FALSE, - nick.as_ptr(), - nick.as_ptr(), - purple_value_new(PurpleType::PURPLE_TYPE_STRING), - null_mut() as *mut c_void, - ) - }; - list = append_item(list, status as *mut c_void); - - let status = unsafe { - purple_status_type_new_with_attrs( - PurpleStatusPrimitive::PURPLE_STATUS_OFFLINE, - offline.as_ptr(), - offline_name.as_ptr(), - TRUE, - TRUE, - FALSE, - nick.as_ptr(), - nick.as_ptr(), - purple_value_new(PurpleType::PURPLE_TYPE_STRING), - null_mut() as *mut c_void, - ) - }; - list = append_item(list, status as *mut c_void); - - list -} - -unsafe extern "C" fn login(account: *mut PurpleAccount) { - println!("account: {:?}", account); -} - -extern "C" fn chat_info(_: *mut PurpleConnection) -> *mut GList { - - let list: *mut GList = null_mut(); - - list -} - -unsafe extern "C" fn join_chat(gc: *mut PurpleConnection, components: *mut GHashTable) { - println!("join_chat: {:?}, {:?}", gc, components); -} - -extern "C" fn chat_info_defaults(_: *mut PurpleConnection, _: *const c_char) -> *mut GHashTable { - - let table: *mut GHashTable = null_mut(); - - table -} - -extern "C" fn close(_: *mut PurpleConnection) {} - -extern "C" fn buddy_list(gc: *mut PurpleConnection) -> *mut PurpleRoomlist { - let buddies = unsafe { purple_roomlist_new(purple_connection_get_account(gc)) }; - - buddies -} - -extern "C" fn callback(_plugin: *mut PurplePlugin) -> i32 { - TRUE -} - -// extern "C" fn action_cb(_: *mut PurplePluginAction) { -// -// } - -extern "C" fn actions(_: *mut PurplePlugin, _: *mut c_void) -> *mut GList { - - let list: *mut GList = null_mut(); - - list -} - -#[no_mangle] -pub extern "C" fn purple_init_plugin(plugin: *mut PurplePlugin) -> i32 { - // save plugin pointer - // PLUGIN.write().unwrap().set(plugin as *mut c_void); - - let id = CString::new("prpl-delta").unwrap(); - let name = CString::new("Delta Chat").unwrap(); - let version = CString::new("0.1.0").unwrap(); - let summary = CString::new("Delta Chat is an email-based instant messaging solution").unwrap(); - let description = CString::new("See https://delta.chat for more information").unwrap(); - let author = CString::new("Nick Thomas ").unwrap(); - let home_page = CString::new("https://delta.chat").unwrap(); - - let mut info = Box::new(PurplePluginInfo::new()); - let mut extra_info = Box::new(PurplePluginProtocolInfo::new()); - - unsafe { - - extra_info.list_icon = Some(list_icon); - extra_info.status_types = Some(status_types); - extra_info.login = Some(login); - extra_info.close = Some(close); - extra_info.roomlist_get_list = Some(buddy_list); - extra_info.chat_info = Some(chat_info); - extra_info.chat_info_defaults = Some(chat_info_defaults); - //extra_info.chat_send = Some(send_chat); - extra_info.join_chat = Some(join_chat); - //extra_info.find_blist_chat = Some(find_blist_chat); - //extra_info.send_im = Some(send_im); - - info.id = id.into_raw(); - info.name = name.into_raw(); - info.version = version.into_raw(); - info.summary = summary.into_raw(); - info.description = description.into_raw(); - info.author = author.into_raw(); - info.homepage = home_page.into_raw(); - info.load = Some(callback); - info.actions = Some(actions); - info.extra_info = Box::into_raw(extra_info) as *mut c_void; - - (*plugin).info = Box::into_raw(info); - }; - - unsafe { purple_plugin_register(plugin) } -} - diff --git a/src/delta/mod.rs b/src/delta/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/delta/mod.rs @@ -0,0 +1 @@ + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2ae68e9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,811 @@ +extern crate async_std; +extern crate lazy_static; +extern crate log; +extern crate purple_rs as purple; + +use async_std::sync::Arc; // RwLock +use chat_info::{ChatInfo, PartialChatInfo}; //ChatInfoVersion +use lazy_static::lazy_static; +//use messages::{AccountInfo, ICQSystemHandle, PurpleMessage, SystemMessage}; +use purple::*; +//use std::cell::RefCell; +use std::ffi::{CStr, CString}; +//use std::io::Read; +//use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; + +mod chat_info; +mod delta; +pub mod logging; +//mod messages; + +pub mod status { + use lazy_static::lazy_static; + use std::ffi::CString; + lazy_static! { + pub static ref ONLINE_ID: CString = CString::new("online").unwrap(); + pub static ref ONLINE_NAME: CString = CString::new("Online").unwrap(); + pub static ref OFFLINE_ID: CString = CString::new("offline").unwrap(); + pub static ref OFFLINE_NAME: CString = CString::new("Offline").unwrap(); + } +} + +lazy_static! { + static ref ICON_FILE: CString = CString::new("delta").unwrap(); +} + +mod blist_node { + pub const LAST_SEEN_TIMESTAMP: &str = "last_seen_timestamp"; +} + +mod commands { + pub const IMEX: &str = "imex"; +} + +pub mod chat_states { + pub const JOINED: &str = "joined"; +} + +pub mod conv_data { + use super::HistoryInfo; + use std::cell::RefCell; + use std::rc::Rc; + + pub const CHAT_INFO: &str = "chat_info"; + pub const HISTORY_INFO: &str = "history_info"; + pub type HistoryInfoType = Rc>; +} + +#[derive(Debug, Clone, Default)] +pub struct HistoryInfo { + pub oldest_message_id: Option, + pub oldest_message_timestamp: Option, +} + +#[derive(Debug, Clone)] +pub struct MsgInfo { + pub chat_sn: String, + pub author_sn: String, + pub author_friendly: String, + pub text: String, + pub time: i64, + pub message_id: String, +} + +#[derive(Debug, Default)] +pub struct AccountData { + display_name: String, + imap_host: String, + imap_port: String, + imap_user: String, + imap_pass: String, + smtp_host: String, + smtp_port: String, + smtp_user: String, + smtp_pass: String, + bcc_self: bool, + + // Not exposed: server_flags, selfstatus, e2ee_enabled + session_closed: AtomicBool, + // session: RwLock>, +} + +impl Drop for AccountData { + fn drop(&mut self) { + log::info!("AccountData dropped"); + } +} + +pub type AccountDataBox = Arc; +pub type Handle = purple::Handle; +pub type ProtocolData = purple::ProtocolData; + +pub struct PurpleDelta { + // system: ICQSystemHandle, + connections: purple::Connections, + input_handle: Option, + imex_command_handle: Option, +} + +impl purple::PrplPlugin for PurpleDelta { + type Plugin = Self; + + fn new() -> Self { + logging::init(log::LevelFilter::Debug).expect("Failed to initialize logging"); + // let system = icq::system::spawn(); + Self { + // system, + input_handle: None, + imex_command_handle: None, + connections: purple::Connections::new(), + } + } + fn register(&self, context: RegisterContext) -> RegisterContext { + let info = purple::PrplInfo { + id: "prpl-delta".into(), + name: "Delta Chat".into(), + version: "0.1.0".into(), + summary: "Delta Chat is an email-based instant messaging solution".into(), + description: "See https://delta.chat for more information".into(), + author: "Nick Thomas ".into(), + homepage: "https://code.ur.gs/lupine/purple-plugin-delta".into(), + }; + + context + .with_info(info) + .with_password() + .with_string_option("Display Name".into(), "displayname".into(), "".into()) + .with_string_option("IMAP server host".into(), "mail_server".into(), "".into()) + .with_string_option("IMAP server port".into(), "mail_port".into(), "".into()) + // Username and password are mail_user and mail_pw + .with_string_option("SMTP server host".into(), "send_server".into(), "".into()) + .with_string_option("SMTP server port".into(), "send_port".into(), "".into()) + .with_string_option("SMTP server username".into(), "send_user".into(), "".into()) + .with_password_option("SMTP server password".into(), "send_pw".into(), "".into()) + .with_bool_option("Copy messages to self".into(), "bcc_self".into(), false) + .enable_login() + .enable_load() + .enable_close() + //.enable_chat_info() + //.enable_chat_info_defaults() + //.enable_join_chat() + //.enable_chat_leave() + //.enable_send_im() + //.enable_chat_send() + //.enable_convo_closed() + //.enable_get_chat_name() + .enable_list_icon() + .enable_status_types() + } +} + +impl purple::LoginHandler for PurpleDelta { + fn login(&mut self, account: &mut Account) { + let display_name = account.get_string("displayname", ""); + + let imap_host = account.get_string("mail_server", ""); + let imap_port = account.get_string("mail_port", ""); + let imap_user = account.get_username().unwrap().into(); + let imap_pass = account.get_password().unwrap().into(); + + let smtp_host = account.get_string("send_server", ""); + let smtp_port = account.get_string("send_port", ""); + let smtp_user = account.get_string("send_user", ""); + let smtp_pass = account.get_string("send_pw", ""); + + let bcc_self = account.get_bool("bcc_self", false); + + let protocol_data: AccountDataBox = Arc::new(AccountData { + display_name, + + imap_host, + imap_port, + imap_user, + imap_pass, + + smtp_host, + smtp_port, + smtp_user, + smtp_pass, + + bcc_self, + + session_closed: AtomicBool::new(false), + // session: RwLock::new(None), + }); + + // Safe as long as we remove the account in "close". + unsafe { + self.connections + .add(account.get_connection().unwrap(), protocol_data.clone()) + }; + /* + self.system + .tx + .try_send(PurpleMessage::Login(AccountInfo { + handle: Handle::from(&mut *account), + protocol_data, + })) + .unwrap();*/ + } +} +impl purple::CloseHandler for PurpleDelta { + fn close(&mut self, connection: &mut Connection) { + let handle = Handle::from(&mut *connection); + match self.connections.get(&handle) { + Some(protocol_data) => { + protocol_data + .data + .session_closed + .store(true, Ordering::Relaxed); + self.connections.remove(*connection); + } + None => { + log::error!("Tried closing a closed connection"); + } + } + } +} +impl purple::StatusTypeHandler for PurpleDelta { + fn status_types(_account: &mut Account) -> Vec { + vec![ + StatusType::new( + PurpleStatusPrimitive::PURPLE_STATUS_AVAILABLE, + Some(&status::ONLINE_ID), + Some(&status::ONLINE_NAME), + true, + ), + StatusType::new( + PurpleStatusPrimitive::PURPLE_STATUS_OFFLINE, + Some(&status::OFFLINE_ID), + Some(&status::OFFLINE_NAME), + true, + ), + ] + } +} +impl purple::LoadHandler for PurpleDelta { + fn load(&mut self, _plugin: &purple::Plugin) -> bool { + logging::set_thread_logger(logging::PurpleDebugLogger); + //use std::os::unix::io::AsRawFd; + /* + self.input_handle = Some(self.enable_input( + self.system.input_rx.as_raw_fd(), + purple::PurpleInputCondition::PURPLE_INPUT_READ, + )); + */ + self.imex_command_handle = + Some(self.enable_command(commands::IMEX, "w", "imex <code>")); + + true + } +} + +impl purple::ListIconHandler for PurpleDelta { + fn list_icon(_account: &mut Account) -> &'static CStr { + &ICON_FILE + } +} +/* +impl purple::ChatInfoHandler for PurpleDelta { + fn chat_info(&mut self, _connection: &mut Connection) -> Vec { + vec![purple::prpl::ChatEntry { + label: &chat_info::SN_NAME, + identifier: &chat_info::SN, + required: true, + is_int: false, + min: 0, + max: 0, + secret: false, + }] + } +} + +impl purple::ChatInfoDefaultsHandler for PurpleDelta { + fn chat_info_defaults( + &mut self, + _connection: &mut Connection, + chat_name: Option<&str>, + ) -> purple::StrHashTable { + let mut defaults = purple::StrHashTable::default(); + defaults.insert(chat_info::SN.as_c_str(), chat_name.unwrap_or("")); + defaults + } +} + +impl purple::JoinChatHandler for PurpleDelta { + fn join_chat(&mut self, connection: &mut Connection, data: Option<&mut StrHashTable>) { + let data = match data { + Some(data) => data, + None => { + return; + } + }; + + let stamp = match Self::get_chat_name(Some(data)) { + Some(stamp) => stamp, + None => { + log::error!("No chat name provided"); + return; + } + }; + + log::info!("Joining {}", stamp); + + let handle = Handle::from(&mut *connection); + let protocol_data = self + .connections + .get(&handle) + .expect("Tried joining chat on closed connection"); + + if let Some(chat_states::JOINED) = data.lookup(&chat_info::STATE) { + match PartialChatInfo::from_hashtable(data) { + Some(chat_info) => { + self.conversation_joined(connection, &chat_info); + /* + self.system + .tx + .try_send(PurpleMessage::get_chat_info( + handle, + protocol_data.data.clone(), + chat_info.sn, + )) + .unwrap(); */ + return; + } + None => { + log::error!("Unable to load chat info"); + } + } + } + /* + self.system + .tx + .try_send(PurpleMessage::join_chat( + handle, + protocol_data.data.clone(), + stamp, + )) + .unwrap() */ + } +} + +impl purple::ChatLeaveHandler for PurpleDelta { + fn chat_leave(&mut self, connection: &mut Connection, id: i32) { + log::info!("Chat leave: {}", id); + match Conversation::find(connection, id) { + Some(mut conversation) => { + unsafe { conversation.remove_data::(conv_data::CHAT_INFO) }; + } + None => { + log::warn!("Leaving chat without conversation"); + } + } + } +} + +impl purple::ConvoClosedHandler for PurpleDelta { + fn convo_closed(&mut self, _connection: &mut Connection, who: Option<&str>) { + log::info!("Convo closed: {:?}", who) + } +} + +impl purple::GetChatNameHandler for PurpleDelta { + fn get_chat_name(data: Option<&mut purple::StrHashTable>) -> Option { + data.and_then(|h| h.lookup(chat_info::SN.as_c_str()).map(Into::into)) + } +} + +impl purple::SendIMHandler for PurpleDelta { + fn send_im( + &mut self, + _connection: &mut Connection, + _who: &str, + _message: &str, + _flags: PurpleMessageFlags, + ) -> i32 { + log::warn!("SendIM is not implemented"); + -1 + } +} + +impl purple::ChatSendHandler for PurpleDelta { + fn chat_send( + &mut self, + connection: &mut Connection, + id: i32, + message: &str, + flags: PurpleMessageFlags, + ) -> i32 { + log::info!("{}: {} [{:?}]", id, message, flags); + let mut conversation = match Conversation::find(connection, id) { + Some(c) => c, + None => { + log::error!("Conversation not found"); + return -1; + } + }; + + let sn = match unsafe { conversation.get_data::(conv_data::CHAT_INFO) } { + Some(info) => info.sn.clone(), + None => { + log::error!("SN not found"); + return -1; + } + }; + + let handle = Handle::from(&mut *connection); + let protocol_data = self.connections.get(&handle).expect("Connection closed"); + /* + self.system + .tx + .try_send(PurpleMessage::send_msg( + handle, + protocol_data.data.clone(), + sn, + message.into(), + )) + .unwrap(); */ + 1 + } +} + +impl purple::InputHandler for PurpleDelta { + fn input(&mut self, _fd: i32, _cond: purple::PurpleInputCondition) { + log::debug!("Input"); + /* + // Consume the byte from the input pipe. + let mut buf = [0; 1]; + + self.system + .input_rx + .read_exact(&mut buf) + .expect("Failed to read input pipe"); + + // Consume the actual message. + match self.system.rx.try_recv() { + Ok(message) => self.process_message(message), + Err(async_std::sync::TryRecvError::Empty) => log::error!("Expected message, but empty"), + Err(async_std::sync::TryRecvError::Disconnected) => { + log::error!("System disconnected"); + if let Some(input_handle) = self.input_handle { + self.disable_input(input_handle); + } + } + }; + */ + } +} +*/ +impl purple::CommandHandler for PurpleDelta { + fn command( + &mut self, + conversation: &mut Conversation, + command: &str, + args: &[&str], + ) -> PurpleCmdRet { + log::debug!( + "command: conv={} cmd={} args={:?}", + conversation.get_title().unwrap_or("unknown"), + command, + args + ); + match command { + commands::IMEX => self.command_imex(conversation, args), + _ => { + log::error!("Unknown command: {}", command); + PurpleCmdRet::PURPLE_CMD_RET_FAILED + } + } + } +} + +impl PurpleDelta { + fn command_imex(&mut self, conversation: &mut Conversation, args: &[&str]) -> PurpleCmdRet { + log::debug!("command_imex"); + + if args.len() != 1 { + log::error!( + "command_imex: Unsupported number of args. Got {}", + args.len() + ); + return PurpleCmdRet::PURPLE_CMD_RET_FAILED; + } + /* + let count = { + let input = match args[0].parse::() { + Ok(count) => count, + Err(_) => { + log::error!("command_history: Could not parse count: {}", args[0]); + return PurpleCmdRet::PURPLE_CMD_RET_FAILED; + } + }; + 0 - input as i32 + }; + + let sn = match conversation.get_name() { + Some(name) => name.to_string(), + None => { + log::error!("command_history: SN not found"); + return PurpleCmdRet::PURPLE_CMD_RET_FAILED; + } + }; + + let from_msg_id = { + match unsafe { + conversation.get_data::(conv_data::HISTORY_INFO) + } { + Some(history_info) => { + let history_info = history_info.borrow_mut(); + match &history_info.oldest_message_id { + Some(oldest_message_id) => oldest_message_id.clone(), + None => { + return PurpleCmdRet::PURPLE_CMD_RET_FAILED; + } + } + } + None => { + log::error!("command_history: Can't find message id"); + return PurpleCmdRet::PURPLE_CMD_RET_FAILED; + } + } + }; + + let handle = Handle::from(&mut conversation.get_connection()); + + let protocol_data = self + .connections + .get(&handle) + .expect("Tried joining chat on closed connection"); + + self.system + .tx + .try_send(PurpleMessage::fetch_history( + handle, + protocol_data.data.clone(), + sn, + from_msg_id, + count, + )) + .unwrap(); + */ + PurpleCmdRet::PURPLE_CMD_RET_OK + } + /* + fn process_message(&mut self, message: SystemMessage) { + match message { + SystemMessage::ExecAccount { handle, function } => { + self.connections + .get(handle) + .map(|protocol_data| function(&mut protocol_data.account)) + .or_else(|| { + log::warn!("The account connection has been closed"); + None + }); + } + SystemMessage::ExecConnection { handle, function } => { + self.connections + .get(handle) + .map(|protocol_data| function(&mut protocol_data.connection)) + .or_else(|| { + log::warn!("The account connection has been closed"); + None + }); + } + SystemMessage::ExecHandle { handle, function } => { + self.connections + .get(handle) + .map(|mut protocol_data| function(self, &mut protocol_data)) + .or_else(|| { + log::warn!("The account connection has been closed"); + None + }); + } + SystemMessage::FlushLogs => logging::flush(), + } + } + + pub fn serv_got_chat_in(&mut self, connection: &mut Connection, msg_info: MsgInfo) { + match purple::Chat::find(&mut connection.get_account(), &msg_info.chat_sn) { + Some(mut chat) => { + // Get the chat and the last seen timestamp. + let mut node = chat.as_blist_node(); + let last_timestamp: i64 = node + .get_string(&blist_node::LAST_SEEN_TIMESTAMP) + .and_then(|t| t.parse::().ok()) + .unwrap_or(0); + let new_timestamp = msg_info.time; + + // Only trigger conversation_joined if this is a new message. + let conversation = { + if new_timestamp > last_timestamp { + node.set_string( + &blist_node::LAST_SEEN_TIMESTAMP, + &new_timestamp.to_string(), + ); + Some(self.conversation_joined( + connection, + &PartialChatInfo { + sn: msg_info.chat_sn.clone(), + title: msg_info.chat_sn.clone(), + ..Default::default() + }, + )) + } else { + None + } + }; + + // Get the conversation and set the oldest *displayed* messageId. + // This is the oldest message that the user can see in the chat window. + // + // If there is no conversation yet, that is okay. It means that we haven't + // seen new messages yet. + if let Some(mut conversation) = conversation { + let history_info = { + match unsafe { + conversation + .get_data::(conv_data::HISTORY_INFO) + } { + Some(history_info) => history_info.clone(), + None => { + let history_info = Rc::new(RefCell::new(HistoryInfo { + oldest_message_id: None, + oldest_message_timestamp: None, + })); + unsafe { + conversation.set_data::( + conv_data::HISTORY_INFO, + history_info.clone(), + ) + }; + history_info + } + } + }; + let mut history_info = history_info.borrow_mut(); + + match history_info.oldest_message_timestamp { + None => { + history_info.oldest_message_id = Some(msg_info.message_id.clone()); + history_info.oldest_message_timestamp = Some(msg_info.time); + } + Some(existing_timestamp) => { + if msg_info.time < existing_timestamp { + history_info.oldest_message_id = Some(msg_info.message_id.clone()); + history_info.oldest_message_timestamp = Some(msg_info.time); + } + } + } + } + } + None => { + // Don't log errors for DMs because they are not yet supported. + // It happens all the time. + if msg_info.chat_sn.ends_with("@chat.agent") { + log::error!("Got message for unknown chat {}", msg_info.chat_sn); + } + } + } + + connection.serv_got_chat_in(msg_info); + } + + pub fn chat_joined(&mut self, connection: &mut Connection, info: &PartialChatInfo) { + log::info!("chat joined: {}", info.sn); + if info.sn.ends_with("@chat.agent") { + self.group_chat_joined(connection, info) + } else { + todo!() + }; + } + + fn group_chat_joined( + &mut self, + connection: &mut Connection, + info: &PartialChatInfo, + ) -> purple::Chat { + let mut account = connection.get_account(); + match purple::Chat::find(&mut account, &info.sn) { + Some(mut chat) => { + // The chat already exists. + + // Should we replace the blist group? + if let Some(info_group) = &info.group { + let should_replace_group = { + match chat.get_group() { + Some(mut chat_group) => !chat_group.get_name().eq(info_group), + None => true, + } + }; + if should_replace_group { + chat.add_to_blist(&mut self.get_or_create_group(Some(&info_group)), None); + } + } + + // Replace the alias + chat.set_alias(&info.title); + chat + } + None => { + let mut components = info.as_hashtable(); + components.insert(&chat_info::STATE, chat_states::JOINED); + let mut chat = purple::Chat::new(&mut account, &info.title, components); + chat.add_to_blist(&mut self.get_or_create_group(info.group.as_deref()), None); + chat + } + } + } + + fn get_or_create_group(&mut self, name: Option<&str>) -> purple::Group { + let name = name.unwrap_or("ICQ"); + Group::find(name).unwrap_or_else(|| { + let mut group = purple::Group::new(name); + group.add_to_blist(None); + group + }) + } + + pub fn conversation_joined( + &mut self, + connection: &mut Connection, + info: &PartialChatInfo, + ) -> Conversation { + match connection.get_account().find_chat_conversation(&info.sn) { + Some(mut conversation) => { + if conversation.get_chat_data().unwrap().has_left() { + log::error!("Trying to join left conversation"); + } else { + conversation.present(); + } + conversation + } + None => { + let mut conversation = connection.serv_got_joined_chat(&info.sn).unwrap(); + conversation.set_title(&info.title); + conversation + } + } + } + + pub fn check_chat_info( + &mut self, + connection: &mut Connection, + sn: &str, + version: &ChatInfoVersion, + ) { + match connection.get_account().find_chat_conversation(&sn) { + Some(mut conversation) => { + let chat_info = unsafe { conversation.get_data::(conv_data::CHAT_INFO) }; + if chat_info + .map(|chat_info| chat_info.need_update(version)) + .unwrap_or(true) + { + log::info!("Fetching chat info: {}", sn); + let handle = Handle::from(&mut *connection); + let protocol_data = self + .connections + .get(&handle) + .expect("Tried get chat info on closed connection"); + self.system + .tx + .try_send(PurpleMessage::get_chat_info( + handle, + protocol_data.data.clone(), + sn.to_string(), + )) + .unwrap(); + } + } + None => { + log::warn!("Checking chat info for no conversation"); + } + } + } + + pub fn load_chat_info(&mut self, connection: &mut Connection, info: &ChatInfo) { + log::debug!("loading chat info: {:?}", info); + match connection.get_account().find_chat_conversation(&info.sn) { + Some(mut conversation) => { + conversation.set_title(&info.title); + let mut chat_conversation = conversation.get_chat_data().unwrap(); + unsafe { conversation.set_data(conv_data::CHAT_INFO, info.clone()) }; + + chat_conversation.clear_users(); + for member in &info.members { + chat_conversation.add_user(&member.sn, "", member.role.as_flags(), false); + } + + if let Some(about) = &info.about { + chat_conversation.set_topic("unknown", about); + } + } + None => { + log::warn!("Loaded chat info for no conversation"); + } + } + } + */ +} + +purple_prpl_plugin!(PurpleDelta);