194 lines
5.5 KiB
Rust
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 = ¤t_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(())
|
|
}
|