mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-02 22:42:14 +08:00
Claude Code v2.1.78 起将 metadata.user_id 从拼接字符串改为 JSON:
旧: user_{hex}_account_{uuid}_session_{uuid}
新: {"device_id":"...","account_uuid":"...","session_id":"..."}
新增集中解析/格式化模块 metadata_userid.go:
- ParseMetadataUserID: 自动识别两种格式,提取 DeviceID/AccountUUID/SessionID
- FormatMetadataUserID: 根据 UA 版本输出对应格式(>= 2.1.78 输出 JSON)
- ExtractCLIVersion: 从 UA 提取版本号,消除与 ClaudeCodeValidator.ExtractVersion 的重复
修改消费者统一使用新模块:
- claude_code_validator: 用 ParseMetadataUserID 替代只匹配旧格式的 userIDPattern
- identity_service: RewriteUserID/WithMasking 增加 fingerprintUA 参数,
解析用 ParseMetadataUserID,输出用 FormatMetadataUserID(版本感知)
- gateway_service: GenerateSessionHash 用 ParseMetadataUserID 提取 session_id,
buildOAuthMetadataUserID 用 FormatMetadataUserID 输出版本匹配格式,
两处 RewriteUserIDWithMasking 调用传入 fp.UserAgent
- account_test_service: generateSessionString 改用 FormatMetadataUserID,
自动跟随 DefaultHeaders UA 版本
删除三个旧正则: userIDPattern, userIDRegex, sessionIDRegex
统一 hex 匹配为 [a-fA-F0-9],修复旧 userIDRegex 只匹配小写的不一致
105 lines
3.0 KiB
Go
105 lines
3.0 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// NewMetadataFormatMinVersion is the minimum Claude Code version that uses
|
|
// JSON-formatted metadata.user_id instead of the legacy concatenated string.
|
|
const NewMetadataFormatMinVersion = "2.1.78"
|
|
|
|
// ParsedUserID represents the components extracted from a metadata.user_id value.
|
|
type ParsedUserID struct {
|
|
DeviceID string // 64-char hex (or arbitrary client id)
|
|
AccountUUID string // may be empty
|
|
SessionID string // UUID
|
|
IsNewFormat bool // true if the original was JSON format
|
|
}
|
|
|
|
// legacyUserIDRegex matches the legacy user_id format:
|
|
//
|
|
// user_{64hex}_account_{optional_uuid}_session_{uuid}
|
|
var legacyUserIDRegex = regexp.MustCompile(`^user_([a-fA-F0-9]{64})_account_([a-fA-F0-9-]*)_session_([a-fA-F0-9-]{36})$`)
|
|
|
|
// jsonUserID is the JSON structure for the new metadata.user_id format.
|
|
type jsonUserID struct {
|
|
DeviceID string `json:"device_id"`
|
|
AccountUUID string `json:"account_uuid"`
|
|
SessionID string `json:"session_id"`
|
|
}
|
|
|
|
// ParseMetadataUserID parses a metadata.user_id string in either format.
|
|
// Returns nil if the input cannot be parsed.
|
|
func ParseMetadataUserID(raw string) *ParsedUserID {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
|
|
// Try JSON format first (starts with '{')
|
|
if raw[0] == '{' {
|
|
var j jsonUserID
|
|
if err := json.Unmarshal([]byte(raw), &j); err != nil {
|
|
return nil
|
|
}
|
|
if j.DeviceID == "" || j.SessionID == "" {
|
|
return nil
|
|
}
|
|
return &ParsedUserID{
|
|
DeviceID: j.DeviceID,
|
|
AccountUUID: j.AccountUUID,
|
|
SessionID: j.SessionID,
|
|
IsNewFormat: true,
|
|
}
|
|
}
|
|
|
|
// Try legacy format
|
|
matches := legacyUserIDRegex.FindStringSubmatch(raw)
|
|
if matches == nil {
|
|
return nil
|
|
}
|
|
return &ParsedUserID{
|
|
DeviceID: matches[1],
|
|
AccountUUID: matches[2],
|
|
SessionID: matches[3],
|
|
IsNewFormat: false,
|
|
}
|
|
}
|
|
|
|
// FormatMetadataUserID builds a metadata.user_id string in the format
|
|
// appropriate for the given CLI version. Components are the rewritten values
|
|
// (not necessarily the originals).
|
|
func FormatMetadataUserID(deviceID, accountUUID, sessionID, uaVersion string) string {
|
|
if IsNewMetadataFormatVersion(uaVersion) {
|
|
b, _ := json.Marshal(jsonUserID{
|
|
DeviceID: deviceID,
|
|
AccountUUID: accountUUID,
|
|
SessionID: sessionID,
|
|
})
|
|
return string(b)
|
|
}
|
|
// Legacy format
|
|
return "user_" + deviceID + "_account_" + accountUUID + "_session_" + sessionID
|
|
}
|
|
|
|
// IsNewMetadataFormatVersion returns true if the given CLI version uses the
|
|
// new JSON metadata.user_id format (>= 2.1.78).
|
|
func IsNewMetadataFormatVersion(version string) bool {
|
|
if version == "" {
|
|
return false
|
|
}
|
|
return CompareVersions(version, NewMetadataFormatMinVersion) >= 0
|
|
}
|
|
|
|
// ExtractCLIVersion extracts the Claude Code version from a User-Agent string.
|
|
// Returns "" if the UA doesn't match the expected pattern.
|
|
func ExtractCLIVersion(ua string) string {
|
|
matches := claudeCodeUAVersionPattern.FindStringSubmatch(ua)
|
|
if len(matches) >= 2 {
|
|
return matches[1]
|
|
}
|
|
return ""
|
|
}
|