• Stars
    star
    169
  • Rank 224,453 (Top 5 %)
  • Language
    Rust
  • License
    Mozilla Public Li...
  • Created almost 3 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

[RUST] 模块化QQ机器人框架 (Based RICQ)

RUST_PROC_QQ

license crates.io

  • Rust语言的QQ机器人框架. (基于RICQ)
  • 开箱即用, 操作简单, 代码简洁

QQ机器人框架 | Telegram(电报)机器人框架

框架目的

  • 简单化 : 让程序员写更少的代码
    • 自动管理客户端生命周期以及TCP重连
    • 封装登录流程, 自动获取ticket, 验证滑动条
  • 模块化 : 让调理更清晰
    • 模块化, 实现插件之间的分离, 更好的启用禁用

设计思路

所有的功能都是由插件完成, 事件发生时, 调度器对插件轮训调用, 插件响应是否处理该事件, 直至有插件响应事件, 插件发生异常, 或插件轮训结束, 最后日志结果被记录, 事件响应周期结束。

img.png

如何使用 / demo

如果您使用密码登录,并且不是windows系统,则需要使用安卓设备安装滑块助手,用于第一次登录的验证(windows将会默认使用弹窗进行滑块,除非您禁用它)

https://github.com/mzdluo123/TxCaptchaHelper

新建项目

新建一个rust项目, 并将rust环境设置为nightly

# 设置rust默认环境为 nightly
rustup default nightly

#

# 设置当前项目rust环境设置为 nightly
rustup override set nightly

引用

在Cargo.toml中引入proc_qq

proc_qq = "0.1"

如果您使用的较新nightly的rust时,ricq可能会编译不通过,您需要使用git的方式引入。在ricq发布到到0.1.20时我们将去除这个提示.

同样master分支具有一些新的features,以及使用了较高版本ricqAPI,还没有发布到 crates.io。

proc_qq = { git = "https://github.com/niuhuan/rust_proc_qq.git", branch = "master" }

声明一个模块

hello_module.rs

use proc_qq::re_exports::ricq::client::event::GroupMessageEvent;
use proc_qq::{
    event, module, MessageChainParseTrait, MessageContentTrait, MessageEvent, MessageSendToSourceTrait,
    Module,
};

/// 监听群消息
/// 使用event宏进行声明监听消息
/// 参数为RICQ支持的任何一个类型的消息事件, 必须是引用.
/// 返回值为 anyhow::Result<bool>, Ok(true)为拦截事件, 不再向下一个监听器传递
#[event]
async fn print(event: &MessageEvent) -> anyhow::Result<bool> {
    let content = event.message_content();
    if content.eq("你好") {
        event
            .send_message_to_source("世界".parse_message_chain())
            .await?;
        Ok(true)
    } else if content.eq("RC") {
        event
            .send_message_to_source("NB".parse_message_chain())
            .await?;
        Ok(true)
    } else {
        Ok(false)
    }
}

#[event]
async fn group_hello(_: &GroupMessageEvent) -> anyhow::Result<bool> {
    Ok(false)
}

/// 返回一个模块
pub(crate) fn module() -> Module {
    // id, name, [plugins ...]
    module!("hello", "你好", print, group_hello)
}

启动

main.rs

use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

use proc_qq::Authentication::{QRCode, UinPassword};
use proc_qq::ClientBuilder;

mod hello_module;

/// 启动并使用为二维码登录
#[tokio::test]
async fn test_qr_login() {
    // 初始化日志打印
    init_tracing_subscriber();
    // 使用builder创建
    let client = ClientBuilder::new()
        // 使用session.token登录
        //    .session_store(Box::new(FileSessionStore {
        //      path: "session.token".to_string(),
        //    }))
        .authentication(QRCode)                    // 若不成功则使用二维码登录
        // 注意,这里使用的设备必须支持二维码登录,例如安卓手表
        // 如果您使用为不支持的协议协议,则会登录失败,例如安卓QQ 
        // .authentication(UinPasswordMd5(config.account.uin, password)) // 账号密码登录
        .device(JsonFile("device.json")) // 设备默认值 
        .version(&ANDROID_WATCH)  // 安卓手表支持扫码登录
        // .show_slider_pop_menu_if_possible() // 密码登录时, 如果是windows, 弹出一个窗口代替手机滑块 (需要启用feature=pop_window_slider)
        .modules(vec![hello_module::module()])    // 您可以注册多个模块
        .schedulers(vec![scheduler_handlers::scheduler()]) // 设置定时任务
        .show_rq(Some(ShowQR::OpenBySystem))  // 自动打开二维码 在macos/linux/windows中, 不支持安卓
        .build()
        .await
        .unwrap();
    run_client(Arc::new(client)).await?;
}

