217 lines
7.5 KiB
Rust
217 lines
7.5 KiB
Rust
#![feature(control_flow_enum)]
|
|
|
|
use eh2telegraph::{
|
|
collector::Registry,
|
|
config::{self},
|
|
http_proxy::ProxiedClient,
|
|
storage,
|
|
sync::Synchronizer,
|
|
telegraph::Telegraph,
|
|
};
|
|
|
|
use clap::Parser;
|
|
|
|
use teloxide::{
|
|
adaptors::DefaultParseMode,
|
|
dispatching::update_listeners,
|
|
error_handlers::IgnoringErrorHandler,
|
|
prelude2::*,
|
|
types::{AllowedUpdate, ChatPermissions, ParseMode, UpdateKind},
|
|
};
|
|
|
|
use handler::{Command, Handler};
|
|
|
|
use crate::{
|
|
handler::AdminCommand,
|
|
util::{wrap_endpoint, PrettyChat},
|
|
};
|
|
|
|
mod handler;
|
|
mod util;
|
|
mod version;
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
pub struct BaseConfig {
|
|
pub bot_token: String,
|
|
pub telegraph: TelegraphConfig,
|
|
#[serde(default)]
|
|
pub admins: Vec<i64>,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
pub struct TelegraphConfig {
|
|
pub tokens: Vec<String>,
|
|
pub author_name: Option<String>,
|
|
pub author_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[clap(author, version=version::VERSION, about, long_about = "eh2telegraph sync bot")]
|
|
struct Args {
|
|
#[clap(short, long, help = "Config file path")]
|
|
config: Option<String>,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let args = Args::parse();
|
|
|
|
let timer = tracing_subscriber::fmt::time::LocalTime::new(time::macros::format_description!(
|
|
"[month]-[day] [hour]:[minute]:[second]"
|
|
));
|
|
tracing_subscriber::fmt().with_timer(timer).init();
|
|
tracing::info!("initializing...");
|
|
|
|
config::init(args.config);
|
|
let base_config: BaseConfig = config::parse("base")
|
|
.expect("unable to parse base config")
|
|
.expect("base config can not be empty");
|
|
let telegraph_config = base_config.telegraph;
|
|
let telegraph =
|
|
Telegraph::new(telegraph_config.tokens).with_proxy(ProxiedClient::new_from_config());
|
|
|
|
let registry = Registry::new_from_config();
|
|
#[cfg(debug_assertions)]
|
|
let cache = storage::SimpleMemStorage::default();
|
|
#[cfg(not(debug_assertions))]
|
|
let cache =
|
|
storage::cloudflare_kv::CFStorage::new_from_config().expect("unable to build storage");
|
|
let mut synchronizer = Synchronizer::new(telegraph, registry, cache);
|
|
if telegraph_config.author_name.is_some() {
|
|
synchronizer =
|
|
synchronizer.with_author(telegraph_config.author_name, telegraph_config.author_url);
|
|
}
|
|
|
|
let admins = base_config.admins.into_iter().collect();
|
|
let handler = Box::leak(Box::new(Handler::new(synchronizer, admins))) as &Handler<_>;
|
|
|
|
// === Bot related ===
|
|
let command_handler = move |bot: AutoSend<DefaultParseMode<Bot>>,
|
|
message: Message,
|
|
command: Command| async move {
|
|
handler.respond_cmd(bot, message, command).await
|
|
};
|
|
let admin_command_handler = move |bot: AutoSend<DefaultParseMode<Bot>>,
|
|
message: Message,
|
|
command: AdminCommand| async move {
|
|
handler.respond_admin_cmd(bot, message, command).await
|
|
};
|
|
let text_handler = move |bot: AutoSend<DefaultParseMode<Bot>>, message: Message| async move {
|
|
handler.respond_text(bot, message).await
|
|
};
|
|
let caption_handler = move |bot: AutoSend<DefaultParseMode<Bot>>, message: Message| async move {
|
|
handler.respond_caption(bot, message).await
|
|
};
|
|
let photo_handler = move |bot: AutoSend<DefaultParseMode<Bot>>, message: Message| async move {
|
|
handler.respond_photo(bot, message).await
|
|
};
|
|
let default_handler = move |bot: AutoSend<DefaultParseMode<Bot>>, message: Message| async move {
|
|
handler.respond_default(bot, message).await
|
|
};
|
|
let permission_filter = |bot: AutoSend<DefaultParseMode<Bot>>, message: Message| async move {
|
|
// If the bot is blocked, we will leave chat and not respond.
|
|
let blocked = message
|
|
.chat
|
|
.permissions()
|
|
.map(|p| !p.contains(ChatPermissions::SEND_MESSAGES))
|
|
.unwrap_or_default();
|
|
if blocked {
|
|
tracing::info!(
|
|
"[permission filter] leave chat {:?}",
|
|
PrettyChat(&message.chat)
|
|
);
|
|
let _ = bot.leave_chat(message.chat.id).await;
|
|
None
|
|
} else {
|
|
Some(message)
|
|
}
|
|
};
|
|
|
|
let bot = Bot::new(base_config.bot_token)
|
|
.parse_mode(ParseMode::MarkdownV2)
|
|
.auto_send();
|
|
let mut bot_dispatcher = Dispatcher::builder(
|
|
bot.clone(),
|
|
dptree::entry()
|
|
.chain(dptree::filter_map(move |update: Update| {
|
|
match update.kind {
|
|
UpdateKind::Message(x) | UpdateKind::EditedMessage(x) => Some(x),
|
|
_ => None,
|
|
}
|
|
}))
|
|
.chain(dptree::filter_map_async(permission_filter))
|
|
.branch(
|
|
dptree::entry()
|
|
.chain(dptree::filter(move |message: Message| {
|
|
handler.admins.contains(&message.chat.id)
|
|
}))
|
|
.filter_command::<AdminCommand>()
|
|
.branch(wrap_endpoint(admin_command_handler)),
|
|
)
|
|
.branch(
|
|
dptree::entry()
|
|
.filter_command::<Command>()
|
|
.branch(wrap_endpoint(command_handler)),
|
|
)
|
|
.branch(
|
|
dptree::entry()
|
|
.chain(dptree::filter_map(move |message: Message| {
|
|
// Ownership mechanism does not allow using map.
|
|
#[allow(clippy::manual_map)]
|
|
match message.text() {
|
|
Some(v) if !v.is_empty() => Some(message),
|
|
_ => None,
|
|
}
|
|
}))
|
|
.branch(wrap_endpoint(text_handler)),
|
|
)
|
|
.branch(
|
|
dptree::entry()
|
|
.chain(dptree::filter_map(move |message: Message| {
|
|
// Ownership mechanism does not allow using map.
|
|
#[allow(clippy::manual_map)]
|
|
match message.caption_entities() {
|
|
Some(v) if !v.is_empty() => Some(message),
|
|
_ => None,
|
|
}
|
|
}))
|
|
.branch(wrap_endpoint(caption_handler)),
|
|
)
|
|
.branch(
|
|
dptree::entry()
|
|
.chain(dptree::filter_map(move |message: Message| {
|
|
// Ownership mechanism does not allow using map.
|
|
#[allow(clippy::manual_map)]
|
|
match message.photo() {
|
|
Some(v) if !v.is_empty() => Some(message),
|
|
_ => None,
|
|
}
|
|
}))
|
|
.branch(wrap_endpoint(photo_handler)),
|
|
)
|
|
.branch(wrap_endpoint(default_handler)),
|
|
)
|
|
.default_handler(Box::new(|_upd| {
|
|
#[cfg(debug_assertions)]
|
|
tracing::warn!("Unhandled update: {:?}", _upd);
|
|
Box::pin(async {})
|
|
}))
|
|
.error_handler(std::sync::Arc::new(IgnoringErrorHandler))
|
|
.build();
|
|
bot_dispatcher.setup_ctrlc_handler();
|
|
let bot_listener = update_listeners::polling(
|
|
bot,
|
|
Some(std::time::Duration::from_secs(10)),
|
|
None,
|
|
Some(vec![AllowedUpdate::Message]),
|
|
);
|
|
|
|
tracing::info!("initializing finished, bot is running");
|
|
bot_dispatcher
|
|
.dispatch_with_listener(
|
|
bot_listener,
|
|
LoggingErrorHandler::with_custom_text("An error from the update listener"),
|
|
)
|
|
.await;
|
|
}
|