{"id":15479258,"url":"https://github.com/niuhuan/rust_proc_qq","last_synced_at":"2025-04-04T19:11:26.284Z","repository":{"id":40637939,"uuid":"461173127","full_name":"niuhuan/rust_proc_qq","owner":"niuhuan","description":"[RUST] 模块化QQ机器人框架 （Based RICQ）","archived":false,"fork":false,"pushed_at":"2024-02-02T08:54:56.000Z","size":796,"stargazers_count":186,"open_issues_count":0,"forks_count":19,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-30T06:58:41.589Z","etag":null,"topics":["bot","coolq","mirai","oicq","qq","qq-bot","qq-protocol","qqbot","ricq","rs-qq","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/niuhuan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-02-19T11:36:27.000Z","updated_at":"2024-10-18T11:59:25.000Z","dependencies_parsed_at":"2024-02-02T09:58:20.755Z","dependency_job_id":null,"html_url":"https://github.com/niuhuan/rust_proc_qq","commit_stats":{"total_commits":124,"total_committers":4,"mean_commits":31.0,"dds":0.08064516129032262,"last_synced_commit":"5900e4adcd98a8e95eae9371ba6fa8f8b202e32e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niuhuan%2Frust_proc_qq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niuhuan%2Frust_proc_qq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niuhuan%2Frust_proc_qq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niuhuan%2Frust_proc_qq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/niuhuan","download_url":"https://codeload.github.com/niuhuan/rust_proc_qq/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247234921,"owners_count":20905854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","coolq","mirai","oicq","qq","qq-bot","qq-protocol","qqbot","ricq","rs-qq","rust"],"created_at":"2024-10-02T04:20:30.258Z","updated_at":"2025-04-04T19:11:26.257Z","avatar_url":"https://github.com/niuhuan.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"RUST_PROC_QQ\n============\n\n[![license](https://img.shields.io/github/license/niuhuan/rust_proc_qq)](https://raw.githubusercontent.com/niuhuan/rust_proc_qq/master/LICENSE)\n[![crates.io](https://img.shields.io/crates/v/proc_qq.svg)](https://crates.io/crates/proc_qq)\n\n- Rust语言的QQ机器人框架. (基于[RICQ](https://github.com/lz1998/ricq))\n- 开箱即用, 操作简单, 代码简洁\n\nQQ机器人框架 | [Telegram(电报)机器人框架](https://github.com/niuhuan/teleser-rs)\n\n## 框架目的\n\n- 简单化 : 让程序员写更少的代码\n  - 自动管理客户端生命周期以及TCP重连\n  - 封装登录流程, 自动获取ticket, 验证滑动条\n- 模块化 : 让调理更清晰\n  - 模块化, 实现插件之间的分离, 更好的启用禁用\n\n# 设计思路\n\n所有的功能都是由插件完成, 事件发生时, 调度器对插件轮训调用, 插件响应是否处理该事件, 直至有插件响应事件, 插件发生异常,\n或插件轮训结束, 最后日志结果被记录, 事件响应周期结束。\n\n![img.png](images/invoke.png)\n\n## 如何使用 / demo\n\n如果您使用密码登录，并且不是windows系统，则需要使用安卓设备安装滑块助手，用于第一次登录的验证(windows将会默认使用弹窗进行滑块，除非您禁用它)\n\nhttps://github.com/mzdluo123/TxCaptchaHelper\n\n\n### 新建项目\n\n新建一个rust项目, 并将rust环境设置为nightly\n\n```shell\n# 设置rust默认环境为 nightly\nrustup default nightly\n\n# 或\n\n# 设置当前项目rust环境设置为 nightly\nrustup override set nightly\n```\n\n### 引用\n\n在Cargo.toml中引入proc_qq\n\n```toml\nproc_qq = \"0.1\"\n```\n\n如果您使用的较新nightly的rust时，ricq可能会编译不通过，您需要使用git的方式引入。在ricq发布到到0.1.20时我们将去除这个提示.\n\n同样master分支具有一些新的features，以及使用了较高版本ricqAPI，还没有发布到 crates.io。\n\n```toml\nproc_qq = { git = \"https://github.com/niuhuan/rust_proc_qq.git\", branch = \"master\" }\n```\n\n\n### 声明一个模块\n\nhello_module.rs\n\n```rust\nuse proc_qq::re_exports::ricq::client::event::GroupMessageEvent;\nuse proc_qq::{\n    event, module, MessageChainParseTrait, MessageContentTrait, MessageEvent, MessageSendToSourceTrait,\n    Module,\n};\n\n/// 监听群消息\n/// 使用event宏进行声明监听消息\n/// 参数为RICQ支持的任何一个类型的消息事件, 必须是引用.\n/// 返回值为 anyhow::Result\u003cbool\u003e, Ok(true)为拦截事件, 不再向下一个监听器传递\n#[event]\nasync fn print(event: \u0026MessageEvent) -\u003e anyhow::Result\u003cbool\u003e {\n    let content = event.message_content();\n    if content.eq(\"你好\") {\n        event\n            .send_message_to_source(\"世界\".parse_message_chain())\n            .await?;\n        Ok(true)\n    } else if content.eq(\"RC\") {\n        event\n            .send_message_to_source(\"NB\".parse_message_chain())\n            .await?;\n        Ok(true)\n    } else {\n        Ok(false)\n    }\n}\n\n#[event]\nasync fn group_hello(_: \u0026GroupMessageEvent) -\u003e anyhow::Result\u003cbool\u003e {\n    Ok(false)\n}\n\n/// 返回一个模块\npub(crate) fn module() -\u003e Module {\n    // id, name, [plugins ...]\n    module!(\"hello\", \"你好\", print, group_hello)\n}\n```\n\n### 启动\n\nmain.rs\n\n```rust\nuse std::sync::Arc;\nuse proc_qq::re_exports::ricq;\nuse proc_qq::Authentication::{QRCode, UinPassword};\nuse proc_qq::ClientBuilder;\nuse tracing::Level;\nuse tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};\n\nmod hello_module;\n\n/// 启动并使用为二维码登录\n#[tokio::test]\nasync fn test_qr_login() {\n  // 初始化日志打印\n  init_tracing_subscriber();\n  // 设置签名服务器\n  let qsign =\n          ricq::qsign::QSignClient::new(\n            \"url\".to_owned(),\n            \"key \".to_owned(),\n            Duration::from_secs(60),\n          ).expect(\"qsign client build err\");\n  // 使用builder创建\n  let client = ClientBuilder::new()\n          // 使用session.token登录\n          //    .session_store(Box::new(FileSessionStore {\n          //      path: \"session.token\".to_string(),\n          //    }))\n          .authentication(QRCode) // 若不成功则使用二维码登录\n          // 注意，这里使用的设备必须支持二维码登录，例如安卓手表\n          // 如果您使用为不支持的协议协议，则会登录失败，例如安卓QQ\n          // .authentication(UinPasswordMd5(config.account.uin, password)) // 账号密码登录\n          .device(JsonFile(\"device.json\".to_owned())) // 设备默认值\n          .version(\u0026ANDROID_WATCH) // 安卓手表支持扫码登录\n          .qsign(Some(Arc::new(qsign))) // 签名服务器，目前的版本必须使用\n          // .show_slider_pop_menu_if_possible() // 密码登录时, 如果是windows, 弹出一个窗口代替手机滑块 (需要启用feature=pop_window_slider)\n          .modules(vec![hello_module::module()]) // 您可以注册多个模块\n          .schedulers(vec![scheduler_handlers::scheduler()]) // 设置定时任务\n          .show_rq(Some(ShowQR::OpenBySystem)) // 自动打开二维码 在macos/linux/windows中, 不支持安卓\n          .build()\n          .await\n          .unwrap();\n  run_client(Arc::new(client)).await?;\n}\n\nfn init_tracing_subscriber() {\n  tracing_subscriber::registry()\n          .with(\n            tracing_subscriber::fmt::layer()\n                    .with_target(true)\n                    .without_time(),\n          )\n          .with(\n            tracing_subscriber::filter::Targets::new()\n                    .with_target(\"ricq\", Level::DEBUG)\n                    .with_target(\"proc_qq\", Level::DEBUG)\n                    // 这里改成自己的crate名称\n                    .with_target(\"proc_qq_examples\", Level::DEBUG),\n          )\n          .init();\n}\n\n```\n\n### 效果\n\n![demo](images/demo_01.jpg)\n\n## 功能\n\n### 登录\n\n- 打印二维码到控制台 `.show_rq(Some(ShowQR::PrintToConsole))`\n- [自定义显示二维码](docs/CustomShowQR.md)\n\n### 支持的事件\n\n```rust\nuse ricq::client::event::{\n    DeleteFriendEvent, FriendMessageEvent, FriendMessageRecallEvent, FriendPokeEvent,\n    NewFriendRequestEvent, GroupLeaveEvent, GroupMessageEvent, GroupMessageRecallEvent,\n    GroupMuteEvent, GroupNameUpdateEvent, JoinGroupRequestEvent, KickedOfflineEvent, MSFOfflineEvent,\n    NewFriendEvent, GroupTempMessageEvent,\n};\nuse ricq::client::event::{\n    GroupDisbandEvent, MemberPermissionChangeEvent, NewMemberEvent, SelfInvitedEvent,\n    GroupAudioMessageEvent, FriendAudioMessageEvent, ClientDisconnect,\n};\nuse proc_qq::{\n    MessageEvent, LoginEvent, ConnectedAndOnlineEvent, DisconnectedAndOfflineEvent,\n};\n```\n\n- MessageEvent: 同时适配多种消息\n- LoginEvent: 登录事件(未登录成功) (RICQ中这个事件类型为i64,这里做了封装)\n- ConnectedAndOnlineEvent: 连接成功, 并且登录后 (proc-qq状态)\n- DisconnectedAndOfflineEvent: 掉线并且断开连接 (proc-qq状态)\n\n支持更多种事件封装中...\n\n### 签名服务器\n\u003e 从`8.9.63`开始，QQ使用了签名服务器，在不使用签名服务器的情况下将无法登录\n\n搭建方法可到 https://github.com/fuqiuluo/unidbg-fetch-qsign 查看\n或者使用 `docker` 直接运行 `docker run -d --restart=always --name qsign -p 8080:8080 xzhouqd/qsign:8.9.63`\n**请注意 sso 版本必须和协议版本一致**\n\n## 字段匹配\n\n对消息进行匹配（`空白字符`或`RQElem界限`作为分隔符）\n\n如下所示，当您输入 `ban @abc 123` 的时候，控制台将会打印 `user : [At:abc] , time : 123`\n\n```rust\n#[event(bot_command = \"ban {user} {time}\")]\nasync fn handle5(\n  _message: \u0026MessageEvent,\n  user: ::proc_qq::re_exports::ricq::msg::elem::At,\n  time: i64,\n) -\u003e anyhow::Result\u003cbool\u003e {\n    println!(\"user : {:?} , time : {:?} \", user, time);\n    Ok(true)\n}\n```\n\n同样的也支持文字和数字的组合\n\n```rust\n#[event(bot_command = \"请{time}秒之后告诉我{text}\")]\nasync fn handle5(\n  _message: \u0026MessageEvent,\n  time: i64,\n  text: String,\n) -\u003e anyhow::Result\u003cbool\u003e {\n  println!(\"text : {:?} , time : {:?} \", text, time);\n  Ok(true)\n}\n```\n\n枚举\n\n请注意，枚举的匹配是通过 `|` 来分割的，第一个枚举值的前面也需要|。\n另外Uint可以时String，或者是数字类型。也可以是实现了`::proc_qq::TryFromStr`的自定义类型。\n\n```rust\n#[event(bot_command = \"请{time}{unit:|时|分|秒|天}之后告诉我{text}\")]\nasync fn handle5(\n  _message: \u0026MessageEvent,\n  time: i64,\n  unit: Unit,\n) -\u003e anyhow::Result\u003cbool\u003e {\n  println!(\"text : {:?} , time : {:?} \", text, time);\n  Ok(true)\n}\n```\n\n#### 目前能匹配的类型\n```\nString,  以及对应的 Vec\u003cT\u003e， Option\u003cT\u003e\n\nu8~u128, i8~i128, isize, usize, char, bool, f32, f64; 以及对应的 Vec\u003cT\u003e， Option\u003cT\u003e\n\nricq::msg::elem::{\n  At, Face, MarketFace, Dice, FingerGuessing,\n  LightApp, RichMsg, FriendImage, GroupImage,\n  FlashImage, VideoFile\n}; 以及对应的 Vec\u003cT\u003e， Option\u003cT\u003e\n\nproc_qq::ImageElement (匹配图片, 包括GroupImage, FriendImage, FlashImage)\n; 以及对应的 Vec\u003cT\u003e, Option\u003cT\u003e \n\nVec\u003cT\u003e 会匹配多个，也会匹配0个, 会尽可能多的匹配。\nOption\u003cT\u003e 匹配到一个会返回Some，否则返回None。\n空白字符串以及空字符串，不会被匹配为值\n```\n\n### 自定义类型匹配\n- 您可以参考`proc_qq/src/handler/mod.rs`中`FromCommandMatcher`实现自定义类型的匹配。\n- 您可以匹配文字，并且在`FromCommandMatcher::matching`去掉消耗了的部分\n- 如果匹配的是RQElem类型，您应该先判断`matching`是否为空，不空则不能匹配成功，如果匹配的元素，然后将`idx`加1, 最后push_text\n- 这里比较难解释，需要您阅读`FromCommandMatcher`的代码，理解他的工作原理\n\n### 拓展\n\n#### 直接获取消息的正文内容\n\n```rust\nuse prco_qq::MessageContentTrait;\nMessageEvent::message_content;\n```\n\n#### 直接回复消息到消息源\n\n```rust\nClient::send_message_to_source;\nEvent::send_message_to_source;\nEvent::send_audio_to_source;\n```\n\n#### 直接将单个消息文字/图片当作MessageChain使用\n\n```rust\nMessageChainParseTrait;\n\nclient\n.send_group_message(group_code, \"\".parse_message_chain())\n.await?;\n```\n\n####     \n\nMessageChain链式追加\n\n```rust\nMessageChainAppendTrait;\n\nlet chain: MessageChain;\nlet chain = chain.append(at).append(text).append(image);\n```\n\n## 事件结果\n\n使用result_handlers监听处理结果 (事件参数正在开发)\n\n用来监听message或者其他event的处理结果（有无异常，由哪个模块处理，主要用于日志记录）\n\n[Example](docs/EventResult.md)\n\n## 定时任务\n\n[Example](docs/SchedulerJob.md)\n\n### 其他\n`ricq::msg::elem::Other`在push_text的时候将会跳过\n\n## 过滤器\n\n    event参数\n    MessageEvent / FriendMessageEvent / GroupMessageEvent / GroupTempMessageEvent\n    trim_regexp trim_eq regexp eq all any not\n    为什么会有trim: ricq获取消息会在最后追加空白字符\n\n```rust\n#[event(trim_regexp = \"^a([\\\\S\\\\s]+)?$\", trim_regexp = \"^([\\\\S\\\\s]+)?b$\")]\nasync fn handle2(event: \u0026MessageEvent) -\u003e anyhow::Result\u003cbool\u003e {\n    event\n        .send_message_to_source(\"a开头且b结束\".parse_message_chain())\n        .await?;\n    Ok(true)\n}\n\n#[event(any(trim_regexp = \"^a([\\\\S\\\\s]+)?$\", trim_regexp = \"^([\\\\S\\\\s]+)?b$\"))]\nasync fn handle3(event: \u0026MessageEvent) -\u003e anyhow::Result\u003cbool\u003e {\n    event\n        .send_message_to_source(\"a开头或b结束\".parse_message_chain())\n        .await?;\n    Ok(true)\n}\n```\n\n## 手动实现handler和原理\n\n手动实现一个handler\n\n```rust\n\n/// 每个handler都是一个struct\nstruct OnMessage;\n\n/// 给他实现一个Process, 它就对应着监听什么事件\n#[async_trait]\nimpl MessageEventProcess for OnMessage {\n    async fn handle(\u0026self, event: \u0026MessageEvent) -\u003e anyhow::Result\u003cbool\u003e {\n        self.do_some(event).await?;\n        Ok(true)\n    }\n}\n\n/// 实现一些其他的方法用于调用\nimpl OnMessage {\n    async fn do_some(\u0026self, _event: \u0026MessageEvent) -\u003e anyhow::Result\u003c()\u003e {\n        Ok(())\n    }\n}\n\n/// 将process转换成handler\nfn on_message() -\u003e ModuleEventHandler {\n    ModuleEventHandler {\n        name: \"OnMessage\".to_owned(),\n        process: ModuleEventProcess::Message(Box::new(OnMessage {})),\n    }\n}\n\n/// 将转化的方法名写到里面\npub(crate) fn module() -\u003e Module {\n    module!(\"hello\", \"你好\", login, print, group_hello, on_message)\n}\n```\n\n为什么要强调一下手动创造handler\n\n```rust\n\nasync fn do_some(_event: \u0026MessageEvent) -\u003e anyhow::Result\u003c()\u003e {\n    // 做一些线程不安全的事情\n    Ok(())\n}\n\n#[event]\nasync fn handle(event: \u0026MessageEvent) -\u003e anyhow::Result\u003cbool\u003e {\n    do_some(event).await?;  // 那么这里的引用生命周期有问题\n    Ok(true)\n}\n\n```\n\n总会遇到一些线程不安全的类, 例如*scraper*. 这个时候编译器会反复告诉你 \"maybe used later\". 您可以尝试使用手创造一个handler解决.\n\n### 使用event_fn解决生命周期问题\n\n```rust\n#[event]\nasync fn handle4(message: \u0026MessageEvent) -\u003e anyhow::Result\u003cbool\u003e {\n    self.handle3_add(message).await;\n    Ok(false)\n}\n\n#[event_fn(handle3, handle4)]\nasync fn handle3_add(message: \u0026MessageEvent) {\n    println!(\"{}\", message.message_content());\n}\n```\n\n## 网络代理\n\n[Example](docs/Proxy.md)\n\n代理功能有助于您使用服务器的ip登录proc_qq, 并有助于部署到非大陆服务器\n\n- 您可以在安卓设备上设置代理（按APP进行分流）并启动手机QQ登录。\n- 在proc_qq设置代理并扫码登录安卓手表（届时proc_qq和手机QQ都处于服务器IP）。\n- 删除session.token, 使用账号密码登录。\n- 登录成功后将session.token和device.json都复制到服务器并启动，本地的文件备份好并且不再使用。\n\n## 其他\n\n实现的功能请转到RICQ仓库查看, 本仓库仅为RICQ的框架.\n\nRICQ 还在发展阶段, 迭代速度较快, 可能出现更改API的情况, 如遇无法运行, 请提issues.\n\n#### [Examples](proc_qq_examples) 中提供了HelloWorld\n\n#### [Template](proc_qq_template) 是一个机器人模版, 并提供了一些模块\n\n##### 模版中封装了一些常用功能\n\n直接回复文字, 如果是在群中会自动@\n\n```rust\nevent.reply_text(\"你好\").await?;\n```\n\n![](images/img_lib_01.png)\n\n![](images/img_lib_02.png)\n\n![](images/group_admin_01.png)\n\n##### 数据库的说明\n\n模版中使用了redis作为缓存, mongo作为数据库. 两个数据源搭建都非常简单.\n\n- redis: 先下载[源码](https://redis.io/download), make, 运行 ./redis-server\n- mongo: 下载[安装包](https://www.mongodb.com/try/download/community), 运行 ./mongod\n\n如不需要, 请将database删除, 删除引用它的module, 最后删除main.rs中的init_mongo和init_redis.\n\n##### 额外依赖的说明\n\n模版中演示了如何发送语音消息\n\n每日英语模块需要运行环境已经安装ffmpeg命令, 并且依赖silk-rs, 编译silk-rs需要libclang.dll.\n\n- 下载LLVM-${version}-win64.exe并安装 : https://github.com/llvm/llvm-project/releases/\n- 下载ffmpeg : https://www.ffmpeg.org/download.html\n\n##### 额外协议的说明\n\n- 暂定本仓库开源协议与RICQ保持一致.\n  - MPL 2.0\n  - 如RICQ更换协议, 请以最新协议为准, 您可以提出ISSUE提醒我进行更新\n- 仓库持有人在变更仓库协议时无需经过其他代码贡献者的同意, 您在PR时就代表您同意此观点\n\n## 贡献代码\n\n- 我很乐意交流，您可以在Issues中提出您的想法进行交流，代码量较少时可直接提PR。如果内容合理，我会尽快CR以及Merge。\n- 如果可以，请使用过程宏对参数进行校验, 将Error在编译时抛出。\n- 使用emit!进行过程宏的代码提交，这有助于Debug。设置环境变量`PROC_QQ_CODEGEN_DEBUG=1`即可打印过程宏生成的代码。\n\n#### 鸣谢\n\n- RICQ commiters\n\n\n- JetBrains IDEs\n\n![](images/JetBrains.png)\n\n- GitHub Copilot\n\n![](images/GitHub-Copilot.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniuhuan%2Frust_proc_qq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniuhuan%2Frust_proc_qq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniuhuan%2Frust_proc_qq/lists"}