Пример #1
0
    def parse(
        cls,
        data: Union[bytes, bytearray],
        key: bytes,
        d2key: bytes,
        session_key: bytes,
    ) -> "IncomingPacket":
        if not isinstance(data, Packet):
            data = Packet(data)

        packet_type, encrypt_type, flag3, uin, payload = (
            data.start().uint32().uint8().uint8().string(4,
                                                         4).remain().execute())

        if packet_type not in [0xA, 0xB]:
            raise ValueError(
                f"Invalid packet type. Expected 0xA / 0xB, got {packet_type}.")
        if flag3 != 0:
            raise ValueError(f"Invalid flag3. Expected 0, got {flag3}.")

        try:
            uin = int(uin)
        except ValueError:
            uin = 0

        if encrypt_type == 0:
            pass
        elif encrypt_type == 1:
            payload = Packet(qqtea_decrypt(bytes(payload), d2key))
        elif encrypt_type == 2:
            payload = Packet(qqtea_decrypt(bytes(payload), bytes(16)))
        else:
            raise ValueError(
                f"Invalid encrypt type. Expected 0 / 1 / 2, got {encrypt_type}."
            )

        if not payload:
            raise ValueError(f"Data cannot be none.")

        # offset = 0
        # sso_head_length = payload.read_int32(offset) - 4
        # offset += 4
        offset = 4
        sso_frame = payload[offset:]

        return cls.parse_sso_frame(sso_frame,
                                   encrypt_type,
                                   key,
                                   session_key,
                                   uin=uin)
Пример #2
0
    def t512(cls, data: bytes) -> Dict[str, Any]:
        data_ = Packet(data)
        offset = 0
        length = data_.read_uint16()
        offset += 2

        ps_key_map: Dict[str, bytes] = {}
        pt4_token_map: Dict[str, bytes] = {}
        for _ in range(length):
            domain_length = data_.read_uint16(offset)
            offset += 2
            domain = data_.read_bytes(domain_length, offset).decode()
            offset += domain_length

            key_length = data_.read_uint16(offset)
            offset += 2
            ps_key = data_.read_bytes(key_length, offset)
            offset += key_length

            token_length = data_.read_uint16(offset)
            offset += 2
            ps4_token = data_.read_bytes(token_length, offset)
            offset += token_length

            ps_key_map[domain] = ps_key
            pt4_token_map[domain] = ps4_token

        return {"ps_key_map": ps_key_map, "pt4_token_map": pt4_token_map}
Пример #3
0
 def build(
     cls,
     uin: int,
     seq: int,
     command_name: str,
     session_id: bytes,
     body_type: int,
     body: Union[bytes, Packet],
     key: bytes,
     extra_data: bytes = b"",
 ) -> "UniPacket":
     data = Packet().write_with_length(
         struct.pack(">I",
                     len(command_name) + 4),
         command_name.encode(),
         struct.pack(">I",
                     len(session_id) + 4),
         session_id,
         struct.pack(">I",
                     len(extra_data) + 4),
         extra_data,
         offset=4,
     )
     data.write_with_length(body, offset=4)
     return cls().write_with_length(
         struct.pack(">IBIB", 0xB, body_type, seq, 0),
         struct.pack(">I",
                     len(str(uin)) + 4),
         str(uin).encode(),
         qqtea_encrypt(bytes(data), key),
         offset=4,
     )
Пример #4
0
    def parse_sso_frame(
        cls,
        sso_frame: Union[bytes, bytearray],
        encrypt_type: int,
        key: bytes,
        session_key: bytes,
        **kwargs,
    ) -> "IncomingPacket":
        if not isinstance(sso_frame, Packet):
            sso_frame = Packet(sso_frame)

        seq, ret_code, extra, command_name, session_id, data = (
            sso_frame.start().uint32().int32().bytes_with_length(4, 4).string(
                4, 4).bytes_with_length(4, 4).remain().execute())

        if not data:
            return cls(
                seq=seq,
                ret_code=ret_code,
                extra=extra,
                command_name=command_name,
                session_id=session_id,
                data=bytes(),
                **kwargs,
            )

        compress_type, compressed_data = data.start().int32().remain().execute(
        )
        decompressed_data: bytes
        if compress_type == 0:
            # data_length = sso_frame.read_int32(offset)
            # if data_length == len(sso_frame) - offset or data_length == len(
            #     sso_frame
            # ) - offset - 4:
            #     decompressed_data = sso_frame[offset + 4:]
            # else:
            #     decompressed_data = sso_frame[offset + 4:]
            decompressed_data = compressed_data[4:]
        elif compress_type == 1:
            decompressed_data = zlib.decompress(compressed_data[4:])
        elif compress_type == 8:
            decompressed_data = compressed_data[4:]
        else:
            raise ValueError(f"Unknown compression type, got {compress_type}.")

        if encrypt_type == 2:
            decompressed_data = cls.parse_oicq_body(decompressed_data, key,
                                                    session_key)

        return cls(
            seq=seq,
            ret_code=ret_code,
            extra=extra,
            command_name=command_name,
            session_id=session_id,
            data=decompressed_data,
            **kwargs,
        )
