Files
MeBox/internal/model/bot.go
T
shuk shuk 22b64d3d47 fix(organize): strip release tags/roman numerals/season markers; de-hardcode paths
feat(bot): button menu, capacity/open-reg quota, redemption codes, user mgmt,
account expiry + signin streak, device anti-sharing + inactivity cleanup,
one-click kick, self-service username/password

- Consolidate organize/rename defaults into Tools panel

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-06-07 09:54:14 +08:00

105 lines
4.1 KiB
Go

// Package model — Telegram Bot 相关数据模型:注册兑换码、签到记录、用户设备会话。
package model
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// RegistrationCodeKind 区分兑换码用途。
const (
// RegistrationCodeRegister 用于注册一个新账号(兑换后自动绑定 / 创建账号)。
RegistrationCodeRegister = "register"
// RegistrationCodeRenew 用于给已有账号续期(延长到期时间 DurationDays 天)。
RegistrationCodeRenew = "renew"
)
// RegistrationCode 是一次性兑换码。管理员生成后发给用户,用户通过 Bot 兑换:
// - register:创建并绑定一个新账号;兑换时按 DurationDays 设置账号有效期。
// - renew:给当前绑定账号延长 DurationDays 天有效期。
//
// 兑换成功后记录 UsedByUserID + UsedAt,之后不可再用。ExpiresAt 是兑换码本身
// 的有效期(过期后即使未使用也不能再兑换)。
type RegistrationCode struct {
Base
Code string `gorm:"uniqueIndex;size:32;not null" json:"code"`
Kind string `gorm:"size:16;not null;default:register" json:"kind"`
DurationDays int `gorm:"default:0" json:"duration_days"` // 账号有效期天数;0 表示永久
CreatedByID string `gorm:"size:36" json:"created_by_id,omitempty"`
UsedByUserID string `gorm:"index;size:36" json:"used_by_user_id,omitempty"`
UsedAt *time.Time `json:"used_at,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 兑换码本身的有效期
}
// BeforeCreate 生成 UUID。
func (c *RegistrationCode) BeforeCreate(_ *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.NewString()
}
return nil
}
// IsUsed 报告兑换码是否已被使用。
func (c *RegistrationCode) IsUsed() bool { return c.UsedAt != nil }
// IsExpired 报告兑换码自身是否过期(与账号有效期无关)。
func (c *RegistrationCode) IsExpired() bool {
return c.ExpiresAt != nil && time.Now().After(*c.ExpiresAt)
}
// SignIn 记录单个用户的连续签到天数(不挂钩积分)。
type SignIn struct {
Base
UserID string `gorm:"uniqueIndex;size:36;not null" json:"user_id"`
LastSignIn time.Time `json:"last_sign_in"`
StreakDays int `gorm:"default:0" json:"streak_days"` // 当前连续签到天数
TotalDays int `gorm:"default:0" json:"total_days"` // 累计签到天数
}
// BeforeCreate 生成 UUID。
func (s *SignIn) BeforeCreate(_ *gorm.DB) error {
if s.ID == "" {
s.ID = uuid.NewString()
}
return nil
}
// UserDevice 记录一个用户在某台设备/客户端上的会话指纹,用于设备管控:
// - 登录设备数:某用户名下 UserDevice 行数(近期活跃)。
// - 并发播放数:LastPlayAt 在并发窗口内的 UserDevice 行数。
// - 设备指纹:首次见到的 Fingerprint;后续同一 DeviceID 上报不同指纹则告警。
// - 观看时长:结合 PlaybackHistory 统计随机窗口内的观看时长。
type UserDevice struct {
Base
UserID string `gorm:"index;size:36;not null;uniqueIndex:uniq_user_device" json:"user_id"`
DeviceID string `gorm:"size:128;not null;uniqueIndex:uniq_user_device" json:"device_id"`
DeviceName string `gorm:"size:128" json:"device_name,omitempty"`
Client string `gorm:"size:128" json:"client,omitempty"`
Fingerprint string `gorm:"size:64" json:"fingerprint,omitempty"`
LastIP string `gorm:"size:64" json:"last_ip,omitempty"`
FirstSeenAt time.Time `json:"first_seen_at"`
LastSeenAt time.Time `gorm:"index" json:"last_seen_at"`
LastPlayAt *time.Time `gorm:"index" json:"last_play_at,omitempty"`
Warnings int `gorm:"default:0" json:"warnings"` // 指纹不匹配累计告警次数
Kicked bool `gorm:"default:false" json:"kicked"` // 被一键踢下线(强制重新登录)
}
// BeforeCreate 生成 UUID。
func (d *UserDevice) BeforeCreate(_ *gorm.DB) error {
if d.ID == "" {
d.ID = uuid.NewString()
}
return nil
}
// BotModels 返回 Bot 相关模型,供 AutoMigrate 使用。
func BotModels() []interface{} {
return []interface{}{
&RegistrationCode{},
&SignIn{},
&UserDevice{},
}
}