feat: telegram bot for moderation (allow/deny commands)
This commit is contained in:
parent
660c02d63f
commit
37d2a2b99e
2 changed files with 87 additions and 1 deletions
20
src/main.rs
20
src/main.rs
|
|
@ -1,9 +1,11 @@
|
||||||
mod config;
|
mod config;
|
||||||
mod entries;
|
mod entries;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod telegram;
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
@ -11,12 +13,28 @@ async fn main() {
|
||||||
|
|
||||||
let config = config::Config::load("config.toml").expect("failed to load config.toml");
|
let config = config::Config::load("config.toml").expect("failed to load config.toml");
|
||||||
let listen = config.listen.clone();
|
let listen = config.listen.clone();
|
||||||
|
let entries_dir = config.data_dir.join("entries");
|
||||||
|
let chat_id = ChatId(config.telegram_chat_id);
|
||||||
|
|
||||||
let (tx, _rx) = tokio::sync::mpsc::channel(32);
|
std::fs::create_dir_all(&entries_dir).ok();
|
||||||
|
|
||||||
|
let bot = Bot::new(&config.telegram_bot_token);
|
||||||
|
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
||||||
|
|
||||||
let state = Arc::new(web::AppState { config, tx });
|
let state = Arc::new(web::AppState { config, tx });
|
||||||
let app = web::router(state);
|
let app = web::router(state);
|
||||||
|
|
||||||
|
// Spawn telegram notification sender
|
||||||
|
let notify_bot = bot.clone();
|
||||||
|
tokio::spawn(telegram::notification_task(notify_bot, chat_id, rx));
|
||||||
|
|
||||||
|
// Spawn telegram command listener
|
||||||
|
let cmd_bot = bot.clone();
|
||||||
|
let cmd_entries_dir = entries_dir.clone();
|
||||||
|
tokio::spawn(telegram::bot_task(cmd_bot, chat_id, cmd_entries_dir));
|
||||||
|
|
||||||
|
// Run web server
|
||||||
tracing::info!("listening on {listen}");
|
tracing::info!("listening on {listen}");
|
||||||
let listener = tokio::net::TcpListener::bind(&listen).await.unwrap();
|
let listener = tokio::net::TcpListener::bind(&listen).await.unwrap();
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
|
||||||
68
src/telegram.rs
Normal file
68
src/telegram.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
|
||||||
|
use crate::entries::{self, Entry, Status};
|
||||||
|
|
||||||
|
/// Send a notification to Telegram about a new entry.
|
||||||
|
async fn notify(bot: &Bot, chat_id: ChatId, entry: &Entry) {
|
||||||
|
let short_id = entry.id.split('-').last().unwrap_or(&entry.id);
|
||||||
|
let text = format!(
|
||||||
|
"New guestbook entry:\n\nName: {}\nWebsite: {}\n\n{}\n\n/allow_{}\n/deny_{}",
|
||||||
|
entry.meta.name, entry.meta.website, entry.body, short_id, short_id
|
||||||
|
);
|
||||||
|
if let Err(e) = bot.send_message(chat_id, &text).await {
|
||||||
|
tracing::error!("failed to send telegram message: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listen for new entries on the channel and send Telegram notifications.
|
||||||
|
pub async fn notification_task(bot: Bot, chat_id: ChatId, mut rx: Receiver<Entry>) {
|
||||||
|
while let Some(entry) = rx.recv().await {
|
||||||
|
notify(&bot, chat_id, &entry).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the Telegram bot that listens for /allow_ and /deny_ commands.
|
||||||
|
pub async fn bot_task(bot: Bot, chat_id: ChatId, entries_dir: PathBuf) {
|
||||||
|
let handler = Update::filter_message().endpoint(
|
||||||
|
|bot: Bot, msg: Message, entries_dir: PathBuf, chat_id: ChatId| async move {
|
||||||
|
let text = msg.text().unwrap_or("");
|
||||||
|
// Only respond to the configured chat
|
||||||
|
if msg.chat.id != chat_id {
|
||||||
|
return respond(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(id) = text.strip_prefix("/allow_") {
|
||||||
|
match entries::set_status(&entries_dir, id, Status::Approved) {
|
||||||
|
Ok(name) => {
|
||||||
|
bot.send_message(msg.chat.id, format!("Approved ({name})."))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
bot.send_message(msg.chat.id, e).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(id) = text.strip_prefix("/deny_") {
|
||||||
|
match entries::set_status(&entries_dir, id, Status::Denied) {
|
||||||
|
Ok(name) => {
|
||||||
|
bot.send_message(msg.chat.id, format!("Denied ({name})."))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
bot.send_message(msg.chat.id, e).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Dispatcher::builder(bot, handler)
|
||||||
|
.dependencies(dptree::deps![entries_dir, chat_id])
|
||||||
|
.build()
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue