mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 20:16:36 +08:00
feat: merge conflict resolution, site management, UI fixes
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Package model 定义第三方 API 配置数据模型。
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ApiConfig 存储第三方 API 密钥和配置信息。
|
||||
// APIKey 字段在 JSON 序列化时隐藏(json:"-"),通过加密存储。
|
||||
type ApiConfig struct {
|
||||
ID string `gorm:"primaryKey;size:36" json:"id"`
|
||||
Provider string `gorm:"size:64;uniqueIndex;not null" json:"provider"`
|
||||
APIKey string `gorm:"size:512" json:"-"`
|
||||
BaseURL string `gorm:"size:512" json:"base_url,omitempty"`
|
||||
Extra string `gorm:"type:text" json:"extra,omitempty"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
Description string `gorm:"size:255" json:"description,omitempty"`
|
||||
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
|
||||
TestResult string `gorm:"size:32" json:"test_result,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate 生成 UUID。
|
||||
func (c *ApiConfig) BeforeCreate(_ *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新时间戳。
|
||||
func (c *ApiConfig) BeforeUpdate(_ *gorm.DB) error {
|
||||
c.UpdatedAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApiProvider 定义支持的 API 提供者列表。
|
||||
type ApiProvider struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
HasAPIKey bool `json:"has_api_key"`
|
||||
HasBaseURL bool `json:"has_base_url"`
|
||||
}
|
||||
|
||||
// PredefinedProviders 返回预定义的 API 提供者列表。
|
||||
func PredefinedProviders() []ApiProvider {
|
||||
return []ApiProvider{
|
||||
{ID: "tmdb", Name: "TMDb", Description: "The Movie Database - 电影/剧集元数据", HasAPIKey: true, HasBaseURL: true},
|
||||
{ID: "douban", Name: "豆瓣", Description: "豆瓣电影/音乐/书籍数据", HasAPIKey: true, HasBaseURL: false},
|
||||
{ID: "bangumi", Name: "Bangumi", Description: "番剧/动漫数据库", HasAPIKey: true, HasBaseURL: false},
|
||||
{ID: "thetvdb", Name: "TheTVDB", Description: "TV Series Database", HasAPIKey: true, HasBaseURL: false},
|
||||
{ID: "fanart", Name: "Fanart.tv", Description: "影视海报/背景图", HasAPIKey: true, HasBaseURL: false},
|
||||
{ID: "openai", Name: "OpenAI", Description: "GPT 系列模型", HasAPIKey: true, HasBaseURL: true},
|
||||
{ID: "deepseek", Name: "DeepSeek", Description: "DeepSeek 大模型", HasAPIKey: true, HasBaseURL: true},
|
||||
{ID: "siliconflow", Name: "SiliconFlow", Description: "AI 模型聚合 API", HasAPIKey: true, HasBaseURL: true},
|
||||
{ID: "adult", Name: "Adult API", Description: "Adult 内容元数据(需额外权限)", HasAPIKey: true, HasBaseURL: false},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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加密
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
// Package model — Emby API 兼容层请求/响应类型。
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// ─── Emby 认证 ────────────────────────────────────────────────────────────────
|
||||
|
||||
// EmbyAuthRequest Emby 认证请求(用户名+密码)。
|
||||
type EmbyAuthRequest struct {
|
||||
Username string `json:"Username"`
|
||||
Password string `json:"Password"`
|
||||
}
|
||||
|
||||
// EmbyAuthResponse Emby 认证响应。
|
||||
type EmbyAuthResponse struct {
|
||||
User EmbyUser `json:"User"`
|
||||
AccessToken string `json:"AccessToken"`
|
||||
ServerID string `json:"ServerId"`
|
||||
}
|
||||
|
||||
// EmbyApiKeyAuthRequest API Key 认证请求。
|
||||
type EmbyApiKeyAuthRequest struct {
|
||||
ApiKey string `json:"ApiKey"`
|
||||
}
|
||||
|
||||
// ─── Emby 用户 ────────────────────────────────────────────────────────────────
|
||||
|
||||
// EmbyUser Emby 用户信息。
|
||||
type EmbyUser struct {
|
||||
Id string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
ServerId string `json:"ServerId"`
|
||||
HasPassword bool `json:"HasPassword"`
|
||||
PrimaryImageTag string `json:"PrimaryImageTag,omitempty"`
|
||||
Configuration *EmbyUserConfiguration `json:"Configuration,omitempty"`
|
||||
LastActivityDate *time.Time `json:"LastActivityDate,omitempty"`
|
||||
LastLoginDate *time.Time `json:"LastLoginDate,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyUserConfiguration 用户配置。
|
||||
type EmbyUserConfiguration struct {
|
||||
AudioLanguagePreference string `json:"AudioLanguagePreference"`
|
||||
SubtitleLanguagePreference string `json:"SubtitleLanguagePreference"`
|
||||
EnableAutoPlay bool `json:"EnableAutoPlay"`
|
||||
EnableNextEpisodeAutoPlay bool `json:"EnableNextEpisodeAutoPlay"`
|
||||
}
|
||||
|
||||
// ─── Emby 系统信息 ────────────────────────────────────────────────────────────
|
||||
|
||||
// EmbySystemInfo 系统信息。
|
||||
type EmbySystemInfo struct {
|
||||
Id string `json:"Id"`
|
||||
ServerName string `json:"ServerName"`
|
||||
Version string `json:"Version"`
|
||||
ProductName string `json:"ProductName"`
|
||||
OperatingSystem string `json:"OperatingSystem"`
|
||||
Architecture string `json:"Architecture"`
|
||||
LocalAddress string `json:"LocalAddress"`
|
||||
WanAddress string `json:"WanAddress,omitempty"`
|
||||
HasPendingRestart bool `json:"HasPendingRestart"`
|
||||
IsShuttingDown bool `json:"IsShuttingDown"`
|
||||
SupportsLibraryScan bool `json:"SupportsLibraryScan"`
|
||||
SupportsHttps bool `json:"SupportsHttps"`
|
||||
SupportsAutoDiscovery bool `json:"SupportsAutoDiscovery"`
|
||||
WebSocketPortNumber int `json:"WebSocketPortNumber"`
|
||||
TranscodingTempPath string `json:"TranscodingTempPath,omitempty"`
|
||||
CanSelfUpdate bool `json:"CanSelfUpdate"`
|
||||
CanLaunchWebBrowser bool `json:"CanLaunchWebBrowser"`
|
||||
CanRestart bool `json:"CanRestart"`
|
||||
CodecCount int `json:"CodecCount"`
|
||||
}
|
||||
|
||||
// EmbyLogEntry 日志条目。
|
||||
type EmbyLogEntry struct {
|
||||
Id string `json:"Id"`
|
||||
DateCreated time.Time `json:"DateCreated"`
|
||||
Level string `json:"Level"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
// EmbyServerConfiguration 服务器配置。
|
||||
type EmbyServerConfiguration struct {
|
||||
Name string `json:"Name"`
|
||||
ServerName string `json:"ServerName"`
|
||||
EnableUPnP bool `json:"EnableUPnP"`
|
||||
PublicPort int `json:"PublicPort"`
|
||||
EnableHttps bool `json:"EnableHttps"`
|
||||
HttpServerPortNumber int `json:"HttpServerPortNumber"`
|
||||
HttpsPortNumber int `json:"HttpsPortNumber"`
|
||||
EnableRemoteAccess bool `json:"EnableRemoteAccess"`
|
||||
}
|
||||
|
||||
// EmbySession 会话信息。
|
||||
type EmbySession struct {
|
||||
Id string `json:"Id"`
|
||||
Client string `json:"Client"`
|
||||
ClientVersion string `json:"ClientVersion"`
|
||||
DeviceId string `json:"DeviceId"`
|
||||
DeviceName string `json:"DeviceName"`
|
||||
UserName string `json:"UserName,omitempty"`
|
||||
UserId string `json:"UserId,omitempty"`
|
||||
LastActivityDate *time.Time `json:"LastActivityDate,omitempty"`
|
||||
RemoteEndPoint string `json:"RemoteEndPoint,omitempty"`
|
||||
NowPlayingItem *EmbyItem `json:"NowPlayingItem,omitempty"`
|
||||
PlayState *EmbyPlaybackState `json:"PlayState,omitempty"`
|
||||
Capabilities EmbyClientCapabilities `json:"Capabilities,omitempty"`
|
||||
SupportsRemoteControl bool `json:"SupportsRemoteControl"`
|
||||
AdditionalUsers []EmbySessionUserInfo `json:"AdditionalUsers,omitempty"`
|
||||
}
|
||||
|
||||
// EmbySessionUserInfo 会话中的附加用户。
|
||||
type EmbySessionUserInfo struct {
|
||||
UserId string `json:"UserId"`
|
||||
UserName string `json:"UserName"`
|
||||
}
|
||||
|
||||
// EmbyPlaybackState 播放状态。
|
||||
type EmbyPlaybackState struct {
|
||||
PositionTicks int64 `json:"PositionTicks"`
|
||||
VolumeLevel int `json:"VolumeLevel"`
|
||||
IsMuted bool `json:"IsMuted"`
|
||||
IsPaused bool `json:"IsPaused"`
|
||||
PlayMethod string `json:"PlayMethod,omitempty"`
|
||||
CanSeek bool `json:"CanSeek"`
|
||||
}
|
||||
|
||||
// EmbyClientCapabilities 客户端能力描述。
|
||||
type EmbyClientCapabilities struct {
|
||||
PlayableMediaTypes []string `json:"PlayableMediaTypes"`
|
||||
SupportedCommands []string `json:"SupportedCommands"`
|
||||
SupportsMediaControl bool `json:"SupportsMediaControl"`
|
||||
SupportsSync bool `json:"SupportsSync"`
|
||||
}
|
||||
|
||||
// ─── Emby 虚拟文件夹 / 媒体库 ────────────────────────────────────────────────
|
||||
|
||||
// EmbyVirtualFolder 虚拟文件夹(媒体库)。
|
||||
type EmbyVirtualFolder struct {
|
||||
Name string `json:"Name"`
|
||||
Locations []string `json:"Locations"`
|
||||
CollectionType string `json:"CollectionType"`
|
||||
LibraryOptions EmbyLibraryOptions `json:"LibraryOptions,omitempty"`
|
||||
RefreshStatus *EmbyRefreshStatus `json:"RefreshStatus,omitempty"`
|
||||
ItemId string `json:"ItemId"`
|
||||
}
|
||||
|
||||
// EmbyLibraryOptions 媒体库选项。
|
||||
type EmbyLibraryOptions struct {
|
||||
PreferredMetadataLanguage string `json:"PreferredMetadataLanguage"`
|
||||
MetadataCountryCode string `json:"MetadataCountryCode"`
|
||||
EnableRealtimeMonitor bool `json:"EnableRealtimeMonitor"`
|
||||
EnableAutomaticSeriesGrouping bool `json:"EnableAutomaticSeriesGrouping"`
|
||||
}
|
||||
|
||||
// EmbyRefreshStatus 刷新状态。
|
||||
type EmbyRefreshStatus struct {
|
||||
LastRefreshResult string `json:"LastRefreshResult"`
|
||||
LastRefreshedAt time.Time `json:"LastRefreshedAt"`
|
||||
IsActive bool `json:"IsActive"`
|
||||
}
|
||||
|
||||
// EmbyItemsCounts 项目计数。
|
||||
type EmbyItemsCounts struct {
|
||||
MovieCount int `json:"MovieCount"`
|
||||
SeriesCount int `json:"SeriesCount"`
|
||||
EpisodeCount int `json:"EpisodeCount"`
|
||||
ArtistCount int `json:"ArtistCount"`
|
||||
AlbumCount int `json:"AlbumCount"`
|
||||
SongCount int `json:"SongCount"`
|
||||
MusicVideoCount int `json:"MusicVideoCount"`
|
||||
BookCount int `json:"BookCount"`
|
||||
BoxSetCount int `json:"BoxSetCount"`
|
||||
}
|
||||
|
||||
// ─── Emby Items ───────────────────────────────────────────────────────────────
|
||||
|
||||
// EmbyItemsResponse Emby 标准分页响应包装。
|
||||
type EmbyItemsResponse struct {
|
||||
Items []EmbyItem `json:"Items"`
|
||||
TotalRecordCount int `json:"TotalRecordCount"`
|
||||
StartIndex int `json:"StartIndex"`
|
||||
}
|
||||
|
||||
// EmbyItem Emby 媒体项。
|
||||
type EmbyItem struct {
|
||||
Id string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Type string `json:"Type"` // Movie / Series / Episode / Season / BoxSet / Folder
|
||||
Overview string `json:"Overview,omitempty"`
|
||||
ProductionYear int `json:"ProductionYear,omitempty"`
|
||||
PremiereDate *time.Time `json:"PremiereDate,omitempty"`
|
||||
CommunityRating float64 `json:"CommunityRating,omitempty"`
|
||||
OfficialRating string `json:"OfficialRating,omitempty"`
|
||||
RunTimeTicks int64 `json:"RunTimeTicks,omitempty"`
|
||||
ParentId string `json:"ParentId,omitempty"`
|
||||
SeriesId string `json:"SeriesId,omitempty"`
|
||||
SeasonId string `json:"SeasonId,omitempty"`
|
||||
IndexNumber int `json:"IndexNumber,omitempty"`
|
||||
ParentIndexNumber int `json:"ParentIndexNumber,omitempty"`
|
||||
UserData *EmbyUserData `json:"UserData,omitempty"`
|
||||
ImageTags map[string]string `json:"ImageTags,omitempty"`
|
||||
BackdropImageTags []string `json:"BackdropImageTags,omitempty"`
|
||||
Genres []string `json:"Genres,omitempty"`
|
||||
Studios []EmbyNameId `json:"Studios,omitempty"`
|
||||
People []EmbyPerson `json:"People,omitempty"`
|
||||
MediaSources []EmbyMediaSource `json:"MediaSources,omitempty"`
|
||||
RecursiveItemCount int `json:"RecursiveItemCount,omitempty"`
|
||||
ChildCount int `json:"ChildCount,omitempty"`
|
||||
Status string `json:"Status,omitempty"`
|
||||
AirDays []string `json:"AirDays,omitempty"`
|
||||
EndDate *time.Time `json:"EndDate,omitempty"`
|
||||
ProviderIds map[string]string `json:"ProviderIds,omitempty"`
|
||||
Taglines []string `json:"Taglines,omitempty"`
|
||||
GenreItems []EmbyNameId `json:"GenreItems,omitempty"`
|
||||
DateCreated *time.Time `json:"DateCreated,omitempty"`
|
||||
Path string `json:"Path,omitempty"`
|
||||
SortName string `json:"SortName,omitempty"`
|
||||
ForcedSortName string `json:"ForcedSortName,omitempty"`
|
||||
Width int `json:"Width,omitempty"`
|
||||
Height int `json:"Height,omitempty"`
|
||||
Container string `json:"Container,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyUserData 用户播放数据。
|
||||
type EmbyUserData struct {
|
||||
PlaybackPositionTicks int64 `json:"PlaybackPositionTicks"`
|
||||
PlayCount int `json:"PlayCount"`
|
||||
IsFavorite bool `json:"IsFavorite"`
|
||||
Played bool `json:"Played"`
|
||||
UnplayedItemCount int `json:"UnplayedItemCount"`
|
||||
PercentagePlayed float64 `json:"PercentagePlayed"`
|
||||
Rating float64 `json:"Rating,omitempty"`
|
||||
PlayedPercentage float64 `json:"PlayedPercentage,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyMediaSource 媒体源。
|
||||
type EmbyMediaSource struct {
|
||||
Id string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Path string `json:"Path"`
|
||||
Size int64 `json:"Size"`
|
||||
Container string `json:"Container,omitempty"`
|
||||
Bitrate int64 `json:"Bitrate,omitempty"`
|
||||
MediaStreams []EmbyMediaStream `json:"MediaStreams"`
|
||||
SupportsTranscoding bool `json:"SupportsTranscoding"`
|
||||
SupportsDirectStream bool `json:"SupportsDirectStream"`
|
||||
SupportsDirectPlay bool `json:"SupportsDirectPlay"`
|
||||
TranscodingUrl string `json:"TranscodingUrl,omitempty"`
|
||||
Protocol string `json:"Protocol,omitempty"`
|
||||
Type string `json:"Type,omitempty"`
|
||||
IsRemote bool `json:"IsRemote,omitempty"`
|
||||
RunTimeTicks int64 `json:"RunTimeTicks,omitempty"`
|
||||
ETag string `json:"ETag,omitempty"`
|
||||
SupportsProbing bool `json:"SupportsProbing,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyMediaStream 媒体流(视频/音频/字幕)。
|
||||
type EmbyMediaStream struct {
|
||||
Codec string `json:"Codec"`
|
||||
Type string `json:"Type"` // Video / Audio / Subtitle
|
||||
Language string `json:"Language,omitempty"`
|
||||
DisplayTitle string `json:"DisplayTitle,omitempty"`
|
||||
Index int `json:"Index"`
|
||||
IsDefault bool `json:"IsDefault"`
|
||||
IsForced bool `json:"IsForced"`
|
||||
IsExternal bool `json:"IsExternal,omitempty"`
|
||||
Height int `json:"Height,omitempty"`
|
||||
Width int `json:"Width,omitempty"`
|
||||
BitRate int64 `json:"BitRate,omitempty"`
|
||||
Channels int `json:"Channels,omitempty"`
|
||||
SampleRate int `json:"SampleRate,omitempty"`
|
||||
AspectRatio string `json:"AspectRatio,omitempty"`
|
||||
VideoRange string `json:"VideoRange,omitempty"`
|
||||
DeliveryUrl string `json:"DeliveryUrl,omitempty"`
|
||||
DeliveryMethod string `json:"DeliveryMethod,omitempty"`
|
||||
ExternalUrl string `json:"ExternalUrl,omitempty"`
|
||||
ExternalSubtitleId string `json:"ExternalSubtitleId,omitempty"`
|
||||
SubtitleFileName string `json:"SubtitleFileName,omitempty"`
|
||||
Title string `json:"Title,omitempty"`
|
||||
Comment string `json:"Comment,omitempty"`
|
||||
Path string `json:"Path,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyPerson 人员信息。
|
||||
type EmbyPerson struct {
|
||||
Id string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Role string `json:"Role,omitempty"`
|
||||
Type string `json:"Type,omitempty"`
|
||||
PrimaryImageTag string `json:"PrimaryImageTag,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyNameId 名称+ID 对。
|
||||
type EmbyNameId struct {
|
||||
Name string `json:"Name"`
|
||||
Id string `json:"Id"`
|
||||
}
|
||||
|
||||
// ─── Emby PlaybackInfo ────────────────────────────────────────────────────────
|
||||
|
||||
// EmbyPlaybackInfoRequest 播放信息请求。
|
||||
type EmbyPlaybackInfoRequest struct {
|
||||
UserId string `json:"UserId,omitempty"`
|
||||
MaxStreamingBitrate int64 `json:"MaxStreamingBitrate,omitempty"`
|
||||
StartTimeTicks int64 `json:"StartTimeTicks,omitempty"`
|
||||
AudioStreamIndex int `json:"AudioStreamIndex,omitempty"`
|
||||
SubtitleStreamIndex int `json:"SubtitleStreamIndex,omitempty"`
|
||||
MaxAudioChannels int `json:"MaxAudioChannels,omitempty"`
|
||||
ItemId string `json:"ItemId,omitempty"`
|
||||
DeviceProfile *EmbyDeviceProfile `json:"DeviceProfile,omitempty"`
|
||||
EnableDirectStream bool `json:"EnableDirectStream,omitempty"`
|
||||
EnableDirectPlay bool `json:"EnableDirectPlay,omitempty"`
|
||||
AutoOpenLiveStream bool `json:"AutoOpenLiveStream,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyPlaybackInfoResponse 播放信息响应。
|
||||
type EmbyPlaybackInfoResponse struct {
|
||||
MediaSources []EmbyMediaSource `json:"MediaSources"`
|
||||
PlaySessionId string `json:"PlaySessionId"`
|
||||
}
|
||||
|
||||
// EmbyDeviceProfile 设备配置文件。
|
||||
type EmbyDeviceProfile struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
MaxStaticBitrate int `json:"MaxStaticBitrate,omitempty"`
|
||||
MaxStreamingBitrate int `json:"MaxStreamingBitrate,omitempty"`
|
||||
MusicStreamingTranscodingBitrate int `json:"MusicStreamingTranscodingBitrate,omitempty"`
|
||||
DirectPlayProfiles []EmbyDirectPlayProfile `json:"DirectPlayProfiles,omitempty"`
|
||||
TranscodingProfiles []EmbyTranscodingProfile `json:"TranscodingProfiles,omitempty"`
|
||||
ContainerProfiles []EmbyContainerProfile `json:"ContainerProfiles,omitempty"`
|
||||
CodecProfiles []EmbyCodecProfile `json:"CodecProfiles,omitempty"`
|
||||
SubtitleProfiles []EmbySubtitleProfile `json:"SubtitleProfiles,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyDirectPlayProfile 直接播放配置。
|
||||
type EmbyDirectPlayProfile struct {
|
||||
Container string `json:"Container,omitempty"`
|
||||
AudioCodec string `json:"AudioCodec,omitempty"`
|
||||
VideoCodec string `json:"VideoCodec,omitempty"`
|
||||
Type string `json:"Type,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyTranscodingProfile 转码配置。
|
||||
type EmbyTranscodingProfile struct {
|
||||
Container string `json:"Container,omitempty"`
|
||||
Type string `json:"Type,omitempty"`
|
||||
VideoCodec string `json:"VideoCodec,omitempty"`
|
||||
AudioCodec string `json:"AudioCodec,omitempty"`
|
||||
Protocol string `json:"Protocol,omitempty"`
|
||||
EstimateContentLength bool `json:"EstimateContentLength,omitempty"`
|
||||
EnableMpegtsM2TsMode bool `json:"EnableMpegtsM2TsMode,omitempty"`
|
||||
TranscodeSeekInfo string `json:"TranscodeSeekInfo,omitempty"`
|
||||
Context string `json:"Context,omitempty"`
|
||||
EnableSubtitlesInManifest bool `json:"EnableSubtitlesInManifest,omitempty"`
|
||||
MaxAudioChannels int `json:"MaxAudioChannels,omitempty"`
|
||||
MinSegments int `json:"MinSegments,omitempty"`
|
||||
SegmentLength int `json:"SegmentLength,omitempty"`
|
||||
BreakOnNonKeyFrames bool `json:"BreakOnNonKeyFrames,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyContainerProfile 容器配置。
|
||||
type EmbyContainerProfile struct {
|
||||
Type string `json:"Type,omitempty"`
|
||||
Conditions []string `json:"Conditions,omitempty"`
|
||||
Container string `json:"Container,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyCodecProfile 编解码器配置。
|
||||
type EmbyCodecProfile struct {
|
||||
Type string `json:"Type,omitempty"`
|
||||
Conditions []EmbyProfileCondition `json:"Conditions,omitempty"`
|
||||
Codec string `json:"Codec,omitempty"`
|
||||
Container string `json:"Container,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyProfileCondition 配置条件。
|
||||
type EmbyProfileCondition struct {
|
||||
Condition string `json:"Condition,omitempty"`
|
||||
Property string `json:"Property,omitempty"`
|
||||
Value string `json:"Value,omitempty"`
|
||||
IsRequired bool `json:"IsRequired,omitempty"`
|
||||
}
|
||||
|
||||
// EmbySubtitleProfile 字幕配置。
|
||||
type EmbySubtitleProfile struct {
|
||||
Format string `json:"Format,omitempty"`
|
||||
Method string `json:"Method,omitempty"`
|
||||
DidlMode string `json:"DidlMode,omitempty"`
|
||||
Language string `json:"Language,omitempty"`
|
||||
Container string `json:"Container,omitempty"`
|
||||
}
|
||||
|
||||
// ─── Emby 播放进度上报 ────────────────────────────────────────────────────────
|
||||
|
||||
// EmbyPlaybackProgressRequest 播放进度上报。
|
||||
type EmbyPlaybackProgressRequest struct {
|
||||
CanSeek bool `json:"CanSeek"`
|
||||
ItemId string `json:"ItemId"`
|
||||
MediaSourceId string `json:"MediaSourceId,omitempty"`
|
||||
PositionTicks int64 `json:"PositionTicks"`
|
||||
RunTimeTicks int64 `json:"RunTimeTicks,omitempty"`
|
||||
IsPaused bool `json:"IsPaused"`
|
||||
IsMuted bool `json:"IsMuted"`
|
||||
VolumeLevel int `json:"VolumeLevel,omitempty"`
|
||||
PlayMethod string `json:"PlayMethod,omitempty"`
|
||||
PlaySessionId string `json:"PlaySessionId,omitempty"`
|
||||
LiveStreamId string `json:"LiveStreamId,omitempty"`
|
||||
QueueableMediaTypes []string `json:"QueueableMediaTypes,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyStopPlaybackRequest 停止播放上报。
|
||||
type EmbyStopPlaybackRequest struct {
|
||||
ItemId string `json:"ItemId"`
|
||||
MediaSourceId string `json:"MediaSourceId,omitempty"`
|
||||
PositionTicks int64 `json:"PositionTicks"`
|
||||
RunTimeTicks int64 `json:"RunTimeTicks,omitempty"`
|
||||
PlaySessionId string `json:"PlaySessionId,omitempty"`
|
||||
LiveStreamId string `json:"LiveStreamId,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyUserDataRequest 用户数据更新。
|
||||
type EmbyUserDataRequest struct {
|
||||
PlaybackPositionTicks int64 `json:"PlaybackPositionTicks,omitempty"`
|
||||
PlayCount int `json:"PlayCount,omitempty"`
|
||||
IsFavorite bool `json:"IsFavorite,omitempty"`
|
||||
Played bool `json:"Played,omitempty"`
|
||||
PlayedPercentage float64 `json:"PlayedPercentage,omitempty"`
|
||||
}
|
||||
|
||||
// ─── Emby Hubs ────────────────────────────────────────────────────────────────
|
||||
|
||||
// EmbyHubResponse Hub 响应。
|
||||
type EmbyHubResponse struct {
|
||||
Items []EmbyHubItem `json:"Items"`
|
||||
}
|
||||
|
||||
// EmbyHubItem Hub 条目。
|
||||
type EmbyHubItem struct {
|
||||
Id string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Type string `json:"Type"`
|
||||
Items []EmbyItem `json:"Items"`
|
||||
TotalCount int `json:"TotalCount,omitempty"`
|
||||
ImageUrl string `json:"ImageUrl,omitempty"`
|
||||
}
|
||||
|
||||
// ─── Emby 字幕 ────────────────────────────────────────────────────────────────
|
||||
|
||||
// EmbyRemoteSubtitleInfo 远程字幕信息。
|
||||
type EmbyRemoteSubtitleInfo struct {
|
||||
ThreeLetterISOLanguageName string `json:"ThreeLetterISOLanguageName"`
|
||||
Id string `json:"Id"`
|
||||
ProviderName string `json:"ProviderName"`
|
||||
Name string `json:"Name"`
|
||||
Format string `json:"Format"`
|
||||
Author string `json:"Author"`
|
||||
Comment string `json:"Comment"`
|
||||
DateCreated *time.Time `json:"DateCreated,omitempty"`
|
||||
CommunityRating float64 `json:"CommunityRating,omitempty"`
|
||||
DownloadCount int `json:"DownloadCount"`
|
||||
IsHashMatch bool `json:"IsHashMatch,omitempty"`
|
||||
IsForced bool `json:"IsForced,omitempty"`
|
||||
IsHearingImpaired bool `json:"IsHearingImpaired,omitempty"`
|
||||
}
|
||||
|
||||
// EmbySubtitleSearchRequest 字幕搜索请求。
|
||||
type EmbySubtitleSearchRequest struct {
|
||||
ItemId string `json:"ItemId"`
|
||||
Language string `json:"Language"`
|
||||
IsPerfectMatch bool `json:"IsPerfectMatch,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyImageRemoteInfo 远程图片信息。
|
||||
type EmbyImageRemoteInfo struct {
|
||||
Providers []EmbyImageProviderInfo `json:"Providers"`
|
||||
TotalRecordCount int `json:"TotalRecordCount"`
|
||||
}
|
||||
|
||||
// EmbyImageProviderInfo 图片提供者信息。
|
||||
type EmbyImageProviderInfo struct {
|
||||
Name string `json:"Name"`
|
||||
RemoteImages []EmbyRemoteImageInfo `json:"RemoteImages,omitempty"`
|
||||
SupportedImages []string `json:"SupportedImages"`
|
||||
}
|
||||
|
||||
// EmbyRemoteImageInfo 远程图片信息。
|
||||
type EmbyRemoteImageInfo struct {
|
||||
Url string `json:"Url"`
|
||||
ThumbnailUrl string `json:"ThumbnailUrl,omitempty"`
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
CommunityRating float64 `json:"CommunityRating,omitempty"`
|
||||
VoteCount int `json:"VoteCount,omitempty"`
|
||||
Language string `json:"Language,omitempty"`
|
||||
Type string `json:"Type"`
|
||||
RatingType string `json:"RatingType,omitempty"`
|
||||
ProviderName string `json:"ProviderName"`
|
||||
}
|
||||
|
||||
// ─── Emby Genre / Person ─────────────────────────────────────────────────────
|
||||
|
||||
// EmbyGenre 类型。
|
||||
type EmbyGenre struct {
|
||||
Name string `json:"Name"`
|
||||
Id string `json:"Id,omitempty"`
|
||||
}
|
||||
|
||||
// EmbyPersonInfo 人物信息。
|
||||
type EmbyPersonInfo struct {
|
||||
Id string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Type string `json:"Type,omitempty"`
|
||||
PrimaryImageTag string `json:"PrimaryImageTag,omitempty"`
|
||||
Overview string `json:"Overview,omitempty"`
|
||||
BirthDate string `json:"BirthDate,omitempty"`
|
||||
ProductionYear int `json:"ProductionYear,omitempty"`
|
||||
EndDate string `json:"EndDate,omitempty"`
|
||||
PremiereDate *time.Time `json:"PremiereDate,omitempty"`
|
||||
}
|
||||
|
||||
// ─── Emby Active Encoding ────────────────────────────────────────────────────
|
||||
|
||||
// EmbyActiveEncodingRequest 活跃编码请求(客户端报告转码进度)。
|
||||
type EmbyActiveEncodingRequest struct {
|
||||
PlaySessionId string `json:"PlaySessionId"`
|
||||
When string `json:"When"`
|
||||
PositionTicks int64 `json:"PositionTicks,omitempty"`
|
||||
IsPaused bool `json:"IsPaused,omitempty"`
|
||||
IsUserPaused bool `json:"IsUserPaused,omitempty"`
|
||||
}
|
||||
+36
-30
@@ -1,6 +1,5 @@
|
||||
// Package model defines GORM data models and the registry used by
|
||||
// auto-migration. Each subsystem in MediaStationGo owns a slice of tables
|
||||
// here; AllModels returns the union for db.AutoMigrate.
|
||||
// Package model 定义 GORM 数据模型和自动迁移使用的注册表。
|
||||
// 每个子系统在 MediaStationGo 中拥有一个表切片;AllModels 返回联合以供 db.AutoMigrate 使用。
|
||||
package model
|
||||
|
||||
import (
|
||||
@@ -10,11 +9,11 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Base captures the fields embedded in every domain entity:
|
||||
// Base 嵌入每个域实体共享的字段:
|
||||
//
|
||||
// - ID: UUID v4 string primary key.
|
||||
// - CreatedAt / UpdatedAt: managed by GORM.
|
||||
// - DeletedAt: soft-delete (queries auto-filter on it).
|
||||
// - ID: UUID v4 字符串主键
|
||||
// - CreatedAt / UpdatedAt: 由 GORM 管理
|
||||
// - DeletedAt: 软删除(查询自动过滤)
|
||||
type Base struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
@@ -22,7 +21,7 @@ type Base struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// BeforeCreate generates a UUID if the caller did not supply one.
|
||||
// BeforeCreate 如果调用者未提供则生成 UUID。
|
||||
func (b *Base) BeforeCreate(_ *gorm.DB) error {
|
||||
if b.ID == "" {
|
||||
b.ID = uuid.NewString()
|
||||
@@ -30,20 +29,23 @@ func (b *Base) BeforeCreate(_ *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// User is a local account. The first registered admin (or seeded admin)
|
||||
// gains the "admin" role; everyone else defaults to "user".
|
||||
// User 是本地账户。第一个注册的管理员(或种子管理员)获得 "admin" 角色;
|
||||
// 其他所有用户默认为 "user"。
|
||||
type User struct {
|
||||
Base
|
||||
Username string `gorm:"uniqueIndex;size:64;not null" json:"username"`
|
||||
PasswordHash string `gorm:"size:128;not null" json:"-"`
|
||||
Role string `gorm:"size:16;not null;default:user" json:"role"`
|
||||
Tier string `gorm:"size:16;default:free" json:"tier"` // free / plus
|
||||
Nickname string `gorm:"size:128" json:"nickname,omitempty"`
|
||||
Email string `gorm:"size:128" json:"email,omitempty"`
|
||||
AvatarURL string `gorm:"size:255" json:"avatar_url,omitempty"`
|
||||
ForcePasswordReset bool `gorm:"default:false" json:"force_password_reset"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||||
}
|
||||
|
||||
// Library represents a user-defined media root directory.
|
||||
// Library 表示用户定义的媒体根目录。
|
||||
type Library struct {
|
||||
Base
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
@@ -52,8 +54,7 @@ type Library struct {
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
}
|
||||
|
||||
// Media is a single playable item. Series episodes link to a SeriesID; movies
|
||||
// have SeriesID == "".
|
||||
// Media 是单个可播放项。剧集链接到 SeriesID;电影 SeriesID == ""。
|
||||
type Media struct {
|
||||
Base
|
||||
LibraryID string `gorm:"index;size:36" json:"library_id"`
|
||||
@@ -116,7 +117,7 @@ type APIConfig struct {
|
||||
Description string `gorm:"size:255" json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// Series groups episodes that belong to the same show.
|
||||
// Series 将属于同一节目的剧集分组。
|
||||
type Series struct {
|
||||
Base
|
||||
LibraryID string `gorm:"index;size:36" json:"library_id"`
|
||||
@@ -130,7 +131,7 @@ type Series struct {
|
||||
BangumiID int `json:"bangumi_id"`
|
||||
}
|
||||
|
||||
// PlaybackHistory records the current playback position for resume support.
|
||||
// PlaybackHistory 记录当前播放位置以支持续播。
|
||||
type PlaybackHistory struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36;not null" json:"user_id"`
|
||||
@@ -141,14 +142,14 @@ type PlaybackHistory struct {
|
||||
Completed bool `json:"completed"`
|
||||
}
|
||||
|
||||
// Favorite marks a media item as favourite for a given user.
|
||||
// Favorite 将媒体项标记为给定用户的收藏。
|
||||
type Favorite struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36;not null;uniqueIndex:uniq_user_media" json:"user_id"`
|
||||
MediaID string `gorm:"index;size:36;not null;uniqueIndex:uniq_user_media" json:"media_id"`
|
||||
}
|
||||
|
||||
// Playlist is a user-curated, ordered list of media items.
|
||||
// Playlist 是用户策划的、有序的媒体列表。
|
||||
type Playlist struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36;not null" json:"user_id"`
|
||||
@@ -156,7 +157,7 @@ type Playlist struct {
|
||||
IsPublic bool `gorm:"default:false" json:"is_public"`
|
||||
}
|
||||
|
||||
// PlaylistItem is the join table between Playlists and Media with ordering.
|
||||
// PlaylistItem 是 Playlist 和 Media 的连接表,带有排序。
|
||||
type PlaylistItem struct {
|
||||
Base
|
||||
PlaylistID string `gorm:"index;size:36;not null" json:"playlist_id"`
|
||||
@@ -164,7 +165,7 @@ type PlaylistItem struct {
|
||||
Position int `json:"position"`
|
||||
}
|
||||
|
||||
// DownloadTask is an outstanding (or completed) torrent / HTTP download.
|
||||
// DownloadTask 是待处理(或已完成)的 torrent / HTTP 下载。
|
||||
type DownloadTask struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36" json:"user_id"`
|
||||
@@ -175,27 +176,25 @@ type DownloadTask struct {
|
||||
Progress float32 `json:"progress"`
|
||||
}
|
||||
|
||||
// Subscription is an automation rule that polls an RSS feed and queues
|
||||
// matching torrents into the configured download client.
|
||||
// Subscription 是自动化规则,轮询 RSS 源并将匹配种子排队到配置的下载客户端。
|
||||
type Subscription struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36" json:"user_id"`
|
||||
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"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
UserID string `gorm:"index;size:36" json:"user_id"`
|
||||
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"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
LastRunAt *time.Time `json:"last_run_at,omitempty"`
|
||||
}
|
||||
|
||||
// Setting is a single key/value system-wide preference (used by the admin UI).
|
||||
// Setting 是单个键/值系统级偏好(供管理 UI 使用)。
|
||||
type Setting struct {
|
||||
Key string `gorm:"primaryKey;size:128" json:"key"`
|
||||
Value string `gorm:"type:text" json:"value"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AccessLog is a structured audit-trail entry. Stored in SQLite for the
|
||||
// admin Activity panel.
|
||||
// AccessLog 是结构化审计跟踪条目。存储在 SQLite 中供管理活动面板使用。
|
||||
type AccessLog struct {
|
||||
Base
|
||||
UserID string `gorm:"index;size:36" json:"user_id"`
|
||||
@@ -205,7 +204,7 @@ type AccessLog struct {
|
||||
Detail string `gorm:"type:text" json:"detail"`
|
||||
}
|
||||
|
||||
// AllModels returns the slice consumed by gorm.AutoMigrate.
|
||||
// AllModels 返回 gorm.AutoMigrate 使用的切片。
|
||||
func AllModels() []interface{} {
|
||||
return []interface{}{
|
||||
&User{},
|
||||
@@ -221,5 +220,12 @@ func AllModels() []interface{} {
|
||||
&Setting{},
|
||||
&AccessLog{},
|
||||
&APIConfig{},
|
||||
&UserPermission{},
|
||||
&RefreshToken{},
|
||||
&ApiConfig{},
|
||||
&DownloadClient{},
|
||||
&NotifyChannel{},
|
||||
&Site{},
|
||||
&STRMRecord{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Package model 定义权限相关的数据模型。
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserPermission 定义用户细粒度权限(19项)。
|
||||
// 默认开启(6项):CanViewDashboard, CanPlayMedia, CanCast, CanExternalPlayer, CanFavorite, CanViewHistory
|
||||
// 默认关闭(13项):其他权限需要管理员分配
|
||||
type UserPermission struct {
|
||||
ID string `gorm:"primaryKey;size:36" json:"id"`
|
||||
UserID string `gorm:"uniqueIndex;size:36;not null" json:"user_id"`
|
||||
|
||||
// 默认开启(6项)- Basic
|
||||
CanViewDashboard bool `gorm:"default:true" json:"can_view_dashboard"`
|
||||
CanPlayMedia bool `gorm:"default:true" json:"can_play_media"`
|
||||
CanCast bool `gorm:"default:true" json:"can_cast"`
|
||||
CanExternalPlayer bool `gorm:"default:true" json:"can_external_player"`
|
||||
CanFavorite bool `gorm:"default:true" json:"can_favorite"`
|
||||
CanViewHistory bool `gorm:"default:true" json:"can_view_history"`
|
||||
|
||||
// 默认关闭(13项)- Advanced
|
||||
CanEditMedia bool `gorm:"default:false" json:"can_edit_media"`
|
||||
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"`
|
||||
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"`
|
||||
CanManageFiles bool `gorm:"default:false" json:"can_manage_files"`
|
||||
CanManageStrm bool `gorm:"default:false" json:"can_manage_strm"`
|
||||
CanAccessSettings bool `gorm:"default:false" json:"can_access_settings"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate 生成 UUID。
|
||||
func (p *UserPermission) BeforeCreate(_ *gorm.DB) error {
|
||||
if p.ID == "" {
|
||||
p.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDefaultPermission 创建带有默认权限的 UserPermission。
|
||||
func NewDefaultPermission(userID string) *UserPermission {
|
||||
return &UserPermission{
|
||||
ID: uuid.NewString(),
|
||||
UserID: userID,
|
||||
CanViewDashboard: true,
|
||||
CanPlayMedia: true,
|
||||
CanCast: true,
|
||||
CanExternalPlayer: true,
|
||||
CanFavorite: true,
|
||||
CanViewHistory: true,
|
||||
CanEditMedia: false,
|
||||
CanRescrape: false,
|
||||
CanUseAI: false,
|
||||
CanCaptureFrames: false,
|
||||
CanManageDownloads: false,
|
||||
CanViewDiscover: false,
|
||||
CanManageSubscriptions: false,
|
||||
CanManageSites: false,
|
||||
CanUseAIAssistant: false,
|
||||
CanManageUsers: false,
|
||||
CanManageFiles: false,
|
||||
CanManageStrm: false,
|
||||
CanAccessSettings: false,
|
||||
}
|
||||
}
|
||||
|
||||
// PermissionMap 将权限结构转换为 map[string]bool 便于检查。
|
||||
func (p *UserPermission) PermissionMap() map[string]bool {
|
||||
return map[string]bool{
|
||||
"can_view_dashboard": p.CanViewDashboard,
|
||||
"can_play_media": p.CanPlayMedia,
|
||||
"can_cast": p.CanCast,
|
||||
"can_external_player": p.CanExternalPlayer,
|
||||
"can_favorite": p.CanFavorite,
|
||||
"can_view_history": p.CanViewHistory,
|
||||
"can_edit_media": p.CanEditMedia,
|
||||
"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_sites": p.CanManageSites,
|
||||
"can_use_ai_assistant": p.CanUseAIAssistant,
|
||||
"can_manage_users": p.CanManageUsers,
|
||||
"can_manage_files": p.CanManageFiles,
|
||||
"can_manage_strm": p.CanManageStrm,
|
||||
"can_access_settings": p.CanAccessSettings,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Package model 定义刷新令牌数据模型。
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RefreshToken 用于双令牌认证机制中的刷新令牌。
|
||||
// 存储时使用 SHA256 哈希,原始令牌不存储。
|
||||
type RefreshToken struct {
|
||||
ID string `gorm:"primaryKey;size:36" json:"id"`
|
||||
UserID string `gorm:"index;size:36;not null" json:"user_id"`
|
||||
TokenHash string `gorm:"uniqueIndex;size:128;not null" json:"-"`
|
||||
ExpiresAt time.Time `gorm:"index" json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Revoked bool `gorm:"default:false" json:"revoked"`
|
||||
}
|
||||
|
||||
// BeforeCreate 生成 UUID。
|
||||
func (t *RefreshToken) BeforeCreate(_ *gorm.DB) error {
|
||||
if t.ID == "" {
|
||||
t.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExpired 检查刷新令牌是否已过期。
|
||||
func (t *RefreshToken) IsExpired() bool {
|
||||
return time.Now().After(t.ExpiresAt)
|
||||
}
|
||||
|
||||
// IsValid 检查刷新令牌是否有效(未撤销且未过期)。
|
||||
func (t *RefreshToken) IsValid() bool {
|
||||
return !t.Revoked && !t.IsExpired()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 / 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 加密
|
||||
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", "discuz", "custom_rss"}
|
||||
}
|
||||
|
||||
// AuthTypes 返回支持的认证方式列表。
|
||||
func AuthTypes() []string {
|
||||
return []string{"cookie", "api_key", "auth_header"}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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:36;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",
|
||||
"s3",
|
||||
"http", "https",
|
||||
}
|
||||
|
||||
// IsAllowedProtocol 检查协议是否在白名单中。
|
||||
func IsAllowedProtocol(protocol string) bool {
|
||||
for _, p := range AllowedSTRMProtocols {
|
||||
if p == protocol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user