Commit Graph

73 Commits

Author SHA1 Message Date
soldosluka857 8fd2ab4b51 feat(playback): client direct-play decode mode (release host transcoding)
Add an admin toggle playback.direct_only that offloads all decoding to the
client. When enabled:
- Emby PlaybackInfo no longer advertises SupportsTranscoding nor a
  TranscodingUrl, forcing Emby/Infuse/Yamby clients to direct play.
- HLS endpoints refuse (ErrTranscodeDisabled) so the host never spawns ffmpeg.
- Web player forces direct play, hides the HLS toggle, and surfaces a hint.
Exposed via /system/info (direct_play_only) and the Settings page.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 20:23:55 +08:00
soldosluka857 f0bc066134 feat(telegram): admin-toggleable Bot registration + remove duplicate 最近入库 on 运行状态
- Add telegram.registration_enabled setting (default off); admins toggle via
  Settings page or /registration on|off bot command.
- Add /register (/reg /signup) bot command: when enabled, regular users can
  create a MediaStation account and auto-bind their Telegram. Reuses AuthService
  (username-taken / user-limit handling) and keeps new accounts as regular users.
- Regular users still limited to bind / adult-toggle / start / help; all other
  commands remain admin-only.
- Update /start and /help text to surface registration when enabled.
- Remove the duplicate 最近入库 module from the 运行状态 (stats) page; the homepage
  already shows recently added.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 18:34:00 +08:00
soldosluka857 d03f84e78d feat(organize): organize arbitrary source dir (e.g. downloads) with dedup + 洗版
- Add OrganizeDirectory service: walk an arbitrary source directory (download
  dir / NAS direct-read path) and organize video files into the destination,
  without requiring the source to be a registered library.
- Dedup: skip media already present in the destination (matched by scanned DB
  identity title+year[/season+episode], robust to dir case/layout, plus a
  filesystem folder fallback).
- 洗版 (resolution replacement): when the source resolution is higher than the
  existing version, replace the lower-res file (+NFO sidecar +DB row). Prefers
  scanned dimensions, then ffprobe, then filename token; never replaces on
  unknown resolution.
- New endpoints: GET /admin/organize/sources (download/media dir candidates)
  and POST /admin/organize/source.
- UI: ToolsPage adds a '整理来源目录(去重+洗版)' form so operators can pick the
  download dir as the organize source.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 16:29:15 +08:00
soldosluka857 7cc59f095c fix(auth/images): long-lived Emby token, unthrottle refresh, serve library posters
Three regressions reported on third-party clients and the web UI:

- Third-party clients (Emby/Jellyfin) dropped login / could not play /
  could not refresh the library, roughly hourly. The Emby
  AuthenticateByName response returned the 60-minute access token, but
  Emby clients have no refresh mechanism and reuse the AccessToken until
  logout. Issue a long-lived (30d) token for the Emby compat layer via
  AuthService.IssueEmbyToken so device sessions persist.

- Web could be thrown back to login under load: /auth/refresh was inside
  the IP rate-limited /auth group, so multiple users/tabs behind one
  reverse-proxy/NAT IP exhausted the budget and refresh failed -> logout.
  Only login/register are rate-limited now (raised to 30/min for shared
  IPs); refresh is excluded (already protected by a one-time refresh token).

- Posters/images stopped displaying on the web home and other pages
  (refresh did not help). The SSRF/path hardening (a) blocked the image
  proxy whenever a hostname *resolved* to a private IP, which happens
  under GFW DNS poisoning of public CDNs like image.tmdb.org, and (b)
  restricted local image reads to data/cache/movies/tv/anime dirs only,
  dropping sidecar posters stored under arbitrary per-library roots to a
  placeholder. isPrivateHost now only blocks literal private/loopback IPs
  (real SSRF vectors) and ImageProxy also allows reads under configured
  library roots.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 15:16:04 +08:00
soldosluka857 8f0c6d2324 feat(organize): separate source dir from destination dir (从源目录整理到目的地目录)
Previously organize only exposed a single 'target dir' that was actually
the destination, conflating 源目录 (where files to organize live) with
目的地目录 (where organized files go). Add an explicit source directory:

- OrganizeOptions gains SourcePath; DestPath replaces the old TargetPath
  (destination) for clarity. New organize.source_dir setting + source_path
  request override; resolveSourceRoot falls back to library path.
- OrganizeLibraryWithOptions only organizes media located under the source
  root, so operators can point at a specific download/staging folder and
  organize into a distinct destination.
- Handler accepts source_path/dest_path (target_path kept as a deprecated
  alias for the destination, backward compatible).
