Code Monkey home page Code Monkey logo

ricq's Introduction

RICQ

QQ Android 协议的 Rust 实现,移植于 OICQ

  • ricq:提供异步 API
  • ricq-core:不带 IO 的数据包构造器、解析器(通常用于 FFI)
  • ricq-axum-api:提供 HTTP API 形式的登录接口,配合 ricq-react-ui,只需要开发登录后的逻辑。

如何使用

本项目是协议 Lib,如果需要直接使用,可以参考 examples 中的例子进行开发。

可以配合前端界面 ricq-react-ui 解压 static.zip 后,运行 ricq-axum-api,使用浏览器 F12-Network 查看调用方式。

普通开发者推荐使用 SDK、框架进行开发:

框架 / SDK 语言 说明
rust_proc_qq Rust 模仿rocket
Walle-Q - onebot协议
pbrq - websocket+protobuf协议(附带Web-UI
atri_qq - 加载原生动态库插件,高性能低占用
awr Python 基于 ricq 包装,供 Python 使用的 QQ 无头客户端。

本项目是一个年轻的项目,请使用 Nightly 工具链构建本项目哦(正经人谁用 Stable 啊)

相关项目

项目 描述
lomirus/gtk-qq Unofficial Linux QQ client, based on GTK4 and libadwaita, developed with Rust and Relm4.
a1967629423/esp32c3-rs-qq 在单片机上运行QQ
ricq-react-ui + ricq-axum-api 登录 demo,附带前端 UI

已完成功能 / 开发计划

登录

  • 账号密码登录
  • 二维码登录
  • 验证码提交
  • 设备锁验证
  • 错误信息解析

消息类型

  • 文本
  • 表情
  • At
  • 回复
  • 匿名
  • 骰子
  • 石头剪刀布
  • 图片
  • 语音
  • 长消息(仅支持群聊发送)
  • 合并转发(仅支持群聊发送)
  • 链接分享
  • 小程序(暂只支持 RAW)
  • 短视频
  • 群文件(上传与接收信息)

事件

  • 群消息
  • 好友消息
  • 新好友请求
  • 收到其他用户进群请求
  • 新好友
  • 群禁言
  • 好友消息撤回
  • 群消息撤回
  • 收到邀请进群请求
  • 群名称变更
  • 好友删除
  • 群成员权限变更
  • 新成员进群 / 退群
  • 登录号加群
  • 临时会话消息
  • 群解散
  • 登录号退群(包含踢出)
  • 客户端离线
  • 群提示(戳一戳 / 运气王等)

主动操作

为防止滥用,将不支持主动邀请新成员进群

  • 修改昵称
  • 发送群消息
  • 获取群列表
  • 获取群成员列表
  • 获取好友列表 / 分组
  • 获取好友个性签名
  • 添加 / 删除 / 重命名好友分组
  • 群成员禁言 / 解除禁言
  • 踢出群成员
  • 戳一戳群友
  • 戳一戳好友
  • 设置群管理员
  • 设置群公告
  • 设置群名称
  • 全员禁言
  • 获取群@全体剩余次数
  • 翻译
  • 修改群成员头衔
  • 设置群精华消息
  • 发送好友消息
  • 发送临时会话消息
  • 修改群成员 Card
  • 撤回群消息
  • 撤回好友消息
  • 处理被邀请加群请求
  • 处理加群请求
  • 处理好友请求
  • 删除好友
  • 获取陌生人信息
  • 设置在线状态
  • 修改个人资料
  • 修改个性签名
  • 获取群文件下载链接
  • 获取群荣誉(龙王 / 群聊火焰等)
  • 群成员邀请

敏感操作

由于 QQ 钱包支付用户服务协议, 将不提供一切有关 QQ 钱包的功能。

4.13 您不得利用本服务实施下列任一的行为:
(9) 侵害 QQ 钱包支付服务系統;

  • QQ 钱包协议(收款 / 付款等)

ricq's People

Contributors

abrahum avatar blueglassblock avatar cherrs avatar fortescarlet avatar goodjooy avatar kkocdko avatar laolittle avatar lz1998 avatar misakatat avatar niuhuan avatar nkdark avatar sclock avatar wybxc avatar zkonge 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  avatar  avatar  avatar  avatar  avatar  avatar

ricq's Issues

无法接收 `GroupMessageRecall` 事件

日志:

2022-08-13T22:25:23.585453Z  INFO qrcode_login: MESSAGE (GROUP=619263282): 将被撤回
2022-08-13T22:25:35.504796Z  INFO qrcode_login: MESSAGE (FRIEND=3423596160): 将被撤回
2022-08-13T22:25:38.331936Z  INFO qrcode_login: FriendMessageRecall EventWithClient { inner: FriendMessageRecall { msg_seq: 62232, friend_uin: 3423596160, time: 1660429535 } }
测试代码:
use anyhow::Result;
use bytes::Bytes;
use ricq::client::{Connector as _, DefaultConnector};
use ricq::ext::common::after_login;
use ricq::handler::DefaultHandler;
use ricq::handler::{Handler, QEvent};
use ricq::{Client, Device, Protocol};
use ricq::{LoginResponse, QRCodeConfirmed, QRCodeImageFetch, QRCodeState};
use std::path::Path;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
    tracing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer().with_target(true))
        .with(
            tracing_subscriber::filter::Targets::new()
                .with_target("ricq", Level::DEBUG)
                .with_target("qrcode_login", Level::DEBUG),
        )
        .init();

    let device = match Path::new("device.json").exists() {
        true => serde_json::from_str(&tokio::fs::read_to_string("device.json").await?)?,
        false => {
            let d = Device::random();
            tokio::fs::write("device.json", serde_json::to_string(&d).unwrap()).await?;
            d
        }
    };

    let client = Arc::new(Client::new(
        device,
        Protocol::AndroidWatch.into(),
        MyHandler,
    ));
    let handle = tokio::spawn({
        let client = client.clone();
        let stream = DefaultConnector.connect(&client).await.unwrap();
        async move { client.start(stream).await }
    });
    tokio::task::yield_now().await;
    let mut resp = client.fetch_qrcode().await?;

    let mut image_sig = Bytes::new();
    loop {
        match resp {
            QRCodeState::ImageFetch(QRCodeImageFetch {
                ref image_data,
                ref sig,
            }) => {
                tokio::fs::write("qrcode.png", &image_data).await?;
                image_sig = sig.clone();
                tracing::info!("二维码: qrcode.png");
            }
            QRCodeState::WaitingForScan => {
                tracing::info!("二维码待扫描")
            }
            QRCodeState::WaitingForConfirm => {
                tracing::info!("二维码待确认")
            }
            QRCodeState::Timeout => {
                tracing::info!("二维码已超时,重新获取");
                if let QRCodeState::ImageFetch(QRCodeImageFetch {
                    ref image_data,
                    ref sig,
                }) = client.fetch_qrcode().await?
                {
                    tokio::fs::write("qrcode.png", &image_data)
                        .await
                        .expect("failed to write file");
                    image_sig = sig.clone();
                    tracing::info!("二维码: qrcode.png");
                }
            }
            QRCodeState::Confirmed(QRCodeConfirmed {
                ref tmp_pwd,
                ref tmp_no_pic_sig,
                ref tgt_qr,
                ..
            }) => {
                tracing::info!("二维码已确认");
                let mut login_resp = client.qrcode_login(tmp_pwd, tmp_no_pic_sig, tgt_qr).await?;
                if let LoginResponse::DeviceLockLogin { .. } = login_resp {
                    login_resp = client.device_lock_login().await?;
                }
                tracing::info!("{:?}", login_resp);
                break;
            }
            QRCodeState::Canceled => {
                panic!("二维码已取消")
            }
        }
        sleep(Duration::from_secs(5)).await;
        resp = client
            .query_qrcode_result(&image_sig)
            .await
            .expect("failed to query qrcode result");
    }

    after_login(&client).await;

    handle.await.unwrap();
    Ok(())
}
pub struct MyHandler;

