Skip to content

Commit

Permalink
feat: 支持清理 URL 能力 & 调整全局状态实现
Browse files Browse the repository at this point in the history
  • Loading branch information
fython committed Apr 21, 2024
1 parent 0215211 commit d86d81b
Show file tree
Hide file tree
Showing 8 changed files with 1,118 additions and 56 deletions.
986 changes: 954 additions & 32 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
teloxide = { version = "0.12", features = ["macros"] }
teloxide = { version = "0.12.2", features = ["full"] }
log = "0.4"
pretty_env_logger = "0.5"
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
tokio = { version = "1.37", features = ["rt-multi-thread", "macros"] }
dashmap = "5.5.3"
chrono = "0.4.34"
once_cell = "1.19.0"
url-track-cleaner = "0.1.2"
anyhow = "1.0.82"
43 changes: 43 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::rpt::RepeaterStates;
use once_cell::sync::Lazy;
use std::sync::Arc;
use tokio::sync::Mutex;
use url_track_cleaner::UrlTrackCleaner;

#[derive(Clone, Debug)]
pub(crate) struct BotApp {
pub repeater_state: Arc<RepeaterStates>,
pub url_track_cleaner: Arc<UrlTrackCleaner>,
}

impl BotApp {
pub fn new(states: RepeaterStates, cleaner: UrlTrackCleaner) -> Self {
BotApp {
repeater_state: Arc::new(states),
url_track_cleaner: Arc::new(cleaner),
}
}
}

impl Default for BotApp {
fn default() -> Self {
BotApp {
repeater_state: Arc::new(RepeaterStates::default()),
url_track_cleaner: Arc::new(UrlTrackCleaner::default()),
}
}
}

static APP: Lazy<Mutex<BotApp>> = Lazy::new(|| Mutex::new(BotApp::default()));

/// Set the global bot app instance
///
/// This function can only be called once when program starts
pub(crate) async fn set(app: BotApp) {
APP.lock().await.clone_from(&app);
}

/// Get the global bot app instance
pub(crate) async fn get() -> BotApp {
APP.lock().await.clone()
}
22 changes: 19 additions & 3 deletions src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
use teloxide::macros::BotCommands;
use teloxide::prelude::*;
use teloxide::utils::command::{BotCommands, ParseError};

#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase", description = "支持以下命令:")]
#[command(rename_rule = "snake_case", description = "支持以下命令:")]
pub(crate) enum Command {
#[command(description = "查看机器人使用说明")]
Help,
#[command(description = "清理 URL", parse_with = CleanUrlCommand::parse_to_command)]
CleanUrl(CleanUrlCommand),
}

#[derive(Clone)]
pub(crate) struct CleanUrlCommand {
pub url: String,
}

impl CleanUrlCommand {
fn parse_to_command(s: String) -> Result<(Self,), ParseError> {
Ok((CleanUrlCommand {
url: s.trim().to_string(),
},))
}
}

pub(crate) async fn handle_help_cmd(bot: Bot, msg: Message, _: Command) -> ResponseResult<()> {
bot.send_message(msg.chat.id, "我的主人很懒!!!!不如直接问问他怎么用")
let descriptions = Command::descriptions();
bot.send_message(msg.chat.id, format!("{}", descriptions))
.await?;
Ok(())
}
15 changes: 13 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
mod app;
mod commands;
mod configs;
mod msgfmt;
mod rpt;
mod urlenhance;
mod userinteract;

