Skip to content

Commit

Permalink
feat: support /replace command & move bot features to individual package
Browse files Browse the repository at this point in the history
  • Loading branch information
fython committed Jul 7, 2024
1 parent 5776641 commit 071eff3
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 51 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ once_cell = "1.19.0"
url-track-cleaner = "0.1.5"
anyhow = "1.0.82"
linkify = "0.10.0"
shlex = "1.3.0"
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::rpt::RepeaterStates;
use crate::features::rpt::RepeaterStates;
use once_cell::sync::Lazy;
use std::sync::Arc;
use tokio::sync::Mutex;
Expand Down
45 changes: 45 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use shlex::Shlex;
use std::io;
use teloxide::prelude::*;
use teloxide::utils::command::{BotCommands, ParseError};

Expand All @@ -8,6 +10,8 @@ pub(crate) enum Command {
Help,
#[command(description = "清理 URL", parse_with = CleanUrlCommand::parse_to_command)]
CleanUrl(CleanUrlCommand),
#[command(description = "替换原文中的关键字并发送", parse_with = ReplaceCommand::parse_to_command)]
Replace(ReplaceCommand),
}

#[derive(Clone)]
Expand All @@ -23,6 +27,47 @@ impl CleanUrlCommand {
}
}

#[derive(Clone)]
pub(crate) struct ReplaceCommand {
pub keyword: String,
pub replacement: String,
}

impl ReplaceCommand {
fn parse_to_command(s: String) -> Result<(Self,), ParseError> {
let mut l = Shlex::new(&s);
let parts: Vec<String> = l.by_ref().collect();
if l.had_error {
return Err(ParseError::IncorrectFormat(
io::Error::other("parse arguments error").into(),
));
}
#[allow(non_upper_case_globals)]
const expected: usize = 2;
let found = parts.len();
if found != expected {
let message = "replace args count should be 2".to_string();
return Err(if found > expected {
ParseError::TooFewArguments {
expected,
found,
message,
}
} else {
ParseError::TooFewArguments {
expected,
found,
message,
}
});
}
Ok((ReplaceCommand {
keyword: parts[0].clone(),
replacement: parts[1].clone(),
},))
}
}

pub(crate) async fn handle_help_cmd(bot: Bot, msg: Message, _: Command) -> ResponseResult<()> {
let descriptions = Command::descriptions();
bot.send_message(msg.chat.id, format!("{}", descriptions))
Expand Down
3 changes: 3 additions & 0 deletions src/features/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod rpt;
pub mod urlenhance;
pub mod userinteract;
File renamed without changes.
File renamed without changes.
89 changes: 89 additions & 0 deletions src/features/userinteract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::commands::ReplaceCommand;
use crate::msgfmt::markup_username_with_link;
use std::any::Any;
use std::ptr::replace;
use teloxide::prelude::*;
use teloxide::types::{MediaKind, MediaText, MessageKind, ParseMode};

/// 处理用户对另一个用户的模拟指令行为
///
/// 当用户发送类似 `/do_sth` 的消息时,机器人会回复类似 `@user1 do_sth 了 @user2` 的消息
/// 若消息不符合条件时,此函数会返回 None。此外,发送失败时不会传递错误到上游
pub(crate) async fn handle_user_do_sth_to_another(bot: &Bot, msg: &Message) -> Option<()> {
let from_user = msg.from();
let reply_user = msg.reply_to_message().and_then(|reply| reply.from());
let text = msg.text();
if text.is_none() || from_user.is_none() || reply_user.is_none() {
return None;
}
let text = text.unwrap();
if text.starts_with("/") && text.find("@").is_none() {
let act = text.strip_prefix("/");
if act.is_none() {
return None;
}
let acts: Vec<&str> = act.unwrap().split_ascii_whitespace().into_iter().collect();
if acts.len() == 0 || acts[0] == "me" {
return None;
}
let mut text = format!(
"{} {}了 {}",
markup_username_with_link(from_user.unwrap()),
acts[0],
markup_username_with_link(reply_user.unwrap())
);
if acts.len() > 1 {
text.push_str(&format!(" {}", acts[1]));
}
let res = bot
.send_message(msg.chat.id, text)
.parse_mode(ParseMode::MarkdownV2)
.await;
if res.is_err() {
log::error!("failed to send message: {:?}", res.err());
}
// TODO 统计行为次数
return Some(());
}
return None;
}

/// 处理用户替换消息中的关键词
///
/// 当用户回复一条消息并发送类似 `/replace keyword replacement` 的消息时,
/// 机器人会将被回复的消息中的 `keyword` 替换为 `replacement` 并回复。
/// 被回复的消息必须是纯文本消息,否则跳过处理。
pub(crate) async fn handle_user_replace_words(
bot: &Bot,
msg: &Message,
cmd: ReplaceCommand,
) -> ResponseResult<()> {
let reply_msg = msg.reply_to_message();
if reply_msg.is_none() {
bot.send_message(msg.chat.id, "此命令需要指定一条消息回复")
.reply_to_message_id(msg.id)
.await?;
return Ok(());
}
// 限制仅支持纯文本消息
if let MessageKind::Common(common) = &reply_msg.unwrap().kind {
if let MediaKind::Text(MediaText { text, entities: _ }) = &common.media_kind {
if msg.from().is_none() {
// ignored
return Ok(());
}
let from = msg.from().unwrap();
let replaced = text.replace(&cmd.keyword, &cmd.replacement);
let replacer = markup_username_with_link(from);
bot.send_message(msg.chat.id, format!("{} :{}", replacer, replaced))
.parse_mode(ParseMode::MarkdownV2)
.reply_to_message_id(msg.id)
.await?;
return Ok(());
}
}
bot.send_message(msg.chat.id, "无法处理此消息")
.reply_to_message_id(msg.id)
.await?;
Ok(())
}
8 changes: 4 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
mod app;
mod commands;
mod configs;
mod features;
mod msgfmt;
mod rpt;
mod urlenhance;
mod userinteract;

use crate::app::BotApp;
use crate::commands::*;
use crate::configs::BotConfigs;
use crate::rpt::RepeaterNextAction;
use features::rpt::RepeaterNextAction;
use features::{urlenhance, userinteract};
use std::sync::Arc;
use teloxide::prelude::*;
use teloxide::types::{MediaKind, MediaText, MessageKind};
Expand Down Expand Up @@ -54,6 +53,7 @@ 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,
Command::Replace(cmd) => userinteract::handle_user_replace_words(&bot, &msg, cmd).await,
}
}

Expand Down
46 changes: 0 additions & 46 deletions src/userinteract.rs

This file was deleted.

0 comments on commit 071eff3

Please sign in to comment.