mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 04:26:38 +08:00
初始化
初始化项目
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
// Package model 定义下载客户端配置数据模型。
|
||||
package model
|
||||
|
||||
// DownloadClient 下载客户端配置(qBittorrent / Transmission / Aria2)。
|
||||
// 支持多客户端并行运行,一个标记为默认客户端。
|
||||
type DownloadClient struct {
|
||||
Base
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Type string `gorm:"size:32;not null" json:"type"` // qbittorrent / transmission / aria2
|
||||
Host string `gorm:"size:512;not null" json:"host"` // http://host:port
|
||||
Username string `gorm:"size:256" json:"username"`
|
||||
Password string `gorm:"size:1024" json:"-"` // AES加密存储
|
||||
IsDefault bool `gorm:"default:false" json:"is_default"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
Extra string `gorm:"type:text" json:"-"` // JSON配置, AES加密
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// DownloadTask 是待处理(或已完成)的 torrent / HTTP 下载。
|
||||
type DownloadTask struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36" json:"user_id"`
|
||||
SubscriptionID string `gorm:"index;size:36" json:"subscription_id,omitempty"`
|
||||
DownloadClientID string `gorm:"index;size:36" json:"download_client_id,omitempty"`
|
||||
ExternalID string `gorm:"index;size:128" json:"external_id,omitempty"`
|
||||
Source string `gorm:"size:32;not null" json:"source"` // qbittorrent / transmission / aria2 / http
|
||||
URL string `gorm:"size:2048;not null" json:"-"`
|
||||
Title string `gorm:"size:512" json:"title,omitempty"`
|
||||
PosterURL string `gorm:"size:2048" json:"poster_url,omitempty"`
|
||||
BackdropURL string `gorm:"size:2048" json:"backdrop_url,omitempty"`
|
||||
Overview string `gorm:"type:text" json:"overview,omitempty"`
|
||||
SavePath string `gorm:"size:1024" json:"save_path"`
|
||||
MediaType string `gorm:"size:16" json:"media_type,omitempty"`
|
||||
MediaCategory string `gorm:"size:128" json:"media_category,omitempty"`
|
||||
// 媒体展示元数据(用于 Telegram 富通知模板等):原始片名/语言/年份/评分/类型。
|
||||
OriginalName string `gorm:"size:512" json:"original_name,omitempty"`
|
||||
OriginalLanguage string `gorm:"size:32" json:"original_language,omitempty"`
|
||||
Year int `json:"year,omitempty"`
|
||||
Rating float32 `json:"rating,omitempty"`
|
||||
Genres string `gorm:"size:255" json:"genres,omitempty"` // comma separated
|
||||
Status string `gorm:"size:32;default:queued" json:"status"`
|
||||
Progress float32 `json:"progress"`
|
||||
|
||||
// AllowExistingLibrary is true for subscription wash/upgrade tasks that are
|
||||
// allowed to replace an existing library item after download completion.
|
||||
AllowExistingLibrary bool `gorm:"default:false" json:"allow_existing_library,omitempty"`
|
||||
}
|
||||
|
||||
// Subscription 是自动化规则,轮询 RSS 源并将匹配种子排队到配置的下载客户端。
|
||||
type Subscription struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36" json:"user_id"`
|
||||
IdentityKey string `gorm:"size:64" json:"-"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
FeedURL string `gorm:"size:2048;not null" json:"feed_url"`
|
||||
Filter string `gorm:"size:512" json:"filter"`
|
||||
MediaType string `gorm:"size:16" json:"media_type,omitempty"`
|
||||
MediaCategory string `gorm:"size:128" json:"media_category,omitempty"`
|
||||
SavePath string `gorm:"size:1024" json:"save_path,omitempty"`
|
||||
SearchMode string `gorm:"size:16;default:keyword" json:"search_mode,omitempty"` // keyword / imdb
|
||||
IMDBID string `gorm:"size:32" json:"imdb_id,omitempty"`
|
||||
Source string `gorm:"size:32" json:"source,omitempty"`
|
||||
PosterURL string `gorm:"size:2048" json:"poster_url,omitempty"`
|
||||
BackdropURL string `gorm:"size:2048" json:"backdrop_url,omitempty"`
|
||||
Overview string `gorm:"type:text" json:"overview,omitempty"`
|
||||
// 媒体展示元数据(用于 Telegram 富通知模板等):原始片名/语言/年份/评分/类型。
|
||||
OriginalName string `gorm:"size:512" json:"original_name,omitempty"`
|
||||
OriginalLanguage string `gorm:"size:32" json:"original_language,omitempty"`
|
||||
Year int `json:"year,omitempty"`
|
||||
Rating float32 `json:"rating,omitempty"`
|
||||
Genres string `gorm:"size:255" json:"genres,omitempty"` // comma separated
|
||||
Resolution string `gorm:"size:32" json:"resolution,omitempty"` // 2160p / 1080p / 720p / best
|
||||
Quality string `gorm:"size:64" json:"quality,omitempty"` // remux / bluray / web-dl / hdtv
|
||||
Effects string `gorm:"size:128" json:"effects,omitempty"` // hdr,dolby-vision,atmos
|
||||
ReleaseGroups string `gorm:"size:255" json:"release_groups,omitempty"` // comma separated
|
||||
ExcludeWords string `gorm:"size:255" json:"exclude_words,omitempty"` // comma separated
|
||||
MinSeeders int `gorm:"default:0" json:"min_seeders,omitempty"`
|
||||
MaxSeeders int `gorm:"default:0" json:"max_seeders,omitempty"`
|
||||
MinSizeGB float64 `gorm:"default:0" json:"min_size_gb,omitempty"`
|
||||
MaxSizeGB float64 `gorm:"default:0" json:"max_size_gb,omitempty"`
|
||||
FreeOnly bool `gorm:"default:false" json:"free_only,omitempty"`
|
||||
WashEnabled bool `gorm:"default:false" json:"wash_enabled"`
|
||||
WashPriority string `gorm:"size:32" json:"wash_priority,omitempty"` // balanced / resolution / quality / effects / seeders
|
||||
TotalEpisodes int `gorm:"default:0" json:"total_episodes,omitempty"`
|
||||
Priority int `gorm:"default:50" json:"priority,omitempty"` // lower is earlier when schedulers sort later
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
LastRunAt *time.Time `json:"last_run_at,omitempty"`
|
||||
ArchivedAt *time.Time `gorm:"index" json:"archived_at,omitempty"`
|
||||
ArchiveReason string `gorm:"size:255" json:"archive_reason,omitempty"`
|
||||
|
||||
DownloadedEpisodes int `gorm:"-" json:"downloaded_episodes,omitempty"`
|
||||
LocalMediaCount int `gorm:"-" json:"local_media_count,omitempty"`
|
||||
MissingEpisodes []int `gorm:"-" json:"missing_episodes,omitempty"`
|
||||
InLibrary bool `gorm:"-" json:"in_library"`
|
||||
}
|
||||
@@ -41,23 +41,13 @@ func AllModels() []interface{} {
|
||||
&Favorite{},
|
||||
&Playlist{},
|
||||
&PlaylistItem{},
|
||||
&DownloadTask{},
|
||||
&Subscription{},
|
||||
&Setting{},
|
||||
&Site{},
|
||||
&AccessLog{},
|
||||
&APIConfig{},
|
||||
&UserPermission{},
|
||||
&RefreshToken{},
|
||||
&ApiConfig{},
|
||||
&DownloadClient{},
|
||||
&NotifyChannel{},
|
||||
&TelegramBinding{},
|
||||
&STRMRecord{},
|
||||
&PlayProfile{},
|
||||
&StorageConfig{},
|
||||
&AssistantSession{},
|
||||
&AssistantMessage{},
|
||||
&RegistrationCode{},
|
||||
&SignIn{},
|
||||
&UserDevice{},
|
||||
|
||||
@@ -19,7 +19,6 @@ func TestMediaReferenceFieldsAllowVirtualEmbyIDs(t *testing.T) {
|
||||
{name: "playback_history_media_id", model: &PlaybackHistory{}, fieldName: "MediaID"},
|
||||
{name: "favorite_media_id", model: &Favorite{}, fieldName: "MediaID"},
|
||||
{name: "playlist_item_media_id", model: &PlaylistItem{}, fieldName: "MediaID"},
|
||||
{name: "strm_record_media_id", model: &STRMRecord{}, fieldName: "MediaID"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// Package model 定义通知渠道配置数据模型。
|
||||
package model
|
||||
|
||||
// NotifyChannel 通知渠道配置。
|
||||
// 支持多种通知渠道:telegram / wechat / bark / webhook / email。
|
||||
// Events 字段存储 JSON array,表示该渠道订阅的事件类型。
|
||||
type NotifyChannel struct {
|
||||
Base
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Type string `gorm:"size:32;not null" json:"type"` // telegram / wechat / bark / webhook / email
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
Config string `gorm:"type:text" json:"-"` // JSON配置, AES加密
|
||||
Events string `gorm:"type:text" json:"events"` // 订阅的事件列表, JSON array
|
||||
}
|
||||
@@ -28,9 +28,8 @@ type UserPermission struct {
|
||||
CanRescrape bool `gorm:"default:false" json:"can_rescrape"`
|
||||
CanUseAI bool `gorm:"default:false" json:"can_use_ai"`
|
||||
CanCaptureFrames bool `gorm:"default:false" json:"can_capture_frames"`
|
||||
CanManageDownloads bool `gorm:"default:false" json:"can_manage_downloads"`
|
||||
CanViewDiscover bool `gorm:"default:false" json:"can_view_discover"`
|
||||
CanManageSubscriptions bool `gorm:"default:false" json:"can_manage_subscriptions"`
|
||||
CanManageDownloads bool `gorm:"default:false" json:"can_manage_downloads"`
|
||||
CanManageSubscriptions bool `gorm:"default:false" json:"can_manage_subscriptions"`
|
||||
CanManageSites bool `gorm:"default:false" json:"can_manage_sites"`
|
||||
CanUseAIAssistant bool `gorm:"default:false" json:"can_use_ai_assistant"`
|
||||
CanManageUsers bool `gorm:"default:false" json:"can_manage_users"`
|
||||
@@ -65,9 +64,8 @@ func NewDefaultPermission(userID string) *UserPermission {
|
||||
CanRescrape: false,
|
||||
CanUseAI: false,
|
||||
CanCaptureFrames: false,
|
||||
CanManageDownloads: false,
|
||||
CanViewDiscover: false,
|
||||
CanManageSubscriptions: false,
|
||||
CanManageDownloads: false,
|
||||
CanManageSubscriptions: false,
|
||||
CanManageSites: false,
|
||||
CanUseAIAssistant: false,
|
||||
CanManageUsers: false,
|
||||
@@ -90,9 +88,8 @@ func (p *UserPermission) PermissionMap() map[string]bool {
|
||||
"can_rescrape": p.CanRescrape,
|
||||
"can_use_ai": p.CanUseAI,
|
||||
"can_capture_frames": p.CanCaptureFrames,
|
||||
"can_manage_downloads": p.CanManageDownloads,
|
||||
"can_view_discover": p.CanViewDiscover,
|
||||
"can_manage_subscriptions": p.CanManageSubscriptions,
|
||||
"can_manage_downloads": p.CanManageDownloads,
|
||||
"can_manage_subscriptions": p.CanManageSubscriptions,
|
||||
"can_manage_sites": p.CanManageSites,
|
||||
"can_use_ai_assistant": p.CanUseAIAssistant,
|
||||
"can_manage_users": p.CanManageUsers,
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// Package model — PT 站点配置数据模型。
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Site PT 站点配置。
|
||||
type Site struct {
|
||||
Base
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Type string `gorm:"size:32;not null" json:"type"` // nexusphp / gazelle / unit3d / mteam / yemapt / discuz / custom_rss
|
||||
URL string `gorm:"size:512;not null" json:"url"`
|
||||
AuthType string `gorm:"size:32;not null" json:"auth_type"` // cookie / api_key / auth_header
|
||||
Cookie string `gorm:"type:text" json:"-"` // AES 加密
|
||||
APIKey string `gorm:"type:text" json:"-"` // AES 加密
|
||||
AuthHeader string `gorm:"type:text" json:"-"` // AES 加密
|
||||
|
||||
// ── 高级设置 ──────────────────────────────────────────────────────────
|
||||
UserAgent string `gorm:"size:500" json:"user_agent"` // 自定义 User-Agent
|
||||
RSSURL string `gorm:"size:1000" json:"rss_url"` // RSS 订阅地址
|
||||
Timeout int `gorm:"default:15" json:"timeout"` // 请求超时(秒), 0=不限制
|
||||
Priority int `gorm:"default:50" json:"priority"` // 优先级, 越小越优先
|
||||
UseProxy bool `gorm:"default:false" json:"use_proxy"` // 是否使用代理
|
||||
RateLimit bool `gorm:"default:false" json:"rate_limit"` // 是否限制访问频率
|
||||
BrowserEmulation bool `gorm:"default:false" json:"browser_emulation"` // 浏览器仿真(防爬)
|
||||
|
||||
// ── 状态与统计 ────────────────────────────────────────────────────────
|
||||
LoginStatus string `gorm:"size:20;default:unknown" json:"login_status"` // unknown / ok / fail
|
||||
UploadBytes int64 `gorm:"default:0" json:"upload_bytes"` // 上传字节统计
|
||||
DownloadBytes int64 `gorm:"default:0" json:"download_bytes"` // 下载字节统计
|
||||
|
||||
// ── 关联下载器 ────────────────────────────────────────────────────────
|
||||
Downloader string `gorm:"size:50" json:"downloader"` // qbittorrent / transmission / aria2
|
||||
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
IsDefault bool `gorm:"default:false" json:"is_default"`
|
||||
Extra string `gorm:"type:text" json:"-"` // JSON 扩展配置, AES 加密
|
||||
LastError string `gorm:"size:1024" json:"last_error"`
|
||||
LastCheckAt *time.Time `json:"last_check_at"`
|
||||
}
|
||||
|
||||
// SiteType 返回支持的站点类型列表。
|
||||
func SiteTypes() []string {
|
||||
return []string{"nexusphp", "gazelle", "unit3d", "mteam", "yemapt", "discuz", "custom_rss"}
|
||||
}
|
||||
|
||||
// AuthTypes 返回支持的认证方式列表。
|
||||
func AuthTypes() []string {
|
||||
return []string{"cookie", "api_key", "auth_header"}
|
||||
}
|
||||
|
||||
// DefaultUserAgent 是默认浏览器 User-Agent(用于 HTTP 请求头)。
|
||||
const DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
||||
|
||||
// SiteTypes 返回支持的站点类型列表(用于前端下拉)。
|
||||
// 注意:此函数供 API 返回类型列表使用,不在此处添加新类型。
|
||||
@@ -14,23 +14,3 @@ type StorageConfig struct {
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
LastError string `gorm:"size:512" json:"last_error,omitempty"`
|
||||
}
|
||||
|
||||
// AssistantSession groups a multi-turn chat with the AI assistant.
|
||||
type AssistantSession struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36;not null" json:"user_id"`
|
||||
Title string `gorm:"size:255" json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// AssistantMessage is one entry in an AssistantSession transcript.
|
||||
//
|
||||
// Role is "user" | "assistant" | "system". The optional OperationID
|
||||
// links a message to an action the assistant proposed (so the UI can
|
||||
// offer Undo).
|
||||
type AssistantMessage struct {
|
||||
Base
|
||||
SessionID string `gorm:"index;size:36;not null" json:"session_id"`
|
||||
Role string `gorm:"size:16;not null" json:"role"`
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
OperationID string `gorm:"size:36" json:"operation_id,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// Package model — STRM 文件记录数据模型。
|
||||
package model
|
||||
|
||||
// STRMRecord STRM 文件记录。
|
||||
// 外部存储以"文件"形式入库,URL 指向实际资源。
|
||||
type STRMRecord struct {
|
||||
Base
|
||||
Title string `gorm:"size:512;not null;index" json:"title"`
|
||||
URL string `gorm:"size:2048;not null" json:"url"` // STRM 文件指向的 URL
|
||||
FilePath string `gorm:"size:1024;not null" json:"file_path"` // 本地 STRM 文件路径
|
||||
Protocol string `gorm:"size:32;not null" json:"protocol"` // webdav / alist / s3 / http / https
|
||||
FileSize int64 `json:"file_size"`
|
||||
MediaID string `gorm:"size:128;index" json:"media_id"` // 关联媒体 ID
|
||||
MediaType string `gorm:"size:16" json:"media_type"` // movie / series
|
||||
SeasonNum int `json:"season_num"`
|
||||
EpisodeNum int `json:"episode_num"`
|
||||
}
|
||||
|
||||
// AllowedSTRMProtocols 协议白名单。
|
||||
var AllowedSTRMProtocols = []string{
|
||||
"webdav", "davs",
|
||||
"alist", "alists",
|
||||
"openlist", "openlists",
|
||||
"s3",
|
||||
"http", "https",
|
||||
}
|
||||
|
||||
// IsAllowedProtocol 检查协议是否在白名单中。
|
||||
func IsAllowedProtocol(protocol string) bool {
|
||||
for _, p := range AllowedSTRMProtocols {
|
||||
if p == protocol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type subscriptionIdentityPayload struct {
|
||||
Name string `json:"name"`
|
||||
FeedURL string `json:"feed_url"`
|
||||
Filter string `json:"filter"`
|
||||
MediaType string `json:"media_type"`
|
||||
MediaCategory string `json:"media_category"`
|
||||
SavePath string `json:"save_path"`
|
||||
SearchMode string `json:"search_mode"`
|
||||
IMDBID string `json:"imdb_id"`
|
||||
Resolution string `json:"resolution"`
|
||||
Quality string `json:"quality"`
|
||||
Effects string `json:"effects"`
|
||||
ReleaseGroups string `json:"release_groups"`
|
||||
ExcludeWords string `json:"exclude_words"`
|
||||
MinSeeders int `json:"min_seeders"`
|
||||
MaxSeeders int `json:"max_seeders"`
|
||||
MinSizeGB float64 `json:"min_size_gb"`
|
||||
MaxSizeGB float64 `json:"max_size_gb"`
|
||||
FreeOnly bool `json:"free_only"`
|
||||
WashEnabled bool `json:"wash_enabled"`
|
||||
WashPriority string `json:"wash_priority"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
// SubscriptionIdentityKey identifies one functional subscription rule. It
|
||||
// intentionally excludes display metadata and runtime/archive state because
|
||||
// those fields are backfilled or changed automatically after creation.
|
||||
func SubscriptionIdentityKey(sub *Subscription) string {
|
||||
if sub == nil {
|
||||
return ""
|
||||
}
|
||||
payload := subscriptionIdentityPayload{
|
||||
Name: subscriptionIdentityFold(sub.Name),
|
||||
FeedURL: strings.TrimSpace(sub.FeedURL),
|
||||
Filter: strings.TrimSpace(sub.Filter),
|
||||
MediaType: subscriptionIdentityFold(sub.MediaType),
|
||||
MediaCategory: subscriptionIdentityFold(sub.MediaCategory),
|
||||
SavePath: strings.TrimSpace(sub.SavePath),
|
||||
SearchMode: subscriptionIdentityFold(sub.SearchMode),
|
||||
IMDBID: subscriptionIdentityFold(sub.IMDBID),
|
||||
Resolution: subscriptionIdentityFold(sub.Resolution),
|
||||
Quality: subscriptionIdentityFold(sub.Quality),
|
||||
Effects: subscriptionIdentityList(sub.Effects),
|
||||
ReleaseGroups: subscriptionIdentityList(sub.ReleaseGroups),
|
||||
ExcludeWords: subscriptionIdentityList(sub.ExcludeWords),
|
||||
MinSeeders: sub.MinSeeders,
|
||||
MaxSeeders: sub.MaxSeeders,
|
||||
MinSizeGB: sub.MinSizeGB,
|
||||
MaxSizeGB: sub.MaxSizeGB,
|
||||
FreeOnly: sub.FreeOnly,
|
||||
WashEnabled: sub.WashEnabled,
|
||||
WashPriority: subscriptionIdentityFold(sub.WashPriority),
|
||||
Priority: sub.Priority,
|
||||
}
|
||||
raw, _ := json.Marshal(payload)
|
||||
sum := sha256.Sum256(raw)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func RefreshSubscriptionIdentity(sub *Subscription) string {
|
||||
key := SubscriptionIdentityKey(sub)
|
||||
if sub != nil {
|
||||
sub.IdentityKey = key
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func subscriptionIdentityFold(value string) string {
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
func subscriptionIdentityList(value string) string {
|
||||
parts := strings.Split(value, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if part = subscriptionIdentityFold(part); part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package model
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSubscriptionIdentityKeyNormalizesEquivalentRules(t *testing.T) {
|
||||
base := Subscription{
|
||||
Name: " Example Show ",
|
||||
FeedURL: "site-search://search?keyword=Example",
|
||||
Filter: " Example.*Show ",
|
||||
MediaType: "TV",
|
||||
MediaCategory: " 欧美剧 ",
|
||||
SearchMode: "KEYWORD",
|
||||
Resolution: "1080P",
|
||||
Effects: " HDR, Atmos ",
|
||||
ExcludeWords: " CAM, TS ",
|
||||
WashPriority: "Balanced",
|
||||
Priority: 50,
|
||||
}
|
||||
equivalent := base
|
||||
equivalent.Name = "example show"
|
||||
equivalent.MediaType = "tv"
|
||||
equivalent.MediaCategory = "欧美剧"
|
||||
equivalent.SearchMode = "keyword"
|
||||
equivalent.Resolution = "1080p"
|
||||
equivalent.Effects = "hdr,atmos"
|
||||
equivalent.ExcludeWords = "cam,ts"
|
||||
equivalent.WashPriority = "balanced"
|
||||
if got, want := SubscriptionIdentityKey(&equivalent), SubscriptionIdentityKey(&base); got != want {
|
||||
t.Fatalf("equivalent keys differ: %s != %s", got, want)
|
||||
}
|
||||
|
||||
differentRule := base
|
||||
differentRule.Resolution = "2160p"
|
||||
if SubscriptionIdentityKey(&differentRule) == SubscriptionIdentityKey(&base) {
|
||||
t.Fatal("different resolution should produce a different identity")
|
||||
}
|
||||
|
||||
displayOnly := base
|
||||
displayOnly.PosterURL = "https://example.test/poster.jpg"
|
||||
displayOnly.Overview = "new overview"
|
||||
displayOnly.TotalEpisodes = 24
|
||||
if SubscriptionIdentityKey(&displayOnly) != SubscriptionIdentityKey(&base) {
|
||||
t.Fatal("display/runtime metadata should not change identity")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package model
|
||||
|
||||
// TelegramBinding links a Telegram account to a local MediaStationGo user.
|
||||
// The binding is password-verified when /start is used, then reused for
|
||||
// low-risk self-service actions such as toggling adult-library visibility.
|
||||
type TelegramBinding struct {
|
||||
Base
|
||||
TelegramUserID int64 `gorm:"uniqueIndex;not null" json:"telegram_user_id"`
|
||||
TelegramName string `gorm:"size:128" json:"telegram_name,omitempty"`
|
||||
ChatID int64 `gorm:"index" json:"chat_id"`
|
||||
UserID string `gorm:"index;size:36;not null" json:"user_id"`
|
||||
}
|
||||
Reference in New Issue
Block a user