fn init_tracing_subscriber() {
    tracing_subscriber::registry()
        .with(
            tracing_subscriber::fmt::layer()
                .with_target(true)
                .without_time(),
        )
        .with(
            tracing_subscriber::filter::Targets::new()
                .with_target("ricq", Level::DEBUG)
                .with_target("proc_qq", Level::DEBUG)
                // 这里改成自己的crate名称
                .with_target("proc_qq_examples", Level::DEBUG),
        )
        .init();
}

效果

demo

功能

登录

支持的事件

use ricq::client::event::{
    DeleteFriendEvent, FriendMessageEvent, FriendMessageRecallEvent, FriendPokeEvent,
    NewFriendRequestEvent, GroupLeaveEvent, GroupMessageEvent, GroupMessageRecallEvent,
    GroupMuteEvent, GroupNameUpdateEvent, JoinGroupRequestEvent, KickedOfflineEvent, MSFOfflineEvent,
    NewFriendEvent, GroupTempMessageEvent,
};
use ricq::client::event::{
    GroupDisbandEvent, MemberPermissionChangeEvent, NewMemberEvent, SelfInvitedEvent,
    GroupAudioMessageEvent, FriendAudioMessageEvent, ClientDisconnect,
};
use proc_qq::{
    MessageEvent, LoginEvent, ConnectedAndOnlineEvent, DisconnectedAndOfflineEvent,
};
  • MessageEvent: 同时适配多种消息
  • LoginEvent: 登录事件(未登录成功) (RICQ中这个事件类型为i64,这里做了封装)
  • ConnectedAndOnlineEvent: 连接成功, 并且登录后 (proc-qq状态)
  • DisconnectedAndOfflineEvent: 掉线并且断开连接 (proc-qq状态)

支持更多种事件封装中...

字段匹配

对消息进行匹配(空白字符RQElem界限作为分隔符)

如下所示,当您输入 ban @abc 123 的时候,控制台将会打印 user : [At:abc] , time : 123

#[event(bot_command = "ban {user} {time}")]
async fn handle5(
  _message: &MessageEvent,
  user: ::proc_qq::re_exports::ricq::msg::elem::At,
  time: i64,
) -> anyhow::Result<bool> {
    println!("user : {:?} , time : {:?} ", user, time);
    Ok(true)
}

同样的也支持文字和数字的组合

#[event(bot_command = "请{time}秒之后告诉我{text}")]
async fn handle5(
  _message: &MessageEvent,
  time: i64,
  text: String,
) -> anyhow::Result<bool> {
  println!("text : {:?} , time : {:?} ", text, time);
  Ok(true)
}

TODO:枚举 : "请{time}{unit:时|分|秒|天}之后告诉我{text}"

目前能匹配的类型

String,  以及对应的 Vec<T>, Option<T>

u8~u128, i8~i128, isize, usize, char, bool, f32, f64; 以及对应的 Vec<T>, Option<T>

ricq::msg::elem::{
  At, Face, MarketFace, Dice, FingerGuessing,
  LightApp, RichMsg, FriendImage, GroupImage,
  FlashImage, VideoFile
}; 以及对应的 Vec<T>, Option<T>

proc_qq::ImageElement (匹配图片, 包括GroupImage, FriendImage, FlashImage)
; 以及对应的 Vec<T>, Option<T> 

Vec<T> 会匹配多个,也会匹配0个, 会尽可能多的匹配。
Option<T> 匹配到一个会返回Some,否则返回None。
空白字符串以及空字符串,不会被匹配为值

自定义类型匹配

  • 您可以参考proc_qq/src/handler/mod.rsFromCommandMatcher实现自定义类型的匹配。
  • 您可以匹配文字,并且在FromCommandMatcher::matching去掉消耗了的部分
  • 如果匹配的是RQElem类型,您应该先判断matching是否为空,不空则不能匹配成功,如果匹配的元素,然后将idx加1, 最后push_text
  • 这里比较难解释,需要您阅读FromCommandMatcher的代码,理解他的工作原理

拓展

直接获取消息的正文内容

use prco_qq::MessageContentTrait;
MessageEvent::message_content;

直接回复消息到消息源

Client::send_message_to_source;
Event::send_message_to_source;
Event::send_audio_to_source;

