├── .editorconfig
├── .gitignore
├── .readthedocs.yaml
├── LICENSE
├── README.md
├── cai
├── __init__.py
├── api
│ ├── __init__.py
│ ├── client.py
│ ├── flow.py
│ ├── friend.py
│ ├── group.py
│ └── login.py
├── client
│ ├── __init__.py
│ ├── client.py
│ ├── command.py
│ ├── config_push
│ │ ├── __init__.py
│ │ ├── command.py
│ │ └── jce.py
│ ├── event.py
│ ├── friendlist
│ │ ├── __init__.py
│ │ ├── command.py
│ │ └── jce.py
│ ├── heartbeat
│ │ └── __init__.py
│ ├── message_service
│ │ ├── __init__.py
│ │ ├── command.py
│ │ ├── decoders.py
│ │ ├── jce.py
│ │ └── models.py
│ ├── models.py
│ ├── online_push
│ │ ├── __init__.py
│ │ ├── command.py
│ │ └── jce.py
│ ├── packet.py
│ ├── qq_service
│ │ ├── __init__.py
│ │ └── jce.py
│ ├── sso_server
│ │ ├── __init__.py
│ │ └── jce.py
│ ├── status_service
│ │ ├── __init__.py
│ │ ├── command.py
│ │ └── jce.py
│ └── wtlogin
│ │ ├── __init__.py
│ │ ├── oicq.py
│ │ └── tlv.py
├── connection
│ ├── __init__.py
│ └── utils.py
├── exceptions.py
├── log.py
├── pb
│ ├── __init__.py
│ ├── im
│ │ ├── __init__.py
│ │ ├── msg
│ │ │ ├── __init__.py
│ │ │ ├── common
│ │ │ │ ├── __init__.py
│ │ │ │ ├── common.proto
│ │ │ │ ├── common_pb2.py
│ │ │ │ └── common_pb2.pyi
│ │ │ ├── msg
│ │ │ │ ├── __init__.py
│ │ │ │ ├── msg.proto
│ │ │ │ ├── msg_pb2.py
│ │ │ │ └── msg_pb2.pyi
│ │ │ ├── msg_body
│ │ │ │ ├── __init__.py
│ │ │ │ ├── msg_body.proto
│ │ │ │ ├── msg_body_pb2.py
│ │ │ │ └── msg_body_pb2.pyi
│ │ │ ├── msg_head
│ │ │ │ ├── __init__.py
│ │ │ │ ├── msg_head.proto
│ │ │ │ ├── msg_head_pb2.py
│ │ │ │ └── msg_head_pb2.pyi
│ │ │ ├── obj_msg
│ │ │ │ ├── __init__.py
│ │ │ │ ├── obj_msg.proto
│ │ │ │ ├── obj_msg_pb2.py
│ │ │ │ └── obj_msg_pb2.pyi
│ │ │ ├── receipt
│ │ │ │ ├── __init__.py
│ │ │ │ ├── receipt.proto
│ │ │ │ ├── receipt_pb2.py
│ │ │ │ └── receipt_pb2.pyi
│ │ │ └── service
│ │ │ │ └── comm_elem
│ │ │ │ ├── __init__.py
│ │ │ │ ├── comm_elem.proto
│ │ │ │ ├── comm_elem_pb2.py
│ │ │ │ └── comm_elem_pb2.pyi
│ │ └── oidb
│ │ │ ├── __init__.py
│ │ │ ├── cmd0x769
│ │ │ ├── __init__.py
│ │ │ ├── cmd0x769.proto
│ │ │ ├── cmd0x769_pb2.py
│ │ │ └── cmd0x769_pb2.pyi
│ │ │ └── cmd0xd50
│ │ │ ├── __init__.py
│ │ │ ├── cmd0xd50.proto
│ │ │ ├── cmd0xd50_pb2.py
│ │ │ └── cmd0xd50_pb2.pyi
│ ├── msf
│ │ ├── __init__.py
│ │ └── msg
│ │ │ ├── __init__.py
│ │ │ ├── comm
│ │ │ ├── __init__.py
│ │ │ ├── comm.proto
│ │ │ ├── comm_pb2.py
│ │ │ └── comm_pb2.pyi
│ │ │ ├── ctrl
│ │ │ ├── __init__.py
│ │ │ ├── ctrl.proto
│ │ │ ├── ctrl_pb2.py
│ │ │ └── ctrl_pb2.pyi
│ │ │ ├── onlinepush
│ │ │ ├── __init__.py
│ │ │ ├── onlinepush.proto
│ │ │ ├── onlinepush_pb2.py
│ │ │ └── onlinepush_pb2.pyi
│ │ │ └── svc
│ │ │ ├── __init__.py
│ │ │ ├── svc.proto
│ │ │ ├── svc_pb2.py
│ │ │ └── svc_pb2.pyi
│ └── wtlogin
│ │ ├── __init__.py
│ │ ├── data.proto
│ │ ├── data_pb2.py
│ │ └── data_pb2.pyi
├── py.typed
├── settings
│ ├── __init__.py
│ ├── device.py
│ └── protocol.py
├── storage
│ ├── __init__.py
│ └── utils.py
└── utils
│ ├── __init__.py
│ ├── binary.py
│ ├── binary.pyi
│ ├── coroutine.py
│ ├── crypto.py
│ ├── dataclass.py
│ ├── future.py
│ └── jce.py
├── docs
├── assets
│ ├── logo.png
│ ├── logo_text.png
│ ├── logo_text_white.png
│ └── logo_white.png
├── conf.py
├── examples
│ └── index.rst
├── index.rst
├── py-modindex.rst
└── source
│ ├── cai.api.rst
│ ├── cai.client.config_push.rst
│ ├── cai.client.friendlist.rst
│ ├── cai.client.heartbeat.rst
│ ├── cai.client.message_service.rst
│ ├── cai.client.online_push.rst
│ ├── cai.client.qq_service.rst
│ ├── cai.client.rst
│ ├── cai.client.sso_server.rst
│ ├── cai.client.status_service.rst
│ ├── cai.client.wtlogin.rst
│ ├── cai.connection.rst
│ ├── cai.pb.im.msg.common.rst
│ ├── cai.pb.im.msg.msg.rst
│ ├── cai.pb.im.msg.msg_body.rst
│ ├── cai.pb.im.msg.msg_head.rst
│ ├── cai.pb.im.msg.obj_msg.rst
│ ├── cai.pb.im.msg.receipt.rst
│ ├── cai.pb.im.msg.rst
│ ├── cai.pb.im.oidb.cmd0x769.rst
│ ├── cai.pb.im.oidb.cmd0xd50.rst
│ ├── cai.pb.im.oidb.rst
│ ├── cai.pb.im.rst
│ ├── cai.pb.msf.msg.comm.rst
│ ├── cai.pb.msf.msg.ctrl.rst
│ ├── cai.pb.msf.msg.onlinepush.rst
│ ├── cai.pb.msf.msg.rst
│ ├── cai.pb.msf.msg.svc.rst
│ ├── cai.pb.msf.rst
│ ├── cai.pb.rst
│ ├── cai.pb.wtlogin.rst
│ ├── cai.rst
│ ├── cai.settings.rst
│ ├── cai.storage.rst
│ ├── cai.utils.rst
│ └── modules.rst
├── examples
├── friend_group.py
├── login.py
├── message.py
└── set_status.py
├── poetry.lock
├── pyproject.toml
└── tests
├── client
├── __init__.py
├── test_login.py
└── test_sso_server.py
└── connection
├── __init__.py
└── test_connection.py
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | # The JSON files contain newlines inconsistently
15 | [*.json]
16 | insert_final_newline = ignore
17 |
18 | # Minified JavaScript files shouldn't be changed
19 | [**.min.js]
20 | indent_style = ignore
21 | insert_final_newline = ignore
22 |
23 | # Makefiles always use tabs for indentation
24 | [Makefile]
25 | indent_style = tab
26 |
27 | # Batch files use tabs for indentation
28 | [*.bat]
29 | indent_style = tab
30 |
31 | [*.md]
32 | trim_trailing_whitespace = false
33 |
34 | # Matches the exact files either package.json or .travis.yml
35 | [{package.json,.travis.yml}]
36 | indent_size = 2
37 |
38 | [{*.py,*.pyi}]
39 | indent_size = 4
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
88 | __pypackages__/
89 |
90 | # Celery stuff
91 | celerybeat-schedule
92 | celerybeat.pid
93 |
94 | # SageMath parsed files
95 | *.sage.py
96 |
97 | # Environments
98 | .env
99 | .venv
100 | env/
101 | venv/
102 | ENV/
103 | env.bak/
104 | venv.bak/
105 |
106 | # Spyder project settings
107 | .spyderproject
108 | .spyproject
109 |
110 | # Rope project settings
111 | .ropeproject
112 |
113 | # mkdocs documentation
114 | /site
115 |
116 | # mypy
117 | .mypy_cache/
118 | .dmypy.json
119 | dmypy.json
120 |
121 | # Pyre type checker
122 | .pyre/
123 |
124 | # Visual Studio Code
125 | .vscode/*
126 | # !.vscode/settings.json
127 | # !.vscode/tasks.json
128 | # !.vscode/launch.json
129 | # !.vscode/extensions.json
130 | *.code-workspace
131 |
132 | # Local History for Visual Studio Code
133 | .history/
134 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | python:
4 | version: 3.8
5 | install:
6 | - method: pip
7 | path: .
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | _✨ Yet Another Bot Framework for Tencent QQ Written in Python ✨_
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ---
25 |
26 | ## 声明
27 |
28 | ### 一切开发旨在学习,请勿用于非法用途
29 |
30 | - `CAI` 是完全免费且开放源代码的软件,仅供学习和娱乐用途使用
31 | - `CAI` 不会通过任何方式强制收取费用,或对使用者提出物质条件
32 |
33 | ### 许可证
34 |
35 | `CAI` 采用 [AGPLv3](LICENSE) 协议开源,不鼓励、不支持一切商业使用。
36 |
37 | ---
38 |
39 | ## 特色
40 |
41 | - 简单易用的 API,支持多账号
42 | - 极少的额外依赖
43 | - 异步编写,效率++
44 |
45 | - 使用 [Asyncio Stream](https://docs.python.org/3/library/asyncio-stream.html) 处理网络连接 ([cai.connection.Connection](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.connection.html#cai.connection.Connection))
46 | - 使用 [Asyncio Future](https://docs.python.org/3/library/asyncio-future.html) 处理收发包 ([cai.utils.future.FutureStore](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.utils.html#cai.utils.future.FutureStore))
47 |
48 | - 完整的 [Type Hints](https://www.python.org/dev/peps/pep-0484/)
49 |
50 | - Packet Query 支持 [Variadic Generics](https://www.python.org/dev/peps/pep-0646/)
51 |
52 | ```python
53 | from cai.utils.binary import Packet
54 | packet = Packet(bytes.fromhex("01000233000000"))
55 | packet.start().int8().uint16().bytes(4).execute()
56 | # return type: INT8, UINT16, BYTES
57 | ```
58 |
59 | - 便携的 JceStruct 定义 (使用方法参考 [JceStruct](https://github.com/yanyongyu/JceStruct))
60 |
61 | ```python
62 | from typing import Optional
63 | from jce import JceStruct, JceField, types
64 |
65 | class CustomStruct(JceStruct):
66 | int32_field: types.INT32 = JceField(jce_id=0)
67 | optional_field: Optional[types.DOUBLE] = JceField(None, jce_id=1)
68 | nested_field: OtherStruct = JceField(jce_id=2)
69 | ```
70 |
71 | ## 功能
72 |
73 | `CAI` 仅作为底层协议库使用,将协议封装为 API。
74 |
75 | > `CAI` 不会支持涉及 **金钱**、**主动邀请**、**获取凭证** 等敏感操作的协议。
76 |
77 |
78 | 已支持的协议列表:
79 |
80 | ### 登录
81 |
82 | [`cai.api.login` API Reference](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.api.html#module-cai.api.login)
83 |
84 | - [x] 账号密码登录
85 | - [x] 设备锁验证
86 | - [x] 图片验证码提交
87 | - [x] 短信验证码提交
88 | - [ ] 扫码登录
89 |
90 | ### 客户端
91 |
92 | [`cai.api.client` API Reference](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.api.html#module-cai.api.client)
93 |
94 | - [x] 设置在线状态
95 |
96 | ### 好友
97 |
98 | [`cai.api.friend` API Reference](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.api.html#module-cai.api.friend)
99 |
100 | - [x] 获取好友列表
101 | - [x] 获取好友信息
102 | - [x] 获取好友分组列表
103 | - [x] 获取好友分组信息
104 |
105 | ### 群组
106 |
107 | [`cai.api.group` API Reference](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.api.html#module-cai.api.group)
108 |
109 | - [x] 获取群列表
110 | - [x] 获取群信息
111 | - [x] 获取群成员列表
112 |
113 | ### 事件
114 |
115 | [`cai.api.flow` API Reference](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.api.html#module-cai.api.flow)
116 |
117 | 通过注册事件监听回调,在事件发生时执行指定操作。事件类型可通过 `cai.client` 模块导入。
118 |
119 | - [x] 好友消息 ([PrivateMessage](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.client.message_service.html#cai.client.message_service.models.PrivateMessage))
120 | - [x] 群消息 ([GroupMessage](https://cai-bot.readthedocs.io/zh_CN/latest/source/cai.client.message_service.html#cai.client.message_service.models.GroupMessage))
121 |
122 |
123 |
124 | ## 文档
125 |
126 | _In Development_
127 |
128 | [See on Read The Docs](https://cai-bot.readthedocs.io/)
129 |
--------------------------------------------------------------------------------
/cai/__init__.py:
--------------------------------------------------------------------------------
1 | """Entry Module for CAI.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 | from .api import *
10 | from .exceptions import *
11 |
--------------------------------------------------------------------------------
/cai/api/__init__.py:
--------------------------------------------------------------------------------
1 | """Application APIs.
2 |
3 | This module wraps the client methods to provide easier control (high-level api).
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from typing import Dict
13 |
14 | from cai.client import Client
15 |
16 | _clients: Dict[int, Client] = {}
17 |
18 |
19 | from .flow import *
20 | from .group import *
21 | from .login import *
22 | from .client import *
23 | from .friend import *
24 |
--------------------------------------------------------------------------------
/cai/api/client.py:
--------------------------------------------------------------------------------
1 | """Application Client APIs.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 |
10 | import asyncio
11 | from typing import Union, Optional
12 |
13 | from cai.client import Client, OnlineStatus
14 | from cai.exceptions import ClientNotAvailable
15 |
16 | from . import _clients
17 |
18 |
19 | def get_client(uin: Optional[int] = None) -> Client:
20 | """Get the specific client or existing client.
21 |
22 | Args:
23 | uin (Optional[int], optional): Specific account client to get. Defaults to None.
24 |
25 | Raises:
26 | ClientNotAvailable: Client not exists.
27 | ClientNotAvailable: No client available.
28 | ClientNotAvailable: Multiple clients found and not specify which one to get.
29 |
30 | Returns:
31 | Client: Current client to use.
32 | """
33 | if not _clients:
34 | raise ClientNotAvailable(uin, f"No client available!")
35 | elif len(_clients) == 1 and not uin:
36 | return list(_clients.values())[0]
37 | else:
38 | if not uin:
39 | raise ClientNotAvailable(
40 | None, f"Multiple clients found! Specify uin to choose."
41 | )
42 | if uin not in _clients:
43 | raise ClientNotAvailable(None, f"Client {uin} not exists!")
44 | return _clients[uin]
45 |
46 |
47 | async def close(uin: Optional[int] = None) -> None:
48 | """Close an existing client and delete it from clients.
49 |
50 | Args:
51 | uin (Optional[int], optional): Account of the client want to close. Defaults to None.
52 | """
53 | client = get_client(uin)
54 | await client.close()
55 | del _clients[client.uin]
56 |
57 |
58 | async def close_all() -> None:
59 | """Close all existing clients and delete them."""
60 | tasks = [close(uin) for uin in _clients.keys()]
61 | await asyncio.gather(*tasks)
62 |
63 |
64 | async def set_status(
65 | status: Union[int, OnlineStatus],
66 | battery_status: Optional[int] = None,
67 | is_power_connected: bool = False,
68 | uin: Optional[int] = None,
69 | ) -> None:
70 | """Change client status.
71 |
72 | This function wraps the :meth:`~cai.client.client.Client.register`
73 | method of the client.
74 |
75 | Args:
76 | status (OnlineStatus): Status want to change.
77 | battery_status (Optional[int], optional): Battery capacity.
78 | Defaults to None.
79 | is_power_connected (bool, optional): Is power connected to phone.
80 | Defaults to False.
81 | uin (Optional[int], optional): Account of the client want to change.
82 | Defaults to None.
83 |
84 | Raises:
85 | RuntimeError: Client already exists and is running.
86 | RuntimeError: Password not provided when login a new account.
87 | ApiResponseError: Invalid API request.
88 | RegisterException: Register Failed.
89 | """
90 | client = get_client(uin)
91 | await client.set_status(
92 | status,
93 | battery_status,
94 | is_power_connected,
95 | )
96 |
97 |
98 | __all__ = ["get_client", "close", "close_all", "set_status"]
99 |
--------------------------------------------------------------------------------
/cai/api/flow.py:
--------------------------------------------------------------------------------
1 | """Application Flow APIs.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 |
10 | from typing import Callable, Optional, Awaitable
11 |
12 | from cai.log import logger
13 | from cai.client import HANDLERS, Event, Client, Command, IncomingPacket
14 |
15 | from .client import get_client
16 |
17 |
18 | def add_event_listener(
19 | listener: Callable[[Client, Event], Awaitable[None]],
20 | uin: Optional[int] = None,
21 | ):
22 | """Add event listener.
23 |
24 | If uin is ``None``, listener will receive events from all clients.
25 |
26 | Args:
27 | listener (Callable[[Client, Event], Awaitable[None]]): Event listener.
28 | uin (Optional[int], optional): Account of the client want to listen.
29 | Defaults to None.
30 | """
31 | if uin:
32 | client = get_client(uin)
33 | client.add_event_listener(listener)
34 | else:
35 | Client.LISTENERS.add(listener)
36 |
37 |
38 | def register_packet_handler(
39 | cmd: str,
40 | packet_handler: Callable[[Client, IncomingPacket], Awaitable[Command]],
41 | ) -> None:
42 | """Register custom packet handler.
43 |
44 | Note:
45 | This function is a low-level api to mock default behavior.
46 | Be aware of what you are doing!
47 |
48 | Args:
49 | cmd (str): Command name of the packet.
50 | packet_handler (Callable[[Client, IncomingPacket], Awaitable[Command]]):
51 | Asynchronous packet handler. A :obj:`~cai.client.command.Command`
52 | object should be returned.
53 | """
54 | if cmd in HANDLERS:
55 | logger.warning(
56 | f"You are overwriting an existing handler for command {cmd}!"
57 | )
58 | HANDLERS[cmd] = packet_handler
59 |
60 |
61 | __all__ = ["add_event_listener", "register_packet_handler"]
62 |
--------------------------------------------------------------------------------
/cai/api/friend.py:
--------------------------------------------------------------------------------
1 | """Application Friend APIs.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 |
10 | from typing import List, Optional
11 |
12 | from cai.client import Friend, FriendGroup
13 |
14 | from .client import get_client
15 |
16 |
17 | async def get_friend(
18 | friend_uin: int, cache: bool = True, uin: Optional[int] = None
19 | ) -> Optional[Friend]:
20 | """Get account friend.
21 |
22 | This function wraps the :meth:`~cai.client.client.Client.get_friend`
23 | method of the client.
24 |
25 | Args:
26 | friend_uin (int): Friend account uin.
27 | cache (bool, optional): Use cached friend list. Defaults to True.
28 | uin (Optional[int], optional): Account of the client want to use.
29 | Defaults to None.
30 |
31 | Returns:
32 | Friend: Friend object.
33 | None: Friend not exists.
34 |
35 | Raises:
36 | RuntimeError: Error response type got. This should not happen.
37 | ApiResponseError: Get friend list failed.
38 | FriendListException: Get friend list returned non-zero ret code.
39 | """
40 | client = get_client(uin)
41 | return await client.get_friend(friend_uin, cache)
42 |
43 |
44 | async def get_friend_list(
45 | cache: bool = True, uin: Optional[int] = None
46 | ) -> List[Friend]:
47 | """Get account friend list.
48 |
49 | This function wraps the :meth:`~cai.client.client.Client.get_friend_list`
50 | method of the client.
51 |
52 | Args:
53 | cache (bool, optional): Use cached friend list. Defaults to True.
54 | uin (Optional[int], optional): Account of the client want to use.
55 | Defaults to None.
56 |
57 | Returns:
58 | List of :obj:`~cai.client.models.Friend`
59 |
60 | Raises:
61 | RuntimeError: Error response type got. This should not happen.
62 | ApiResponseError: Get friend list failed.
63 | FriendListException: Get friend list returned non-zero ret code.
64 | """
65 | client = get_client(uin)
66 | return await client.get_friend_list(cache)
67 |
68 |
69 | async def get_friend_group(
70 | group_id: int, cache: bool = True, uin: Optional[int] = None
71 | ) -> Optional[FriendGroup]:
72 | """Get Friend Group.
73 |
74 | This function wraps the :meth:`~cai.client.client.Client.get_friend_group`
75 | method of the client.
76 |
77 | Args:
78 | group_id (int): Friend group id.
79 | cache (bool, optional): Use cached friend group list. Defaults to True.
80 | uin (Optional[int], optional): Account of the client want to use.
81 | Defaults to None.
82 |
83 | Returns:
84 | FriendGroup: Friend group object.
85 | None: Friend group not exists.
86 |
87 | Raises:
88 | RuntimeError: Error response type got. This should not happen.
89 | ApiResponseError: Get friend list failed.
90 | FriendListException: Get friend list returned non-zero ret code.
91 | """
92 | client = get_client(uin)
93 | return await client.get_friend_group(group_id, cache)
94 |
95 |
96 | async def get_friend_group_list(
97 | cache: bool = True, uin: Optional[int] = None
98 | ) -> List[FriendGroup]:
99 | """Get account friend group list.
100 |
101 | This function wraps the :meth:`~cai.client.client.Client.get_friend_group_list`
102 | method of the client.
103 |
104 | Args:
105 | cache (bool, optional): Use cached friend group list. Defaults to True.
106 | uin (Optional[int], optional): Account of the client want to use.
107 | Defaults to None.
108 |
109 | Returns:
110 | List[FriendGroup]: Friend group list.
111 |
112 | Raises:
113 | RuntimeError: Error response type got. This should not happen.
114 | ApiResponseError: Get friend group list failed.
115 | FriendListException: Get friend group list returned non-zero ret code.
116 | """
117 | client = get_client(uin)
118 | return await client.get_friend_group_list(cache)
119 |
120 |
121 | __all__ = [
122 | "get_friend",
123 | "get_friend_list",
124 | "get_friend_group",
125 | "get_friend_group_list",
126 | ]
127 |
--------------------------------------------------------------------------------
/cai/api/group.py:
--------------------------------------------------------------------------------
1 | """Application Group APIs.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 |
10 | from typing import List, Union, Optional
11 |
12 | from cai.client import Group, GroupMember
13 |
14 | from .client import get_client
15 |
16 |
17 | async def get_group(
18 | group_id: int, cache: bool = True, uin: Optional[int] = None
19 | ) -> Optional[Group]:
20 | """Get Group.
21 |
22 | This function wraps the :meth:`~cai.client.client.Client.get_group`
23 | method of the client.
24 |
25 | Args:
26 | group_id (int): Group id.
27 | cache (bool, optional): Use cached friend group list. Defaults to True.
28 | uin (Optional[int], optional): Account of the client want to use.
29 | Defaults to None.
30 |
31 | Returns:
32 | Group: Group object.
33 | None: Group not exists.
34 |
35 | Raises:
36 | RuntimeError: Error response type got. This should not happen.
37 | ApiResponseError: Get friend list failed.
38 | FriendListException: Get friend list returned non-zero ret code.
39 | """
40 | client = get_client(uin)
41 | return await client.get_group(group_id, cache)
42 |
43 |
44 | async def get_group_list(
45 | cache: bool = True, uin: Optional[int] = None
46 | ) -> List[Group]:
47 | """Get account group list.
48 |
49 | This function wraps the :meth:`~cai.client.client.Client.get_group_list`
50 | method of the client.
51 |
52 | Args:
53 | cache (bool, optional): Use cached group list. Defaults to True.
54 | uin (Optional[int], optional): Account of the client want to use.
55 | Defaults to None.
56 |
57 | Returns:
58 | List[Group]: Group list.
59 |
60 | Raises:
61 | RuntimeError: Error response type got. This should not happen.
62 | ApiResponseError: Get group list failed.
63 | GroupListException: Get group list returned non-zero ret code.
64 | """
65 | client = get_client(uin)
66 | return await client.get_group_list(cache)
67 |
68 |
69 | async def get_group_member_list(
70 | group: Union[int, Group], cache: bool = True, uin: Optional[int] = None
71 | ) -> Optional[List[GroupMember]]:
72 | """Get account group member list.
73 |
74 | This function wraps the :meth:`~cai.client.client.Client.get_group_member_list`
75 | method of the client.
76 |
77 | Args:
78 | group (Union[int, Group]): Group id or group object want to get members.
79 | cache (bool, optional): Use cached group list. Defaults to True.
80 | uin (Optional[int], optional): Account of the client want to use.
81 | Defaults to None.
82 |
83 | Returns:
84 | List[GroupMember]: Group member list.
85 | None: Group not exists.
86 |
87 | Raises:
88 | RuntimeError: Error response type got. This should not happen.
89 | ApiResponseError: Get group list failed.
90 | GroupMemberListException: Get group member list returned non-zero ret code.
91 | """
92 | client = get_client(uin)
93 | return await client.get_group_member_list(group, cache)
94 |
95 |
96 | __all__ = ["get_group", "get_group_list", "get_group_member_list"]
97 |
--------------------------------------------------------------------------------
/cai/api/login.py:
--------------------------------------------------------------------------------
1 | """Application Login APIs.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 |
10 | from typing import Optional
11 |
12 | from cai.client import Client
13 | from cai.exceptions import LoginException
14 |
15 | from . import _clients
16 | from .client import get_client
17 |
18 |
19 | async def login(uin: int, password_md5: Optional[bytes] = None) -> Client:
20 | """Create a new client (or use an existing one) and login.
21 |
22 | Password md5 should be provided when login a new account.
23 |
24 | This function wraps the :meth:`~cai.client.client.Client.login` method of the client.
25 |
26 | Args:
27 | uin (int): QQ account number.
28 | password_md5 (Optional[bytes], optional): md5 bytes of the password. Defaults to None.
29 |
30 | Raises:
31 | RuntimeError: Client already exists and is running.
32 | RuntimeError: Password not provided when login a new account.
33 | LoginSliderException: Need slider ticket.
34 | LoginCaptchaException: Need captcha image.
35 | """
36 | if uin in _clients:
37 | client = _clients[uin]
38 | if client.connected:
39 | raise RuntimeError(f"Client {uin} already connected!")
40 | client._password_md5 = password_md5 or client._password_md5
41 | else:
42 | if not password_md5:
43 | raise RuntimeError(f"Password md5 needed for creating new client!")
44 | client = Client(uin, password_md5)
45 | _clients[uin] = client
46 |
47 | await client.reconnect()
48 | try:
49 | await client.login()
50 | except LoginException:
51 | raise
52 | except Exception:
53 | await client.close()
54 | raise
55 | return client
56 |
57 |
58 | async def submit_captcha(
59 | captcha: str, captcha_sign: bytes, uin: Optional[int] = None
60 | ) -> bool:
61 | """Submit captcha data to login.
62 |
63 | This function wraps the :meth:`~cai.client.client.Client.submit_captcha`
64 | method of the client.
65 |
66 | Args:
67 | captcha (str): Captcha data to submit.
68 | captcha_sign (bytes): Captcha sign received when login.
69 | uin (Optional[int], optional): Account of the client want to login.
70 | Defaults to None.
71 |
72 | Raises:
73 | RuntimeError: Client already exists and is running.
74 | RuntimeError: Password not provided when login a new account.
75 | LoginSliderException: Need slider ticket.
76 | LoginCaptchaException: Need captcha image.
77 | """
78 | client = get_client(uin)
79 | try:
80 | await client.submit_captcha(captcha, captcha_sign)
81 | except LoginException:
82 | raise
83 | except Exception:
84 | await client.close()
85 | raise
86 | return True
87 |
88 |
89 | async def submit_slider_ticket(ticket: str, uin: Optional[int] = None) -> bool:
90 | """Submit slider ticket to login.
91 |
92 | This function wraps the :meth:`~cai.client.client.Client.submit_slider_ticket`
93 | method of the client.
94 |
95 | Args:
96 | ticket (str): Slider ticket to submit.
97 | uin (Optional[int], optional): Account of the client want to login.
98 | Defaults to None.
99 |
100 | Raises:
101 | RuntimeError: Client already exists and is running.
102 | RuntimeError: Password not provided when login a new account.
103 | LoginSliderException: Need slider ticket.
104 | LoginCaptchaException: Need captcha image.
105 | """
106 | client = get_client(uin)
107 | try:
108 | await client.submit_slider_ticket(ticket)
109 | except LoginException:
110 | raise
111 | except Exception:
112 | await client.close()
113 | raise
114 | return True
115 |
116 |
117 | async def request_sms(uin: Optional[int] = None) -> bool:
118 | """Request sms code message to login.
119 |
120 | This function wraps the :meth:`~cai.client.client.Client.request_sms`
121 | method of the client.
122 |
123 | Args:
124 | uin (Optional[int], optional): Account of the client want to login.
125 | Defaults to None.
126 |
127 | Raises:
128 | RuntimeError: Client already exists and is running.
129 | RuntimeError: Password not provided when login a new account.
130 | LoginSMSRequestError: Too many SMS messages were sent.
131 | """
132 | client = get_client(uin)
133 | return await client.request_sms()
134 |
135 |
136 | async def submit_sms(sms_code: str, uin: Optional[int] = None) -> bool:
137 | """Submit sms code to login.
138 |
139 | This function wraps the :meth:`~cai.client.client.Client.submit_sms`
140 | method of the client.
141 |
142 | Args:
143 | sms_code (str): SMS code to submit.
144 | uin (Optional[int], optional): Account of the client want to login.
145 | Defaults to None.
146 |
147 | Raises:
148 | RuntimeError: Client already exists and is running.
149 | RuntimeError: Password not provided when login a new account.
150 | LoginSliderException: Need slider ticket.
151 | LoginCaptchaException: Need captcha image.
152 | """
153 | client = get_client(uin)
154 | try:
155 | await client.submit_sms(sms_code)
156 | except LoginException:
157 | raise
158 | except Exception:
159 | await client.close()
160 | raise
161 | return True
162 |
163 |
164 | __all__ = [
165 | "login",
166 | "submit_captcha",
167 | "submit_slider_ticket",
168 | "request_sms",
169 | "submit_sms",
170 | ]
171 |
--------------------------------------------------------------------------------
/cai/client/__init__.py:
--------------------------------------------------------------------------------
1 | """Application Client.
2 |
3 | This module is main entry point for the application.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from .event import Event
13 | from .command import Command
14 | from .packet import IncomingPacket
15 | from .client import HANDLERS, Client
16 | from .status_service import OnlineStatus, RegPushReason
17 | from .message_service import GroupMessage, PrivateMessage
18 | from .models import Group, Friend, FriendGroup, GroupMember, GroupMemberRole
19 |
--------------------------------------------------------------------------------
/cai/client/command.py:
--------------------------------------------------------------------------------
1 | """Client Base Command.
2 |
3 | This module is used to build command from packet.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | from typing import TYPE_CHECKING
12 | from dataclasses import dataclass
13 |
14 | from .packet import IncomingPacket
15 |
16 | if TYPE_CHECKING:
17 | from .client import Client
18 |
19 |
20 | @dataclass
21 | class Command:
22 | uin: int
23 | seq: int
24 | ret_code: int
25 | command_name: str
26 |
27 |
28 | @dataclass
29 | class UnhandledCommand(Command):
30 | data: bytes
31 |
32 |
33 | async def _packet_to_command(
34 | client: "Client", packet: IncomingPacket
35 | ) -> UnhandledCommand:
36 | return UnhandledCommand(
37 | packet.uin,
38 | packet.seq,
39 | packet.ret_code,
40 | packet.command_name,
41 | packet.data,
42 | )
43 |
--------------------------------------------------------------------------------
/cai/client/config_push/__init__.py:
--------------------------------------------------------------------------------
1 | """ConfigPushSvc Related SDK.
2 |
3 | This module is used to build and handle config push service related packet.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from typing import TYPE_CHECKING
13 |
14 | from jce import types
15 |
16 | from cai.log import logger
17 | from cai.utils.binary import Packet
18 | from cai.utils.jce import RequestPacketVersion3
19 | from cai.client.packet import UniPacket, IncomingPacket
20 |
21 | from .jce import PushResp, FileServerPushList
22 | from .command import (
23 | ConfigPushCommand,
24 | LogActionPushCommand,
25 | SsoServerPushCommand,
26 | FileServerPushCommand,
27 | _ConfigPushCommandBase,
28 | )
29 |
30 | if TYPE_CHECKING:
31 | from cai.client import Client
32 |
33 |
34 | def encode_config_push_response(
35 | uin: int,
36 | seq: int,
37 | session_id: bytes,
38 | d2key: bytes,
39 | type: int,
40 | jcebuf: bytes,
41 | large_seq: int,
42 | ) -> Packet:
43 | """Build config push response packet.
44 |
45 | command name: ``ConfigPushSvc.PushResp``
46 |
47 | Note:
48 | Source: com.tencent.mobileqq.msf.core.a.c.b
49 |
50 | Args:
51 | uin (int): User QQ number.
52 | seq (int): Packet sequence.
53 | session_id (bytes): Session ID.
54 | d2key (bytes): Siginfo d2 key.
55 | type (int): ConfigPushSvc request type.
56 | jcebuf (bytes): ConfigPushSvc request jcebuf.
57 | large_seq (int): ConfigPushSvc request large_seq.
58 | """
59 | COMMAND_NAME = "ConfigPushSvc.PushResp"
60 |
61 | resp = PushResp(
62 | type=type, jcebuf=jcebuf if type == 3 else None, large_seq=large_seq
63 | )
64 | payload = PushResp.to_bytes(0, resp)
65 | resp_packet = RequestPacketVersion3(
66 | servant_name="QQService.ConfigPushSvc.MainServant",
67 | func_name="PushResp",
68 | data=types.MAP({types.STRING("PushResp"): types.BYTES(payload)}),
69 | ).encode()
70 | packet = UniPacket.build(
71 | uin, seq, COMMAND_NAME, session_id, 1, resp_packet, d2key
72 | )
73 | return packet
74 |
75 |
76 | # ConfigPushSvc.PushReq
77 | async def handle_config_push_request(
78 | client: "Client", packet: IncomingPacket
79 | ) -> ConfigPushCommand:
80 | command = ConfigPushCommand.decode_push_req(
81 | packet.uin,
82 | packet.seq,
83 | packet.ret_code,
84 | packet.command_name,
85 | packet.data,
86 | )
87 | if isinstance(command, SsoServerPushCommand):
88 | logger.debug(f"ConfigPush: Got new server addresses.")
89 | elif isinstance(command, FileServerPushCommand):
90 | client._file_storage_info = command.list
91 |
92 | if isinstance(command, _ConfigPushCommandBase):
93 | resp_packet = encode_config_push_response(
94 | client.uin,
95 | command.seq,
96 | client._session_id,
97 | client._siginfo.d2key,
98 | command.type,
99 | command.jcebuf,
100 | command.large_seq,
101 | )
102 | await client.send(command.seq, "ConfigPushSvc.PushResp", resp_packet)
103 |
104 | return command
105 |
106 |
107 | __all__ = [
108 | "handle_config_push_request",
109 | "FileServerPushList",
110 | "ConfigPushCommand",
111 | "SsoServerPushCommand",
112 | "FileServerPushCommand",
113 | "LogActionPushCommand",
114 | ]
115 |
--------------------------------------------------------------------------------
/cai/client/config_push/command.py:
--------------------------------------------------------------------------------
1 | """ConfigPushSvc Command Parser.
2 |
3 | This module is used to parse ConfigPushSvc packets into command.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from dataclasses import dataclass
13 |
14 | from cai.client.command import Command
15 | from cai.utils.jce import RequestPacketVersion2
16 |
17 | from .jce import PushReq, SsoServerPushList, FileServerPushList
18 |
19 |
20 | @dataclass
21 | class ConfigPushCommand(Command):
22 | @classmethod
23 | def decode_push_req(
24 | cls, uin: int, seq: int, ret_code: int, command_name: str, data: bytes
25 | ) -> "ConfigPushCommand":
26 | """Decode ConfigPush.PushReq packet.
27 |
28 | Note:
29 | Source:
30 |
31 | com.tencent.mobileqq.msf.core.a.c.a (type 1)
32 |
33 | com.tencent.mobileqq.servlet.PushServlet.onReceive (type 2)
34 |
35 | Args:
36 | uin (int): User QQ
37 | seq (int): Sequence number of the response packet.
38 | ret_code (int): Return code of the response.
39 | command_name (str): Command name of the response.
40 | data (bytes): Payload data of the response.
41 |
42 | Returns:
43 | SsoServerPushCommand: Push Sso Server Info List.
44 | FileServerPushCommand: Push File Server Info List.
45 | """
46 | if ret_code != 0 or not data:
47 | return cls(uin, seq, ret_code, command_name)
48 |
49 | packet = RequestPacketVersion2.decode(data)
50 | push = PushReq.decode(
51 | packet.data["PushReq"]["ConfigPush.PushReq"][1:-1] # type: ignore
52 | )
53 | if push.type == 1:
54 | list = SsoServerPushList.decode(push.jcebuf)
55 | return SsoServerPushCommand(
56 | uin,
57 | seq,
58 | ret_code,
59 | command_name,
60 | push.type,
61 | push.jcebuf,
62 | push.large_seq,
63 | list,
64 | )
65 | elif push.type == 2:
66 | list = FileServerPushList.decode(push.jcebuf)
67 | return FileServerPushCommand(
68 | uin,
69 | seq,
70 | ret_code,
71 | command_name,
72 | push.type,
73 | push.jcebuf,
74 | push.large_seq,
75 | list,
76 | )
77 | elif push.type == 3:
78 | # LogAction, do nothing
79 | return LogActionPushCommand(
80 | uin,
81 | seq,
82 | ret_code,
83 | command_name,
84 | push.type,
85 | push.jcebuf,
86 | push.large_seq,
87 | )
88 | return ConfigPushCommand(uin, seq, ret_code, command_name)
89 |
90 |
91 | @dataclass
92 | class _ConfigPushCommandBase(ConfigPushCommand):
93 | type: int
94 | jcebuf: bytes
95 | large_seq: int
96 |
97 |
98 | @dataclass
99 | class SsoServerPushCommand(_ConfigPushCommandBase):
100 | list: SsoServerPushList
101 |
102 |
103 | @dataclass
104 | class FileServerPushCommand(_ConfigPushCommandBase):
105 | list: FileServerPushList
106 |
107 |
108 | @dataclass
109 | class LogActionPushCommand(_ConfigPushCommandBase):
110 | pass
111 |
--------------------------------------------------------------------------------
/cai/client/event.py:
--------------------------------------------------------------------------------
1 | """Client Base Command.
2 |
3 | This module is used to build command from packet.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | import abc
13 |
14 |
15 | class Event(abc.ABC):
16 | @property
17 | @abc.abstractmethod
18 | def type(self) -> str:
19 | raise NotImplementedError
20 |
--------------------------------------------------------------------------------
/cai/client/heartbeat/__init__.py:
--------------------------------------------------------------------------------
1 | """Heartbeat Related SDK.
2 |
3 | This module is used to build and handle heartbeat related packet.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from typing import TYPE_CHECKING
13 | from dataclasses import dataclass
14 |
15 | from cai.utils.binary import Packet
16 | from cai.client.command import Command
17 | from cai.settings.device import get_device
18 | from cai.settings.protocol import get_protocol
19 | from cai.client.packet import CSsoBodyPacket, CSsoDataPacket, IncomingPacket
20 |
21 | if TYPE_CHECKING:
22 | from cai.client import Client
23 |
24 | DEVICE = get_device()
25 | APK_INFO = get_protocol()
26 |
27 |
28 | def encode_heartbeat(
29 | seq: int, session_id: bytes, ksid: bytes, uin: int
30 | ) -> Packet:
31 | """Build heartbeat alive packet.
32 |
33 | Called in `com.tencent.mobileqq.msf.core.C26002ac.A`.
34 |
35 | command name: `Heartbeat.Alive`
36 |
37 | Note:
38 | Source: oicq.wlogin_sdk.request.n
39 |
40 | Args:
41 | seq (int): Packet sequence.
42 | session_id (bytes): Session ID.
43 | ksid (bytes): KSID of client.
44 | uin (int): User QQ number.
45 |
46 | Returns:
47 | Packet: Login packet.
48 | """
49 | COMMAND_NAME = "Heartbeat.Alive"
50 |
51 | SUB_APP_ID = APK_INFO.sub_app_id
52 |
53 | sso_packet = CSsoBodyPacket.build(
54 | seq, SUB_APP_ID, COMMAND_NAME, DEVICE.imei, session_id, ksid, bytes()
55 | )
56 | packet = CSsoDataPacket.build(uin, 0, sso_packet, key=None)
57 | return packet
58 |
59 |
60 | @dataclass
61 | class Heartbeat(Command):
62 | pass
63 |
64 |
65 | async def handle_heartbeat(
66 | client: "Client", packet: IncomingPacket
67 | ) -> Heartbeat:
68 | return Heartbeat(
69 | packet.uin, packet.seq, packet.ret_code, packet.command_name
70 | )
71 |
72 |
73 | __all__ = ["encode_heartbeat", "handle_heartbeat", "Heartbeat"]
74 |
--------------------------------------------------------------------------------
/cai/client/message_service/command.py:
--------------------------------------------------------------------------------
1 | """MessageSvc Command Parser.
2 |
3 | This module is used to parse MessageSvc response packets into command.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from dataclasses import dataclass
13 |
14 | from cai.client.command import Command
15 | from cai.pb.msf.msg.svc import PbGetMsgResp
16 | from cai.utils.jce import RequestPacketVersion2
17 |
18 | from .jce import RequestPushNotify, RequestPushForceOffline
19 |
20 |
21 | @dataclass
22 | class GetMessageCommand(Command):
23 | @classmethod
24 | def decode_response(
25 | cls, uin: int, seq: int, ret_code: int, command_name: str, data: bytes
26 | ) -> "GetMessageCommand":
27 | """Decode MessageSvc get message response packet.
28 |
29 | Note:
30 | Source: c2c 1002
31 |
32 | com.tencent.mobileqq.app.handler.receivesuccess.MessageSvcPbGetMsg
33 |
34 | com.tencent.mobileqq.app.MessageHandler.h
35 |
36 | com.tencent.imcore.message.C2CMessageProcessor.b
37 |
38 | Args:
39 | uin (int): User QQ
40 | seq (int): Sequence number of the response packet.
41 | ret_code (int): Return code of the response.
42 | command_name (str): Command name of the response.
43 | data (bytes): Payload data of the response.
44 | """
45 | if ret_code != 0 or not data:
46 | return GetMessageCommand(uin, seq, ret_code, command_name)
47 |
48 | try:
49 | result = PbGetMsgResp.FromString(data)
50 | return GetMessageSuccess(uin, seq, ret_code, command_name, result)
51 | except Exception as e:
52 | return GetMessageFail(
53 | uin,
54 | seq,
55 | ret_code,
56 | command_name,
57 | f"Error when decoding response! {repr(e)}",
58 | )
59 |
60 |
61 | @dataclass
62 | class GetMessageSuccess(GetMessageCommand):
63 | response: PbGetMsgResp
64 |
65 |
66 | @dataclass
67 | class GetMessageFail(GetMessageCommand):
68 | message: str
69 |
70 |
71 | @dataclass
72 | class PushNotifyCommand(Command):
73 | @classmethod
74 | def decode_response(
75 | cls, uin: int, seq: int, ret_code: int, command_name: str, data: bytes
76 | ) -> "PushNotifyCommand":
77 | """Decode MessageSvc push notify packet.
78 |
79 | Note:
80 | Source: com.tencent.mobileqq.service.message.MessageFactoryReceiver.f
81 |
82 | Args:
83 | uin (int): User QQ
84 | seq (int): Sequence number of the response packet.
85 | ret_code (int): Return code of the response.
86 | command_name (str): Command name of the response.
87 | data (bytes): Payload data of the response.
88 | """
89 | if ret_code != 0 or not data:
90 | return PushNotifyCommand(uin, seq, ret_code, command_name)
91 |
92 | try:
93 | # data offset 4 in source? test get 15
94 | # req_packet = RequestPacketVersion2.decode(data[4:])
95 | req_packet = RequestPacketVersion2.decode(data[15:])
96 | push_offline_request = RequestPushNotify.decode(
97 | req_packet.data["req_PushNotify"][ # type: ignore
98 | "PushNotifyPack.RequestPushNotify"
99 | ][1:-1]
100 | )
101 | return PushNotify(
102 | uin, seq, ret_code, command_name, push_offline_request
103 | )
104 | except Exception as e:
105 | return PushNotifyError(
106 | uin,
107 | seq,
108 | ret_code,
109 | command_name,
110 | f"Error when decoding response! {repr(e)}",
111 | )
112 |
113 |
114 | @dataclass
115 | class PushNotify(PushNotifyCommand):
116 | notify: RequestPushNotify
117 |
118 |
119 | @dataclass
120 | class PushNotifyError(PushNotifyCommand):
121 | message: str
122 |
123 |
124 | @dataclass
125 | class PushForceOfflineCommand(Command):
126 | @classmethod
127 | def decode_response(
128 | cls, uin: int, seq: int, ret_code: int, command_name: str, data: bytes
129 | ) -> "PushForceOfflineCommand":
130 | """Decode MessageSvc Force Offline request.
131 |
132 | Note:
133 | Source: mqq.app.MainService
134 |
135 | Args:
136 | uin (int): User QQ
137 | seq (int): Sequence number of the response packet.
138 | ret_code (int): Return code of the response.
139 | command_name (str): Command name of the response.
140 | data (bytes): Payload data of the response.
141 | """
142 | if ret_code != 0 or not data:
143 | return PushForceOfflineCommand(uin, seq, ret_code, command_name)
144 |
145 | try:
146 | req_packet = RequestPacketVersion2.decode(data)
147 | push_offline_request = RequestPushForceOffline.decode(
148 | req_packet.data["req_PushForceOffline"][ # type: ignore
149 | "PushNotifyPack.RequestPushForceOffline"
150 | ][1:-1]
151 | )
152 | return PushForceOffline(
153 | uin, seq, ret_code, command_name, push_offline_request
154 | )
155 | except Exception as e:
156 | return PushForceOfflineError(
157 | uin,
158 | seq,
159 | ret_code,
160 | command_name,
161 | f"Error when decoding response! {repr(e)}",
162 | )
163 |
164 |
165 | @dataclass
166 | class PushForceOffline(PushForceOfflineCommand):
167 | request: RequestPushForceOffline
168 |
169 |
170 | @dataclass
171 | class PushForceOfflineError(PushForceOfflineCommand):
172 | message: str
173 |
--------------------------------------------------------------------------------
/cai/client/message_service/jce.py:
--------------------------------------------------------------------------------
1 | """MessageSvc Packet Builder.
2 |
3 | This module is used to build and handle MessageSvc packets.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from typing import Optional
13 |
14 | from jce import JceField, JceStruct, types
15 |
16 | from cai.client.qq_service.jce import StShareData
17 |
18 |
19 | # Push Notify
20 | class CPicInfo(JceStruct):
21 | """MessageSvc Online Push CPic Info jce packet.
22 |
23 | Note:
24 | Source: OnlinePushPack.CPicInfo
25 | """
26 |
27 | path: types.BYTES = JceField(jce_id=0)
28 | host: types.BYTES = JceField(bytes(), jce_id=1)
29 |
30 |
31 | class TempMsgHead(JceStruct):
32 | """MessageSvc Online Push Temp Message Head jce packet.
33 |
34 | Note:
35 | Source: OnlinePushPack.TempMsgHead
36 | """
37 |
38 | c2c_type: types.INT32 = JceField(0, jce_id=0)
39 | service_type: types.INT32 = JceField(0, jce_id=1)
40 |
41 |
42 | class MessageInfo(JceStruct):
43 | """MessageSvc Online Push Message Info jce packet.
44 |
45 | Note:
46 | Source: OnlinePushPack.MsgInfo
47 | """
48 |
49 | from_uin: types.INT64 = JceField(jce_id=0)
50 | message_time: types.INT64 = JceField(jce_id=1)
51 | message_type: types.INT16 = JceField(jce_id=2)
52 | message_seq: types.INT16 = JceField(jce_id=3)
53 | message: types.STRING = JceField(jce_id=4)
54 | real_message_time: types.INT32 = JceField(0, jce_id=5)
55 | vec_message: types.BYTES = JceField(bytes(), jce_id=6)
56 | app_share_id: types.INT64 = JceField(0, jce_id=7)
57 | message_cookies: types.BYTES = JceField(bytes(), jce_id=8)
58 | app_share_cookie: types.BYTES = JceField(bytes(), jce_id=9)
59 | message_uid: types.INT64 = JceField(0, jce_id=10)
60 | last_change_time: types.INT64 = JceField(0, jce_id=11)
61 | cpic_info: types.LIST[CPicInfo] = JceField([], jce_id=12)
62 | share_data: Optional[StShareData] = JceField(None, jce_id=13)
63 | from_inst_id: types.INT64 = JceField(0, jce_id=14)
64 | remark_of_sender: types.BYTES = JceField(bytes(), jce_id=15)
65 | from_mobile: types.STRING = JceField("", jce_id=16)
66 | from_name: types.STRING = JceField("", jce_id=17)
67 | nickname: types.LIST[types.STRING] = JceField([], jce_id=18)
68 | c2c_temp_msg_head: Optional[TempMsgHead] = JceField(None, jce_id=19)
69 |
70 |
71 | class RequestPushNotify(JceStruct):
72 | """MessageSvc Push Notify Request jce packet.
73 |
74 | Note:
75 | Source: PushNotifyPack.RequestPushNotify
76 | """
77 |
78 | uin: types.INT64 = JceField(jce_id=0)
79 | type: types.INT8 = JceField(jce_id=1)
80 | service: types.STRING = JceField(jce_id=2)
81 | cmd: types.STRING = JceField(jce_id=3)
82 | notify_cookie: types.BYTES = JceField(bytes(), jce_id=4)
83 | message_type: types.INT32 = JceField(0, jce_id=5)
84 | user_active: types.INT32 = JceField(0, jce_id=6)
85 | general_flag: types.INT32 = JceField(0, jce_id=7)
86 | binded_uin: types.INT64 = JceField(0, jce_id=8)
87 | message_info: Optional[MessageInfo] = JceField(None, jce_id=9)
88 | message_ctrl_buf: types.STRING = JceField("", jce_id=10)
89 | server_buf: types.BYTES = JceField(bytes(), jce_id=11)
90 | ping_flag: types.INT64 = JceField(0, jce_id=12)
91 | svrip: types.INT64 = JceField(0, jce_id=13)
92 |
93 |
94 | # Push Force Offline
95 | class RequestPushForceOffline(JceStruct):
96 | """MessageSvc Push Force Offline Request jce packet.
97 |
98 | Note:
99 | Source: PushNotifyPack.RequestPushForceOffline
100 | """
101 |
102 | uin: types.INT64 = JceField(jce_id=0)
103 | title: types.STRING = JceField("", jce_id=1)
104 | tips: types.STRING = JceField("", jce_id=2)
105 | same_device: types.BOOL = JceField(False, jce_id=3)
106 |
--------------------------------------------------------------------------------
/cai/client/message_service/models.py:
--------------------------------------------------------------------------------
1 | """MessageSvc message models.
2 |
3 | This module is used to define message models.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | import abc
13 | from dataclasses import dataclass
14 | from typing import List, Optional
15 |
16 | from cai.client.event import Event
17 | from cai.pb.msf.msg.comm import Msg
18 |
19 |
20 | @dataclass
21 | class PrivateMessage(Event):
22 | _msg: Msg
23 | seq: int
24 | time: int
25 | auto_reply: bool
26 | from_uin: int
27 | from_nick: str
28 | to_uin: int
29 | message: List["Element"]
30 |
31 | @property
32 | def type(self) -> str:
33 | return "private_message"
34 |
35 |
36 | @dataclass
37 | class GroupMessage(Event):
38 | _msg: Msg
39 | seq: int
40 | time: int
41 | group_id: int
42 | group_name: str
43 | group_level: int
44 | from_uin: int
45 | from_group_card: str
46 | message: List["Element"]
47 |
48 | @property
49 | def type(self) -> str:
50 | return "group_message"
51 |
52 |
53 | class Element(abc.ABC):
54 | @property
55 | @abc.abstractmethod
56 | def type(self) -> str:
57 | raise NotImplementedError
58 |
59 |
60 | @dataclass
61 | class ReplyElement(Element):
62 | seq: int
63 | time: int
64 | sender: int
65 | message: List[Element]
66 | troop_name: Optional[str]
67 |
68 | @property
69 | def type(self) -> str:
70 | return "reply"
71 |
72 |
73 | @dataclass
74 | class TextElement(Element):
75 | content: str
76 |
77 | @property
78 | def type(self) -> str:
79 | return "text"
80 |
81 |
82 | @dataclass
83 | class FaceElement(Element):
84 | id: int
85 |
86 | @property
87 | def type(self) -> str:
88 | return "face"
89 |
90 |
91 | @dataclass
92 | class SmallEmojiElement(Element):
93 | id: int
94 | text: str
95 | # byte: bytes
96 |
97 | @property
98 | def type(self) -> str:
99 | return "small_emoji"
100 |
101 |
102 | @dataclass
103 | class ImageElement(Element):
104 | filename: str
105 | size: int
106 | width: int
107 | height: int
108 | md5: bytes
109 | url: str
110 |
111 | @property
112 | def type(self) -> str:
113 | return "image"
114 |
115 |
116 | @dataclass
117 | class PokeElement(Element):
118 | id: int
119 | name: str
120 | strength: int
121 | double_hit: int
122 |
123 | @property
124 | def type(self) -> str:
125 | return "poke"
126 |
--------------------------------------------------------------------------------
/cai/client/online_push/command.py:
--------------------------------------------------------------------------------
1 | """MessageSvc Command Parser.
2 |
3 | This module is used to parse MessageSvc response packets into command.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from dataclasses import dataclass
13 |
14 | from cai.client.command import Command
15 | from cai.pb.msf.msg.onlinepush import PbPushMsg
16 |
17 |
18 | @dataclass
19 | class PushMsgCommand(Command):
20 | @classmethod
21 | def decode_response(
22 | cls, uin: int, seq: int, ret_code: int, command_name: str, data: bytes
23 | ) -> "PushMsgCommand":
24 | """Decode OnlinePush push message packet.
25 |
26 | Including commands: ``OnlinePush.PbPushGroupMsg``,
27 | ``OnlinePush.PbPushDisMsg``, ``OnlinePush.PbC2CMsgSync``,
28 | ``OnlinePush.PbPushC2CMsg``, ``OnlinePush.PbPushBindUinGroupMsg``
29 |
30 | Note:
31 | Source: com.tencent.mobileqq.app.MessageHandler.b
32 |
33 | Args:
34 | uin (int): User QQ
35 | seq (int): Sequence number of the response packet.
36 | ret_code (int): Return code of the response.
37 | command_name (str): Command name of the response.
38 | data (bytes): Payload data of the response.
39 | """
40 | if ret_code != 0 or not data:
41 | return PushMsgCommand(uin, seq, ret_code, command_name)
42 |
43 | try:
44 | push_message = PbPushMsg.FromString(data)
45 | return PushMsg(uin, seq, ret_code, command_name, push_message)
46 | except Exception as e:
47 | return PushMsgError(
48 | uin,
49 | seq,
50 | ret_code,
51 | command_name,
52 | f"Error when decoding response! {repr(e)}",
53 | )
54 |
55 |
56 | @dataclass
57 | class PushMsg(PushMsgCommand):
58 | push: PbPushMsg
59 |
60 |
61 | @dataclass
62 | class PushMsgError(PushMsgCommand):
63 | message: str
64 |
--------------------------------------------------------------------------------
/cai/client/online_push/jce.py:
--------------------------------------------------------------------------------
1 | """OnlinePush Packet Builder.
2 |
3 | This module is used to build and handle OnlinePush packets.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from typing import Optional
13 |
14 | from jce import JceField, JceStruct, types
15 |
16 |
17 | class DelMsgInfo(JceStruct):
18 | """OnlinePush Delete Message Info Packet.
19 |
20 | Note:
21 | Source: OnlinePushPack.DelMsgInfo
22 | """
23 |
24 | from_uin: types.INT64 = JceField(jce_id=0)
25 | msg_time: types.INT64 = JceField(jce_id=1)
26 | msg_seq: types.INT8 = JceField(jce_id=2)
27 | msg_cookies: types.BYTES = JceField(bytes(), jce_id=3)
28 | cmd: types.INT8 = JceField(0, jce_id=4)
29 | msg_type: types.INT64 = JceField(0, jce_id=5)
30 | app_id: types.INT64 = JceField(0, jce_id=6)
31 | send_time: types.INT64 = JceField(0, jce_id=7)
32 | sso_seq: types.INT32 = JceField(0, jce_id=8)
33 | sso_ip: types.INT32 = JceField(0, jce_id=9)
34 | client_ip: types.INT32 = JceField(0, jce_id=10)
35 |
36 |
37 | class DeviceInfo(JceStruct):
38 | """OnlinePush Device Info Packet.
39 |
40 | Note:
41 | Source: OnlinePushPack.DeviceInfo
42 | """
43 |
44 | net_type: types.BYTE = JceField(bytes(1), jce_id=0)
45 | dev_type: types.STRING = JceField("", jce_id=1)
46 | os_ver: types.STRING = JceField("", jce_id=2)
47 | vendor_name: types.STRING = JceField("", jce_id=3)
48 | vendor_os_name: types.STRING = JceField("", jce_id=4)
49 | ios_idfa: types.STRING = JceField("", jce_id=5)
50 |
51 |
52 | class SvcRespPushMsg(JceStruct):
53 | """OnlinePush Service Push Response Packet.
54 |
55 | Note:
56 | Source: OnlinePushPack.SvcRespPushMsg
57 | """
58 |
59 | uin: types.INT64 = JceField(jce_id=0)
60 | del_infos: types.LIST[DelMsgInfo] = JceField(jce_id=1)
61 | svrip: types.INT32 = JceField(jce_id=2)
62 | push_token: Optional[types.BYTES] = JceField(None, jce_id=3)
63 | service_type: types.INT32 = JceField(0, jce_id=4)
64 | device_info: Optional[DeviceInfo] = JceField(None, jce_id=5)
65 |
--------------------------------------------------------------------------------
/cai/client/qq_service/__init__.py:
--------------------------------------------------------------------------------
1 | """QQService Related SDK.
2 |
3 | This module is used to build and handle qq service related packet.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
--------------------------------------------------------------------------------
/cai/client/qq_service/jce.py:
--------------------------------------------------------------------------------
1 | """QQ Service Packet Builder.
2 |
3 | This module is used to build and handle qq service packets.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from jce import JceField, JceStruct, types
13 |
14 |
15 | class StShareData(JceStruct):
16 | """Share Data Jce Packet.
17 |
18 | Note:
19 | Source: QQService.ShareData
20 | """
21 |
22 | pkg_name: types.STRING = JceField(jce_id=0)
23 | msgtail: types.STRING = JceField(jce_id=1)
24 | pic_url: types.STRING = JceField(jce_id=2)
25 | url: types.STRING = JceField(jce_id=3)
26 |
27 |
28 | class VipOpenInfo(JceStruct):
29 | """Vip Open Info Jce Packet.
30 |
31 | Note:
32 | Source: QQService.VipOpenInfo
33 | """
34 |
35 | open: types.BOOL = JceField(jce_id=0)
36 | vip_type: types.INT32 = JceField(jce_id=1)
37 | vip_level: types.INT32 = JceField(jce_id=2)
38 | vip_flag: types.INT32 = JceField(0, jce_id=3)
39 | nameplate_id: types.INT64 = JceField(0, jce_id=4)
40 |
41 |
42 | class VipBaseInfo(JceStruct):
43 | """Vip Base Info Jce Packet.
44 |
45 | Note:
46 | Source: QQService.VipBaseInfo
47 | """
48 |
49 | open_info: types.MAP[types.INT, VipOpenInfo] = JceField(jce_id=0)
50 | nameplate_vip_type: types.INT32 = JceField(0, jce_id=1)
51 | gray_nameplate_flag: types.INT32 = JceField(0, jce_id=2)
52 | extend_nameplate_id: types.STRING = JceField("", jce_id=3)
53 |
--------------------------------------------------------------------------------
/cai/client/sso_server/__init__.py:
--------------------------------------------------------------------------------
1 | """SSO Server SDK.
2 |
3 | This module is used to get server list and choose the best one.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181:License: AGPL-3.0 or later. See `LICENSE`_ for detail.
6 |
7 | .. _LICENSE:
8 | https://github.com/cscs181/CAI/blob/master/LICENSE
9 | """
10 | import asyncio
11 | import http.client
12 | from io import BytesIO
13 | from typing import List, Tuple, Union, Iterable, Optional, Container
14 |
15 | from jce import types
16 | from rtea import qqtea_decrypt, qqtea_encrypt
17 |
18 | from cai.connection import connect
19 | from cai.settings.device import get_device
20 | from cai.exceptions import SsoServerException
21 | from cai.settings.protocol import get_protocol
22 | from cai.utils.jce import RequestPacketVersion3
23 | from cai.connection.utils import tcp_latency_test
24 |
25 | from .jce import SsoServer, SsoServerRequest, SsoServerResponse
26 |
27 | _cached_server: Optional["SsoServer"] = None
28 | _cached_servers: List["SsoServer"] = []
29 |
30 |
31 | class _FakeSocket:
32 | """Fake socket object.
33 |
34 | This class is used to wrap raw response bytes
35 | and pass them to :class:`http.client.HTTPResponse`.
36 |
37 | Args:
38 | response (:obj:`bytes`): HTTP raw response from server.
39 | """
40 |
41 | def __init__(self, response: bytes):
42 | self._file = BytesIO(response)
43 |
44 | def makefile(self, *args, **kwargs):
45 | return self._file
46 |
47 |
48 | async def get_sso_list() -> SsoServerResponse:
49 | """Do sso server list request and return the response.
50 |
51 | Note:
52 | Source: com.tencent.mobileqq.msf.core.a.f.run
53 |
54 | Returns:
55 | SsoServerResponse: Sso server list response
56 |
57 | Raises:
58 | SsoServerException: Get sso server list failed.
59 | """
60 | device = get_device()
61 | protocol = get_protocol()
62 | key = bytes(
63 | [
64 | 0xF0,
65 | 0x44,
66 | 0x1F,
67 | 0x5F,
68 | 0xF4,
69 | 0x2D,
70 | 0xA5,
71 | 0x8F,
72 | 0xDC,
73 | 0xF7,
74 | 0x94,
75 | 0x9A,
76 | 0xBA,
77 | 0x62,
78 | 0xD4,
79 | 0x11,
80 | ]
81 | )
82 | payload = SsoServerRequest.to_bytes(
83 | 0, SsoServerRequest(app_id=protocol.app_id, imei=device.imei)
84 | )
85 | req_packet = RequestPacketVersion3(
86 | servant_name="HttpServerListReq",
87 | func_name="HttpServerListReq",
88 | data=types.MAP(
89 | {types.STRING("HttpServerListReq"): types.BYTES(payload)}
90 | ),
91 | ).encode(with_length=True)
92 | buffer: bytes = qqtea_encrypt(req_packet, key)
93 | async with connect("configsvr.msf.3g.qq.com", 443, ssl=True) as conn:
94 | query = (
95 | b"POST /configsvr/serverlist.jsp HTTP/1.1\r\n"
96 | b"Host: configsvr.msf.3g.qq.com\r\n"
97 | b"User-Agent: QQ/8.4.1.2703 CFNetwork/1126\r\n"
98 | b"Net-Type: Wifi\r\n"
99 | b"Accept: */*\r\n"
100 | b"Connection: close\r\n"
101 | b"Content-Type: application/octet-stream\r\n"
102 | b"Content-Length: " + str(len(buffer)).encode() + b"\r\n"
103 | b"\r\n" + buffer
104 | )
105 | conn.write(query)
106 | conn.write_eof()
107 | resp_bytes = await conn.read_all()
108 | response = http.client.HTTPResponse(
109 | _FakeSocket(resp_bytes) # type: ignore
110 | )
111 | response.begin()
112 |
113 | if response.status != 200:
114 | raise SsoServerException(
115 | f"Get sso server list failed with response code {response.status}"
116 | )
117 | data: bytes = qqtea_decrypt(response.read(), key)
118 | resp_packet = RequestPacketVersion3.decode(data[4:])
119 | server_info = SsoServerResponse.decode(
120 | resp_packet.data["HttpServerListRes"][1:-1] # type: ignore
121 | )
122 | return server_info
123 |
124 |
125 | async def quality_test(
126 | servers: Iterable[SsoServer], threshold: float = 500.0
127 | ) -> List[Tuple[SsoServer, float]]:
128 | """Test given servers' quality by tcp latency.
129 |
130 | Args:
131 | servers (Iterable[:obj:`.SsoServer`]): Server list to test.
132 | threshold (float, optional): Latency more than threshold will not be returned.
133 | Defaults to 500.0.
134 |
135 | Returns:
136 | List[Tuple[:obj:`.SsoServer`, float]]: List of server and latency in tuple.
137 | """
138 | servers_ = list(servers)
139 | tasks = [tcp_latency_test(server.host, server.port) for server in servers_]
140 | result: List[Union[float, Exception]] = await asyncio.gather(
141 | *tasks, return_exceptions=True
142 | )
143 | success_servers = [
144 | (server, latency)
145 | for server, latency in zip(servers_, result)
146 | if isinstance(latency, float) and latency < threshold
147 | ]
148 | return success_servers
149 |
150 |
151 | async def get_sso_server(
152 | cache: bool = True,
153 | cache_server_list: bool = True,
154 | exclude: Optional[Container[str]] = None,
155 | ) -> SsoServer:
156 | """Get the best sso server
157 |
158 | Args:
159 | cache (bool, optional): Using cache server or not. Defaults to True.
160 | cache_server_list (bool, optional): Using cache server list or not. Defaults to True.
161 | exclude (List[str], optional): List of servers' ip want to be excluded
162 |
163 | Returns:
164 | :obj:`.SsoServer`: The best server with smallest latency.
165 | """
166 | global _cached_server
167 | if cache and _cached_server:
168 | return _cached_server
169 |
170 | if cache_server_list and _cached_servers:
171 | servers = _cached_servers
172 | else:
173 | sso_list = await get_sso_list()
174 | servers = [*sso_list.socket_v4_mobile, *sso_list.socket_v4_wifi]
175 | _cached_servers.clear()
176 | _cached_servers.extend(servers)
177 |
178 | exclude_server = exclude or []
179 | success_servers = await quality_test(
180 | server for server in servers if server.host not in exclude_server
181 | )
182 | success_servers.sort(key=lambda x: x[1])
183 | _cached_server = success_servers[0][0]
184 | return _cached_server
185 |
--------------------------------------------------------------------------------
/cai/client/sso_server/jce.py:
--------------------------------------------------------------------------------
1 | """SSO Server Packet Builder.
2 |
3 | This module is used to build sso server packets.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181:License: AGPL-3.0 or later. See `LICENSE`_ for detail.
6 |
7 | .. _LICENSE:
8 | https://github.com/cscs181/CAI/blob/master/LICENSE
9 | """
10 |
11 | from jce import JceField, JceStruct, types
12 |
13 |
14 | class SsoServerRequest(JceStruct):
15 | """Sso Server List Request.
16 |
17 | Note:
18 | Source: com.tencent.msf.service.protocol.serverconfig.d
19 | """
20 |
21 | uin: types.INT64 = JceField(0, jce_id=1) # uin or 0
22 | """:obj:`~jce.types.INT64`: User QQ or 0, renamed from a."""
23 | timeout: types.INT64 = JceField(0, jce_id=2)
24 | """:obj:`~jce.types.INT64`: may be timeout (s), default is 60, renamed from b."""
25 | c: types.BYTE = JceField(bytes([1]), jce_id=3)
26 | """:obj:`~jce.types.BYTE`: always 1."""
27 | imsi: types.STRING = JceField("46000", jce_id=4)
28 | """:obj:`~jce.types.STRING`: imsi string, renamed from d.
29 |
30 | ``null``->``""`` or ``imsi.substring(0, 5)``.
31 | """
32 | is_wifi: types.INT32 = JceField(100, jce_id=5)
33 | """:obj:`~jce.types.INT32`: is_wifi, renamed from e.
34 |
35 | NetConnInfoCenter.isWifiConn: true->100, false->1.
36 | """
37 | app_id: types.INT64 = JceField(jce_id=6)
38 | """:obj:`~jce.types.INT64`: app_id, renamed from f.
39 |
40 | same to :attr:`cai.settings.protocol.ApkInfo.app_id`.
41 | """
42 | imei: types.STRING = JceField(jce_id=7)
43 | """:obj:`~jce.types.STRING`: imei, renamed from g.
44 |
45 | same to :attr:`cai.settings.device.DeviceInfo.imei`.
46 | """
47 | cell_id: types.INT64 = JceField(0, jce_id=8)
48 | """:obj:`~jce.types.INT64`: cell_id, renamed from h.
49 |
50 | cell location get from ``CdmaCellLocation.getBaseStationId``.
51 | """
52 | i: types.INT64 = JceField(0, jce_id=9)
53 | """:obj:`~jce.types.INT64`: unknown."""
54 | j: types.INT64 = JceField(0, jce_id=10)
55 | """:obj:`~jce.types.INT64`: unknown."""
56 | k: types.BYTE = JceField(bytes(1), jce_id=11)
57 | """:obj:`~jce.types.BYTE`: unknown boolean, true->1, false->0."""
58 | l: types.BYTE = JceField(bytes(1), jce_id=12)
59 | """:obj:`~jce.types.BYTE`: active net ip family.
60 |
61 | get from ``NetConnInfoCenter.getActiveNetIpFamily``.
62 | """
63 | m: types.INT64 = JceField(0, jce_id=13)
64 | """:obj:`~jce.types.INT64`: unknown."""
65 |
66 |
67 | class SsoServer(JceStruct):
68 | """Sso Server Info.
69 |
70 | Note:
71 | Source: com.tencent.msf.service.protocol.serverconfig.i
72 | """
73 |
74 | host: types.STRING = JceField(jce_id=1)
75 | """:obj:`~jce.types.STRING`: server host, renamed from a."""
76 | port: types.INT32 = JceField(jce_id=2)
77 | """:obj:`~jce.types.INT32`: server port, renamed from b."""
78 | # c: types.BYTE = JceField(jce_id=3)
79 | # d: types.BYTE = JceField(jce_id=4)
80 | protocol: types.BYTE = JceField(jce_id=5)
81 | """:obj:`~jce.types.BYTE`: protocol, renamed from e.
82 |
83 | 0, 1: socket; 2, 3: http.
84 | """
85 | # f: types.INT32 = JceField(jce_id=6)
86 | # g: types.BYTE = JceField(jce_id=7)
87 | city: types.STRING = JceField(jce_id=8)
88 | """:obj:`~jce.types.STRING`: city, renamed from h."""
89 | country: types.STRING = JceField(jce_id=9)
90 | """:obj:`~jce.types.STRING`: country, renamed from i."""
91 |
92 |
93 | class SsoServerResponse(JceStruct):
94 | """Sso Server List Response.
95 |
96 | Note:
97 | Source: com.tencent.msf.service.protocol.serverconfig.e
98 | """
99 |
100 | # a: types.INT32 = JceField(0, jce_id=1)
101 | socket_v4_mobile: types.LIST[SsoServer] = JceField(jce_id=2)
102 | """:obj:`~jce.types.LIST` of :obj:`.SsoServer`:
103 | socket ipv4 mobile server, renamed from b.
104 | """
105 | socket_v4_wifi: types.LIST[SsoServer] = JceField(jce_id=3)
106 | """:obj:`~jce.types.LIST` of :obj:`.SsoServer`:
107 | socket ipv4 wifi server, renamed from c.
108 | """
109 | # d: types.INT32 = JceField(0, jce_id=4)
110 | # e: types.INT32 = JceField(86400, jce_id=5)
111 | # f: types.BYTE = JceField(bytes(1), jce_id=6)
112 | # g: types.BYTE = JceField(bytes(1), jce_id=7)
113 | # h: types.INT32 = JceField(1, jce_id=8)
114 | # i: types.INT32 = JceField(5, jce_id=9)
115 | # j: types.INT64 = JceField(0, jce_id=10)
116 | # k: types.INT32 = JceField(0, jce_id=11)
117 | http_v4_mobile: types.LIST[SsoServer] = JceField(jce_id=12)
118 | """:obj:`~jce.types.LIST` of :obj:`.SsoServer`:
119 | http ipv4 mobile server, renamed from l.
120 | """
121 | http_v4_wifi: types.LIST[SsoServer] = JceField(jce_id=13)
122 | """:obj:`~jce.types.LIST` of :obj:`.SsoServer`:
123 | http ipv4 wifi server, renamed from m.
124 | """
125 | speed_info: types.BYTES = JceField(jce_id=14)
126 | """:obj:`~jce.types.BYTES`: vCesuInfo, renamed from n.
127 |
128 | bytes from :class:`~cai.utils.jce.RequestPacketVersion3` (QualityTest)
129 | """
130 | socket_v6: types.LIST[SsoServer] = JceField(jce_id=15)
131 | """:obj:`~jce.types.LIST` of :obj:`.SsoServer`:
132 | socket ipv6 server, renamed from o.
133 |
134 | used when (wifi and :attr:`~.SsoServerResponse.nettype` & 1 == 1)
135 | or (mobile and :attr:`~.SsoServerResponse.nettype` & 2 == 2)
136 | """
137 | http_v6: types.LIST[SsoServer] = JceField(jce_id=16)
138 | """:obj:`~jce.types.LIST` of :obj:`.SsoServer`:
139 | http ipv6 server, renamed from p.
140 |
141 | used when (wifi and :attr:`~.SsoServerResponse.nettype` & 1 == 1)
142 | or (mobile and :attr:`~.SsoServerResponse.nettype` & 2 == 2)
143 | """
144 | udp_v6: types.LIST[SsoServer] = JceField(jce_id=17)
145 | """:obj:`~jce.types.LIST` of :obj:`.SsoServer`:
146 | quic ipv6 server, renamed from q.
147 |
148 | used when (wifi and :attr:`~.SsoServerResponse.nettype` & 1 == 1)
149 | or (mobile and :attr:`~.SsoServerResponse.nettype` & 2 == 2)
150 | """
151 | nettype: types.BYTE = JceField(bytes(1), jce_id=18)
152 | """:obj:`~jce.types.BYTE`: nettype, renamed from r."""
153 | delay_threshold: types.INT32 = JceField(0, jce_id=19)
154 | """:obj:`~jce.types.INT32`: delay threshold, renamed from s."""
155 | policy_id: types.STRING = JceField("", jce_id=20)
156 | """:obj:`~jce.types.STRING`: policy id, renamed from t."""
157 |
--------------------------------------------------------------------------------
/cai/client/status_service/command.py:
--------------------------------------------------------------------------------
1 | """StatSvc Command Parser.
2 |
3 | This module is used to parse StatSvc response packets into command.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 |
12 | from typing import Optional
13 | from dataclasses import dataclass
14 |
15 | from cai.client.command import Command
16 | from cai.utils.jce import RequestPacketVersion2, RequestPacketVersion3
17 |
18 | from .jce import SvcRespRegister, RequestMSFForceOffline
19 |
20 |
21 | @dataclass
22 | class SvcRegisterResponse(Command):
23 | @classmethod
24 | def decode_response(
25 | cls, uin: int, seq: int, ret_code: int, command_name: str, data: bytes
26 | ) -> "SvcRegisterResponse":
27 | """Decode StatSvc register response.
28 |
29 | Note:
30 | Source: com.tencent.mobileqq.servlet.PushServlet.onReceive
31 |
32 | Args:
33 | uin (int): User QQ
34 | seq (int): Sequence number of the response packet.
35 | ret_code (int): Return code of the response.
36 | command_name (str): Command name of the response.
37 | data (bytes): Payload data of the response.
38 |
39 | Returns:
40 | RegisterSuccess: register success.
41 | RegisterFail: register failed.
42 | """
43 | if ret_code != 0 or not data:
44 | return SvcRegisterResponse(uin, seq, ret_code, command_name)
45 |
46 | try:
47 | resp_packet = RequestPacketVersion2.decode(data)
48 | svc_register_response = SvcRespRegister.decode(
49 | resp_packet.data["SvcRespRegister"][ # type: ignore
50 | "QQService.SvcRespRegister"
51 | ][1:-1]
52 | )
53 | return RegisterSuccess(
54 | uin, seq, ret_code, command_name, svc_register_response
55 | )
56 | except Exception as e:
57 | return RegisterFail(
58 | uin,
59 | seq,
60 | ret_code,
61 | command_name,
62 | f"Error when decoding response! {repr(e)}",
63 | )
64 |
65 |
66 | @dataclass
67 | class RegisterFail(SvcRegisterResponse):
68 | message: Optional[str] = None
69 |
70 |
71 | @dataclass
72 | class RegisterSuccess(SvcRegisterResponse):
73 | response: SvcRespRegister
74 |
75 |
76 | @dataclass
77 | class MSFForceOfflineCommand(Command):
78 | @classmethod
79 | def decode_response(
80 | cls, uin: int, seq: int, ret_code: int, command_name: str, data: bytes
81 | ) -> "MSFForceOfflineCommand":
82 | """Decode StatSvc MSF Offline request.
83 |
84 | Note:
85 | Source: com.tencent.mobileqq.msf.core.af.a
86 |
87 | Args:
88 | uin (int): User QQ
89 | seq (int): Sequence number of the response packet.
90 | ret_code (int): Return code of the response.
91 | command_name (str): Command name of the response.
92 | data (bytes): Payload data of the response.
93 | """
94 | if ret_code != 0 or not data:
95 | return MSFForceOfflineCommand(uin, seq, ret_code, command_name)
96 |
97 | try:
98 | req_packet = RequestPacketVersion3.decode(data)
99 | msf_offline_request = RequestMSFForceOffline.decode(
100 | req_packet.data["RequestMSFForceOffline"][1:-1] # type: ignore
101 | )
102 | return MSFForceOffline(
103 | uin, seq, ret_code, command_name, msf_offline_request
104 | )
105 | except Exception as e:
106 | return MSFForceOfflineError(
107 | uin,
108 | seq,
109 | ret_code,
110 | command_name,
111 | f"Error when decoding response! {repr(e)}",
112 | )
113 |
114 |
115 | @dataclass
116 | class MSFForceOffline(MSFForceOfflineCommand):
117 | request: RequestMSFForceOffline
118 |
119 |
120 | @dataclass
121 | class MSFForceOfflineError(MSFForceOfflineCommand):
122 | message: str
123 |
--------------------------------------------------------------------------------
/cai/connection/__init__.py:
--------------------------------------------------------------------------------
1 | """Low Level Async TCP Connection
2 |
3 | This module is used to build a async TCP connection to target.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | import asyncio
12 | from types import TracebackType
13 | from typing import Any, Type, Union, Optional
14 |
15 | from cai.utils.binary import Packet
16 | from cai.utils.coroutine import ContextManager
17 |
18 |
19 | class Connection:
20 | def __init__(
21 | self,
22 | host: str,
23 | port: int,
24 | ssl: bool = False,
25 | timeout: Optional[float] = None,
26 | ) -> None:
27 | self._host = host
28 | self._port = port
29 | self._ssl = ssl
30 | self.timeout = timeout
31 |
32 | self._reader: Optional[asyncio.StreamReader] = None
33 | self._writer: Optional[asyncio.StreamWriter] = None
34 |
35 | @property
36 | def host(self) -> str:
37 | return self._host
38 |
39 | @property
40 | def port(self) -> int:
41 | return self._port
42 |
43 | @property
44 | def ssl(self) -> bool:
45 | return self._ssl
46 |
47 | @property
48 | def writer(self) -> asyncio.StreamWriter:
49 | if not self._writer:
50 | raise RuntimeError("Connection closed!")
51 | return self._writer
52 |
53 | @property
54 | def reader(self) -> asyncio.StreamReader:
55 | if not self._reader:
56 | raise RuntimeError("Connection closed!")
57 | return self._reader
58 |
59 | @property
60 | def closed(self) -> bool:
61 | return self._writer is None
62 |
63 | async def __aenter__(self):
64 | await self._connect()
65 | return self
66 |
67 | async def __aexit__(
68 | self,
69 | exc_type: Optional[Type[BaseException]],
70 | exc_value: Optional[BaseException],
71 | traceback: Optional[TracebackType],
72 | ) -> None:
73 | await self.close()
74 | return
75 |
76 | async def _connect(self):
77 | try:
78 | self._reader, self._writer = await asyncio.wait_for(
79 | asyncio.open_connection(self._host, self._port, ssl=self._ssl),
80 | self.timeout,
81 | )
82 | except Exception as e:
83 | if self._writer:
84 | self._writer.transport.close()
85 | self._reader = None
86 | self._writer = None
87 | raise ConnectionError(
88 | f"Open connection to {self._host}:{self._port} failed"
89 | ) from e
90 |
91 | async def close(self):
92 | if self._writer:
93 | self._writer.close()
94 | await self._writer.wait_closed()
95 | self._writer = None
96 | self._reader = None
97 |
98 | async def reconnect(self) -> None:
99 | await self.close()
100 | await self._connect()
101 |
102 | async def read_bytes(self, num_bytes: int):
103 | try:
104 | data = await self.reader.readexactly(num_bytes)
105 | except (asyncio.IncompleteReadError, IOError, OSError) as e:
106 | await self.close()
107 | raise ConnectionAbortedError(
108 | f"Lost connection to {self._host}:{self._port}"
109 | ) from e
110 | return data
111 |
112 | async def read_line(self):
113 | try:
114 | data = await self.reader.readline()
115 | except (asyncio.IncompleteReadError, IOError, OSError) as e:
116 | await self.close()
117 | raise ConnectionAbortedError(
118 | f"Lost connection to {self._host}:{self._port}"
119 | ) from e
120 | return data
121 |
122 | async def read_all(self):
123 | try:
124 | data = await self.reader.read(-1)
125 | except (asyncio.IncompleteReadError, IOError, OSError) as e:
126 | await self.close()
127 | raise ConnectionAbortedError(
128 | f"Lost connection to {self._host}:{self._port}"
129 | ) from e
130 | return data
131 |
132 | def write(self, data: Union[bytes, Packet]):
133 | self.writer.write(data) # type: ignore
134 |
135 | def write_eof(self):
136 | if self.writer.can_write_eof():
137 | self.writer.write_eof()
138 |
139 | async def awrite(self, data: Union[bytes, Packet]):
140 | self.writer.write(data) # type: ignore
141 | await self.writer.drain()
142 |
143 |
144 | def connect(
145 | host: str, port: int, ssl: bool = False, timeout: Optional[float] = None
146 | ) -> ContextManager[Any, Any, Connection]:
147 | coro = _connect(host, port, ssl=ssl, timeout=timeout)
148 | return ContextManager(coro)
149 |
150 |
151 | async def _connect(*args, **kwargs):
152 | conn = Connection(*args, **kwargs)
153 | await conn._connect()
154 | return conn
155 |
--------------------------------------------------------------------------------
/cai/connection/utils.py:
--------------------------------------------------------------------------------
1 | """Connection Related tools
2 |
3 | This module is used to build tcp latency test and other convenient tools.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | import math
12 | import time
13 |
14 | from . import connect
15 |
16 |
17 | async def tcp_latency_test(host: str, port: int, timeout: float = 5.0) -> float:
18 | start = time.time()
19 |
20 | try:
21 | async with connect(host, port, timeout=timeout):
22 | pass
23 | except ConnectionError:
24 | return math.inf
25 |
26 | end = time.time()
27 | return (end - start) * 1000
28 |
--------------------------------------------------------------------------------
/cai/exceptions.py:
--------------------------------------------------------------------------------
1 | """Application Exceptions
2 |
3 | This module is used to collect all application exceptions.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | from typing import Optional
12 |
13 |
14 | class CaiException(Exception):
15 | """Application Base Exception"""
16 |
17 | def __str__(self):
18 | return self.__repr__()
19 |
20 |
21 | # sso server
22 | class SsoServerException(CaiException):
23 | """Server Related Exception"""
24 |
25 |
26 | # api
27 | class ApiException(CaiException):
28 | """Base Exception for API"""
29 |
30 | def __init__(self, uin: Optional[int]):
31 | self.uin = uin
32 |
33 | def __repr__(self) -> str:
34 | return f"ApiException(uin={self.uin})"
35 |
36 |
37 | class ClientNotAvailable(ApiException):
38 | """Cannot get client"""
39 |
40 | def __init__(self, uin: Optional[int], reason: str):
41 | super().__init__(uin)
42 | self.reason = reason
43 |
44 | def __repr__(self) -> str:
45 | return f"ClientNotAvailable(uin={self.uin}, reason={self.reason})"
46 |
47 |
48 | class ApiResponseError(ApiException):
49 | """Receive a response with non zero return code."""
50 |
51 | def __init__(self, uin: int, seq: int, ret_code: int, command_name: str):
52 | super().__init__(uin)
53 | self.seq = seq
54 | self.ret_code = ret_code
55 | self.command_name = command_name
56 |
57 | def __repr__(self) -> str:
58 | return (
59 | f"ApiResponseError(uin={self.uin}, seq={self.seq}, "
60 | f"ret_code={self.ret_code}, command_name={self.command_name})"
61 | )
62 |
63 |
64 | # login api
65 | class LoginException(ApiException):
66 | """Base Exception for Login"""
67 |
68 | def __init__(self, uin: int, status: int, message: str = ""):
69 | self.uin = uin
70 | self.status = status
71 | self.message = message
72 |
73 | def __repr__(self) -> str:
74 | return f"LoginException(uin={self.uin}, status={self.status}, message={self.message})"
75 |
76 |
77 | class LoginSliderNeeded(LoginException):
78 | """Need Slider Ticket when Login"""
79 |
80 | def __init__(self, uin: int, verify_url: str):
81 | self.uin = uin
82 | self.verify_url = verify_url
83 |
84 | def __repr__(self) -> str:
85 | return f"LoginSliderException(uin={self.uin}, verify_url={self.verify_url})"
86 |
87 |
88 | class LoginCaptchaNeeded(LoginException):
89 | """Need Captcha Image when Login"""
90 |
91 | def __init__(self, uin: int, captcha_image: bytes, captcha_sign: bytes):
92 | self.uin = uin
93 | self.captcha_image = captcha_image
94 | self.captcha_sign = captcha_sign
95 |
96 | def __repr__(self) -> str:
97 | return (
98 | f"LoginCaptchaException(uin={self.uin}, captcha_image=)"
99 | )
100 |
101 |
102 | class LoginAccountFrozen(LoginException):
103 | """Account is already frozen"""
104 |
105 | def __init__(self, uin: int):
106 | self.uin = uin
107 |
108 | def __repr__(self) -> str:
109 | return f"LoginAccountFrozen(uin={self.uin})"
110 |
111 |
112 | class LoginDeviceLocked(LoginException):
113 | """Device lock checking is needed"""
114 |
115 | def __init__(
116 | self,
117 | uin: int,
118 | sms_phone: Optional[str],
119 | verify_url: Optional[str],
120 | message: Optional[str],
121 | ):
122 | self.uin = uin
123 | self.sms_phone = sms_phone
124 | self.verify_url = verify_url
125 | self.message = message
126 |
127 | def __repr__(self) -> str:
128 | return (
129 | f"LoginDeviceLocked(uin={self.uin}, message={self.message}, "
130 | f"sms_phone={self.sms_phone}, verify_url={self.verify_url})"
131 | )
132 |
133 |
134 | class LoginSMSRequestError(LoginException):
135 | """Too many sms verify request"""
136 |
137 | def __init__(self, uin: int):
138 | self.uin = uin
139 |
140 | def __repr__(self) -> str:
141 | return f"LoginSMSRequestError(uin={self.uin})"
142 |
143 |
144 | # register
145 | class RegisterException(ApiException):
146 | """Exception for Register"""
147 |
148 | def __init__(self, uin: int, status: int, message: str = ""):
149 | self.uin = uin
150 | self.status = status
151 | self.message = message
152 |
153 | def __repr__(self) -> str:
154 | return f"RegisterException(uin={self.uin}, status={self.status}, message={self.message})"
155 |
156 |
157 | # friendlist
158 | class BaseFriendListException(ApiException):
159 | """Base Exception for Friend List"""
160 |
161 |
162 | class FriendListException(BaseFriendListException):
163 | """Exception for Friend List"""
164 |
165 | def __init__(self, uin: int, status: int, message: str = ""):
166 | self.uin = uin
167 | self.status = status
168 | self.message = message
169 |
170 | def __repr__(self) -> str:
171 | return f"FriendListException(uin={self.uin}, status={self.status}, message={self.message})"
172 |
173 |
174 | class GroupListException(BaseFriendListException):
175 | """Exception for Group List"""
176 |
177 | def __init__(self, uin: int, status: int, message: str = ""):
178 | self.uin = uin
179 | self.status = status
180 | self.message = message
181 |
182 | def __repr__(self) -> str:
183 | return f"GroupListException(uin={self.uin}, status={self.status}, message={self.message})"
184 |
185 |
186 | class GroupMemberListException(BaseFriendListException):
187 | """Exception for Group Member List"""
188 |
189 | def __init__(self, uin: int, status: int, message: str = ""):
190 | self.uin = uin
191 | self.status = status
192 | self.message = message
193 |
194 | def __repr__(self) -> str:
195 | return (
196 | f"GroupMemberListException(uin={self.uin}, "
197 | f"status={self.status}, message={self.message})"
198 | )
199 |
--------------------------------------------------------------------------------
/cai/log.py:
--------------------------------------------------------------------------------
1 | """Application Logger
2 |
3 | This module is used to build application logger.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | import sys
12 | import logging
13 |
14 | logger = logging.getLogger("cai")
15 | logger.setLevel(logging.DEBUG)
16 | logger.addHandler(logging.StreamHandler(sys.stdout))
17 |
--------------------------------------------------------------------------------
/cai/pb/__init__.py:
--------------------------------------------------------------------------------
1 | """Protocol Buffer Model Category.
2 |
3 | This module is used to store all protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
--------------------------------------------------------------------------------
/cai/pb/im/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Protocol Buffer Model Category.
2 |
3 | This module is used to store all im protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/common/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .common_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/common/common.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package im.msg.common;
3 |
4 | // tencent/im/msg/im_common.java
5 |
6 | message GroupInfo {
7 | optional uint64 group_id = 1;
8 | optional uint32 group_type = 2;
9 | }
10 |
11 | message Signature {
12 | optional uint32 key_type = 1;
13 | optional uint32 session_app_id = 2;
14 | optional bytes session_key = 3;
15 | }
16 |
17 | message Token {
18 | optional bytes buf = 1;
19 | optional uint32 c2c_type = 2;
20 | optional uint32 service_type = 3;
21 | }
22 |
23 | message User {
24 | optional uint64 uin = 1;
25 | optional uint32 app_id = 2;
26 | optional uint32 instance_id = 3;
27 | optional uint32 app_type = 4;
28 | optional fixed32 client_ip = 5;
29 | optional uint32 version = 6;
30 | optional string phone_number = 7;
31 | optional uint32 platform_id = 8;
32 | optional uint32 language = 9;
33 | optional bytes equip_key = 10;
34 | }
35 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/common/common_pb2.pyi:
--------------------------------------------------------------------------------
1 | """
2 | @generated by mypy-protobuf. Do not edit manually!
3 | isort:skip_file
4 | """
5 | from builtins import (
6 | bool,
7 | bytes,
8 | int,
9 | )
10 |
11 | from google.protobuf.descriptor import (
12 | Descriptor,
13 | FileDescriptor,
14 | )
15 |
16 | from google.protobuf.message import (
17 | Message,
18 | )
19 |
20 | from typing import (
21 | Optional,
22 | Text,
23 | )
24 |
25 | from typing_extensions import (
26 | Literal,
27 | )
28 |
29 |
30 | DESCRIPTOR: FileDescriptor = ...
31 |
32 | class GroupInfo(Message):
33 | DESCRIPTOR: Descriptor = ...
34 | GROUP_ID_FIELD_NUMBER: int
35 | GROUP_TYPE_FIELD_NUMBER: int
36 | group_id: int = ...
37 | group_type: int = ...
38 |
39 | def __init__(self,
40 | *,
41 | group_id : Optional[int] = ...,
42 | group_type : Optional[int] = ...,
43 | ) -> None: ...
44 | def HasField(self, field_name: Literal[u"group_id",b"group_id",u"group_type",b"group_type"]) -> bool: ...
45 | def ClearField(self, field_name: Literal[u"group_id",b"group_id",u"group_type",b"group_type"]) -> None: ...
46 |
47 | class Signature(Message):
48 | DESCRIPTOR: Descriptor = ...
49 | KEY_TYPE_FIELD_NUMBER: int
50 | SESSION_APP_ID_FIELD_NUMBER: int
51 | SESSION_KEY_FIELD_NUMBER: int
52 | key_type: int = ...
53 | session_app_id: int = ...
54 | session_key: bytes = ...
55 |
56 | def __init__(self,
57 | *,
58 | key_type : Optional[int] = ...,
59 | session_app_id : Optional[int] = ...,
60 | session_key : Optional[bytes] = ...,
61 | ) -> None: ...
62 | def HasField(self, field_name: Literal[u"key_type",b"key_type",u"session_app_id",b"session_app_id",u"session_key",b"session_key"]) -> bool: ...
63 | def ClearField(self, field_name: Literal[u"key_type",b"key_type",u"session_app_id",b"session_app_id",u"session_key",b"session_key"]) -> None: ...
64 |
65 | class Token(Message):
66 | DESCRIPTOR: Descriptor = ...
67 | BUF_FIELD_NUMBER: int
68 | C2C_TYPE_FIELD_NUMBER: int
69 | SERVICE_TYPE_FIELD_NUMBER: int
70 | buf: bytes = ...
71 | c2c_type: int = ...
72 | service_type: int = ...
73 |
74 | def __init__(self,
75 | *,
76 | buf : Optional[bytes] = ...,
77 | c2c_type : Optional[int] = ...,
78 | service_type : Optional[int] = ...,
79 | ) -> None: ...
80 | def HasField(self, field_name: Literal[u"buf",b"buf",u"c2c_type",b"c2c_type",u"service_type",b"service_type"]) -> bool: ...
81 | def ClearField(self, field_name: Literal[u"buf",b"buf",u"c2c_type",b"c2c_type",u"service_type",b"service_type"]) -> None: ...
82 |
83 | class User(Message):
84 | DESCRIPTOR: Descriptor = ...
85 | UIN_FIELD_NUMBER: int
86 | APP_ID_FIELD_NUMBER: int
87 | INSTANCE_ID_FIELD_NUMBER: int
88 | APP_TYPE_FIELD_NUMBER: int
89 | CLIENT_IP_FIELD_NUMBER: int
90 | VERSION_FIELD_NUMBER: int
91 | PHONE_NUMBER_FIELD_NUMBER: int
92 | PLATFORM_ID_FIELD_NUMBER: int
93 | LANGUAGE_FIELD_NUMBER: int
94 | EQUIP_KEY_FIELD_NUMBER: int
95 | uin: int = ...
96 | app_id: int = ...
97 | instance_id: int = ...
98 | app_type: int = ...
99 | client_ip: int = ...
100 | version: int = ...
101 | phone_number: Text = ...
102 | platform_id: int = ...
103 | language: int = ...
104 | equip_key: bytes = ...
105 |
106 | def __init__(self,
107 | *,
108 | uin : Optional[int] = ...,
109 | app_id : Optional[int] = ...,
110 | instance_id : Optional[int] = ...,
111 | app_type : Optional[int] = ...,
112 | client_ip : Optional[int] = ...,
113 | version : Optional[int] = ...,
114 | phone_number : Optional[Text] = ...,
115 | platform_id : Optional[int] = ...,
116 | language : Optional[int] = ...,
117 | equip_key : Optional[bytes] = ...,
118 | ) -> None: ...
119 | def HasField(self, field_name: Literal[u"app_id",b"app_id",u"app_type",b"app_type",u"client_ip",b"client_ip",u"equip_key",b"equip_key",u"instance_id",b"instance_id",u"language",b"language",u"phone_number",b"phone_number",u"platform_id",b"platform_id",u"uin",b"uin",u"version",b"version"]) -> bool: ...
120 | def ClearField(self, field_name: Literal[u"app_id",b"app_id",u"app_type",b"app_type",u"client_ip",b"client_ip",u"equip_key",b"equip_key",u"instance_id",b"instance_id",u"language",b"language",u"phone_number",b"phone_number",u"platform_id",b"platform_id",u"uin",b"uin",u"version",b"version"]) -> None: ...
121 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/msg/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .msg_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/msg/msg.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package im.msg.msg;
3 |
4 | // tencent/im/msg/im_msg.java
5 |
6 | import "cai/pb/im/msg/common/common.proto";
7 | import "cai/pb/im/msg/msg_body/msg_body.proto";
8 |
9 | message C2C {
10 | optional common.User sender = 1;
11 | optional common.User receiver = 2;
12 | optional C2CRelation c2c_relation = 3;
13 | }
14 |
15 | message C2CRelation {
16 | optional uint32 c2c_type = 1;
17 | optional common.GroupInfo group_info = 2;
18 | optional common.Token token = 3;
19 | }
20 |
21 | message ContentHead {
22 | optional uint32 pkg_num = 1;
23 | optional uint32 pkg_index = 2;
24 | optional uint32 seq = 3;
25 | optional uint32 date_time = 4;
26 | optional uint32 type = 5;
27 | optional uint32 div_seq = 6;
28 | optional uint64 msgdb_uin = 7;
29 | optional uint32 msgdb_seq = 8;
30 | optional uint32 word_msg_seq = 9;
31 | optional uint32 rand = 10;
32 | }
33 |
34 | message Group {
35 | optional common.User sender = 1;
36 | optional common.User receiver = 2;
37 | optional common.GroupInfo group_info = 3;
38 | }
39 |
40 | message Msg {
41 | optional MsgHead head = 1;
42 | optional msg_body.MsgBody body = 2;
43 | }
44 |
45 | message MsgHead {
46 | optional RoutingHead routing_head = 1;
47 | optional ContentHead content_head = 2;
48 | optional bytes gbk_tmp_msg_body = 3;
49 | }
50 |
51 | message MsgSendReq {
52 | optional Msg msg = 1;
53 | optional bytes bu_msg = 2;
54 | optional uint32 tail_id = 3;
55 | optional uint32 conn_msg_flag = 4;
56 | optional bytes cookie = 5;
57 | }
58 |
59 | message MsgSendResp {
60 | }
61 |
62 | message RoutingHead {
63 | optional C2C c2_c = 1;
64 | optional Group group = 2;
65 | }
66 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/msg_body/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .msg_body_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/msg_head/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .msg_head_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/msg_head/msg_head.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package im.msg.msg_head;
3 |
4 | // tencent/im/msg/im_msg_head.java
5 |
6 | message C2CHead {
7 | optional uint64 to_uin = 1;
8 | optional uint64 from_uin = 2;
9 | optional uint32 cc_type = 3;
10 | optional uint32 cc_cmd = 4;
11 | optional bytes auth_pic_sig = 5;
12 | optional bytes auth_sig = 6;
13 | optional bytes auth_buf = 7;
14 | optional uint32 server_time = 8;
15 | optional uint32 client_time = 9;
16 | optional uint32 rand = 10;
17 | optional string phone_number = 11;
18 | }
19 |
20 | message CSHead {
21 | optional uint64 uin = 1;
22 | optional uint32 command = 2;
23 | optional uint32 seq = 3;
24 | optional uint32 version = 4;
25 | optional uint32 retry_times = 5;
26 | optional uint32 client_type = 6;
27 | optional uint32 pubno = 7;
28 | optional uint32 localid = 8;
29 | optional uint32 timezone = 9;
30 | optional fixed32 client_ip = 10;
31 | optional uint32 client_port = 11;
32 | optional fixed32 conn_ip = 12;
33 | optional uint32 conn_port = 13;
34 | optional fixed32 interface_ip = 14;
35 | optional uint32 interface_port = 15;
36 | optional fixed32 actual_ip = 16;
37 | optional uint32 flag = 17;
38 | optional fixed32 timestamp = 18;
39 | optional uint32 subcmd = 19;
40 | optional uint32 result = 20;
41 | optional uint32 app_id = 21;
42 | optional uint32 instance_id = 22;
43 | optional uint64 session_id = 23;
44 | optional uint32 idc_id = 24;
45 | }
46 |
47 | message DeltaHead {
48 | optional uint64 total_len = 1;
49 | optional uint64 offset = 2;
50 | optional uint64 ack_offset = 3;
51 | optional bytes cookie = 4;
52 | optional bytes ack_cookie = 5;
53 | optional uint32 result = 6;
54 | optional uint32 flags = 7;
55 | }
56 |
57 | message Head {
58 | optional uint32 head_type = 1;
59 | optional CSHead cs_head = 2;
60 | optional S2CHead s2c_head = 3;
61 | optional HttpConnHead httpconn_head = 4;
62 | optional uint32 paint_flag = 5;
63 | optional LoginSig login_sig = 6;
64 | optional DeltaHead delta_head = 7;
65 | optional C2CHead c2c_head = 8;
66 | optional SConnHead sconn_head = 9;
67 | optional InstCtrl inst_ctrl = 10;
68 | }
69 |
70 | message HttpConnHead {
71 | optional uint64 uin = 1;
72 | optional uint32 command = 2;
73 | optional uint32 sub_command = 3;
74 | optional uint32 seq = 4;
75 | optional uint32 version = 5;
76 | optional uint32 retry_times = 6;
77 | optional uint32 client_type = 7;
78 | optional uint32 pub_no = 8;
79 | optional uint32 local_id = 9;
80 | optional uint32 time_zone = 10;
81 | optional fixed32 client_ip = 11;
82 | optional uint32 client_port = 12;
83 | optional fixed32 qzhttp_ip = 13;
84 | optional uint32 qzhttp_port = 14;
85 | optional fixed32 spp_ip = 15;
86 | optional uint32 spp_port = 16;
87 | optional uint32 flag = 17;
88 | optional bytes key = 18;
89 | optional uint32 compress_type = 19;
90 | optional uint32 origin_size = 20;
91 | optional uint32 error_code = 21;
92 | optional RedirectMsg redirect = 22;
93 | optional uint32 command_id = 23;
94 | optional uint32 service_cmdid = 24;
95 | optional TransOidbHead oidbhead = 25;
96 | }
97 |
98 | message InstCtrl {
99 | repeated InstInfo send_to_inst = 1;
100 | repeated InstInfo exclude_inst = 2;
101 | optional InstInfo from_inst = 3;
102 | }
103 |
104 | message InstInfo {
105 | optional uint32 apppid = 1;
106 | optional uint32 instid = 2;
107 | optional uint32 platform = 3;
108 | optional uint32 enum_device_type = 10;
109 | }
110 |
111 | message LoginSig {
112 | optional uint32 type = 1;
113 | optional bytes sig = 2;
114 | }
115 |
116 | message RedirectMsg {
117 | optional fixed32 last_redirect_ip = 1;
118 | optional uint32 last_redirect_port = 2;
119 | optional fixed32 redirect_ip = 3;
120 | optional uint32 redirect_port = 4;
121 | optional uint32 redirect_count = 5;
122 | }
123 |
124 | message S2CHead {
125 | optional uint32 sub_msgtype = 1;
126 | optional uint32 msg_type = 2;
127 | optional uint64 from_uin = 3;
128 | optional uint32 msg_id = 4;
129 | optional fixed32 relay_ip = 5;
130 | optional uint32 relay_port = 6;
131 | optional uint64 to_uin = 7;
132 | }
133 |
134 | message SConnHead {
135 | }
136 |
137 | message TransOidbHead {
138 | optional uint32 command = 1;
139 | optional uint32 service_type = 2;
140 | optional uint32 result = 3;
141 | optional string error_msg = 4;
142 | }
143 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/obj_msg/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .obj_msg_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/obj_msg/obj_msg.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package im.msg.obj_msg;
3 |
4 | // tencent/im/msg/obj_msg.java
5 |
6 | message MsgContentInfo {
7 | optional bytes content_info_id = 1;
8 | optional MsgFile file = 2;
9 | }
10 |
11 | message MsgFile {
12 | optional uint32 bus_id = 1;
13 | optional bytes file_path = 2;
14 | optional uint64 file_size = 3;
15 | optional string file_name = 4;
16 | optional int64 dead_time = 5;
17 | optional bytes file_sha1 = 6;
18 | optional bytes ext = 7;
19 | optional bytes file_md5 = 8;
20 | }
21 |
22 | message MsgPic {
23 | optional bytes small_pic_url = 1;
24 | optional bytes original_pic_url = 2;
25 | optional uint32 local_pic_id = 3;
26 | }
27 |
28 | message ObjMsg {
29 | optional uint32 msg_type = 1;
30 | optional bytes title = 2;
31 | repeated bytes abstact = 3;
32 | optional bytes title_ext = 5;
33 | repeated MsgPic pic = 6;
34 | repeated MsgContentInfo content_info = 7;
35 | optional uint32 report_id_show = 8;
36 | }
37 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/obj_msg/obj_msg_pb2.pyi:
--------------------------------------------------------------------------------
1 | """
2 | @generated by mypy-protobuf. Do not edit manually!
3 | isort:skip_file
4 | """
5 | from builtins import (
6 | bool,
7 | bytes,
8 | int,
9 | )
10 |
11 | from google.protobuf.descriptor import (
12 | Descriptor,
13 | FileDescriptor,
14 | )
15 |
16 | from google.protobuf.internal.containers import (
17 | RepeatedCompositeFieldContainer,
18 | RepeatedScalarFieldContainer,
19 | )
20 |
21 | from google.protobuf.message import (
22 | Message,
23 | )
24 |
25 | from typing import (
26 | Iterable,
27 | Optional,
28 | Text,
29 | )
30 |
31 | from typing_extensions import (
32 | Literal,
33 | )
34 |
35 |
36 | DESCRIPTOR: FileDescriptor = ...
37 |
38 | class MsgContentInfo(Message):
39 | DESCRIPTOR: Descriptor = ...
40 | CONTENT_INFO_ID_FIELD_NUMBER: int
41 | FILE_FIELD_NUMBER: int
42 | content_info_id: bytes = ...
43 |
44 | @property
45 | def file(self) -> MsgFile: ...
46 |
47 | def __init__(self,
48 | *,
49 | content_info_id : Optional[bytes] = ...,
50 | file : Optional[MsgFile] = ...,
51 | ) -> None: ...
52 | def HasField(self, field_name: Literal[u"content_info_id",b"content_info_id",u"file",b"file"]) -> bool: ...
53 | def ClearField(self, field_name: Literal[u"content_info_id",b"content_info_id",u"file",b"file"]) -> None: ...
54 |
55 | class MsgFile(Message):
56 | DESCRIPTOR: Descriptor = ...
57 | BUS_ID_FIELD_NUMBER: int
58 | FILE_PATH_FIELD_NUMBER: int
59 | FILE_SIZE_FIELD_NUMBER: int
60 | FILE_NAME_FIELD_NUMBER: int
61 | DEAD_TIME_FIELD_NUMBER: int
62 | FILE_SHA1_FIELD_NUMBER: int
63 | EXT_FIELD_NUMBER: int
64 | FILE_MD5_FIELD_NUMBER: int
65 | bus_id: int = ...
66 | file_path: bytes = ...
67 | file_size: int = ...
68 | file_name: Text = ...
69 | dead_time: int = ...
70 | file_sha1: bytes = ...
71 | ext: bytes = ...
72 | file_md5: bytes = ...
73 |
74 | def __init__(self,
75 | *,
76 | bus_id : Optional[int] = ...,
77 | file_path : Optional[bytes] = ...,
78 | file_size : Optional[int] = ...,
79 | file_name : Optional[Text] = ...,
80 | dead_time : Optional[int] = ...,
81 | file_sha1 : Optional[bytes] = ...,
82 | ext : Optional[bytes] = ...,
83 | file_md5 : Optional[bytes] = ...,
84 | ) -> None: ...
85 | def HasField(self, field_name: Literal[u"bus_id",b"bus_id",u"dead_time",b"dead_time",u"ext",b"ext",u"file_md5",b"file_md5",u"file_name",b"file_name",u"file_path",b"file_path",u"file_sha1",b"file_sha1",u"file_size",b"file_size"]) -> bool: ...
86 | def ClearField(self, field_name: Literal[u"bus_id",b"bus_id",u"dead_time",b"dead_time",u"ext",b"ext",u"file_md5",b"file_md5",u"file_name",b"file_name",u"file_path",b"file_path",u"file_sha1",b"file_sha1",u"file_size",b"file_size"]) -> None: ...
87 |
88 | class MsgPic(Message):
89 | DESCRIPTOR: Descriptor = ...
90 | SMALL_PIC_URL_FIELD_NUMBER: int
91 | ORIGINAL_PIC_URL_FIELD_NUMBER: int
92 | LOCAL_PIC_ID_FIELD_NUMBER: int
93 | small_pic_url: bytes = ...
94 | original_pic_url: bytes = ...
95 | local_pic_id: int = ...
96 |
97 | def __init__(self,
98 | *,
99 | small_pic_url : Optional[bytes] = ...,
100 | original_pic_url : Optional[bytes] = ...,
101 | local_pic_id : Optional[int] = ...,
102 | ) -> None: ...
103 | def HasField(self, field_name: Literal[u"local_pic_id",b"local_pic_id",u"original_pic_url",b"original_pic_url",u"small_pic_url",b"small_pic_url"]) -> bool: ...
104 | def ClearField(self, field_name: Literal[u"local_pic_id",b"local_pic_id",u"original_pic_url",b"original_pic_url",u"small_pic_url",b"small_pic_url"]) -> None: ...
105 |
106 | class ObjMsg(Message):
107 | DESCRIPTOR: Descriptor = ...
108 | MSG_TYPE_FIELD_NUMBER: int
109 | TITLE_FIELD_NUMBER: int
110 | ABSTACT_FIELD_NUMBER: int
111 | TITLE_EXT_FIELD_NUMBER: int
112 | PIC_FIELD_NUMBER: int
113 | CONTENT_INFO_FIELD_NUMBER: int
114 | REPORT_ID_SHOW_FIELD_NUMBER: int
115 | msg_type: int = ...
116 | title: bytes = ...
117 | abstact: RepeatedScalarFieldContainer[bytes] = ...
118 | title_ext: bytes = ...
119 | report_id_show: int = ...
120 |
121 | @property
122 | def pic(self) -> RepeatedCompositeFieldContainer[MsgPic]: ...
123 |
124 | @property
125 | def content_info(self) -> RepeatedCompositeFieldContainer[MsgContentInfo]: ...
126 |
127 | def __init__(self,
128 | *,
129 | msg_type : Optional[int] = ...,
130 | title : Optional[bytes] = ...,
131 | abstact : Optional[Iterable[bytes]] = ...,
132 | title_ext : Optional[bytes] = ...,
133 | pic : Optional[Iterable[MsgPic]] = ...,
134 | content_info : Optional[Iterable[MsgContentInfo]] = ...,
135 | report_id_show : Optional[int] = ...,
136 | ) -> None: ...
137 | def HasField(self, field_name: Literal[u"msg_type",b"msg_type",u"report_id_show",b"report_id_show",u"title",b"title",u"title_ext",b"title_ext"]) -> bool: ...
138 | def ClearField(self, field_name: Literal[u"abstact",b"abstact",u"content_info",b"content_info",u"msg_type",b"msg_type",u"pic",b"pic",u"report_id_show",b"report_id_show",u"title",b"title",u"title_ext",b"title_ext"]) -> None: ...
139 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/receipt/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .receipt_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/receipt/receipt.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package im.msg.receipt;
3 |
4 | message MsgInfo {
5 | optional uint64 from_uin = 1;
6 | optional uint64 to_uin = 2;
7 | optional uint32 msg_seq = 3;
8 | optional uint32 msg_random = 4;
9 | }
10 |
11 | message ReceiptInfo {
12 | optional uint64 read_time = 1;
13 | }
14 |
15 | message ReceiptReq {
16 | optional uint32 command = 1;
17 | optional MsgInfo info = 2;
18 | }
19 |
20 | message ReceiptResp {
21 | optional uint32 command = 1;
22 | optional ReceiptInfo receipt_info = 2;
23 | }
24 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/receipt/receipt_pb2.pyi:
--------------------------------------------------------------------------------
1 | """
2 | @generated by mypy-protobuf. Do not edit manually!
3 | isort:skip_file
4 | """
5 | from builtins import (
6 | bool,
7 | int,
8 | )
9 |
10 | from google.protobuf.descriptor import (
11 | Descriptor,
12 | FileDescriptor,
13 | )
14 |
15 | from google.protobuf.message import (
16 | Message,
17 | )
18 |
19 | from typing import (
20 | Optional,
21 | )
22 |
23 | from typing_extensions import (
24 | Literal,
25 | )
26 |
27 |
28 | DESCRIPTOR: FileDescriptor = ...
29 |
30 | class MsgInfo(Message):
31 | DESCRIPTOR: Descriptor = ...
32 | FROM_UIN_FIELD_NUMBER: int
33 | TO_UIN_FIELD_NUMBER: int
34 | MSG_SEQ_FIELD_NUMBER: int
35 | MSG_RANDOM_FIELD_NUMBER: int
36 | from_uin: int = ...
37 | to_uin: int = ...
38 | msg_seq: int = ...
39 | msg_random: int = ...
40 |
41 | def __init__(self,
42 | *,
43 | from_uin : Optional[int] = ...,
44 | to_uin : Optional[int] = ...,
45 | msg_seq : Optional[int] = ...,
46 | msg_random : Optional[int] = ...,
47 | ) -> None: ...
48 | def HasField(self, field_name: Literal[u"from_uin",b"from_uin",u"msg_random",b"msg_random",u"msg_seq",b"msg_seq",u"to_uin",b"to_uin"]) -> bool: ...
49 | def ClearField(self, field_name: Literal[u"from_uin",b"from_uin",u"msg_random",b"msg_random",u"msg_seq",b"msg_seq",u"to_uin",b"to_uin"]) -> None: ...
50 |
51 | class ReceiptInfo(Message):
52 | DESCRIPTOR: Descriptor = ...
53 | READ_TIME_FIELD_NUMBER: int
54 | read_time: int = ...
55 |
56 | def __init__(self,
57 | *,
58 | read_time : Optional[int] = ...,
59 | ) -> None: ...
60 | def HasField(self, field_name: Literal[u"read_time",b"read_time"]) -> bool: ...
61 | def ClearField(self, field_name: Literal[u"read_time",b"read_time"]) -> None: ...
62 |
63 | class ReceiptReq(Message):
64 | DESCRIPTOR: Descriptor = ...
65 | COMMAND_FIELD_NUMBER: int
66 | INFO_FIELD_NUMBER: int
67 | command: int = ...
68 |
69 | @property
70 | def info(self) -> MsgInfo: ...
71 |
72 | def __init__(self,
73 | *,
74 | command : Optional[int] = ...,
75 | info : Optional[MsgInfo] = ...,
76 | ) -> None: ...
77 | def HasField(self, field_name: Literal[u"command",b"command",u"info",b"info"]) -> bool: ...
78 | def ClearField(self, field_name: Literal[u"command",b"command",u"info",b"info"]) -> None: ...
79 |
80 | class ReceiptResp(Message):
81 | DESCRIPTOR: Descriptor = ...
82 | COMMAND_FIELD_NUMBER: int
83 | RECEIPT_INFO_FIELD_NUMBER: int
84 | command: int = ...
85 |
86 | @property
87 | def receipt_info(self) -> ReceiptInfo: ...
88 |
89 | def __init__(self,
90 | *,
91 | command : Optional[int] = ...,
92 | receipt_info : Optional[ReceiptInfo] = ...,
93 | ) -> None: ...
94 | def HasField(self, field_name: Literal[u"command",b"command",u"receipt_info",b"receipt_info"]) -> bool: ...
95 | def ClearField(self, field_name: Literal[u"command",b"command",u"receipt_info",b"receipt_info"]) -> None: ...
96 |
--------------------------------------------------------------------------------
/cai/pb/im/msg/service/comm_elem/__init__.py:
--------------------------------------------------------------------------------
1 | """IM Message Protocol Buffer Model Category.
2 |
3 | This module is used to store all im message protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .comm_elem_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/im/oidb/__init__.py:
--------------------------------------------------------------------------------
1 | """IM OIDB Protocol Buffer Model Category.
2 |
3 | This module is used to store all oidb protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
--------------------------------------------------------------------------------
/cai/pb/im/oidb/cmd0x769/__init__.py:
--------------------------------------------------------------------------------
1 | from .cmd0x769_pb2 import *
2 |
--------------------------------------------------------------------------------
/cai/pb/im/oidb/cmd0x769/cmd0x769.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package im.oidb.cmd0x769;
3 |
4 | // tencent/im/oidb/cmd0xd50/Oidb_0x769.java
5 |
6 | message CPU {
7 | optional string model = 1;
8 | optional uint32 cores = 2;
9 | optional uint32 frequency = 3;
10 | }
11 |
12 | message Camera {
13 | optional uint64 primary = 1;
14 | optional uint64 secondary = 2;
15 | optional bool flash = 3;
16 | }
17 |
18 | message Config {
19 | optional uint32 type = 1;
20 | optional uint32 version = 2;
21 | repeated string content_list = 3;
22 | optional string debug_msg = 4;
23 | repeated Content msg_content_list = 5;
24 | }
25 |
26 | message ConfigSeq {
27 | optional uint32 type = 1;
28 | optional uint32 version = 2;
29 | }
30 |
31 | message Content {
32 | optional uint32 task_id = 1;
33 | optional uint32 compress = 2;
34 | optional bytes content = 10;
35 | }
36 |
37 | message DeviceInfo {
38 | optional string brand = 1;
39 | optional string model = 2;
40 | optional OS os = 3;
41 | optional CPU cpu = 4;
42 | optional Memory memory = 5;
43 | optional Storage storage = 6;
44 | optional Screen screen = 7;
45 | optional Camera camera = 8;
46 | }
47 |
48 | message Memory {
49 | optional uint64 total = 1;
50 | optional uint64 process = 2;
51 | }
52 |
53 | message OS {
54 | optional uint32 type = 1;
55 | optional string version = 2;
56 | optional string sdk = 3;
57 | optional string kernel = 4;
58 | optional string rom = 5;
59 | }
60 |
61 | message QueryUinPackageUsageReq {
62 | optional uint32 type = 1;
63 | optional uint64 uin_file_size = 2;
64 | }
65 |
66 | message QueryUinPackageUsageRsp {
67 | optional uint32 status = 1;
68 | optional uint64 left_uin_num = 2;
69 | optional uint64 max_uin_num = 3;
70 | optional uint32 proportion = 4;
71 | repeated UinPackageUsedInfo uin_package_used_list = 10;
72 | }
73 |
74 | message ReqBody {
75 | repeated ConfigSeq config_list = 1;
76 | optional DeviceInfo device_info = 2;
77 | optional string info = 3;
78 | optional string province = 4;
79 | optional string city = 5;
80 | optional int32 req_debug_msg = 6;
81 | optional QueryUinPackageUsageReq query_uin_package_usage_req = 101;
82 | }
83 |
84 | message RspBody {
85 | optional uint32 result = 1;
86 | repeated Config config_list = 2;
87 | optional QueryUinPackageUsageRsp query_uin_package_usage_rsp = 101;
88 | }
89 |
90 | message Screen {
91 | optional string model = 1;
92 | optional uint32 width = 2;
93 | optional uint32 height = 3;
94 | optional uint32 dpi = 4;
95 | optional bool multi_touch = 5;
96 | }
97 |
98 | message Storage {
99 | optional uint64 builtin = 1;
100 | optional uint64 external = 2;
101 | }
102 |
103 | message UinPackageUsedInfo {
104 | optional uint32 rule_id = 1;
105 | optional string author = 2;
106 | optional string url = 3;
107 | optional uint64 uin_num = 4;
108 | }
109 |
--------------------------------------------------------------------------------
/cai/pb/im/oidb/cmd0xd50/__init__.py:
--------------------------------------------------------------------------------
1 | from .cmd0xd50_pb2 import *
2 |
--------------------------------------------------------------------------------
/cai/pb/im/oidb/cmd0xd50/cmd0xd50.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package im.oidb.cmd0xd50;
3 |
4 | // tencent/im/oidb/cmd0xd50/Oidb_0xd50.java
5 |
6 | message ExtSnsFrdData {
7 | optional uint64 frd_uin = 1;
8 | optional bytes lovers = 11002;
9 | optional bytes confidante = 21002;
10 | optional bytes buddy = 31002;
11 | optional bytes frd_tree = 41001;
12 | optional bytes chat = 51001;
13 | optional bytes praise = 61001;
14 | optional bytes qzone_love = 71001;
15 | optional bytes qzone_house = 81001;
16 | optional bytes music_switch = 91001;
17 | optional bytes mutualmark_alienation = 101001;
18 | optional bytes unread_message = 111001;
19 | optional bytes boat = 121001;
20 | optional bytes close_frd = 131001;
21 | optional bytes mutualmark_score = 141001;
22 | optional bytes ksing_switch = 151001;
23 | optional bytes lbs_share = 181001;
24 | optional bytes dont_forget_me = 211001;
25 | optional bytes my_online_status_visible_to_frd = 221001;
26 | optional bytes frd_online_status_visible_to_me = 221002;
27 | optional bytes visitor_record = 231001;
28 | optional bytes frd_steal_record = 231002;
29 | optional bytes my_steal_record = 231003;
30 | optional bytes avgame = 241001;
31 | optional bytes aio_quick_app = 251001;
32 | }
33 |
34 | message KSingRelationInfo {
35 | optional uint32 flag = 1;
36 | }
37 |
38 | message ReqBody {
39 | optional uint64 appid = 1;
40 | optional uint32 max_pkg_size = 2;
41 | optional uint32 start_time = 3;
42 | optional uint32 start_index = 4;
43 | optional uint32 req_num = 5;
44 | repeated uint64 uin_list = 6;
45 | optional uint32 req_lovers = 11002;
46 | optional uint32 req_confidante = 21002;
47 | optional uint32 req_buddy = 31002;
48 | optional uint32 req_frd_tree = 41001;
49 | optional uint32 req_chat = 51001;
50 | optional uint32 req_praise = 61001;
51 | optional uint32 req_qzone_love = 71001;
52 | optional uint32 req_qzone_house = 81001;
53 | optional uint32 req_music_switch = 91001;
54 | optional uint32 req_mutualmark_alienation = 101001;
55 | optional uint32 req_unread_message = 111001;
56 | optional uint32 req_boat = 121001;
57 | optional uint32 req_close_frd = 131001;
58 | optional uint32 req_mutualmark_score = 141001;
59 | optional uint32 req_ksing_switch = 151001;
60 | optional uint32 req_mutualmark_lbsshare = 181001;
61 | optional uint32 req_dont_forget_me = 211001;
62 | optional uint32 req_my_online_status_visible_to_frd = 221001;
63 | optional uint32 req_frd_online_status_visible_to_me = 221002;
64 | optional uint32 req_visitor_record = 231001;
65 | optional uint32 req_frd_steal_record = 231002;
66 | optional uint32 req_my_steal_record = 231003;
67 | optional uint32 req_avgame = 241001;
68 | optional uint32 req_aio_quick_app = 251001;
69 | }
70 |
71 | message RspBody {
72 | repeated ExtSnsFrdData update_data = 1;
73 | optional uint32 over = 11;
74 | optional uint32 next_start = 12;
75 | repeated uint64 unfinished_uins = 13;
76 | }
77 |
--------------------------------------------------------------------------------
/cai/pb/msf/__init__.py:
--------------------------------------------------------------------------------
1 | """MSF Protocol Buffer Model Category.
2 |
3 | This module is used to store all msf protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/__init__.py:
--------------------------------------------------------------------------------
1 | """Message Service Protocol Buffer Model Category.
2 |
3 | This module is used to store all message service protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/comm/__init__.py:
--------------------------------------------------------------------------------
1 | """Message Service Protocol Buffer Model Category.
2 |
3 | This module is used to store all message service protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .comm_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/comm/comm.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package msf.msg.comm;
3 |
4 | // msf/msgcomm/msg_comm.java
5 |
6 | import "cai/pb/im/msg/msg_body/msg_body.proto";
7 | import "cai/pb/im/msg/msg_head/msg_head.proto";
8 |
9 | message AppShareInfo {
10 | optional uint32 appshare_id = 1;
11 | optional bytes appshare_cookie = 2;
12 | optional PluginInfo appshare_resource = 3;
13 | }
14 |
15 | message C2CTmpMsgHead {
16 | optional uint32 c2c_type = 1;
17 | optional uint32 service_type = 2;
18 | optional uint64 group_uin = 3;
19 | optional uint64 group_code = 4;
20 | optional bytes sig = 5;
21 | optional uint32 sig_type = 6;
22 | optional string from_phone = 7;
23 | optional string to_phone = 8;
24 | optional uint32 lock_display = 9;
25 | optional uint32 direction_flag = 10;
26 | optional bytes reserved = 11;
27 | optional bytes business_name = 14;
28 | optional bytes business_sub_content = 15;
29 | }
30 |
31 | message ContentHead {
32 | optional uint32 pkg_num = 1;
33 | optional uint32 pkg_index = 2;
34 | optional uint32 div_seq = 3;
35 | optional uint32 auto_reply = 4;
36 | }
37 |
38 | message DiscussInfo {
39 | optional uint64 discuss_uin = 1;
40 | optional uint32 discuss_type = 2;
41 | optional uint64 discuss_info_seq = 3;
42 | optional bytes discuss_remark = 4;
43 | optional bytes discuss_name = 5;
44 | }
45 |
46 | message ExtGroupKeyInfo {
47 | optional uint32 cur_max_seq = 1;
48 | optional uint64 cur_time = 2;
49 | optional uint32 operate_by_parents = 3;
50 | optional bytes ext_group_info = 4;
51 | }
52 |
53 | message GroupInfo {
54 | optional uint64 group_code = 1;
55 | optional uint32 group_type = 2;
56 | optional uint64 group_info_seq = 3;
57 | optional bytes group_card = 4;
58 | optional bytes group_rank = 5;
59 | optional uint32 group_level = 6;
60 | optional uint32 group_card_type = 7;
61 | optional bytes group_name = 8;
62 | }
63 |
64 | message Msg {
65 | optional MsgHead head = 1;
66 | optional ContentHead content_head = 2;
67 | optional im.msg.msg_body.MsgBody body = 3;
68 | optional AppShareInfo appshare_info = 4;
69 | }
70 |
71 | message MsgHead {
72 | optional uint64 from_uin = 1;
73 | optional uint64 to_uin = 2;
74 | optional uint32 type = 3;
75 | optional uint32 c2c_cmd = 4;
76 | optional uint32 seq = 5;
77 | optional uint32 time = 6;
78 | optional uint64 uid = 7;
79 | optional C2CTmpMsgHead c2c_tmp_msg_head = 8;
80 | optional GroupInfo group_info = 9;
81 | optional uint32 from_appid = 10;
82 | optional uint32 from_instid = 11;
83 | optional uint32 user_active = 12;
84 | optional DiscussInfo discuss_info = 13;
85 | optional string from_nick = 14;
86 | optional uint64 auth_uin = 15;
87 | optional string auth_nick = 16;
88 | optional uint32 flag = 17;
89 | optional string auth_remark = 18;
90 | optional string group_name = 19;
91 | optional MutilTransHead mutiltrans_head = 20;
92 | optional im.msg.msg_head.InstCtrl inst_ctrl = 21;
93 | optional uint32 public_account_group_send_flag = 22;
94 | optional uint32 wseq_in_c2c_msghead = 23;
95 | optional uint64 cpid = 24;
96 | optional ExtGroupKeyInfo ext_group_key_info = 25;
97 | optional string multi_compatible_text = 26;
98 | optional uint32 auth_sex = 27;
99 | optional bool is_src_msg = 28;
100 | }
101 |
102 | message MsgType0x210 {
103 | optional uint32 sub_msg_type = 1;
104 | optional bytes content = 2;
105 | }
106 |
107 | message MutilTransHead {
108 | optional uint32 status = 1;
109 | optional uint32 msg_id = 2;
110 | }
111 |
112 | message PluginInfo {
113 | optional uint32 res_id = 1;
114 | optional string pkg_name = 2;
115 | optional uint32 new_ver = 3;
116 | optional uint32 res_type = 4;
117 | optional uint32 lan_type = 5;
118 | optional uint32 priority = 6;
119 | optional string res_name = 7;
120 | optional string res_desc = 8;
121 | optional string res_url_big = 9;
122 | optional string res_url_small = 10;
123 | optional string res_conf = 11;
124 | }
125 |
126 | message Uin2Nick {
127 | optional uint64 uin = 1;
128 | optional string nick = 2;
129 | }
130 |
131 | message UinPairMsg {
132 | optional uint32 last_read_time = 1;
133 | optional uint64 peer_uin = 2;
134 | optional uint32 completed = 3;
135 | repeated Msg msg = 4;
136 | optional uint32 unread_msg_num = 5;
137 | optional uint32 c2c_type = 8;
138 | optional uint32 service_type = 9;
139 | optional bytes pb_reserve = 10;
140 | optional uint64 to_tiny_id = 11;
141 | }
142 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/ctrl/__init__.py:
--------------------------------------------------------------------------------
1 | """Message Service Protocol Buffer Model Category.
2 |
3 | This module is used to store all message service protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .ctrl_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/ctrl/ctrl.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package msf.msg.ctrl;
3 |
4 | // msf/msgsvc/msg_ctrl.java
5 |
6 | message MsgCtrl {
7 | optional uint32 flag = 1;
8 | optional ResvResvInfo resv_resv_info = 2;
9 | }
10 |
11 | message ResvResvInfo {
12 | optional uint32 flag = 1;
13 | optional bytes reserv1 = 2;
14 | optional uint64 reserv2 = 3;
15 | optional uint64 reserv3 = 4;
16 | optional uint32 create_time = 5;
17 | optional uint32 pic_height = 6;
18 | optional uint32 pic_width = 7;
19 | optional uint32 resv_flag = 8;
20 | }
21 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/ctrl/ctrl_pb2.pyi:
--------------------------------------------------------------------------------
1 | """
2 | @generated by mypy-protobuf. Do not edit manually!
3 | isort:skip_file
4 | """
5 | from builtins import (
6 | bool,
7 | bytes,
8 | int,
9 | )
10 |
11 | from google.protobuf.descriptor import (
12 | Descriptor,
13 | FileDescriptor,
14 | )
15 |
16 | from google.protobuf.message import (
17 | Message,
18 | )
19 |
20 | from typing import (
21 | Optional,
22 | )
23 |
24 | from typing_extensions import (
25 | Literal,
26 | )
27 |
28 |
29 | DESCRIPTOR: FileDescriptor = ...
30 |
31 | class MsgCtrl(Message):
32 | DESCRIPTOR: Descriptor = ...
33 | FLAG_FIELD_NUMBER: int
34 | RESV_RESV_INFO_FIELD_NUMBER: int
35 | flag: int = ...
36 |
37 | @property
38 | def resv_resv_info(self) -> ResvResvInfo: ...
39 |
40 | def __init__(self,
41 | *,
42 | flag : Optional[int] = ...,
43 | resv_resv_info : Optional[ResvResvInfo] = ...,
44 | ) -> None: ...
45 | def HasField(self, field_name: Literal[u"flag",b"flag",u"resv_resv_info",b"resv_resv_info"]) -> bool: ...
46 | def ClearField(self, field_name: Literal[u"flag",b"flag",u"resv_resv_info",b"resv_resv_info"]) -> None: ...
47 |
48 | class ResvResvInfo(Message):
49 | DESCRIPTOR: Descriptor = ...
50 | FLAG_FIELD_NUMBER: int
51 | RESERV1_FIELD_NUMBER: int
52 | RESERV2_FIELD_NUMBER: int
53 | RESERV3_FIELD_NUMBER: int
54 | CREATE_TIME_FIELD_NUMBER: int
55 | PIC_HEIGHT_FIELD_NUMBER: int
56 | PIC_WIDTH_FIELD_NUMBER: int
57 | RESV_FLAG_FIELD_NUMBER: int
58 | flag: int = ...
59 | reserv1: bytes = ...
60 | reserv2: int = ...
61 | reserv3: int = ...
62 | create_time: int = ...
63 | pic_height: int = ...
64 | pic_width: int = ...
65 | resv_flag: int = ...
66 |
67 | def __init__(self,
68 | *,
69 | flag : Optional[int] = ...,
70 | reserv1 : Optional[bytes] = ...,
71 | reserv2 : Optional[int] = ...,
72 | reserv3 : Optional[int] = ...,
73 | create_time : Optional[int] = ...,
74 | pic_height : Optional[int] = ...,
75 | pic_width : Optional[int] = ...,
76 | resv_flag : Optional[int] = ...,
77 | ) -> None: ...
78 | def HasField(self, field_name: Literal[u"create_time",b"create_time",u"flag",b"flag",u"pic_height",b"pic_height",u"pic_width",b"pic_width",u"reserv1",b"reserv1",u"reserv2",b"reserv2",u"reserv3",b"reserv3",u"resv_flag",b"resv_flag"]) -> bool: ...
79 | def ClearField(self, field_name: Literal[u"create_time",b"create_time",u"flag",b"flag",u"pic_height",b"pic_height",u"pic_width",b"pic_width",u"reserv1",b"reserv1",u"reserv2",b"reserv2",u"reserv3",b"reserv3",u"resv_flag",b"resv_flag"]) -> None: ...
80 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/onlinepush/__init__.py:
--------------------------------------------------------------------------------
1 | """Message Service Protocol Buffer Model Category.
2 |
3 | This module is used to store all message service protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .onlinepush_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/onlinepush/onlinepush.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package msf.msg.onlinepush;
3 |
4 | // msf/msgsvc/msg_onlinepush.java
5 |
6 | import "cai/pb/msf/msg/comm/comm.proto";
7 |
8 | message PbPushMsg {
9 | optional comm.Msg msg = 1;
10 | optional int32 svrip = 2;
11 | optional bytes push_token = 3;
12 | optional uint32 ping_flag = 4;
13 | optional uint32 general_flag = 9;
14 | optional uint64 bind_uin = 10;
15 | }
16 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/onlinepush/onlinepush_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # source: cai/pb/msf/msg/onlinepush/onlinepush.proto
4 | """Generated protocol buffer code."""
5 | from google.protobuf import descriptor as _descriptor
6 | from google.protobuf import message as _message
7 | from google.protobuf import reflection as _reflection
8 | from google.protobuf import symbol_database as _symbol_database
9 | # @@protoc_insertion_point(imports)
10 |
11 | _sym_db = _symbol_database.Default()
12 |
13 |
14 | from cai.pb.msf.msg.comm import comm_pb2 as cai_dot_pb_dot_msf_dot_msg_dot_comm_dot_comm__pb2
15 |
16 |
17 | DESCRIPTOR = _descriptor.FileDescriptor(
18 | name='cai/pb/msf/msg/onlinepush/onlinepush.proto',
19 | package='msf.msg.onlinepush',
20 | syntax='proto2',
21 | serialized_options=None,
22 | create_key=_descriptor._internal_create_key,
23 | serialized_pb=b'\n*cai/pb/msf/msg/onlinepush/onlinepush.proto\x12\x12msf.msg.onlinepush\x1a\x1e\x63\x61i/pb/msf/msg/comm/comm.proto\"\x89\x01\n\tPbPushMsg\x12\x1e\n\x03msg\x18\x01 \x01(\x0b\x32\x11.msf.msg.comm.Msg\x12\r\n\x05svrip\x18\x02 \x01(\x05\x12\x12\n\npush_token\x18\x03 \x01(\x0c\x12\x11\n\tping_flag\x18\x04 \x01(\r\x12\x14\n\x0cgeneral_flag\x18\t \x01(\r\x12\x10\n\x08\x62ind_uin\x18\n \x01(\x04'
24 | ,
25 | dependencies=[cai_dot_pb_dot_msf_dot_msg_dot_comm_dot_comm__pb2.DESCRIPTOR,])
26 |
27 |
28 |
29 |
30 | _PBPUSHMSG = _descriptor.Descriptor(
31 | name='PbPushMsg',
32 | full_name='msf.msg.onlinepush.PbPushMsg',
33 | filename=None,
34 | file=DESCRIPTOR,
35 | containing_type=None,
36 | create_key=_descriptor._internal_create_key,
37 | fields=[
38 | _descriptor.FieldDescriptor(
39 | name='msg', full_name='msf.msg.onlinepush.PbPushMsg.msg', index=0,
40 | number=1, type=11, cpp_type=10, label=1,
41 | has_default_value=False, default_value=None,
42 | message_type=None, enum_type=None, containing_type=None,
43 | is_extension=False, extension_scope=None,
44 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
45 | _descriptor.FieldDescriptor(
46 | name='svrip', full_name='msf.msg.onlinepush.PbPushMsg.svrip', index=1,
47 | number=2, type=5, cpp_type=1, label=1,
48 | has_default_value=False, default_value=0,
49 | message_type=None, enum_type=None, containing_type=None,
50 | is_extension=False, extension_scope=None,
51 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
52 | _descriptor.FieldDescriptor(
53 | name='push_token', full_name='msf.msg.onlinepush.PbPushMsg.push_token', index=2,
54 | number=3, type=12, cpp_type=9, label=1,
55 | has_default_value=False, default_value=b"",
56 | message_type=None, enum_type=None, containing_type=None,
57 | is_extension=False, extension_scope=None,
58 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
59 | _descriptor.FieldDescriptor(
60 | name='ping_flag', full_name='msf.msg.onlinepush.PbPushMsg.ping_flag', index=3,
61 | number=4, type=13, cpp_type=3, label=1,
62 | has_default_value=False, default_value=0,
63 | message_type=None, enum_type=None, containing_type=None,
64 | is_extension=False, extension_scope=None,
65 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
66 | _descriptor.FieldDescriptor(
67 | name='general_flag', full_name='msf.msg.onlinepush.PbPushMsg.general_flag', index=4,
68 | number=9, type=13, cpp_type=3, label=1,
69 | has_default_value=False, default_value=0,
70 | message_type=None, enum_type=None, containing_type=None,
71 | is_extension=False, extension_scope=None,
72 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
73 | _descriptor.FieldDescriptor(
74 | name='bind_uin', full_name='msf.msg.onlinepush.PbPushMsg.bind_uin', index=5,
75 | number=10, type=4, cpp_type=4, label=1,
76 | has_default_value=False, default_value=0,
77 | message_type=None, enum_type=None, containing_type=None,
78 | is_extension=False, extension_scope=None,
79 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
80 | ],
81 | extensions=[
82 | ],
83 | nested_types=[],
84 | enum_types=[
85 | ],
86 | serialized_options=None,
87 | is_extendable=False,
88 | syntax='proto2',
89 | extension_ranges=[],
90 | oneofs=[
91 | ],
92 | serialized_start=99,
93 | serialized_end=236,
94 | )
95 |
96 | _PBPUSHMSG.fields_by_name['msg'].message_type = cai_dot_pb_dot_msf_dot_msg_dot_comm_dot_comm__pb2._MSG
97 | DESCRIPTOR.message_types_by_name['PbPushMsg'] = _PBPUSHMSG
98 | _sym_db.RegisterFileDescriptor(DESCRIPTOR)
99 |
100 | PbPushMsg = _reflection.GeneratedProtocolMessageType('PbPushMsg', (_message.Message,), {
101 | 'DESCRIPTOR' : _PBPUSHMSG,
102 | '__module__' : 'cai.pb.msf.msg.onlinepush.onlinepush_pb2'
103 | # @@protoc_insertion_point(class_scope:msf.msg.onlinepush.PbPushMsg)
104 | })
105 | _sym_db.RegisterMessage(PbPushMsg)
106 |
107 |
108 | # @@protoc_insertion_point(module_scope)
109 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/onlinepush/onlinepush_pb2.pyi:
--------------------------------------------------------------------------------
1 | """
2 | @generated by mypy-protobuf. Do not edit manually!
3 | isort:skip_file
4 | """
5 | from builtins import (
6 | bool,
7 | bytes,
8 | int,
9 | )
10 |
11 | from cai.pb.msf.msg.comm.comm_pb2 import (
12 | Msg,
13 | )
14 |
15 | from google.protobuf.descriptor import (
16 | Descriptor,
17 | FileDescriptor,
18 | )
19 |
20 | from google.protobuf.message import (
21 | Message,
22 | )
23 |
24 | from typing import (
25 | Optional,
26 | )
27 |
28 | from typing_extensions import (
29 | Literal,
30 | )
31 |
32 |
33 | DESCRIPTOR: FileDescriptor = ...
34 |
35 | class PbPushMsg(Message):
36 | DESCRIPTOR: Descriptor = ...
37 | MSG_FIELD_NUMBER: int
38 | SVRIP_FIELD_NUMBER: int
39 | PUSH_TOKEN_FIELD_NUMBER: int
40 | PING_FLAG_FIELD_NUMBER: int
41 | GENERAL_FLAG_FIELD_NUMBER: int
42 | BIND_UIN_FIELD_NUMBER: int
43 | svrip: int = ...
44 | push_token: bytes = ...
45 | ping_flag: int = ...
46 | general_flag: int = ...
47 | bind_uin: int = ...
48 |
49 | @property
50 | def msg(self) -> Msg: ...
51 |
52 | def __init__(self,
53 | *,
54 | msg : Optional[Msg] = ...,
55 | svrip : Optional[int] = ...,
56 | push_token : Optional[bytes] = ...,
57 | ping_flag : Optional[int] = ...,
58 | general_flag : Optional[int] = ...,
59 | bind_uin : Optional[int] = ...,
60 | ) -> None: ...
61 | def HasField(self, field_name: Literal[u"bind_uin",b"bind_uin",u"general_flag",b"general_flag",u"msg",b"msg",u"ping_flag",b"ping_flag",u"push_token",b"push_token",u"svrip",b"svrip"]) -> bool: ...
62 | def ClearField(self, field_name: Literal[u"bind_uin",b"bind_uin",u"general_flag",b"general_flag",u"msg",b"msg",u"ping_flag",b"ping_flag",u"push_token",b"push_token",u"svrip",b"svrip"]) -> None: ...
63 |
--------------------------------------------------------------------------------
/cai/pb/msf/msg/svc/__init__.py:
--------------------------------------------------------------------------------
1 | """Message Service Protocol Buffer Model Category.
2 |
3 | This module is used to store all message service protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .svc_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/wtlogin/__init__.py:
--------------------------------------------------------------------------------
1 | """Wtlogin Protocol Buffer Model Category.
2 |
3 | This module is used to store all wtlogin protobuf files.
4 |
5 | Generate all protobuf file using:
6 |
7 | .. code-block:: bash
8 |
9 | protoc cai/pb/**/*.proto --python_out=. --mypy_out=readable_stubs:.
10 |
11 | :Copyright: Copyright (C) 2021-2021 cscs181
12 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
13 |
14 | .. _LICENSE:
15 | https://github.com/cscs181/CAI/blob/master/LICENSE
16 | """
17 |
18 | from .data_pb2 import *
19 |
--------------------------------------------------------------------------------
/cai/pb/wtlogin/data.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | package wtlogin;
3 |
4 | // oicq/wlogin_sdk/pb/device_report.java
5 | message DeviceReport {
6 | optional string bootloader = 1;
7 | optional string proc_version = 2;
8 | optional string codename = 3;
9 | optional string incremental = 4;
10 | optional string fingerprint = 5;
11 | optional string boot_id = 6;
12 | optional string android_id = 7;
13 | optional string base_band = 8;
14 | optional string inner_version = 9;
15 | }
16 |
17 | // oicq/wlogin_sdk/pb/sec_trans.java
18 | message SecTransInfo {
19 | optional string phone_brand = 1;
20 | optional string model_type = 2;
21 | optional string wifi_mac = 3;
22 | optional string bssid = 4;
23 | optional string os_language = 5;
24 | optional uint32 qq_language = 6;
25 | optional string gps_location = 7;
26 | }
27 |
--------------------------------------------------------------------------------
/cai/pb/wtlogin/data_pb2.pyi:
--------------------------------------------------------------------------------
1 | """
2 | @generated by mypy-protobuf. Do not edit manually!
3 | isort:skip_file
4 | """
5 | from builtins import (
6 | bool,
7 | int,
8 | )
9 |
10 | from google.protobuf.descriptor import (
11 | Descriptor,
12 | FileDescriptor,
13 | )
14 |
15 | from google.protobuf.message import (
16 | Message,
17 | )
18 |
19 | from typing import (
20 | Optional,
21 | Text,
22 | )
23 |
24 | from typing_extensions import (
25 | Literal,
26 | )
27 |
28 |
29 | DESCRIPTOR: FileDescriptor = ...
30 |
31 | class DeviceReport(Message):
32 | DESCRIPTOR: Descriptor = ...
33 | BOOTLOADER_FIELD_NUMBER: int
34 | PROC_VERSION_FIELD_NUMBER: int
35 | CODENAME_FIELD_NUMBER: int
36 | INCREMENTAL_FIELD_NUMBER: int
37 | FINGERPRINT_FIELD_NUMBER: int
38 | BOOT_ID_FIELD_NUMBER: int
39 | ANDROID_ID_FIELD_NUMBER: int
40 | BASE_BAND_FIELD_NUMBER: int
41 | INNER_VERSION_FIELD_NUMBER: int
42 | bootloader: Text = ...
43 | proc_version: Text = ...
44 | codename: Text = ...
45 | incremental: Text = ...
46 | fingerprint: Text = ...
47 | boot_id: Text = ...
48 | android_id: Text = ...
49 | base_band: Text = ...
50 | inner_version: Text = ...
51 |
52 | def __init__(self,
53 | *,
54 | bootloader : Optional[Text] = ...,
55 | proc_version : Optional[Text] = ...,
56 | codename : Optional[Text] = ...,
57 | incremental : Optional[Text] = ...,
58 | fingerprint : Optional[Text] = ...,
59 | boot_id : Optional[Text] = ...,
60 | android_id : Optional[Text] = ...,
61 | base_band : Optional[Text] = ...,
62 | inner_version : Optional[Text] = ...,
63 | ) -> None: ...
64 | def HasField(self, field_name: Literal[u"android_id",b"android_id",u"base_band",b"base_band",u"boot_id",b"boot_id",u"bootloader",b"bootloader",u"codename",b"codename",u"fingerprint",b"fingerprint",u"incremental",b"incremental",u"inner_version",b"inner_version",u"proc_version",b"proc_version"]) -> bool: ...
65 | def ClearField(self, field_name: Literal[u"android_id",b"android_id",u"base_band",b"base_band",u"boot_id",b"boot_id",u"bootloader",b"bootloader",u"codename",b"codename",u"fingerprint",b"fingerprint",u"incremental",b"incremental",u"inner_version",b"inner_version",u"proc_version",b"proc_version"]) -> None: ...
66 |
67 | class SecTransInfo(Message):
68 | DESCRIPTOR: Descriptor = ...
69 | PHONE_BRAND_FIELD_NUMBER: int
70 | MODEL_TYPE_FIELD_NUMBER: int
71 | WIFI_MAC_FIELD_NUMBER: int
72 | BSSID_FIELD_NUMBER: int
73 | OS_LANGUAGE_FIELD_NUMBER: int
74 | QQ_LANGUAGE_FIELD_NUMBER: int
75 | GPS_LOCATION_FIELD_NUMBER: int
76 | phone_brand: Text = ...
77 | model_type: Text = ...
78 | wifi_mac: Text = ...
79 | bssid: Text = ...
80 | os_language: Text = ...
81 | qq_language: int = ...
82 | gps_location: Text = ...
83 |
84 | def __init__(self,
85 | *,
86 | phone_brand : Optional[Text] = ...,
87 | model_type : Optional[Text] = ...,
88 | wifi_mac : Optional[Text] = ...,
89 | bssid : Optional[Text] = ...,
90 | os_language : Optional[Text] = ...,
91 | qq_language : Optional[int] = ...,
92 | gps_location : Optional[Text] = ...,
93 | ) -> None: ...
94 | def HasField(self, field_name: Literal[u"bssid",b"bssid",u"gps_location",b"gps_location",u"model_type",b"model_type",u"os_language",b"os_language",u"phone_brand",b"phone_brand",u"qq_language",b"qq_language",u"wifi_mac",b"wifi_mac"]) -> bool: ...
95 | def ClearField(self, field_name: Literal[u"bssid",b"bssid",u"gps_location",b"gps_location",u"model_type",b"model_type",u"os_language",b"os_language",u"phone_brand",b"phone_brand",u"qq_language",b"qq_language",u"wifi_mac",b"wifi_mac"]) -> None: ...
96 |
--------------------------------------------------------------------------------
/cai/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/cai/py.typed
--------------------------------------------------------------------------------
/cai/settings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/cai/settings/__init__.py
--------------------------------------------------------------------------------
/cai/settings/protocol.py:
--------------------------------------------------------------------------------
1 | """Application Protocol setting
2 |
3 | This module is used to get or new the application protocol setting.
4 | Protocol settings will be stored in APP_DIR provided by storage manager.
5 | Once the protocol setting is loaded, it will be cached until application shut down.
6 |
7 | :Copyright: Copyright (C) 2021-2021 cscs181
8 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
9 |
10 | .. _LICENSE:
11 | https://github.com/cscs181/CAI/blob/master/LICENSE
12 | """
13 | import os
14 | from typing import Optional, NamedTuple
15 |
16 | from cai.storage import Storage
17 |
18 | MISSING = "MISSING"
19 |
20 | _protocol: Optional["ApkInfo"] = None
21 |
22 |
23 | class ApkInfo(NamedTuple):
24 | apk_id: str
25 | app_id: int
26 | sub_app_id: int # com.tencent.common.config.AppSetting.f
27 | version: str
28 | apk_sign: bytes
29 | build_time: int # oicq.wlogin_sdk.tools.util.BUILD_TIME
30 | sdk_version: str # oicq.wlogin_sdk.tools.util.SDK_VERSION
31 | sso_version: int # oicq.wlogin_sdk.tools.util.SSO_VERSION
32 | bitmap: int # oicq.wlogin_sdk.request.WtloginHelper.mMiscBitmap | 0x2000000
33 | main_sigmap: int # com.tencent.mobileqq.msf.core.auth.n.f
34 | sub_sigmap: int # oicq.wlogin_sdk.request.WtloginHelper.mSubSigMap
35 |
36 |
37 | ANDROID_PHONE = ApkInfo(
38 | apk_id="com.tencent.mobileqq",
39 | app_id=16,
40 | sub_app_id=537066738,
41 | version="8.5.0",
42 | build_time=1607689988,
43 | apk_sign=bytes(
44 | [
45 | 0xA6,
46 | 0xB7,
47 | 0x45,
48 | 0xBF,
49 | 0x24,
50 | 0xA2,
51 | 0xC2,
52 | 0x77,
53 | 0x52,
54 | 0x77,
55 | 0x16,
56 | 0xF6,
57 | 0xF3,
58 | 0x6E,
59 | 0xB6,
60 | 0x8D,
61 | ]
62 | ),
63 | sdk_version="6.0.0.2454",
64 | sso_version=15,
65 | bitmap=184024956,
66 | main_sigmap=34869472,
67 | sub_sigmap=0x10400,
68 | )
69 | ANDROID_WATCH = ApkInfo(
70 | apk_id="com.tencent.mobileqq",
71 | app_id=16,
72 | sub_app_id=537061176,
73 | version="8.2.7",
74 | build_time=1571193922,
75 | apk_sign=bytes(
76 | [
77 | 0xA6,
78 | 0xB7,
79 | 0x45,
80 | 0xBF,
81 | 0x24,
82 | 0xA2,
83 | 0xC2,
84 | 0x77,
85 | 0x52,
86 | 0x77,
87 | 0x16,
88 | 0xF6,
89 | 0xF3,
90 | 0x6E,
91 | 0xB6,
92 | 0x8D,
93 | ]
94 | ),
95 | sdk_version="6.0.0.2413",
96 | sso_version=5,
97 | bitmap=184024956,
98 | main_sigmap=34869472,
99 | sub_sigmap=0x10400,
100 | )
101 | IPAD = ApkInfo(
102 | apk_id="com.tencent.minihd.qq",
103 | app_id=16,
104 | sub_app_id=537065739,
105 | version="5.8.9",
106 | build_time=1595836208,
107 | apk_sign=bytes(
108 | [
109 | 170,
110 | 57,
111 | 120,
112 | 244,
113 | 31,
114 | 217,
115 | 111,
116 | 249,
117 | 145,
118 | 74,
119 | 102,
120 | 158,
121 | 24,
122 | 100,
123 | 116,
124 | 199,
125 | ]
126 | ),
127 | sdk_version="6.0.0.2433",
128 | sso_version=12,
129 | bitmap=150470524,
130 | main_sigmap=1970400,
131 | sub_sigmap=66560,
132 | )
133 | MACOS = ApkInfo(
134 | apk_id="com.tencent.minihd.qq",
135 | app_id=16,
136 | sub_app_id=537064315,
137 | version="5.8.9",
138 | build_time=1595836208,
139 | apk_sign=bytes(
140 | [
141 | 170,
142 | 57,
143 | 120,
144 | 244,
145 | 31,
146 | 217,
147 | 111,
148 | 249,
149 | 145,
150 | 74,
151 | 102,
152 | 158,
153 | 24,
154 | 100,
155 | 116,
156 | 199,
157 | ]
158 | ),
159 | sdk_version="6.0.0.2433",
160 | sso_version=12,
161 | bitmap=150470524,
162 | main_sigmap=1970400,
163 | sub_sigmap=66560,
164 | )
165 |
166 |
167 | def get_apk_info(type_: str = "0") -> ApkInfo:
168 | info = {"0": IPAD, "1": ANDROID_PHONE, "2": ANDROID_WATCH, "3": MACOS}
169 | if type_ not in info:
170 | raise ValueError(f"Invalid Protocol Type: {type_}")
171 | return info[type_]
172 |
173 |
174 | def get_protocol(cache: bool = True) -> ApkInfo:
175 | global _protocol
176 | if cache and _protocol:
177 | return _protocol
178 |
179 | type_ = os.getenv(Storage.protocol_env_name, MISSING)
180 | if type_ is MISSING and os.path.exists(Storage.protocol_file):
181 | with open(Storage.protocol_file, "r") as f:
182 | type_ = f.read()
183 | elif type_ is MISSING:
184 | type_ = "0"
185 | with open(Storage.protocol_file, "w") as f:
186 | f.write("0")
187 | _protocol = get_apk_info(type_)
188 | return _protocol
189 |
--------------------------------------------------------------------------------
/cai/storage/__init__.py:
--------------------------------------------------------------------------------
1 | """Application Storage Manager
2 |
3 | This module is used to manage application storage.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | import os
12 | import shutil
13 |
14 | from .utils import user_cache_dir, user_config_dir
15 |
16 |
17 | class Storage:
18 | app_name: str = "CAI"
19 |
20 | # application config dir
21 | default_app_dir: str = user_config_dir(app_name, roaming=True)
22 | app_dir: str = os.getenv(f"{app_name}_APP_DIR", default_app_dir)
23 | if not os.path.exists(app_dir):
24 | os.makedirs(app_dir)
25 | if not os.path.isdir(app_dir):
26 | raise RuntimeError(
27 | f"Application directory {app_dir} is not a directory!"
28 | )
29 |
30 | # application cache dir
31 | default_cache_dir: str = user_cache_dir(app_name)
32 | cache_dir: str = os.getenv(f"{app_name}_CACHE_DIR", default_cache_dir)
33 | if not os.path.exists(cache_dir):
34 | os.makedirs(cache_dir)
35 | if not os.path.isdir(cache_dir):
36 | raise RuntimeError(
37 | f"Application Cache directory {cache_dir} is not a directory!"
38 | )
39 |
40 | # cai.settings.device
41 | device_file: str = os.path.join(app_dir, "device.json")
42 |
43 | # cai.settings.protocol
44 | protocol_env_name: str = f"{app_name}_PROTOCOL"
45 | protocol_file: str = os.path.join(app_dir, "protocol")
46 |
47 | @classmethod
48 | def clear_cache(cls):
49 | # FIXME: delete used dir only
50 | for path in os.listdir(cls.cache_dir):
51 | if os.path.isdir(path):
52 | shutil.rmtree(path)
53 | else:
54 | os.remove(path)
55 |
--------------------------------------------------------------------------------
/cai/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/cai/utils/__init__.py
--------------------------------------------------------------------------------
/cai/utils/binary.pyi:
--------------------------------------------------------------------------------
1 | # type: ignore
2 | """Binary Tools Stub File.
3 |
4 | Type hints in this file needs python>=3.10 or using ``pyright`` or ``pylance``...
5 |
6 | :Copyright: Copyright (C) 2021-2021 cscs181
7 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
8 |
9 | .. _LICENSE:
10 | https://github.com/cscs181/CAI/blob/master/LICENSE
11 | """
12 |
13 | from typing_extensions import Unpack, TypeVarTuple
14 | from typing import (
15 | Any,
16 | List,
17 | Type,
18 | Tuple,
19 | Union,
20 | Generic,
21 | NewType,
22 | TypeVar,
23 | Callable,
24 | Optional,
25 | )
26 |
27 | P = TypeVar("P", bound="BasePacket")
28 | Ts = TypeVarTuple("Ts")
29 |
30 | BOOL = NewType("BOOL", bool)
31 | INT8 = NewType("INT8", int)
32 | UINT8 = NewType("UINT8", int)
33 | INT16 = NewType("INT16", int)
34 | UINT16 = NewType("UINT16", int)
35 | INT32 = NewType("INT32", int)
36 | UINT32 = NewType("UINT32", int)
37 | INT64 = NewType("INT64", int)
38 | UINT64 = NewType("UINT64", int)
39 | FLOAT = NewType("FLOAT", float)
40 | DOUBLE = NewType("DOUBLE", float)
41 | BYTE = NewType("BYTE", bytes)
42 | BYTES = NewType("BYTES", bytes)
43 | STRING = NewType("STRING", str)
44 |
45 | class BasePacket(bytearray):
46 | @classmethod
47 | def build(cls: Type[P], *data: Union[bytes, "BasePacket"]) -> P: ...
48 | def write(self: P, *data: Union[bytes, "BasePacket"]) -> P: ...
49 | def write_with_length(
50 | self: P, *data: Union[bytes, "BasePacket"], offset: int = ...
51 | ) -> P: ...
52 | def unpack(self, format: Union[bytes, str]) -> Tuple[Any, ...]: ...
53 | def unpack_from(
54 | self, format: Union[bytes, str], offset: int = ...
55 | ) -> Tuple[Any, ...]: ...
56 | def read_int8(self, offset: int = ...) -> INT8: ...
57 | def read_uint8(self, offset: int = ...) -> UINT8: ...
58 | def read_int16(self, offset: int = ...) -> INT16: ...
59 | def read_uint16(self, offset: int = ...) -> UINT16: ...
60 | def read_int32(self, offset: int = ...) -> INT32: ...
61 | def read_uint32(self, offset: int = ...) -> UINT32: ...
62 | def read_int64(self, offset: int = ...) -> INT64: ...
63 | def read_uint64(self, offset: int = ...) -> UINT64: ...
64 | def read_byte(self, offset: int = ...) -> BYTE: ...
65 | def read_bytes(self, n: int, offset: int = ...) -> BYTES: ...
66 | def read_string(self, offset: int = ...) -> STRING: ...
67 |
68 | _bool = bool
69 |
70 | class Packet(BasePacket, Generic[Unpack[Ts]]):
71 | _query: str = ...
72 | _offset: int = ...
73 | _executed: _bool = ...
74 | _filters: List[Callable[[Any], Any]] = ...
75 | def __init__(
76 | self, *args, cache: Optional[Tuple[Any, ...]] = None, **kwargs
77 | ) -> Packet[()]: ...
78 | def __new__(cls, *args, **kwargs) -> Packet[()]: ...
79 | def start(self: Packet[Unpack[Ts]], offset: int = ...) -> Packet[()]: ...
80 | def bool(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], BOOL]: ...
81 | def int8(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], INT8]: ...
82 | def uint8(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], UINT8]: ...
83 | def int16(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], INT16]: ...
84 | def uint16(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], UINT16]: ...
85 | def int32(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], INT32]: ...
86 | def uint32(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], UINT32]: ...
87 | def int64(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], INT64]: ...
88 | def uint64(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], UINT64]: ...
89 | def float(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], FLOAT]: ...
90 | def double(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], DOUBLE]: ...
91 | def byte(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts], BYTE]: ...
92 | def bytes(
93 | self: Packet[Unpack[Ts]], length: int
94 | ) -> Packet[Unpack[Ts], BYTES]: ...
95 | def bytes_with_length(
96 | self: Packet[Unpack[Ts]], head_bytes: int, offset: int = ...
97 | ) -> Packet[Unpack[Ts], BYTES]: ...
98 | def string(
99 | self: Packet[Unpack[Ts]],
100 | head_bytes: int,
101 | offset: int = ...,
102 | encoding: str = ...,
103 | ) -> Packet[Unpack[Ts], STRING]: ...
104 | def offset(self: Packet[Unpack[Ts]], offset: int) -> Packet[Unpack[Ts]]: ...
105 | def remain(
106 | self: Packet[Unpack[Ts]],
107 | ) -> Packet[Unpack[Ts], Packet[()]]: ...
108 | def _exec_cache(self: Packet[Unpack[Ts]]) -> Packet[Unpack[Ts]]: ...
109 | def execute(self: Packet[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: ...
110 |
--------------------------------------------------------------------------------
/cai/utils/coroutine.py:
--------------------------------------------------------------------------------
1 | """Coroutine Related Tools
2 |
3 | This module is used to build coroutine related tools.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | from types import TracebackType
12 | from collections.abc import Coroutine
13 | from typing import AsyncContextManager
14 | from typing import Coroutine as CoroutineGeneric
15 | from typing import Type, Generic, TypeVar, Optional
16 |
17 | TY = TypeVar("TY")
18 | TS = TypeVar("TS")
19 | TR = TypeVar("TR", bound=AsyncContextManager)
20 |
21 |
22 | class ContextManager(Coroutine, Generic[TY, TS, TR]):
23 |
24 | __slots__ = ("_coro", "_obj")
25 |
26 | def __init__(self, coro: CoroutineGeneric[TY, TS, TR]):
27 | self._coro = coro
28 | self._obj: Optional[TR] = None
29 |
30 | def send(self, value: TS) -> TY:
31 | return self._coro.send(value)
32 |
33 | def throw(self, typ, val=None, tb=None):
34 | if val is None:
35 | return self._coro.throw(typ)
36 | elif tb is None:
37 | return self._coro.throw(typ, val)
38 | else:
39 | return self._coro.throw(typ, val, tb)
40 |
41 | def close(self):
42 | return self._coro.close()
43 |
44 | def __next__(self):
45 | return self.send(None)
46 |
47 | def __iter__(self):
48 | return self._coro.__await__()
49 |
50 | def __await__(self):
51 | return self._coro.__await__()
52 |
53 | async def __aenter__(self) -> TR:
54 | self._obj = await self._coro
55 | return self._obj
56 |
57 | async def __aexit__(
58 | self,
59 | exc_type: Optional[Type[BaseException]],
60 | exc_value: Optional[BaseException],
61 | traceback: Optional[TracebackType],
62 | ):
63 | await self._obj.__aexit__(exc_type, exc_value, traceback) # type: ignore
64 | self._obj = None
65 |
--------------------------------------------------------------------------------
/cai/utils/crypto.py:
--------------------------------------------------------------------------------
1 | """Crypto Tool.
2 |
3 | This module is used to encrypt data using ECDH or Session ticket encryption.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | import struct
12 | from hashlib import md5
13 | from typing import Union
14 |
15 | from rtea import qqtea_encrypt
16 | from cryptography.hazmat.primitives.asymmetric import ec
17 | from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
18 |
19 | from cai.utils.binary import Packet
20 |
21 |
22 | class ECDH:
23 | id = 0x87
24 | _p256 = ec.SECP256R1()
25 |
26 | svr_public_key = ec.EllipticCurvePublicKey.from_encoded_point(
27 | _p256,
28 | bytes.fromhex(
29 | "04"
30 | "EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFB"
31 | "C91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E"
32 | ),
33 | )
34 |
35 | client_private_key = ec.generate_private_key(_p256)
36 | client_public_key = client_private_key.public_key().public_bytes(
37 | Encoding.X962, PublicFormat.UncompressedPoint
38 | )
39 |
40 | share_key = md5(
41 | client_private_key.exchange(ec.ECDH(), svr_public_key)[:16]
42 | ).digest()
43 |
44 | @classmethod
45 | def encrypt(
46 | cls, data: Union[bytes, Packet], key: Union[bytes, Packet]
47 | ) -> Packet:
48 | return Packet.build(
49 | struct.pack(">BB", 2, 1),
50 | key,
51 | struct.pack(
52 | ">HHH",
53 | 305,
54 | 1, # oicq.wlogin_sdk.tools.EcdhCrypt.sKeyVersion
55 | len(cls.client_public_key),
56 | ),
57 | cls.client_public_key,
58 | qqtea_encrypt(bytes(data), cls.share_key),
59 | )
60 |
61 |
62 | class EncryptSession:
63 | id = 0x45
64 |
65 | def __init__(self, ticket: bytes):
66 | self.ticket = ticket
67 |
68 | def encrypt(
69 | self, data: Union[bytes, Packet], key: Union[bytes, Packet]
70 | ) -> Packet:
71 | return Packet.build(
72 | struct.pack(">H", len(self.ticket)),
73 | self.ticket,
74 | qqtea_encrypt(bytes(data), bytes(key)),
75 | )
76 |
--------------------------------------------------------------------------------
/cai/utils/future.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from typing import Any, Dict, Generic, TypeVar, Callable, Optional
3 |
4 | KT = TypeVar("KT")
5 | VT = TypeVar("VT")
6 |
7 |
8 | class FutureStore(Generic[KT, VT]):
9 | def __init__(self):
10 | # Generic Future is supported since py3.9
11 | self._futures: Dict[KT, "asyncio.Future[VT]"] = {}
12 |
13 | def __contains__(self, seq: KT) -> bool:
14 | return seq in self._futures
15 |
16 | def store_seq(self, seq: KT) -> "asyncio.Future[VT]":
17 | if seq in self._futures:
18 | raise KeyError(f"Sequence {seq} already exists!")
19 |
20 | future = asyncio.get_event_loop().create_future()
21 | self._futures[seq] = future
22 | return future
23 |
24 | def store_result(self, seq: KT, result: VT):
25 | future = self._futures.get(seq)
26 | if future and not future.cancelled():
27 | future.set_result(result)
28 |
29 | def pop_seq(self, seq: KT) -> "asyncio.Future[VT]":
30 | return self._futures.pop(seq)
31 |
32 | def add_callback(
33 | self, seq: KT, callback: Callable[["asyncio.Future[VT]"], Any]
34 | ):
35 | future = self._futures[seq]
36 | future.add_done_callback(callback)
37 |
38 | def remove_callback(
39 | self, seq: KT, callback: Callable[["asyncio.Future[VT]"], Any]
40 | ) -> int:
41 | future = self._futures[seq]
42 | return future.remove_done_callback(callback)
43 |
44 | def done(self, seq: KT) -> bool:
45 | return self._futures[seq].done()
46 |
47 | def result(self, seq: KT) -> VT:
48 | return self._futures[seq].result()
49 |
50 | def cancel(self, seq: KT) -> bool:
51 | return self._futures[seq].cancel()
52 |
53 | def cancel_all(self):
54 | for future in self._futures.values():
55 | future.cancel()
56 |
57 | def exception(self, seq: KT) -> Optional[BaseException]:
58 | return self._futures[seq].exception()
59 |
60 | async def fetch(self, seq: KT, timeout: Optional[float] = None) -> VT:
61 | future = (
62 | self.store_seq(seq)
63 | if seq not in self._futures
64 | else self._futures[seq]
65 | )
66 | try:
67 | return await asyncio.wait_for(future, timeout)
68 | finally:
69 | del self._futures[seq]
70 |
--------------------------------------------------------------------------------
/cai/utils/jce.py:
--------------------------------------------------------------------------------
1 | """JCE Related Tools
2 |
3 | This module is used to build JCE related tools including packaging and serialization.
4 |
5 | :Copyright: Copyright (C) 2021-2021 cscs181
6 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
7 |
8 | .. _LICENSE:
9 | https://github.com/cscs181/CAI/blob/master/LICENSE
10 | """
11 | import struct
12 | from typing import Optional
13 |
14 | from jce import JceField, JceStruct, JceDecoder, types
15 |
16 |
17 | class RequestPacket(JceStruct):
18 | """
19 | Note:
20 | Source: com.qq.taf.RequestPacket
21 | """
22 |
23 | version: types.INT16 = JceField(0, jce_id=1)
24 | pkg_type: types.BYTE = JceField(bytes(1), jce_id=2)
25 | msg_type: types.INT32 = JceField(0, jce_id=3)
26 | req_id: types.INT32 = JceField(0, jce_id=4)
27 | servant_name: types.STRING = JceField(jce_id=5)
28 | func_name: types.STRING = JceField(jce_id=6)
29 | buffer: types.BYTES = JceField(bytes(0), jce_id=7)
30 | timeout: types.INT32 = JceField(0, jce_id=8)
31 | context: types.MAP = JceField({}, jce_id=9)
32 | status: types.MAP = JceField({}, jce_id=10)
33 |
34 |
35 | class RequestPacketVersion2(RequestPacket):
36 | """
37 | Note:
38 | Source: com.qq.jce.wup.OldUniAttribute
39 | """
40 |
41 | version: types.INT16 = JceField(2, jce_id=1)
42 | # raw data for buffer field
43 | data: Optional[
44 | types.MAP[types.STRING, types.MAP[types.STRING, types.JceType]]
45 | ] = None
46 |
47 | def _prepare_buffer(self):
48 | if not self.data:
49 | raise RuntimeError("No data available")
50 | self.buffer = types.BYTES.validate(types.MAP.to_bytes(0, self.data))
51 |
52 | @classmethod
53 | def _prepare_data(
54 | cls, buffer: types.BYTES
55 | ) -> types.MAP[types.STRING, types.MAP[types.STRING, types.JceType]]:
56 | _, data, _ = JceDecoder.decode_single(buffer)
57 | return data # type: ignore
58 |
59 | def encode(self, with_length: bool = False) -> bytes:
60 | self._prepare_buffer()
61 | buffer = super().encode()
62 | if with_length:
63 | return struct.pack(">I", len(buffer) + 4) + buffer
64 | return buffer
65 |
66 | @classmethod
67 | def decode(cls, data: bytes, **extra) -> "RequestPacketVersion2":
68 | packet: RequestPacketVersion2 = super().decode(
69 | data, **extra
70 | ) # type: ignore
71 | data_ = cls._prepare_data(packet.buffer)
72 | packet.data = data_
73 | return packet
74 |
75 |
76 | class RequestPacketVersion3(RequestPacket):
77 | """
78 | Note:
79 | Source: com.qq.jce.wup.UniAttribute
80 | """
81 |
82 | version: types.INT16 = JceField(3, jce_id=1)
83 | # raw data for buffer field
84 | data: Optional[types.MAP[types.STRING, types.JceType]] = None
85 |
86 | def _prepare_buffer(self):
87 | if not self.data:
88 | raise RuntimeError("No data available")
89 | self.buffer = types.BYTES.validate(types.MAP.to_bytes(0, self.data))
90 |
91 | @classmethod
92 | def _prepare_data(
93 | cls, buffer: types.BYTES
94 | ) -> types.MAP[types.STRING, types.JceType]:
95 | _, data, _ = JceDecoder.decode_single(buffer)
96 | return data # type: ignore
97 |
98 | def encode(self, with_length: bool = False) -> bytes:
99 | self._prepare_buffer()
100 | buffer = super().encode()
101 | if with_length:
102 | return struct.pack(">I", len(buffer) + 4) + buffer
103 | return buffer
104 |
105 | @classmethod
106 | def decode(cls, data: bytes, **extra) -> "RequestPacketVersion3":
107 | packet: RequestPacketVersion3 = super().decode(
108 | data, **extra
109 | ) # type: ignore
110 | data_ = cls._prepare_data(packet.buffer)
111 | packet.data = data_
112 | return packet
113 |
--------------------------------------------------------------------------------
/docs/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/docs/assets/logo.png
--------------------------------------------------------------------------------
/docs/assets/logo_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/docs/assets/logo_text.png
--------------------------------------------------------------------------------
/docs/assets/logo_text_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/docs/assets/logo_text_white.png
--------------------------------------------------------------------------------
/docs/assets/logo_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/docs/assets/logo_white.png
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | import os
14 | import sys
15 |
16 | sys.path.insert(0, os.path.abspath(".."))
17 |
18 | if os.getenv("READTHEDOCS"):
19 | import subprocess
20 |
21 | requirements_path = os.path.join(
22 | os.path.dirname(os.path.abspath(__file__)), "requirements.txt"
23 | )
24 | prog = subprocess.run(
25 | f"{sys.executable} -m pip install poetry &&"
26 | f"{sys.executable} -m poetry export -o {requirements_path} --dev --without-hashes &&"
27 | f"{sys.executable} -m pip install -r {requirements_path}",
28 | shell=True,
29 | )
30 | assert prog.returncode == 0
31 |
32 | # -- Project information -----------------------------------------------------
33 |
34 | project = "cai"
35 | copyright = "2021, cscs181"
36 | author = "cscs181"
37 |
38 | # The full version, including alpha/beta/rc tags
39 | release = "0.1.0"
40 | language = "zh_CN"
41 |
42 | # -- General configuration ---------------------------------------------------
43 |
44 | # Add any Sphinx extension module names here, as strings. They can be
45 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
46 | # ones.
47 | import sphinx_rtd_theme
48 |
49 | extensions = [
50 | "sphinxcontrib.napoleon",
51 | "sphinx_rtd_theme",
52 | "sphinx_copybutton",
53 | ]
54 |
55 | # Add any paths that contain templates here, relative to this directory.
56 | templates_path = ["_templates"]
57 |
58 | # List of patterns, relative to source directory, that match files and
59 | # directories to ignore when looking for source files.
60 | # This pattern also affects html_static_path and html_extra_path.
61 | exclude_patterns = []
62 |
63 | # -- Options for HTML output -------------------------------------------------
64 |
65 | # The theme to use for HTML and HTML Help pages. See the documentation for
66 | # a list of builtin themes.
67 | #
68 | html_theme = "sphinx_rtd_theme"
69 | html_theme_options = {"logo_only": True, "collapse_navigation": False}
70 | html_logo = "assets/logo_text_white.png"
71 |
72 | # Add any paths that contain custom static files (such as style sheets) here,
73 | # relative to this directory. They are copied after the builtin static files,
74 | # so a file named "default.css" will overwrite the builtin "default.css".
75 | html_static_path = ["_static"]
76 |
77 | # -- Options for autodoc extension ----------------------------------------------
78 | autodoc_default_options = {
79 | "member-order": "bysource",
80 | "ignore-module-all": True,
81 | }
82 |
--------------------------------------------------------------------------------
/docs/examples/index.rst:
--------------------------------------------------------------------------------
1 | Examples
2 | ========
3 |
4 |
5 | Login
6 | -----
7 |
8 | .. literalinclude:: ../../examples/login.py
9 | :language: python
10 | :linenos:
11 |
12 |
13 | Set Client Status
14 | -----------------
15 |
16 | .. literalinclude:: ../../examples/set_status.py
17 | :language: python
18 | :linenos:
19 |
20 |
21 | Friend and Group
22 | ----------------
23 |
24 | .. literalinclude:: ../../examples/friend_group.py
25 | :language: python
26 | :linenos:
27 |
28 |
29 | Message
30 | -------
31 |
32 | .. literalinclude:: ../../examples/message.py
33 | :language: python
34 | :linenos:
35 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. cai documentation master file, created by
2 | sphinx-quickstart on Fri Feb 5 17:41:41 2021.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to cai's documentation!
7 | ===============================
8 |
9 | Examples
10 | --------
11 |
12 | :doc:`Example Code `
13 |
14 | Api Reference
15 | -------------
16 |
17 | :doc:`Api Reference `
18 |
19 | .. toctree::
20 | :maxdepth: 2
21 | :caption: Examples
22 |
23 | examples/index
24 |
25 | .. toctree::
26 | :maxdepth: 1
27 | :caption: Api Reference
28 |
29 | py-modindex
30 |
--------------------------------------------------------------------------------
/docs/py-modindex.rst:
--------------------------------------------------------------------------------
1 | Api Reference
2 | =============
3 |
--------------------------------------------------------------------------------
/docs/source/cai.api.rst:
--------------------------------------------------------------------------------
1 | cai.api package
2 | ===============
3 |
4 | .. automodule:: cai.api
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.api.client module
13 | ---------------------
14 |
15 | .. automodule:: cai.api.client
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.api.flow module
21 | -------------------
22 |
23 | .. automodule:: cai.api.flow
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
28 | cai.api.friend module
29 | ---------------------
30 |
31 | .. automodule:: cai.api.friend
32 | :members:
33 | :undoc-members:
34 | :show-inheritance:
35 |
36 | cai.api.group module
37 | --------------------
38 |
39 | .. automodule:: cai.api.group
40 | :members:
41 | :undoc-members:
42 | :show-inheritance:
43 |
44 | cai.api.login module
45 | --------------------
46 |
47 | .. automodule:: cai.api.login
48 | :members:
49 | :undoc-members:
50 | :show-inheritance:
51 |
--------------------------------------------------------------------------------
/docs/source/cai.client.config_push.rst:
--------------------------------------------------------------------------------
1 | cai.client.config\_push package
2 | ===============================
3 |
4 | .. automodule:: cai.client.config_push
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.config\_push.command module
13 | --------------------------------------
14 |
15 | .. automodule:: cai.client.config_push.command
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.client.config\_push.jce module
21 | ----------------------------------
22 |
23 | .. automodule:: cai.client.config_push.jce
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
--------------------------------------------------------------------------------
/docs/source/cai.client.friendlist.rst:
--------------------------------------------------------------------------------
1 | cai.client.friendlist package
2 | =============================
3 |
4 | .. automodule:: cai.client.friendlist
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.friendlist.command module
13 | ------------------------------------
14 |
15 | .. automodule:: cai.client.friendlist.command
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.client.friendlist.jce module
21 | --------------------------------
22 |
23 | .. automodule:: cai.client.friendlist.jce
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
--------------------------------------------------------------------------------
/docs/source/cai.client.heartbeat.rst:
--------------------------------------------------------------------------------
1 | cai.client.heartbeat package
2 | ============================
3 |
4 | .. automodule:: cai.client.heartbeat
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/docs/source/cai.client.message_service.rst:
--------------------------------------------------------------------------------
1 | cai.client.message\_service package
2 | ===================================
3 |
4 | .. automodule:: cai.client.message_service
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.message\_service.command module
13 | ------------------------------------------
14 |
15 | .. automodule:: cai.client.message_service.command
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.client.message\_service.decoders module
21 | -------------------------------------------
22 |
23 | .. automodule:: cai.client.message_service.decoders
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
28 | cai.client.message\_service.jce module
29 | --------------------------------------
30 |
31 | .. automodule:: cai.client.message_service.jce
32 | :members:
33 | :undoc-members:
34 | :show-inheritance:
35 |
36 | cai.client.message\_service.models module
37 | -----------------------------------------
38 |
39 | .. automodule:: cai.client.message_service.models
40 | :members:
41 | :undoc-members:
42 | :show-inheritance:
43 |
--------------------------------------------------------------------------------
/docs/source/cai.client.online_push.rst:
--------------------------------------------------------------------------------
1 | cai.client.online\_push package
2 | ===============================
3 |
4 | .. automodule:: cai.client.online_push
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.online\_push.command module
13 | --------------------------------------
14 |
15 | .. automodule:: cai.client.online_push.command
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.client.online\_push.jce module
21 | ----------------------------------
22 |
23 | .. automodule:: cai.client.online_push.jce
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
--------------------------------------------------------------------------------
/docs/source/cai.client.qq_service.rst:
--------------------------------------------------------------------------------
1 | cai.client.qq\_service package
2 | ==============================
3 |
4 | .. automodule:: cai.client.qq_service
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.qq\_service.jce module
13 | ---------------------------------
14 |
15 | .. automodule:: cai.client.qq_service.jce
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.client.rst:
--------------------------------------------------------------------------------
1 | cai.client package
2 | ==================
3 |
4 | .. automodule:: cai.client
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.client.config_push
16 | cai.client.friendlist
17 | cai.client.heartbeat
18 | cai.client.message_service
19 | cai.client.online_push
20 | cai.client.qq_service
21 | cai.client.sso_server
22 | cai.client.status_service
23 | cai.client.wtlogin
24 |
25 | Submodules
26 | ----------
27 |
28 | cai.client.client module
29 | ------------------------
30 |
31 | .. automodule:: cai.client.client
32 | :members:
33 | :undoc-members:
34 | :show-inheritance:
35 |
36 | cai.client.command module
37 | -------------------------
38 |
39 | .. automodule:: cai.client.command
40 | :members:
41 | :undoc-members:
42 | :show-inheritance:
43 |
44 | cai.client.event module
45 | -----------------------
46 |
47 | .. automodule:: cai.client.event
48 | :members:
49 | :undoc-members:
50 | :show-inheritance:
51 |
52 | cai.client.models module
53 | ------------------------
54 |
55 | .. automodule:: cai.client.models
56 | :members:
57 | :undoc-members:
58 | :show-inheritance:
59 |
60 | cai.client.packet module
61 | ------------------------
62 |
63 | .. automodule:: cai.client.packet
64 | :members:
65 | :undoc-members:
66 | :show-inheritance:
67 |
--------------------------------------------------------------------------------
/docs/source/cai.client.sso_server.rst:
--------------------------------------------------------------------------------
1 | cai.client.sso\_server package
2 | ==============================
3 |
4 | .. automodule:: cai.client.sso_server
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.sso\_server.jce module
13 | ---------------------------------
14 |
15 | .. automodule:: cai.client.sso_server.jce
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.client.status_service.rst:
--------------------------------------------------------------------------------
1 | cai.client.status\_service package
2 | ==================================
3 |
4 | .. automodule:: cai.client.status_service
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.status\_service.command module
13 | -----------------------------------------
14 |
15 | .. automodule:: cai.client.status_service.command
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.client.status\_service.jce module
21 | -------------------------------------
22 |
23 | .. automodule:: cai.client.status_service.jce
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
--------------------------------------------------------------------------------
/docs/source/cai.client.wtlogin.rst:
--------------------------------------------------------------------------------
1 | cai.client.wtlogin package
2 | ==========================
3 |
4 | .. automodule:: cai.client.wtlogin
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.client.wtlogin.oicq module
13 | ------------------------------
14 |
15 | .. automodule:: cai.client.wtlogin.oicq
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.client.wtlogin.tlv module
21 | -----------------------------
22 |
23 | .. automodule:: cai.client.wtlogin.tlv
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
--------------------------------------------------------------------------------
/docs/source/cai.connection.rst:
--------------------------------------------------------------------------------
1 | cai.connection package
2 | ======================
3 |
4 | .. automodule:: cai.connection
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.connection.utils module
13 | ---------------------------
14 |
15 | .. automodule:: cai.connection.utils
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.msg.common.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.msg.common package
2 | ============================
3 |
4 | .. automodule:: cai.pb.im.msg.common
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.msg.common.common\_pb2 module
13 | ---------------------------------------
14 |
15 | .. automodule:: cai.pb.im.msg.common.common_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.msg.msg.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.msg.msg package
2 | =========================
3 |
4 | .. automodule:: cai.pb.im.msg.msg
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.msg.msg.msg\_pb2 module
13 | ---------------------------------
14 |
15 | .. automodule:: cai.pb.im.msg.msg.msg_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.msg.msg_body.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.msg.msg\_body package
2 | ===============================
3 |
4 | .. automodule:: cai.pb.im.msg.msg_body
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.msg.msg\_body.msg\_body\_pb2 module
13 | ---------------------------------------------
14 |
15 | .. automodule:: cai.pb.im.msg.msg_body.msg_body_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.msg.msg_head.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.msg.msg\_head package
2 | ===============================
3 |
4 | .. automodule:: cai.pb.im.msg.msg_head
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.msg.msg\_head.msg\_head\_pb2 module
13 | ---------------------------------------------
14 |
15 | .. automodule:: cai.pb.im.msg.msg_head.msg_head_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.msg.obj_msg.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.msg.obj\_msg package
2 | ==============================
3 |
4 | .. automodule:: cai.pb.im.msg.obj_msg
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.msg.obj\_msg.obj\_msg\_pb2 module
13 | -------------------------------------------
14 |
15 | .. automodule:: cai.pb.im.msg.obj_msg.obj_msg_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.msg.receipt.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.msg.receipt package
2 | =============================
3 |
4 | .. automodule:: cai.pb.im.msg.receipt
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.msg.receipt.receipt\_pb2 module
13 | -----------------------------------------
14 |
15 | .. automodule:: cai.pb.im.msg.receipt.receipt_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.msg.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.msg package
2 | =====================
3 |
4 | .. automodule:: cai.pb.im.msg
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.pb.im.msg.common
16 | cai.pb.im.msg.msg
17 | cai.pb.im.msg.msg_body
18 | cai.pb.im.msg.msg_head
19 | cai.pb.im.msg.obj_msg
20 | cai.pb.im.msg.receipt
21 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.oidb.cmd0x769.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.oidb.cmd0x769 package
2 | ===============================
3 |
4 | .. automodule:: cai.pb.im.oidb.cmd0x769
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.oidb.cmd0x769.cmd0x769\_pb2 module
13 | --------------------------------------------
14 |
15 | .. automodule:: cai.pb.im.oidb.cmd0x769.cmd0x769_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.oidb.cmd0xd50.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.oidb.cmd0xd50 package
2 | ===============================
3 |
4 | .. automodule:: cai.pb.im.oidb.cmd0xd50
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.im.oidb.cmd0xd50.cmd0xd50\_pb2 module
13 | --------------------------------------------
14 |
15 | .. automodule:: cai.pb.im.oidb.cmd0xd50.cmd0xd50_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.oidb.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im.oidb package
2 | ======================
3 |
4 | .. automodule:: cai.pb.im.oidb
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.pb.im.oidb.cmd0x769
16 | cai.pb.im.oidb.cmd0xd50
17 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.im.rst:
--------------------------------------------------------------------------------
1 | cai.pb.im package
2 | =================
3 |
4 | .. automodule:: cai.pb.im
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.pb.im.msg
16 | cai.pb.im.oidb
17 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.msf.msg.comm.rst:
--------------------------------------------------------------------------------
1 | cai.pb.msf.msg.comm package
2 | ===========================
3 |
4 | .. automodule:: cai.pb.msf.msg.comm
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.msf.msg.comm.comm\_pb2 module
13 | ------------------------------------
14 |
15 | .. automodule:: cai.pb.msf.msg.comm.comm_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.msf.msg.ctrl.rst:
--------------------------------------------------------------------------------
1 | cai.pb.msf.msg.ctrl package
2 | ===========================
3 |
4 | .. automodule:: cai.pb.msf.msg.ctrl
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.msf.msg.ctrl.ctrl\_pb2 module
13 | ------------------------------------
14 |
15 | .. automodule:: cai.pb.msf.msg.ctrl.ctrl_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.msf.msg.onlinepush.rst:
--------------------------------------------------------------------------------
1 | cai.pb.msf.msg.onlinepush package
2 | =================================
3 |
4 | .. automodule:: cai.pb.msf.msg.onlinepush
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.msf.msg.onlinepush.onlinepush\_pb2 module
13 | ------------------------------------------------
14 |
15 | .. automodule:: cai.pb.msf.msg.onlinepush.onlinepush_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.msf.msg.rst:
--------------------------------------------------------------------------------
1 | cai.pb.msf.msg package
2 | ======================
3 |
4 | .. automodule:: cai.pb.msf.msg
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.pb.msf.msg.comm
16 | cai.pb.msf.msg.ctrl
17 | cai.pb.msf.msg.onlinepush
18 | cai.pb.msf.msg.svc
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.msf.msg.svc.rst:
--------------------------------------------------------------------------------
1 | cai.pb.msf.msg.svc package
2 | ==========================
3 |
4 | .. automodule:: cai.pb.msf.msg.svc
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.msf.msg.svc.svc\_pb2 module
13 | ----------------------------------
14 |
15 | .. automodule:: cai.pb.msf.msg.svc.svc_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.msf.rst:
--------------------------------------------------------------------------------
1 | cai.pb.msf package
2 | ==================
3 |
4 | .. automodule:: cai.pb.msf
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.pb.msf.msg
16 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.rst:
--------------------------------------------------------------------------------
1 | cai.pb package
2 | ==============
3 |
4 | .. automodule:: cai.pb
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.pb.im
16 | cai.pb.msf
17 | cai.pb.wtlogin
18 |
--------------------------------------------------------------------------------
/docs/source/cai.pb.wtlogin.rst:
--------------------------------------------------------------------------------
1 | cai.pb.wtlogin package
2 | ======================
3 |
4 | .. automodule:: cai.pb.wtlogin
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.pb.wtlogin.data\_pb2 module
13 | -------------------------------
14 |
15 | .. automodule:: cai.pb.wtlogin.data_pb2
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.rst:
--------------------------------------------------------------------------------
1 | cai package
2 | ===========
3 |
4 | .. automodule:: cai
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Subpackages
10 | -----------
11 |
12 | .. toctree::
13 | :maxdepth: 4
14 |
15 | cai.api
16 | cai.client
17 | cai.connection
18 | cai.pb
19 | cai.settings
20 | cai.storage
21 | cai.utils
22 |
23 | Submodules
24 | ----------
25 |
26 | cai.exceptions module
27 | ---------------------
28 |
29 | .. automodule:: cai.exceptions
30 | :members:
31 | :undoc-members:
32 | :show-inheritance:
33 |
34 | cai.log module
35 | --------------
36 |
37 | .. automodule:: cai.log
38 | :members:
39 | :undoc-members:
40 | :show-inheritance:
41 |
--------------------------------------------------------------------------------
/docs/source/cai.settings.rst:
--------------------------------------------------------------------------------
1 | cai.settings package
2 | ====================
3 |
4 | .. automodule:: cai.settings
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.settings.device module
13 | --------------------------
14 |
15 | .. automodule:: cai.settings.device
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.settings.protocol module
21 | ----------------------------
22 |
23 | .. automodule:: cai.settings.protocol
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
--------------------------------------------------------------------------------
/docs/source/cai.storage.rst:
--------------------------------------------------------------------------------
1 | cai.storage package
2 | ===================
3 |
4 | .. automodule:: cai.storage
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.storage.utils module
13 | ------------------------
14 |
15 | .. automodule:: cai.storage.utils
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/source/cai.utils.rst:
--------------------------------------------------------------------------------
1 | cai.utils package
2 | =================
3 |
4 | .. automodule:: cai.utils
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | Submodules
10 | ----------
11 |
12 | cai.utils.binary module
13 | -----------------------
14 |
15 | .. automodule:: cai.utils.binary
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | cai.utils.coroutine module
21 | --------------------------
22 |
23 | .. automodule:: cai.utils.coroutine
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
28 | cai.utils.crypto module
29 | -----------------------
30 |
31 | .. automodule:: cai.utils.crypto
32 | :members:
33 | :undoc-members:
34 | :show-inheritance:
35 |
36 | cai.utils.dataclass module
37 | --------------------------
38 |
39 | .. automodule:: cai.utils.dataclass
40 | :members:
41 | :undoc-members:
42 | :show-inheritance:
43 |
44 | cai.utils.future module
45 | -----------------------
46 |
47 | .. automodule:: cai.utils.future
48 | :members:
49 | :undoc-members:
50 | :show-inheritance:
51 |
52 | cai.utils.jce module
53 | --------------------
54 |
55 | .. automodule:: cai.utils.jce
56 | :members:
57 | :undoc-members:
58 | :show-inheritance:
59 |
--------------------------------------------------------------------------------
/docs/source/modules.rst:
--------------------------------------------------------------------------------
1 | cai
2 | ===
3 |
4 | .. toctree::
5 | :maxdepth: 4
6 |
7 | cai
8 |
--------------------------------------------------------------------------------
/examples/friend_group.py:
--------------------------------------------------------------------------------
1 | """Example Code for Friend/Group.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 | import os
10 | import signal
11 | import asyncio
12 | from hashlib import md5
13 |
14 | import cai
15 |
16 |
17 | async def run():
18 | account = os.getenv("ACCOUNT", "")
19 | password = os.getenv("PASSWORD")
20 | try:
21 | account = int(account)
22 | assert password
23 | except Exception:
24 | print(
25 | f"Error: account '{account}', password '{password}'" # type: ignore
26 | )
27 | return
28 |
29 | client = await cai.login(account, md5(password.encode()).digest())
30 |
31 | # friend
32 | friend_list = await cai.get_friend_list()
33 | friend_group_list = await cai.get_friend_group_list()
34 | print("========== friends ==========", *friend_list, sep="\n")
35 | print("========== friend groups ==========", *friend_group_list, sep="\n")
36 | example_friend = friend_list[0]
37 | # friend = await cai.get_friend(friend_uin)
38 | print("========== example friend ==========")
39 | print("uin: ", example_friend.uin)
40 | print("nick: ", example_friend.nick)
41 | print("remark: ", example_friend.remark)
42 | print("group: ", await example_friend.get_group())
43 |
44 | group_list = await cai.get_group_list()
45 | print("\n========== group list ==========", *group_list, sep="\n")
46 | example_group = group_list[0]
47 | # group = await cai.get_group(group_id)
48 | print("========== example group ==========")
49 | print("group id: ", example_group.group_id)
50 | print("group name: ", example_group.group_name)
51 | print("group owner: ", example_group.group_owner_uin)
52 | example_group_member_list = await example_group.get_members()
53 | print(
54 | "========== example group members ==========",
55 | *example_group_member_list,
56 | sep="\n",
57 | )
58 |
59 |
60 | if __name__ == "__main__":
61 | close = asyncio.Event()
62 |
63 | async def wait_cleanup():
64 | await close.wait()
65 | await cai.close_all()
66 |
67 | loop = asyncio.get_event_loop()
68 | loop.add_signal_handler(signal.SIGINT, close.set)
69 | loop.add_signal_handler(signal.SIGTERM, close.set)
70 | loop.create_task(run())
71 | loop.run_until_complete(wait_cleanup())
72 |
--------------------------------------------------------------------------------
/examples/login.py:
--------------------------------------------------------------------------------
1 | """Example Code for Login.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 | import os
10 | import signal
11 | import asyncio
12 | import traceback
13 | from io import BytesIO
14 | from hashlib import md5
15 |
16 | from PIL import Image
17 |
18 | import cai
19 | from cai.exceptions import (
20 | LoginException,
21 | ApiResponseError,
22 | LoginDeviceLocked,
23 | LoginSliderNeeded,
24 | LoginAccountFrozen,
25 | LoginCaptchaNeeded,
26 | )
27 |
28 |
29 | async def run():
30 | account = os.getenv("ACCOUNT", "")
31 | password = os.getenv("PASSWORD")
32 | try:
33 | account = int(account)
34 | assert password
35 | except Exception:
36 | print(
37 | f"Error: account '{account}', password '{password}'" # type: ignore
38 | )
39 | return
40 |
41 | try:
42 | client = await cai.login(account, md5(password.encode()).digest())
43 | print(f"Login Success! Client status: {client.status!r}")
44 | except Exception as e:
45 | await handle_failure(e)
46 |
47 |
48 | async def handle_failure(exception: Exception):
49 | if isinstance(exception, LoginSliderNeeded):
50 | print("Verify url:", exception.verify_url)
51 | ticket = input("Please enter the ticket: ").strip()
52 | try:
53 | await cai.submit_slider_ticket(ticket)
54 | print("Login Success!")
55 | await asyncio.sleep(3)
56 | except Exception as e:
57 | await handle_failure(e)
58 | elif isinstance(exception, LoginCaptchaNeeded):
59 | print("Captcha:")
60 | image = Image.open(BytesIO(exception.captcha_image))
61 | image.show()
62 | captcha = input("Please enter the captcha: ").strip()
63 | try:
64 | await cai.submit_captcha(captcha, exception.captcha_sign)
65 | print("Login Success!")
66 | await asyncio.sleep(3)
67 | except Exception as e:
68 | await handle_failure(e)
69 | elif isinstance(exception, LoginAccountFrozen):
70 | print("Account is frozen!")
71 | elif isinstance(exception, LoginDeviceLocked):
72 | print("Device lock detected!")
73 | way = (
74 | "sms"
75 | if exception.sms_phone
76 | else "url"
77 | if exception.verify_url
78 | else ""
79 | )
80 | if exception.sms_phone and exception.verify_url:
81 | while True:
82 | choice = input(
83 | f"1. Send sms message to {exception.sms_phone}.\n"
84 | f"2. Verify device by scanning.\nChoose: "
85 | )
86 | if "1" in choice:
87 | way = "sms"
88 | break
89 | elif "2" in choice:
90 | way = "url"
91 | break
92 | print(f"'{choice}' is not valid!")
93 | if not way:
94 | print("No way to verify device...")
95 | elif way == "sms":
96 | await cai.request_sms()
97 | print(f"SMS was sent to {exception.sms_phone}!")
98 | sms_code = input("Please enter the sms_code: ").strip()
99 | try:
100 | await cai.submit_sms(sms_code)
101 | except Exception as e:
102 | await handle_failure(e)
103 | elif way == "url":
104 | await cai.close()
105 | print(f"Go to {exception.verify_url} to verify device!")
106 | input("Press ENTER after verification to continue login...")
107 | try:
108 | await cai.login(exception.uin)
109 | except Exception as e:
110 | await handle_failure(e)
111 | elif isinstance(exception, LoginException):
112 | print("Login Error:", repr(exception))
113 | elif isinstance(exception, ApiResponseError):
114 | print("Response Error:", repr(exception))
115 | else:
116 | print("Unknown Error:", repr(exception))
117 | traceback.print_exc()
118 |
119 |
120 | if __name__ == "__main__":
121 | close = asyncio.Event()
122 |
123 | async def wait_cleanup():
124 | await close.wait()
125 | await cai.close_all()
126 |
127 | loop = asyncio.get_event_loop()
128 | loop.add_signal_handler(signal.SIGINT, close.set)
129 | loop.add_signal_handler(signal.SIGTERM, close.set)
130 | loop.create_task(run())
131 | loop.run_until_complete(wait_cleanup())
132 |
--------------------------------------------------------------------------------
/examples/message.py:
--------------------------------------------------------------------------------
1 | """Example Code for Message.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 | import os
10 | import signal
11 | import asyncio
12 | from hashlib import md5
13 |
14 | import cai
15 | from cai.client import Event, Client, GroupMessage, PrivateMessage
16 |
17 |
18 | async def run():
19 | account = os.getenv("ACCOUNT", "")
20 | password = os.getenv("PASSWORD")
21 | try:
22 | account = int(account)
23 | assert password
24 | except Exception:
25 | print(
26 | f"Error: account '{account}', password '{password}'" # type: ignore
27 | )
28 | return
29 |
30 | client = await cai.login(account, md5(password.encode()).digest())
31 |
32 | cai.add_event_listener(listen_message)
33 | # cai.add_event_listener(listen_message, uin=account)
34 | # client.add_event_listener(listen_message)
35 |
36 |
37 | async def listen_message(client: Client, event: Event):
38 | if isinstance(event, PrivateMessage):
39 | print("Private Message received from", event.from_uin)
40 | print("Private Message elements:", event.message)
41 | elif isinstance(event, GroupMessage):
42 | print(
43 | f"Group Message received from {event.group_name}({event.group_id})"
44 | )
45 | print("Group Message elements:", event.message)
46 |
47 |
48 | if __name__ == "__main__":
49 | close = asyncio.Event()
50 |
51 | async def wait_cleanup():
52 | await close.wait()
53 | await cai.close_all()
54 |
55 | loop = asyncio.get_event_loop()
56 | loop.add_signal_handler(signal.SIGINT, close.set)
57 | loop.add_signal_handler(signal.SIGTERM, close.set)
58 | loop.create_task(run())
59 | loop.run_until_complete(wait_cleanup())
60 |
--------------------------------------------------------------------------------
/examples/set_status.py:
--------------------------------------------------------------------------------
1 | """Example Code for Set Client Status.
2 |
3 | :Copyright: Copyright (C) 2021-2021 cscs181
4 | :License: AGPL-3.0 or later. See `LICENSE`_ for detail.
5 |
6 | .. _LICENSE:
7 | https://github.com/cscs181/CAI/blob/master/LICENSE
8 | """
9 | import os
10 | import signal
11 | import asyncio
12 | from hashlib import md5
13 |
14 | import cai
15 | from cai.client import OnlineStatus
16 |
17 |
18 | async def run():
19 | account = os.getenv("ACCOUNT", "")
20 | password = os.getenv("PASSWORD")
21 | try:
22 | account = int(account)
23 | assert password
24 | except Exception:
25 | print(
26 | f"Error: account '{account}', password '{password}'" # type: ignore
27 | )
28 | return
29 |
30 | client = await cai.login(account, md5(password.encode()).digest())
31 |
32 | await asyncio.sleep(10)
33 | await cai.set_status(OnlineStatus.Qme)
34 | print("Current client status: ", client.status)
35 |
36 |
37 | if __name__ == "__main__":
38 | close = asyncio.Event()
39 |
40 | async def wait_cleanup():
41 | await close.wait()
42 | await cai.close_all()
43 |
44 | loop = asyncio.get_event_loop()
45 | loop.add_signal_handler(signal.SIGINT, close.set)
46 | loop.add_signal_handler(signal.SIGTERM, close.set)
47 | loop.create_task(run())
48 | loop.run_until_complete(wait_cleanup())
49 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "CAI"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["yanyongyu "]
6 | license = "AGPL-3.0-or-later"
7 | readme = "README.md"
8 | homepage = "https://github.com/cscs181/CAI"
9 | repository = "https://github.com/cscs181/CAI"
10 | documentation = "https://github.com/cscs181/CAI#readme"
11 | keywords = ["qq", "mirai", "cqhttp"]
12 | classifiers = ["Framework :: Robot Framework", "Programming Language :: Python :: 3"]
13 | include = ["cai/py.typed"]
14 |
15 | [tool.poetry.dependencies]
16 | python = "^3.7"
17 | rtea = "^0.3.0"
18 | jcestruct = "^0.1.0"
19 | protobuf = "^3.14.0"
20 | cachetools = "^4.2.2"
21 | cryptography = "^3.4.1"
22 | typing-extensions = ">=3.10.0,<5.0.0"
23 |
24 | [tool.poetry.dev-dependencies]
25 | isort = "^5.9.3"
26 | black = "^21.7b0"
27 | pillow = "^8.1.0"
28 | sphinx = "^4.1.0"
29 | mypy-protobuf = "^2.4"
30 | sphinx-rtd-theme = "^0.5.1"
31 | sphinx-copybutton = "^0.4.0"
32 | sphinxcontrib-napoleon = "^0.7"
33 |
34 | # [[tool.poetry.source]]
35 | # name = "aliyun"
36 | # url = "https://mirrors.aliyun.com/pypi/simple/"
37 | # default = true
38 |
39 | [tool.black]
40 | line-length = 80
41 | extend-exclude = '''
42 | ^/cai/pb/
43 | '''
44 |
45 | [tool.isort]
46 | profile = "black"
47 | line_length = 80
48 | length_sort = true
49 | extend_skip = "cai/pb"
50 | skip_gitignore = true
51 | force_sort_within_sections = true
52 | extra_standard_library = "typing_extensions"
53 |
54 | [tool.pyright]
55 | ignore = [".vscode/*.py", "**/site-packages/**/*.py", "cai/pb/**/*_pb2.py"]
56 |
57 | [build-system]
58 | requires = ["poetry-core>=1.0.0"]
59 | build-backend = "poetry.core.masonry.api"
60 |
--------------------------------------------------------------------------------
/tests/client/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/tests/client/__init__.py
--------------------------------------------------------------------------------
/tests/client/test_login.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import logging
3 | import unittest
4 | import unittest.mock
5 | from hashlib import md5
6 |
7 | from cai.log import logger
8 | import cai.settings.device as device
9 | import cai.settings.protocol as protocol
10 |
11 | # clear cache
12 | for module in list(sys.modules.keys()):
13 | if module.startswith("cai") and not module in [
14 | "cai.settings.device",
15 | "cai.settings.protocol",
16 | "cai.log",
17 | ]:
18 | del sys.modules[module]
19 |
20 | mock_device = unittest.mock.patch.object(
21 | device,
22 | "get_device",
23 | return_value=device.DeviceInfo(
24 | product="missi",
25 | device="venus",
26 | board="venus",
27 | brand="Xiaomi",
28 | model="MI 11",
29 | vendor_name="MIUI",
30 | vendor_os_name="MIUI",
31 | bootloader="unknown",
32 | boot_id="dc109fd7-f17f-4f43-a266-b68469c19a1f",
33 | proc_version="Linux version 4.19.71-ab0b8e88 (android-build@github.com)",
34 | baseband="",
35 | mac_address="89:C2:A9:C5:FA:E9",
36 | ip_address="10.0.46.76",
37 | wifi_ssid="",
38 | imei="862542082770767",
39 | android_id="BRAND.141613.779",
40 | version=device.Version(
41 | incremental="V12.0.19.0.RKBCNXM",
42 | release="11",
43 | codename="REL",
44 | sdk=30,
45 | ),
46 | sim="T-Mobile",
47 | os_type="android",
48 | apn="wifi",
49 | _imsi_md5="0f63d5c351fd1d75a29d88cae86d315d",
50 | _tgtgt_md5="c4e512e6924e4872e552e861a914d49a",
51 | _guid_md5=None,
52 | ),
53 | )
54 | mock_protocol = unittest.mock.patch.object(
55 | protocol,
56 | "get_protocol",
57 | return_value=protocol.ApkInfo(
58 | apk_id="com.tencent.minihd.qq",
59 | app_id=16,
60 | sub_app_id=537065739,
61 | version="5.8.9",
62 | build_time=1595836208,
63 | apk_sign=bytes(
64 | [
65 | 170,
66 | 57,
67 | 120,
68 | 244,
69 | 31,
70 | 217,
71 | 111,
72 | 249,
73 | 145,
74 | 74,
75 | 102,
76 | 158,
77 | 24,
78 | 100,
79 | 116,
80 | 199,
81 | ]
82 | ),
83 | sdk_version="6.0.0.2433",
84 | sso_version=12,
85 | bitmap=150470524,
86 | main_sigmap=1970400,
87 | sub_sigmap=66560,
88 | ),
89 | )
90 |
91 | with mock_device:
92 | with mock_protocol:
93 | from cai.settings.device import get_device
94 | from cai.client.wtlogin import encode_login_request9
95 |
96 |
97 | class TestEncodeLoginRequest(unittest.IsolatedAsyncioTestCase):
98 | def log(self, level: int, message: str, *args, exc_info=False, **kwargs):
99 | message = "| TestEncodeLoginRequest | " + message
100 | return logger.log(level, message, *args, exc_info=exc_info, **kwargs)
101 |
102 | def setUp(self):
103 | self.log(logging.INFO, "Start Testing Encode Login Request...")
104 |
105 | def tearDown(self):
106 | self.log(logging.INFO, "End Testing Encode Login Request!")
107 |
108 | def test_encode_login_request(self):
109 | self.log(logging.INFO, "test encode login request")
110 | # ensure encode has no error
111 | packet = encode_login_request9(
112 | 10,
113 | bytes(16),
114 | bytes([0x02, 0xB0, 0x5B, 0x8B]),
115 | f"|{get_device().imei}|A8.2.7.27f6ea96".encode(),
116 | 123456,
117 | md5("123456".encode()).digest(),
118 | )
119 | # print(packet.hex())
120 |
121 |
122 | if __name__ == "__main__":
123 | unittest.main()
124 |
--------------------------------------------------------------------------------
/tests/client/test_sso_server.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import unittest
3 | import ipaddress
4 |
5 | from cai.log import logger
6 | from cai.client.sso_server import (
7 | SsoServer,
8 | SsoServerResponse,
9 | get_sso_list,
10 | get_sso_server,
11 | )
12 |
13 |
14 | class TestSsoServer(unittest.IsolatedAsyncioTestCase):
15 | def log(self, level: int, message: str, *args, exc_info=False, **kwargs):
16 | message = "| TestSsoServer | " + message
17 | return logger.log(level, message, *args, exc_info=exc_info, **kwargs)
18 |
19 | def setUp(self):
20 | self.log(logging.INFO, "Start Testing SsoServer...")
21 |
22 | def tearDown(self):
23 | self.log(logging.INFO, "End Testing SsoServer!")
24 |
25 | async def test_get_list(self):
26 | self.log(logging.INFO, "test get sso server response")
27 | sso_list = await get_sso_list()
28 | self.assertIsInstance(sso_list, SsoServerResponse)
29 | self.assertGreater(
30 | len(sso_list.socket_v4_mobile) + len(sso_list.socket_v4_wifi), 0
31 | )
32 |
33 | async def test_get_sso_server(self):
34 | self.log(logging.INFO, "test get sso server")
35 | sso_server = await get_sso_server(cache=False)
36 | self.assertIsInstance(sso_server, SsoServer)
37 | self.assertIsInstance(
38 | ipaddress.ip_address(sso_server.host), ipaddress.IPv4Address
39 | )
40 |
41 | self.log(logging.INFO, "test get sso server again with cache")
42 | sso_server_cached = await get_sso_server(cache=True)
43 | self.assertIs(sso_server_cached, sso_server)
44 |
45 |
46 | if __name__ == "__main__":
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/tests/connection/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cscs181/CAI/89eb91ce0d9eb609f30ecaa4007a0e84f094d86d/tests/connection/__init__.py
--------------------------------------------------------------------------------
/tests/connection/test_connection.py:
--------------------------------------------------------------------------------
1 | import time
2 | import asyncio
3 | import logging
4 | import unittest
5 | from types import TracebackType
6 | from typing import Any, Type, Tuple, Union, Optional
7 |
8 | from cai.log import logger
9 | from cai.connection import Connection, connect
10 | from cai.connection.utils import tcp_latency_test
11 |
12 | _SysExcInfoType = Union[
13 | Tuple[Type[BaseException], BaseException, Optional[TracebackType]],
14 | Tuple[None, None, None],
15 | ]
16 |
17 |
18 | class TestConnection(unittest.IsolatedAsyncioTestCase):
19 | def log(
20 | self,
21 | level: int,
22 | message: str,
23 | *args: Any,
24 | exc_info: Union[None, bool, Exception, _SysExcInfoType] = False,
25 | **kwargs: Any,
26 | ):
27 | message = "| TestConnection | " + message
28 | return logger.log(level, message, *args, exc_info=exc_info, **kwargs)
29 |
30 | def setUp(self):
31 | self.log(logging.INFO, "Start Testing Connection...")
32 |
33 | def tearDown(self):
34 | self.log(logging.INFO, "End Testing Connection!")
35 |
36 | async def test_tcp_echo(self):
37 | MSG: bytes = b"Hello"
38 |
39 | self.log(logging.INFO, "test tcp echo")
40 |
41 | start = time.time()
42 | conn = await connect("tcpbin.com", 4242, timeout=10.0)
43 | end = time.time()
44 | self.assertIsInstance(conn, Connection)
45 | self.log(logging.INFO, f"connected in {end - start} seconds")
46 |
47 | conn.write(MSG)
48 | conn.write_eof()
49 | resp = await conn.read_all()
50 | self.log(logging.INFO, f"received in {time.time() - end} seconds")
51 | await conn.close()
52 | self.assertEqual(resp, MSG)
53 |
54 | await asyncio.sleep(3)
55 |
56 | self.log(logging.INFO, "test tcp echo with context manager")
57 |
58 | start = time.time()
59 | async with connect("tcpbin.com", 4242, timeout=10.0) as conn:
60 | end = time.time()
61 | self.assertIsInstance(conn, Connection)
62 | self.log(logging.INFO, f"connected in {end - start} seconds")
63 |
64 | conn.write(MSG)
65 | conn.write_eof()
66 | resp = await conn.read_all()
67 | self.log(logging.INFO, f"received in {time.time() - end} seconds")
68 | self.assertEqual(resp, MSG)
69 |
70 | async def test_tcp_latency(self):
71 | self.log(logging.INFO, "test TCP latency")
72 |
73 | delay = await tcp_latency_test("tcpbin.com", 4242, timeout=10.0)
74 | self.assertIsInstance(delay, float)
75 | self.assertGreater(delay, 0.0)
76 |
77 |
78 | if __name__ == "__main__":
79 | unittest.main()
80 |
--------------------------------------------------------------------------------