Пример #5
0
    def decode_response(cls, uin: int, seq: int, ret_code: int,
                        command_name: str, data: bytes) -> "OICQResponse":
        """Decode login response and wrap main info of the response.

        Note:
            Source: oicq.wlogin_sdk.request.WtloginHelper.GetStWithPasswd

        Args:
            uin (int): User QQ
            seq (int): Sequence number of the response packet.
            ret_code (int): Return code of the response.
            command_name (str): Command name of the response.
            data (bytes): Payload data of the response.

        Returns:
            LoginSuccess: Login success.
            NeedCaptcha: Captcha image needed.
            AccountFrozen: Account is frozen.
            DeviceLocked: Device lock detected.
            TooManySMSRequest: Too many SMS messages were sent.
            DeviceLockLogin: More login packet needed.
            UnknownLoginStatus: Unknown login status.
            OICQResponse: Invalid login response.
        """
        if ret_code != 0 or not data:
            return OICQResponse(uin, seq, ret_code, command_name)

        data_ = Packet(data)

        sub_command, status, _tlv_bytes = (
            data_.start().uint16().uint8().offset(2).remain().execute())

        _tlv_map = TlvDecoder.decode(_tlv_bytes)

        if status == 0:
            return LoginSuccess(uin, seq, ret_code, command_name, sub_command,
                                status, _tlv_map)
        elif status == 2:
            return NeedCaptcha(uin, seq, ret_code, command_name, sub_command,
                               status, _tlv_map)
        elif status == 40:
            return AccountFrozen(uin, seq, ret_code, command_name, sub_command,
                                 status, _tlv_map)
        elif status == 160 or status == 239:
            return DeviceLocked(uin, seq, ret_code, command_name, sub_command,
                                status, _tlv_map)
        elif status == 162:
            return TooManySMSRequest(uin, seq, ret_code, command_name,
                                     sub_command, status, _tlv_map)
        elif status == 204:
            return DeviceLockLogin(uin, seq, ret_code, command_name,
                                   sub_command, status, _tlv_map)
        else:
            return UnknownLoginStatus(uin, seq, ret_code, command_name,
                                      sub_command, status, _tlv_map)
Пример #6
0
    def t130(cls, data: bytes) -> Dict[str, Any]:
        """Decode tlv 130 data.

        Data:
            * time_diff (int): time difference between server and local.
            * ip_address (bytes(4)): may be server ip

        Note:
            Source: oicq.wlogin_sdk.tlv_type.tlv_t130
        """
        data_ = Packet(data)
        return {
            "time_diff": data_.read_int32(offset=2) - int(time.time()),
            "ip_address": data_.read_bytes(4, offset=6),
        }
Пример #7
0
    def t200(cls, data: bytes) -> Dict[str, Any]:
        """Decode tlv 200 data.

        Data:
            * pf (bytes)
            * pf_key (bytes)

        Note:
            Source: oicq.wlogin_sdk.tlv_type.tlv_t200
        """
        data_ = Packet(data)
        offset = 0
        pf_length = data_.read_uint16(offset)
        offset += 2 + pf_length
        key_length = data_.read_uint16(offset)
        return {
            "pf": data_.read_bytes(pf_length, 2),
            "pf_key": data_.read_bytes(key_length, offset + 2),
        }