直接将单个消息文字/图片当作MessageChain使用

MessageChainParseTrait;

client
.send_group_message(group_code, "".parse_message_chain())
.await?;

MessageChain链式追加

MessageChainAppendTrait;

let chain: MessageChain;
let chain = chain.append(at).append(text).append(image);

事件结果

使用result_handlers监听处理结果 (事件参数正在开发)

用来监听message或者其他event的处理结果(有无异常,由哪个模块处理,主要用于日志记录)

Example

定时任务

Example

其他

ricq::msg::elem::Other在push_text的时候将会跳过

过滤器

event参数
MessageEvent / FriendMessageEvent / GroupMessageEvent / GroupTempMessageEvent
trim_regexp trim_eq regexp eq all any not
为什么会有trim: ricq获取消息会在最后追加空白字符
#[event(trim_regexp = "^a([\\S\\s]+)?$", trim_regexp = "^([\\S\\s]+)?b$")]
async fn handle2(event: &MessageEvent) -> anyhow::Result<bool> {
    event
        .send_message_to_source("a开头且b结束".parse_message_chain())
        .await?;
    Ok(true)
}

#[event(any(trim_regexp = "^a([\\S\\s]+)?$", trim_regexp = "^([\\S\\s]+)?b$"))]
async fn handle3(event: &MessageEvent) -> anyhow::Result<bool> {
    event
        .send_message_to_source("a开头或b结束".parse_message_chain())
        .await?;
    Ok(true)
}

手动实现handler和原理

手动实现一个handler

/// 每个handler都是一个struct
struct OnMessage;

/// 给他实现一个Process, 它就对应着监听什么事件
#[async_trait]
impl MessageEventProcess for OnMessage {
    async fn handle(&self, event: &MessageEvent) -> anyhow::Result<bool> {
        self.do_some(event).await?;
        Ok(true)
    }
}

/// 实现一些其他的方法用于调用
impl OnMessage {
    async fn do_some(&self, _event: &MessageEvent) -> anyhow::Result<()> {
        Ok(())
    }
}

/// 将process转换成handler
fn on_message() -> ModuleEventHandler {
    ModuleEventHandler {
        name: "OnMessage".to_owned(),
        process: ModuleEventProcess::Message(Box::new(OnMessage {})),
    }
}

/// 将转化的方法名写到里面
pub(crate) fn module() -> Module {
    module!("hello", "你好", login, print, group_hello, on_message)
}

为什么要强调一下手动创造handler

async fn do_some(_event: &MessageEvent) -> anyhow::Result<()> {
    // 做一些线程不安全的事情
    Ok(())
}

#[event]
async fn handle(event: &MessageEvent) -> anyhow::Result<bool> {
    do_some(event).await?;  // 那么这里的引用生命周期有问题
    Ok(true)
}

总会遇到一些线程不安全的类, 例如scraper. 这个时候编译器会反复告诉你 "maybe used later". 您可以尝试使用手创造一个handler解决.

使用event_fn解决生命周期问题

#[event]
async fn handle4(message: &MessageEvent) -> anyhow::Result<bool> {
    self.handle3_add(message).await;
    Ok(false)
}

#[event_fn(handle3, handle4)]
async fn handle3_add(message: &MessageEvent) {
    println!("{}", message.message_content());
}

网络代理

Example

代理功能有助于您使用服务器的ip登录proc_qq, 并有助于部署到非大陆服务器

  • 您可以在安卓设备上设置代理(按APP进行分流)并启动手机QQ登录。
  • 在proc_qq设置代理并扫码登录安卓手表(届时proc_qq和手机QQ都处于服务器IP)。
  • 删除session.token, 使用账号密码登录。
  • 登录成功后将session.token和device.json都复制到服务器并启动,本地的文件备份好并且不再使用。

其他

实现的功能请转到RICQ仓库查看, 本仓库仅为RICQ的框架.

RICQ 还在发展阶段, 迭代速度较快, 可能出现更改API的情况, 如遇无法运行, 请提issues.

Examples 中提供了HelloWorld

Template 是一个机器人模版, 并提供了一些模块

模版中封装了一些常用功能

直接回复文字, 如果是在群中会自动@

event.reply_text("你好").await?;

数据库的说明

模版中使用了redis作为缓存, mongo作为数据库. 两个数据源搭建都非常简单.

  • redis: 先下载源码, make, 运行 ./redis-server
  • mongo: 下载安装包, 运行 ./mongod

