From 591d25c0c02bafe8567b728f6bbec4c1718dcfd0 Mon Sep 17 00:00:00 2001 From: Noa Himesaka Date: Thu, 29 Jan 2026 01:58:09 +0900 Subject: [PATCH] feat: some fixes, features, and README --- .env.example | 1 + README.md | 42 +++++++++++++++++++++++++++++++++++ docker-compose.example.yml | 1 + src/links.rs | 6 +++++ src/main.rs | 45 +++++++++++++++++++++++++++++++++----- 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 README.md diff --git a/.env.example b/.env.example index 9a8b568..5ef717b 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ MATRIX_HOMESERVER=https://matrix.org MATRIX_USERNAME=your_username MATRIX_PASSWORD=your_password +IGNORE_BRIDGE_USERS=false diff --git a/README.md b/README.md new file mode 100644 index 0000000..f857fbb --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Matrix Wiki Link Bot + +A simple Matrix bot that listens for commands starting with `.wiki` and responds with links from a configured JSON store. + +## Configuration + +The bot is configured via environment variables. + +| Variable | Description | Default | +|----------|-------------|---------| +| `MATRIX_HOMESERVER` | The URL of your Matrix homeserver (e.g., `https://matrix.org`). | | +| `MATRIX_USERNAME` | The bot's Matrix username. | | +| `MATRIX_PASSWORD` | The bot's Matrix password. | | +| `IGNORE_BRIDGE_USERS`| If set to `true`, the bot will ignore messages from senders whose MXID starts with `@discord_`. | `false` | + +## Usage + +### Docker + +1. Copy `docker-compose.example.yml` to `docker-compose.yml` and `links.json` to a local file. +2. Update the environment variables in `docker-compose.yml`. +3. Populate `links.json` with your key-value pairs. +4. Run the bot: + + ```bash + docker-compose up -d + ``` + +### Local Development + +1. Copy `.env.example` to `.env`. +2. Update `.env` with your credentials. +3. Run the bot: + + ```bash + cargo run + ``` + +## Commands + +- `.wiki list`: Lists all available wiki keys. +- `.wiki `: Fetches the link associated with `` from the `links.json` file. diff --git a/docker-compose.example.yml b/docker-compose.example.yml index c2af3d5..4855ce0 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -5,6 +5,7 @@ services: - MATRIX_HOMESERVER=https://matrix.org - MATRIX_USERNAME=your_username - MATRIX_PASSWORD=your_password + - IGNORE_BRIDGE_USERS=false volumes: - ./links.json:/usr/local/bin/links.json restart: unless-stopped diff --git a/src/links.rs b/src/links.rs index 3402f10..eb1d827 100644 --- a/src/links.rs +++ b/src/links.rs @@ -17,6 +17,12 @@ impl LinkStore { pub fn get(&self, key: &str) -> Option<&String> { self.links.get(key) } + + pub fn list_keys(&self) -> Vec { + let mut keys: Vec = self.links.keys().cloned().collect(); + keys.sort(); + keys + } } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index 4b8e7a1..038e6b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,20 +37,55 @@ async fn main() -> Result<()> { client.matrix_auth().login_username(&username, &password).await?; + let ignore_bridge_users = env::var("IGNORE_BRIDGE_USERS") + .map(|v| v.to_lowercase() == "true") + .unwrap_or(false); + + let start_time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as u64; + // Share link_store with event handler let store_clone = link_store.clone(); client.add_event_handler(move |event: OriginalSyncRoomMessageEvent, room: Room| { let store = store_clone.clone(); async move { + let sender = event.sender.as_str(); + if ignore_bridge_users && sender.starts_with("@discord_") { + return; + } + + if u64::from(event.origin_server_ts.get()) < start_time { + return; + } + let RoomMessageEventContent { msgtype, .. } = event.content; if let MessageType::Text(text_content) = msgtype { let body = text_content.body; - if body.starts_with(".wiki ") { - let argument = body.trim_start_matches(".wiki ").trim(); - if let Some(link) = store.get(argument) { - let response = format!("Link for {}: {}", argument, link); + if body == ".wiki" || body.starts_with(".wiki ") { + let argument = body.trim_start_matches(".wiki").trim(); + if argument.is_empty() { + let response = "Use .wiki list to print all available links"; + let content = RoomMessageEventContent::text_plain(response); + if let Err(e) = room.send(content).await { + eprintln!("Failed to send message: {}", e); + } + } else if argument == "list" { + let keys = store.list_keys(); + let response = if keys.is_empty() { + "No wiki links available.".to_string() + } else { + format!("Available wiki links: {}", keys.join(", ")) + }; + let content = RoomMessageEventContent::text_plain(response); + if let Err(e) = room.send(content).await { + eprintln!("Failed to send message: {}", e); + } + } else if let Some(link) = store.get(argument) { + let response = format!("Link for {}: {}\n(Use .wiki list to print all available links)", argument, link); // Send response let content = RoomMessageEventContent::text_plain(response); if let Err(e) = room.send(content).await { @@ -58,7 +93,7 @@ async fn main() -> Result<()> { } } else { // Optional: reply not found, or just ignore - let response = format!("No wiki link found for '{}'", argument); + let response = format!("No wiki link found for '{}'\n(Use .wiki list to print all available links)", argument); let content = RoomMessageEventContent::text_plain(response); if let Err(e) = room.send(content).await { eprintln!("Failed to send message: {}", e);