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