Skip to content
This repository has been archived by the owner on Oct 21, 2023. It is now read-only.

Sync Change #39

Draft
wants to merge 119 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
01abd29
:twisted_rightwards_arrows: Merge pull request #31
yanyongyu May 28, 2021
7d46eaa
:twisted_rightwards_arrows: Merge pull request #33
yanyongyu Aug 2, 2021
89eb91c
:twisted_rightwards_arrows: Merge pull request #37
yanyongyu Dec 8, 2021
25d746e
change: rewrite login.py
wyapx Mar 19, 2022
1c037d5
fix: logical of login.py
wyapx Mar 19, 2022
d776777
change: rewrite group.py
wyapx Mar 19, 2022
346fd7a
change: rewrite friend.py
wyapx Mar 19, 2022
8337fc0
change: rewrite client.py
wyapx Mar 19, 2022
80e00eb
update: cai/api
wyapx Mar 19, 2022
71b5811
update: examples/login.py
wyapx Mar 19, 2022
3ac1fc8
update: remove unused code
wyapx Mar 19, 2022
259cc94
remove: strong dependency on cai/settings
wyapx Mar 19, 2022
c134a6d
update: named logger
wyapx Mar 19, 2022
876b02f
update: logger
wyapx Mar 19, 2022
68ac10d
add: Client.status
wyapx Mar 19, 2022
06d03d6
add: more debug message
wyapx Mar 19, 2022
4097415
add: basic send group message[see example/login.py]
wyapx Mar 20, 2022
6cb9103
add: highway pb file
wyapx Mar 20, 2022
9a82405
update: todo
wyapx Mar 20, 2022
28def19
add: longmsg.proto
wyapx Mar 20, 2022
a98a144
add: Client.send_unipkg_and_wait & optimized code
wyapx Mar 20, 2022
ff083da
add: multi_apply_up_pkg handle
wyapx Mar 20, 2022
aa06c4e
add: GroupIdConverter
wyapx Mar 20, 2022
b7b3768
fix: some bugs
wyapx Mar 20, 2022
46c42c5
fix: some bugs
wyapx Mar 20, 2022
88064e4
add: cmd0x388
wyapx Mar 20, 2022
0d452b5
sync changes
wyapx Mar 21, 2022
8e41ae5
fix: struct parse error
wyapx Mar 22, 2022
c87d702
sync changes
wyapx Mar 22, 2022
6411f29
add: highway & upload image
wyapx Mar 23, 2022
1a97045
fix: send image to group
wyapx Mar 24, 2022
c318dcc
sync changes
wyapx Apr 3, 2022
646bc7b
add: reconnect & Client.closed
wyapx Apr 3, 2022
de92289
revert change
wyapx Apr 3, 2022
93279cf
update: split highway.py
wyapx Apr 3, 2022
270ed93
update: reconnect method
wyapx Apr 3, 2022
43ee0a4
remove: unused code
wyapx Apr 3, 2022
7905915
fix: login failfast
wyapx Apr 3, 2022
97ca3a4
add: FlashImage At RichMsg parse
wyapx Apr 3, 2022
398b92b
add: raise send_group_msg error
wyapx Apr 3, 2022
f73d868
add: send At & FlashImage
wyapx Apr 3, 2022
9afe298
add: send RichMsg
wyapx Apr 3, 2022
5cbafe8
fix: remove redundancy data of Reply
wyapx Apr 4, 2022
77cd27a
fix: rich_msg parse
wyapx Apr 4, 2022
ab243e0
change: heartbeat err
wyapx Apr 4, 2022
95d6d51
change: log level
wyapx Apr 4, 2022
d5fbcd8
add: AtAll recv/send
wyapx Apr 4, 2022
325e0ab
add: AtAll recv/send
wyapx Apr 4, 2022
18be327
add: Image.to_flash
wyapx Apr 4, 2022
446f383
fix: flash_image build err
wyapx Apr 4, 2022
897e78c
fix: flash_image no url
wyapx Apr 4, 2022
0054da1
add: shake_window & improve parser
wyapx Apr 4, 2022
e7d37e0
add: poke send
wyapx Apr 4, 2022
1d3f731
fix: highway bug & support send voice
wyapx Apr 5, 2022
ef0865f
add: parse voice.url
wyapx Apr 5, 2022
57617ee
:arrow_up: upgrade dependencies
yanyongyu Apr 7, 2022
0f8c524
:label: update typing for pep646
yanyongyu Apr 7, 2022
05eb52b
:art: format code
yanyongyu Apr 7, 2022
c61a84a
delete: unused file
wyapx Apr 7, 2022
23f8692
Revert "delete: unused file"
wyapx Apr 7, 2022
205a572
fix: event register
wyapx Apr 7, 2022
7e225de
fix: unsync changes
wyapx Apr 7, 2022
fdef3f9
fix: some bugs
wyapx Apr 7, 2022
3d1f263
add: more poke type
wyapx Apr 7, 2022
9f25e6b
change: PokeType
wyapx Apr 7, 2022
e0487e3
add: parse group mute/unmute,recall,nudge event
wyapx Apr 7, 2022
cff4e4c
fix: type error
wyapx Apr 7, 2022
eb17c61
add: send OnlinePush.RespPush & optimized code
wyapx Apr 7, 2022
473d8b2
sync file
wyapx Apr 7, 2022
cbb506e
:art: improve reconnect
yanyongyu Apr 8, 2022
21aa1ee
:wheelchair: fix reconnect init
yanyongyu Apr 8, 2022
55e9fac
:art: remove device handle param
yanyongyu Apr 8, 2022
4220e75
:arrow_up: upgrade protobuf gen
yanyongyu Apr 8, 2022
cc2c19b
:arrow_up: upgrade oidb proto files
yanyongyu Apr 8, 2022
7d7677a
add: reconnect fallback
wyapx Apr 8, 2022
73912ce
f**k u, tencent
wyapx Apr 8, 2022
7415cf1
update: close client when force_offline triggered
wyapx Apr 8, 2022
79ac21d
add: ignore repeat event
wyapx Apr 8, 2022
15e0728
fix: nudge event
wyapx Apr 8, 2022
3587d32
:art: rewrite reqpush
yanyongyu Apr 9, 2022
49e177b
fix: reconnect fail
wyapx Apr 9, 2022
6ce883c
fix: drop msg when reconnect
wyapx Apr 9, 2022
83f2d13
:wheelchair: refactor receiver start
yanyongyu Apr 9, 2022
7f8fb29
add: remove duplicates req
wyapx Apr 9, 2022
b4c52f2
Merge branch 'master' of https://github.com/wyapx/CAI
wyapx Apr 9, 2022
016b31e
:wheelchair: improve online push cache
yanyongyu Apr 9, 2022
e66eeef
:construction: process del msg
yanyongyu Apr 9, 2022
e099cfa
:wrench: improve account settings cache
yanyongyu Apr 9, 2022
4d4a6b0
:bug: fix delete online push seq error
yanyongyu Apr 9, 2022
0254e5a
:bug: fix incoming packet signed seq
yanyongyu Apr 9, 2022
3e6a61e
fix: unipkg seq signed
wyapx Apr 9, 2022
7752c20
change: incoming log message
wyapx Apr 9, 2022
b782fd5
Merge remote-tracking branch 'wyapx/master'
wyapx Apr 9, 2022
1d0bd82
:wrench: fix config dir error
yanyongyu Apr 10, 2022
1a819eb
:construction: process onlinepush reqpush types
yanyongyu Apr 10, 2022
a99179e
:sparkles: parse nudge event
yanyongyu Apr 11, 2022
3369b13
:sparkles: add group lucky character events
yanyongyu Apr 11, 2022
1b80059
fix: receiver bug
cincrement Apr 11, 2022
2773e55
Merge branch 'master' of https://github.com/wyapx/CAI into HEAD
cincrement Apr 11, 2022
69c097f
add: CustomDataElement
wyapx Apr 12, 2022
454f274
:construction: process event type default
yanyongyu Apr 12, 2022
c731371
:memo: update readme
yanyongyu Apr 12, 2022
60f7f84
:sparkles: add group member mute event
yanyongyu Apr 12, 2022
0799e2a
:construction: move comments
yanyongyu Apr 12, 2022
0999df1
add: part of oidb packet
wyapx Apr 13, 2022
808aa01
add: group operation
wyapx Apr 13, 2022
df052cf
fix: stupid problem
wyapx Apr 13, 2022
2dcf545
:bug: fix group mute count decode error
yanyongyu Apr 13, 2022
19dd4db
remove: unused file
wyapx Apr 7, 2022
c3b8c5a
add: image decoder
wyapx Apr 15, 2022
0c508c5
fix: image/enum.py
wyapx Apr 15, 2022
1d7efc7
update: image/enum.py
wyapx Apr 15, 2022
95df065
fix: image upload
wyapx Apr 15, 2022
ec74eda
fix: some design problem
wyapx Apr 15, 2022
abca32b
add: parse GroupFile
wyapx Apr 16, 2022
1f4d1a3
add: BMPDecoder & optimized code
wyapx Apr 16, 2022
0656836
remove: unused code
wyapx Apr 16, 2022
b3dbbb6
fix: send image fail
wyapx Apr 18, 2022
3c9c9d1
:art: improve parse message element
yanyongyu May 29, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ _✨ Yet Another Bot Framework for Tencent QQ Written in Python ✨_
<a target="_blank" href="https://github.com/sindresorhus/awesome">
<img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg">
</a>
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=5NsG-WIp3hqzM3ihjY87JEvsNCRdUW2x&jump_from=webapi">
<img src="https://img.shields.io/badge/qq%E7%BE%A4-768887710-success" alt="QQ Chat">
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=1SsvybRcQrgt6Kd5fV9W2Kpfje0l3VFw&jump_from=webapi">
<img src="https://img.shields.io/badge/qq%E7%BE%A4-552362998-success" alt="QQ Chat">
</a>
</p>

