核心功能: - 实现基于 Telegram Inline Button 交互的后台面板与用户端 - 支持通过账密登录和 RT (Refresh Token) 方式添加 ChatGPT Team 账号 - 支持管理、拉取和删除待处理邀请,支持一键清空多余邀请 - 支持按剩余容量自动生成邀请兑换码,支持分页查看与一键清空未使用兑换码 - 随机邀请功能:成功拉人后自动核销兑换码 - 定时检测 Token 状态,实现自动续订/刷新并拦截封禁账号 (处理 401/402 错误) 系统与配置: - 使用 PostgreSQL 数据库管理账号、邀请和兑换记录 - 支持在端内动态添加、移除管理员 - 完善 Docker 部署配置与 .gitignore 规则
97 lines
2.8 KiB
Go
97 lines
2.8 KiB
Go
package scheduler
|
|
|
|
import (
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
"go-helper/internal/chatgpt"
|
|
"go-helper/internal/database"
|
|
)
|
|
|
|
// StartTokenChecker runs a periodic loop that refreshes access tokens
|
|
// for all accounts that have a refresh token.
|
|
func StartTokenChecker(db *database.DB, client *chatgpt.Client, intervalMinutes int) {
|
|
if intervalMinutes <= 0 {
|
|
intervalMinutes = 30
|
|
}
|
|
interval := time.Duration(intervalMinutes) * time.Minute
|
|
log.Printf("[Scheduler] Token 定时检测已启动,间隔 %d 分钟", intervalMinutes)
|
|
|
|
go func() {
|
|
// Run once immediately at startup.
|
|
checkAndRefreshAll(db, client)
|
|
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
checkAndRefreshAll(db, client)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func checkAndRefreshAll(db *database.DB, client *chatgpt.Client) {
|
|
accounts, err := db.GetAccountsWithRT()
|
|
if err != nil {
|
|
log.Printf("[Scheduler] 获取账号列表失败: %v", err)
|
|
return
|
|
}
|
|
|
|
if len(accounts) == 0 {
|
|
return
|
|
}
|
|
|
|
log.Printf("[Scheduler] 开始检查 %d 个账号的 Token 状态", len(accounts))
|
|
|
|
refreshed, failed, banned := 0, 0, 0
|
|
for _, acc := range accounts {
|
|
result, err := client.RefreshAccessToken(acc.RefreshToken)
|
|
if err != nil {
|
|
log.Printf("[Scheduler] 刷新失败 [ID=%d %s]: %v", acc.ID, acc.Email, err)
|
|
failed++
|
|
|
|
// Check if the error indicates token is completely invalid.
|
|
errMsg := strings.ToLower(err.Error())
|
|
if strings.Contains(errMsg, "invalid_grant") ||
|
|
strings.Contains(errMsg, "token has been revoked") ||
|
|
strings.Contains(errMsg, "unauthorized") {
|
|
log.Printf("[Scheduler] RT 无效,标记封号 [ID=%d %s]", acc.ID, acc.Email)
|
|
_ = db.BanAccount(acc.ID)
|
|
banned++
|
|
}
|
|
continue
|
|
}
|
|
|
|
if err := db.UpdateAccountTokens(acc.ID, result.AccessToken, result.RefreshToken); err != nil {
|
|
log.Printf("[Scheduler] 更新 Token 失败 [ID=%d]: %v", acc.ID, err)
|
|
failed++
|
|
continue
|
|
}
|
|
|
|
// Also try to fetch account info to update subscription expiry.
|
|
infos, err := client.FetchAccountInfo(result.AccessToken)
|
|
if err != nil {
|
|
// Token works but account info fetch failed — might be account_deactivated.
|
|
errMsg := strings.ToLower(err.Error())
|
|
if strings.Contains(errMsg, "account_deactivated") || strings.Contains(errMsg, "已停用") {
|
|
log.Printf("[Scheduler] 账号已停用,标记封号 [ID=%d %s]", acc.ID, acc.Email)
|
|
_ = db.BanAccount(acc.ID)
|
|
banned++
|
|
continue
|
|
}
|
|
} else if len(infos) > 0 {
|
|
// Update expire_at from subscription info.
|
|
for _, info := range infos {
|
|
if info.AccountID == acc.ChatgptAccountID && info.ExpiresAt != "" {
|
|
_ = db.UpdateAccountInfo(acc.ID, acc.ChatgptAccountID, info.ExpiresAt)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
refreshed++
|
|
}
|
|
|
|
log.Printf("[Scheduler] 检查完成: 刷新成功 %d, 失败 %d, 封号 %d", refreshed, failed, banned)
|
|
}
|