Initial commit

master
Tranquillity Codes 12 months ago
parent d4d2463a05
commit b26bfd50fa

1
.gitignore vendored

@ -0,0 +1 @@
/target

@ -0,0 +1,12 @@
[package]
name = "mxclient"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = {version = "1.0.191", features = ["derive"]}
serde_json = {version = "1.0.108", features = ["raw_value"]}
serde_with = {version = "3.4.0", features = ["json"]}
ureq = {version = "2.8.0", features = ["json"]}

@ -0,0 +1,429 @@
#![allow(dead_code)]
#![allow(unused_variables)]
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::collections::HashMap;
use std::time::Instant;
use std::fs::read_to_string;
use std::fs;
//use serde_json::Result;
// {"m.homeserver": {"base_url": "https://matrix.itycodes.org", "onion_url": "http://matrix.gnozq2oxulflviccmdjt3aour42jgahycazoy3mdpuebzi2s4kktpwqd.onion"}}
#[derive(Serialize, Deserialize, Debug)]
struct HomeServer {
base_url: String,
onion_url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct WellKnownClient {
#[serde(rename(deserialize = "m.homeserver"))]
m_homeserver: HomeServer,
}
#[derive(Eq, Hash, PartialEq, Debug, Clone)]
struct MatrixUser {
name: String,
server: String
}
#[derive(Debug)]
struct MatrixClient {
homeserver: String,
user: MatrixUser,
token: Option<String>
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize)]
struct Config {
homeserver: String,
user: String,
password: String,
token: Option<String>,
device: Option<String>
}
#[derive(Serialize, Deserialize, Debug)]
struct LoginIdentifier {
#[serde(rename(serialize = "type"))]
ident_type: String,
user: String
}
#[derive(Serialize, Deserialize, Debug)]
struct LoginDetails {
identifier: LoginIdentifier,
#[serde(rename(serialize = "initial_device_display_name"))]
initial_name: String,
password: String,
#[serde(rename(serialize = "type"))]
login_type: String
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct LoginToken {
#[serde(rename(deserialize = "access_token"))]
token: String,
device_id: String
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
struct StateFilter {
limit: Option<u64>,
contains_url: Option<bool>,
include_redudant_members: Option<bool>,
unread_thread_notifications: Option<bool>,
lazy_load_members: Option<bool>,
not_rooms: Option<Vec<String>>,
not_types: Option<Vec<String>>,
not_senders: Option<Vec<String>>,
rooms: Option<Vec<String>>,
senders: Option<Vec<String>>,
types: Option<Vec<String>>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
struct RoomEventFilter {
limit: Option<u64>,
contains_url: Option<bool>,
include_redudant_members: Option<bool>,
unread_thread_notifications: Option<bool>,
lazy_load_members: Option<bool>,
not_rooms: Option<Vec<String>>,
not_types: Option<Vec<String>>,
not_senders: Option<Vec<String>>,
rooms: Option<Vec<String>>,
senders: Option<Vec<String>>,
types: Option<Vec<String>>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
struct EventFilter {
limit: Option<u64>,
not_senders: Option<Vec<String>>,
not_types: Option<Vec<String>>,
senders: Option<Vec<String>>,
types: Option<Vec<String>>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
struct RoomFilter {
account_data: RoomEventFilter,
ephemeral: RoomEventFilter,
include_leave: Option<bool>,
not_rooms: Option<Vec<String>>,
rooms: Option<Vec<String>>,
state: StateFilter,
timeline: RoomEventFilter
}
#[derive(Serialize, Deserialize, Debug)]
enum EventFormat {
#[serde(rename = "client")]
Client,
#[serde(rename = "federation")]
Federation
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Default)]
struct FilterConfig {
account_data: EventFilter,
event_fields: Option<Vec<String>>,
event_format: Option<EventFormat>,
presence: EventFilter,
room: RoomFilter
}
#[derive(Serialize, Deserialize, Debug)]
struct Filter {
filter_id: String,
}
#[derive(Serialize, Deserialize, Debug)]
enum SetPresence {
#[serde(rename = "offline")]
Offline,
#[serde(rename = "online")]
Online,
#[serde(rename = "unavailable")]
Unavailable
}
#[derive(Serialize, Deserialize, Debug, Default)]
struct SyncConfig {
filter: String, // Does Rust have proper unions?
full_state: Option<bool>,
set_presence: Option<SetPresence>,
since: Option<String>,
timeout: Option<u64>
}
#[derive(Serialize, Deserialize, Debug)]
struct Event {
content: HashMap<String, serde_json::Value>,
#[serde(rename = "type")]
event_type: String
}
#[derive(Serialize, Deserialize, Debug)]
struct AccountData {
events: Vec<Event>
}
#[derive(Serialize, Deserialize, Debug)]
struct StrippedStateEvent {
//content: EventContent,
sender: String,
state_key: String,
#[serde(rename = "type")]
event_type: String
}
//#[derive(Serialize, Deserialize, Debug)]
//struct InviteState {
// events: Vec<StrippedStateEvent>
//}
//#[derive(Serialize, Deserialize, Debug)]
//struct InvitedRooms {
// invite_state: InviteState
//}
#[derive(Serialize, Deserialize, Debug)]
struct ClientEventWithoutRoomID {
content: HashMap<String, serde_json::Value>,
event_id: String,
origin_server_ts: u64,
sender: String,
state_key: Option<String>,
#[serde(rename = "type")]
event_type: String,
// unsigned: UnsignedData
}
#[derive(Serialize, Deserialize, Debug)]
struct State {
events: Vec<Event>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Timeline {
events: Vec<Event>,
limited: bool,
prev_batch: String
}
#[derive(Serialize, Deserialize, Debug)]
struct JoinedRooms {
account_data: AccountData,
state: State,
timeline: Timeline
}
#[derive(Serialize, Deserialize, Debug)]
struct Rooms {
//invite: ,
join: HashMap<String, JoinedRooms>,
//knock: ,
//leave: ,
}
#[derive(Serialize, Deserialize, Debug)]
struct SyncData {
account_data: AccountData,
rooms: Rooms,
}
#[derive(Default, Debug, Clone)]
enum Priority {
Low,
#[default]
Normal,
High
}
#[derive(Default, Debug, Clone)]
struct Room {
id: String,
name: Option<String>,
alias: Option<String>,
is_dm: bool,
priority: Priority,
prev_batch: String
}
#[derive(Default, Debug)]
struct RoomIndex {
high_priority: Vec<Room>,
normal_priority: Vec<Room>,
low_priority: Vec<Room>,
}
#[derive(Default, Debug)]
struct DMIndex {
high_priority: HashMap<MatrixUser, Vec<Room>>,
normal_priority: HashMap<MatrixUser, Vec<Room>>,
low_priority: HashMap<MatrixUser, Vec<Room>>,
}
fn well_known(homeserver_url: &str) -> WellKnownClient {
let body: String = ureq::get(format!("{homeserver_url}/.well-known/matrix/client").as_str())
//.set("Example-Header", "header value")
.call().unwrap()
.into_string().unwrap();
let well_known: WellKnownClient = serde_json::from_str(body.as_str()).unwrap();
well_known
}
fn login(config: &MatrixClient, details: LoginDetails) -> LoginToken {
let body: String = ureq::post(format!("{0}/_matrix/client/v3/login", config.homeserver).as_str())
.send_json(details).unwrap()
.into_string().unwrap();
let token: LoginToken = serde_json::from_str(body.as_str()).unwrap();
token
}
fn make_filter(config: &MatrixClient, filter: FilterConfig) -> Filter {
let body: String = ureq::post(format!("{0}/_matrix/client/v3/user/{1}/filter", config.homeserver, format!("@{0}:{1}", config.user.name.as_str(), config.user.server.as_str())).as_str())
.set("Authorization", format!("Bearer {0}", config.token.as_ref().unwrap()).as_str())
.send_json(filter).unwrap()
.into_string().unwrap();
let filter: Filter = serde_json::from_str(body.as_str()).unwrap();
filter
}
fn do_sync(config: &MatrixClient, params: SyncConfig) -> SyncData {
let body: String = ureq::get(format!("{0}/_matrix/client/v3/sync", config.homeserver).as_str())
.set("Authorization", format!("Bearer {0}", config.token.as_ref().unwrap()).as_str())
.query("filter", params.filter.as_str())
.send_json(params).unwrap()
.into_string().unwrap();
//println!("{}", body);
let synced: SyncData = serde_json::from_str(body.as_str()).unwrap();
synced
}
fn main() {
let mut cfg: Config = serde_json::from_str(&read_to_string("./config.json").unwrap()).unwrap();
let homeserver = cfg.homeserver.clone();
let mut client = MatrixClient { homeserver: well_known(format!("https://{homeserver}").as_str()).m_homeserver.base_url.to_owned(), user: MatrixUser { name: cfg.user.clone(), server: homeserver.to_string() }, token: None };
let login_details = LoginDetails {
identifier: LoginIdentifier { user: cfg.user.clone(), ident_type: String::from("m.id.user") },
initial_name: String::from("rsmx"),
password: cfg.password.clone(),
login_type: String::from("m.login.password")
};
let before_sync = Instant::now();
let mut opt_token: Option<LoginToken> = None;
if cfg.token == None || cfg.device == None {
let logged = login(&client, login_details);
opt_token = Some(logged.clone());
cfg.token = Some(logged.token);
cfg.device = Some(logged.device_id);
fs::write("./config.json", serde_json::to_string(&cfg).unwrap()).unwrap();
}
//let token = LoginToken { token: String::from("syt_aXR5_pZMxSUrXpalVqsEVWOco_4BFwQl"), device_id: "KIYJVNPCYP".to_string() };
let token = opt_token.unwrap_or( LoginToken { token: cfg.token.unwrap(), device_id: cfg.device.unwrap() } );
println!("Device ID: {0}", token.device_id);
println!("Token: {0}", token.token);
client.token = Some(token.token);
let filter_conf = FilterConfig {
room: RoomFilter {
timeline: RoomEventFilter {
types: Some(Vec::from(["m.room.member".to_string()])),
senders: Some(Vec::from([format!("@{0}:{1}", client.user.name.as_str(), client.user.server.as_str())])),
..Default::default()
},
ephemeral: RoomEventFilter {
types: Some(Vec::new()),
..Default::default()
},
state: StateFilter {
types: Some(Vec::from(["m.room.name".to_string(), "m.room.canonical_alias".to_string()])),
..Default::default()
},
..Default::default()
},
presence: EventFilter {
types: Some(Vec::new()),
..Default::default()
},
..Default::default()
};
//println!("{:?}", filter_conf);
//println!("{}", serde_json::to_string(&filter_conf).unwrap());
let filter = make_filter(&client, filter_conf);
//println!("{:?}", &filter);
let initial = do_sync(&client, SyncConfig {filter: filter.filter_id, ..Default::default() } );
println!("Sync: {0}ms", before_sync.elapsed().as_millis());
//println!("{}", serde_json::to_string(&initial).unwrap());
let joined = initial.rooms.join;
//let mut tmp_dms: HashMap<MatrixUser, Vec<Room>> = Default::default();
let before_parse = Instant::now();
let mut room_index: RoomIndex = Default::default();
for (room_id, room_data) in joined {
let mut name: Option<String> = None;
let mut alias: Option<String> = None;
for state_ev in room_data.state.events {
if state_ev.event_type == "m.room.name" {
name = Some(state_ev.content.get("name").unwrap().as_str().unwrap().to_string());
}
if state_ev.event_type == "m.room.canonical_alias" {
alias = Some(state_ev.content.get("alias").unwrap().as_str().unwrap().to_string());
}
}
let is_direct = false;
let prev_batch = room_data.timeline.prev_batch;
for ev in room_data.account_data.events {
if ev.event_type == "m.tag" {
if ev.content.get("tags").unwrap().as_object().unwrap().contains_key("m.lowpriority") {
room_index.low_priority.push(Room { id: room_id.clone(), name: name.clone(), alias: alias.clone(), priority: Priority::Low, is_dm: is_direct, prev_batch: prev_batch.clone() });
} else if ev.content.get("tags").unwrap().as_object().unwrap().contains_key("m.favourite") {
room_index.high_priority.push(Room { id: room_id.clone(), name: name.clone(), alias: alias.clone(), priority: Priority::High, is_dm: is_direct, prev_batch: prev_batch.clone() });
} else {
let weird_res = ev.content.get("tags").unwrap().as_object().unwrap();
panic!("Unreachable: {}", serde_json::to_string(&weird_res).unwrap());
}
break;
}
}
room_index.normal_priority.push(Room { id: room_id.clone(), name: name.clone(), alias: alias.clone(), priority: Priority::Normal, is_dm: is_direct, prev_batch: prev_batch.clone() });
}
let account_events = initial.account_data.events;
let mut dms: DMIndex = Default::default();
for ev in account_events {
if ev.event_type == "m.direct" {
for (user_name, room_ids) in ev.content {
let mut low_dms: Vec<Room> = Vec::new();
let mut normal_dms: Vec<Room> = Vec::new();
let mut high_dms: Vec<Room> = Vec::new();
for room_id in room_ids.as_array().unwrap() {
// why is a clone needed here if the iterator is not mutable?
for room in &room_index.low_priority {
if room.id == room_id.as_str().unwrap() {
low_dms.push(room.clone());
break;
}
}
for room in &room_index.normal_priority {
if room.id == room_id.as_str().unwrap() {
normal_dms.push(room.clone());
break;
}
}
for room in &room_index.high_priority {
if room.id == room_id.as_str().unwrap() {
high_dms.push(room.clone());
break;
}
}
}
let split = user_name.split(":").collect::<Vec<&str>>();
let name = split.get(0).unwrap()[1..].to_string();
let server = split.get(1).unwrap().to_string();
let user = MatrixUser { name: name, server: server };
if low_dms.len() > 0 {
dms.low_priority.insert(user.clone(), low_dms);
}
if normal_dms.len() > 0 {
dms.normal_priority.insert(user.clone(), normal_dms);
}
if high_dms.len() > 0 {
dms.high_priority.insert(user.clone(), high_dms);
}
}
}
}
println!("Parse: {0}ms", before_parse.elapsed().as_millis());
//for room in dms.high_priority {
// println!("{:?}", room);
//}
}
Loading…
Cancel
Save