Пример #8
0
    def t199(cls, data: bytes) -> Dict[str, Any]:
        """Decode tlv 199 data.

        Data:
            * open_id (bytes)
            * pay_token (bytes)

        Note:
            Source: oicq.wlogin_sdk.tlv_type.tlv_t199
        """
        data_ = Packet(data)
        offset = 0
        id_length = data_.read_uint16(offset)
        offset += 2 + id_length
        token_length = data_.read_uint16(offset)
        return {
            "open_id": data_.read_bytes(id_length, 2),
            "pay_token": data_.read_bytes(token_length, offset + 2),
        }
Пример #9
0
    def t125(cls, data: bytes) -> Dict[str, Any]:
        """Decode tlv 125 data.

        Data:
            * open_id (bytes)
            * open_key (bytes)

        Note:
            Source: oicq.wlogin_sdk.tlv_type.tlv_t125
        """
        data_ = Packet(data)
        offset = 0
        id_length = data_.read_uint16(offset)
        offset += 2 + id_length
        key_length = data_.read_uint16(offset)
        return {
            "open_id": data_.read_bytes(id_length, 2),
            "open_key": data_.read_bytes(key_length, offset + 2),
        }
Пример #10
0
    def parse_oicq_body(cls, data: Union[bytes, bytearray], key: bytes,
                        session_key: bytes) -> bytes:
        """Parse incoming OICQ packet.

        Note:
            Source: oicq.wlogin_sdk.request.oicq_request.b

        Args:
            data (Union[bytes, Packet]): OICQ data.
            key (bytes): Random key.
            session_key (bytes): Siginfo wt_session_ticket_key.

        Raises:
            ValueError: Flag error or unknown encrypt type.

        Returns:
            bytes: Decode data.
        """
        if not isinstance(data, Packet):
            data = Packet(data)

        flag, encrypt_type, body = (data.start().uint8().offset(
            12).uint16().offset(1).remain().execute())

        if flag != 2:
            raise ValueError(
                f"Invalid OICQ response flag. Expected 2, got {flag}.")

        body = body[:-1]
        if encrypt_type == 0:
            try:
                return qqtea_decrypt(bytes(body), ECDH.share_key)
            except Exception:
                return qqtea_decrypt(bytes(body), key)
        elif encrypt_type == 3:
            return qqtea_decrypt(bytes(body), session_key)
        elif encrypt_type == 4:
            # seems not used
            # data = qqtea_decrypt(bytes(body), ECDH.share_key)
            # ...
            raise NotImplementedError
        else:
            raise ValueError(f"Unknown encrypt type: {encrypt_type}")
Пример #11
0
 def __init__(
     self,
     uin: int,
     seq: int,
     ret_code: int,
     command_name: str,
     sub_command: int,
     status: int,
     _tlv_map: Dict[int, Any],
 ):
     super().__init__(uin, seq, ret_code, command_name, sub_command, status,
                      _tlv_map)
     self.sms_phone = None
     self.verify_url = _tlv_map.get(0x204, b"").decode() or None
     self.message = _tlv_map.get(0x17E, b"").decode() or None
     self.rand_seed = _tlv_map.get(0x403)
     self.t104 = _tlv_map.get(0x104)
     self.t174 = _tlv_map.get(0x174)
     if self.t174:
         t178 = Packet(_tlv_map[0x178])
         self.sms_phone = t178.start().string(4).execute()[0]
Пример #12
0
 def __init__(
     self,
     uin: int,
     seq: int,
     ret_code: int,
     command_name: str,
     sub_command: int,
     status: int,
     _tlv_map: Dict[int, Any],
 ):
     super().__init__(uin, seq, ret_code, command_name, sub_command, status,
                      _tlv_map)
     self.t104 = _tlv_map[0x104]
     self.captcha_image = bytes()
     self.captcha_sign = bytes()
     self.verify_url = _tlv_map.get(0x192, b"").decode()
     if 0x165 in _tlv_map:
         data = Packet(_tlv_map[0x165])
         sign, image = (data.start().bytes_with_length(
             2, 4).remain().execute())
         self.captcha_sign = sign[2:]
         self.captcha_image = bytes(image)