- Settings page splits into 整理源目录(待整理) + 整理目的地目录; Tools organize
  panel exposes 源目录 + 目的地目录 inputs with clear copy.

Defaults are unchanged (source = destination = library path) so existing
setups behave identically. Adds a regression test that organize is scoped
to the source directory.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 13:02:36 +08:00
soldosluka857 b2ccd04844 fix(dlna): always return non-nil device slice so API serializes [] not null
The DLNA device list endpoint could return {"devices":null} in two cases:
the SSDP-failure path returned a nil slice, and the cached path copied an
empty cache via append([]DLNADevice(nil), cache...) which also yields nil.
The web UI reads res.devices.length, so a null devices field crashed the
DLNA page to a white screen. Return non-nil slices in both paths and guard
the frontend with a null coalesce. Adds a regression test.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 13:02:16 +08:00
ShukeBta 7e83047fdb chore: release MediaStationGo v0.0.31 2026-05-30 12:07:35 +08:00
soldosluka857 b9b7b7052a feat(organize/scan): transfer modes, seeding-safe relocation, inode dedup, incremental scanning
- Organizer: add move/copy/hardlink/symlink transfer modes (default move),
  per-request target_path/transfer_mode overrides, and honor organize.target_dir
  / organize.transfer_mode settings.
- keep_seeding (default on): escalate move->hardlink (cross-device->copy) so the
  qBittorrent source stays in place and continues seeding after organize.
- qBittorrent SetLocation + POST /downloads/relocate to migrate whole torrents
  while keeping them seeding.
- Scanner: FileID (device:inode) hardlink dedup to avoid duplicate recognition
  and double-counted storage; extract single-file ingest.
- Watcher: recursive watch + incremental per-file ingest/remove instead of full
  re-scan; periodic full library scan now gated behind scan.periodic_enabled
  (default off) to reduce disk wear.
- Telegram bot: handle callback_query in polling, declare allowed_updates in
  webhook, answer callbacks.
- Frontend: settings for transfer mode / keep_seeding / periodic scan; organize
  panel target dir + transfer mode overrides.
- Tests for transfer modes, organizer resolution, SetLocation, inode dedup,
  incremental ingest/remove.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 12:01:46 +08:00
