feat: 二次元前端改版 + 后端账号plan探测/调度修复

This commit is contained in:
2026-08-06 10:11:05 +08:00
parent 7f4701d362
commit f76ecd5769
37 changed files with 2484 additions and 813 deletions
+99 -8
View File
@@ -10,6 +10,7 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/google/uuid"
@@ -19,6 +20,13 @@ import (
// "4BDA81F069FC6DA40A495FAB@AdobeID").
var adobeUserIDPat = regexp.MustCompile(`[A-Fa-f0-9]{20,}@AdobeID`)
// PID pool: one PID per token, reused across requests for the same account.
var (
arpPIDMu sync.Mutex
arpTokenPID = map[string]int{} // token → pid
arpPIDToken = map[int]string{} // pid → token
)
func extractUserIDFromCookie(cookie string) string {
decoded, err := url.QueryUnescape(cookie)
if err != nil {
@@ -95,19 +103,102 @@ func decodeJWTPayload(token string) map[string]any {
return out
}
func buildARPSessionID() string {
// Matches adobe2api's format exactly:
// base64({"sid":"<uuid>","ftr":"<hex16>_<ts_ms>_<pid>_dUAL43-mnts-ants-d4_31ck__tt"})
// Two fields only (no "ark") — mirrors what a real browser session sends.
ftr := randomHex(16) + "_" + strconv.FormatInt(time.Now().UnixMilli(), 10) + "_" + strconv.Itoa(randomInt(1000, 99999)) + "_dUAL43-mnts-ants-d4_31ck__tt"
raw := map[string]any{
// arkHead and arkTail are the site-constant halves of the ark blob, as captured
// from firefly.adobe.com. The leading "<17hex>.<10digits>" pair and the rid
// field sit between them and vary per session, so buildARKBlob randomizes those
// instead of shipping one frozen blob for every account.
const (
arkHead = "|r=ap-southeast-1|meta=3|metabgclr=transparent|metaiconclr=%23757575|guitextcolor=%23000000|pk=BBCC314C-4937-4CCD-B0A3-FDF0F0F7603C|at=40|sup=1"
arkTail = "|ag=101|cdn_url=https%3A%2F%2Farks-client.adobe.com%2Fcdn%2Ffc|surl=https%3A%2F%2Farks-client.adobe.com|smurl=https%3A%2F%2Farks-client.adobe.com%2Fcdn%2Ffc%2Fassets%2Fstyle-manager"
)
func buildARKBlob() string {
return randomHex(9)[:17] + "." + randomDigits(10) +
arkHead + "|rid=" + strconv.Itoa(randomInt(1, 99)) + arkTail
}
// buildARPSessionID returns the x-arp-session-id value: base64 of
// {"sid","ark","ftr"}, shaped to match live firefly.adobe.com traffic:
//
// ark: <17hex>.<10digits>|…|rid=<n>|…
// ftr: <32hex>_<unix_ms>_<pid>_UDF43-m4_31ck__tt
//
// The pid is allocated per token (allocPID) so it stays stable across an
// account's requests, the way a real browser session would.
//
// The Arkose segment is left empty on purpose. In a real browser that slot
// carries a "<b64>=-<4digits>-v2" payload emitted by Adobe's Arkose bot-manager
// script; a synthesized one has the right shape but fails Adobe's server-side
// validation, and 3p-images/generate-async rejects it with a misleading
// {"error_code":"timeout_error","message":"system under load"}. Sending the
// slot empty reads as "no Arkose payload yet" and is accepted.
func buildARPSessionID(token string) string {
ftr := strings.Join([]string{
randomHex(16), // 32 hex chars, matching the capture
strconv.FormatInt(time.Now().UnixMilli(), 10),
strconv.Itoa(allocPID(token)),
"UDF43-m4",
"31ck",
"",
"tt",
}, "_")
b, _ := json.Marshal(map[string]any{
"sid": uuid.NewString(),
"ark": buildARKBlob(),
"ftr": ftr,
}
b, _ := json.Marshal(raw)
})
return base64.StdEncoding.EncodeToString(b)
}
// randomDigits returns n decimal digits with a non-zero leading digit, so the
// rendered value keeps a constant width.
func randomDigits(n int) string {
if n <= 0 {
return ""
}
var b strings.Builder
b.Grow(n)
b.WriteByte(byte('1' + randomInt(0, 8)))
for i := 1; i < n; i++ {
b.WriteByte(byte('0' + randomInt(0, 9)))
}
return b.String()
}
func randomBase64(n int) string {
buf := make([]byte, n)
rand.Read(buf)
return base64.RawURLEncoding.EncodeToString(buf)
}
func allocPID(token string) int {
arpPIDMu.Lock()
defer arpPIDMu.Unlock()
if pid, ok := arpTokenPID[token]; ok {
return pid
}
for {
pid := randomInt(1000, 99999)
if _, used := arpPIDToken[pid]; !used {
arpPIDToken[pid] = token
arpTokenPID[token] = pid
return pid
}
}
}
// ReleasePID releases the PID bound to token so it can be reused by another
// account.
func ReleasePID(token string) {
arpPIDMu.Lock()
defer arpPIDMu.Unlock()
if pid, ok := arpTokenPID[token]; ok {
delete(arpPIDToken, pid)
delete(arpTokenPID, token)
}
}
func randomHex(n int) string {
if n <= 0 {
return ""