Expand Down
13 changes: 2 additions & 11 deletions cai/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@
https://github.com/cscs181/CAI/blob/master/LICENSE
"""

from typing import Dict
from .client import Client, make_client

from cai.client import Client

_clients: Dict[int, Client] = {}


from .flow import *
from .group import *
from .login import *
from .client import *
from .friend import *
__all__ = ["Client", "make_client"]
17 changes: 17 additions & 0 deletions cai/api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from cai.client.client import Client as client_t


class BaseAPI:
client: client_t

async def _executor(
self, func_name: str, *args, uncaught_error=False, **kwargs
):
if not hasattr(self.client, func_name):
raise AttributeError(f"client has no attribute '{func_name}'")
try:
return await getattr(self.client, func_name)(*args, **kwargs)
except Exception:
if uncaught_error:
await self.client.close()
raise
207 changes: 118 additions & 89 deletions cai/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,92 +7,121 @@
https://github.com/cscs181/CAI/blob/master/LICENSE
"""

import asyncio
from typing import Union, Optional

from cai.client import Client, OnlineStatus
from cai.exceptions import ClientNotAvailable

from . import _clients


def get_client(uin: Optional[int] = None) -> Client:
"""Get the specific client or existing client.

Args:
uin (Optional[int], optional): Specific account client to get. Defaults to None.

Raises:
ClientNotAvailable: Client not exists.
ClientNotAvailable: No client available.
ClientNotAvailable: Multiple clients found and not specify which one to get.

Returns:
Client: Current client to use.
"""
if not _clients:
raise ClientNotAvailable(uin, f"No client available!")
elif len(_clients) == 1 and not uin:
return list(_clients.values())[0]
else:
if not uin:
raise ClientNotAvailable(
None, f"Multiple clients found! Specify uin to choose."
)
if uin not in _clients:
raise ClientNotAvailable(None, f"Client {uin} not exists!")
return _clients[uin]