#[async_trait::async_trait]
impl Handler for MyHandler {
    async fn handle(&self, e: QEvent) {
        match e {
            QEvent::GroupMessage(m) => {
                tracing::info!(
                    "MESSAGE (GROUP={}): {}",
                    m.inner.group_code,
                    m.inner.elements
                )
            }
            QEvent::FriendMessage(m) => {
                tracing::info!(
                    "MESSAGE (FRIEND={}): {}",
                    m.inner.from_uin,
                    m.inner.elements
                )
            }
            QEvent::GroupMessageRecall(e) => {
                tracing::info!("GroupMessageRecall {:?}", e);
            }
            QEvent::FriendMessageRecall(e) => {
                tracing::info!("FriendMessageRecall {:?}", e);
            }
            // _ => {}
            _ => tracing::info!("{:?}", e),
        }
    }
}
[package]
name = "qrcode_login"
version = "0.1.0"
edition = "2021"

[dependencies]
ricq = "0.1.17"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
async-trait = "*"
serde_json = "1"
bytes = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt"] }

偶尔会出现 Worker Panic

thread 'tokio-runtime-worker' panicked at 'index out of bounds: the len is 1 but the index is 1', /home/kkocdko/.cargo/git/checkouts/ricq-6bb4bd71a72b13ed/bd07155/ricq-core/src/msg/elem/market_face.rs:92:20
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

