Skip to content

Commit

Permalink
Update to pcqq protocal 9.7.5
Browse files Browse the repository at this point in the history
  • Loading branch information
DawnNights committed Mar 22, 2023
1 parent c7ac125 commit e62326f
Show file tree
Hide file tree
Showing 89 changed files with 2,937 additions and 4,050 deletions.
46 changes: 4 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,9 @@
## 简介

PS: 因协议版本已过期, 此版本协议库已无法使用
因为之前用的轻聊版协议失效, 所以基于最新的 PCQQ9.7.5 协议重新开发

PY-PCQQ 是一个基于 **QQ轻聊版 7.9** 客户端协议的 Python 异步 QQ 机器人支持库,它会对 QQ 服务端发出的协议包进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器。
依赖的第三方库有: `cryptography`, `httpx`, `pillow`

除了起到解析消息的作用,PY-PCQQ 还通过 装饰器、异步、回调等方式实现了一套简洁易用的会话机制和插件机制,以便于用户快速上手。
目前完善了一小部分, 可以 clone 本项目并运行 `example.py` 中的代码查看效果

PY-PCQQ 在其底层与 QQ 服务端实现交互的部分使用的是标准库 `asyncio.open_connection` 所创建的异步 TCP 连接,这意味着在本协议库提供的内容多为异步操作,在调用相关函数或方法时应注意加上 **await** 关键字。

