github.com/LagrangeDev/LagrangeGo@v0.0.0-20240512064304-ad4a85e10cb4/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	"github.com/LagrangeDev/LagrangeGo/client/packets/tlv"
    10  	"github.com/LagrangeDev/LagrangeGo/client/packets/wtlogin"
    11  	"github.com/LagrangeDev/LagrangeGo/client/packets/wtlogin/loginState"
    12  	"github.com/LagrangeDev/LagrangeGo/client/packets/wtlogin/qrcodeState"
    13  	"github.com/LagrangeDev/LagrangeGo/utils"
    14  	"github.com/LagrangeDev/LagrangeGo/utils/binary"
    15  	"github.com/LagrangeDev/LagrangeGo/utils/crypto"
    16  )
    17  
    18  func (c *QQClient) Login(password, qrcodePath string) error {
    19  	// prefer session login
    20  	if len(c.transport.Sig.D2) != 0 && c.transport.Sig.Uin != 0 {
    21  		c.infoln("Session found, try to login with session")
    22  		c.Uin = c.transport.Sig.Uin
    23  		if c.Online.Load() {
    24  			return ErrAlreadyOnline
    25  		}
    26  		err := c.connect()
    27  		if err != nil {
    28  			return err
    29  		}
    30  		err = c.init()
    31  		if err != nil {
    32  			err = fmt.Errorf("failed to register session: %v", err)
    33  			c.errorln(err)
    34  			return err
    35  		}
    36  		return nil
    37  	}
    38  
    39  	if len(c.transport.Sig.TempPwd) != 0 {
    40  		err := c.keyExchange()
    41  		if err != nil {
    42  			return err
    43  		}
    44  
    45  		ret, err := c.TokenLogin()
    46  		if err != nil {
    47  			return fmt.Errorf("EasyLogin fail: %s", err)
    48  		}
    49  
    50  		if ret.Successful() {
    51  			return c.init()
    52  		}
    53  	}
    54  
    55  	if password != "" {
    56  		c.infoln("login with password")
    57  		err := c.keyExchange()
    58  		if err != nil {
    59  			return err
    60  		}
    61  
    62  		for {
    63  			ret, err := c.PasswordLogin(password)
    64  			if err != nil {
    65  				return err
    66  			}
    67  			switch {
    68  			case ret.Successful():
    69  				return c.init()
    70  			case ret == loginState.CaptchaVerify:
    71  				c.warningln("captcha verification required")
    72  				c.transport.Sig.CaptchaInfo[0] = utils.ReadLine("ticket?->")
    73  				c.transport.Sig.CaptchaInfo[1] = utils.ReadLine("rand_str?->")
    74  			default:
    75  				c.error("Unhandled exception raised: %s", ret.Name())
    76  			}
    77  		}
    78  		// panic("unreachable")
    79  	}
    80  	c.infoln("login with qrcode")
    81  	png, _, err := c.FecthQRCode()
    82  	if err != nil {
    83  		return err
    84  	}
    85  	err = os.WriteFile(qrcodePath, png, 0666)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	c.info("qrcode saved to %s", qrcodePath)
    90  	err = c.QRCodeLogin(3)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	return c.init()
    95  }
    96  
    97  func (c *QQClient) TokenLogin() (loginState.State, error) {
    98  	if c.Online.Load() {
    99  		return -996, ErrAlreadyOnline
   100  	}
   101  	err := c.connect()
   102  	if err != nil {
   103  		return -997, err
   104  	}
   105  	data, err := buildNtloginRequest(c.Uin, c.version(), c.Device(), &c.transport.Sig, c.transport.Sig.TempPwd)
   106  	if err != nil {
   107  		return -998, err
   108  	}
   109  	packet, err := c.sendUniPacketAndWait(
   110  		"trpc.login.ecdh.EcdhService.SsoNTLoginEasyLogin",
   111  		data,
   112  	)
   113  	if err != nil {
   114  		return -999, err
   115  	}
   116  	return parseNtloginResponse(packet, &c.transport.Sig)
   117  }
   118  
   119  func (c *QQClient) FecthQRCode() ([]byte, string, error) {
   120  	if c.Online.Load() {
   121  		return nil, "", ErrAlreadyOnline
   122  	}
   123  	err := c.connect()
   124  	if err != nil {
   125  		return nil, "", err
   126  	}
   127  
   128  	body := binary.NewBuilder(nil).
   129  		WriteU16(0).
   130  		WriteU64(0).
   131  		WriteU8(0).
   132  		WriteTLV(
   133  			tlv.T16(c.version().AppID, c.version().SubAppID,
   134  				utils.MustParseHexStr(c.Device().Guid), c.version().PTVersion, c.version().PackageName),
   135  			tlv.T1b(),
   136  			tlv.T1d(c.version().MiscBitmap),
   137  			tlv.T33(utils.MustParseHexStr(c.Device().Guid)),
   138  			tlv.T35(c.version().PTOSVersion),
   139  			tlv.T66(c.version().PTOSVersion),
   140  			tlv.Td1(c.version().OS, c.Device().DeviceName),
   141  		).WriteU8(3).ToBytes()
   142  
   143  	packet := c.buildCode2dPacket(c.Uin, 0x31, body)
   144  	response, err := c.sendUniPacketAndWait("wtlogin.trans_emp", packet)
   145  	if err != nil {
   146  		return nil, "", err
   147  	}
   148  
   149  	decrypted := binary.NewReader(response)
   150  	decrypted.SkipBytes(54)
   151  	retCode := decrypted.ReadU8()
   152  	qrsig := decrypted.ReadBytesWithLength("u16", false)
   153  	tlvs := decrypted.ReadTlv()
   154  
   155  	if retCode == 0 && tlvs[0x17] != nil {
   156  		c.transport.Sig.Qrsig = qrsig
   157  		urlreader := binary.NewReader(tlvs[209])
   158  		// 这样是不对的,调试后发现应该丢一个字节,然后读下一个字节才是数据的大小
   159  		// string(binary.NewReader(tlvs[209]).ReadBytesWithLength("u16", true))
   160  		urlreader.ReadU8()
   161  		return tlvs[0x17], utils.B2S(urlreader.ReadBytesWithLength("u8", false)), nil
   162  	}
   163  
   164  	return nil, "", fmt.Errorf("err qr retcode %d", retCode)
   165  }
   166  
   167  func (c *QQClient) GetQRCodeResult() (qrcodeState.State, error) {
   168  	c.infoln("get qrcode result")
   169  	if c.transport.Sig.Qrsig == nil {
   170  		return -1, errors.New("no qrsig found, execute fetch_qrcode first")
   171  	}
   172  
   173  	body := binary.NewBuilder(nil).
   174  		WritePacketBytes(c.transport.Sig.Qrsig, "u16", false).
   175  		WriteU64(0).
   176  		WriteU32(0).
   177  		WriteU8(0).
   178  		WriteU8(0x83).ToBytes()
   179  
   180  	response, err := c.sendUniPacketAndWait("wtlogin.trans_emp",
   181  		c.buildCode2dPacket(0, 0x12, body))
   182  	if err != nil {
   183  		return -1, err
   184  	}
   185  
   186  	reader := binary.NewReader(response)
   187  	//length := reader.ReadU32()
   188  	reader.SkipBytes(8) // 4 + 4
   189  	reader.ReadU16()    // cmd, 0x12
   190  	reader.SkipBytes(40)
   191  	_ = reader.ReadU32() // app id
   192  	retCode := qrcodeState.State(reader.ReadU8())
   193  
   194  	if retCode == 0 {
   195  		reader.SkipBytes(4)
   196  		c.Uin = reader.ReadU32()
   197  		reader.SkipBytes(4)
   198  		t := reader.ReadTlv()
   199  		c.t106 = t[0x18]
   200  		c.t16a = t[0x19]
   201  		c.transport.Sig.Tgtgt = t[0x1e]
   202  		c.debugln("len(c.t106) =", len(c.t106), "len(c.t16a) =", len(c.t16a))
   203  		c.debugln("len(c.transport.Sig.Tgtgt) =", len(c.transport.Sig.Tgtgt))
   204  	}
   205  
   206  	return retCode, nil
   207  }
   208  
   209  func (c *QQClient) keyExchange() error {
   210  	data, err := wtlogin.BuildKexExchangeRequest(c.Uin, c.Device().Guid)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	packet, err := c.sendUniPacketAndWait(
   215  		"trpc.login.ecdh.EcdhService.SsoKeyExchange",
   216  		data,
   217  	)
   218  	if err != nil {
   219  		c.errorln(err)
   220  		return err
   221  	}
   222  	c.debug("keyexchange proto data: %x", packet)
   223  	c.transport.Sig.ExchangeKey, c.transport.Sig.KeySig, err = wtlogin.ParseKeyExchangeResponse(packet)
   224  	return err
   225  }
   226  
   227  func (c *QQClient) PasswordLogin(password string) (loginState.State, error) {
   228  	if c.Online.Load() {
   229  		return -996, ErrAlreadyOnline
   230  	}
   231  	err := c.connect()
   232  	if err != nil {
   233  		return -997, err
   234  	}
   235  
   236  	md5Password := crypto.MD5Digest(utils.S2B(password))
   237  
   238  	cr := tlv.T106(
   239  		c.version().AppID,
   240  		c.version().AppClientVersion,
   241  		int(c.Uin),
   242  		c.Device().Guid,
   243  		md5Password,
   244  		c.transport.Sig.Tgtgt,
   245  		make([]byte, 4),
   246  		true)[4:]
   247  
   248  	data, err := buildNtloginRequest(c.Uin, c.version(), c.Device(), &c.transport.Sig, cr)
   249  	if err != nil {
   250  		return -998, err
   251  	}
   252  	packet, err := c.sendUniPacketAndWait(
   253  		"trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLogin",
   254  		data,
   255  	)
   256  	if err != nil {
   257  		return -999, err
   258  	}
   259  	return parseNtloginResponse(packet, &c.transport.Sig)
   260  }
   261  
   262  func (c *QQClient) QRCodeLogin(refreshInterval int) error {
   263  	if c.transport.Sig.Qrsig == nil {
   264  		return errors.New("no QrSig found, fetch qrcode first")
   265  	}
   266  
   267  	for {
   268  		retCode, err := c.GetQRCodeResult()
   269  		if err != nil {
   270  			c.errorln(err)
   271  			return err
   272  		}
   273  		if retCode.Waitable() {
   274  			time.Sleep(time.Duration(refreshInterval) * time.Second)
   275  			continue
   276  		}
   277  		if !retCode.Success() {
   278  			return errors.New(retCode.Name())
   279  		}
   280  		break
   281  	}
   282  
   283  	app := c.version()
   284  	device := c.Device()
   285  	response, err := c.sendUniPacketAndWait(
   286  		"wtlogin.login",
   287  		c.buildLoginPacket(c.Uin, "wtlogin.login", binary.NewBuilder(nil).
   288  			WriteU16(0x09).
   289  			WriteTLV(
   290  				binary.NewBuilder(nil).WriteBytes(c.t106).Pack(0x106),
   291  				tlv.T144(c.transport.Sig.Tgtgt, app, device),
   292  				tlv.T116(app.SubSigmap),
   293  				tlv.T142(app.PackageName, 0),
   294  				tlv.T145(utils.MustParseHexStr(device.Guid)),
   295  				tlv.T18(0, app.AppClientVersion, int(c.Uin), 0, 5, 0),
   296  				tlv.T141([]byte("Unknown"), nil),
   297  				tlv.T177(app.WTLoginSDK, 0),
   298  				tlv.T191(0),
   299  				tlv.T100(5, app.AppID, app.SubAppID, 8001, app.MainSigmap, 0),
   300  				tlv.T107(1, 0x0d, 0, 1),
   301  				tlv.T318(nil),
   302  				binary.NewBuilder(nil).WriteBytes(c.t16a).Pack(0x16a),
   303  				tlv.T166(5),
   304  				tlv.T521(0x13, "basicim"),
   305  			).ToBytes()))
   306  
   307  	if err != nil {
   308  		c.errorln(err)
   309  		return err
   310  	}
   311  
   312  	return c.decodeLoginResponse(response, &c.transport.Sig)
   313  }
   314  
   315  func (c *QQClient) init() error {
   316  	response, err := c.sendUniPacketAndWait(
   317  		"trpc.qq_new_tech.status_svc.StatusService.Register",
   318  		wtlogin.BuildRegisterRequest(c.version(), c.Device()))
   319  
   320  	if err != nil {
   321  		c.errorln(err)
   322  		return err
   323  	}
   324  
   325  	err = wtlogin.ParseRegisterResponse(response)
   326  	if err != nil {
   327  		c.errorln("register failed:", err)
   328  		return err
   329  	}
   330  	c.transport.Sig.Uin = c.Uin
   331  	c.setOnline()
   332  	go c.doHeartbeat()
   333  	c.infoln("register succeeded")
   334  	return nil
   335  }