2021-06-17 17:32:31 +02:00
|
|
|
use anyhow::{anyhow, bail, Context};
|
2021-08-17 23:00:37 +02:00
|
|
|
use libjens::JMClient;
|
2021-06-17 17:32:31 +02:00
|
|
|
use log::{error, info, warn};
|
|
|
|
use matrix_sdk::{
|
2022-01-23 15:16:51 +01:00
|
|
|
config::SyncSettings,
|
2021-06-18 23:15:19 +02:00
|
|
|
deserialized_responses::SyncResponse,
|
2022-01-23 15:16:51 +01:00
|
|
|
encryption::verification::Verification,
|
|
|
|
room::Room,
|
|
|
|
ruma::{
|
|
|
|
api::client::r0::{
|
|
|
|
session::login,
|
|
|
|
uiaa::{AuthData, Password, UserIdentifier},
|
|
|
|
},
|
|
|
|
assign,
|
|
|
|
events::{
|
|
|
|
room::{
|
|
|
|
member::StrippedRoomMemberEvent,
|
|
|
|
message::{
|
|
|
|
MessageType,
|
|
|
|
RoomMessageEventContent,
|
|
|
|
SyncRoomMessageEvent,
|
|
|
|
TextMessageEventContent,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
AnyToDeviceEvent,
|
|
|
|
SyncMessageEvent,
|
2021-06-17 17:32:31 +02:00
|
|
|
},
|
2022-01-23 15:16:51 +01:00
|
|
|
UserId,
|
2021-06-17 17:32:31 +02:00
|
|
|
},
|
2022-01-23 15:16:51 +01:00
|
|
|
Client,
|
2021-07-26 23:58:04 +02:00
|
|
|
LoopCtrl,
|
2021-06-17 17:32:31 +02:00
|
|
|
};
|
2021-06-18 23:15:19 +02:00
|
|
|
use rand::{rngs::StdRng, SeedableRng};
|
|
|
|
use sled::Db;
|
2021-01-12 00:44:51 +01:00
|
|
|
use std::{
|
2021-06-17 17:32:31 +02:00
|
|
|
path::PathBuf,
|
2021-06-18 23:15:19 +02:00
|
|
|
sync::{
|
|
|
|
atomic::{AtomicBool, AtomicU32},
|
|
|
|
Arc,
|
|
|
|
},
|
2021-06-17 17:32:31 +02:00
|
|
|
time::Duration,
|
2021-01-12 00:44:51 +01:00
|
|
|
};
|
|
|
|
use structopt::StructOpt;
|
2021-06-22 14:16:47 +02:00
|
|
|
use tokio::sync::{Mutex, RwLock};
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
use config::Config;
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
mod config;
|
2021-06-18 23:15:19 +02:00
|
|
|
mod meme;
|
|
|
|
mod responder;
|
2021-06-22 16:36:38 +02:00
|
|
|
mod util;
|
2021-06-17 17:32:31 +02:00
|
|
|
|
|
|
|
#[derive(Debug, StructOpt)]
|
|
|
|
struct Opt {
|
2021-01-12 00:44:51 +01:00
|
|
|
#[structopt(
|
|
|
|
short,
|
2021-06-17 17:32:31 +02:00
|
|
|
long,
|
|
|
|
help = "config file to use",
|
|
|
|
default_value = "~/.config/ruff/config.toml"
|
2021-01-12 00:44:51 +01:00
|
|
|
)]
|
2021-06-17 17:32:31 +02:00
|
|
|
config: PathBuf,
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
2021-06-17 17:32:31 +02:00
|
|
|
async fn main() -> anyhow::Result<()> {
|
|
|
|
env_logger::init();
|
|
|
|
|
|
|
|
let opt = Opt::from_args();
|
|
|
|
let config = std::fs::read(&opt.config).map_err(|e| anyhow!("Error reading config: {}", e))?;
|
|
|
|
let config =
|
|
|
|
toml::from_slice::<Config>(&config).map_err(|e| anyhow!("Error parsing config: {}", e))?;
|
2021-06-18 23:15:19 +02:00
|
|
|
let config = Arc::new(config);
|
2021-06-17 17:32:31 +02:00
|
|
|
|
2022-01-23 15:16:51 +01:00
|
|
|
let client = Client::new(config.homeserver_url.clone())?;
|
2021-06-17 17:32:31 +02:00
|
|
|
|
|
|
|
let device_name = config.device_name.as_ref().map(String::as_ref);
|
2022-01-23 15:16:51 +01:00
|
|
|
|
|
|
|
let bot = Arc::new(Bot {
|
|
|
|
client: client.clone(),
|
|
|
|
jm_client: RwLock::new(JMClient::new()),
|
|
|
|
memecache: sled::open(config.store_path.join("memecache"))
|
|
|
|
.map_err(|e| anyhow!("error opening memecache: {}", e))?,
|
|
|
|
config: Arc::clone(&config),
|
|
|
|
meme_count: AtomicU32::new(0),
|
|
|
|
rng: Mutex::new(StdRng::from_rng(rand::thread_rng())?),
|
|
|
|
});
|
2021-06-17 17:32:31 +02:00
|
|
|
|
|
|
|
client
|
2022-01-23 15:16:51 +01:00
|
|
|
.register_event_handler(on_stripped_state_member)
|
2021-06-17 17:32:31 +02:00
|
|
|
.await;
|
2022-01-23 15:16:51 +01:00
|
|
|
let bot_ = Arc::clone(&bot);
|
|
|
|
client
|
|
|
|
.register_event_handler(move |ev, client, room| {
|
|
|
|
on_room_message(ev, client, room, Arc::clone(&bot_))
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let login::Response { user_id, .. } = client
|
|
|
|
.login(&config.user_id, &config.password, device_name, Some("ruff"))
|
|
|
|
.await?;
|
2021-06-17 17:32:31 +02:00
|
|
|
|
|
|
|
let initial = AtomicBool::from(true);
|
|
|
|
let initial_ref = &initial;
|
2022-01-23 15:16:51 +01:00
|
|
|
let client_ref = &client;
|
2021-06-17 17:32:31 +02:00
|
|
|
let config_ref = &config;
|
|
|
|
let user_id_ref = &user_id;
|
2022-01-23 15:16:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
client
|
|
|
|
.sync_with_callback(SyncSettings::new(), |response| async move {
|
|
|
|
if let Err(e) = on_response(&response, client_ref).await {
|
|
|
|
error!("Error processing response: {}", e);
|
|
|
|
}
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
let initial = initial_ref;
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
if initial.load(std::sync::atomic::Ordering::SeqCst) {
|
|
|
|
if let Err(e) =
|
2022-01-23 15:16:51 +01:00
|
|
|
on_initial_response(client_ref, user_id_ref, &config_ref.password).await
|
2021-06-17 17:32:31 +02:00
|
|
|
{
|
|
|
|
error!("Error processing initial response: {}", e);
|
|
|
|
}
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
initial.store(false, std::sync::atomic::Ordering::SeqCst);
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
LoopCtrl::Continue
|
|
|
|
})
|
|
|
|
.await;
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
Ok(())
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
2021-06-18 23:15:19 +02:00
|
|
|
pub struct Bot {
|
2022-01-23 15:16:51 +01:00
|
|
|
client: Client,
|
2021-06-18 23:15:19 +02:00
|
|
|
jm_client: RwLock<JMClient>,
|
|
|
|
memecache: Db,
|
|
|
|
config: Arc<Config>,
|
|
|
|
/// used to keep track of how many memes have been sent.
|
2021-06-22 14:16:47 +02:00
|
|
|
/// this is reset once the threshold set in the config has been reached, and
|
|
|
|
/// the JMClient cache is cleared.
|
2021-06-18 23:15:19 +02:00
|
|
|
meme_count: AtomicU32,
|
|
|
|
rng: Mutex<StdRng>,
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
2022-01-23 15:16:51 +01:00
|
|
|
async fn on_stripped_state_member(event: StrippedRoomMemberEvent, client: Client, room: Room) {
|
|
|
|
if event.state_key == client.user_id().await.unwrap() {
|
|
|
|
return;
|
|
|
|
}
|
2021-06-17 17:32:31 +02:00
|
|
|
|
2022-01-23 15:16:51 +01:00
|
|
|
if let Room::Invited(room) = room {
|
|
|
|
info!("Autojoining room {}", room.room_id());
|
|
|
|
let mut delay = 2;
|
|
|
|
|
|
|
|
while let Err(err) = client.join_room_by_id(room.room_id()).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
|
|
|
|
warn!(
|
|
|
|
"Failed to join room {} ({:?}), retrying in {}s",
|
|
|
|
room.room_id(),
|
|
|
|
err,
|
|
|
|
delay
|
|
|
|
);
|
|
|
|
|
|
|
|
tokio::time::sleep(Duration::from_secs(delay)).await;
|
|
|
|
delay *= 2;
|
|
|
|
|
|
|
|
if delay > 3600 {
|
|
|
|
error!("Can't join room {} ({:?})", room.room_id(), err);
|
|
|
|
break;
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
2021-06-17 17:32:31 +02:00
|
|
|
}
|
2022-01-23 15:16:51 +01:00
|
|
|
|
|
|
|
info!("Successfully joined room {}", room.room_id());
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
2022-01-23 15:16:51 +01:00
|
|
|
}
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2022-01-23 15:16:51 +01:00
|
|
|
async fn on_room_message(msg: SyncRoomMessageEvent, client: Client, room: Room, bot: Arc<Bot>) {
|
|
|
|
if client
|
|
|
|
.user_id()
|
|
|
|
.await
|
|
|
|
.map(|u| u == msg.sender)
|
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2021-06-18 23:15:19 +02:00
|
|
|
|
2022-01-23 15:16:51 +01:00
|
|
|
if let SyncMessageEvent {
|
|
|
|
content:
|
|
|
|
RoomMessageEventContent {
|
|
|
|
msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
..
|
|
|
|
} = msg
|
|
|
|
{
|
|
|
|
if let Err(e) = responder::on_msg(&msg_body, room, &bot).await {
|
|
|
|
error!("Responder error: {}", e);
|
2021-06-18 23:15:19 +02:00
|
|
|
}
|
2021-06-17 17:32:31 +02:00
|
|
|
}
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
async fn on_initial_response(
|
|
|
|
client: &Client,
|
|
|
|
user_id: &UserId,
|
|
|
|
password: &str,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
bootstrap_cross_signing(client, user_id, password).await?;
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
Ok(())
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
2021-06-18 23:15:19 +02:00
|
|
|
async fn on_response(response: &SyncResponse, client: &Client) -> anyhow::Result<()> {
|
2021-06-17 17:32:31 +02:00
|
|
|
for event in response
|
|
|
|
.to_device
|
|
|
|
.events
|
|
|
|
.iter()
|
|
|
|
.filter_map(|e| e.deserialize().ok())
|
|
|
|
{
|
|
|
|
match event {
|
|
|
|
AnyToDeviceEvent::KeyVerificationStart(e) => {
|
|
|
|
info!("Starting verification");
|
2021-06-18 23:15:19 +02:00
|
|
|
if let Some(Verification::SasV1(sas)) = &client
|
|
|
|
.get_verification(&e.sender, &e.content.transaction_id)
|
|
|
|
.await
|
|
|
|
{
|
2021-06-17 17:32:31 +02:00
|
|
|
if let Err(e) = sas.accept().await {
|
|
|
|
error!("Error accepting key verification request: {}", e);
|
|
|
|
}
|
|
|
|
}
|
2021-07-26 23:58:04 +02:00
|
|
|
},
|
2021-01-12 00:44:51 +01:00
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
AnyToDeviceEvent::KeyVerificationKey(e) => {
|
2021-06-18 23:15:19 +02:00
|
|
|
if let Some(Verification::SasV1(sas)) = &client
|
|
|
|
.get_verification(&e.sender, &e.content.transaction_id)
|
|
|
|
.await
|
|
|
|
{
|
2021-06-17 17:32:31 +02:00
|
|
|
if let Err(e) = sas.confirm().await {
|
|
|
|
error!("Error confirming key verification request: {}", e);
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
2021-06-17 17:32:31 +02:00
|
|
|
}
|
2021-07-26 23:58:04 +02:00
|
|
|
},
|
2021-06-17 17:32:31 +02:00
|
|
|
|
2021-07-26 23:58:04 +02:00
|
|
|
_ => {},
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
}
|
2021-06-17 17:32:31 +02:00
|
|
|
|
|
|
|
Ok(())
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
async fn bootstrap_cross_signing(
|
|
|
|
client: &Client,
|
|
|
|
user_id: &UserId,
|
|
|
|
password: &str,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
info!("bootstrapping e2e");
|
2021-06-18 23:15:19 +02:00
|
|
|
if let Err(e) = client.bootstrap_cross_signing(None).await {
|
2021-06-17 17:32:31 +02:00
|
|
|
if let Some(response) = e.uiaa_response() {
|
2022-01-23 15:16:51 +01:00
|
|
|
let auth_data = AuthData::Password(assign!(
|
|
|
|
Password::new(UserIdentifier::MatrixId(user_id.as_str()), password),
|
|
|
|
{ session: response.session.as_deref() }
|
|
|
|
));
|
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
client
|
|
|
|
.bootstrap_cross_signing(Some(auth_data))
|
|
|
|
.await
|
|
|
|
.context("Couldn't bootstrap cross signing")?;
|
|
|
|
} else {
|
|
|
|
bail!("Error during cross-signing bootstrap {:#?}", e);
|
|
|
|
}
|
2021-01-12 00:44:51 +01:00
|
|
|
}
|
|
|
|
|
2021-06-19 21:12:53 +02:00
|
|
|
info!("bootstrapped e2e");
|
|
|
|
|
2021-06-17 17:32:31 +02:00
|
|
|
Ok(())
|
|
|
|
}
|