use crate::app::BotApp;
use crate::commands::*;
use crate::configs::BotConfigs;
use crate::rpt::{RepeaterNextAction, REPEATER_STATES};
use crate::rpt::RepeaterNextAction;
use std::sync::Arc;
use teloxide::prelude::*;
use teloxide::types::{MediaKind, MediaText, MessageKind};
use teloxide::Bot;
Expand All @@ -20,6 +24,11 @@ async fn main() {
let bot_cfg = BotConfigs {
bot_maintainer: UserId(0),
};
app::set(BotApp {
repeater_state: Default::default(),
url_track_cleaner: Arc::new(urlenhance::default_cleaner()),
})
.await;

let handlers = Update::filter_message()
.branch(
Expand All @@ -44,6 +53,7 @@ async fn main() {
async fn handle_cmd(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
match cmd {
Command::Help => handle_help_cmd(bot, msg, cmd).await,
Command::CleanUrl(cmd) => urlenhance::handle_clean_url_cmd(bot, msg, cmd).await,
}
}

Expand All @@ -58,7 +68,8 @@ async fn handle_messages_in_any_groups(bot: Bot, msg: Message) -> ResponseResult
{
return Ok(());
} else {
match REPEATER_STATES.get_next_action(msg.chat.id, text.clone()) {
let repeater_states = app::get().await.repeater_state;
match repeater_states.get_next_action(msg.chat.id, text.clone()) {
RepeaterNextAction::Repeat => {
log::info!("{} needs repeat", text.clone());
bot.forward_message(msg.chat.id, msg.chat.id, msg.id)
Expand Down
4 changes: 4 additions & 0 deletions src/msgfmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ pub(crate) fn markup_username_with_link(user: &User) -> String {
&markdown::escape(&user.full_name()),
)
}

pub(crate) fn plain_link(url: &str) -> String {
markdown::link(url, &markdown::escape(url))
}
41 changes: 24 additions & 17 deletions src/rpt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::time::SystemTime;
use chrono::{DateTime, TimeDelta, Utc};
use dashmap::DashMap;
use once_cell::sync::Lazy;
use std::time::SystemTime;
use teloxide::prelude::ChatId;

/// 复读机状态
Expand All @@ -12,6 +11,7 @@ use teloxide::prelude::ChatId;
/// 当群组内有小于 N 长度的文本消息在 M 时间间隔内重复出现第二次,则机器人复读一次,
/// 并记录下复读发生的时间,在相同条件下距离上次复读发生的 R 时间间隔内不再复读,
/// 直至超时。
#[derive(Debug)]
pub(crate) struct RepeaterStates {
/// 复读文本的最大长度,超过此长度则不进行统计和复读
pub max_text_length: u32,
Expand All @@ -23,24 +23,28 @@ pub(crate) struct RepeaterStates {
pub groups: DashMap<ChatId, RepeaterGroupState>,
}

pub(crate) static REPEATER_STATES: Lazy<RepeaterStates> = Lazy::new(|| {
RepeaterStates {
max_text_length: 3 * 10,
max_wait_repeat_duration: TimeDelta::seconds(45),
min_recent_repeat_duration: TimeDelta::seconds(120),
groups: DashMap::new(),
}
});

impl RepeaterStates {
pub fn get_next_action(&self, chat_id: ChatId, text: String) -> RepeaterNextAction {
log::debug!("text.len(): {}", text.len());
if text.len() > self.max_text_length as usize {
return RepeaterNextAction::Nothing;
}
self.groups.entry(chat_id)
.or_default()
.get_next_action(text, self.max_wait_repeat_duration, self.min_recent_repeat_duration)
self.groups.entry(chat_id).or_default().get_next_action(
text,
self.max_wait_repeat_duration,
self.min_recent_repeat_duration,
)
}
}

impl Default for RepeaterStates {
fn default() -> Self {
RepeaterStates {
max_text_length: 3 * 10,
max_wait_repeat_duration: TimeDelta::seconds(45),
min_recent_repeat_duration: TimeDelta::seconds(120),
groups: DashMap::new(),
}
}
}

Expand All @@ -59,9 +63,12 @@ impl Default for RepeaterGroupState {
}

impl RepeaterGroupState {
pub fn get_next_action(&self, text: String,
max_wait_repeat_duration: TimeDelta,
min_recent_repeat_duration: TimeDelta) -> RepeaterNextAction {
pub fn get_next_action(
&self,
text: String,
max_wait_repeat_duration: TimeDelta,
min_recent_repeat_duration: TimeDelta,
) -> RepeaterNextAction {
let now = DateTime::from(SystemTime::now());
let mut state = self.text_states.entry(text).or_default();
let mut res = RepeaterNextAction::Nothing;
Expand Down
57 changes: 57 additions & 0 deletions src/urlenhance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::commands::CleanUrlCommand;
use crate::{app, msgfmt};
use log::{debug, error};
use teloxide::prelude::*;
use teloxide::types::ParseMode;
use teloxide::utils::markdown;
use teloxide::Bot;
use url_track_cleaner::{RedirectPolicy, ReserveRule, UrlTrackCleanerBuilder};

/// 生成默认的 URL 清理器
pub(crate) fn default_cleaner() -> url_track_cleaner::UrlTrackCleaner {
UrlTrackCleanerBuilder::new()
.follow_redirect(RedirectPolicy::All)
.reserve_rules(vec![ReserveRule::new_with_regex(
r#"^http(s)?://www.bilibili.com/.*"#,
vec!["t".to_string()],
)
.expect("failed to create reserve rule")])
.build()
}

/// 处理 `CleanUrl` (/clean_url)命令
pub(crate) async fn handle_clean_url_cmd(
bot: Bot,
msg: Message,
cmd: CleanUrlCommand,
) -> ResponseResult<()> {
if cmd.url.is_empty() {
bot.send_message(msg.chat.id, "URL 不能为空")
.reply_to_message_id(msg.id)
.await?;
return Ok(());
}
let cleaner = app::get().await.url_track_cleaner;
match cleaner.do_clean(&cmd.url).await {
Ok(res) => {
let res = res.as_str().strip_suffix("?").unwrap_or("");
debug!("cleaned url: {} (original={})", res, cmd.url);
bot.send_message(
msg.chat.id,
format!("清理后的 URL:{}", msgfmt::plain_link(res)),
)
}
Err(e) => {
let err = e.to_string();
error!("failed to clean url: {}", err);
bot.send_message(
msg.chat.id,
format!("清理 URL 时出现错误:{}", markdown::code_block(&err)),
)
}
}
.parse_mode(ParseMode::MarkdownV2)
.reply_to_message_id(msg.id)
.await?;
Ok(())
}

0 comments on commit d86d81b

Please sign in to comment.