mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-07 22:06:38 +08:00
Rebrand MMTL to MeBox (name, logo, Docker image) (#17)
* Rebrand MMTL to MeBox across codebase and assets Rename the project display name, Go module path, environment variable prefix (MEBOX_*), Docker image references, and UI branding from MMTL/mmtl to MeBox/mebox. Replace logo assets with the new MeBox icon and keep legacy SQLite migration support for existing mmtl.db deployments. Co-authored-by: truewhile <truewhile@users.noreply.github.com> * Fix logo icons: use cube-only crop without truncated text Previous icon generation cropped too much of the source image, including partial MeBox wordmark text that was cut off in square icon containers. Regenerate logo-64/192/512, favicon, and SVG from cube-only region. Co-authored-by: truewhile <truewhile@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: truewhile <truewhile@users.noreply.github.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestAdultProviderRouting(t *testing.T) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestNormalizeAdultCode(t *testing.T) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// read so values stored on disk are useless without the JWT secret.
|
||||
//
|
||||
// On first read it seeds the table with the providers supported by
|
||||
// MMTL today (TMDb / Bangumi / TheTVDB / Fanart / OpenAI / Douban).
|
||||
// MeBox today (TMDb / Bangumi / TheTVDB / Fanart / OpenAI / Douban).
|
||||
package service
|
||||
|
||||
import (
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// APIConfigService coordinates third-party API key storage.
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// TestConnection 测试 API 连接。
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// GetEffectiveConfig 获取生效的 API 配置(数据库配置优先于配置文件)。
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// ApiConfigService 负责第三方 API 配置的 CRUD 和加密管理。
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// AuditService is the only sanctioned writer for the access_logs table.
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// AuthService handles registration, login, and JWT issuance.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// IssueToken signs a JWT for the given user (60min validity, includes tier).
|
||||
@@ -21,7 +21,7 @@ func (s *AuthService) IssueToken(u *model.User) (string, error) {
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(60 * time.Minute)),
|
||||
Issuer: "mmtl",
|
||||
Issuer: "mebox",
|
||||
Subject: u.ID,
|
||||
},
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func (s *AuthService) IssueExternalPlaybackToken(u *model.User, mediaID string,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(ExternalPlaybackTokenDurationForMedia(durationSec))),
|
||||
Issuer: "mmtl",
|
||||
Issuer: "mebox",
|
||||
Subject: u.ID,
|
||||
},
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (s *AuthService) IssueEmbyToken(u *model.User) (string, error) {
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(EmbyTokenDuration)),
|
||||
Issuer: "mmtl",
|
||||
Issuer: "mebox",
|
||||
Subject: u.ID,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/database"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/database"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func newAuthTestServices(t *testing.T) (*repository.Container, *AuthService, *ProfileService, *PermissionService) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
// BackupService manages database backups.
|
||||
@@ -72,7 +72,7 @@ func (b *BackupService) Create(ctx context.Context) (*BackupInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
ts := time.Now().Format("20060102_150405")
|
||||
dst := filepath.Join(dir, fmt.Sprintf("mmtl_%s.db", ts))
|
||||
dst := filepath.Join(dir, fmt.Sprintf("mebox_%s.db", ts))
|
||||
|
||||
// VACUUM INTO creates a clean, non-WAL copy atomically.
|
||||
if err := b.db.WithContext(ctx).Exec("VACUUM INTO ?", dst).Error; err != nil {
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
// BangumiProvider talks to https://api.bgm.tv.
|
||||
@@ -181,7 +181,7 @@ func (b *BangumiProvider) getJSON(ctx context.Context, u string, out any) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "MMTL/0.1 (https://github.com/ShukeBta/MMTL)")
|
||||
req.Header.Set("User-Agent", "MeBox/0.1 (https://github.com/truewhile/MeBox)")
|
||||
if t := strings.TrimSpace(b.cfg.Secrets.BangumiToken); t != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+t)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// Bot / 设备管控相关的设置键。全部存储在 settings 表,由管理员通过
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/service/cloud115"
|
||||
"github.com/truewhile/MeBox/internal/service/cloud115"
|
||||
)
|
||||
|
||||
// 115 开放平台(openapi)驱动测试:mock proapi 的列目录/直链接口。
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// cloudDrive2Provider bridges CloudDrive2 through its WebDAV endpoint.
|
||||
//
|
||||
// CloudDrive2 integrates many cloud disks (115 / 123 / Aliyun and more).
|
||||
// Treating it as a WebDAV-backed cloud provider lets MMTL
|
||||
// Treating it as a WebDAV-backed cloud provider lets MeBox
|
||||
// browse, mount and upload to those disks without carrying every provider's
|
||||
// private chunk-upload protocol in this project.
|
||||
type cloudDrive2Provider struct {
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
// embyProvider implements Provider against a remote Emby server using an
|
||||
// api_key (token) for authentication. DirectLink.Resolve returns the remote
|
||||
// stream URL; whether MMTL reverse-proxies the bytes is decided by the
|
||||
// stream URL; whether MeBox reverse-proxies the bytes is decided by the
|
||||
// emby.proxy_play account config (defaults to off).
|
||||
type embyProvider struct {
|
||||
base string // e.g. http://host:8096(自动补 /emby 前缀)
|
||||
@@ -159,7 +159,7 @@ func (p *embyProvider) ensureToken(ctx context.Context) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Emby-Authorization", `MediaBrowser Client="MMTL", Device="MMTL-Federated", DeviceId="mmtl-federated", Version="1.0"`)
|
||||
req.Header.Set("X-Emby-Authorization", `MediaBrowser Client="MeBox", Device="MeBox-Federated", DeviceId="mebox-federated", Version="1.0"`)
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -234,7 +234,7 @@ func (p *embyProvider) List(ctx context.Context, dirID string) ([]FileEntry, err
|
||||
}
|
||||
|
||||
// Resolve 返回远程 Emby 直链。Proxy=true 时由调用方(StrmService.ProxyDirect)
|
||||
// 反向代理流量;false 时 302 到直链。默认不代理(播放字节不经过 MMTL)。
|
||||
// 反向代理流量;false 时 302 到直链。默认不代理(播放字节不经过 MeBox)。
|
||||
func (p *embyProvider) Resolve(ctx context.Context, fileRef string) (*DirectLink, error) {
|
||||
token, err := p.ensureToken(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/service/cloud115"
|
||||
"github.com/truewhile/MeBox/internal/service/cloud115"
|
||||
)
|
||||
|
||||
// OpenAPI115Provider 暴露 115 开放平台驱动接口。
|
||||
@@ -112,7 +112,7 @@ func (p *openAPI115Provider) OpenClient() *cloud115.OpenClient { return p.c }
|
||||
// io.Reader 无法携带文件名,因此走独立的 named 上传接口。将内容落为临时文件后
|
||||
// 重命名为目标文件名,再交给 115 上传(/open/upload/init 的 file_name 取真实文件名)。
|
||||
func (p *openAPI115Provider) PutFileNamed(ctx context.Context, parentCID, fileName string, r io.Reader) error {
|
||||
tmp, err := os.CreateTemp("", "mmtl-upload-*")
|
||||
tmp, err := os.CreateTemp("", "mebox-upload-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("115: 创建临时文件失败:%w", err)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ const (
|
||||
)
|
||||
|
||||
// DefaultUA 请求 UA(标准 API 客户端格式,避免伪装浏览器触发 115 WAF 拦截与 405 错误)。
|
||||
const DefaultUA = "MMTL-115-GoClient/1.0"
|
||||
const DefaultUA = "MeBox-115-GoClient/1.0"
|
||||
|
||||
// OpenAPIError 保留 115 开放平台返回的原始错误信息。
|
||||
type OpenAPIError struct {
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// CloudMountInfo 是云盘挂载库的规范标识(因网盘后端已移除,仅保留类型以兼容
|
||||
|
||||
@@ -29,12 +29,12 @@ import (
|
||||
|
||||
// dandanplayObfuscationKey 是解开内置凭据的 XOR 混淆密钥。它与密文
|
||||
// 同处一个二进制,仅作提取门槛,不作安全边界。
|
||||
const dandanplayObfuscationKey = "MMTL-Danmaku#2026!v2"
|
||||
const dandanplayObfuscationKey = "MeBox-Danmaku#2026!v2"
|
||||
|
||||
// 内置回退凭据(XOR 混淆后的 hex 编码)。
|
||||
const (
|
||||
danmakuEmbeddedAppIDHex = "352c24755f7c115d0a52"
|
||||
danmakuEmbeddedAppKeyHex = "74390d751832375917150d4717586578586620652a05043d5e320f0d0b120d38"
|
||||
danmakuEmbeddedAppIDHex = "350432560a153452095e"
|
||||
danmakuEmbeddedAppKeyHex = "74111b564d5b1256141907594149677a5c717721550535331c0e4327071d0b2c"
|
||||
)
|
||||
|
||||
// danmakuEmbeddedCredentials 解出内置回退凭据。
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/service/cloud"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/service/cloud"
|
||||
)
|
||||
|
||||
// overrideDanmakuOfficialBase points the "official" endpoint (match + fallback)
|
||||
@@ -34,7 +34,7 @@ func overrideDanmakuOfficialBase(t *testing.T, base string) {
|
||||
// since the file is smaller than the 16MB prefix).
|
||||
func writeDanmakuTestVideo(t *testing.T, name string) (path, wantHash string) {
|
||||
t.Helper()
|
||||
content := bytes.Repeat([]byte("MMTL-danmaku-hash-test-0123456789"), 500)
|
||||
content := bytes.Repeat([]byte("MeBox-danmaku-hash-test-0123456789"), 500)
|
||||
path = filepath.Join(t.TempDir(), name)
|
||||
require.NoError(t, os.WriteFile(path, content, 0o644))
|
||||
sum := md5.Sum(content)
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// Danmaku setting keys, managed through the admin settings UI (PUT
|
||||
@@ -439,7 +439,7 @@ func (s *DanmakuService) fetchBody(ctx context.Context, sourceURL string, follow
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("User-Agent", "MMTL/danmaku (+https://github.com/ShukeBta/MMTL)")
|
||||
req.Header.Set("User-Agent", "MeBox/danmaku (+https://github.com/truewhile/MeBox)")
|
||||
req.Header.Set("Accept", "application/json, application/xml, */*")
|
||||
if appID, appKey, ok := s.danmakuCredentials(ctx, sourceURL); ok {
|
||||
// 签名认证:base64(sha256(AppId+Timestamp+Path+Secret)),密钥不出服务器。
|
||||
@@ -522,7 +522,7 @@ func (s *DanmakuService) hashCachePut(stamp, hash string) {
|
||||
}
|
||||
|
||||
// danmakuMatchFileName derives the /api/v2/match fileName: base name without
|
||||
// the final extension. .strm items are covered too — MMTL strm files drop the
|
||||
// the final extension. .strm items are covered too — MeBox strm files drop the
|
||||
// video extension ("xxx.strm") while pre-existing ones may keep it
|
||||
// ("xxx.mkv.strm") — so a second strip removes a real video extension only
|
||||
// (filepath.Ext would misread names like "xxx.第01话" as having an extension).
|
||||
@@ -636,7 +636,7 @@ func (s *DanmakuService) hashLocalFile(path string) (string, bool) {
|
||||
}
|
||||
|
||||
// hashStrmTarget computes the video hash behind a .strm indirection:
|
||||
// MMTL-internal /api/strm/play URLs are resolved through strmResolve (local
|
||||
// MeBox-internal /api/strm/play URLs are resolved through strmResolve (local
|
||||
// path read directly, cloud links range-fetched); plain http(s) links are
|
||||
// fetched directly. Only the 16MB prefix is ever downloaded.
|
||||
func (s *DanmakuService) hashStrmTarget(ctx context.Context, raw string) (string, bool) {
|
||||
@@ -707,7 +707,7 @@ func (s *DanmakuService) openRangeBody(ctx context.Context, target string, heade
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "MMTL/danmaku (+https://github.com/ShukeBta/MMTL)")
|
||||
req.Header.Set("User-Agent", "MeBox/danmaku (+https://github.com/truewhile/MeBox)")
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=0-%d", danmakuHashPrefixBytes-1))
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func newDanmakuTestService(t *testing.T) *DanmakuService {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/database"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/database"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// DatabaseAdminService manages database configuration, connectivity testing, and migration.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package service
|
||||
|
||||
import "github.com/ShukeBta/MMTL/internal/repository"
|
||||
import "github.com/truewhile/MeBox/internal/repository"
|
||||
|
||||
func IsTransientDatabaseLock(err error) bool {
|
||||
return repository.IsSQLiteBusyError(err)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// SweepInactiveUsers is kept for compatibility with the existing scheduler; it
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// KickDevice marks a device as kicked so the next request from it is rejected
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// DeviceService implements device/session tracking and the two decoupled
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// service to start playback of an HTTP URL.
|
||||
//
|
||||
// We do NOT mediate the renderer ↔ client traffic; the renderer pulls
|
||||
// the bytes directly from MMTL's /api/stream endpoint, so
|
||||
// the bytes directly from MeBox's /api/stream endpoint, so
|
||||
// the cast call only ever transports a URL string.
|
||||
package service
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
// DoubanProvider talks to the unofficial Douban movie API.
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// ImageURL returns artwork for a media/series/season item id.
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestImageURLFallsBackToLibraryCoverURL(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// EmbyService produces JSON envelopes shaped like the most-consumed
|
||||
// Emby-API endpoints so existing players (Infuse / Yamby / Hills /
|
||||
// Senplayer / Kodi NextPVR extension / iOS native clients) can talk to
|
||||
// MMTL without a custom plugin.
|
||||
// MeBox without a custom plugin.
|
||||
//
|
||||
// The shim is read-mostly: items, images, playback are fully covered;
|
||||
// 播放进度上报 / 收藏切换 是写路径但走我们自己的 PlaybackHistory /
|
||||
@@ -17,14 +17,14 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 用一个固定的 ServerId 字符串。Emby 客户端会缓存这个 id,第一次见到
|
||||
// 该 id 后会把所有派生数据(cookie/收藏/历史)和它绑定。
|
||||
const embyServerID = "mmtl-001"
|
||||
const embyServerID = "mebox-001"
|
||||
|
||||
// embyCompatVersion deliberately reports an Emby 4.x server. Official Emby
|
||||
// clients reject Jellyfin-style 10.x identities as unsupported/too old during
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func newTestEmbyService(t *testing.T) *EmbyService {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// Item 单条目详情。
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func (e *EmbyService) mediaItems(ctx context.Context, p ItemsParams) (map[string]any, error) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func (e *EmbyService) mediaSourcesForItem(ctx context.Context, m *model.Media, asEmbedded, directOnly bool) []map[string]any {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// movieLibraryHasEpisodicContent 报告电影类型库里是否混入了「剧集结构」内容
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestEmbyMovieLibrarySeasonNumbersStayMovies(t *testing.T) {
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// PlaybackInfo returns a PlaybackInfoResponse usable by Emby clients.
|
||||
// 远程 Emby 条目直接转发远程 PlaybackInfo,并按账号 proxy_play 配置决定
|
||||
// 播放地址指向远程(直连)还是 MMTL 本地代理端点。
|
||||
// 播放地址指向远程(直连)还是 MeBox 本地代理端点。
|
||||
func (e *EmbyService) PlaybackInfo(ctx context.Context, mediaID, userID string) (map[string]any, error) {
|
||||
if e.remote != nil && IsEmbyRemoteID(mediaID) {
|
||||
mountID, remoteID, _ := DecodeEmbyRemoteID(mediaID)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestEmbyRootItemsExposeLibraries(t *testing.T) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// EmbyRemoteService 是「远程 Emby 联邦聚合」核心:把挂载的远程 Emby 服务器
|
||||
// 作为外部媒体源,通过 MMTL 的 Emby 兼容 API 透出。
|
||||
// 作为外部媒体源,通过 MeBox 的 Emby 兼容 API 透出。
|
||||
//
|
||||
// 设计要点:
|
||||
// - 远程媒体的元数据完全不落库:每次请求实时向远程 Emby 拉取;
|
||||
// - 条目 ID 用 embyremote~{accountID}~{remoteID} 伪装(见 emby_remote_ids.go),
|
||||
// 客户端拿伪装 ID 回来时按账号路由回远程;
|
||||
// - 播放分流由账号级 proxy_play 配置决定:
|
||||
// 不代理(默认)= MediaSource 下发热门远程绝对 URL,播放字节完全不经过 MMTL;
|
||||
// 代理 = 下发 MMTL 本地 /Videos/{encoded} 端点,由 ProxyVideoStream 反向拉流。
|
||||
// 不代理(默认)= MediaSource 下发热门远程绝对 URL,播放字节完全不经过 MeBox;
|
||||
// 代理 = 下发 MeBox 本地 /Videos/{encoded} 端点,由 ProxyVideoStream 反向拉流。
|
||||
//
|
||||
// 配置复用 STRM 账号体系(StrmAccount.Provider = emby_remote),CRUD/加密/连通
|
||||
// 测试全部走既有 /admin/strm/accounts 接口,不需要新增数据表。
|
||||
@@ -30,9 +30,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// embyRemoteHTTPTimeout 远程 Emby 常规 API 请求超时(流式代理不在此列)。
|
||||
@@ -63,7 +63,7 @@ type EmbyRemoteConfig struct {
|
||||
Password string
|
||||
Token string // api_key(手动填写或自动认证获得)
|
||||
RemoteUserID string // 远程用户 Id(自动认证后回填)
|
||||
ProxyPlay bool // true=播放流量经 MMTL 反向代理;false=客户端直连远程
|
||||
ProxyPlay bool // true=播放流量经 MeBox 反向代理;false=客户端直连远程
|
||||
}
|
||||
|
||||
// EmbyRemoteService 提供对远程 Emby 服务器的读写封装。
|
||||
@@ -401,7 +401,7 @@ func (r *EmbyRemoteService) ensureTokenOnLine(ctx context.Context, acct *model.S
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Emby-Authorization", `MediaBrowser Client="MMTL", Device="MMTL-Federated", DeviceId="mmtl-federated", Version="1.0"`)
|
||||
req.Header.Set("X-Emby-Authorization", `MediaBrowser Client="MeBox", Device="MeBox-Federated", DeviceId="mebox-federated", Version="1.0"`)
|
||||
resp, err := r.http.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接远程 Emby 失败: %w", err)
|
||||
@@ -699,7 +699,7 @@ func (r *EmbyRemoteService) RemoteLatest(ctx context.Context, mount *model.EmbyM
|
||||
}
|
||||
|
||||
// RemotePlaybackInfo 拉取远程 PlaybackInfo,并按挂载的 proxy_play 配置重写
|
||||
// 播放 URL:不代理=指向远程绝对地址(播放字节不过 MMTL);代理=指向 MMTL
|
||||
// 播放 URL:不代理=指向远程绝对地址(播放字节不过 MeBox);代理=指向 MeBox
|
||||
// 本地 /Videos/{encodedID} 端点(由 ProxyVideoStream 反代)。
|
||||
func (r *EmbyRemoteService) RemotePlaybackInfo(ctx context.Context, mount *model.EmbyMount, acct *model.StrmAccount, remoteID, userID string) (map[string]any, error) {
|
||||
cfg, err := r.configOf(acct)
|
||||
@@ -719,7 +719,7 @@ func (r *EmbyRemoteService) RemotePlaybackInfo(ctx context.Context, mount *model
|
||||
|
||||
// rewritePlayURLs 按挂载代理模式重写载荷内 MediaSources 的播放地址。
|
||||
// 远程 Emby 的 PlaybackInfo 通常不返回 DirectStreamUrl(客户端靠它拼
|
||||
// /Videos/{Id}/stream),因此这里总是强制构造播放地址,完全由 MMTL 掌控
|
||||
// /Videos/{Id}/stream),因此这里总是强制构造播放地址,完全由 MeBox 掌控
|
||||
// 直连(远程绝对 URL)或代理(本地 /Videos/{encoded})的最终去向。
|
||||
func (r *EmbyRemoteService) rewritePlayURLs(value any, mount *model.EmbyMount, cfg *EmbyRemoteConfig, remoteID string) {
|
||||
encoded := EncodeEmbyRemoteID(mount.ID, remoteID)
|
||||
@@ -979,7 +979,7 @@ func (r *EmbyRemoteService) proxySubtitleOnLine(ctx context.Context, w http.Resp
|
||||
|
||||
// ─── 播放状态透传 ──────────────────────────────────────────────────────────────
|
||||
|
||||
// ProxySetPlayed 把「已看/未看」状态透传到远程 Emby(MMTL 本地不落库)。
|
||||
// ProxySetPlayed 把「已看/未看」状态透传到远程 Emby(MeBox 本地不落库)。
|
||||
func (r *EmbyRemoteService) ProxySetPlayed(ctx context.Context, acct *model.StrmAccount, remoteID string, played bool) error {
|
||||
cfg, err := r.configOf(acct)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 远程 Emby 聚合的 ID 伪装。
|
||||
//
|
||||
// MMTL 作为 Emby 联邦网关把多个远程 Emby 服务器的媒体库透明聚合到自身的
|
||||
// MeBox 作为 Emby 联邦网关把多个远程 Emby 服务器的媒体库透明聚合到自身的
|
||||
// Emby API 之下,远程条目完全不落库。为了把本地 ID 与多个远程服务器的 ID
|
||||
// 隔离开,远程条目在返回给客户端之前统一被改写为:
|
||||
//
|
||||
@@ -42,7 +42,7 @@ func DecodeEmbyRemoteID(id string) (accountID, remoteID string, ok bool) {
|
||||
}
|
||||
|
||||
// embyRemoteStringIDs 是条目 JSON 中需要伪装(编码)成远程 ID 的字符串字段。
|
||||
// 图片 / 详情 / 播放请求都会以这些字段的值作为 ID 回指 MMTL。
|
||||
// 图片 / 详情 / 播放请求都会以这些字段的值作为 ID 回指 MeBox。
|
||||
var embyRemoteStringIDs = []string{
|
||||
"Id",
|
||||
"ParentId",
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// EmbyRemoteLine 表示同一 Emby 服务器的一条接入线路(内网/外网/CDN 等)。
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// rewriteSubtitleDeliveryURLs 只应改动字幕轨道的 DeliveryUrl,其余媒体流不动。
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// RemoteLibraryView 是一个网页端可见的远程媒体库(对应一个挂载的远程 View)。
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestMapRemoteItemToMediaSortingFields(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
type embySeriesGroup struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestEmbyItemsExposeSeriesSeasonEpisodeHierarchy(t *testing.T) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func (e *EmbyService) seriesIDForMedia(m *model.Media) string {
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// writeTempVideoWithSubtitle creates a temp directory with a fake video and a
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// SystemInfo returns the full Emby identity payload.
|
||||
@@ -13,7 +13,7 @@ func (e *EmbyService) SystemInfo() map[string]any {
|
||||
return map[string]any{
|
||||
"Id": embyServerID,
|
||||
"ServerId": embyServerID,
|
||||
"ServerName": "MMTL",
|
||||
"ServerName": "MeBox",
|
||||
"Version": embyCompatVersion,
|
||||
"ServerVersion": embyCompatVersion,
|
||||
"ProductName": "Emby Server",
|
||||
@@ -42,7 +42,7 @@ func (e *EmbyService) SystemInfoPublic() map[string]any {
|
||||
return map[string]any{
|
||||
"Id": embyServerID,
|
||||
"ServerId": embyServerID,
|
||||
"ServerName": "MMTL",
|
||||
"ServerName": "MeBox",
|
||||
"Version": embyCompatVersion,
|
||||
"ServerVersion": embyCompatVersion,
|
||||
"ProductName": "Emby Server",
|
||||
@@ -85,7 +85,7 @@ func (e *EmbyService) userPayload(u *model.User) map[string]any {
|
||||
"Id": u.ID,
|
||||
"Name": u.Username,
|
||||
"ServerId": embyServerID,
|
||||
"ServerName": "MMTL",
|
||||
"ServerName": "MeBox",
|
||||
"HasPassword": true,
|
||||
"HasConfiguredPassword": true,
|
||||
"HasConfiguredEasyPassword": false,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// SetFavorite 把 mediaID 标为 userID 的收藏。远程 Emby 条目直接透传到对应
|
||||
@@ -121,9 +121,9 @@ func (e *EmbyService) RecordProgress(ctx context.Context, userID, mediaID string
|
||||
return err
|
||||
}
|
||||
|
||||
// mergeRemoteUserData applies the current MMTL user's locally recorded playback
|
||||
// mergeRemoteUserData applies the current MeBox user's locally recorded playback
|
||||
// state to remote Emby payloads. Remote metadata remains authoritative unless the
|
||||
// user has played the item through MMTL.
|
||||
// user has played the item through MeBox.
|
||||
func (e *EmbyService) mergeRemoteUserData(ctx context.Context, userID string, payload any) error {
|
||||
if strings.TrimSpace(userID) == "" || payload == nil {
|
||||
return nil
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestMergedRemoteUserData(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestEmbyLatestItemsCollapsesMovieVersions(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func (e *EmbyService) applyUserMediaVisibility(ctx context.Context, q *gorm.DB, userID string) *gorm.DB {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// pollutedEpisodeCleanupSettingKey marks that the one-time normalization has run.
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func newCleanupTestContainer(t *testing.T) (*Container, *gorm.DB) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
// FanartProvider talks to https://webservice.fanart.tv.
|
||||
@@ -133,7 +133,7 @@ func (f *FanartProvider) getJSON(ctx context.Context, u string, out any) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "MMTL/0.1")
|
||||
req.Header.Set("User-Agent", "MeBox/0.1")
|
||||
resp, err := f.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/ulikunitz/xz"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
// ffmpegDownloadTarget 描述某个平台对应的官方构建下载源。
|
||||
@@ -121,7 +121,7 @@ func installFFmpegTools(ctx context.Context, log *zap.Logger, cfg *config.Config
|
||||
return "", "", fmt.Errorf("创建安装目录失败: %w", err)
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "mmtl-ffmpeg-*")
|
||||
tempDir, err := os.MkdirTemp("", "mebox-ffmpeg-*")
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("创建临时目录失败: %w", err)
|
||||
}
|
||||
@@ -202,7 +202,7 @@ func downloadFFmpegFile(ctx context.Context, log *zap.Logger, url, dest string)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "MMTL/ffmpeg-installer ("+runtime.GOOS+"/"+runtime.GOARCH+")")
|
||||
req.Header.Set("User-Agent", "MeBox/ffmpeg-installer ("+runtime.GOOS+"/"+runtime.GOARCH+")")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// FFmpegToolsService 管理 ffmpeg/ffprobe 的自动下载安装状态(单飞:同一时间
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
func TestFFmpegTargetForPlatform(t *testing.T) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
// FFprobeService wraps the external ffprobe binary.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
func TestFFprobeServiceDefaultsToSingleConcurrentProbe(t *testing.T) {
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// FileManagerService browses and mutates the server-side filesystem.
|
||||
|
||||
@@ -67,8 +67,8 @@ func (s *FileManagerService) allowedRoots() (map[string]string, error) {
|
||||
add("movies", s.cfg.Media.MoviesDir)
|
||||
add("tv", s.cfg.Media.TVDir)
|
||||
add("anime", s.cfg.Media.AnimeDir)
|
||||
add("downloads", envOrDefault("MMTL_DOWNLOAD_CONTAINER_DIR", "/downloads"))
|
||||
add("media", envOrDefault("MMTL_MEDIA_CONTAINER_DIR", "/media"))
|
||||
add("downloads", envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads"))
|
||||
add("media", envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media"))
|
||||
if s.repo != nil && s.repo.Setting != nil {
|
||||
addSetting := func(label, key string) {
|
||||
if value, err := s.repo.Setting.Get(context.Background(), key); err == nil {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func newFileManagerTestServiceWithRepo(t *testing.T, root string) (*FileManagerService, *repository.Container) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// Some deployments cannot reach image.tmdb.org directly (GFW, internal-only
|
||||
// networks). ImageProxy fronts a remote image URL so the browser only ever
|
||||
// talks to the MMTL origin. The proxy:
|
||||
// talks to the MeBox origin. The proxy:
|
||||
//
|
||||
// - validates the URL scheme is http/https,
|
||||
// - streams bytes through with a small disk cache under cache/images,
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
// ImageProxy fetches and caches remote images on behalf of the browser.
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
func TestImageProxyDoesNotNegativeCacheFailedRemoteImageFetch(t *testing.T) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
)
|
||||
|
||||
var testJPEG = []byte{
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
var availabilityNoiseRE = regexp.MustCompile(`(?i)(自动订阅|订阅|全集|合集|complete|batch|s\d{1,2}e\d{1,3}|season\s*\d+|s\d{1,2}|第\s*\d+\s*季|第\s*\d+\s*[集话話期]|\(\d{4}\)|\b\d{4}\b|2160p|1080p|720p|4k|uhd|bluray|blu-ray|web-?dl|hdtv|remux|x26[45]|h\.?26[45]|hevc|avc|hdr10?\+?|dovi|dv|atmos|aac|ddp?5\.1|truehd|flac)`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package service
|
||||
|
||||
import "github.com/ShukeBta/MMTL/internal/model"
|
||||
import "github.com/truewhile/MeBox/internal/model"
|
||||
|
||||
func applyLocalMetadata(m *model.Media, local *LocalMetadata) {
|
||||
applyLocalIdentityMetadata(m, local)
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func TestApplyLocalMetadataPreservesPathHintAsPending(t *testing.T) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
type ManualScrapeRequest struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
type manualTMDbCandidate struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func (s *ScraperService) ManualSearch(ctx context.Context, media *model.Media, query, provider, mediaType string) ([]ExternalMediaResult, error) {
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestManualRequestMatchFallsBackToCandidatePayload(t *testing.T) {
|
||||
|
||||
@@ -4,9 +4,9 @@ package service
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// MediaService offers high-level CRUD over libraries and media items.
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
type mediaListCacheValue struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func (s *MediaService) attachLibraryMetadata(ctx context.Context, items []model.Media) {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
type LibraryRootInput struct {
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestCreateLibraryWithRootsAppendsToExistingLogicalLibrary(t *testing.T) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestListLibrariesWithPreview(t *testing.T) {
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// ListMedia paginates media items inside a library.
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
type MediaMetadataUpdate struct {
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestUpdateMediaMetadataMarksManualMatch(t *testing.T) {
|
||||
|
||||
@@ -69,8 +69,8 @@ func embeddedContainerMarkerCandidates(input string) []string {
|
||||
part string
|
||||
container string
|
||||
}{
|
||||
{part: "/media", container: envOrDefault("MMTL_MEDIA_CONTAINER_DIR", "/media")},
|
||||
{part: "/downloads", container: envOrDefault("MMTL_DOWNLOAD_CONTAINER_DIR", "/downloads")},
|
||||
{part: "/media", container: envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media")},
|
||||
{part: "/downloads", container: envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads")},
|
||||
} {
|
||||
part := strings.TrimRight(marker.part, "/")
|
||||
container := strings.TrimRight(filepath.ToSlash(marker.container), "/")
|
||||
@@ -83,17 +83,17 @@ func embeddedContainerMarkerCandidates(input string) []string {
|
||||
|
||||
// describeUnresolvedLibraryPath 把「路径解析失败」变成可操作的诊断信息:列出尝试过的
|
||||
// 候选路径与当前的宿主机→容器映射状态。旧库存的是宿主机路径,在新版/容器内常因为缺少
|
||||
// MMTL_MEDIA_DIR 映射而扫不出媒体——这条诊断帮助用户直接定位到底哪一步断了。
|
||||
// MEBOX_MEDIA_DIR 映射而扫不出媒体——这条诊断帮助用户直接定位到底哪一步断了。
|
||||
// 仅用于日志与扫描错误提示,不改变解析逻辑本身。
|
||||
func describeUnresolvedLibraryPath(rawPath string) string {
|
||||
rawPath = strings.TrimSpace(rawPath)
|
||||
candidates := mappedPathCandidates(rawPath)
|
||||
mediaHost := strings.TrimSpace(os.Getenv("MMTL_MEDIA_DIR"))
|
||||
mediaContainer := envOrDefault("MMTL_MEDIA_CONTAINER_DIR", "/media")
|
||||
mediaHost := strings.TrimSpace(os.Getenv("MEBOX_MEDIA_DIR"))
|
||||
mediaContainer := envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media")
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "媒体库路径无法解析为可访问目录:%s(已尝试候选:%s)", rawPath, strings.Join(candidates, " | "))
|
||||
if mediaHost == "" {
|
||||
b.WriteString(";未配置 MMTL_MEDIA_DIR / MMTL_MEDIA_CONTAINER_DIR。若为 Docker 部署且此库为旧宿主机路径,请设置这两个变量把宿主机路径映射到容器内路径,并确认对应 volume 已挂载。")
|
||||
b.WriteString(";未配置 MEBOX_MEDIA_DIR / MEBOX_MEDIA_CONTAINER_DIR。若为 Docker 部署且此库为旧宿主机路径,请设置这两个变量把宿主机路径映射到容器内路径,并确认对应 volume 已挂载。")
|
||||
} else {
|
||||
fmt.Fprintf(&b, ";当前映射 %s → %s。请确认该库路径位于此宿主机目录下,或补充对应的 volume 与路径映射。", mediaHost, mediaContainer)
|
||||
}
|
||||
@@ -204,8 +204,8 @@ func dockerVolumePathCandidates(path string) []string {
|
||||
env string
|
||||
container string
|
||||
}{
|
||||
{env: "MMTL_MEDIA_DIR", container: envOrDefault("MMTL_MEDIA_CONTAINER_DIR", "/media")},
|
||||
{env: "MMTL_DOWNLOAD_DIR", container: envOrDefault("MMTL_DOWNLOAD_CONTAINER_DIR", "/downloads")},
|
||||
{env: "MEBOX_MEDIA_DIR", container: envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media")},
|
||||
{env: "MEBOX_DOWNLOAD_DIR", container: envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads")},
|
||||
} {
|
||||
container := cleanPathForVolumeMapping(mapping.container)
|
||||
if container == "." || container == "" || strings.HasPrefix(container, ".") {
|
||||
@@ -247,8 +247,8 @@ func dockerVolumePathCandidates(path string) []string {
|
||||
part string
|
||||
container string
|
||||
}{
|
||||
{part: "/media", container: envOrDefault("MMTL_MEDIA_CONTAINER_DIR", "/media")},
|
||||
{part: "/downloads", container: envOrDefault("MMTL_DOWNLOAD_CONTAINER_DIR", "/downloads")},
|
||||
{part: "/media", container: envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media")},
|
||||
{part: "/downloads", container: envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads")},
|
||||
} {
|
||||
part := strings.TrimRight(marker.part, "/")
|
||||
container := strings.TrimRight(filepath.ToSlash(marker.container), "/")
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
const maxRecycleBinRecords = 200
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestListRecycleBinPrunesOldRowsOverLimit(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
func mediaReleaseSortTime(media model.Media) time.Time {
|
||||
|
||||
@@ -3,8 +3,8 @@ package service
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
// SearchMedia performs a simple LIKE search across titles.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
type seriesCardsCacheValue struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
var episodicPathRE = regexp.MustCompile(`(?i)[\\/](?:电视剧|剧集|连续剧|短剧|国产剧|国剧|大陆剧|华语剧|国产电视剧|大陆电视剧|华语电视剧|欧美剧|欧美电视剧|美剧|英剧|日韩剧|日韩电视剧|日剧|韩剧|港剧|台剧|港台剧|泰剧|综艺|纪录片|儿童|动漫|番剧|国漫|日番|韩漫|美漫|欧美动漫|欧美动画|其他动漫|tv|series|shows?|season[\s._-]*\d|s\d{1,2}(?:[\s._-]|[\\/])|special[\s._-]*episodes?|specials?|sp|ovas?|oads?|extras?|bonus(?:es)?|omake|特别篇|特別篇|番外篇?|特典|外传|外傳|总集篇|總集篇)[\\/]`)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
type mediaSeriesKeyResolver struct {
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/config"
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/config"
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
)
|
||||
|
||||
func TestListRecentSeriesCardsCountsAllEpisodesInSeries(t *testing.T) {
|
||||
|
||||
@@ -14,8 +14,8 @@ func TestResolveAccessibleLibraryPathMapsConfiguredHostMediaDir(t *testing.T) {
|
||||
if err := os.MkdirAll(containerLibrary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_MEDIA_DIR", hostRoot)
|
||||
t.Setenv("MMTL_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
t.Setenv("MEBOX_MEDIA_DIR", hostRoot)
|
||||
t.Setenv("MEBOX_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
got, err := resolveAccessibleLibraryPath(filepath.Join(hostRoot, "电视剧", "国产剧"))
|
||||
if err != nil {
|
||||
@@ -33,8 +33,8 @@ func TestResolveAccessibleLibraryPathMapsWindowsDriveBeforeLinuxAbs(t *testing.T
|
||||
if err := os.MkdirAll(containerLibrary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_MEDIA_DIR", `Q:\media`)
|
||||
t.Setenv("MMTL_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
t.Setenv("MEBOX_MEDIA_DIR", `Q:\media`)
|
||||
t.Setenv("MEBOX_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
for _, input := range []string{
|
||||
`Q:\media\电视剧\国产剧`,
|
||||
@@ -61,8 +61,8 @@ func TestResolveAccessibleLibraryPathRecoversDockerPollutedWindowsDrive(t *testi
|
||||
if err := os.MkdirAll(containerLibrary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_MEDIA_DIR", `F:\media`)
|
||||
t.Setenv("MMTL_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
t.Setenv("MEBOX_MEDIA_DIR", `F:\media`)
|
||||
t.Setenv("MEBOX_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
got, err := resolveAccessibleLibraryPath(`/app/F:\media\电视剧\国产剧`)
|
||||
if err != nil {
|
||||
@@ -95,7 +95,7 @@ func TestResolveAccessibleLibraryPathMapsRelativeDockerMediaMarker(t *testing.T)
|
||||
if err := os.MkdirAll(containerLibrary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
t.Setenv("MEBOX_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
got, err := resolveAccessibleLibraryPath(filepath.Join("media", "电视剧", "国产剧"))
|
||||
if err != nil {
|
||||
@@ -116,7 +116,7 @@ func TestResolveAccessibleLibraryPathMapsEmbeddedHostMediaMarker(t *testing.T) {
|
||||
if err := os.MkdirAll(containerLibrary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
t.Setenv("MEBOX_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
got, err := resolveAccessibleLibraryPath("/vol1/1000/Docker/moviepilot-v2/media/电视剧/国产剧")
|
||||
if err != nil {
|
||||
@@ -134,7 +134,7 @@ func TestResolveAccessibleMappedPathMapsEmbeddedHostDownloadMarker(t *testing.T)
|
||||
if err := os.MkdirAll(containerItem, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_DOWNLOAD_CONTAINER_DIR", containerDownloads)
|
||||
t.Setenv("MEBOX_DOWNLOAD_CONTAINER_DIR", containerDownloads)
|
||||
|
||||
got, _, err := resolveAccessibleMappedPath("/vol1/1000/Docker/qbittorrent/downloads/国产剧")
|
||||
if err != nil {
|
||||
@@ -180,7 +180,7 @@ func TestMappedPathCandidatesMapWindowsDriveDownloadMarker(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
containerDownloads := filepath.Join(root, "container", "downloads")
|
||||
containerLibrary := filepath.Join(containerDownloads, "国产剧")
|
||||
t.Setenv("MMTL_DOWNLOAD_CONTAINER_DIR", containerDownloads)
|
||||
t.Setenv("MEBOX_DOWNLOAD_CONTAINER_DIR", containerDownloads)
|
||||
|
||||
want := filepath.Clean(containerLibrary)
|
||||
for _, got := range mappedPathCandidates(`F:\downloads\国产剧`) {
|
||||
@@ -197,8 +197,8 @@ func TestResolveMappedDestinationPathPrefersConfiguredContainerMapping(t *testin
|
||||
if err := os.MkdirAll(containerMedia, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_MEDIA_DIR", `Q:\media`)
|
||||
t.Setenv("MMTL_MEDIA_CONTAINER_DIR", containerMedia)
|
||||
t.Setenv("MEBOX_MEDIA_DIR", `Q:\media`)
|
||||
t.Setenv("MEBOX_MEDIA_CONTAINER_DIR", containerMedia)
|
||||
|
||||
for _, input := range []string{`Q:\media`, `Q:/media`, `/app/Q:\media`} {
|
||||
t.Run(input, func(t *testing.T) {
|
||||
@@ -221,7 +221,7 @@ func TestResolveMappedDestinationPathPrefersContainerForRelativeMediaMarker(t *t
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Chdir(workDir)
|
||||
t.Setenv("MMTL_MEDIA_CONTAINER_DIR", containerMedia)
|
||||
t.Setenv("MEBOX_MEDIA_CONTAINER_DIR", containerMedia)
|
||||
|
||||
got := resolveMappedDestinationPath(filepath.Join("media", "电视剧", "国产剧"))
|
||||
want := filepath.Join(containerMedia, "电视剧", "国产剧")
|
||||
@@ -237,8 +237,8 @@ func TestResolveAccessibleMappedPathMapsWindowsDownloadVariants(t *testing.T) {
|
||||
if err := os.MkdirAll(containerSource, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MMTL_DOWNLOAD_DIR", `Q:\downloads`)
|
||||
t.Setenv("MMTL_DOWNLOAD_CONTAINER_DIR", containerDownloads)
|
||||
t.Setenv("MEBOX_DOWNLOAD_DIR", `Q:\downloads`)
|
||||
t.Setenv("MEBOX_DOWNLOAD_CONTAINER_DIR", containerDownloads)
|
||||
|
||||
for _, input := range []string{`Q:\downloads\国产剧`, `Q:/downloads/国产剧`, `/app/Q:\downloads\国产剧`} {
|
||||
t.Run(input, func(t *testing.T) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user