matrix-ril100/src/main.rs

194 lines
5.5 KiB
Rust

use matrix_sdk::{
config::SyncSettings,
room::Room,
ruma::{
events::room::{
member::StrippedRoomMemberEvent,
message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent},
},
OwnedUserId,
},
Client,
};
use miette::{miette, Context, IntoDiagnostic, Result};
use dotenv::dotenv;
use reqwest::Url;
use serde_json::Value;
use std::env;
#[tokio::main]
async fn main() -> Result<()> {
dotenv().ok();
let hs = env::var("MATRIX_HOMESERVER")
.into_diagnostic()
.wrap_err("Variable: MATRIX_HOMESERVER")?;
let username = env::var("MATRIX_USERNAME")
.into_diagnostic()
.wrap_err("Variable: MATRIX_USERNAME")?;
let password = env::var("MATRIX_PASSWORD")
.into_diagnostic()
.wrap_err("Variable: MATRIX_PASSWORD")?;
let hs_url = Url::parse(&hs).into_diagnostic()?;
let client = Client::new(hs_url.clone()).await.into_diagnostic()?;
client
.login_username(&username, &password)
.initial_device_display_name("RIL100 Bot")
.send()
.await
.into_diagnostic()?;
let response = client
.sync_once(SyncSettings::default())
.await
.into_diagnostic()?;
println!("Logged in as RIL100 bot");
let user_id = client
.user_id()
.ok_or_else(|| return miette!("Client does not have a User Id"))?
.to_owned();
client.add_event_handler(on_stripped_state_member);
client.add_event_handler(move |ev, room| on_room_message(ev, room, user_id.clone()));
let settings = SyncSettings::default().token(response.next_batch);
client.sync(settings).await.into_diagnostic()?;
Ok(())
}
async fn on_room_message(
event: OriginalSyncRoomMessageEvent,
room: Room,
current_user: OwnedUserId,
) -> Result<()> {
// Make sure room is joined
let Room::Joined(room) = room else { return Ok(()) };
// Check if message has text content
let MessageType::Text(text) = event.clone().content.msgtype else { return Ok(()) };
// Do not reply to own messages
if event.sender == current_user {
return Ok(());
};
let user_uri = &current_user.matrix_to_uri().to_string();
let user_uri = urlencoding::decode(user_uri)
.into_diagnostic()?
.into_owned();
// Only reply to mentions
if text.formatted.map_or(false, |v| {
v.body.contains(&format!("<a href=\"{user_uri}\">"))
}) {
// Drop the mention
let query = text
.body
.split_once(": ")
.ok_or_else(|| {
return miette!("Message both contains and doesn't contain a mention 🤨");
})?
.1
.trim()
.to_string();
let response = if query.chars().all(char::is_uppercase) {
let body = reqwest::get(format!("https://v6.db.transport.rest/stations/{query}"))
.await
.into_diagnostic()?
.json::<Value>()
.await
.into_diagnostic()?;
let name = body
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
format!("<b>{query}</b> is <b>{name}</b>")
} else {
let body = reqwest::get(format!(
"https://v6.db.transport.rest/stations?query={query}&results=1"
))
.await
.into_diagnostic()?
.json::<Value>()
.await
.into_diagnostic()?;
// Get first object field
let body = body.as_object().and_then(|v| v.values().next());
let code = body
.and_then(|v| v.get("ril100"))
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let station = body
.and_then(|v| v.get("name"))
.and_then(|v| v.as_str())
.unwrap_or(&query);
format!("<b>{station}</b> is <b>{code}</b>")
};
room.send(
RoomMessageEventContent::text_html(
response.replace("<b>", "").replace("</b>", ""),
response,
)
.make_reply_to(&event.into_full_event(room.room_id().into())),
None,
)
.await
.into_diagnostic()?;
};
Ok(())
}
async fn on_stripped_state_member(
room_member: StrippedRoomMemberEvent,
client: Client,
room: Room,
) -> Result<()> {
if room_member.state_key
!= client
.user_id()
.ok_or_else(|| return miette!("Client does not have a User Id"))?
{
return Ok(());
}
if let Room::Invited(room) = room {
tokio::spawn(async move {
let mut delay = 2;
while let Err(err) = room.accept_invitation().await {
// retry autojoin due to synapse sending invites, before the
// invited user can join for more information see
// https://github.com/matrix-org/synapse/issues/4345
miette!(format!(
"Failed to join room {} ({err:?}), retrying in {delay}s",
room.room_id()
));
tokio::time::sleep(tokio::time::Duration::from_secs(delay)).await;
delay *= 2;
if delay > 3600 {
miette!(format!("Can't join room {} ({err:?})", room.room_id()));
break;
}
}
});
}
Ok(())
}