mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 06:52:13 +08:00
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 ""
|
||
|
|
}
|