diff --git a/lagrange/client/events/group.py b/lagrange/client/events/group.py index 14b2584..c9d4156 100644 --- a/lagrange/client/events/group.py +++ b/lagrange/client/events/group.py @@ -81,7 +81,7 @@ class GroupMemberJoinRequest(GroupEvent): @dataclass class GroupMemberJoined(GroupEvent): - uin: int + # uin: int //it cant get uid: str join_type: int @@ -152,4 +152,35 @@ class GroupInvite(GroupEvent): @dataclass class GroupMemberJoinedByInvite(GroupEvent): invitor_uin: int - uin: int \ No newline at end of file + uin: int + + +@dataclass +class GroupSelfJoined(GroupEvent): + grp_id: int + op_uid: str + + +@dataclass +class GroupSelfRequireReject(GroupEvent): + grp_id: int + message: str + + +@dataclass +class GroupBotAdded(GroupEvent): + bot_uid: str + + +@dataclass +class BotGrayTip(GroupEvent): + content: str + + +@dataclass +class GroupBotJoined(GroupEvent): + opqq_uin: int + nick_name: str + robot_name: str + robot_schema: str + user_schema: str diff --git a/lagrange/client/message/decoder.py b/lagrange/client/message/decoder.py index a891f4a..0a47cc6 100644 --- a/lagrange/client/message/decoder.py +++ b/lagrange/client/message/decoder.py @@ -12,7 +12,7 @@ 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.message.rich_text.elems import GroupFileExtra, FileExtra, PBKeyboard from lagrange.pb.highway.comm import MsgInfo if TYPE_CHECKING: @@ -163,6 +163,12 @@ async def parse_msg_new( f8=common.pb_elem[8], ) ) + if common.service_type == 45: + md_c: bytes = common.pb_elem[1] + msg_chain.append(elems.Markdown(content=md_c.decode())) + if common.service_type == 46: + kb = PBKeyboard.decode(proto_encode(common.pb_elem)).keyboard + msg_chain.append(elems.Keyboard(content=kb.content, bot_appid=kb.bot_appid)) if common.bus_type in [10, 20]: # 10: friend, 20: group extra = MsgInfo.decode(proto_encode(raw.common_elem.pb_elem)) index = extra.body[0].index diff --git a/lagrange/client/message/elems.py b/lagrange/client/message/elems.py index ea8168f..f1ee905 100644 --- a/lagrange/client/message/elems.py +++ b/lagrange/client/message/elems.py @@ -20,6 +20,7 @@ def type(self) -> str: @dataclass class CompatibleText(BaseElem): """仅用于兼容性变更,不应作为判断条件""" + @property def text(self) -> str: return self.display @@ -28,6 +29,7 @@ def text(self) -> str: def text(self, text: str): """ignore""" + @dataclass class MediaInfo: name: str @@ -215,9 +217,15 @@ def display(self) -> str: return f"[file:{self.file_name}]" @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": + 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( file_size=file_size, file_name=file_name, @@ -244,8 +252,66 @@ class GreyTips(BaseElem): 建议搭配Text使用 冷却3分钟左右? """ + text: str @property def display(self) -> str: return f"" + + +@dataclass +class Markdown(BaseElem): + content: str + + @property + def display(self) -> str: + return f"[markdown:{self.md_c.decode()}]" + + +class Permission: + type: int + specify_role_ids: Optional[list[str]] + specify_user_ids: Optional[list[str]] + + +class RenderData: + label: Optional[str] + visited_label: Optional[str] + style: int + + +class Action: + type: Optional[int] + permission: Optional[Permission] + data: str + reply: bool + enter: bool + anchor: Optional[int] + unsupport_tips: Optional[str] + click_limit: Optional[int] # deprecated + at_bot_show_channel_list: bool # deprecated + + +class Button: + id: Optional[str] + render_data: Optional[RenderData] + action: Optional[Action] + + +class InlineKeyboardRow: + buttons: Optional[list[Button]] + + +class InlineKeyboard: + rows: list[InlineKeyboardRow] + + +@dataclass +class Keyboard(BaseElem): + content: Optional[list[InlineKeyboard]] + bot_appid: int + + @property + def display(self) -> str: + return f"[keyboard:{self.bot_appid}]" diff --git a/lagrange/client/message/types.py b/lagrange/client/message/types.py index 21c817d..4624659 100644 --- a/lagrange/client/message/types.py +++ b/lagrange/client/message/types.py @@ -17,7 +17,9 @@ GreyTips, Video, Service, - File + File, + Markdown, + Keyboard, ) # T = TypeVar( @@ -49,5 +51,7 @@ "GreyTips", "Video", "Service", - "File" + "File", + "Markdown", + "Keyboard", ] diff --git a/lagrange/client/server_push/msg.py b/lagrange/client/server_push/msg.py index 3fa375b..7cf45c1 100644 --- a/lagrange/client/server_push/msg.py +++ b/lagrange/client/server_push/msg.py @@ -14,18 +14,21 @@ MemberJoinRequest, MemberRecallMsg, GroupSub20Head, + PBBotGrayTip, PBGroupAlbumUpdate, + PBGroupBotAdded, PBGroupInvite, + PBSelfJoinInGroup, ) -from lagrange.pb.status.friend import ( - PBFriendRecall, - PBFriendRequest -) +from lagrange.pb.status.friend import PBFriendRecall, PBFriendRequest from lagrange.utils.binary.protobuf import proto_decode, ProtoStruct, proto_encode from lagrange.utils.binary.reader import Reader from lagrange.utils.operator import unpack_dict, timestamp from ..events.group import ( + BotGrayTip, + GroupBotAdded, + GroupBotJoined, GroupInvite, GroupMemberGotSpecialTitle, GroupMemberJoined, @@ -36,14 +39,13 @@ GroupRecall, GroupNudge, GroupReaction, + GroupSelfJoined, + GroupSelfRequireReject, GroupSign, GroupAlbumUpdate, - GroupMemberJoinedByInvite -) -from ..events.friend import ( - FriendRecall, - FriendRequest + GroupMemberJoinedByInvite, ) +from ..events.friend import FriendRecall, FriendRequest from ..wtlogin.sso import SSOPacket from .log import logger @@ -74,10 +76,9 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): elif typ == 33: # member joined pb = MemberChanged.decode(pkg.message.buf2) return GroupMemberJoined( - grp_id=pkg.response_head.from_uin, - uin=pb.uin, - uid=pb.uid, - join_type=pb.join_type, + grp_id=pb.uin, + uid=pb.uid, # right u cant get uin + join_type=pb.join_type_new, ) elif typ == 34: # member exit pb = MemberChanged.decode(pkg.message.buf2) @@ -91,16 +92,25 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): elif typ == 84: pb = MemberJoinRequest.decode(pkg.message.buf2) return GroupMemberJoinRequest(grp_id=pb.grp_id, uid=pb.uid, answer=pb.request_field) + elif typ == 85: + pb = PBSelfJoinInGroup.decode(pkg.message.buf2) + return GroupSelfJoined(grp_id=pb.gid, op_uid=pb.operator_uid) + elif typ == 86: + reader = Reader(pkg.message.buf2) + grp_id = reader.read_u32() # it should have more infomation,but i cant guess it + return GroupSelfRequireReject(grp_id, "") elif typ == 87: pb = PBGroupInvite.decode(pkg.message.buf2) return GroupInvite(grp_id=pb.gid, invitor_uid=pb.invitor_uid) + elif typ == 167: + print(pkg.message.encode().hex()) elif typ == 525: pb = MemberInviteRequest.decode(pkg.message.buf2) if pb.cmd == 87: inn = pb.info.inner return GroupMemberJoinRequest(grp_id=inn.grp_id, uid=inn.uid, invitor_uid=inn.invitor_uid) elif typ == 0x210: # friend event, 528 / group file upload notice event - if sub_typ == 35: # friend request + if sub_typ == 35: # friend request pb = PBFriendRequest.decode(pkg.message.buf2) return FriendRequest( pkg.response_head.from_uin, @@ -108,9 +118,9 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): pkg.response_head.to_uin, pb.info.to_uid, pb.info.verify, - pb.info.source + pb.info.source, ) - elif sub_typ == 138: # friend recall + elif sub_typ == 138: # friend recall pb = PBFriendRecall.decode(pkg.message.buf2) return FriendRecall( pkg.response_head.from_uin, @@ -119,10 +129,16 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): pb.info.to_uid, pb.info.seq, pb.info.random, - pb.info.time + pb.info.time, ) + if sub_typ == 368: + pass + # print(pkg.message.encode().hex()) logger.debug(f"unhandled friend event / group file upload notice event: {pkg}") # TODO: paste elif typ == 0x2DC: # grp event, 732 + if sub_typ == 1: + # print(pkg.encode().hex()) + pass if sub_typ == 20: # nudge and group_sign(群打卡) if pkg.message: grp_id, pb = unpack(pkg.message.buf2, GroupSub20Head) @@ -136,14 +152,19 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): attrs[k.decode()] = int(v.decode()) else: attrs[k.decode()] = v.decode() + if pb.body.f2 == 19217: + return GroupBotJoined( + grp_id, + attrs["mqq_uin"], + attrs["nick_name"], + attrs["robot_name"], + attrs["robot_schema"], + attrs["user_schema"], + ) if pb.body.type == 1: if "invitor" in attrs: # reserve: attrs["msg_nums"] - return GroupMemberJoinedByInvite( - grp_id, - attrs["invitor"], - attrs["invitee"] - ) + return GroupMemberJoinedByInvite(grp_id, attrs["invitor"], attrs["invitee"]) elif "user" in attrs and "uin" in attrs: # todo: 群代办 pass @@ -169,13 +190,17 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): pb.body.attrs_xml, ) else: - raise ValueError(f"unknown type({pb.body.type}) on GroupSub20: {attrs}") + raise ValueError(f"unknown type({pb.body.type}) f2({pb.body.f2}) on GroupSub20: {attrs}") else: # print(pkg.encode().hex(), 2) return elif sub_typ == 16: # rename & special_title & reaction + # print(sso.data.hex()) if pkg.message: grp_id, pb = unpack(pkg.message.buf2, GroupSub16Head) + if pb.flag is None: + _, pb = unpack(pkg.message.buf2, PBBotGrayTip) # 傻逼tx,我13号位呢 + return BotGrayTip(grp_id, pb.body.message) if pb.flag == 6: # special_title body = MemberGotTitleBody.decode(pb.body) for el in re.findall(r"<(\{.*?})>", body.string): @@ -222,6 +247,9 @@ async def msg_push_handler(client: "Client", sso: SSOPacket): timestamp=pb.timestamp, image_id=q["i"], ) + elif pb.flag == 38: + _, pb = unpack(pkg.message.buf2, PBGroupBotAdded) + return GroupBotAdded(pb.body.grp_id, pb.body.bot_uid_1 or pb.body.bot_uid_2) else: raise ValueError(f"Unknown subtype_12 flag: {pb.flag}: {pb.body.hex() if pb.body else pb}") elif sub_typ == 17: # recall diff --git a/lagrange/pb/message/rich_text/elems.py b/lagrange/pb/message/rich_text/elems.py index 86391ae..0c74267 100644 --- a/lagrange/pb/message/rich_text/elems.py +++ b/lagrange/pb/message/rich_text/elems.py @@ -206,6 +206,7 @@ class GreyTipsExtraInfo(ProtoStruct): class GreyTipsExtra(ProtoStruct): body: GreyTipsExtraInfo = proto_field(1) + class PBGreyTips(ProtoStruct): grey: Optional[GreyTipsExtra] = proto_field(101, default=None) @@ -240,3 +241,54 @@ class GeneralFlags(ProtoStruct): PendantId: Optional[int] = proto_field(17, default=None) RpIndex: Optional[bytes] = proto_field(18, default=None) PbReserve: Optional[PBGreyTips] = proto_field(19, default=None) + + +# class Markdown(ProtoStruct): +# content: str = proto_field(1) + + +class Permission(ProtoStruct): + type: int = proto_field(1, default=0) + specify_role_ids: Optional[list[str]] = proto_field(2, default=None) + specify_user_ids: Optional[list[str]] = proto_field(3, default=None) + + +class RenderData(ProtoStruct): + label: Optional[str] = proto_field(1, default=None) + visited_label: Optional[str] = proto_field(2, default=None) + style: int = proto_field(3, default=0) + + +class Action(ProtoStruct): + type: Optional[int] = proto_field(1, default=None) + permission: Optional[Permission] = proto_field(2, default=None) + data: str = proto_field(5) + reply: bool = proto_field(7, default=False) + enter: bool = proto_field(8, default=False) + anchor: Optional[int] = proto_field(9, default=None) + unsupport_tips: Optional[str] = proto_field(4, default=None) + click_limit: Optional[int] = proto_field(3) # deprecated + at_bot_show_channel_list: bool = proto_field(6, default=False) # deprecated + + +class Button(ProtoStruct): + id: Optional[str] = proto_field(1, default=None) + render_data: Optional[RenderData] = proto_field(2, default=None) + action: Optional[Action] = proto_field(3, default=None) + + +class InlineKeyboardRow(ProtoStruct): + buttons: Optional[list[Button]] = proto_field(1, default=None) + + +class InlineKeyboard(ProtoStruct): + rows: list[InlineKeyboardRow] = proto_field(1) + + +class Keyboard(ProtoStruct): + content: Optional[list[InlineKeyboard]] = proto_field(1, default=None) + bot_appid: int = proto_field(2) + + +class PBKeyboard(ProtoStruct): + keyboard: Keyboard = proto_field(1) diff --git a/lagrange/pb/status/group.py b/lagrange/pb/status/group.py index 0f17ad3..674f7ad 100644 --- a/lagrange/pb/status/group.py +++ b/lagrange/pb/status/group.py @@ -12,7 +12,10 @@ class MemberChanged(ProtoStruct): uid: str = proto_field(3) exit_type: Optional[int] = proto_field(4, default=None) # 3kick_me, 131kick, 130exit operator_uid: str = proto_field(5, default="") - join_type: Optional[int] = proto_field(6, default=None) # 6scanqr, + join_type: Optional[int] = proto_field(6, default=None) # 6other, 0slef_invite + join_type_new: Optional[int] = proto_field( + 4, default=None + ) # 130 by_other(click url,scan qr,input grpid), 131 by_invite class MemberJoinRequest(ProtoStruct): @@ -106,21 +109,23 @@ class GroupSub16Head(ProtoStruct): timestamp: int = proto_field(2, default=0) uin: Optional[int] = proto_field(4, default=None) body: Optional[bytes] = proto_field(5, default=None) - flag: int = proto_field(13) # 12: renamed, 6: set special_title, 13: unknown, 35: set reaction + flag: Optional[int] = proto_field( + 13, default=None + ) # 12: renamed, 6: set special_title, 13: unknown, 35: set reaction, 38: bot add operator_uid: str = proto_field(21, default="") f44: Optional[PBGroupReaction] = proto_field(44, default=None) # set reaction only class GroupSub20Head(ProtoStruct): - f1: int = proto_field(1) # 20 + f1: int = proto_field(1, default=None) # 20 grp_id: int = proto_field(4) f13: int = proto_field(13) # 19 body: "GroupSub20Body" = proto_field(26) class GroupSub20Body(ProtoStruct): - type: int = proto_field(1) # 12: nudge, 14: group_sign - # f2: int = proto_field(2) # 1061 + type: Optional[int] = proto_field(1, default=None) # 12: nudge, 14: group_sign + f2: int = proto_field(2) # 1061 , bot added group:19217 # f3: int = proto_field(3) # 7 # f6: int = proto_field(6) # 1132 attrs: list[dict] = proto_field(7, default_factory=list) @@ -160,3 +165,37 @@ class PBGroupInvite(ProtoStruct): f4: int = proto_field(4) # 0 invitor_uid: str = proto_field(5) invite_info: bytes = proto_field(6) + + +class PBSelfJoinInGroup(ProtoStruct): + gid: int = proto_field(1) + f2: int = proto_field(2) + f4: int = proto_field(4) # 0 + f6: int = proto_field(6) # 48 + f7: str = proto_field(7) + operator_uid: str = proto_field(3) + + +class PBGroupBotAddedBody(ProtoStruct): + grp_id: int = proto_field(1) + bot_uid_1: Optional[str] = proto_field(2, default=None) + bot_uid_2: Optional[str] = proto_field(3, default=None) # f**k tx + flag: int = proto_field(4) + + +class PBGroupBotAdded(ProtoStruct): + # f1: 39 + grp_id: int = proto_field(4) + # f13: 38 + body: PBGroupBotAddedBody = proto_field(47) + + +class PBGroupGrayTipBody(ProtoStruct): + message: str = proto_field(2) + flag: int = proto_field(3) + + +class PBBotGrayTip(ProtoStruct): + # f1: 1 + grp_id: int = proto_field(4) + body: PBGroupGrayTipBody = proto_field(5)