Code Monkey home page Code Monkey logo

rust_proc_qq's Introduction

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 std::sync::Arc;
use proc_qq::re_exports::ricq;
use proc_qq::Authentication::{QRCode, UinPassword};
use proc_qq::ClientBuilder;
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

mod hello_module;

/// 启动并使用为二维码登录
#[tokio::test]
async fn test_qr_login() {
  // 初始化日志打印
  init_tracing_subscriber();
  // 设置签名服务器
  let qsign =
          ricq::qsign::QSignClient::new(
            "url".to_owned(),
            "key ".to_owned(),
            Duration::from_secs(60),
          ).expect("qsign client build err");
  // 使用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".to_owned())) // 设备默认值
          .version(&ANDROID_WATCH) // 安卓手表支持扫码登录
          .qsign(Some(Arc::new(qsign))) // 签名服务器,目前的版本必须使用
          // .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状态)

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

签名服务器

8.9.63开始,QQ使用了签名服务器,在不使用签名服务器的情况下将无法登录

搭建方法可到 https://github.com/fuqiuluo/unidbg-fetch-qsign 查看 或者使用 docker 直接运行 docker run -d --restart=always --name qsign -p 8080:8080 xzhouqd/qsign:8.9.63 请注意 sso 版本必须和协议版本一致

字段匹配

对消息进行匹配(空白字符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)
}

枚举

请注意,枚举的匹配是通过 | 来分割的,第一个枚举值的前面也需要|。 另外Uint可以时String,或者是数字类型。也可以是实现了::proc_qq::TryFromStr的自定义类型。

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

目前能匹配的类型

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

rust_proc_qq's People

Contributors

dylan-dpc avatar lovesasuna avatar mosttt avatar niuhuan avatar rcoplo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust_proc_qq's Issues

无法扫码登录

扫码时提示该APP尚未开通扫描二维码登录的权限。

ClientBuilder::new()
        .priority_session("session.token") // 默认使用session.token登录
        .device(JsonFile("device.json".to_owned())) // 设备默认值
        .authentication(Authentication::QRCode) // 若不成功则使用二维码登录
        .modules(vec![module::dnd::module(), module::dice::module()]) // 您可以注册多个模块
        .build()
        .await
        .unwrap()
        .start()
        .await
        .unwrap()
        .unwrap();

使用账号密码登录无法通过设备锁。关闭设备锁账号疑似被风控,QQ群信息发送长度受限。

anyhow报错问题解决办法

Cargo.toml

anyhow = "1.0.59"

Cargo.lock

[[package]]
name = "anyhow"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9"

module添加方式

能否为Module添加一种父子模块的注册方式
比如一个父模块如下

lazy_static! {
    static ref MODULES: Arc<Vec<Module>> = Arc::new(vec![]);
}

pub fn all_module() -> Arc<Vec<Module>> {
    MODULES.clone()
}

在modules的目录下有一个子目录的模块

lazy_static! {
    static ref OSU_MODULES: Arc<Vec<Module>> = Arc::new(vec![]);
}

pub fn osu_module() -> Arc<Vec<Module>> {
    OSU_MODULES.clone()
}

能否添加一种方法(比如迭代器)将子模块与父模块进行合并, 比如

pub fn all_module() -> Arc<Vec<Module>> {
    let mut modules = MODULES.clone();
    modules.extend(osu::osu_module().clone().into_iter());
    modules
}

联系方式

有联系方式吗请问,想用这个做个软件,可以交流下吗

教学视频,文档

第一次尝试写qq机器人,请问从哪里开始学可以入门呢?(好像这个提issue不太合适,如有冒犯请删除)
: )

恢复会话失败回调

能否提供一个重新登陆失败的回调接口,方便失败之后做一些操作,例如重启自身来登录。

使用正则或其他对event匹配

参考 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}"))]

无法登录

使用二维码扫码登录,扫码后提示 当前环境存在风险,为保障你的账号安全,暂时无法登录, 换了三个账号都是同样情况

后换成使用密码登录,但是弹出滑块验证滑动验证完成后,滑块验证页面白屏无任何反应,打开控制台显示两个错误

image

这种情况该如何处理呢

[功能] 定时任务支持

大佬看看这个可行吗 commit

/// 每1分钟获取一次 bot uin
#[scheduler_job(cron = "0 0/1 * * * ?")]
async fn handle_scheduler(c:Arc<Client>) {
    let bot_uin = c.bot_uin().await;
    println!("{}", bot_uin);
}

/// scheduler
pub fn scheduler() -> SchedulerJob {
    scheduler!(
        "hello_jobs",
        handle_scheduler
    )
}

如果可以的话我提一个pr

桌面弹窗滑块

# 滑块助手
.show_slider(Some(ShowSlider:AndroidHelper)) // 默认
# 桌面弹窗
.show_slider(Some(ShowSlider:PopWindow))

参考showQR

桌面弹窗参考 https://github.com/Cherrs/lailai 实现, 可能安卓会编译不通过, 那样的话使用#[cfg]。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.