如不需要, 请将database删除, 删除引用它的module, 最后删除main.rs中的init_mongo和init_redis.

额外依赖的说明

模版中演示了如何发送语音消息

每日英语模块需要运行环境已经安装ffmpeg命令, 并且依赖silk-rs, 编译silk-rs需要libclang.dll.

额外协议的说明
  • 暂定本仓库开源协议与RICQ保持一致.
    • MPL 2.0
    • 如RICQ更换协议, 请以最新协议为准, 您可以提出ISSUE提醒我进行更新
  • 仓库持有人在变更仓库协议时无需经过其他代码贡献者的同意, 您在PR时就代表您同意此观点

贡献代码

  • 我很乐意交流,您可以在Issues中提出您的想法进行交流,代码量较少时可直接提PR。如果内容合理,我会尽快CR以及Merge。
  • 如果可以,请使用过程宏对参数进行校验, 将Error在编译时抛出。
  • 使用emit!进行过程宏的代码提交,这有助于Debug。设置环境变量PROC_QQ_CODEGEN_DEBUG=1即可打印过程宏生成的代码。

鸣谢

  • RICQ commiters

  • JetBrains IDEs

  • GitHub Copilot

More Repositories

1

pikapika

美观易用且无广告的漫画和游戏客户端,同时支持MacOS,Windows,Android,iOS。
Dart
4,806
star
2

jasmine

A comic browser,support Android / iOS / MacOS / Windows / Linux.
Dart
2,898
star
3

jenny

A comic browser,support Android / iOS / MacOS / Windows / Linux.
Dart
952
star
4

daisy

美观易用且无广告的漫画和轻小说客户端, 同时支持MacOS,Windows,Android,iOS。类似动漫之家。
Dart
797
star
5

wax

“绅士漫画”的客户端
Dart
552
star
6

nhentai-cross

A nhentai client
Dart
430
star
7

pansy

A illustration client.
Dart
222
star
8

coco

A illustrations and wallpapers client.
Rust
117
star
9

bili-cli-rs

[RUST] 一个Rust的Bilibili视频下载器
Rust
106
star
10

glxx

绅士集结 捐赠地址
97
star
11

kobi

拷贝漫画客户端
Dart
57
star
12

niuhuan

39
star
13

html-comic

A hitomi.la client. 一个 Hitomi.la客户端。
Rust
30
star
14

pica-go

Golang版的哔咔漫画API。
Go
29
star
15

mirai-bot

一个基于MariFramework的QQ机器人, 可以玩农场小游戏。
Go
21
star
16

pica-rust

Rust哔卡漫画API
Rust
20
star
17

mirai-framework

一个基于MariGo的QQ机器人框架, 完全插件化的设计, 帮您轻而易举的建立属于自己的机器人, 对其增改插件, 同时保持更为清晰的代码结构。
Go
18
star
18

bilirust

RUST 哔哩哔哩客户端
Rust
16
star
19

csdn-fucker

捐赠地址
JavaScript
13
star
20

notebook

13
star
21

dont-starve-mod-character-properties

一个饥荒的mod, 用于修改角色的基本属性(三维, 攻速, 伤害, 回血, 回san), 以及增加新手礼包功能。
Lua
10
star
22

feign-rs

A restful http client of rust. like java feign.
Rust
9
star
23

gbc-swing

Best game boy emulator in java. Compile to java1.6.
Java
9
star
24

psp-pixels

一个PSP游戏机项目, 字库, 文件浏览器, 图片查看器。
C
8
star
25

bilibili-cross

Dart
7
star
26

pixirust

Rust
7
star
27

pikapika-frb

6
star
28

reqwest_dav

An async webdav client for rust with tokio and reqwest
Rust
6
star
29

nhentai-go

N-hentai api with go
Go
5
star
30

appstoreconnect-rs

Appstoreconnect client for rust
Rust
5
star
31

teleser-rs

Telegrame bot framework (MTProto) (Grammers)
Rust
5
star
32

tetris-rs

Tetris by rust sdl2. (Rust + SDL2实现的俄罗斯方块)
Rust
5
star
33

patreon-rs

Patreon client crate for rust
Rust
4
star
34

gofile

gofile.io api for golang
Go
3
star
35

wdav-rs

[RUST][BIN] Quick start a webdav server
Rust
2
star
36

app_sources

2
star
37

jc-rs

Rust
1
star
38

kami

Dart
1
star
39

pkz-go

Go
1
star
40

jammy

C++
1
star
41

pkz-rs

Rust
1
star