更新408
This commit is contained in:
@@ -116,7 +116,7 @@ func NewApp(ctx context.Context) (*App, error) {
|
|||||||
authSvc := service.NewAuthService(userRepo, siteRepo, sessionSvc, emailCodeSvc, smtpSvc, cgroupRepo)
|
authSvc := service.NewAuthService(userRepo, siteRepo, sessionSvc, emailCodeSvc, smtpSvc, cgroupRepo)
|
||||||
appSettingsSvc := service.NewAppSettingsService(siteRepo, eventRepo, smtpSvc, rustfsClient)
|
appSettingsSvc := service.NewAppSettingsService(siteRepo, eventRepo, smtpSvc, rustfsClient)
|
||||||
imageAccessSvc := service.NewImageAccessService(cfg.GeneratedRoot, showcaseRepo, authSvc)
|
imageAccessSvc := service.NewImageAccessService(cfg.GeneratedRoot, showcaseRepo, authSvc)
|
||||||
adobeClient := adobe.NewClient("clio-playground-web", "")
|
adobeClient := adobe.NewClient("projectx_webapp", "")
|
||||||
chatGPTClient := chatgpt.NewClient("")
|
chatGPTClient := chatgpt.NewClient("")
|
||||||
runwayClient := runway.NewClient("")
|
runwayClient := runway.NewClient("")
|
||||||
leonardoClient := leonardo.NewClient("")
|
leonardoClient := leonardo.NewClient("")
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
refreshURL = "https://adobeid-na1.services.adobe.com/ims/check/v6/token?jslVersion=v2-v0.48.0-1-g1e322cb"
|
refreshURL = "https://adobeid-na1.services.adobe.com/ims/check/v6/token?jslVersion=v2-v0.48.0-1-g1e322cb"
|
||||||
clientID = "clio-playground-web"
|
clientID = "projectx_webapp"
|
||||||
scopeValue = "AdobeID,firefly_api,openid,pps.read,pps.write,additional_info.projectedProductContext,additional_info.ownerOrg,uds_read,uds_write,ab.manage,read_organizations,additional_info.roles,account_cluster.read,creative_production,profile"
|
scopeValue = "AdobeID,firefly_api,openid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrAdobeCookieEmpty = errors.New("cookie is empty")
|
var ErrAdobeCookieEmpty = errors.New("cookie is empty")
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ var profileURLs = []string{
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
apiKey string
|
apiKey string
|
||||||
proxy string
|
proxy string
|
||||||
|
arpSessionID string // cached per-client, reused across requests (matches adobe2api)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(apiKey, proxy string) *Client {
|
func NewClient(apiKey, proxy string) *Client {
|
||||||
@@ -69,6 +70,18 @@ func NewClient(apiKey, proxy string) *Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getARPSessionID returns a cached ARP session id matching adobe2api's format:
|
||||||
|
// base64({"sid":"<uuid>","ftr":"<hex16>_<ts_ms>_<pid>_dUAL43-mnts-ants-d4_31ck__tt"})
|
||||||
|
// Generated once per client and reused — adobe2api reuses the same session id per
|
||||||
|
// token/profile instead of rotating every request.
|
||||||
|
func (c *Client) getARPSessionID() string {
|
||||||
|
if c.arpSessionID != "" {
|
||||||
|
return c.arpSessionID
|
||||||
|
}
|
||||||
|
c.arpSessionID = buildARPSessionID()
|
||||||
|
return c.arpSessionID
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) SetProxy(proxy string) {
|
func (c *Client) SetProxy(proxy string) {
|
||||||
c.proxy = strings.TrimSpace(proxy)
|
c.proxy = strings.TrimSpace(proxy)
|
||||||
}
|
}
|
||||||
@@ -447,8 +460,8 @@ func (c *Client) submitImage(ctx context.Context, sess *tlsSession, token, promp
|
|||||||
"x-api-key": {c.apiKey},
|
"x-api-key": {c.apiKey},
|
||||||
"content-type": {"application/json"},
|
"content-type": {"application/json"},
|
||||||
"accept": {"*/*"},
|
"accept": {"*/*"},
|
||||||
"origin": {"https://firefly.adobe.com"},
|
"origin": {"https://new.express.adobe.com"},
|
||||||
"referer": {"https://firefly.adobe.com/"},
|
"referer": {"https://new.express.adobe.com/"},
|
||||||
"accept-language": {"en-US,en;q=0.9"},
|
"accept-language": {"en-US,en;q=0.9"},
|
||||||
"sec-ch-ua": {sess.fp.secCHUA},
|
"sec-ch-ua": {sess.fp.secCHUA},
|
||||||
"sec-ch-ua-mobile": {"?0"},
|
"sec-ch-ua-mobile": {"?0"},
|
||||||
@@ -457,7 +470,7 @@ func (c *Client) submitImage(ctx context.Context, sess *tlsSession, token, promp
|
|||||||
"sec-fetch-mode": {"cors"},
|
"sec-fetch-mode": {"cors"},
|
||||||
"sec-fetch-dest": {"empty"},
|
"sec-fetch-dest": {"empty"},
|
||||||
"user-agent": {sess.fp.userAgent},
|
"user-agent": {sess.fp.userAgent},
|
||||||
"x-arp-session-id": {buildARPSessionID()},
|
"x-arp-session-id": {c.getARPSessionID()},
|
||||||
http.HeaderOrderKey: {
|
http.HeaderOrderKey: {
|
||||||
"authorization",
|
"authorization",
|
||||||
"x-api-key",
|
"x-api-key",
|
||||||
@@ -516,16 +529,16 @@ func (c *Client) submitImage(ctx context.Context, sess *tlsSession, token, promp
|
|||||||
return respBody, "", err
|
return respBody, "", err
|
||||||
}
|
}
|
||||||
if override := strings.TrimSpace(resp.Header.Get("x-override-status-link")); override != "" {
|
if override := strings.TrimSpace(resp.Header.Get("x-override-status-link")); override != "" {
|
||||||
return respBody, override, nil
|
return respBody, normalizePollURL(override), nil
|
||||||
}
|
}
|
||||||
if links, ok := payloadResp["links"].(map[string]any); ok {
|
if links, ok := payloadResp["links"].(map[string]any); ok {
|
||||||
if result, ok := links["result"].(map[string]any); ok {
|
if result, ok := links["result"].(map[string]any); ok {
|
||||||
if href := strings.TrimSpace(stringValue(result["href"])); href != "" {
|
if href := strings.TrimSpace(stringValue(result["href"])); href != "" {
|
||||||
return respBody, href, nil
|
return respBody, normalizePollURL(href), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if href := strings.TrimSpace(stringValue(links["result"])); href != "" {
|
if href := strings.TrimSpace(stringValue(links["result"])); href != "" {
|
||||||
return respBody, href, nil
|
return respBody, normalizePollURL(href), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return respBody, "", errors.New("submit ok but no poll url")
|
return respBody, "", errors.New("submit ok but no poll url")
|
||||||
@@ -545,8 +558,8 @@ func (c *Client) pollImage(ctx context.Context, sess *tlsSession, token, pollURL
|
|||||||
req.Header = http.Header{
|
req.Header = http.Header{
|
||||||
"authorization": {"Bearer " + strings.TrimSpace(token)},
|
"authorization": {"Bearer " + strings.TrimSpace(token)},
|
||||||
"accept": {"*/*"},
|
"accept": {"*/*"},
|
||||||
"origin": {"https://firefly.adobe.com"},
|
"origin": {"https://new.express.adobe.com"},
|
||||||
"referer": {"https://firefly.adobe.com/"},
|
"referer": {"https://new.express.adobe.com/"},
|
||||||
"user-agent": {sess.fp.userAgent},
|
"user-agent": {sess.fp.userAgent},
|
||||||
http.HeaderOrderKey: {
|
http.HeaderOrderKey: {
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -625,8 +638,8 @@ func (c *Client) submitVideo(ctx context.Context, sess *tlsSession, token, endpo
|
|||||||
"x-api-key": {c.apiKey},
|
"x-api-key": {c.apiKey},
|
||||||
"content-type": {"application/json"},
|
"content-type": {"application/json"},
|
||||||
"accept": {"*/*"},
|
"accept": {"*/*"},
|
||||||
"origin": {"https://firefly.adobe.com"},
|
"origin": {"https://new.express.adobe.com"},
|
||||||
"referer": {"https://firefly.adobe.com/"},
|
"referer": {"https://new.express.adobe.com/"},
|
||||||
"accept-language": {"en-US,en;q=0.9"},
|
"accept-language": {"en-US,en;q=0.9"},
|
||||||
"sec-ch-ua": {sess.fp.secCHUA},
|
"sec-ch-ua": {sess.fp.secCHUA},
|
||||||
"sec-ch-ua-mobile": {"?0"},
|
"sec-ch-ua-mobile": {"?0"},
|
||||||
@@ -635,7 +648,7 @@ func (c *Client) submitVideo(ctx context.Context, sess *tlsSession, token, endpo
|
|||||||
"sec-fetch-mode": {"cors"},
|
"sec-fetch-mode": {"cors"},
|
||||||
"sec-fetch-dest": {"empty"},
|
"sec-fetch-dest": {"empty"},
|
||||||
"user-agent": {sess.fp.userAgent},
|
"user-agent": {sess.fp.userAgent},
|
||||||
"x-arp-session-id": {buildARPSessionID()},
|
"x-arp-session-id": {c.getARPSessionID()},
|
||||||
http.HeaderOrderKey: {
|
http.HeaderOrderKey: {
|
||||||
"authorization",
|
"authorization",
|
||||||
"x-api-key",
|
"x-api-key",
|
||||||
@@ -700,16 +713,16 @@ func (c *Client) submitVideo(ctx context.Context, sess *tlsSession, token, endpo
|
|||||||
return respBody, "", err
|
return respBody, "", err
|
||||||
}
|
}
|
||||||
if override := strings.TrimSpace(resp.Header.Get("x-override-status-link")); override != "" {
|
if override := strings.TrimSpace(resp.Header.Get("x-override-status-link")); override != "" {
|
||||||
return respBody, normalizeVideoPollURL(override), nil
|
return respBody, normalizePollURL(override), nil
|
||||||
}
|
}
|
||||||
if links, ok := payloadResp["links"].(map[string]any); ok {
|
if links, ok := payloadResp["links"].(map[string]any); ok {
|
||||||
if result, ok := links["result"].(map[string]any); ok {
|
if result, ok := links["result"].(map[string]any); ok {
|
||||||
if href := strings.TrimSpace(stringValue(result["href"])); href != "" {
|
if href := strings.TrimSpace(stringValue(result["href"])); href != "" {
|
||||||
return respBody, normalizeVideoPollURL(href), nil
|
return respBody, normalizePollURL(href), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if href := strings.TrimSpace(stringValue(links["result"])); href != "" {
|
if href := strings.TrimSpace(stringValue(links["result"])); href != "" {
|
||||||
return respBody, normalizeVideoPollURL(href), nil
|
return respBody, normalizePollURL(href), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return respBody, "", errors.New("video submit ok but no poll url")
|
return respBody, "", errors.New("video submit ok but no poll url")
|
||||||
@@ -729,8 +742,8 @@ func (c *Client) pollVideo(ctx context.Context, sess *tlsSession, token, pollURL
|
|||||||
req.Header = http.Header{
|
req.Header = http.Header{
|
||||||
"authorization": {"Bearer " + strings.TrimSpace(token)},
|
"authorization": {"Bearer " + strings.TrimSpace(token)},
|
||||||
"accept": {"*/*"},
|
"accept": {"*/*"},
|
||||||
"origin": {"https://firefly.adobe.com"},
|
"origin": {"https://new.express.adobe.com"},
|
||||||
"referer": {"https://firefly.adobe.com/"},
|
"referer": {"https://new.express.adobe.com/"},
|
||||||
"user-agent": {sess.fp.userAgent},
|
"user-agent": {sess.fp.userAgent},
|
||||||
http.HeaderOrderKey: {
|
http.HeaderOrderKey: {
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -949,7 +962,14 @@ func exchangeCookieWithTLSClient(ctx context.Context, sess *tlsSession, cookie s
|
|||||||
return nil, ErrAdobeCookieEmpty
|
return nil, ErrAdobeCookieEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
body := "client_id=" + clientID + "&guest_allowed=true&scope=" + strings.ReplaceAll(scopeValue, ",", "%2C")
|
// Prefer user_id (HAR behavior); fall back to guest_allowed for first-time login.
|
||||||
|
userID := extractUserIDFromCookie(cookie)
|
||||||
|
var body string
|
||||||
|
if userID != "" {
|
||||||
|
body = "client_id=" + clientID + "&scope=" + strings.ReplaceAll(scopeValue, ",", "%2C") + "&user_id=" + url.QueryEscape(userID)
|
||||||
|
} else {
|
||||||
|
body = "client_id=" + clientID + "&guest_allowed=true&scope=" + strings.ReplaceAll(scopeValue, ",", "%2C")
|
||||||
|
}
|
||||||
req, err := http.NewRequest(http.MethodPost, refreshURL, strings.NewReader(body))
|
req, err := http.NewRequest(http.MethodPost, refreshURL, strings.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -960,8 +980,8 @@ func exchangeCookieWithTLSClient(ctx context.Context, sess *tlsSession, cookie s
|
|||||||
"accept-language": {"zh-CN,zh;q=0.9"},
|
"accept-language": {"zh-CN,zh;q=0.9"},
|
||||||
"content-type": {"application/x-www-form-urlencoded;charset=UTF-8"},
|
"content-type": {"application/x-www-form-urlencoded;charset=UTF-8"},
|
||||||
"cookie": {cookie},
|
"cookie": {cookie},
|
||||||
"origin": {"https://firefly.adobe.com"},
|
"origin": {"https://new.express.adobe.com"},
|
||||||
"referer": {"https://firefly.adobe.com/"},
|
"referer": {"https://new.express.adobe.com/"},
|
||||||
"user-agent": {sess.fp.userAgent},
|
"user-agent": {sess.fp.userAgent},
|
||||||
http.HeaderOrderKey: {
|
http.HeaderOrderKey: {
|
||||||
"accept",
|
"accept",
|
||||||
@@ -1033,7 +1053,7 @@ func ExtractAccountID(token string) string {
|
|||||||
return userID
|
return userID
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeVideoPollURL(raw string) string {
|
func normalizePollURL(raw string) string {
|
||||||
if strings.TrimSpace(raw) == "" {
|
if strings.TrimSpace(raw) == "" {
|
||||||
return raw
|
return raw
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,6 +16,18 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// adobeUserIDPat matches Adobe IMS user IDs embedded in cookies (e.g.
|
||||||
|
// "4BDA81F069FC6DA40A495FAB@AdobeID").
|
||||||
|
var adobeUserIDPat = regexp.MustCompile(`[A-Fa-f0-9]{20,}@AdobeID`)
|
||||||
|
|
||||||
|
func extractUserIDFromCookie(cookie string) string {
|
||||||
|
decoded, err := url.QueryUnescape(cookie)
|
||||||
|
if err != nil {
|
||||||
|
decoded = cookie
|
||||||
|
}
|
||||||
|
return adobeUserIDPat.FindString(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
func stringValue(v any) string {
|
func stringValue(v any) string {
|
||||||
switch x := v.(type) {
|
switch x := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -82,12 +97,14 @@ func decodeJWTPayload(token string) map[string]any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildARPSessionID() string {
|
func buildARPSessionID() string {
|
||||||
// Every field is randomized per request: no embedded process pid or
|
// Matches adobe2api's format exactly:
|
||||||
// hardcoded constant suffix (those would make all requests from this
|
// base64({"sid":"<uuid>","ftr":"<hex16>_<ts_ms>_<pid>_dUAL43-mnts-ants-d4_31ck__tt"})
|
||||||
// install share a static feature — a cross-account correlation point).
|
// Two fields only (no "ark") — mirrors what a real browser session sends.
|
||||||
|
pid := os.Getpid()
|
||||||
|
ftr := randomHex(16) + "_" + strconv.FormatInt(time.Now().UnixMilli(), 10) + "_" + strconv.Itoa(pid) + "_dUAL43-mnts-ants-d4_31ck__tt"
|
||||||
raw := map[string]any{
|
raw := map[string]any{
|
||||||
"sid": uuid.NewString(),
|
"sid": uuid.NewString(),
|
||||||
"ftr": randomHex(16) + "_" + strconv.FormatInt(time.Now().UnixMilli(), 10) + "_" + strconv.Itoa(randomInt(1000, 999999)) + "_" + randomHex(8),
|
"ftr": ftr,
|
||||||
}
|
}
|
||||||
b, _ := json.Marshal(raw)
|
b, _ := json.Marshal(raw)
|
||||||
return base64.StdEncoding.EncodeToString(b)
|
return base64.StdEncoding.EncodeToString(b)
|
||||||
|
|||||||
Reference in New Issue
Block a user