Files
fanqie-go/internal/fqapi/device_register.go
T
chenxin 9aec09bb40 ✨ feat(manga,device): 漫画免签名兜底与 phone 注册身份选项
- /manga 设备池签名路线全部失败时自动降级 wkbrowser 免签名路线(materials/skeleton 三段策略收集原图直链,无需设备/签名/解密),响应附 source: wkbrowser
- 新增配置 fanqie.device_identity(默认 pad 保持现状;phone 对齐书源真机注册 payload:host_bit 64 + 补 hardware/device_device/fingerprint 字段)
- README 与 openapi.yaml 同步文档,新增 manga_fallback 解析测试
2026-10-12 14:53:08 +08:00

411 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package fqapi
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fanqie/internal/common"
"fanqie/internal/config"
"fmt"
"math/big"
"net"
"os"
"runtime"
"sort"
"strconv"
"strings"
"time"
"github.com/google/uuid"
)
// 使用共享的设备配置
var (
androidVersions = DeviceConfig.AndroidVersions
brandModels = DeviceConfig.BrandModels
deviceBrands = DeviceConfig.DeviceBrands
)
// generateUUID 生成UUID4字符串
func generateUUID() string {
return uuid.New().String()
}
// currentMillis 返回当前毫秒级时间戳
func currentMillis() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}
// getCustomBT 获取设备开机时间戳
// 模拟 Android 中的 m25453o() 方法,计算设备开机时间戳:
// custom_bt = current_millis - elapsed_ms_since_boot
func getCustomBT() string {
var uptimeSeconds float64
if runtime.GOOS == "linux" {
// 在Linux上读取/proc/uptime
data, err := os.ReadFile("/proc/uptime")
if err == nil {
// /proc/uptime格式: "系统运行时间(秒) 空闲时间(秒)"
parts := strings.Split(string(data), " ")
if len(parts) > 0 {
uptime, err := strconv.ParseFloat(strings.TrimSpace(parts[0]), 64)
if err == nil {
uptimeSeconds = uptime
}
}
}
// 如果读取失败,使用time.monotonic作为备选
if uptimeSeconds == 0 {
// Go没有直接的monotonic API,使用启动时间估算
uptimeSeconds = float64(time.Now().Unix() % 86400)
}
} else {
// 非Linux系统,使用估算值
// 这里可以使用进程启动时间作为近似
uptimeSeconds = float64(time.Now().Unix() % 86400)
}
return fmt.Sprintf("%d", currentMillis()-int64(uptimeSeconds*1000))
}
// generateDeviceInfo 随机生成设备及操作系统信息
func generateDeviceInfo() map[string]interface{} {
osVersion := androidVersions[randInt(len(androidVersions))]
// 随机选择品牌
deviceBrand := deviceBrands[randInt(len(deviceBrands))]
// 根据品牌选择对应的型号
models := brandModels[deviceBrand]
deviceModel := models[randInt(len(models))]
releaseBytes := make([]byte, 7)
rand.Read(releaseBytes)
releaseBuild := hex.EncodeToString(releaseBytes)
openUDIDBytes := make([]byte, 10)
rand.Read(openUDIDBytes)
return map[string]interface{}{
"os": "Android",
"os_version": osVersion[0],
"os_api": osVersion[1],
"device_model": deviceModel,
"device_brand": deviceBrand,
"device_manufacturer": deviceBrand,
"cpu_abi": "arm64-v8a",
"release_build": releaseBuild,
"cdid": generateUUID(),
"sig_hash": "a4a27c2633195374c15651ffc3c4a497",
"openudid": hex.EncodeToString(openUDIDBytes),
"clientudid": generateUUID(),
"req_id": generateUUID(),
}
}
// randInt 生成随机整数
func randInt(max int) int {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(max)))
return int(n.Int64())
}
// randomString 生成随机字符串
func randomString(length int) string {
const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
b := make([]byte, length)
for i := range b {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
b[i] = charset[n.Int64()]
}
return string(b)
}
// generateOptionalFields 生成ROM信息等
func generateOptionalFields() (map[string]interface{}, map[string]int64) {
rom := randomString(14)
part1 := randomString(4)
part2 := randomString(6)
part3 := randomString(8)
genTime := currentMillis()
return map[string]interface{}{
"rom": rom,
"rom_version": fmt.Sprintf("coloros__%s.%s.%s release-keys", part1, part2, part3),
"apk_first_install_time": genTime - int64(randInt(365))*86400000,
}, map[string]int64{
"_gen_time": genTime,
}
}
// generateIPv6AddressLinkLocal 生成链路本地IPv6地址
func generateIPv6AddressLinkLocal() string {
ifaces, err := net.Interfaces()
if err != nil {
return "::1"
}
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP
if ip.To16() != nil && strings.HasPrefix(ip.String(), "fe80::") {
return strings.Split(ip.String(), "%")[0]
}
}
}
return "::1"
}
// generateIPv6AddressUniqueLocal 生成唯一本地IPv6地址
func generateIPv6AddressUniqueLocal() string {
b := make([]byte, 1)
rand.Read(b)
parts := []string{fmt.Sprintf("fd%02x", b[0])}
for i := 0; i < 7; i++ {
b := make([]byte, 2)
rand.Read(b)
parts = append(parts, fmt.Sprintf("%04x", uint16(b[0])<<8|uint16(b[1])))
}
return strings.Join(parts, ":")
}
// generateIPv6List 生成IPv6列表
func generateIPv6List() []map[string]string {
return []map[string]string{
{"type": "client_anpi", "value": generateIPv6AddressLinkLocal()},
{"type": "client_anpi", "value": generateIPv6AddressUniqueLocal()},
{"type": "client_anpi", "value": generateIPv6AddressUniqueLocal()},
}
}
// deviceIdentity 注册设备身份:pad(默认,历史行为)/ phone(对齐手机 App 书源注册 payload)
func deviceIdentity() string {
if cfg := config.GetConfig(); cfg != nil && cfg.Fanqie.DeviceIdentity == "phone" {
return "phone"
}
return "pad"
}
// deviceCustomFields 注册身份对应的 custom 字段
func deviceCustomFields() map[string]interface{} {
if deviceIdentity() == "phone" {
return map[string]interface{}{"host_bit": 64, "dragon_device_type": "phone"}
}
return map[string]interface{}{"host_bit": 32, "dragon_device_type": "pad"}
}
// GenerateFullRequestBody 生成完整的请求体数据
func GenerateFullRequestBody() map[string]interface{} {
rawData := map[string]interface{}{
"magic_tag": "ss_app_log",
"header": map[string]interface{}{
"display_name": "番茄免费小说",
"aid": 1967,
"channel": "43536163a",
"package": "com.dragon.read",
"sdk_version": "3.7.0-rc.25-fanqie-xiaoshuo",
"sdk_target_version": 29,
"git_hash": "711d1a7",
"density_dpi": 240,
"display_density": "hdpi",
"resolution": "720x1280",
"language": "zh",
"timezone": 8,
"access": "wifi",
"not_request_sender": 0,
"carrier": "CHINA MOBILE",
"mcc_mnc": "46000",
"region": "CN",
"tz_name": "Asia/Shanghai",
"tz_offset": 28800,
"sim_region": "cn",
"sim_serial_number": []interface{}{},
"oaid_may_support": false,
"device_platform": "android",
"custom": deviceCustomFields(),
"pre_installed_channel": "",
"is_system_app": 0,
"sdk_flavor": "china",
"guest_mode": 0,
},
}
deviceInfo := generateDeviceInfo()
optionalFields, genTime := generateOptionalFields()
header := rawData["header"].(map[string]interface{})
for k, v := range deviceInfo {
header[k] = v
}
for k, v := range optionalFields {
header[k] = v
}
header["ipv6_list"] = generateIPv6List()
// phone 身份补充真机指纹字段(对齐书源注册 payload,规避 pad 身份的差异化风控)
if deviceIdentity() == "phone" {
rom, _ := header["rom"].(string)
header["hardware"] = "qcom"
header["device_device"] = rom
header["fingerprint"] = fmt.Sprintf("%v/%v/%v:%v/%s/%s:user/release-keys",
header["device_brand"], header["device_model"], header["device_model"],
header["os_version"], rom, rom)
}
for k, v := range genTime {
rawData[k] = v
}
return rawData
}
// tryActivatePremium 尝试激活设备特权
func tryActivatePremium(header string) {
logger := common.NewLogger("device_register", "INFO", "")
// 构建查询参数
baseParams := []string{
"aid=1967",
"app_name=novelapp",
"channel=0",
"device_platform=android",
"os_version=0",
"version_code=" + ClientVersionCode,
"manifest_version_code=" + ClientVersionCode,
"update_version_code=" + ClientVersionCode,
header,
}
// 合并并排序参数
allParams := strings.Join(baseParams, "&")
paramList := strings.Split(allParams, "&")
sort.Strings(paramList)
sortedParams := strings.Join(paramList, "&")
requestBody := map[string]interface{}{
"add_count_daily": 0,
"amount": 2592000,
"privilege_id": 7210376203117531962,
"from": 8,
"unique_key": fmt.Sprintf("%d", time.Now().UnixNano()/1e6),
}
bodyJSON, _ := json.Marshal(requestBody)
url := "https://reading.snssdk.com/reading/user/privilege/add/v/?" + sortedParams
resp, err := GorgonRequestSync(url, map[string]interface{}{
"method": "post",
"headers": map[string]string{
"content-type": "application/json",
},
"body": bodyJSON,
})
if err != nil {
logger.Error("激活特权失败: " + err.Error())
return
}
var result map[string]interface{}
json.Unmarshal(resp.Content, &result)
if code, ok := result["code"].(float64); ok && code == 0 {
logger.Info("已成功激活设备特权")
} else {
logger.Warning("设备特权激活失败...")
}
}
// Register 设备注册
func Register() (string, error) {
logger := common.NewLogger("device_register", "INFO", "")
logger.Info("正在注册设备...")
// 生成完整请求体数据
data := GenerateFullRequestBody()
// 只记录关键设备信息
header := data["header"].(map[string]interface{})
logger.Info(fmt.Sprintf("生成设备信息 - 品牌: %s, 型号: %s, 系统: Android %v",
header["device_brand"],
header["device_model"],
header["os_version"]))
jsonData, err := json.Marshal(data)
if err != nil {
logger.Error("JSON序列化失败: " + err.Error())
return "", err
}
// 使用TTEncrypt加密
encrypted, err := TTEncrypt(jsonData)
if err != nil {
logger.Error("加密失败: " + err.Error())
return "", err
}
url := "https://i.snssdk.com/service/2/device_register/?tt_data=a"
headers := map[string]string{
"user-agent": "okhttp/4.10.0",
"content-type": "application/octet-stream; tt-data=a",
}
client := common.NewHTTPClient()
resp, err := client.Post(url, encrypted, headers)
if err != nil {
logger.Warning("设备注册失败!")
logger.Warning(err.Error())
return "", fmt.Errorf("设备注册错误")
}
var respData map[string]interface{}
err = json.Unmarshal(resp.Content, &respData)
if err != nil {
logger.Error("响应解析失败: " + err.Error())
return "", err
}
if resp.Status == 200 {
// 检查device_id
deviceID, ok := respData["device_id"]
if !ok {
logger.Warning("设备注册失败!响应中没有device_id")
return "", fmt.Errorf("设备注册失败")
}
// 转换device_id为float64并检查
deviceIDFloat, ok := deviceID.(float64)
if !ok || deviceIDFloat == 0 {
logger.Warning("设备注册失败!device_id无效")
return "", fmt.Errorf("设备注册失败")
}
deviceModel := data["header"].(map[string]interface{})["device_model"].(string)
header := fmt.Sprintf("device_id=%v&device_type=%s&iid=%v&version_name="+ClientVersionName,
respData["device_id_str"],
deviceModel,
respData["install_id_str"])
logger.Info("设备注册成功!")
// 尝试激活特权
tryActivatePremium(header)
return header, nil
}
logger.Warning("设备注册失败!")
return "", fmt.Errorf("设备注册失败")
}