Compare commits

...

5 Commits

Author SHA1 Message Date
130d6bebfc Start receive message 2024-06-03 17:39:25 +02:00
ba276d9b00 Receive message do nothing 2024-06-03 17:39:25 +02:00
855df73ef0 Don't crash when no messages 2024-06-03 17:39:25 +02:00
cc657d75f6 Remove chrono warning and replace with TODO 2024-06-03 17:39:25 +02:00
382caf83a8 Load real contacts 2024-06-03 17:39:17 +02:00
5 changed files with 140 additions and 50 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
Cargo.lock

View File

@ -1,5 +1,5 @@
[package]
name = "xmpp-client"
name = "rino"
version = "0.1.0"
edition = "2021"
@ -13,3 +13,6 @@ nom = "7.1.3"
html-escape = "0.2.13"
hsluv = "0.3.1"
sha1 = "0.10.6"
tokio = { version = "1", features = [ "rt" ] }
xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs" }
async-channel = "2.3.1"

View File

@ -21,6 +21,7 @@ mod message;
mod poezio_logs;
mod tab;
mod window;
mod xmpp_client;
use message::Message;
use tab::Tab;
@ -29,30 +30,6 @@ fn get_own_nick() -> String {
std::env::var("USER").unwrap()
}
fn load_logs(jid: &str) -> gio::ListStore {
let logs = {
let mut file = std::fs::File::open(jid).unwrap();
let mut string = String::new();
use std::io::Read;
file.read_to_string(&mut string).unwrap();
string
};
let (_, logs) = poezio_logs::parse_logs(&logs).unwrap();
let store = gio::ListStore::new::<Message>();
for item in logs {
if let poezio_logs::Item::Message(message) = item {
use poezio_logs::LogItem;
let message = Message::new(
message.get_time(),
message.get_nick(),
&message.get_message(),
);
store.append(&message);
}
}
store
}
fn xep_0392(input: &str) -> String {
use sha1::Digest;
let sha1 = sha1::Sha1::digest(input);
@ -63,6 +40,11 @@ fn xep_0392(input: &str) -> String {
}
fn main() {
let mut args = std::env::args();
let _ = args.next().unwrap();
let username = args.next().expect("Please give username argument 1");
let password = args.next().expect("Please give password argument 2");
let app = adw::Application::builder()
.application_id("fr.linkmauve.XmppClient")
.flags(gio::ApplicationFlags::HANDLES_COMMAND_LINE)
@ -70,29 +52,22 @@ fn main() {
let tabs_store = gio::ListStore::new::<Tab>();
let tabs_store2 = tabs_store.clone();
app.connect_command_line(move |app, command_line| {
let args = command_line.arguments();
let mut iter = args.iter();
iter.next().unwrap();
let mut first_tab = None;
for os_arg in iter {
let jid = os_arg.to_str().unwrap();
let tab = Tab::new(jid, "User");
tabs_store2.append(&tab);
if first_tab.is_none() {
first_tab = Some(tab);
let (xmpp_receiver, cmd_sender) = xmpp_client::client(&username, &password);
let tabs_store_copy = tabs_store.clone();
glib::spawn_future_local(async move {
while let Ok(event) = xmpp_receiver.recv().await {
match event {
xmpp::Event::ContactAdded(jid) => {
let tab = Tab::new(jid.jid.as_str(), jid.jid.as_str());
tabs_store_copy.append(&tab);
}
xmpp::Event::ChatMessage(_id, _from, _body, _time) => {
// TODO: Insert message into tab history
continue;
}
_ => continue,
}
}
if let Some(tab) = first_tab {
let win = app.active_window().unwrap();
let win: &window::MainWindow = win.downcast_ref().unwrap();
let store = load_logs(&tab.jid());
win.selection().set_model(Some(&store));
win.messages()
.scroll_to(win.selection().n_items() - 1, gtk::ListScrollFlags::FOCUS, None);
}
0
});
app.connect_startup(move |app| {
@ -167,7 +142,7 @@ fn main() {
let item = tabs_selection.selected_item().unwrap();
let tab: &Tab = item.downcast_ref().unwrap();
println!("Switching to {}", tab.jid());
let store = load_logs(&tab.jid());
let store = poezio_logs::load_logs(&tab.jid());
let selection = win2.selection();
selection.set_model(Some(&store));
win2.messages().scroll_to(
@ -180,10 +155,22 @@ fn main() {
});
let win2 = win.clone();
let cmd_sender2 = cmd_sender.clone();
win.entry().connect_activate(move |entry| {
let text = entry.text();
entry.set_text("");
println!("Send: {text}");
let current_tab: Tab = win2
.tabs_selection()
.selected_item()
.and_downcast()
.unwrap();
cmd_sender2
.send_blocking(xmpp_client::XMPPCommand::SendPM(
xmpp::BareJid::new(&current_tab.jid()).unwrap(),
text.as_str().to_string(),
))
.unwrap();
let message = Message::now(&get_own_nick(), &text);
let selection = win2.selection();
let store = selection
@ -196,8 +183,11 @@ fn main() {
.scroll_to(selection.n_items() - 1, gtk::ListScrollFlags::FOCUS, None);
});
win.messages()
.scroll_to(win.selection().n_items() - 1, gtk::ListScrollFlags::FOCUS, None);
win.messages().scroll_to(
win.selection().n_items() - 1,
gtk::ListScrollFlags::FOCUS,
None,
);
win.present();
});

View File

@ -14,6 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use chrono::{DateTime, TimeZone, Utc};
use gtk::gio;
use nom;
use nom::{
bytes::complete::{tag, take, take_until},
@ -22,8 +23,13 @@ use nom::{
sequence::tuple,
IResult,
};
use std::fs::read_to_string;
use std::path::PathBuf;
use std::str::FromStr;
use crate::Message;
pub trait LogItem {
fn get_time(&self) -> &DateTime<Utc>;
fn get_message(&self) -> String;
@ -81,7 +87,13 @@ pub fn parse_datetime(i: &str) -> IResult<&str, DateTime<Utc>> {
map_res(take(2usize), u32::from_str),
tag("Z"),
))(i)?;
Ok((i, Utc.ymd(year, month, day).and_hms(hour, minute, second)))
// TODO: handle DST and other time transitions
Ok((
i,
Utc.with_ymd_and_hms(year, month, day, hour, minute, second)
.single()
.unwrap(),
))
}
pub fn parse_log_info(i: &str) -> IResult<&str, LogInfo> {
@ -142,6 +154,30 @@ pub enum Item<'a> {
Info(LogInfo<'a>),
}
pub fn load_logs(jid: &str) -> gio::ListStore {
let xdg_data = std::env::var("XDG_DATA_HOME").unwrap_or(format!(
"{}/.local/share",
std::env::var("HOME").expect("NO $HOME?!")
));
let xdg_data = PathBuf::from(xdg_data);
let entry = xdg_data.join("poezio/logs").join(jid);
let logs = read_to_string(entry).unwrap_or("".to_string());
let (_, logs) = parse_logs(&logs).unwrap();
let store = gio::ListStore::new::<Message>();
for item in logs {
if let Item::Message(message) = item {
let message = Message::new(
message.get_time(),
message.get_nick(),
&message.get_message(),
);
store.append(&message);
}
}
store
}
pub fn parse_logs(mut logs: &str) -> IResult<&str, Vec<Item>> {
let mut items = vec![];
loop {

60
src/xmpp_client.rs Normal file
View File

@ -0,0 +1,60 @@
use async_channel::{Receiver, Sender};
use tokio::runtime::Runtime;
use xmpp::{BareJid, ClientBuilder, ClientFeature, Event};
use std::sync::OnceLock;
#[derive(Clone, Debug)]
pub enum XMPPCommand {
SendPM(BareJid, String),
}
fn tokio_runtime() -> &'static Runtime {
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
RUNTIME.get_or_init(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."))
}
pub(crate) fn client(jid: &str, password: &str) -> (Receiver<xmpp::Event>, Sender<XMPPCommand>) {
let (event_sender, event_receiver) = async_channel::bounded::<xmpp::Event>(1);
let (cmd_sender, cmd_receiver) = async_channel::bounded::<XMPPCommand>(1);
let jid = jid.to_string();
let password = password.to_string();
tokio_runtime().spawn(async move {
let mut client = ClientBuilder::new(BareJid::new(&jid).unwrap(), &password)
.set_default_nick("xmpp-client")
.enable_feature(ClientFeature::ContactList)
.build();
loop {
tokio::select! {
Some(events) = client.wait_for_events() => {
for event in events {
match event {
Event::ContactAdded(_) => {
event_sender.send(event).await.expect("BOOOOOOHOOOO");
}
Event::ChatMessage(_, _, _, _) => {
event_sender.send(event).await.expect("BOOOHOOOO");
}
_ => {
continue;
}
}
}
}
command = cmd_receiver.recv() => {
match command.unwrap() {
XMPPCommand::SendPM(jid, content) => {
client.send_message(jid.into(), xmpp::parsers::message::MessageType::Chat, "en", &content).await;
},
}
}
}
}
});
(event_receiver, cmd_sender)
}