soldosluka857 4e5797f1f4 fix: correct HTTP status codes and error handling in remaining handlers
- createPlaylistHandler: 200 → 201 Created, service errors → 500
- createSiteHandler: service/DB errors → 500 (was 400)
- saveStorageConfigHandler: service errors → 500 (was 400)
- SchedulerHandler.RunTask: task errors → 500 (was 400 with ErrInternal)
- aria2StatsHandler: service errors → 500 (was 400)
- organizeMediaHandler: service errors → 500 (was 400)
- testDownloadClientHandler: test failure → 200 with ok:false (was 400)
- testStorageConfigHandler: test failure → 200 with ok:false (was 400)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 10:52:28 +08:00
soldosluka857 260cc164a0 fix: additional HTTP status code and error handling corrections
- createBackupHandler: 200 → 201 Created
- addDownloadHandler: 200 → 201 Created, service errors → 500
- createAssistantSessionHandler: 200 → 201 Created
- writeUserMutationError: default error → 500 (was 400)
- resetUserPasswordHandler: service errors → 500, success → 204 NoContent
- createLibraryHandler: service errors → 500 (was 400)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 10:45:29 +08:00
soldosluka857 e97891175a fix: security hardening and HTTP status code corrections
- importSTRMHandler: add URL scheme validation (blocks file://, ftp://, etc.)
- backup Delete/Restore: harden path traversal check (block backslash, require .db extension)
- HTTP 201 for create endpoints: register, subscription, download client, notify channel, library, STRM import
- Error handling: return 500 for service/infra errors in download client and notify channel handlers

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 10:44:47 +08:00
soldosluka857 0572612833 fix: add field whitelist to site update, fix HTTP status codes in site handlers
- SiteService.Update: whitelist updatable fields to prevent injection of
  id, created_at, deleted_at, login_status, upload_bytes, download_bytes
- createSiteHandler: return 201 Created (was 200 OK)
- siteSearchHandler: return 500 for infra errors (was 400)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 10:31:54 +08:00
soldosluka857 2c45fa596a fix: improve play profile input validation, error responses, and HTTP status codes
- createPlayProfileHandler: return 201 Created (not 200) on success
- create/update handlers: return 500 for infra errors, 400 only for
  validation errors (using new ErrPlayProfileValidation sentinel)
- deletePlayProfileHandler: validate JSON body (was silently ignored)
- verifyPlayProfilePINHandler: validate JSON body (was silently ignored)
- verify handler catch-all: return 500 (not 400) for unexpected errors
- Service layer: wrap validation errors with ErrPlayProfileValidation
  so handlers can distinguish client vs server errors

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 10:19:57 +08:00
soldosluka857 5bbc9fadfe security: fix SSRF, restrict CORS, add rate limiting on auth endpoints
- Add isPrivateHost() to block image proxy requests to loopback/private/
  link-local IPs (SSRF mitigation)
- Add isAllowedLocalPath() to restrict local file reads to configured
  data/cache/media directories only
- CORS middleware now takes debug flag; wildcard only when debug=true,
  production omits headers (same-origin enforced)
- Add per-IP sliding-window rate limiter (10 req/min) on login/register
  and Emby AuthenticateByName endpoints

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 10:03:43 +08:00
ShukeBta d35086f49d fix: hide adult libraries across dashboard views 2026-05-30 03:50:52 +08:00
ShukeBta e8c2cf1ea4 fix: require password only for adult visibility changes 2026-05-30 03:45:01 +08:00
ShukeBta ce9abf6306 fix: repair user password reset and recreate flow 2026-05-30 03:33:36 +08:00
ShukeBta 99ecae0c44 fix: add global adult library visibility controls 2026-05-30 03:24:08 +08:00
ShukeBta ce8ffabba7 fix: ignore non-command telegram group messages 2026-05-30 03:04:30 +08:00
ShukeBta f8ef35c8e1 fix: stabilize telegram bot proxy and legacy permissions 2026-05-30 02:54:25 +08:00
ShukeBta 67ba1bdcce fix: clarify telegram member binding permissions 2026-05-30 02:19:22 +08:00
ShukeBta 3c946559f5 fix: simplify telegram channel access rules 2026-05-30 02:16:18 +08:00
ShukeBta 7e01c42857 fix: improve telegram channel connectivity 2026-05-30 02:07:09 +08:00
ShukeBta be6b8bf27f fix: tighten telegram channel binding 2026-05-30 01:54:26 +08:00
ShukeBta b5e11b6938 fix: secure adult visibility and telegram bot access 2026-05-30 01:39:11 +08:00
ShukeBta db65e54c45 fix: avoid duplicate subscription downloads 2026-05-29 17:39:57 +08:00
ShukeBta 99fe7329f7 fix: isolate play profiles per user 2026-05-29 15:56:51 +08:00
ShukeBta 633a8cf715 fix: enforce adult profile pin visibility 2026-05-29 15:16:20 +08:00
ShukeBta a8395c9de7 fix: enforce transcoding resource controls 2026-05-29 13:54:19 +08:00
ShukeBta 27df93fa3d feat: add licensing and access controls 2026-05-29 12:47:54 +08:00
ShukeBta 00b9c1bb79 fix: refresh library artwork previews 2026-05-29 10:13:34 +08:00
ShukeBta dfe2b3f017 fix: prioritize local poster artwork 2026-05-29 09:52:16 +08:00
ShukeBta b7ea68a67c fix: make qbittorrent login compatible 2026-05-29 04:15:44 +08:00
ShukeBta 2d90d9cba5 fix: improve qbittorrent host connectivity 2026-05-29 03:35:14 +08:00
ShukeBta 48d33275cb fix: keep existing library files in place 2026-05-29 03:04:27 +08:00
ShukeBta fc0dc70bad fix: avoid repeated category organization 2026-05-29 02:55:46 +08:00
ShukeBta 3031893156 fix: map docker host library paths 2026-05-29 02:44:52 +08:00
ShukeBta 00b5418fb0 feat: align smart media classification paths 2026-05-29 02:08:38 +08:00
ShukeBta c1b5dac784 feat: improve subscription and download cards 2026-05-28 23:27:34 +08:00
ShukeBta 05d49ca470 Add advanced discovery subscriptions 2026-05-28 22:21:48 +08:00
ShukeBta a666245c44 Fix subscription search to queue full series 2026-05-28 20:10:19 +08:00
ShukeBta b5f1f1954e Update deployment docs and media library UI 2026-05-28 17:32:55 +08:00
ShukeBta 68a5a1e3c0 Fix Emby playback routes and stream compatibility 2026-05-28 16:00:09 +08:00
ShukeBta e93eb40f4e Add commit message generation 2026-05-28 14:55:01 +08:00
ShukeBta f553674b71 Update commit message generation 2026-05-28 14:52:52 +08:00
ShukeBta 32ccb33fed Fix local adult metadata and watch history removal 2026-05-28 14:52:20 +08:00
ShukeBta 352edd86ed Fix series selection playback and query cleaning 2026-05-28 08:47:49 +08:00
ShukeBta 5baf9515ea TG Bot 命令交互 + UI 圆角色差深度优化 + 通知配置修复
## TG Bot 交互命令系统 (新增)
- telegram_bot.go: 命令路由 + /start /help /status /search /downloads /stats
- telegram_webhook.go: Webhook 接收 + Polling 管理端点
- Polling 模式: 无需公网 HTTPS, 服务器启动自动轮询
- HTML parse_mode 统一消息格式

## 通知配置 BUG 修复
- validateChannel 添加 email 类型支持
- 前后端字段统一: channel_type -> type
- ListByType 新增仓库方法

## UI 深度优化
- glass-panel: 添加 rounded-2xl + 可见边框 + padding
- card: 添加 p-4 sm:p-6
- 全局 rounded-md -> rounded-xl, 裸 rounded -> rounded-lg
- 500+ 深色硬编码类名清零
- text-gray-400 -> gray-500 色差提升
- 按钮组 flex-wrap 防溢出
- data-table 表格溢出截断
2026-05-28 08:35:59 +08:00
ShukeBta 3a2db6bdd9 feat: Emby 兼容层完整实现 + 多模块功能增强
## Emby/Jellyfin 兼容层 (emby_compat.go / emby.go)
- 新增 SystemInfoPublic、FindUser、Items、Item、LatestItems、ResumeItems
- 新增 SetFavorite、MarkPlayed、RecordProgress 用户播放状态同步
- 新增 itemPayload、mediaSource、mediaStreams 媒体信息组装
- 双前缀路由 /emby/* 和 / 根路径,兼容 Infuse/Yamby/Senplayer/Kodi
- 新增 Ping、SystemEndpoint、AuthByName 端点
- emby.go 扩展对应 handler 函数

## 站点适配器 (site_adapter.go / site.go)
- SiteConfig 扩展 UserAgent/Timeout/Extra/FlareSolverrURL 字段
- doRequest() GET 请求支持 FlareSolverr 代理绕过 Cloudflare/WAF
- MTeam api_key 认证改为 Authorization: Bearer 格式
- Search() 重构为 sync.WaitGroup 并发执行,提升多站搜索性能
- siteModelToConfig() 改为 SiteService 方法,按 BrowserEmulation 填充 FlareSolverrURL

## 图片代理 (image_proxy.go)
- 重构图片代理服务,支持更多来源和缓存策略

## 下载管理 (downloads.go / download_clients.go / qbittorrent.go)
- 下载任务增强:状态管理、进度追踪优化
- qBittorrent 客户端连接稳定性改进

## 刮削与数据库 (scraper.go / tmdb.go / repository.go)
- 刮削器增强 TMDB 集成,补全元数据字段
- repository 扩展查询方法

## 前端 (web/src/)
- HomePage: 首页布局重构,按媒体库分组展示,系列聚合优化
- DiscoverPage: 发现页增强,错误处理改进(API key 缺失/网络错误分离)
- PosterWallPage: 海报墙优化,系列聚合展示
- MediaCard: 媒体卡片优化
- PlayerPage: 播放器改进
- 新增 utils/groupSeries.ts: 系列聚合工具函数
- .gitignore: 添加 .tmp_* 临时文件排除规则
2026-05-26 16:09:13 +08:00
ShukeBta 5d1ae91419 feat: 站点适配器 FlareSolverr 集成 + 并发搜索优化
- site_adapter.go: SiteConfig 扩展 UserAgent/Timeout/Extra/FlareSolverrURL 字段
- site_adapter.go: doRequest() GET 请求支持 FlareSolverr 代理绕过 Cloudflare/WAF
- site_adapter.go: buildRequest() 使用 cfg.UserAgent(覆盖默认 UA)
- site_adapter.go: MTeam api_key 认证改为 Authorization: Bearer 格式
- site_adapter.go: 修复 2 个 go vet 冗余 StatusFound 警告
- site.go: Search() 重构为 sync.WaitGroup 并发执行,提升多站搜索性能
- site.go: siteModelToConfig() 改为 SiteService 方法,按 BrowserEmulation 填充 FlareSolverrURL
- site.go: Search() 空关键词返回 [] 而非 error,nil slice 防护
- 新增 system_handler.go: 系统工具探测接口
- 新增 ffmpeg_auto_install.go: FFmpeg 自动安装服务
- 新增 scripts/install-ffmpeg.ps1: Windows FFmpeg 安装脚本
2026-05-24 17:14:47 +08:00