Compare commits
5 Commits
130d6bebfc
...
8b2654622d
Author | SHA1 | Date | |
---|---|---|---|
8b2654622d | |||
ba7e9746c1 | |||
a1ec955fdd | |||
8ba2df41e0 | |||
77fd97aa31 |
2422
Cargo.lock
generated
Normal file
2422
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "xmpp-client"
|
name = "rino"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
@ -13,3 +13,6 @@ nom = "7.1.3"
|
||||||
html-escape = "0.2.13"
|
html-escape = "0.2.13"
|
||||||
hsluv = "0.3.1"
|
hsluv = "0.3.1"
|
||||||
sha1 = "0.10.6"
|
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 poezio_logs;
|
||||||
mod tab;
|
mod tab;
|
||||||
mod window;
|
mod window;
|
||||||
|
mod xmpp_client;
|
||||||
|
|
||||||
use message::Message;
|
use message::Message;
|
||||||
use tab::Tab;
|
use tab::Tab;
|
||||||
|
@ -29,30 +30,6 @@ fn get_own_nick() -> String {
|
||||||
std::env::var("USER").unwrap()
|
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 {
|
fn xep_0392(input: &str) -> String {
|
||||||
use sha1::Digest;
|
use sha1::Digest;
|
||||||
let sha1 = sha1::Sha1::digest(input);
|
let sha1 = sha1::Sha1::digest(input);
|
||||||
|
@ -63,6 +40,11 @@ fn xep_0392(input: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
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()
|
let app = adw::Application::builder()
|
||||||
.application_id("fr.linkmauve.XmppClient")
|
.application_id("fr.linkmauve.XmppClient")
|
||||||
.flags(gio::ApplicationFlags::HANDLES_COMMAND_LINE)
|
.flags(gio::ApplicationFlags::HANDLES_COMMAND_LINE)
|
||||||
|
@ -70,29 +52,22 @@ fn main() {
|
||||||
|
|
||||||
let tabs_store = gio::ListStore::new::<Tab>();
|
let tabs_store = gio::ListStore::new::<Tab>();
|
||||||
|
|
||||||
let tabs_store2 = tabs_store.clone();
|
let (xmpp_receiver, cmd_sender) = xmpp_client::client(&username, &password);
|
||||||
app.connect_command_line(move |app, command_line| {
|
let tabs_store_copy = tabs_store.clone();
|
||||||
let args = command_line.arguments();
|
glib::spawn_future_local(async move {
|
||||||
let mut iter = args.iter();
|
while let Ok(event) = xmpp_receiver.recv().await {
|
||||||
iter.next().unwrap();
|
match event {
|
||||||
let mut first_tab = None;
|
xmpp::Event::ContactAdded(jid) => {
|
||||||
for os_arg in iter {
|
let tab = Tab::new(jid.jid.as_str(), jid.jid.as_str());
|
||||||
let jid = os_arg.to_str().unwrap();
|
tabs_store_copy.append(&tab);
|
||||||
let tab = Tab::new(jid, "User");
|
}
|
||||||
tabs_store2.append(&tab);
|
xmpp::Event::ChatMessage(_id, _from, _body, _time) => {
|
||||||
if first_tab.is_none() {
|
// TODO: Insert message into tab history
|
||||||
first_tab = Some(tab);
|
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| {
|
app.connect_startup(move |app| {
|
||||||
|
@ -167,7 +142,7 @@ fn main() {
|
||||||
let item = tabs_selection.selected_item().unwrap();
|
let item = tabs_selection.selected_item().unwrap();
|
||||||
let tab: &Tab = item.downcast_ref().unwrap();
|
let tab: &Tab = item.downcast_ref().unwrap();
|
||||||
println!("Switching to {}", tab.jid());
|
println!("Switching to {}", tab.jid());
|
||||||
let store = load_logs(&tab.jid());
|
let store = poezio_logs::load_logs(&tab.jid());
|
||||||
let selection = win2.selection();
|
let selection = win2.selection();
|
||||||
selection.set_model(Some(&store));
|
selection.set_model(Some(&store));
|
||||||
win2.messages().scroll_to(
|
win2.messages().scroll_to(
|
||||||
|
@ -180,10 +155,22 @@ fn main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let win2 = win.clone();
|
let win2 = win.clone();
|
||||||
|
let cmd_sender2 = cmd_sender.clone();
|
||||||
win.entry().connect_activate(move |entry| {
|
win.entry().connect_activate(move |entry| {
|
||||||
let text = entry.text();
|
let text = entry.text();
|
||||||
entry.set_text("");
|
entry.set_text("");
|
||||||
println!("Send: {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 message = Message::now(&get_own_nick(), &text);
|
||||||
let selection = win2.selection();
|
let selection = win2.selection();
|
||||||
let store = selection
|
let store = selection
|
||||||
|
@ -196,8 +183,11 @@ fn main() {
|
||||||
.scroll_to(selection.n_items() - 1, gtk::ListScrollFlags::FOCUS, None);
|
.scroll_to(selection.n_items() - 1, gtk::ListScrollFlags::FOCUS, None);
|
||||||
});
|
});
|
||||||
|
|
||||||
win.messages()
|
win.messages().scroll_to(
|
||||||
.scroll_to(win.selection().n_items() - 1, gtk::ListScrollFlags::FOCUS, None);
|
win.selection().n_items() - 1,
|
||||||
|
gtk::ListScrollFlags::FOCUS,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
win.present();
|
win.present();
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
|
use gtk::gio;
|
||||||
use nom;
|
use nom;
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{tag, take, take_until},
|
bytes::complete::{tag, take, take_until},
|
||||||
|
@ -22,8 +23,13 @@ use nom::{
|
||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::Message;
|
||||||
|
|
||||||
pub trait LogItem {
|
pub trait LogItem {
|
||||||
fn get_time(&self) -> &DateTime<Utc>;
|
fn get_time(&self) -> &DateTime<Utc>;
|
||||||
fn get_message(&self) -> String;
|
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),
|
map_res(take(2usize), u32::from_str),
|
||||||
tag("Z"),
|
tag("Z"),
|
||||||
))(i)?;
|
))(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> {
|
pub fn parse_log_info(i: &str) -> IResult<&str, LogInfo> {
|
||||||
|
@ -142,6 +154,30 @@ pub enum Item<'a> {
|
||||||
Info(LogInfo<'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>> {
|
pub fn parse_logs(mut logs: &str) -> IResult<&str, Vec<Item>> {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
loop {
|
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