niuhuan / rust_proc_qq

[RUST] 模块化QQ机器人框架 (Based RICQ)
Mozilla Public License 2.0
186 stars 19 forks source link

使用正则或其他对event匹配 #17

Closed niuhuan closed 1 year ago

niuhuan commented 1 year ago

参考 SimpleBot


// 场景一
// 解析命令
#[event(command("hello {name}"))]
async fn hello(event: &MessageEvent, name: Option<String>) -> anyhow::Result<bool> {
    if name.is_none() {
        return Ok(false);
    }
    event.send_message_to_source(format!("hello {}", name.unwrap()).parse_message_chain()).await.unwrap();
    Ok(true)
}

// 场景二
// 解析且必须通过过滤器,例如游戏,判断有没有redis状态机
#[event(all(command("hello {name}", filter = "i_am_filter")))]
async fn hello(event: &MessageEvent) -> anyhow::Result<bool> {
    if name.is_none() {
        return Ok(false);
    }
    event.send_message_to_source(format!("hello {}", name.unwrap()).parse_message_chain()).await.unwrap();
    Ok(true)
}

async fn filter(event: &MessageEvent) -> anyhow::Result<bool> {
    if name.is_none() {
        return Ok(false);
    }
    event.send_message_to_source(format!("hello {}", name.unwrap()).parse_message_chain()).await.unwrap();
    Ok(true)
}

// 场景3 消息必须符合正则表达式
#[event(regexp("hello {name}"))]
niuhuan commented 1 year ago

我觉得可以 @LovesAsuna , 你得想法非常棒 (๑•̀ㅂ•́)و✧

niuhuan commented 1 year ago

这块我不知道怎么获取event里面表达式,例如 #[event(command("hello {name}"))] #[event(all(command("hello {name}", filter = "i_am_filter")))]

@LovesAsuna

我没有看懂你写的代码。

LovesAsuna commented 1 year ago

什么叫获取event里面的方法?

niuhuan commented 1 year ago

#[event(command("hello {name}"))] 获取到 command("hello {name}") 并解析 ,或者是嵌套的 all(a(),b()) 这种的解析。

LovesAsuna commented 1 year ago

我的宏里面的格式#[action("hello {name}")],所以代码是

let look_head = input.lookahead1();
if look_head.peek(syn::LitStr) {
    let lit_str = input.parse::<syn::LitStr>().unwrap();
    let pattern = lit_str.value();
    let stream = pattern.parse::<TokenStream>().unwrap();
    let args = syn::parse::<Arg>(stream)
    .map(|args| {
        args.clone()
        .into_iter()
        .enumerate()
        .collect::<HashMap<_, _>>()
    })
    .ok()
    .unwrap_or(HashMap::new());
    Ok(Meta { pattern, args })
}

这里lookhead1把解析流推进了一步,这是个隐式操作,并不知道推进到什么地步。然后下面对这个推进的内容进行一次判断,由于我这里只是用了简单的字符串字面量,所以是用peek(LitSstr)判断获取到的是不是一个字符串字面量。符合的话通过value就可以拿到值了,就是hello {name}


如果你要写成#[event(command("hello {name}"))],那里面的内容就是command("hello {name}")了,处理起来可能会有点复杂,在这里command什么都不是,我觉得当成Ident处理会比较适合,拿到值之后判断是不是command就好了,然会匹配一次左括号,一次字符串字面量,一次右括号,把过程封装起来就好了(猜测写法,不一定是这么写的,尤其是Idennt不确定这里是不是要用这个)

niuhuan commented 1 year ago

syn::AttributeArgs 看起来很可行

:construction: :construction: :construction: :construction: :construction: https://github.com/niuhuan/rust_proc_qq/blob/f950dc679501cdcebe78211c12a714260f82010b/proc_qq_codegen/src/lib.rs#L75

LovesAsuna commented 1 year ago

https://github.com/niuhuan/rust_proc_qq/blob/f950dc679501cdcebe78211c12a714260f82010b/proc_qq_codegen/src/lib.rs#L220 这一行是不是要单独放在impl里的?这样子是可以过编译的嘛?

niuhuan commented 1 year ago

https://github.com/niuhuan/rust_proc_qq/blob/f950dc679501cdcebe78211c12a714260f82010b/proc_qq_codegen/src/lib.rs#L220

这一行是不是要单独放在impl里的?这样子是可以过编译的嘛?

你说的对 o((>ω< ))o 啊啊啊啊,我再改改,没有放参数的时候编译过了 ♥♥♥♥♥

niuhuan commented 1 year ago

19

any , all , not, trim_regexp , trim_eq, regexp, eq 写完了

filter还没有写。看着像是有生命周期问题,需要再弄一个宏变成trait或者struct。

LovesAsuna commented 1 year ago

