add deltachat login and command handler stubs
This commit is contained in:
87
Cargo.lock
generated
87
Cargo.lock
generated
@@ -92,6 +92,21 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse 0.2.7",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -99,7 +114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse 1.0.0",
|
||||||
"anstyle-query",
|
"anstyle-query",
|
||||||
"anstyle-wincon",
|
"anstyle-wincon",
|
||||||
"colorchoice",
|
"colorchoice",
|
||||||
@@ -113,6 +128,15 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -535,11 +559,14 @@ dependencies = [
|
|||||||
name = "bot"
|
name = "bot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"deltachat",
|
"deltachat",
|
||||||
|
"env_logger",
|
||||||
"eui48",
|
"eui48",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -740,7 +767,7 @@ version = "4.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream 1.0.0",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim",
|
"strsim",
|
||||||
@@ -1593,6 +1620,29 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d"
|
||||||
|
dependencies = [
|
||||||
|
"anstream 0.6.21",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"jiff",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -2860,6 +2910,30 @@ version = "1.0.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.91"
|
version = "0.3.91"
|
||||||
@@ -3981,6 +4055,15 @@ version = "1.13.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portmapper"
|
name = "portmapper"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.102"
|
||||||
clap = { version = "4", features = [ "derive" ] }
|
clap = { version = "4", features = [ "derive" ] }
|
||||||
deltachat = { path = "./chatmail-core" }
|
deltachat = { path = "./chatmail-core" }
|
||||||
|
env_logger = "0.11.9"
|
||||||
eui48 = { version = "1.1.0", features = [ "serde" ] }
|
eui48 = { version = "1.1.0", features = [ "serde" ] }
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = [ "derive" ] }
|
||||||
serde_yaml = { version = "0.9" }
|
serde_yaml = { version = "0.9" }
|
||||||
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
|
|||||||
136
src/handler.rs
Normal file
136
src/handler.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
use std::{collections::HashSet, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Result as AnyhowResult;
|
||||||
|
use deltachat::{
|
||||||
|
chat::{self, ChatId},
|
||||||
|
contact::ContactId,
|
||||||
|
context::Context,
|
||||||
|
message::Message,
|
||||||
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::config::BotConfig;
|
||||||
|
|
||||||
|
pub struct BotContext {
|
||||||
|
authed_contacts: HashSet<ContactId>,
|
||||||
|
config: BotConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BotContext {
|
||||||
|
pub fn new(config: BotConfig) -> BotContext {
|
||||||
|
BotContext {
|
||||||
|
authed_contacts: HashSet::new(),
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn echo(dchat_ctx: Arc<Mutex<Context>>, msg: Message) -> AnyhowResult<()> {
|
||||||
|
let chat_id = msg.get_chat_id();
|
||||||
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, msg.get_text()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn auth_command(
|
||||||
|
dchat_ctx: Arc<Mutex<Context>>,
|
||||||
|
ctx: Arc<Mutex<BotContext>>,
|
||||||
|
msg: Message,
|
||||||
|
args: &[&str],
|
||||||
|
) -> AnyhowResult<()> {
|
||||||
|
let chat_id = msg.get_chat_id();
|
||||||
|
let contact_id = msg.get_from_id();
|
||||||
|
|
||||||
|
let mut ctx_lock = ctx.lock().await;
|
||||||
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
|
|
||||||
|
if ctx_lock.authed_contacts.contains(&contact_id) {
|
||||||
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, "Already authenticated".to_owned()).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(password) = args.first() else {
|
||||||
|
chat::send_text_msg(
|
||||||
|
&dchat_ctx_lock,
|
||||||
|
chat_id,
|
||||||
|
"Usage: /auth <password>".to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if *password == ctx_lock.config.auth.password {
|
||||||
|
ctx_lock.authed_contacts.insert(contact_id);
|
||||||
|
chat::send_text_msg(
|
||||||
|
&dchat_ctx_lock,
|
||||||
|
chat_id,
|
||||||
|
"Authentication successful!".to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, "Incorrect password".to_owned()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wol_command(
|
||||||
|
dchat_ctx: Arc<Mutex<Context>>,
|
||||||
|
ctx: Arc<Mutex<BotContext>>,
|
||||||
|
msg: Message,
|
||||||
|
args: &[&str],
|
||||||
|
) -> AnyhowResult<()> {
|
||||||
|
let chat_id = msg.get_chat_id();
|
||||||
|
let contact_id = msg.get_from_id();
|
||||||
|
|
||||||
|
let ctx_lock = ctx.lock().await;
|
||||||
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
|
|
||||||
|
if !ensure_auth(&dchat_ctx_lock, &ctx_lock, chat_id, contact_id).await? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ssh_unlock_disk_command(
|
||||||
|
dchat_ctx: Arc<Mutex<Context>>,
|
||||||
|
ctx: Arc<Mutex<BotContext>>,
|
||||||
|
msg: Message,
|
||||||
|
args: &[&str],
|
||||||
|
) -> AnyhowResult<()> {
|
||||||
|
let chat_id = msg.get_chat_id();
|
||||||
|
let contact_id = msg.get_from_id();
|
||||||
|
|
||||||
|
let ctx_lock = ctx.lock().await;
|
||||||
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
|
|
||||||
|
if !ensure_auth(&dchat_ctx_lock, &ctx_lock, chat_id, contact_id).await? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ensure_auth(
|
||||||
|
dchat_ctx: &Context,
|
||||||
|
ctx: &BotContext,
|
||||||
|
chat_id: ChatId,
|
||||||
|
contact_id: ContactId,
|
||||||
|
) -> AnyhowResult<bool> {
|
||||||
|
if !ctx.authed_contacts.contains(&contact_id) {
|
||||||
|
chat::send_text_msg(
|
||||||
|
dchat_ctx,
|
||||||
|
chat_id,
|
||||||
|
"Authenticate yourself first with '/auth <password>'.".to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/main.rs
138
src/main.rs
@@ -1,11 +1,25 @@
|
|||||||
mod config;
|
mod config;
|
||||||
|
mod handler;
|
||||||
|
|
||||||
|
use anyhow::{Context as _, Result as AnyhowResult};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::path::PathBuf;
|
use deltachat::{
|
||||||
|
EventType, Events,
|
||||||
|
config::Config,
|
||||||
|
context::Context,
|
||||||
|
message::{Message, MsgId},
|
||||||
|
securejoin,
|
||||||
|
stock_str::StockStrings,
|
||||||
|
};
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::handler::BotContext;
|
||||||
|
|
||||||
const CONFIG_FILENAME: &str = "bot.yml";
|
const CONFIG_FILENAME: &str = "bot.yml";
|
||||||
const APP_NAME: &str = "deltachat-remotecontrol-bot";
|
const APP_NAME: &str = "deltachat-remotecontrol-bot";
|
||||||
const APP_CONFIG_DIR: &str = APP_NAME;
|
const APP_CONFIG_DIR: &str = APP_NAME;
|
||||||
|
const APP_DATA_DIR: &str = APP_NAME;
|
||||||
|
|
||||||
/// Delta Chat bot for remote control of local network machines.
|
/// Delta Chat bot for remote control of local network machines.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -42,7 +56,124 @@ fn default_config_paths() -> Vec<PathBuf> {
|
|||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn data_path() -> PathBuf {
|
||||||
|
std::env::var("XDG_DATA_HOME")
|
||||||
|
.map(|dh| PathBuf::from(dh).join(APP_DATA_DIR))
|
||||||
|
.or_else(|_| {
|
||||||
|
std::env::var("HOME").map(|h| {
|
||||||
|
PathBuf::from(h)
|
||||||
|
.join(".local")
|
||||||
|
.join("share")
|
||||||
|
.join(APP_DATA_DIR)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(PathBuf::from("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_bot(cfg: config::BotConfig) -> AnyhowResult<()> {
|
||||||
|
let dchat_db_dir = data_path();
|
||||||
|
std::fs::create_dir_all(&dchat_db_dir)
|
||||||
|
.with_context(|| format!("Failed to create data directory {}", dchat_db_dir.display()))?;
|
||||||
|
|
||||||
|
let dchat_db_filename = dchat_db_dir.join("deltachat.sqlite");
|
||||||
|
|
||||||
|
let dchat_ctx = Context::new(
|
||||||
|
&dchat_db_filename,
|
||||||
|
0,
|
||||||
|
Events::default(),
|
||||||
|
StockStrings::default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to open Delta Chat client DB")?;
|
||||||
|
|
||||||
|
if !dchat_ctx.is_configured().await? {
|
||||||
|
dchat_ctx
|
||||||
|
.set_config(Config::Addr, Some(&cfg.delta_chat.email))
|
||||||
|
.await?;
|
||||||
|
dchat_ctx
|
||||||
|
.set_config(Config::MailPw, Some(&cfg.delta_chat.password))
|
||||||
|
.await?;
|
||||||
|
dchat_ctx.set_config(Config::Bot, Some("1")).await?;
|
||||||
|
dchat_ctx
|
||||||
|
.configure()
|
||||||
|
.await
|
||||||
|
.context("Failed to configure Delta Chat client")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
dchat_ctx.start_io().await;
|
||||||
|
|
||||||
|
let invite_link = securejoin::get_securejoin_qr(&dchat_ctx, None)
|
||||||
|
.await
|
||||||
|
.context("Failed to generate invite link")?;
|
||||||
|
|
||||||
|
println!("Add this bot to your Delta Chat contacts:");
|
||||||
|
println!("{invite_link}");
|
||||||
|
|
||||||
|
let dchat_ctx = Arc::new(Mutex::new(dchat_ctx));
|
||||||
|
|
||||||
|
let bot_context = Arc::new(Mutex::new(BotContext::new(cfg)));
|
||||||
|
|
||||||
|
let ev_emitter = dchat_ctx.lock().await.get_event_emitter();
|
||||||
|
|
||||||
|
while let Some(ev) = ev_emitter.recv().await {
|
||||||
|
match ev.typ {
|
||||||
|
EventType::IncomingMsg { chat_id: _, msg_id } => {
|
||||||
|
// let dchat_ctx = dchat_ctx.clone();
|
||||||
|
// let bot_context = Arc::clone(&bot_context);
|
||||||
|
|
||||||
|
if let Err(e) = handle_message(dchat_ctx.clone(), bot_context.clone(), msg_id).await
|
||||||
|
{
|
||||||
|
eprintln!("Error in message handler: {e:#}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_message(
|
||||||
|
dchat_ctx: Arc<Mutex<Context>>,
|
||||||
|
bot_ctx: Arc<Mutex<BotContext>>,
|
||||||
|
msg_id: MsgId,
|
||||||
|
) -> AnyhowResult<()> {
|
||||||
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
|
let msg = Message::load_from_db(&dchat_ctx_lock, msg_id).await?;
|
||||||
|
let text = msg.get_text();
|
||||||
|
drop(dchat_ctx_lock);
|
||||||
|
|
||||||
|
if !text.starts_with('/') {
|
||||||
|
handler::echo(dchat_ctx, msg).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = text.split_whitespace();
|
||||||
|
let Some(cmd) = parts.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let args: Vec<&str> = parts.collect();
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
"/auth" => {
|
||||||
|
handler::auth_command(dchat_ctx, bot_ctx, msg, &args).await?;
|
||||||
|
}
|
||||||
|
"/wol" => {
|
||||||
|
handler::wol_command(dchat_ctx, bot_ctx, msg, &args).await?;
|
||||||
|
}
|
||||||
|
"/ssh-unlock-disk" => {
|
||||||
|
handler::ssh_unlock_disk_command(dchat_ctx, bot_ctx, msg, &args).await?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let config_path: PathBuf = match args.config {
|
let config_path: PathBuf = match args.config {
|
||||||
@@ -71,6 +202,5 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: start the bot using `config`.
|
run_bot(config).await.expect("error");
|
||||||
let _ = config;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user