Compare commits
5 Commits
8b2654622d
...
130d6bebfc
Author | SHA1 | Date | |
---|---|---|---|
130d6bebfc | |||
ba276d9b00 | |||
855df73ef0 | |||
cc657d75f6 | |||
382caf83a8 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
|
|
|
@ -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"
|
||||
|
|
86
src/main.rs
86
src/main.rs
|
@ -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(¤t_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();
|
||||
});
|
||||
|
|
|
@ -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
60
src/xmpp_client.rs
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user