参数注入这个功能好像还没有实现,现在只有raw的 这个功能还要把匹配的位置或者参数名把对应的参数提取出来注入到方法参数中,自动对类型进行parse。attribute里定义的跟方法参数方法定义的还可以位置不一一对应(这个可以后面实现)

niuhuan commented 1 year ago

我觉得参数注入这个可能需要改变函数的参数个数(加一个map或者多个参数),或者生成一些代码在#block里。再弄个macro!提取。

let name: String = command_param!("name");

或者像issue的demo一样,做的简单一点,其他的event参数互斥。暂时还没有想好怎么弄。hhhh

LovesAsuna commented 1 year ago

我的做法就是加了一个map,要解决的问题就是编译时和运行时的交互,这部分我没什么好的想法,我是直接用json硬编码在代码里了,虽然很丑陋但是起码能用o((>ω< ))o

niuhuan commented 1 year ago

25 这看起来不错

#[event(bot_command = "/ban {user} {time}")]
async fn handle5(_message: &MessageEvent, user: String, time: String) -> anyhow::Result<bool> {
    println!("user : {user} , time : {time} ");
    Ok(false)
}
LovesAsuna commented 1 year ago

看了下代码,限制参数顺序一致确实好写多了,不用考虑很多奇怪的情况。

然后参数的类型是不是可以扩展到支持ricq_core::msg::elem::RQElem? (另外我还挺好奇现在是怎么测试的,tlv544直接让机器人跑不起来)

niuhuan commented 1 year ago

看了下代码,限制参数顺序一致确实好写多了,不用考虑很多奇怪的情况。

然后参数的类型是不是可以扩展到支持ricq_core::msg::elem::RQElem? (另外我还挺好奇现在是怎么测试的,tlv544直接让机器人跑不起来)

我用的之前的session恢复登录测试的。

不过我本地cargo clean之后也编译不过了。改了一下依赖才编译过 https://github.com/niuhuan/rust_proc_qq/commit/a33f2db44042f4659bd5dfcc27db56491a1840d2

我还没有写完参数匹配,先用 default 填充了一下参数。

前两天我确实出现安卓手表账号密码让我换登录方式的情况。我把协议换成安卓手机就好了。可能需要协议和device.json对应?

niuhuan commented 1 year ago

匹配了一些类型 cargo check 通过了, 我还没测是不是能用

ricq_core::msg::elem::RQElem 应该也可以,你是想匹配@At么?一个element 一个element去匹配,想想就很累。

LovesAsuna commented 1 year ago

对,匹配at的对象是挺常用的。就处理几个常用的RQElem就好了

niuhuan commented 1 year ago

ricq_core::msg::elem::RQElem

还有可能会@多个人,得到一个 Vec 🙂

这样我现在代码里的参数,也许这里需要改成迭代器

https://github.com/niuhuan/rust_proc_qq/blob/5387f3bcb51cb88b11230417009afc439220595f/proc_qq_codegen/src/lib.rs#L382

如果把 RQElem::序列化成一些特殊的字符串,例如[RICQ::AT("i64,base64")] 之后再去匹配。减小部分难度。会损失部分性能,或者@会被匹配成字符串

也可以迭代 RQElem ,需要迭代到下一个不是@At的节点 😵

niuhuan commented 1 year ago

已经支持@了,可以试一试,我只自测了几个demo。examples里有demo。

https://github.com/niuhuan/rust_proc_qq/pull/26

niuhuan commented 1 year ago

已经支持各种 RQElem::* 枚举, 并且支持了自定义类型的解析,目前主要代码上了,还在调试

LovesAsuna commented 1 year ago

想迁移命令,突然发现命令缺少一个次要命令。就是 T1F@8TN2KJK}HW2CZ@NT 3U

niuhuan commented 1 year ago

hhhh 我有空闲时间试着搞一下。暂时先用参数代替第二个命令吧。

LovesAsuna commented 1 year ago

