核心功能: - 实现基于 Telegram Inline Button 交互的后台面板与用户端 - 支持通过账密登录和 RT (Refresh Token) 方式添加 ChatGPT Team 账号 - 支持管理、拉取和删除待处理邀请,支持一键清空多余邀请 - 支持按剩余容量自动生成邀请兑换码,支持分页查看与一键清空未使用兑换码 - 随机邀请功能:成功拉人后自动核销兑换码 - 定时检测 Token 状态,实现自动续订/刷新并拦截封禁账号 (处理 401/402 错误) 系统与配置: - 使用 PostgreSQL 数据库管理账号、邀请和兑换记录 - 支持在端内动态添加、移除管理员 - 完善 Docker 部署配置与 .gitignore 规则
140 lines
3.8 KiB
Go
140 lines
3.8 KiB
Go
package redeem
|
||
|
||
import (
|
||
"fmt"
|
||
"math/rand"
|
||
"regexp"
|
||
"strings"
|
||
|
||
"go-helper/internal/chatgpt"
|
||
"go-helper/internal/database"
|
||
"go-helper/internal/model"
|
||
)
|
||
|
||
var (
|
||
emailRegex = regexp.MustCompile(`^[^\s@]+@[^\s@]+\.[^\s@]+$`)
|
||
codeRegex = regexp.MustCompile(`^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$`)
|
||
codeChars = []byte("ABCDEFGHJKLMNPQRSTUVWXYZ23456789") // exclude confusable chars
|
||
)
|
||
|
||
// RedeemResult contains information about a successful redemption.
|
||
type RedeemResult struct {
|
||
AccountEmail string
|
||
InviteOK bool
|
||
Message string
|
||
}
|
||
|
||
// Redeem validates the code, finds an available account, and sends an invite.
|
||
func Redeem(db *database.DB, client *chatgpt.Client, code, email string, capacity int) (*RedeemResult, error) {
|
||
email = strings.TrimSpace(strings.ToLower(email))
|
||
code = strings.TrimSpace(strings.ToUpper(code))
|
||
|
||
if email == "" {
|
||
return nil, fmt.Errorf("请输入邮箱地址")
|
||
}
|
||
if !emailRegex.MatchString(email) {
|
||
return nil, fmt.Errorf("邮箱格式不正确")
|
||
}
|
||
if code == "" {
|
||
return nil, fmt.Errorf("请输入兑换码")
|
||
}
|
||
if !codeRegex.MatchString(code) {
|
||
return nil, fmt.Errorf("兑换码格式不正确(格式:XXXX-XXXX-XXXX)")
|
||
}
|
||
|
||
// 1. Look up the code.
|
||
rc, err := db.GetCodeByCode(code)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("兑换码不存在或已失效")
|
||
}
|
||
if rc.IsRedeemed {
|
||
return nil, fmt.Errorf("该兑换码已被使用")
|
||
}
|
||
|
||
// 2. Find a usable account.
|
||
var account *model.GptAccount
|
||
|
||
if rc.AccountEmail != "" {
|
||
// Code is bound to a specific account.
|
||
accounts, err := db.GetOpenAccounts(capacity + 100) // get all open
|
||
if err != nil {
|
||
return nil, fmt.Errorf("查找账号失败: %v", err)
|
||
}
|
||
for i := range accounts {
|
||
if strings.EqualFold(accounts[i].Email, rc.AccountEmail) {
|
||
if accounts[i].UserCount+accounts[i].InviteCount < capacity {
|
||
account = &accounts[i]
|
||
}
|
||
break
|
||
}
|
||
}
|
||
if account == nil {
|
||
return nil, fmt.Errorf("该兑换码绑定的账号不可用或已满")
|
||
}
|
||
} else {
|
||
// Find any open account with capacity.
|
||
accounts, err := db.GetOpenAccounts(capacity)
|
||
if err != nil || len(accounts) == 0 {
|
||
return nil, fmt.Errorf("暂无可用账号,请稍后再试")
|
||
}
|
||
account = &accounts[0]
|
||
}
|
||
|
||
// 3. Send invite.
|
||
inviteErr := client.InviteUser(email, account)
|
||
|
||
// 4. Mark code as redeemed regardless of invite outcome.
|
||
if err := db.RedeemCode(rc.ID, email); err != nil {
|
||
return nil, fmt.Errorf("更新兑换码状态失败: %v", err)
|
||
}
|
||
|
||
// 5. Sync counts.
|
||
syncCounts(db, client, account)
|
||
|
||
result := &RedeemResult{AccountEmail: account.Email}
|
||
if inviteErr != nil {
|
||
result.InviteOK = false
|
||
result.Message = fmt.Sprintf("兑换成功,但邀请发送失败: %v\n请联系管理员手动添加", inviteErr)
|
||
} else {
|
||
result.InviteOK = true
|
||
result.Message = "兑换成功!邀请邮件已发送到您的邮箱,请查收。"
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// GenerateCode creates a random code in XXXX-XXXX-XXXX format.
|
||
func GenerateCode() string {
|
||
parts := make([]byte, 12)
|
||
for i := range parts {
|
||
parts[i] = codeChars[rand.Intn(len(codeChars))]
|
||
}
|
||
return fmt.Sprintf("%s-%s-%s", string(parts[0:4]), string(parts[4:8]), string(parts[8:12]))
|
||
}
|
||
|
||
// GenerateCodes creates n unique codes.
|
||
func GenerateCodes(n int) []string {
|
||
seen := make(map[string]bool)
|
||
var codes []string
|
||
for len(codes) < n {
|
||
c := GenerateCode()
|
||
if !seen[c] {
|
||
seen[c] = true
|
||
codes = append(codes, c)
|
||
}
|
||
}
|
||
return codes
|
||
}
|
||
|
||
// syncCounts updates user_count and invite_count from the ChatGPT API.
|
||
func syncCounts(db *database.DB, client *chatgpt.Client, account *model.GptAccount) {
|
||
userTotal, _, err := client.GetUsers(account)
|
||
if err != nil {
|
||
return
|
||
}
|
||
inviteTotal, _, err := client.GetInvites(account)
|
||
if err != nil {
|
||
return
|
||
}
|
||
_ = db.UpdateAccountCounts(account.ID, userTotal, inviteTotal)
|
||
}
|