diff --git a/lagrange/client/message/decoder.py b/lagrange/client/message/decoder.py index bd11310..e91a02e 100644 --- a/lagrange/client/message/decoder.py +++ b/lagrange/client/message/decoder.py @@ -8,7 +8,9 @@ from . import elems from .types import Element +from lagrange.utils.binary.reader import Reader from lagrange.utils.binary.protobuf import proto_encode +from lagrange.pb.message.rich_text.elems import GroupFileExtra, FileExtra from lagrange.pb.highway.comm import MsgInfo if TYPE_CHECKING: @@ -36,6 +38,18 @@ def parse_friend_info(pkg: MsgPushBody) -> Tuple[int, str, int, str]: async def parse_msg_new(client: "Client", pkg: MsgPushBody) -> Sequence[Element]: + if not pkg.message or not pkg.message.body: + if pkg.content_head.sub_type == 4: + data = FileExtra.decode(pkg.message.buf2) + return [ + elems.File.pri_paste_build( + file_size=data.file.file_size, + file_name=data.file.file_name, + file_md5=data.file.file_md5, + file_uuid=data.file.file_uuid, + file_hash=data.file.file_hash, + ) + ] rich: RichText = pkg.message.body if rich.ptt: ptt = rich.ptt @@ -166,6 +180,21 @@ async def parse_msg_new(client: "Client", pkg: MsgPushBody) -> Sequence[Element] qmsg=None, ) ) + elif raw.trans_elem: + elem_type, trans = raw.trans_elem.elem_type, raw.trans_elem.elem_value + if elem_type == 24: + reader = Reader(trans) + reader.read_bytes(1) + data = reader.read_bytes_with_length("u16", False) + file_extra = GroupFileExtra.decode(data) + msg_chain.append( + elems.File.grp_paste_build( + file_size=file_extra.inner.info.file_size, + file_name=file_extra.inner.info.file_name, + file_md5=file_extra.inner.info.file_md5, + file_id=file_extra.inner.info.file_id + ) + ) elif raw.rich_msg: service = raw.rich_msg if service.template: diff --git a/lagrange/client/message/elems.py b/lagrange/client/message/elems.py index 128f3cd..90f30ac 100644 --- a/lagrange/client/message/elems.py +++ b/lagrange/client/message/elems.py @@ -144,3 +144,37 @@ class MarketFace(Text): def url(self) -> str: pic_id = self.face_id.hex() return f"https://i.gtimg.cn/club/item/parcel/item/{pic_id[:2]}/{pic_id}/{self.width}x{self.height}.png" + + +@dataclass +class File(Text): + file_size: int + file_name: str + file_md5: bytes + file_url: Optional[str] + file_id: Optional[str] # only in group + file_uuid: Optional[str] # only in private + file_hash: Optional[str] + + @classmethod + def _paste_build(cls, file_size: int, file_name: str, + file_md5: bytes, file_id: Optional[str] = None, + file_uuid: Optional[str] = None, file_hash: Optional[str] = None) -> "File": + return cls( + text=f"[file:{file_name}]", + file_size=file_size, + file_name=file_name, + file_md5=file_md5, + file_url=None, + file_id=file_id, + file_uuid=file_uuid, + file_hash=file_hash, + ) + + @classmethod + def grp_paste_build(cls, file_size: int, file_name: str, file_md5: bytes, file_id: str) -> "File": + return cls._paste_build(file_size, file_name, file_md5, file_id=file_id) + + @classmethod + def pri_paste_build(cls, file_size: int, file_name: str, file_md5: bytes, file_uuid: str, file_hash: str) -> "File": + return cls._paste_build(file_size, file_name, file_md5, file_uuid=file_uuid, file_hash=file_hash) diff --git a/lagrange/client/server_push/msg.py b/lagrange/client/server_push/msg.py index 1b84f84..e44b6ce 100644 --- a/lagrange/client/server_push/msg.py +++ b/lagrange/client/server_push/msg.py @@ -57,7 +57,7 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): logger.debug("msg_push received, type: {}.{}".format(typ, sub_typ)) if typ == 82: # grp msg return await parse_grp_msg(client, pkg) - elif typ == 166: # frd msg + elif typ in [166, 529]: # frd msg return await parse_friend_msg(client, pkg) elif typ == 33: # member joined pb = MemberChanged.decode(pkg.message.buf2) @@ -88,8 +88,8 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): return GroupMemberJoinRequest( grp_id=inn.grp_id, uid=inn.uid, invitor_uid=inn.invitor_uid ) - elif typ == 0x210: # frd event - logger.debug("unhandled friend event: %s" % pkg) + elif typ == 0x210: # friend event / group file upload notice event + logger.debug("unhandled friend event / group file upload notice event: %s" % pkg) # TODO: paste elif typ == 0x2DC: # grp event, 732 if sub_typ == 20: # nudge and group_sign(群打卡) if pkg.message: diff --git a/lagrange/pb/message/rich_text/elems.py b/lagrange/pb/message/rich_text/elems.py index 4545b24..10be308 100644 --- a/lagrange/pb/message/rich_text/elems.py +++ b/lagrange/pb/message/rich_text/elems.py @@ -148,3 +148,51 @@ class VideoFile(ProtoStruct): thumb_width: int = proto_field(16) thumb_height: int = proto_field(17) # reserve on field 24? + + +class NotOnlineFile(ProtoStruct): + file_type: Optional[int] = proto_field(1) + # sig: Optional[bytes] = proto_field(2) + file_uuid: Optional[str] = proto_field(3) + file_md5: Optional[bytes] = proto_field(4) + file_name: Optional[str] = proto_field(5) + file_size: Optional[int] = proto_field(6) + # note: Optional[bytes] = proto_field(7) + # reserved: Optional[int] = proto_field(8) + subcmd: Optional[int] = proto_field(9) + # micro_cloud: Optional[int] = proto_field(10) + # bytes_file_urls: Optional[list[bytes]] = proto_field(11) + # download_flag: Optional[int] = proto_field(12) + danger_evel: Optional[int] = proto_field(50) + # life_time: Optional[int] = proto_field(51) + # upload_time: Optional[int] = proto_field(52) + # abs_file_type: Optional[int] = proto_field(53) + # client_type: Optional[int] = proto_field(54) + expire_time: Optional[int] = proto_field(55) + pb_reserve: bytes = proto_field(56) + file_hash: Optional[str] = proto_field(57) + + +class FileExtra(ProtoStruct): + file: NotOnlineFile = proto_field(1) + + +class GroupFileExtraInfo(ProtoStruct): + bus_id: int = proto_field(1) + file_id: str = proto_field(2) + file_size: int = proto_field(3) + file_name: str = proto_field(4) + f5: int = proto_field(5) + f7: str = proto_field(7) + file_md5: bytes = proto_field(8) + + +class GroupFileExtraInner(ProtoStruct): + info: GroupFileExtraInfo = proto_field(2) + + +class GroupFileExtra(ProtoStruct): + f1: int = proto_field(1) + file_name: str = proto_field(2) + display: str = proto_field(3) + inner: GroupFileExtraInner = proto_field(7)