async def close(uin: Optional[int] = None) -> None:
"""Close an existing client and delete it from clients.

Args:
uin (Optional[int], optional): Account of the client want to close. Defaults to None.
"""
client = get_client(uin)
await client.close()
del _clients[client.uin]


async def close_all() -> None:
"""Close all existing clients and delete them."""
tasks = [close(uin) for uin in _clients.keys()]
await asyncio.gather(*tasks)


async def set_status(
status: Union[int, OnlineStatus],
battery_status: Optional[int] = None,
is_power_connected: bool = False,
uin: Optional[int] = None,
) -> None:
"""Change client status.

This function wraps the :meth:`~cai.client.client.Client.register`
method of the client.

Args:
status (OnlineStatus): Status want to change.
battery_status (Optional[int], optional): Battery capacity.
Defaults to None.
is_power_connected (bool, optional): Is power connected to phone.
Defaults to False.
uin (Optional[int], optional): Account of the client want to change.
Defaults to None.

Raises:
RuntimeError: Client already exists and is running.
RuntimeError: Password not provided when login a new account.
ApiResponseError: Invalid API request.
RegisterException: Register Failed.
"""
client = get_client(uin)
await client.set_status(
status,
battery_status,
is_power_connected,
)


__all__ = ["get_client", "close", "close_all", "set_status"]
import hashlib
from typing import Union, BinaryIO, Optional, Sequence

