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
|
/target
|
||||||
|
.env
|
||||||
|
|
|
||||||
872
Cargo.lock
generated
872
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -10,3 +10,7 @@ serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tracing = "0.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"
|
||||||
|
|
@ -6,3 +6,4 @@ services:
|
||||||
- "9000:9000"
|
- "9000:9000"
|
||||||
extra_hosts:
|
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::{
|
use axum::{
|
||||||
Json, Router,
|
Json, Router,
|
||||||
extract::{Path, Query},
|
extract::{Path, Query, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
routing::{get, put},
|
routing::{get, put},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AuthQuery {
|
struct AuthQuery {
|
||||||
|
|
@ -17,55 +18,100 @@ struct Transaction {
|
||||||
events: Vec<Value>,
|
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]
|
#[tokio::main]
|
||||||
async fn 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()
|
let app = Router::new()
|
||||||
.route("/_matrix/app/v1/transactions/{txn_id}", put(transactions))
|
.route("/_matrix/app/v1/transactions/{txn_id}", put(transactions))
|
||||||
.route("/_matrix/app/v1/users/{user_id}", get(query_user))
|
.route("/_matrix/app/v1/users/{user_id}", get(query_user))
|
||||||
.route("/_matrix/app/v1/rooms/{room_alias}", get(query_alias))
|
.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();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:9000").await.unwrap();
|
||||||
axum::serve(listener, app).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() {
|
match q.access_token.as_deref() {
|
||||||
Some(t) if t == HS_TOKEN => Ok(()),
|
Some(t) if t == hs_token => Ok(()),
|
||||||
_ => Err(StatusCode::FORBIDDEN),
|
_ => Err(StatusCode::FORBIDDEN),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn transactions(
|
async fn transactions(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
Path(txn_id): Path<String>,
|
Path(txn_id): Path<String>,
|
||||||
Query(auth): Query<AuthQuery>,
|
Query(auth): Query<AuthQuery>,
|
||||||
Json(body): Json<Transaction>,
|
Json(body): Json<Transaction>,
|
||||||
) -> Result<Json<Value>, StatusCode> {
|
) -> Result<Json<Value>, StatusCode> {
|
||||||
check_auth(&auth)?;
|
check_auth(&auth, &state.hs_token)?;
|
||||||
tracing::info!("txn {} with {} events", txn_id, body.events.len());
|
tracing::info!("txn {} with {} events", txn_id, body.events.len());
|
||||||
for ev in &body.events {
|
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!({})))
|
Ok(Json(json!({})))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn query_user(
|
async fn query_user(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
Path(user_id): Path<String>,
|
Path(user_id): Path<String>,
|
||||||
Query(auth): Query<AuthQuery>,
|
Query(auth): Query<AuthQuery>,
|
||||||
) -> Result<Json<Value>, StatusCode> {
|
) -> Result<Json<Value>, StatusCode> {
|
||||||
check_auth(&auth)?;
|
check_auth(&auth, &state.hs_token)?;
|
||||||
tracing::info!("user query: {}", user_id);
|
tracing::info!("user query: {}", user_id);
|
||||||
Err(StatusCode::NOT_FOUND)
|
Err(StatusCode::NOT_FOUND)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn query_alias(
|
async fn query_alias(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
Path(room_alias): Path<String>,
|
Path(room_alias): Path<String>,
|
||||||
Query(auth): Query<AuthQuery>,
|
Query(auth): Query<AuthQuery>,
|
||||||
) -> Result<Json<Value>, StatusCode> {
|
) -> Result<Json<Value>, StatusCode> {
|
||||||
check_auth(&auth)?;
|
check_auth(&auth, &state.hs_token)?;
|
||||||
tracing::info!("alias query: {}", room_alias);
|
tracing::info!("alias query: {}", room_alias);
|
||||||
Err(StatusCode::NOT_FOUND)
|
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