Пример #13
0
    def decode(
        cls,
        data: Union[bytes, bytearray],
        offset: int = 0,
        tag_size: int = 2,
    ) -> Dict[int, Any]:
        if not isinstance(data, Packet):
            data = Packet(data)

        result: Dict[int, bytes] = {}
        while offset + tag_size <= len(data):
            tag: int
            if tag_size == 1:
                tag = data.read_int8(offset)
                offset += 1
            elif tag_size == 2:
                tag = data.read_int16(offset)
                offset += 2
            elif tag_size == 4:
                tag = data.read_int32(offset)
                offset += 4
            else:
                raise ValueError(
                    f"Invalid tag size. Expected 1 / 2 / 4, got {tag_size}.")

            if tag == 255:
                return result

            length = data.read_uint16(offset)
            offset += 2
            value = data.read_bytes(length, offset)
            offset += length
            futher_decode = getattr(cls, f"t{tag:x}", None)
            if futher_decode:
                value = futher_decode(value)
            result[tag] = value

        return result
Пример #14
0
    def t11a(cls, data: bytes) -> Dict[str, Any]:
        """Decode tlv 11a data.

        Data:
            * face (bytes(2))
            * age (int)
            * gender (int)
            * nick (str)

        Note:
            Source: oicq.wlogin_sdk.tlv_type.tlv_t11a
        """
        data_ = Packet(data)
        return {
            "face":
            data_.read_bytes(2),
            "age":
            data_.read_uint8(offset=2),
            "gender":
            data_.read_uint8(offset=3),
            "nick":
            data_.read_bytes(data_.read_uint8(offset=4), offset=5).decode(),
        }
Пример #15
0
 def _pack_tlv(cls, type: int, *data: Union[bytes,
                                            bytearray]) -> "Packet[()]":
     return Packet(struct.pack(">HH", type, sum(map(len,
                                                    data)))).write(*data)
Пример #16
0
    async def _handle_login_response(self,
                                     response: Command,
                                     try_times: int = 1) -> LoginSuccess:
        if not isinstance(response, OICQResponse):
            raise RuntimeError("Invalid login response type!")

        if not isinstance(response, UnknownLoginStatus):
            raise ApiResponseError(
                response.uin,
                response.seq,
                response.ret_code,
                response.command_name,
            )

        if isinstance(response, LoginSuccess):
            logger.info(f"{self.nick}({self.uin}) 登录成功!")
            await self._init()
            return response
        elif isinstance(response, NeedCaptcha):
            if response.verify_url:
                logger.info(f"登录失败!请前往 {response.verify_url} 获取 ticket")
                raise LoginSliderNeeded(response.uin, response.verify_url)
            elif response.captcha_image:
                logger.info(f"登录失败!需要根据图片输入验证码")
                raise LoginCaptchaNeeded(response.uin, response.captcha_image,
                                         response.captcha_sign)
            else:
                raise LoginException(
                    response.uin,
                    response.status,
                    "Cannot get verify_url or captcha_image from the response!",
                )
        elif isinstance(response, AccountFrozen):
            logger.info("账号已被冻结!")
            raise LoginAccountFrozen(response.uin)
        elif isinstance(response, DeviceLocked):
            msg = "账号已开启设备锁!"
            if response.sms_phone:
                msg += f"向手机{response.sms_phone}发送验证码 "
            if response.verify_url:
                msg += f"或前往{response.verify_url}扫码验证"
            logger.info(msg + ". " + str(response.message))

            raise LoginDeviceLocked(
                response.uin,
                response.sms_phone,
                response.verify_url,
                response.message,
            )
        elif isinstance(response, TooManySMSRequest):
            logger.info("验证码发送频繁!")
            raise LoginSMSRequestError(response.uin)
        elif isinstance(response, DeviceLockLogin):
            if try_times:
                seq = self.next_seq()
                packet = encode_login_request20(
                    seq,
                    self._key,
                    self._session_id,
                    self._ksid,
                    self.uin,
                    self._t104,
                    self._siginfo.g,
                )
                response = await self.send_and_wait(seq, "wtlogin.login",
                                                    packet)
                return await self._handle_login_response(
                    response, try_times - 1)
            else:
                raise LoginException(
                    response.uin,
                    response.status,
                    "Maximum number of login attempts exceeded!",
                )
        elif isinstance(response, UnknownLoginStatus):
            t146 = response._tlv_map.get(0x146)
            t149 = response._tlv_map.get(0x149)
            if t146:
                packet_ = Packet(t146)
                msg = packet_.start(4).string(2).execute()[0]
            elif t149:
                packet_ = Packet(t149)
                msg = packet_.start(2).string(2).execute()[0]
            else:
                msg = ""
            logger.info(f"未知的登录返回码 {response.status}! {msg}")
            raise LoginException(response.uin, response.status,
                                 "Unknown login status.")