from cai import log
from cai.client import OnlineStatus
from cai.client import Client as client_t
from cai.settings.device import get_device
from cai.pb.msf.msg.svc import PbSendMsgResp
from cai.client.highway import HighWaySession
from cai.settings.protocol import get_protocol
from cai.client.message_service.encoders import build_msg, make_group_msg_pkg
from cai.client.message.models import (
Element,
ImageElement,
VoiceElement,
)

from .group import Group as _Group
from .login import Login as _Login
from .flow import Events as _Events
from .friend import Friend as _Friend
from .error import (
BotMutedException,
AtAllLimitException,
GroupMsgLimitException,
)


def make_client(uin: int, passwd: Union[str, bytes]) -> client_t:
if not (isinstance(passwd, bytes) and len(passwd) == 16):
# not a vailed md5 passwd
if isinstance(passwd, bytes):
passwd = hashlib.md5(passwd).digest()
else:
passwd = hashlib.md5(passwd.encode()).digest()
device = get_device(uin)
apk_info = get_protocol(uin)
return client_t(uin, passwd, device, apk_info)


class Client(_Login, _Friend, _Group, _Events):
def __init__(self, client: client_t):
self.client = client
self._highway_session = HighWaySession(client, logger=log.highway)

@property
def connected(self) -> bool:
return self.client.connected

@property
def status(self) -> Optional[OnlineStatus]:
return self.client.status

async def send_group_msg(self, gid: int, msg: Sequence[Element]):
# todo: split long msg
resp: PbSendMsgResp = PbSendMsgResp.FromString(
(
await self.client.send_unipkg_and_wait(
"MessageSvc.PbSendMsg",
make_group_msg_pkg(
self.client.next_seq(), gid, build_msg(msg)
).SerializeToString(),
)
).data
)

if resp.result == 120:
raise BotMutedException
elif resp.result == 121:
raise AtAllLimitException
elif resp.result == 299:
raise GroupMsgLimitException
else:
# todo: store msg
return resp

async def upload_image(self, group_id: int, file: BinaryIO) -> ImageElement:
return await self._highway_session.upload_image(file, group_id)

