Add support for out-of-line message reading

This commit is contained in:
Edward Shen 2022-05-09 01:04:08 -07:00
parent de71131f6f
commit 5cbb24d51f
Signed by: edward
GPG key ID: 19182661E818369F
4 changed files with 821 additions and 873 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target /target
config.toml config.toml
/data /crypto_data

1537
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,19 +4,23 @@ version = "0.1.0"
authors = ["Edward Shen <code@eddie.sh>"] authors = ["Edward Shen <code@eddie.sh>"]
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
matrix-sdk = "0.4" # matrix-sdk = "0.4"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
url = "2.1" # url = "2.1"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
toml = "0.5" toml = "0.5"
clap = { version = "3", features = ["derive"] } clap = { version = "3", features = ["derive", "cargo"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
# ruma-client-api = "0.14"
uwuify = "0.2" uwuify = "0.2"
enum-iterator = "0.7" enum-iterator = "0.7"
anyhow = "1"
[dependencies.matrix-sdk]
git = "https://github.com/matrix-org/matrix-rust-sdk"
rev = "be2a818c84b21980e9219fa8bd4c15d3a9c588c2"
[profile.release] [profile.release]
strip = true strip = true

View file

@ -5,18 +5,25 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use anyhow::{Context, Result};
use clap::Parser; use clap::Parser;
use enum_iterator::IntoEnumIterator; use enum_iterator::IntoEnumIterator;
use matrix_sdk::room::{Joined, Room}; use matrix_sdk::config::SyncSettings;
use matrix_sdk::ruma::events::room::member::MemberEventContent; use matrix_sdk::room::{Joined, MessagesOptions, Room};
use matrix_sdk::ruma::events::room::member::StrippedRoomMemberEvent;
use matrix_sdk::ruma::events::room::message::{ use matrix_sdk::ruma::events::room::message::{
MessageEventContent, MessageType, TextMessageEventContent, MessageType, RoomMessageEventContent, SyncRoomMessageEvent, TextMessageEventContent,
}; };
use matrix_sdk::ruma::events::{StrippedStateEvent, SyncMessageEvent}; use matrix_sdk::ruma::events::{
use matrix_sdk::ruma::UserId; AnyMessageLikeEvent, AnyRoomEvent, MessageLikeEvent, OriginalMessageLikeEvent,
use matrix_sdk::{Client, ClientConfig, Result, Session, SyncSettings}; OriginalSyncMessageLikeEvent, SyncMessageLikeEvent,
};
use matrix_sdk::ruma::OwnedUserId;
use matrix_sdk::store::CryptoStore;
use matrix_sdk::{Client, Session};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{error, info}; use tracing::{error, info, Level};
use tracing_subscriber::util::SubscriberInitExt;
#[derive(Parser)] #[derive(Parser)]
struct Args { struct Args {
@ -27,14 +34,14 @@ struct Args {
#[derive(Parser)] #[derive(Parser)]
enum Command { enum Command {
Login { Login {
user_id: UserId, user_id: OwnedUserId,
password: String, password: String,
}, },
Run { Run {
#[clap(default_value = "config.toml")] #[clap(default_value = "config.toml")]
config_path: PathBuf, config_path: PathBuf,
#[clap(default_value = "data")] #[clap(default_value = "crypto_data")]
store_path: PathBuf, crypto_store_path: PathBuf,
}, },
} }
@ -45,20 +52,23 @@ struct TomlConfig {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt()
.with_max_level(Level::INFO)
.finish()
.init();
let args = Args::parse(); let args = Args::parse();
match args.subcommand { match args.subcommand {
Command::Login { user_id, password } => handle_login(user_id, &password).await, Command::Login { user_id, password } => handle_login(user_id, &password).await,
Command::Run { Command::Run {
config_path, config_path,
store_path, crypto_store_path,
} => handle_run(config_path, store_path).await, } => handle_run(config_path, crypto_store_path).await,
} }
} }
async fn handle_login(user_id: UserId, password: &str) -> Result<()> { async fn handle_login(user_id: OwnedUserId, password: &str) -> Result<()> {
let client = Client::new_from_user_id(user_id.clone()).await?; let client = Client::builder().user_id(&user_id).build().await?;
let resp = client let resp = client
.login(user_id.localpart(), password, None, None) .login(user_id.localpart(), password, None, None)
.await?; .await?;
@ -79,13 +89,19 @@ async fn handle_login(user_id: UserId, password: &str) -> Result<()> {
Ok(()) Ok(())
} }
async fn handle_run(config_path: PathBuf, store_path: PathBuf) -> Result<()> { async fn handle_run(config_path: PathBuf, crypto_store_path: PathBuf) -> Result<()> {
let data = std::fs::read_to_string(config_path)?; let data = std::fs::read_to_string(config_path)?;
let config: TomlConfig = toml::from_str(&data).unwrap(); let config: TomlConfig = toml::from_str(&data).unwrap();
let client_config = ClientConfig::default().store_path(store_path); let client = Client::builder()
let client = .user_id(&config.session.user_id)
Client::new_from_user_id_with_config(config.session.user_id.clone(), client_config).await?; .crypto_store(Box::new(CryptoStore::open_with_passphrase(
crypto_store_path,
None,
)?))
.build()
.await
.context("s")?;
client.restore_login(config.session.clone()).await?; client.restore_login(config.session.clone()).await?;
info!(user = %config.session.user_id, device_id = %config.session.device_id, "Logged in"); info!(user = %config.session.user_id, device_id = %config.session.device_id, "Logged in");
@ -108,12 +124,8 @@ async fn handle_run(config_path: PathBuf, store_path: PathBuf) -> Result<()> {
Ok(()) Ok(())
} }
async fn auto_join( async fn auto_join(room_member: StrippedRoomMemberEvent, client: Client, room: Room) -> Result<()> {
room_member: StrippedStateEvent<MemberEventContent>, if Some(room_member.sender) == client.user_id().await {
client: Client,
room: Room,
) -> Result<()> {
if room_member.state_key != client.user_id().await.unwrap() {
return Ok(()); return Ok(());
} }
@ -150,13 +162,9 @@ async fn auto_join(
Ok(()) Ok(())
} }
async fn on_room_message( async fn on_room_message(event: SyncRoomMessageEvent, room: Room, client: Client) -> Result<()> {
event: SyncMessageEvent<MessageEventContent>,
room: Room,
client: Client,
) -> Result<()> {
// Ignore messages sent from self. // Ignore messages sent from self.
if client.user_id().await.as_ref() == Some(&event.sender) { if client.user_id().await.as_deref() == Some(event.sender()) {
return Ok(()); return Ok(());
} }
@ -167,9 +175,18 @@ async fn on_room_message(
Ok(()) Ok(())
} }
async fn parse_message(event: SyncMessageEvent<MessageEventContent>, room: Joined) -> Result<()> { async fn parse_message(event: SyncRoomMessageEvent, room: Joined) -> Result<()> {
let message = match &event.content.msgtype { room.read_receipt(event.event_id()).await?;
MessageType::Text(TextMessageEventContent { body, .. }) => body.clone(),
let message = match &event {
SyncMessageLikeEvent::Original(OriginalSyncMessageLikeEvent {
content:
RoomMessageEventContent {
msgtype: MessageType::Text(TextMessageEventContent { body, .. }),
..
},
..
}) => body.clone(),
_ => return Ok(()), _ => return Ok(()),
}; };
@ -187,7 +204,7 @@ async fn parse_message(event: SyncMessageEvent<MessageEventContent>, room: Joine
Ok(()) Ok(())
} }
#[derive(enum_iterator::IntoEnumIterator)] #[derive(IntoEnumIterator)]
enum BotCommand { enum BotCommand {
Help, Help,
Source, Source,
@ -232,24 +249,57 @@ impl Display for BotCommand {
} }
async fn source(room: Joined) -> Result<()> { async fn source(room: Joined) -> Result<()> {
let content = MessageEventContent::text_plain("https://git.eddie.sh/edward/matrix-bot"); let content = RoomMessageEventContent::text_plain("https://git.eddie.sh/edward/matrix-bot");
room.send(content, None).await?; room.send(content, None).await?;
Ok(()) Ok(())
} }
async fn uwuify(room: Joined, message: &str) -> Result<()> { async fn uwuify(room: Joined, message: &str) -> Result<()> {
let message = if message.is_empty() { let message = if message.is_empty() {
if let Ok(msg) = get_previous_message(&room).await {
uwuifier::uwuify_str_sse(&msg.body)
} else {
"uwu".to_owned() "uwu".to_owned()
}
} else { } else {
uwuifier::uwuify_str_sse(message) uwuifier::uwuify_str_sse(message)
}; };
let content = MessageEventContent::text_plain(message); let content = RoomMessageEventContent::text_plain(message);
room.send(content, None).await?; room.send(content, None).await?;
Ok(()) Ok(())
} }
async fn get_previous_message(room: &Joined) -> Result<TextMessageEventContent> {
let last_prev_batch = (**room).last_prev_batch().unwrap();
let request = MessagesOptions::backward(&last_prev_batch);
let messages = room.messages(request).await?;
messages
.chunk
.iter()
.filter_map(|msg| msg.event.deserialize().ok())
.find_map(|msg| {
if let AnyRoomEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(
MessageLikeEvent::Original(OriginalMessageLikeEvent {
content:
RoomMessageEventContent {
msgtype: MessageType::Text(content),
..
},
..
}),
)) = msg
{
Some(content)
} else {
None
}
})
.context("Failed to find previous message")
}
async fn ping(room: Joined) -> Result<()> { async fn ping(room: Joined) -> Result<()> {
let content = MessageEventContent::text_plain("Pong!"); let content = RoomMessageEventContent::text_plain("Pong!");
room.send(content, None).await?; room.send(content, None).await?;
Ok(()) Ok(())
} }
@ -262,19 +312,24 @@ async fn help(room: Joined) -> Result<()> {
msg.push_str(command.help_text()); msg.push_str(command.help_text());
msg.push_str("\n"); msg.push_str("\n");
} }
let content = MessageEventContent::notice_plain(msg); let content = RoomMessageEventContent::notice_plain(msg);
room.send(content, None).await?; room.send(content, None).await?;
Ok(()) Ok(())
} }
async fn unsupported_command( async fn unsupported_command(
original_event: SyncMessageEvent<MessageEventContent>, event: SyncRoomMessageEvent,
room: Joined, room: Joined,
command: &str, command: &str,
) -> Result<()> { ) -> Result<()> {
let resp = original_event.into_full_event(room.room_id().clone()); // let resp = .into_full_event(room.room_id().to_owned());
let event = if let SyncRoomMessageEvent::Original(event) = event {
event.into_full_event(room.room_id().to_owned())
} else {
return Ok(());
};
let content = let content =
MessageEventContent::notice_reply_plain(format!("Unknown command `{command}`"), &resp); RoomMessageEventContent::notice_reply_plain(format!("Unknown command `{command}`"), &event);
room.send(content, None).await?; room.send(content, None).await?;
Ok(()) Ok(())
} }