diff --git a/Cargo.lock b/Cargo.lock index f4709b5..bf71fa9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,7 @@ dependencies = [ "libc", "num-integer", "num-traits", + "serde", "time", "winapi", ] @@ -191,6 +192,8 @@ dependencies = [ "edit", "parking_lot", "rusqlite", + "serde", + "serde_json", "tokio", "toss", ] diff --git a/cove-tui/Cargo.toml b/cove-tui/Cargo.toml index 531845a..9ed4cd7 100644 --- a/cove-tui/Cargo.toml +++ b/cove-tui/Cargo.toml @@ -6,11 +6,13 @@ edition = "2021" [dependencies] anyhow = "1.0.57" async-trait = "0.1.56" -chrono = "0.4.19" +chrono = { version = "0.4.19", features = ["serde"] } crossterm = "0.23.2" directories = "4.0.1" edit = "0.1.4" parking_lot = "0.12.1" rusqlite = "0.27.0" +serde = { version = "1.0.137", features = ["derive"] } +serde_json = "1.0.81" tokio = { version = "1.19.2", features = ["full"] } toss = { git = "https://github.com/Garmelon/toss.git", rev = "761519c1a7cdc950eab70fd6539c71bf22919a50" } diff --git a/cove-tui/src/euph.rs b/cove-tui/src/euph.rs new file mode 100644 index 0000000..b098144 --- /dev/null +++ b/cove-tui/src/euph.rs @@ -0,0 +1,12 @@ +mod api; + +use std::convert::Infallible; + +use tokio::sync::{mpsc, oneshot}; + +enum Request {} + +pub struct EuphRoom { + canary: oneshot::Sender, + tx: mpsc::Sender, +} diff --git a/cove-tui/src/euph/api.rs b/cove-tui/src/euph/api.rs new file mode 100644 index 0000000..9339dde --- /dev/null +++ b/cove-tui/src/euph/api.rs @@ -0,0 +1,24 @@ +//! Models the euphoria API at . + +mod events; +mod room_cmds; +mod session_cmds; +mod types; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +pub use events::*; +pub use room_cmds::*; +pub use session_cmds::*; +pub use types::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Packet { + pub id: Option, + pub r#type: PacketType, + pub data: Option, + pub error: Option, + pub throttled: Option, + pub throttled_reason: Option, +} diff --git a/cove-tui/src/euph/api/events.rs b/cove-tui/src/euph/api/events.rs new file mode 100644 index 0000000..1d79b03 --- /dev/null +++ b/cove-tui/src/euph/api/events.rs @@ -0,0 +1,170 @@ +//! Asynchronous events. + +use serde::{Deserialize, Serialize}; + +use super::{AuthOption, Message, PersonalAccountView, SessionView, Snowflake, Time, UserId}; + +/// Indicates that access to a room is denied. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BounceEvent { + /// The reason why access was denied. + pub reason: Option, + /// Authentication options that may be used. + pub auth_options: Option>, + /// Internal use only. + pub agent_id: Option, + /// Internal use only. + pub ip: Option, +} + +/// Indicates that the session is being closed. The client will subsequently be +/// disconnected. +/// +/// If the disconnect reason is `authentication changed`, the client should +/// immediately reconnect. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DisconnectEvent { + /// The reason for disconnection. + pub reason: String, +} + +/// Sent by the server to the client when a session is started. +/// +/// It includes information about the client's authentication and associated +/// identity. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HelloEvent { + /// The id of the agent or account logged into this session. + pub id: UserId, + /// Details about the user's account, if the session is logged in. + pub account: Option, + /// Details about the session. + pub session: SessionView, + /// If true, then the account has an explicit access grant to the current + /// room. + pub account_has_access: Option, + /// Whether the account's email address has been verified. + pub account_email_verified: Option, + /// If true, the session is connected to a private room. + pub room_is_private: bool, + /// The version of the code being run and served by the server. + pub version: String, +} + +/// Indicates a session just joined the room. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct JoinEvent(pub SessionView); + +/// Sent to all sessions of an agent when that agent is logged in (except for +/// the session that issued the login command). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LoginEvent { + pub account_id: Snowflake, +} + +/// Sent to all sessions of an agent when that agent is logged out (except for +/// the session that issued the logout command). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LogoutEvent; + +/// Indicates some server-side event that impacts the presence of sessions in a +/// room. +/// +/// If the network event type is `partition`, then this should be treated as a +/// [`PartEvent`] for all sessions connected to the same server id/era combo. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetworkEvent { + /// The type of network event; for now, always `partition`. + pub r#type: String, + /// The id of the affected server. + pub server_id: String, + /// The era of the affected server. + pub server_era: String, +} + +/// Announces a nick change by another session in the room. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NickEvent { + /// The id of the session this name applies to. + pub session_id: String, + /// The id of the agent or account logged into the session. + pub id: UserId, + /// The previous name associated with the session. + pub from: String, + /// The name associated with the session henceforth. + pub to: String, +} + +/// Indicates that a message in the room has been modified or deleted. +/// +/// If the client offers a user interface and the indicated message is currently +/// displayed, it should update its display accordingly. +/// +/// The event packet includes a snapshot of the message post-edit. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EditMessageEvent { + /// The id of the edit. + pub edit_id: Snowflake, + /// The snapshot of the message post-edit. + #[serde(flatten)] + pub message: Message, +} + +/// Indicates a session just disconnected from the room. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PartEvent(pub SessionView); + +/// Represents a server-to-client ping. +/// +/// The client should send back a ping-reply with the same value for the time +/// field as soon as possible (or risk disconnection). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PingEvent { + /// A unix timestamp according to the server's clock. + pub time: Time, + /// The expected time of the next ping event, according to the server's + /// clock. + pub next: Time, +} + +/// Informs the client that another user wants to chat with them privately. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PmInitiateEvent { + /// The id of the user inviting the client to chat privately. + pub from: UserId, + /// The nick of the inviting user. + pub from_nick: String, + /// The room where the invitation was sent from. + pub from_room: String, + /// The private chat can be accessed at `/room/pm:`. + pub pm_id: Snowflake, +} + +/// Indicates a message received by the room from another session. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SendEvent(pub Message); + +/// Indicates that a session has successfully joined a room. +/// +/// It also offers a snapshot of the room’s state and recent history. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SnapshotEvent { + /// The id of the agent or account logged into this session. + pub identity: UserId, + /// The globally unique id of this session. + pub session_id: String, + /// The server’s version identifier. + pub version: String, + /// The list of all other sessions joined to the room (excluding this + /// session). + pub listing: Vec, + /// The most recent messages posted to the room (currently up to 100). + pub log: Vec, + /// The acting nick of the session; if omitted, client set nick before + /// speaking. + pub nick: Option, + /// If given, this room is for private chat with the given nick. + pub pm_with_nick: Option, + /// If given, this room is for private chat with the given user. + pub pm_with_user_id: Option, +} diff --git a/cove-tui/src/euph/api/room_cmds.rs b/cove-tui/src/euph/api/room_cmds.rs new file mode 100644 index 0000000..02dddd4 --- /dev/null +++ b/cove-tui/src/euph/api/room_cmds.rs @@ -0,0 +1,116 @@ +//! Chat room commands. + +use serde::{Deserialize, Serialize}; + +use super::{Message, SessionView, Snowflake, UserId}; + +/// Retrieve the full content of a single message in the room. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GetMessage { + /// The id of the message to retrieve. + pub id: Snowflake, +} + +/// The message retrieved by [`GetMessage`]. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GetMessageReply(pub Message); + +/// Request messages from the room's message log. +/// +/// This can be used to supplement the log provided by snapshot-event (for +/// example, when scrolling back further in history). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Log { + /// Maximum number of messages to return (up to 1000). + pub n: usize, + /// Return messages prior to this snowflake. + pub before: Option, +} + +/// List of messages from the room's message log. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LogReply { + /// List of messages returned. + pub log: Vec, + /// Messages prior to this snowflake were returned. + pub before: Option, +} + +/// Set the name you present to the room. +/// +/// This name applies to all messages sent during this session, until the nick +/// command is called again. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Nick { + /// The requested name (maximum length 36 bytes). + pub name: String, +} + +/// Confirms the [`Nick`] command. +/// +/// Returns the session's former and new names (the server may modify the +/// requested nick). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NickReply { + /// The id of the session this name applies to. + pub session_id: String, + /// The id of the agent or account logged into the session. + pub id: UserId, + /// The previous name associated with the session. + pub from: String, + /// The name associated with the session henceforth. + pub to: String, +} + +/// Constructs a virtual room for private messaging between the client and the +/// given [`UserId`]. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PmInitiate { + /// The id of the user to invite to chat privately. + pub user_id: UserId, +} + +/// Provides the PMID for the requested private messaging room. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PmInitiateReply { + /// The private chat can be accessed at `/room/pm:`. + pub pm_id: Snowflake, + /// The nickname of the recipient of the invitation. + pub to_nick: String, +} + +/// Send a message to a room. +/// +/// The session must be successfully joined with the room. This message will be +/// broadcast to all sessions joined with the room. +/// +/// If the room is private, then the message content will be encrypted before it +/// is stored and broadcast to the rest of the room. +/// +/// The caller of this command will not receive the corresponding +/// [`SendEvent`](super::SendEvent), but will receive the same information in +/// the [`SendReply`]. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Send { + /// The content of the message (client-defined). + pub content: String, + /// The id of the parent message, if any. + pub parent: Option, +} + +/// The message that was sent. +/// +/// this includes the message id, which was populated by the server. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SendReply(pub Message); + +/// Request a list of sessions currently joined in the room. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Who; + +/// Lists the sessions currently joined in the room. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WhoReply { + /// A list of session views. + listing: Vec, +} diff --git a/cove-tui/src/euph/api/session_cmds.rs b/cove-tui/src/euph/api/session_cmds.rs new file mode 100644 index 0000000..e169f7d --- /dev/null +++ b/cove-tui/src/euph/api/session_cmds.rs @@ -0,0 +1,43 @@ +//! Session commands. + +use serde::{Deserialize, Serialize}; + +use super::{AuthOption, Time}; + +/// Attempt to join a private room. +/// +/// This should be sent in response to a bounce event at the beginning of a +/// session. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Auth { + /// The method of authentication. + pub r#type: AuthOption, + /// Use this field for [`AuthOption::Passcode`] authentication. + pub passcode: Option, +} + +/// Reports whether the [`Auth`] command succeeded. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuthReply { + /// True if authentication succeeded. + pub success: bool, + /// If [`Self::success`] was false, the reason for failure. + pub reason: Option, +} + +/// Initiate a client-to-server ping. +/// +/// The server will send back a [`PingReply`] with the same timestamp as soon as +/// possible. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Ping { + /// An arbitrary value, intended to be a unix timestamp. + pub time: Time, +} + +/// Response to a [`Ping`] command or [`PingEvent`](super::PingEvent). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PingReply { + /// The timestamp of the ping being replied to. + pub time: Option