本项目基本仅由 Python3 的标准库所实现,但若是在没有图形界面的系统中使用扫码登录,需要自行安装第三方库 `pillow` 使得程序能在终端环境中打印登录二维码。值得一提的是,在 Android 手机等移动设备中,你也可以通过 [pydroid3](https://apkdownloadforandroid.com/ru.iiec.pydroid3/) 这样的应用来安装运行本协议库,例如: [在手机上玩转QQ机器人?](https://b23.tv/ZVHP0lK)

最后要说的是,这个项目仅仅只是本废物空闲之余的兴趣之作,存在着大量不成熟与不完善的地方。如果对 QQ 机器人开发有所需求,可以移步至更加强大与稳定的 [mirai](https://github.com/mamoe/mirai)[go-cqhttp](https://github.com/Mrs4s/go-cqhttp/) 等项目。

## 已实现功能

#### 登录
- [x] 账号密码登录
- [x] 二维码登录
- [x] 本地Token重连

#### 发送消息
- [x] At
- [x] 文本
- [x] 表情
- [x] xml卡片
- [x] 图片

#### 接收消息
- [x] At
- [x] 文本
- [x] 图片
- [x] 表情

#### 接收事件
- [x] 群消息
- [x] 好友消息
- [x] 进群事件
- [x] 退群事件
- [x] 禁言事件

#### 其它操作
- [x] 修改群成员Card
- [x] 设置群成员禁言

## 文档
暂时咕咕中,请自行查看项目中的 `example.py` 查看案例
估摸着这个垃圾玩意也不会有什么用户, 所以本项目将一直处于佛系更新中
66 changes: 66 additions & 0 deletions core/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import os
import traceback

from socket import inet_aton
from functools import partial
from asyncio import (
Queue,
new_event_loop,
run_coroutine_threadsafe
)

from core.entities import (
Packet,
QQStruct,
PacketManger,
)

from core import croto, const

from core.utils import (
UDPSocket,
rand_udp_host
)


class QQClient:
def __init__(self, uin: int = 0):
self.loop = new_event_loop()

self.sock = UDPSocket(
host=rand_udp_host(),
port=const.UDP_PORT,
)
self.manage = PacketManger()

root = os.path.join(os.getcwd(), "data")
if uin != 0:
root = os.path.join(root, str(uin))

if not os.path.exists(root):
os.makedirs(root)

self.stru = QQStruct(
path=root,
ecdh=croto.ECDH(),
addr=(self.sock.host, self.sock.port),
server_ip=inet_aton(self.sock.host)
)

self.run_task = self.loop.run_until_complete
self.add_task = partial(run_coroutine_threadsafe, loop=self.loop)

def send(self, packet: Packet):
data = packet.encode()
self.sock.send(data)

def recv(self, tea_key: bytes):
data = self.sock.recv()
return Packet.from_raw(data, tea_key)

async def recv_and_exec(self, tea_key: bytes):
try:
packet = self.recv(tea_key)
await self.manage.exec_all(packet)
except Exception as err:
print('发生异常: ', traceback.format_exc())
29 changes: 29 additions & 0 deletions core/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
__bytes = bytes.fromhex

Header = "02 3B 41"
Tail = "03"

StructVersion = __bytes("03 00 00 00 01 01 01 00 00 6A 9C 00 00 00 00")
BodyVersion = __bytes("02 00 00 00 01 01 01 00 00 6A 9C")
FuncVersion = __bytes("04 00 00 00 01 01 01 00 00 6A 9C 00 00 00 00 00 00 00 00")
VMainVer = __bytes("3B 41")

EcdhVersion = __bytes("01 03")
DWQDVersion = __bytes("04 04 04 00")
SsoVersion = __bytes("00 00 04 61")
ClientVersion = __bytes("00 00 17 41")

RandKey = __bytes("66 D0 9F 63 A2 37 02 27 13 17 3B 1E 01 1C A9 DA")
ServiceId = __bytes("00 00 00 01")
DeviceID = __bytes("EE D2 37 A4 94 D3 7A 04 7D 98 18 E8 EE DF B0 D6 96 B3 A3 1C BB 4F 95 6A 3E 6C EE F5 02 C5 5A 1F")

TCP_PORT = 443
UDP_PORT = 8000
HeartBeatInterval = 30.0

StateOnline = 10 # 上线
StateLeave = 30 # 离开
StateInvisible = 40 # 隐身
StateBusy = 50 # 忙碌
StateCallMe = 60 # Q我吧
StateUndisturb = 70 # 请勿打扰
17 changes: 17 additions & 0 deletions core/croto/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from .ecdh import ECDH

from .gid import gid_from_group

from .hash import (
md5,
sha256,
sha512,
sha1024,
rand_str,
rand_str2,
sub_16F90
)

from .offical import create_official

from .qqtea import tea_encrypt, tea_decrypt
37 changes: 37 additions & 0 deletions core/croto/ecdh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from cryptography.hazmat.bindings._openssl import ffi, lib


class ECDH:
def __init__(self):
self.public_key = bytes(25)
self.share_key = bytes(16)

self.ec_key = lib.EC_KEY_new_by_curve_name(711)
self.group = lib.EC_KEY_get0_group(self.ec_key)
self.point = lib.EC_POINT_new(self.group)

if lib.EC_KEY_generate_key(self.ec_key) == 1:
lib.EC_POINT_point2oct(self.group, lib.EC_KEY_get0_public_key(
self.ec_key), 2, self.public_key, len(self.public_key), ffi.NULL)

buf = bytes([
4, 191, 71, 161, 207, 120, 166,
41, 102, 139, 11, 195, 159, 142,
84, 201, 204, 243, 182, 56, 75,
8, 184, 174, 236, 135, 218, 159,
48, 72, 94, 223, 231, 103, 150,
157, 193, 163, 175, 17, 21, 254,
13, 204, 142, 11, 23, 202, 207
])
if lib.EC_POINT_oct2point(self.group, self.point, buf, len(buf), ffi.NULL) == 1:
lib.ECDH_compute_key(self.share_key, len(
self.share_key), self.point, self.ec_key, ffi.NULL)

def twice(self, tk_key: bytes):
twice_key = bytes(16)

if lib.EC_POINT_oct2point(self.group, self.point, tk_key, len(tk_key), ffi.NULL) == 1:
lib.ECDH_compute_key(twice_key, len(twice_key),
self.point, self.ec_key, ffi.NULL)

return twice_key
40 changes: 40 additions & 0 deletions core/croto/gid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
def gid_from_group(group_id: int) -> int:
group = str(group_id)
left = int(group[0:-6])

if left >= 0 and left <= 10:
right = group[-6:]
gid = str(left + 202) + right
elif left >= 11 and left <= 19:
right = group[-6:]
gid = str(left + 469) + right
elif left >= 20 and left <= 66:
left = int(str(left)[0:1])
right = group[-7:]
gid = str(left + 208) + right
elif left >= 67 and left <= 156:
right = group[-6:]
gid = str(left + 1943) + right
elif left >= 157 and left <= 209:
left = int(str(left)[0:2])
right = group[-7:]
gid = str(left + 199) + right
elif left >= 210 and left <= 309:
left = int(str(left)[0:2])
right = group[-7:]
gid = str(left + 389) + right
elif left >= 310 and left <= 335:
left = int(str(left)[0:2])
right = group[-7:]
gid = str(left + 349) + right
elif left >= 336 and left <= 386:
left = int(str(left)[0:3])
right = group[-6:]
gid = str(left + 2265) + right
elif left >= 387 and left <= 499:
left = int(str(left)[0:3])
right = group[-6:]
gid = str(left + 3490) + right
elif left >= 500:
return int(group)
return int(gid)
Loading

0 comments on commit e62326f

Please sign in to comment.