initial commit
This commit is contained in:
commit
59034086e9
4 changed files with 3050 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
config.toml
|
||||||
|
/data
|
2830
Cargo.lock
generated
Normal file
2830
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "matrix-bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Edward Shen <code@eddie.sh>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
matrix-sdk = "0.4"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
url = "2.1"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
toml = "0.5"
|
||||||
|
clap = { version = "3", features = ["derive"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
uwuify = "0.2"
|
199
src/main.rs
Normal file
199
src/main.rs
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
#![warn(clippy::nursery, clippy::pedantic)]
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use matrix_sdk::room::{Joined, Room};
|
||||||
|
use matrix_sdk::ruma::events::room::member::MemberEventContent;
|
||||||
|
use matrix_sdk::ruma::events::room::message::{
|
||||||
|
MessageEventContent, MessageType, TextMessageEventContent,
|
||||||
|
};
|
||||||
|
use matrix_sdk::ruma::events::{StrippedStateEvent, SyncMessageEvent};
|
||||||
|
use matrix_sdk::ruma::UserId;
|
||||||
|
use matrix_sdk::{Client, ClientConfig, Result, Session, SyncSettings};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
subcommand: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
enum Command {
|
||||||
|
Login {
|
||||||
|
user_id: UserId,
|
||||||
|
password: String,
|
||||||
|
},
|
||||||
|
Run {
|
||||||
|
#[clap(default_value = "config.toml")]
|
||||||
|
config_path: PathBuf,
|
||||||
|
#[clap(default_value = "data")]
|
||||||
|
store_path: PathBuf,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct TomlConfig {
|
||||||
|
session: Session,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
match args.subcommand {
|
||||||
|
Command::Login { user_id, password } => handle_login(user_id, &password).await,
|
||||||
|
Command::Run {
|
||||||
|
config_path,
|
||||||
|
store_path,
|
||||||
|
} => handle_run(config_path, store_path).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_login(user_id: UserId, password: &str) -> Result<()> {
|
||||||
|
let client = Client::new_from_user_id(user_id.clone()).await?;
|
||||||
|
let resp = client
|
||||||
|
.login(user_id.localpart(), password, None, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Config generated. Please store this somewhere (like config.toml)!");
|
||||||
|
println!(
|
||||||
|
"\n\n{}",
|
||||||
|
toml::to_string_pretty(&TomlConfig {
|
||||||
|
session: Session {
|
||||||
|
access_token: resp.access_token,
|
||||||
|
device_id: resp.device_id,
|
||||||
|
user_id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_run(config_path: PathBuf, store_path: PathBuf) -> Result<()> {
|
||||||
|
let data = std::fs::read_to_string(config_path)?;
|
||||||
|
let config: TomlConfig = toml::from_str(&data).unwrap();
|
||||||
|
|
||||||
|
let client_config = ClientConfig::default().store_path(store_path);
|
||||||
|
let client =
|
||||||
|
Client::new_from_user_id_with_config(config.session.user_id.clone(), client_config).await?;
|
||||||
|
client.restore_login(config.session.clone()).await?;
|
||||||
|
|
||||||
|
info!(user = %config.session.user_id, device_id = %config.session.device_id, "Logged in");
|
||||||
|
|
||||||
|
let sync_settings = SyncSettings::default();
|
||||||
|
|
||||||
|
// Sync once before registering events to not double-send events on boot.
|
||||||
|
client.sync_once(sync_settings.clone()).await?;
|
||||||
|
|
||||||
|
client
|
||||||
|
.register_event_handler(auto_join)
|
||||||
|
.await
|
||||||
|
.register_event_handler(on_room_message)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Syncing is important to synchronize the client state with the server.
|
||||||
|
// This method will never return.
|
||||||
|
client.sync(sync_settings).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn auto_join(
|
||||||
|
room_member: StrippedStateEvent<MemberEventContent>,
|
||||||
|
client: Client,
|
||||||
|
room: Room,
|
||||||
|
) -> Result<()> {
|
||||||
|
if room_member.state_key != client.user_id().await.unwrap() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Room::Invited(room) = room {
|
||||||
|
info!("Autojoining room {}", room.room_id());
|
||||||
|
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
|
||||||
|
error!(
|
||||||
|
room_id = ?room.room_id(),
|
||||||
|
?err,
|
||||||
|
"Failed to join room, retrying in {delay}s",
|
||||||
|
);
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(delay)).await;
|
||||||
|
delay *= 2;
|
||||||
|
|
||||||
|
if delay > 3600 {
|
||||||
|
error!(
|
||||||
|
room_id = ?room.room_id(),
|
||||||
|
?err,
|
||||||
|
"Failed to join room. Giving up.",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(room_id = ?room.room_id(), "Successfully joined room.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_room_message(event: SyncMessageEvent<MessageEventContent>, room: Room) -> Result<()> {
|
||||||
|
if let Room::Joined(room) = room {
|
||||||
|
parse_message(event, room).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse_message(event: SyncMessageEvent<MessageEventContent>, room: Joined) -> Result<()> {
|
||||||
|
let message = match &event.content.msgtype {
|
||||||
|
MessageType::Text(TextMessageEventContent { body, .. }) => body.clone(),
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(message) = message.strip_prefix("!") {
|
||||||
|
if let Some((command, args)) = message.split_once(" ") {
|
||||||
|
match command {
|
||||||
|
"source" => source(room).await?,
|
||||||
|
"uwu" => uwuify(room, args).await?,
|
||||||
|
_ => unsupported_command(event, room, command).await?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn source(room: Joined) -> Result<()> {
|
||||||
|
let content = MessageEventContent::text_plain("https://git.eddie.sh/edward/matrix-bot");
|
||||||
|
room.send(content, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn uwuify(room: Joined, message: &str) -> Result<()> {
|
||||||
|
let content = MessageEventContent::text_plain(uwuifier::uwuify_str_sse(message));
|
||||||
|
room.send(content, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unsupported_command(
|
||||||
|
original_event: SyncMessageEvent<MessageEventContent>,
|
||||||
|
room: Joined,
|
||||||
|
command: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let resp = original_event.into_full_event(room.room_id().to_owned());
|
||||||
|
let content =
|
||||||
|
MessageEventContent::notice_reply_plain(format!("Unknown command `{command}`"), &resp);
|
||||||
|
room.send(content, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue