oshatori

Crates.iooshatori
lib.rsoshatori
version0.2.0
created_at2026-01-08 23:42:17.632551+00
updated_at2026-01-08 23:42:17.632551+00
descriptionMulti-connection, protocol-agnostic, instant messaging.
homepage
repository
max_upload_size
id2031312
size163,891
(saikuru0)

documentation

README


oshatori

A versatile multi-protocol chat library

[!WARNING] This library is in its early stages and the core structure will evolve as more protocols are implemented. If you're still considering contributing at this point in time, make sure to look out for a more stable release of oshatori before sending a pull request.

[!WARNING] README is currently out of date.

Introduction

This project aims to generalize a broad range of text chat and social interactions. To start off let's go over the types provided by the library:

Name Kind Fields / Variants Description
Account struct auth: Vec<AuthField>
protocol_name: String
private_profile: Option<Profile>
Represents a user's account on a protocol, with auth fields and an optional private profile.
Profile struct id: Option<String>
username: Option<String>
display_name: Option<String>
color: Option<[u8;4]>
picture: Option<String>
Holds display info for a user (defaults all to None).
Message struct id: Option<String>
sender_id: Option<String>
content: Vec<MessageFragment>
timestamp: DateTime<Utc>
message_type: MessageType
status: MessageStatus
Encapsulates a single chat message with fragments, timestamp, type, and delivery status.
MessageStatus enum Sent
Delivered
Edited
Deleted
Failed
Tracks the state of a message.
MessageType enum CurrentUser
Normal
Server
Meta
Categorizes if a message was sent by the current user, another user, the server, or internally by the protocol implementation itself.
MessageFragment enum Text(String)
Image { url: String, mime: String }
Video { url: String, mime: String }
Audio { url: String, mime: String }
Url(String)
A piece of a message: plaintext, media embed, or URL.
Channel struct id: String
name: Option<String>
channel_type: ChannelType
Represents a chat channel (group, direct, or broadcast).
ChannelType enum Group
Direct
Broadcast
Defines the type of channel (multi-user, peer-to-peer, or broadcast-only).
Asset enum Emote, Sticker, Audio { id: Option, keys: Vec, src: String, source: AssetSource, }
Command {id: Option, keys: Vec, args: Vec, source: AssetSource,}
An asset available for use by the user.
AssetSource enum User, Server, Meta Categorizes if the asset was added by the user, the protocol itself, or a connected server.
Protocol struct name: String
auth: Option<Vec<AuthField>>
Describes a messaging protocol with its auth fields (or None if no authentication is needed.
AuthField struct name: String
display: Option<String>
value: FieldValue
required: bool
One input field needed for authentication (e.g. username, password).
FieldValue enum Text(Option<String>)
Password(Option<String>)
Group(Vec<AuthField>)
The type and current value of an AuthField: plain text, password, or nested group of fields.

Common interface trait called Connection:

pub trait Connection: Send + Sync {
    async fn connect(&mut self, auth: Vec<AuthField>) -> Result<(), String>;
    async fn disconnect(&mut self) -> Result<(), String>;
    async fn send(&mut self, event: ConnectionEvent) -> Result<(), String>;
    fn subscribe(&self) -> broadcast::Receiver<ConnectionEvent>;
    fn protocol_spec() -> Protocol;
}

Every implemented protocol can be interacted with using this interface, and the same set of events:

Type Variant Fields
ChatEvent New channel_id: Option<String>, message: Message
Update channel_id: Option<String>, message_id: String, new_message: Message
Remove channel_id: Option<String>, message_id: String
ChannelEvent New channel: Channel
Update channel_id: String, new_channel: Channel
Remove channel_id: String
Join channel_id: String
Leave channel_id: String
Switch channel_id: String
Kick channel_id: Option<String>, reason: Option<String>, ban: bool
Wipe channel_id: Option<String>
ClearList (no fields)
UserEvent New channel_id: Option<String>, user: Profile
Update channel_id: Option<String>, user_id: String, new_user: Profile
Remove channel_id: Option<String>, user_id: String
ClearList channel_id: Option<String>
StatusEvent Ping artifact: Option<String>
Connected artifact: Option<String>
Disconnected artifact: Option<String>
AssetEvent New channel_id: Option<String>, asset: Asset
Update channel_id: Option<String>, asset_id: String, new_asset: Asset
Remove channel_id: Option<String>, asset_id: String
ClearList channel_id: Option<String>
ConnectionEvent Chat event: ChatEvent
User event: UserEvent
Channel event: ChannelEvent
Status event: StatusEvent
Asset event: AssetEvent

Example

Here is an example straight from the mock_connection.rs test:

use chrono::Utc;
use oshatori::{
    connection::{ChatEvent, ConnectionEvent, MockConnection},
    Connection, Message, MessageFragment, MessageStatus, MessageType,
};

#[tokio::test]
async fn test_mock_connection_integration() {
    let mut conn = MockConnection::new();
    let mut rx = conn.subscribe();

    let test_message = Message {
        id: None,
        sender_id: None,
        content: vec![MessageFragment::Text("some text".to_string())],
        timestamp: Utc::now(),
        message_type: MessageType::Normal,
        status: MessageStatus::Sent,
    };

    conn.send(ConnectionEvent::Chat {
        event: ChatEvent::New {
            channel_id: None,
            message: test_message.clone(),
        },
    })
    .await
    .expect("failed to send");

    let received = rx.recv().await.expect("failed to receive");

    if let ConnectionEvent::Chat { event } = received {
        if let ChatEvent::New {
            channel_id,
            message,
        } = event
        {
            assert_eq!(channel_id, None);
            match message.content.get(0) {
                Some(fragment) => match fragment {
                    MessageFragment::Text(value) => {
                        assert_eq!(value.to_owned(), "some text".to_string())
                    }
                    _ => {}
                },
                None => {}
            }
        } else {
            panic!("unexpected chat event");
        }
    } else {
        panic!("unexpected connection event");
    }
}

Coverage

Currently these protocols are implemented:

  • sockchat - using kanii-lib
  • mock - a mock protocol for testing

Styleguide

The folder structure is used as follows:

  • src
    • connection - protocol implementations
      • mod.rs - Connection trait definition
      • sockchat.rs
      • mock.rs
    • utils - helper functions used by multiple protocols
      • bbcode.rs - bbcode parser
      • color.rs - kanii_to_rgba
      • html.rs - replacing &lt;, &gt;, and \s<br/>\s with <, >, and \n
      • mod.rs
    • lib.rs - type definitions
  • tests - tests for each protocol
    • mock_connection.rs
    • sockchat_connection
Commit count: 0

cargo fmt