From 05b0d38490a69d058cdd0ee7b17140634d116af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cel=20=F0=9F=8C=B8?= Date: Wed, 12 Feb 2025 17:33:12 +0000 Subject: [PATCH] WIP: rfc 6121 data(base)types --- README.md | 1 + luz/Cargo.toml | 1 + luz/migrations/20240113011930_luz.sql | 56 +++++++++++++++++++-- luz/src/chat.rs | 30 +++++++++++ luz/src/error.rs | 26 +++++++--- luz/src/lib.rs | 72 +++++++++++++++++++++++---- luz/src/presence.rs | 19 +++++++ luz/src/roster.rs | 29 +++++++++++ 8 files changed, 212 insertions(+), 22 deletions(-) create mode 100644 luz/src/chat.rs create mode 100644 luz/src/presence.rs create mode 100644 luz/src/roster.rs diff --git a/README.md b/README.md index b40173a..06e98a3 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ file/media sharing (further research needed): - [ ] xep-0234: jingle file transfer need more research: +- [ ] xep-0154: user profile - [ ] message editing - [ ] xep-0308: last message correction (should not be used for older than last message according to spec) - [ ] chat read markers diff --git a/luz/Cargo.toml b/luz/Cargo.toml index ebff7d9..cd86ce2 100644 --- a/luz/Cargo.toml +++ b/luz/Cargo.toml @@ -15,3 +15,4 @@ tokio-stream = "0.1.17" tokio-util = "0.7.13" tracing = "0.1.41" tracing-subscriber = "0.3.19" +uuid = "1.13.1" diff --git a/luz/migrations/20240113011930_luz.sql b/luz/migrations/20240113011930_luz.sql index bae43ea..a758a23 100644 --- a/luz/migrations/20240113011930_luz.sql +++ b/luz/migrations/20240113011930_luz.sql @@ -1,5 +1,53 @@ -CREATE TABLE roster( - id INTEGER PRIMARY KEY, - jid TEXT NOT NULL, - nickname TEXT, +PRAGMA foreign_keys = on; + +-- a user jid will never change, only a chat user will change +-- TODO: avatar, nick, etc. +create table user( + jid jid primary key, + cached_status text, +); + +-- enum for subscription state +create table subscription( + state text primary key, +); + +insert into subscription ( state ) values ('none'), ('pending-out'), ('pending-in'), ('only-out'), ('only-in'), ('out-pending-in'), ('in-pending-out'), ('buddy'); + +-- a roster contains users, with client-set nickname +CREATE TABLE roster( + jid jid primary key, + name TEXT, + subscription text not null, + foreign key(subscription) references subscription(state), + foreign key(jid) references users(jid) +); + +create table groups( + group text primary key +); + +create table groups_roster( + group_id text, + contact_id jid, + foreign key(group_id) references group(id), + foreign key(contact_id) references roster(id), + primary key(group_id, contact_id) +); + +-- chat includes reference to user jid chat is with +create table chats ( + id uuid primary key, + contact_id jid not null unique, +); + +-- messages include reference to chat they are in, and who sent them. +create table messages ( + id uuid primary key, + body text, + chat_id uuid not null, + from jid not null, + -- TODO: read bool not null, + foreign key(chat_id) references chats(id), + foreign key(from) references users(jid) ); diff --git a/luz/src/chat.rs b/luz/src/chat.rs new file mode 100644 index 0000000..a084d29 --- /dev/null +++ b/luz/src/chat.rs @@ -0,0 +1,30 @@ +use uuid::Uuid; + +use crate::roster::Contact; + +pub enum Chat { + Direct(DM), + Channel(Channel), +} + +#[derive(Debug)] +pub struct Message { + id: Uuid, + // contains full contact information + from: Contact, + // TODO: rich text, other contents, threads + body: Body, +} + +#[derive(Debug)] +pub struct Body { + body: String, +} + +pub struct DM { + contact: Contact, + message_history: Vec, +} + +// TODO: group chats +pub struct Channel {} diff --git a/luz/src/error.rs b/luz/src/error.rs index 6c3fb5d..16e1c6e 100644 --- a/luz/src/error.rs +++ b/luz/src/error.rs @@ -1,11 +1,23 @@ #[derive(Debug)] pub enum Error { AlreadyConnected, + Presence(Reason), + Roster(Reason), + SendMessage(Reason), + AlreadyDisconnected, + LostConnection, +} + +#[derive(Debug)] +pub enum Reason { + // TODO: organisastion of error into internal error thing + Timeout, + Stream(stanza::stream_error::Error), + Stanza(stanza::stanza_error::Error), Jabber(jabber::Error), XML(peanuts::Error), SQL(sqlx::Error), - JID(jid::ParseError), - AlreadyDisconnected, + // JID(jid::ParseError), LostConnection, } @@ -15,11 +27,11 @@ impl From for Error { } } -impl From for Error { - fn from(e: jid::ParseError) -> Self { - Self::JID(e) - } -} +// impl From for Error { +// fn from(e: jid::ParseError) -> Self { +// Self::JID(e) +// } +// } impl From for Error { fn from(e: sqlx::Error) -> Self { diff --git a/luz/src/lib.rs b/luz/src/lib.rs index 4c5a841..1fb58d6 100644 --- a/luz/src/lib.rs +++ b/luz/src/lib.rs @@ -4,27 +4,31 @@ use std::{ sync::Arc, }; +use chat::{Body, Message}; use connection::{write::WriteMessage, SupervisorSender}; use jabber::JID; +use presence::{Offline, Online, Presence}; +use roster::Contact; use sqlx::SqlitePool; -use stanza::{ - client::{ - iq::{self, Iq, IqType}, - Stanza, - }, - roster::{self, Query}, +use stanza::client::{ + iq::{self, Iq, IqType}, + Stanza, }; use tokio::{ sync::{mpsc, oneshot, Mutex}, task::JoinSet, }; +use uuid::Uuid; use crate::connection::write::WriteHandle; use crate::connection::{SupervisorCommand, SupervisorHandle}; use crate::error::Error; +mod chat; mod connection; mod error; +mod presence; +mod roster; pub struct Luz { receiver: mpsc::Receiver, @@ -190,7 +194,7 @@ impl CommandMessage { to: None, r#type: IqType::Get, lang: None, - query: Some(iq::Query::Roster(roster::Query { + query: Some(iq::Query::Roster(stanza::roster::Query { ver: None, items: Vec::new(), })), @@ -265,16 +269,62 @@ impl LuzHandle { } pub enum CommandMessage { + /// connect to XMPP chat server. gets roster and Connect, + /// disconnect from XMPP chat server. Disconnect, - /// gets the roster. if offline, retreives cached version from database. should be stored in application memory. + /// get the roster. if offline, retreive cached version from database. should be stored in application memory GetRoster, - SendMessage(JID, String), + // add a contact to your roster, with a status of none, no subscriptions + AddContact(JID), + /// send a friend request i.e. a subscription request with a subscription pre-approval. if not already added to roster server adds to roster. + BuddyRequest(JID), + /// send a subscription request, without pre-approval. if not already added to roster server adds to roster. + SubscriptionRequest(JID), + /// accept a friend request by accepting a pending subscription and sending a subscription request back. if not already added to roster adds to roster. + AcceptBuddyRequest(JID), + /// accept a pending subscription and doesn't send a subscription request back. if not already added to roster adds to roster. + AcceptSubscriptionRequest(JID), + /// unsubscribe to a contact, but don't remove their subscription. + UnsubscribeFromContact(JID), + /// stop a contact from being subscribed, but stay subscribed to the contact. + UnsubscribeContact(JID), + /// remove subscriptions to and from contact, but keep in roster. + UnfriendContact(JID), + /// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster. + DeleteContact(JID), + /// set online status + SendStatus(Online), + SendOffline(Offline), + /// send a directed presence (usually to a non-contact). + // TODO: should probably make it so people can add non-contact auto presence sharing in the client. + SendDirectedPresence { + to: JID, + presence: Presence, + }, + SendMessage { + id: Uuid, + to: JID, + body: Body, + }, } #[derive(Debug)] pub enum UpdateMessage { Error(Error), - Connected, - Roster(Vec), + Connected(Online), + Disconnected(Offline), + /// full roster (replace full app roster state with this) + Roster(Vec), + /// roster update (only update app roster state) + RosterPush(Contact), + Presence { + from: JID, + presence: Presence, + }, + MessageDispatched(Uuid), + Message { + from: JID, + message: Message, + }, } diff --git a/luz/src/presence.rs b/luz/src/presence.rs new file mode 100644 index 0000000..0423d52 --- /dev/null +++ b/luz/src/presence.rs @@ -0,0 +1,19 @@ +use stanza::client::presence::Show; + +#[derive(Debug)] +pub struct Online { + show: Option, + status: Option, + priority: Option, +} + +#[derive(Debug)] +pub struct Offline { + status: Option, +} + +#[derive(Debug)] +pub enum Presence { + Online(Online), + Offline(Offline), +} diff --git a/luz/src/roster.rs b/luz/src/roster.rs new file mode 100644 index 0000000..c4502a0 --- /dev/null +++ b/luz/src/roster.rs @@ -0,0 +1,29 @@ +use std::collections::HashSet; + +use jid::JID; +use uuid::Uuid; + +#[derive(Debug)] +pub struct Contact { + // jid is the id used to reference everything, but not the primary key + jid: JID, + subscription: Subscription, + /// client user defined name + name: Option, + // TODO: avatar, nickname + /// nickname picked by contact + // nickname: Option, + groups: HashSet, +} + +#[derive(Debug)] +enum Subscription { + None, + PendingOut, + PendingIn, + OnlyOut, + OnlyIn, + OutPendingIn, + InPendingOut, + Buddy, +}