又发现还有这种命令了😂, 63 5$ 0P%CX@ _9OG8~G1`Q 这种我要怎么迁移?

niuhuan commented 1 year ago

以现在的源代码。我觉得可以给枚举加上一些标类型。这种匹配数字。文字只能在结尾。或者是正则

niuhuan commented 1 year ago

已经在写了,脑子快被烧光了。而且我把rust升到最新版就没办法编译了。/(ㄒoㄒ)/~~。

https://github.com/niuhuan/rust_proc_qq/blob/b5f479fd4ade62cdfdcb306c35da9ec666d1417f/proc_qq/src/handler/mod.rs#L624

LovesAsuna commented 1 year ago

对,我也发现了,新的nightly加了一些限制,然后ricq过不了编译

niuhuan commented 1 year ago

我的思路就是把botCommand切的更细,包含多个BotElement,每个element匹配完记录一下是否到字符串的结尾。

LovesAsuna commented 1 year ago

感觉有没有空格切割都差不多,都是一样的算法

niuhuan commented 1 year ago

感觉有没有空格切割都差不多,都是一样的算法

连续匹配两个数字没有空格分隔的话不知道前后顺序

niuhuan commented 1 year ago

你可以试试 proc_qq = { git = "", rev= "c3b7201" } , 大概意思写完了

现在是贪婪匹配。只支持数字在中间。字符串会直接匹配到空白字符或末尾。这种匹配方式不是很满意。我觉得还是应该做成根据汉字去str_find的形式, 把文字切成几段,再去匹配参数。

niuhuan commented 1 year ago

改进了一版本,推到sub_command分支了,感觉还不错。会把 {param1}ABC{param2} 看成一个整体去匹配。棒棒,不过没有测试效果。用git引入得ricq,不需要降低rust版本

LovesAsuna commented 1 year ago

越来越复杂了,要看不懂了🤣

niuhuan commented 1 year ago

改进了一版本,推到sub_command分支了,感觉还不错。会把 {param1}ABC{param2} 看成一个整体去匹配。棒棒,不过没有测试效果。用git引入得ricq,不需要降低rust版本

挺简单的,就是先按照空格切。然后再切开子元素。

niuhuan commented 1 year ago

你看看能用不

LovesAsuna commented 1 year ago

我只能测试能不能编译,实际效果没法测。ricq一直没有弄tlv544,我的机器人一直是没法登录

rcoplo commented 1 year ago

第一个参数一直为None 指令:

请5秒后发送ok

代码:

#[event(bot_command = "请{time}秒后发送{context}")]
async fn test_1(e:&MessageEvent,time:Option<i8>,context:Option<String>) -> anyhow::Result<bool>{
    tracing::info!("test ok");
    tracing::info!("{:?}   {:?}",&time,&context);
    Ok(true)
}

日志:

test ok
None   Some("5")
rcoplo commented 1 year ago

还有,如果指令是:

/test5秒后发送ok

会报错

 panicked at 'byte index 11 is not a char boundary; it is inside '后' (bytes 9..12) of `/test5秒后发送ok`
niuhuan commented 1 year ago

好的 🌹

LovesAsuna commented 1 year ago

挺简单的,就是先按照空格切。然后再切开子元素。

这些正则表达式看不太懂,看了一下好像是rust的regex这crate特有的?


然后tlv544怎么破?

niuhuan commented 1 year ago

上面的panic是我切字的时候切错了。协议问题只能等ricq维护,QQ的协议我一点也搞不懂。

LovesAsuna commented 1 year ago

其他部分都懂了,匹配的部分还在试图理解😵


已经催过lz佬很多次,他觉得麻烦不想动:(

niuhuan commented 1 year ago

我只是想偷懒 想复用之前的逻辑,就加了一个 new_from_text , 可能那里有问题。 也有可能 match_mutilple有问题,还得debug才知道, 我没下班。 /(ㄒoㄒ)/~~

LovesAsuna commented 1 year ago

匹配这部分太复杂了,multi_matcher看不懂,没有debug太难理解了。如果有mock的单元测试可以辅助debug来理解就好了。 然后有好多multi写成了mutil

niuhuan commented 1 year ago

匹配这部分太复杂了,multi_matcher看不懂,没有debug太难理解了。如果有mock的单元测试可以辅助debug来理解就好了。 然后有好多multi写成了mutil

我应该改单词错了嘛,我换个词

niuhuan commented 1 year ago

我测了最后提交的代码里,中文会出现问题,英文不会。

thread 'tokio-runtime-worker' panicked at 'byte index 11 is not a char boundary; it is inside '后' (bytes 9..12) of /test5秒后发送ok', proc_qq\src\handler\mod.rs:604:36

是个头疼的问题

niuhuan commented 1 year ago

好了,最后一次的push,我已经测通过了

// 匹配子命令2
#[event(bot_command = "/test{var1}秒后发送{var2}")]
async fn handle10(_message: &MessageEvent, var1: i64, var2: String) -> anyhow::Result<bool> {
    println!("handle10。 : {:?} , {:?} ", var1, var2);
    Ok(true)
}
/test5秒后发送ok
handle10。 : 5 , "ok"
LovesAsuna commented 1 year ago

要不要弄一个mock单测

niuhuan commented 1 year ago

要不要弄一个mock单测

message里面还有client。数据太过复杂。我并不知道怎么才能弄一个mock的mod出来。 如果你在尝试读我写的代码,可以PR点备注或者README进来。

LovesAsuna commented 1 year ago

再抽象出一个新的trait,让mock和ricq分别实现它,单测的时候用这个trait

niuhuan commented 1 year ago

研究了快一个月。终于有了一个helloworld。rust对声明周期的校验很让人难过。

https://github.com/niuhuan/rijq