async def upload_voice(self, group_id: int, file: BinaryIO) -> VoiceElement:
return await self._highway_session.upload_voice(file, group_id)

async def close(self):
"""Stop Client"""
await self.client.close()

async def set_status(
self,
status: Union[int, OnlineStatus],
battery_status: Optional[int] = None,
is_power_connected: bool = False,
) -> None:
"""Change client status.

This function wraps the :meth:`~cai.client.client.Client.register`
method of the client.

Args:
status (OnlineStatus): Status want to change.
battery_status (Optional[int], optional): Battery capacity.
Defaults to None.
is_power_connected (bool, optional): Is power connected to phone.
Defaults to False.

Raises:
RuntimeError: Client already exists and is running.
RuntimeError: Password not provided when login a new account.
ApiResponseError: Invalid API request.
RegisterException: Register Failed.
"""
await self.client.set_status(
status,
battery_status,
is_power_connected,
)


__all__ = ["Client"]
18 changes: 18 additions & 0 deletions cai/api/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class BotException(Exception):
pass


class BotMutedException(BotException):
pass


class LimitException(Exception):
pass


class AtAllLimitException(LimitException):
pass


class GroupMsgLimitException(LimitException):
pass
88 changes: 40 additions & 48 deletions cai/api/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,47 @@
https://github.com/cscs181/CAI/blob/master/LICENSE
"""

from typing import Callable, Optional, Awaitable
from typing import TYPE_CHECKING, Callable, Awaitable

from cai.log import logger
from cai.client import HANDLERS, Event, Client, Command, IncomingPacket

from .client import get_client


def add_event_listener(
listener: Callable[[Client, Event], Awaitable[None]],
uin: Optional[int] = None,
):
"""Add event listener.

If uin is ``None``, listener will receive events from all clients.

Args:
listener (Callable[[Client, Event], Awaitable[None]]): Event listener.
uin (Optional[int], optional): Account of the client want to listen.
Defaults to None.
"""
if uin:
client = get_client(uin)
client.add_event_listener(listener)
else:
Client.LISTENERS.add(listener)


def register_packet_handler(
cmd: str,
packet_handler: Callable[[Client, IncomingPacket], Awaitable[Command]],
) -> None:
"""Register custom packet handler.

Note:
This function is a low-level api to mock default behavior.
Be aware of what you are doing!

Args:
cmd (str): Command name of the packet.
packet_handler (Callable[[Client, IncomingPacket], Awaitable[Command]]):
Asynchronous packet handler. A :obj:`~cai.client.command.Command`
object should be returned.
"""
if cmd in HANDLERS:
logger.warning(
f"You are overwriting an existing handler for command {cmd}!"
)
HANDLERS[cmd] = packet_handler


__all__ = ["add_event_listener", "register_packet_handler"]
from .base import BaseAPI

if TYPE_CHECKING:
from .client import Client as Client_T


class Events(BaseAPI):
def add_event_listener(
self, listener: Callable[["Client_T", Event], Awaitable[None]]
):
"""Add event listener.

Args:
listener (Callable[[Client, Event], Awaitable[None]]): Event listener.
"""
self.client.add_event_listener(lambda _, e: listener(self, e)) # type: ignore

def register_packet_handler(
self,
cmd: str,
packet_handler: Callable[[Client, IncomingPacket], Awaitable[Command]],
) -> None:
"""Register custom packet handler.

Note:
This function is a low-level api to mock default behavior.
Be aware of what you are doing!

Args:
cmd (str): Command name of the packet.
packet_handler (Callable[[Client, IncomingPacket], Awaitable[Command]]):
Asynchronous packet handler. A :obj:`~cai.client.command.Command`
object should be returned.
"""
if cmd in HANDLERS:
logger.warning(
f"You are overwriting an existing handler for command {cmd}!"
)
HANDLERS[cmd] = packet_handler
Loading