Add support for out-of-line message reading
This commit is contained in:
parent
de71131f6f
commit
5cbb24d51f
4 changed files with 821 additions and 873 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
/target
|
/target
|
||||||
config.toml
|
config.toml
|
||||||
/data
|
/crypto_data
|
||||||
|
|
1537
Cargo.lock
generated
1537
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
@ -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
|
||||||
|
|
139
src/main.rs
139
src/main.rs
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue