parent
d4d2463a05
commit
b26bfd50fa
@ -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…
Reference in new issue