grok修复恢复时间
This commit is contained in:
@@ -142,7 +142,7 @@ func (c *Client) FetchCreditsBalance(ctx context.Context, token string) (map[str
|
|||||||
|
|
||||||
// GetGrokCreditsConfig field #1 is the credits USED this period (not remaining):
|
// GetGrokCreditsConfig field #1 is the credits USED this period (not remaining):
|
||||||
// an exhausted account reads 100, a fresh one reads ~0. Remaining = 100 - used.
|
// an exhausted account reads 100, a fresh one reads ~0. Remaining = 100 - used.
|
||||||
used, reset, ok := parseCreditsConfig(raw)
|
used, _, ok := parseCreditsConfig(raw)
|
||||||
if !ok {
|
if !ok {
|
||||||
return unknownBalance("unparsable credits config"), nil
|
return unknownBalance("unparsable credits config"), nil
|
||||||
}
|
}
|
||||||
@@ -154,11 +154,13 @@ func (c *Client) FetchCreditsBalance(ctx context.Context, token string) (map[str
|
|||||||
}
|
}
|
||||||
remaining := fullCredits - used
|
remaining := fullCredits - used
|
||||||
|
|
||||||
// 恢复时间: prefer the subscription's billing-period end (when the plan renews
|
// 恢复时间: taken solely from the subscription's billing-period end (when the
|
||||||
// and credits reset) over the credits-config timestamp. Free accounts have no
|
// plan renews and credits reset). The credits-config weekly reset timestamp is
|
||||||
// subscription, so this falls back to the credits-config reset above.
|
// intentionally NOT used as a fallback — an account with no active subscription
|
||||||
|
// has no recovery time.
|
||||||
|
reset := ""
|
||||||
sub, _ := c.FetchSubscription(ctx, token)
|
sub, _ := c.FetchSubscription(ctx, token)
|
||||||
if sub != nil && strings.TrimSpace(sub.BillingPeriodEnd) != "" {
|
if sub != nil {
|
||||||
reset = strings.TrimSpace(sub.BillingPeriodEnd)
|
reset = strings.TrimSpace(sub.BillingPeriodEnd)
|
||||||
}
|
}
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
@@ -273,6 +275,7 @@ func (c *Client) FetchSession(ctx context.Context, token string) (email, userID
|
|||||||
return "", "", fmt.Errorf("%w: session http %d", ErrTemporaryUpstream, resp.StatusCode)
|
return "", "", fmt.Errorf("%w: session http %d", ErrTemporaryUpstream, resp.StatusCode)
|
||||||
}
|
}
|
||||||
var body struct {
|
var body struct {
|
||||||
|
Status string `json:"status"`
|
||||||
Session struct {
|
Session struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
@@ -281,6 +284,10 @@ func (c *Client) FetchSession(ctx context.Context, token string) (email, userID
|
|||||||
if err := json.Unmarshal(raw, &body); err != nil {
|
if err := json.Unmarshal(raw, &body); err != nil {
|
||||||
return "", "", fmt.Errorf("%w: session non-json", ErrTemporaryUpstream)
|
return "", "", fmt.Errorf("%w: session non-json", ErrTemporaryUpstream)
|
||||||
}
|
}
|
||||||
|
// A dead sso cookie answers 200 {"status":"unauthenticated"} rather than 401.
|
||||||
|
if !strings.EqualFold(body.Status, "authenticated") {
|
||||||
|
return "", "", ErrAuth
|
||||||
|
}
|
||||||
return strings.TrimSpace(body.Session.Email), strings.TrimSpace(body.Session.UserID), nil
|
return strings.TrimSpace(body.Session.Email), strings.TrimSpace(body.Session.UserID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -867,6 +867,9 @@ func (s *TokenService) ImportGrokToken(ctx context.Context, ssoToken, tokenID st
|
|||||||
email := ""
|
email := ""
|
||||||
if s.grok != nil {
|
if s.grok != nil {
|
||||||
s.applyProxy(ctx)
|
s.applyProxy(ctx)
|
||||||
|
// Best-effort email resolution. A dead/blocked probe must NOT block import:
|
||||||
|
// land the account and let the async checkPendingGrok disable it if the
|
||||||
|
// session is truly dead. Empty email just falls back to the session id.
|
||||||
if e, _, ferr := s.grok.FetchSession(ctx, ssoToken); ferr == nil {
|
if e, _, ferr := s.grok.FetchSession(ctx, ssoToken); ferr == nil {
|
||||||
email = e
|
email = e
|
||||||
}
|
}
|
||||||
@@ -923,6 +926,17 @@ func (s *TokenService) checkPendingGrok(tokenID, ssoToken string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.applyProxy(ctx)
|
s.applyProxy(ctx)
|
||||||
|
// Validate the session first: a dead sso answers 200 {"status":"unauthenticated"},
|
||||||
|
// which FetchSession now maps to ErrAuth → disable. Also backfill the email if
|
||||||
|
// import couldn't resolve it (empty account_email currently shows the session id).
|
||||||
|
if email, _, serr := s.grok.FetchSession(ctx, ssoToken); serr != nil {
|
||||||
|
if errors.Is(serr, grok.ErrAuth) {
|
||||||
|
s.finishPending(ctx, "grok", tokenID, "disabled", true, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if strings.TrimSpace(email) != "" {
|
||||||
|
_, _ = s.tokens.Update(ctx, "grok", tokenID, map[string]any{"account_email": strings.TrimSpace(email)})
|
||||||
|
}
|
||||||
data, err := s.grok.FetchCreditsBalance(ctx, ssoToken)
|
data, err := s.grok.FetchCreditsBalance(ctx, ssoToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, grok.ErrAuth) {
|
if errors.Is(err, grok.ErrAuth) {
|
||||||
|
|||||||
Reference in New Issue
Block a user