more fixes and matrix work
This commit is contained in:
parent
36c21c3c2b
commit
89486478fc
6 changed files with 1025 additions and 18 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
/target
|
||||
.env
|
||||
|
|
|
|||
872
Cargo.lock
generated
872
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,4 +9,8 @@ tokio = { version = "1", features = ["full"] }
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
tracing-subscriber = "0.3"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
urlencoding = "2"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
anyhow = "1"
|
||||
|
|
@ -5,4 +5,5 @@ services:
|
|||
ports:
|
||||
- "9000:9000"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
- "host.docker.internal:host-gateway"
|
||||
env_file: .env
|
||||
68
src/main.rs
68
src/main.rs
|
|
@ -1,11 +1,12 @@
|
|||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Path, Query},
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
routing::{get, put},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AuthQuery {
|
||||
|
|
@ -17,55 +18,100 @@ struct Transaction {
|
|||
events: Vec<Value>,
|
||||
}
|
||||
|
||||
const HS_TOKEN: &str = "6fab517dbd08182ccd5d935f0b6e2719dea572fa8dc15206a6f6fb9f2948accb";
|
||||
mod matrix;
|
||||
use matrix::MatrixClient;
|
||||
|
||||
use std::env;
|
||||
|
||||
fn env_var(name: &str) -> String {
|
||||
env::var(name).unwrap_or_else(|_| panic!("missing env {}", name))
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
hs_token: String,
|
||||
bot_mxid: String,
|
||||
matrix: MatrixClient,
|
||||
}
|
||||
|
||||
/// TODO: change post handler and then path to correct location
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
let hs_url = env_var("HS_URL");
|
||||
let hs_token = env_var("HS_TOKEN");
|
||||
let as_token = env_var("AS_TOKEN");
|
||||
let bot_mxid = env_var("BOT_MXID");
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
hs_token,
|
||||
bot_mxid,
|
||||
matrix: MatrixClient::new(hs_url, as_token),
|
||||
});
|
||||
|
||||
let app = Router::new()
|
||||
.route("/_matrix/app/v1/transactions/{txn_id}", put(transactions))
|
||||
.route("/_matrix/app/v1/users/{user_id}", get(query_user))
|
||||
.route("/_matrix/app/v1/rooms/{room_alias}", get(query_alias))
|
||||
.route("/health", get(|| async { "ok" }));
|
||||
.route("/health", get(|| async { "ok" }))
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:9000").await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
fn check_auth(q: &AuthQuery) -> Result<(), StatusCode> {
|
||||
fn check_auth(q: &AuthQuery, hs_token: &str) -> Result<(), StatusCode> {
|
||||
match q.access_token.as_deref() {
|
||||
Some(t) if t == HS_TOKEN => Ok(()),
|
||||
Some(t) if t == hs_token => Ok(()),
|
||||
_ => Err(StatusCode::FORBIDDEN),
|
||||
}
|
||||
}
|
||||
|
||||
async fn transactions(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(txn_id): Path<String>,
|
||||
Query(auth): Query<AuthQuery>,
|
||||
Json(body): Json<Transaction>,
|
||||
) -> Result<Json<Value>, StatusCode> {
|
||||
check_auth(&auth)?;
|
||||
check_auth(&auth, &state.hs_token)?;
|
||||
tracing::info!("txn {} with {} events", txn_id, body.events.len());
|
||||
for ev in &body.events {
|
||||
println!("{}", serde_json::to_string_pretty(ev).unwrap());
|
||||
if let Err(e) = handle_event(&state.matrix, &state.bot_mxid, ev).await {
|
||||
tracing::error!("error handling event: {}", e);
|
||||
}
|
||||
}
|
||||
Ok(Json(json!({})))
|
||||
}
|
||||
|
||||
async fn query_user(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(user_id): Path<String>,
|
||||
Query(auth): Query<AuthQuery>,
|
||||
) -> Result<Json<Value>, StatusCode> {
|
||||
check_auth(&auth)?;
|
||||
check_auth(&auth, &state.hs_token)?;
|
||||
tracing::info!("user query: {}", user_id);
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
|
||||
async fn query_alias(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(room_alias): Path<String>,
|
||||
Query(auth): Query<AuthQuery>,
|
||||
) -> Result<Json<Value>, StatusCode> {
|
||||
check_auth(&auth)?;
|
||||
check_auth(&auth, &state.hs_token)?;
|
||||
tracing::info!("alias query: {}", room_alias);
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
|
||||
async fn handle_event(client: &MatrixClient, bot_mxid: &str, ev: &Value) -> anyhow::Result<()> {
|
||||
let ev_type = ev["type"].as_str().unwrap_or("");
|
||||
let room_id = ev["room_id"].as_str().unwrap_or("");
|
||||
|
||||
if ev_type == "m.room.member"
|
||||
&& ev["state_key"].as_str() == Some(bot_mxid)
|
||||
&& ev["content"]["membership"].as_str() == Some("invite")
|
||||
{
|
||||
tracing::info!("joining {} as {}", room_id, bot_mxid);
|
||||
client.join_room(room_id, bot_mxid).await?;
|
||||
client.send_text(room_id, bot_mxid, "hi, i'm alive").await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
93
src/matrix.rs
Normal file
93
src/matrix.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use reqwest::Client;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
pub struct MatrixClient {
|
||||
http: Client,
|
||||
homeserver: String,
|
||||
as_token: String,
|
||||
}
|
||||
|
||||
impl MatrixClient {
|
||||
pub fn new(homeserver: impl Into<String>, as_token: impl Into<String>) -> Self {
|
||||
Self {
|
||||
http: Client::new(),
|
||||
homeserver: homeserver.into(),
|
||||
as_token: as_token.into(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn req(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: &str,
|
||||
user_id: Option<&str>,
|
||||
body: Option<Value>,
|
||||
) -> Result<Value> {
|
||||
let mut url = format!("{}{}", self.homeserver, path);
|
||||
if let Some(u) = user_id {
|
||||
let sep = if path.contains('?') { '&' } else { '?' };
|
||||
url.push(sep);
|
||||
url.push_str(&format!("user_id={}", urlencoding::encode(u)));
|
||||
}
|
||||
let mut req = self.http.request(method, &url).bearer_auth(&self.as_token);
|
||||
if let Some(b) = body {
|
||||
req = req.json(&b);
|
||||
}
|
||||
let res = req.send().await?;
|
||||
let status = res.status();
|
||||
let text = res.text().await?;
|
||||
if !status.is_success() {
|
||||
return Err(anyhow!("matrix {} {}: {}", status, url, text));
|
||||
}
|
||||
Ok(serde_json::from_str(&text).unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
pub async fn join_room(&self, room_id: &str, user_id: &str) -> Result<()> {
|
||||
let path = format!(
|
||||
"/_matrix/client/v3/rooms/{}/join",
|
||||
urlencoding::encode(room_id)
|
||||
);
|
||||
self.req(reqwest::Method::POST, &path, Some(user_id), Some(json!({})))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_text(&self, room_id: &str, user_id: &str, body: &str) -> Result<String> {
|
||||
let txn = uuid::Uuid::new_v4().to_string();
|
||||
let path = format!(
|
||||
"/_matrix/client/v3/rooms/{}/send/m.room.message/{}",
|
||||
urlencoding::encode(room_id),
|
||||
txn
|
||||
);
|
||||
let res = self
|
||||
.req(
|
||||
reqwest::Method::PUT,
|
||||
&path,
|
||||
Some(user_id),
|
||||
Some(json!({ "msgtype": "m.text", "body": body })),
|
||||
)
|
||||
.await?;
|
||||
Ok(res["event_id"].as_str().unwrap_or("").to_string())
|
||||
}
|
||||
|
||||
pub async fn register_ghost(&self, localpart: &str) -> Result<()> {
|
||||
let body = json!({
|
||||
"type": "m.login.application_service",
|
||||
"username": localpart,
|
||||
});
|
||||
match self
|
||||
.req(
|
||||
reqwest::Method::POST,
|
||||
"/_matrix/client/v3/register",
|
||||
None,
|
||||
Some(body),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) if e.to_string().contains("M_USER_IN_USE") => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue