-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c7ac125
commit e62326f
Showing
89 changed files
with
2,937 additions
and
4,050 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` 查看案例 | ||
估摸着这个垃圾玩意也不会有什么用户, 所以本项目将一直处于佛系更新中 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 # 请勿打扰 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.