不清楚怎样触发,版本是 commit bd07155。也许加个判断就好了吧?

Feature: Support resolving `sms_phone`

diff --git a/ricq-core/src/command/wtlogin/mod.rs b/ricq-core/src/command/wtlogin/mod.rs
index 4d2f03d..f710a4f 100644
--- a/ricq-core/src/command/wtlogin/mod.rs
+++ b/ricq-core/src/command/wtlogin/mod.rs
@@ -164,20 +164,32 @@ impl LoginResponse {
                 }),
             }),
             40 => LoginResponse::AccountFrozen,
-            160 | 239 => LoginResponse::DeviceLocked(LoginDeviceLocked {
-                // TODO?
-                sms_phone: tlv_map.remove(&0x178).map(|_| "todo".into()),
-                verify_url: tlv_map
-                    .remove(&0x204)
-                    .map(|v| String::from_utf8_lossy(&v).into_owned()),
-                message: tlv_map
-                    .remove(&0x17e)
-                    .map(|v| String::from_utf8_lossy(&v).into_owned()),
-                rand_seed: tlv_map.remove(&0x403),
-                t104: tlv_map.remove(&0x104),
-                t174: tlv_map.remove(&0x174),
-                t402: tlv_map.remove(&0x402),
-            }),
+            160 | 239 => {
+                let t174 = tlv_map.remove(&0x174);
+                let t178 = tlv_map.remove(&0x178);
+                let sms_phone = if t174.is_some() {
+                    t178.map(|mut v| {
+                        let country_code = v.read_string_short();
+                        let phone_number = v.read_string_short();
+                        format!("+{} {}", country_code, phone_number)
+                    })
+                } else {
+                    None
+                };
+                LoginResponse::DeviceLocked(LoginDeviceLocked {
+                    sms_phone,
+                    verify_url: tlv_map
+                        .remove(&0x204)
+                        .map(|v| String::from_utf8_lossy(&v).into_owned()),
+                    message: tlv_map
+                        .remove(&0x17e)
+                        .map(|v| String::from_utf8_lossy(&v).into_owned()),
+                    rand_seed: tlv_map.remove(&0x403),
+                    t104: tlv_map.remove(&0x104),
+                    t174,
+                    t402: tlv_map.remove(&0x402),
+                })
+            }
             162 => LoginResponse::TooManySMSRequest,
             204 => LoginResponse::DeviceLockLogin(LoginDeviceLockLogin {
                 t104: tlv_map.remove(&0x104),

Not tested

解散 QQ 群时触发了这个

thread 'tokio-runtime-worker' panicked at 'called Result::unwrap() on an Err value: Decode("decode_online_push_trans_packet unknown error")', E:\walle-q\rs-qq\rs-qq\src\client\processor\mod.rs:120:26

`impl Trait` in associated types is unstable

编译提示如下错误:

error[E0658]: `impl Trait` in associated types is unstable
   .../ricq-core-0.1.19/src/msg/mod.rs:101:19
    |
101 |   type IntoIter = impl Iterator<Item = RQElem> + 'static;
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information
    = help: add `#![feature(impl_trait_in_assoc_type)]` to the crate attributes to enable

For more information about this error, try `rustc --explain E0658`.
error: could not compile `ricq-core` (lib) due to previous error

编译器版本:nightly-x86_64_linux_1.72

关于下次发版的建议

语义化版本

当前,依赖 ricq 的项目里总是要特意写成 ricq = "=0.1.17" 来保证构建成功,比较麻烦。可以使用语义化版本,下个版本发布为 0.2.X,如果有 breaking change 就跳到 0.3.X

Cargo.toml 中同步修改

版本号,以及最近的 License 切换 (三个 Cargo.toml 中的 license = "AGPL-3.0" 需要修改)。

ricq/ricq/Cargo.toml

Lines 16 to 17 in 373522b

[dependencies]
ricq-core = { version = "0.1.16", path = "../ricq-core" }

[dependencies.ricq]
version = "0.1.17"
path = "../ricq"
[dependencies.ricq-core]
version = "0.1.17"
path = "../ricq-core"

希望增加对文件的支持

希望支持:群文件上传下载,私聊文件上传下载。

场景:苹果的安装包签名,我希望给用户提供签名服务,但是不提供我的秘钥,用户发安装包给机器人,机器人签好名后发回。

获取朋友信息错误

我在wall-q中使用相关接口出现了异常:

{
    "status": "failed",
    "retcode": 34001,
    "data": null,
    "message": "ricq错误:failed to decode, missing SummaryCard.RespSummaryCard",
    "echo": null
}

确认后可能是ricq的协议错误

如何回复群消息

有提供api对消息进行回复吗, 没有的话有能提供的大致实现方案能让我实现吗

群成员列表中, 群主permission为Member而不是Owner

我想知道某个群成员到底是什么角色, 所以将members收集成map, 然后通过key获取.
但是多次测试发现, 发现群主和普通成员permission为Member, 管理员为Administrator

let mut members = HashMap::<i64, &GroupMemberInfo>::new();
let group: Arc<Group>;
let lock = group.members.read().await;
for x in lock.deref() {
    members.insert(x.uin, x);
}
print!("{:?}", members);

Error: 当前QQ版本过低,请升级至最新版本后再登录。点击进入下载页面

os info: archlinux docker
stderr :

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: 不能解析的登录响应: 235, {1288: b"\x01\0\0\x03\xe8\0\x1b\x02\0\0\0\x10 \x03\xca2\x08\x10\0\0\0\xeb\0\0\0\0\x0c\\\x17\xb7\0\0\0\xeb"}, "当前QQ版本过低,请升级至最新版本后再登录。点击进入下载页面"', src/main.rs:30:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

device: MACOS,IPAD,ANDROID_PHONE均已经尝试
源码:https://github.com/Caviar-X/qq_bot

如何运行

你好,请问这个项目如何运行呢,

pb:D88dGroupInfo 的错误使用

GraiaProject/Ichika#83 中, 提到 get_group_info 的返回中 Group.mute_timestampGroup.global_mute_timestamp 在实际中无论禁言情况如何将始终为 0 的问题, 经核实, decode_group_info_response 中错误的使用 pb:D88dGroupInfo 有关.

这一 API 具有局限性, 缓解方式已提出.

  • wybxc/CAI 中使用 jce:StTroopNum 获取
  • mamoe/mirai 中亦从 jce 中获取
  • Mrs4s/MiraiGo 根本不存在 pb:D88dGroupInfo 的声明与使用

建议参考现有其他协议实现修补.

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.