Initial commit
This commit is contained in:
commit
9be5f1c1b6
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
.env
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "matrix-ril100"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Agatha V. Lovelace <agatha@technogothic.net>"]
|
||||||
|
description = "A matrix bot that looks up RIL100 codes and station names"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
matrix-sdk = "0.6.2"
|
||||||
|
miette = { version = "5.9.0", features = ["fancy"] }
|
||||||
|
reqwest = { version = "0.11.18", features = ["json"] }
|
||||||
|
serde_json = "1.0.99"
|
||||||
|
tokio = { version = "1.29.0", features = ["full"] }
|
|
@ -0,0 +1,95 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687852486,
|
||||||
|
"narHash": "sha256-2rXkhKUVQxbVaC+TITPpILiy/dSbordOLs87eoWHYxA=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "df10963b956962913b693a638746a95d6c506404",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687946342,
|
||||||
|
"narHash": "sha256-vRxti8pOuXS0rJmqjbD8ueEEFXWSK22ISHoCWkhgzzg=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1c851e8c92b76a00ce84167984a7ec7ba2b1f29c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687946342,
|
||||||
|
"narHash": "sha256-vRxti8pOuXS0rJmqjbD8ueEEFXWSK22ISHoCWkhgzzg=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1c851e8c92b76a00ce84167984a7ec7ba2b1f29c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687709756,
|
||||||
|
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
naersk.url = "github:nix-community/naersk/master";
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, utils, naersk }:
|
||||||
|
utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
naersk-lib = pkgs.callPackage naersk { };
|
||||||
|
in {
|
||||||
|
packages.default = naersk-lib.buildPackage {
|
||||||
|
src = ./.;
|
||||||
|
nativeBuildInputs = with pkgs; [ pkg-config ];
|
||||||
|
buildInputs = with pkgs; [ openssl ];
|
||||||
|
};
|
||||||
|
devShells.default = with pkgs;
|
||||||
|
mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
cargo
|
||||||
|
rustc
|
||||||
|
rustfmt
|
||||||
|
pre-commit
|
||||||
|
rustPackages.clippy
|
||||||
|
rust-analyzer
|
||||||
|
];
|
||||||
|
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
use matrix_sdk::{
|
||||||
|
config::SyncSettings,
|
||||||
|
room::Room,
|
||||||
|
ruma::{
|
||||||
|
events::room::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 homeserver = hs_url
|
||||||
|
.host_str()
|
||||||
|
.ok_or_else(|| return miette!("Homeserver URL does not contain a host 🤨"))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let user_id = client
|
||||||
|
.user_id()
|
||||||
|
.ok_or_else(|| return miette!("Client does not have a User ID"))?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
client.add_event_handler(move |ev, room| {
|
||||||
|
on_room_message(
|
||||||
|
ev,
|
||||||
|
room,
|
||||||
|
username.clone(),
|
||||||
|
homeserver.clone(),
|
||||||
|
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,
|
||||||
|
username: String,
|
||||||
|
homeserver: String,
|
||||||
|
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(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only reply to mentions
|
||||||
|
if text.formatted.map_or(false, |v| {
|
||||||
|
v.body.contains(&format!(
|
||||||
|
"<a href=\"https://matrix.to/#/@{username}:{homeserver}\">"
|
||||||
|
))
|
||||||
|
}) {
|
||||||
|
// 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(())
|
||||||
|
}
|
Loading…
Reference in New Issue