Files
miaosic/providers/qq/qimei.go
aynakeya 971f5fc4e5 finish qq provider
fix session cmd: save session every in every command

add qq init

fix init sequence

update gitignore

fix miaosic cmd

add kugou album info in search

update qq music api.
2025-07-07 23:08:23 +08:00

279 lines
7.5 KiB
Go

package qq
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/AynaLivePlayer/miaosic"
"strings"
"time"
)
const (
QiMeiPublicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIxgwoutfwoJxcGQeedgP7FG9qaIuS0qzfR8gWkrkTZKM2iWHn2ajQpBRZjMSoSf6+KJGvar2ORhBfpDXyVtZCKpqLQ+FLkpncClKVIrBwv6PHyUvuCb0rIarmgDnzkfQAqVufEtR64iazGDKatvJ9y6B9NMbHddGSAUmRTCrHQIDAQAB
-----END PUBLIC KEY-----`
QiMeiSecret = "ZdJqM15EeO2zWc08"
QiMeiAppKey = "0AND0HD6FE4HY80F"
)
type QimeiResult struct {
Q16 string `json:"q16"`
Q36 string `json:"q36"`
}
type qimeiReserved struct {
Harmony string `json:"harmony"`
Clone string `json:"clone"`
Containe string `json:"containe"`
Oz string `json:"oz"`
Oo string `json:"oo"`
Kelong string `json:"kelong"`
Uptimes string `json:"uptimes"`
MultiUser string `json:"multiUser"`
Bod string `json:"bod"`
Dv string `json:"dv"`
FirstLevel string `json:"firstLevel"`
Manufact string `json:"manufact"`
Name string `json:"name"`
Host string `json:"host"`
Kernel string `json:"kernel"`
}
type qimeiPayload struct {
AndroidID string `json:"androidId"`
PlatformID int `json:"platformId"`
AppKey string `json:"appKey"`
AppVersion string `json:"appVersion"`
BeaconIDSrc string `json:"beaconIdSrc"`
Brand string `json:"brand"`
ChannelID string `json:"channelId"`
Cid string `json:"cid"`
Imei string `json:"imei"`
Imsi string `json:"imsi"`
Mac string `json:"mac"`
Model string `json:"model"`
NetworkType string `json:"networkType"`
Oaid string `json:"oaid"`
OSVersion string `json:"osVersion"`
Qimei string `json:"qimei"`
Qimei36 string `json:"qimei36"`
SDKVersion string `json:"sdkVersion"`
TargetSDKVersion string `json:"targetSdkVersion"`
Audit string `json:"audit"`
UserID string `json:"userId"`
PackageID string `json:"packageId"`
DeviceType string `json:"deviceType"`
SDKName string `json:"sdkName"`
Reserved string `json:"reserved"`
}
type qimeiParams struct {
Key string `json:"key"`
Params string `json:"params"`
Time string `json:"time"`
Nonce string `json:"nonce"`
Sign string `json:"sign"`
Extra string `json:"extra"`
}
type qimeiRequest struct {
App int `json:"app"`
Os int `json:"os"`
QimeiParams qimeiParams `json:"qimeiParams"`
}
type qimeiResponse struct {
Data string `json:"data"`
}
type qimeiData struct {
Data struct {
Q16 string `json:"q16"`
Q36 string `json:"q36"`
} `json:"data"`
}
func qimeiRandomBeaconID() string {
timeMonth := time.Now().Format("2006-01-") + "01"
rand1 := rng.Intn(900000) + 100000
rand2 := rng.Intn(900000000) + 100000000
var parts []string
for i := 1; i <= 40; i++ {
switch i {
case 1, 2, 13, 14, 17, 18, 21, 22, 25, 26, 29, 30, 33, 34, 37, 38:
parts = append(parts, fmt.Sprintf("k%d:%s%d.%d", i, timeMonth, rand1, rand2))
case 3:
parts = append(parts, "k3:0000000000000000")
case 4:
// 生成16位随机十六进制
buf := make([]byte, 8)
rng.Read(buf)
parts = append(parts, fmt.Sprintf("k4:%x", buf))
default:
parts = append(parts, fmt.Sprintf("k%d:%d", i, rng.Intn(10000)))
}
}
return strings.Join(parts, ";")
}
func qimeiRandomPayloadByDevice(device *Device, version string) *qimeiPayload {
fixedRand := rng.Intn(14400)
uptime := time.Now().Add(-time.Duration(fixedRand) * time.Second).Format("2006-01-02 15:04:05")
reserved := &qimeiReserved{
Harmony: "0",
Clone: "0",
Containe: "",
Oz: "UhYmelwouA+V2nPWbOvLTgN2/m8jwGB+yUB5v9tysQg=",
Oo: "Xecjt+9S1+f8Pz2VLSxgpw==",
Kelong: "0",
Uptimes: uptime,
MultiUser: "0",
Bod: device.Brand,
Dv: device.Device,
FirstLevel: "",
Manufact: device.Brand,
Name: device.Model,
Host: "se.infra",
Kernel: device.ProcVersion,
}
reservedJSON, _ := json.Marshal(reserved)
return &qimeiPayload{
AndroidID: device.AndroidID,
PlatformID: 1,
AppKey: QiMeiAppKey,
AppVersion: version,
BeaconIDSrc: qimeiRandomBeaconID(),
Brand: device.Brand,
ChannelID: "10003505",
Cid: "",
Imei: device.IMEI,
Imsi: "",
Mac: "",
Model: device.Model,
NetworkType: "unknown",
Oaid: "",
OSVersion: fmt.Sprintf("Android %s,level %d", device.Version.Release, device.Version.Sdk),
Qimei: "",
Qimei36: "",
SDKVersion: version,
TargetSDKVersion: "33",
Audit: "",
UserID: "{}",
PackageID: "com.tencent.qqmusic",
DeviceType: "Phone",
SDKName: "",
Reserved: string(reservedJSON),
}
}
func getQimei(device *Device, version string) (*QimeiResult, error) {
result, err := fetchQimei(device, version)
if err == nil {
device.Qimei = result.Q36
return result, nil
}
if device.Qimei != "" {
return &QimeiResult{Q16: "", Q36: device.Qimei}, nil
}
return &QimeiResult{Q16: "", Q36: "6c9d3cd110abca9b16311cee10001e717614"}, nil
}
func qimeiRandomString(length int) string {
const charset = "abcdef0123456789"
result := make([]byte, length)
for i := range result {
result[i] = charset[rng.Intn(len(charset))]
}
return string(result)
}
// 从腾讯API获取QIMEI
func fetchQimei(device *Device, version string) (*QimeiResult, error) {
payload := qimeiRandomPayloadByDevice(device, version)
payloadJSON, err := json.Marshal(payload)
if err != nil {
return nil, err
}
// 生成加密密钥和随机数
cryptKey := qimeiRandomString(16)
nonce := qimeiRandomString(16)
ts := time.Now().UnixMilli()
// RSA加密密钥
rsaEncryptedKey, err := rsaEncrypt([]byte(cryptKey), QiMeiPublicKey)
if err != nil {
return nil, err
}
keyBase64 := base64.StdEncoding.EncodeToString(rsaEncryptedKey)
// AES加密payload
aesEncrypted, err := aesEncrypt([]byte(cryptKey), payloadJSON)
if err != nil {
return nil, err
}
paramsBase64 := base64.StdEncoding.EncodeToString(aesEncrypted)
// 生成签名
extra := fmt.Sprintf(`{"appKey":"%s"}`, QiMeiAppKey)
sign := calcMd5(keyBase64, paramsBase64, fmt.Sprintf("%d", ts), nonce, QiMeiSecret, extra)
// 构建请求
qimeiParams := qimeiParams{
Key: keyBase64,
Params: paramsBase64,
Time: fmt.Sprintf("%d", ts),
Nonce: nonce,
Sign: sign,
Extra: extra,
}
requestBody := qimeiRequest{
App: 0,
Os: 1,
QimeiParams: qimeiParams,
}
requestJSON, err := json.Marshal(requestBody)
if err != nil {
return nil, err
}
resp, err := miaosic.Requester.Post(
"https://api.tencentmusic.com/tme/trpc/proxy",
map[string]string{
"Host": "api.tencentmusic.com",
"method": "GetQimei",
"service": "trpc.tme_datasvr.qimeiproxy.QimeiProxy",
"appid": "qimei_qq_android",
"sign": calcMd5("qimei_qq_androidpzAuCmaFAaFaHrdakPjLIEqKrGnSOOvH", fmt.Sprintf("%d", ts/1000)),
"user-agent": "QQMusic",
"timestamp": fmt.Sprintf("%d", ts/1000),
"Content-Type": "application/json",
}, requestJSON,
)
if err != nil {
return nil, err
}
var qimeiResp qimeiResponse
if err := json.Unmarshal(resp.Body(), &qimeiResp); err != nil {
return nil, err
}
var qimeiData qimeiData
if err := json.Unmarshal([]byte(qimeiResp.Data), &qimeiData); err != nil {
return nil, err
}
return &QimeiResult{
Q16: qimeiData.Data.Q16,
Q36: qimeiData.Data.Q36,
}, nil
}