use chrono::prelude::*;
use chrono::DateTime;
use chrono::TimeZone;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::cmp::Ordering;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub sender: String,
pub mtype: String,
pub body: String,
pub date: DateTime<Local>,
pub room: String,
pub thumb: Option<String>,
pub url: Option<String>,
pub id: String,
pub formatted_body: Option<String>,
pub format: Option<String>,
pub source: Option<String>,
pub receipt: HashMap<String, i64>, pub redacted: bool,
pub in_reply_to: Option<String>,
pub extra_content: Option<JsonValue>,
}
impl PartialEq for Message {
fn eq(&self, other: &Message) -> bool {
self.id == other.id
}
}
impl PartialOrd for Message {
fn partial_cmp(&self, other: &Message) -> Option<Ordering> {
if self == other {
Some(Ordering::Equal)
} else {
self.date.partial_cmp(&other.date)
}
}
}
impl Message {
pub fn new(room: String, sender: String, body: String, mtype: String) -> Self {
let date = Local::now();
Message {
id: get_txn_id(&room, &body, &date.to_string()),
sender,
mtype,
body,
date,
room,
thumb: None,
url: None,
formatted_body: None,
format: None,
source: None,
receipt: HashMap::new(),
redacted: false,
in_reply_to: None,
extra_content: None,
}
}
pub fn types() -> [&'static str; 2] {
["m.room.message", "m.sticker"]
}
pub fn supported_event(ev: &&JsonValue) -> bool {
let type_ = ev["type"].as_str().unwrap_or_default();
for t in Message::types().iter() {
if t == &type_ {
return true;
}
}
false
}
pub fn parse_room_message(roomid: &str, msg: &JsonValue) -> Message {
let sender = msg["sender"].as_str().unwrap_or_default();
let timestamp = msg["origin_server_ts"].as_i64().unwrap_or_default() / 1000;
let server_timestamp: DateTime<Local> = Local.timestamp(timestamp, 0);
let id = msg["event_id"].as_str().unwrap_or_default();
let type_ = msg["type"].as_str().unwrap_or_default();
let redacted = msg["unsigned"].get("redacted_because") != None;
let mut message = Message {
sender: sender.to_string(),
date: server_timestamp,
room: String::from(roomid),
id: id.to_string(),
mtype: type_.to_string(),
body: String::new(),
url: None,
thumb: None,
formatted_body: None,
format: None,
source: serde_json::to_string_pretty(&msg).ok(),
receipt: HashMap::new(),
redacted,
in_reply_to: None,
extra_content: None,
};
let c = &msg["content"];
match type_ {
"m.room.message" => Message::parse_m_room_message(&mut message, c),
"m.sticker" => Message::parse_m_sticker(&mut message, c),
_ => {}
};
message
}
fn parse_m_room_message(msg: &mut Message, c: &JsonValue) {
let mtype = c["msgtype"].as_str().unwrap_or_default();
let body = c["body"].as_str().unwrap_or_default();
let formatted_body = c["formatted_body"].as_str().map(String::from);
let format = c["format"].as_str().map(String::from);
match mtype {
"m.image" | "m.file" | "m.video" | "m.audio" => {
let url = String::from(c["url"].as_str().unwrap_or_default());
let mut t = String::from(c["info"]["thumbnail_url"].as_str().unwrap_or_default());
if t.is_empty() && !url.is_empty() {
t = url.clone();
}
msg.url = Some(url);
msg.thumb = Some(t);
}
"m.text" => {
msg.in_reply_to = c["m.relates_to"]["m.in_reply_to"]["event_id"]
.as_str()
.map(String::from)
}
_ => {}
};
msg.mtype = mtype.to_string();
msg.body = body.to_string();
msg.formatted_body = formatted_body;
msg.format = format;
}
fn parse_m_sticker(msg: &mut Message, c: &JsonValue) {
let body = c["body"].as_str().unwrap_or_default();
let url = String::from(c["url"].as_str().unwrap_or_default());
let mut t = String::from(c["info"]["thumbnail_url"].as_str().unwrap_or_default());
if t.is_empty() && !url.is_empty() {
t = url.clone();
}
msg.body = body.to_string();
msg.url = Some(url);
msg.thumb = Some(t);
}
pub fn from_json_events_iter<'a, I>(roomid: &str, events: I) -> Vec<Message>
where
I: Iterator<Item = &'a JsonValue>,
{
let mut ms = vec![];
let evs = events.filter(Message::supported_event);
for msg in evs {
let m = Message::parse_room_message(roomid.clone(), msg);
ms.push(m);
}
ms
}
pub fn set_receipt(&mut self, receipt: HashMap<String, i64>) {
self.receipt = receipt;
}
}
pub fn get_txn_id(room: &str, body: &str, date: &str) -> String {
let msg = format!("{}{}{}", room, body, date);
let digest = md5::compute(msg.as_bytes());
format!("{:x}", digest)
}