mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-26 18:50:40 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48f4bd5f18 | ||
|
|
7a2b003d02 | ||
|
|
a700ba9379 | ||
|
|
36dd381260 | ||
|
|
f25ee25bb0 | ||
|
|
d625196b08 |
@@ -16,7 +16,7 @@ from app.db.user_oper import get_current_active_user, get_current_active_superus
|
||||
from app.schemas import MediaType, MediaRecognizeConvertEventData
|
||||
from app.schemas.category import CategoryConfig
|
||||
from app.schemas.types import ChainEventType
|
||||
from app.utils.media import parse_media_key
|
||||
from app.utils.media import MEDIA_SOURCE_ID_FIELDS, parse_media_key
|
||||
|
||||
router = APIRouter()
|
||||
MediaSource = str
|
||||
@@ -414,8 +414,10 @@ async def detail(
|
||||
mediaid=source_media_id,
|
||||
mtype=mtype,
|
||||
)
|
||||
else:
|
||||
# 广播事件解析媒体信息
|
||||
if not mediainfo and (
|
||||
not media_source or media_source not in MEDIA_SOURCE_ID_FIELDS
|
||||
):
|
||||
# 旧探索插件可能只提供列表或转换事件,原生 ID 直识别失败后需保留原有兼容链路。
|
||||
event_data = MediaRecognizeConvertEventData(
|
||||
mediaid=mediaid, convert_type=settings.RECOGNIZE_SOURCE
|
||||
)
|
||||
@@ -432,7 +434,7 @@ async def detail(
|
||||
mediaid=str(new_id),
|
||||
mtype=mtype,
|
||||
)
|
||||
elif title:
|
||||
if not mediainfo and title:
|
||||
# 使用名称识别兜底
|
||||
meta = MetaInfo(title)
|
||||
if year:
|
||||
|
||||
@@ -951,6 +951,7 @@ class DownloadChain(ChainBase):
|
||||
seasons=_meta.season,
|
||||
episodes=download_episodes or _meta.episode,
|
||||
image=_media.get_backdrop_image(),
|
||||
poster=_media.get_poster_image(),
|
||||
downloader=_downloader,
|
||||
download_hash=_hash,
|
||||
torrent_name=_torrent.title,
|
||||
@@ -1777,8 +1778,11 @@ class DownloadChain(ChainBase):
|
||||
"title": history.title,
|
||||
"season": history.seasons,
|
||||
"episode": history.episodes,
|
||||
"image": history.image,
|
||||
"image": history.poster,
|
||||
"poster": history.poster,
|
||||
"backdrop": history.image,
|
||||
}
|
||||
torrent.site_name = history.torrent_site
|
||||
# 下载用户
|
||||
torrent.userid = history.userid
|
||||
torrent.username = history.username
|
||||
|
||||
@@ -39,8 +39,10 @@ class DownloadHistory(Base):
|
||||
seasons = Column(String)
|
||||
# Exx
|
||||
episodes = Column(String)
|
||||
# 海报
|
||||
# 背景图
|
||||
image = Column(String)
|
||||
# 海报
|
||||
poster = Column(String)
|
||||
# 下载器
|
||||
downloader = Column(String)
|
||||
# 下载任务Hash
|
||||
|
||||
@@ -14,7 +14,8 @@ class YemaSpider:
|
||||
YemaPT 开放 API 索引器
|
||||
"""
|
||||
|
||||
_size = 100
|
||||
# YemaPT 开放 API 使用更大的分页容量时会返回空结果。
|
||||
_size = 40
|
||||
_movie_category = [4]
|
||||
_tv_category = [5, 6, 13, 14, 15, 16, 17]
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class DownloadHistory(BaseModel):
|
||||
"""
|
||||
下载历史记录
|
||||
"""
|
||||
|
||||
# ID
|
||||
id: int
|
||||
# 保存路程
|
||||
@@ -34,8 +38,10 @@ class DownloadHistory(BaseModel):
|
||||
seasons: Optional[str] = None
|
||||
# 集Exx
|
||||
episodes: Optional[str] = None
|
||||
# 海报
|
||||
# 背景图
|
||||
image: Optional[str] = None
|
||||
# 海报
|
||||
poster: Optional[str] = None
|
||||
# 下载器Hash
|
||||
download_hash: Optional[str] = None
|
||||
# 种子名称
|
||||
|
||||
@@ -17,6 +17,7 @@ class DownloaderTorrent(BaseModel):
|
||||
downloader: Optional[str] = None
|
||||
hash: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
site_name: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
year: Optional[str] = None
|
||||
season_episode: Optional[str] = None
|
||||
|
||||
42
database/versions/a8c4e2f6b1d9_2_2_15.py
Normal file
42
database/versions/a8c4e2f6b1d9_2_2_15.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""2.2.15
|
||||
为下载历史增加海报字段
|
||||
|
||||
Revision ID: a8c4e2f6b1d9
|
||||
Revises: f7b2d5c9a301
|
||||
Create Date: 2026-08-03
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "a8c4e2f6b1d9"
|
||||
down_revision = "f7b2d5c9a301"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _has_column(table_name: str, column_name: str) -> bool:
|
||||
"""检查数据表是否已存在指定字段。"""
|
||||
inspector = sa.inspect(op.get_bind())
|
||||
if table_name not in inspector.get_table_names():
|
||||
return False
|
||||
return any(
|
||||
column["name"] == column_name
|
||||
for column in inspector.get_columns(table_name)
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""为下载历史增加可空海报字段。"""
|
||||
if not _has_column("downloadhistory", "poster"):
|
||||
op.add_column(
|
||||
"downloadhistory",
|
||||
sa.Column("poster", sa.String(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""移除下载历史海报字段。"""
|
||||
if _has_column("downloadhistory", "poster"):
|
||||
op.drop_column("downloadhistory", "poster")
|
||||
@@ -170,7 +170,7 @@ AniList 榜单、探索、详情、人物和推荐接口优先通过 `anilist-ch
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| GET | `/api/v1/download/` | 查询正在下载的任务,参数:`name` |
|
||||
| GET | `/api/v1/download/` | 查询正在下载的任务,参数:`name`;关联下载历史时返回媒体类型、来源站点 `site_name`,以及 `media.poster` 海报和 `media.backdrop` 背景图;兼容字段 `media.image` 与 `media.poster` 相同 |
|
||||
| POST | `/api/v1/download/` | 添加含媒体信息的下载任务,请求体包含媒体信息和种子信息 |
|
||||
| POST | `/api/v1/download/add` | 添加不含媒体信息的下载任务,请求体包含 `torrent_in`,可选 `media_source` + `media_id`;继续兼容四种专用 ID,并支持 `downloader`、`save_path` |
|
||||
| POST | `/api/v1/download/subtitle` | 下载字幕到识别出的媒体下载目录,请求体包含 `subtitle_in`,可选 `media_source` + `media_id`;继续兼容四种专用 ID,并支持 `save_path` |
|
||||
@@ -180,6 +180,13 @@ AniList 榜单、探索、详情、人物和推荐接口优先通过 `anilist-ch
|
||||
| GET | `/api/v1/download/paths` | 查询可用于下载接口 `save_path` 参数的下载路径 |
|
||||
| DELETE | `/api/v1/download/{hashString}` | 删除下载任务,参数:`name` |
|
||||
|
||||
#### 历史
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| GET | `/api/v1/history/download` | 按下载时间倒序查询下载历史,参数:`page`、`count`;`poster` 为海报,兼容字段 `image` 为背景图 |
|
||||
| DELETE | `/api/v1/history/download` | 删除下载历史,请求体为下载历史记录 |
|
||||
|
||||
#### 系统
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|
||||
@@ -38,7 +38,7 @@ pillow~=12.2.0
|
||||
pillow-avif-plugin~=1.5.5
|
||||
pyTelegramBotAPI~=4.34.0
|
||||
telegramify-markdown~=1.2.0
|
||||
cloakbrowser~=0.4.0
|
||||
cloakbrowser~=0.5.3
|
||||
torrentool~=1.2.0
|
||||
fast-bencode~=1.1.8
|
||||
slack-bolt~=1.28.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: moviepilot-api
|
||||
version: 6
|
||||
version: 7
|
||||
description: >-
|
||||
Use this skill when you need to call MoviePilot REST API endpoints directly
|
||||
with the bundled Python client. Covers MoviePilot HTTP endpoints across media
|
||||
@@ -175,7 +175,7 @@ Streaming search sends `{"type":"heartbeat"}` every 15 seconds without business
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/v1/download/` | List active downloads. Params: `name` (downloader name) |
|
||||
| GET | `/api/v1/download/` | List active downloads. Params: `name` (downloader name); linked history adds media type and source `site_name` |
|
||||
| POST | `/api/v1/download/` | Add download (with media info). Body: JSON |
|
||||
| POST | `/api/v1/download/add` | Add download without media info. Body: `torrent_in`, optional `media_source` + `media_id` (all four dedicated IDs remain supported), `downloader`, `save_path` |
|
||||
| POST | `/api/v1/download/subtitle` | Download subtitle file to the recognized media download directory. Body: `subtitle_in`, optional `media_source` + `media_id` (all four dedicated IDs remain supported), `save_path` |
|
||||
@@ -251,7 +251,7 @@ Streaming search sends `{"type":"heartbeat"}` every 15 seconds without business
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/v1/history/download` | Download history, newest first. Params: `page`, `count` |
|
||||
| GET | `/api/v1/history/download` | Download history, newest first. Params: `page`, `count`. `poster` is the poster image; legacy `image` is the backdrop image. |
|
||||
| DELETE | `/api/v1/history/download` | Delete download history. Body: DownloadHistory JSON |
|
||||
| GET | `/api/v1/history/transfer` | Transfer history. Params: `title`, `page`, `count`, `status` |
|
||||
| DELETE | `/api/v1/history/transfer` | Delete transfer history. Params: `deletesrc`, `deletedest`. Body: TransferHistory |
|
||||
|
||||
@@ -23,7 +23,7 @@ def test_modified_builtin_skills_have_incremented_versions() -> None:
|
||||
"""本次修改过的内置技能必须递增版本,确保用户端同步更新。"""
|
||||
expected_versions = {
|
||||
"database-operation": "3",
|
||||
"moviepilot-api": "6",
|
||||
"moviepilot-api": "7",
|
||||
"moviepilot-cli": "6",
|
||||
"moviepilot-update": "3",
|
||||
"transfer-failed-retry": "2",
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.chain.download import DownloadChain
|
||||
from app.core.config import settings
|
||||
from app.core.context import Context, MediaInfo, SubtitleInfo, TorrentInfo
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.schemas import FileItem, NotExistMediaInfo, TransferDirectoryConf
|
||||
from app.schemas import DownloaderTorrent, FileItem, NotExistMediaInfo, TransferDirectoryConf
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
|
||||
@@ -249,9 +249,11 @@ def test_download_single_persists_custom_words_snapshot(monkeypatch):
|
||||
"""捕获写入下载历史的字段,验证识别词快照确实落库。"""
|
||||
|
||||
def add(self, **kwargs):
|
||||
"""捕获下载历史字段。"""
|
||||
captured.update(kwargs)
|
||||
|
||||
def add_files(self, _files):
|
||||
"""忽略与当前断言无关的下载文件记录。"""
|
||||
pass
|
||||
|
||||
_FakeThreadHelper.submitted = []
|
||||
@@ -278,6 +280,8 @@ def test_download_single_persists_custom_words_snapshot(monkeypatch):
|
||||
year="2024",
|
||||
tmdb_id=1,
|
||||
genre_ids=[18],
|
||||
poster_path="https://images.example.com/original/poster.jpg",
|
||||
backdrop_path="https://images.example.com/original/backdrop.jpg",
|
||||
),
|
||||
torrent_info=TorrentInfo(
|
||||
title="Demo Show 2024",
|
||||
@@ -298,6 +302,8 @@ def test_download_single_persists_custom_words_snapshot(monkeypatch):
|
||||
|
||||
assert result == "hash123"
|
||||
assert captured["custom_words"] == custom_words
|
||||
assert captured["poster"] == "https://images.example.com/w500/poster.jpg"
|
||||
assert captured["image"] == "https://images.example.com/w500/backdrop.jpg"
|
||||
|
||||
|
||||
def test_save_subtitle_response_creates_missing_temp_directory(monkeypatch, tmp_path):
|
||||
@@ -1036,3 +1042,40 @@ def test_batch_download_keeps_count_check_without_complete_coverage(monkeypatch)
|
||||
assert lefts == {}
|
||||
assert context.confirmed_full_coverage is False
|
||||
chain.download_single.assert_called_once()
|
||||
|
||||
|
||||
def test_downloading_includes_media_type_and_source_site(monkeypatch):
|
||||
"""
|
||||
正在下载任务应从下载历史回填媒体类型和来源站点。
|
||||
"""
|
||||
torrent = DownloaderTorrent(hash="download-hash", title="Demo.Release")
|
||||
history = SimpleNamespace(
|
||||
episodes="E02",
|
||||
image="https://images.example.com/backdrop.jpg",
|
||||
poster="https://images.example.com/poster.jpg",
|
||||
seasons="S01",
|
||||
title="示例剧集",
|
||||
tmdbid=1001,
|
||||
torrent_site="示例站点",
|
||||
type="电视剧",
|
||||
userid="user-1",
|
||||
username="tester",
|
||||
)
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
monkeypatch.setattr(chain, "list_torrents", lambda **_kwargs: [torrent])
|
||||
monkeypatch.setattr(
|
||||
download_module,
|
||||
"DownloadHistoryOper",
|
||||
lambda: SimpleNamespace(get_by_hashes=lambda _hashes: {torrent.hash: history}),
|
||||
)
|
||||
|
||||
result = chain.downloading(name="qb-main")
|
||||
|
||||
assert result == [torrent]
|
||||
assert torrent.media["type"] == "电视剧"
|
||||
assert torrent.media["image"] == "https://images.example.com/poster.jpg"
|
||||
assert torrent.media["poster"] == "https://images.example.com/poster.jpg"
|
||||
assert torrent.media["backdrop"] == "https://images.example.com/backdrop.jpg"
|
||||
assert torrent.site_name == "示例站点"
|
||||
assert torrent.userid == "user-1"
|
||||
assert torrent.username == "tester"
|
||||
|
||||
50
tests/test_downloadhistory_poster_migration.py
Normal file
50
tests/test_downloadhistory_poster_migration.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import importlib
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
|
||||
|
||||
def test_downloadhistory_poster_migration_adds_and_removes_column(monkeypatch) -> None:
|
||||
"""迁移应为下载历史增加可空海报字段,并支持回滚。"""
|
||||
migration = importlib.import_module(
|
||||
"database.versions.a8c4e2f6b1d9_2_2_15"
|
||||
)
|
||||
engine = sa.create_engine("sqlite://")
|
||||
metadata = sa.MetaData()
|
||||
download_history = sa.Table(
|
||||
"downloadhistory",
|
||||
metadata,
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("image", sa.String()),
|
||||
)
|
||||
|
||||
with engine.begin() as connection:
|
||||
metadata.create_all(connection)
|
||||
connection.execute(download_history.insert(), {
|
||||
"id": 1,
|
||||
"image": "https://images.example.com/backdrop.jpg",
|
||||
})
|
||||
context = MigrationContext.configure(connection)
|
||||
monkeypatch.setattr(migration, "op", Operations(context))
|
||||
|
||||
migration.upgrade()
|
||||
migration.upgrade()
|
||||
|
||||
migrated = sa.Table(
|
||||
"downloadhistory",
|
||||
sa.MetaData(),
|
||||
autoload_with=connection,
|
||||
)
|
||||
row = connection.execute(sa.select(migrated)).mappings().one()
|
||||
assert "poster" in migrated.c
|
||||
assert row["image"] == "https://images.example.com/backdrop.jpg"
|
||||
assert row["poster"] is None
|
||||
|
||||
migration.downgrade()
|
||||
rolled_back = sa.Table(
|
||||
"downloadhistory",
|
||||
sa.MetaData(),
|
||||
autoload_with=connection,
|
||||
)
|
||||
assert "poster" not in rolled_back.c
|
||||
@@ -252,18 +252,18 @@ class SearchChainAIRecommendTest(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
def test_search_all_sites_uses_parser_page_size_for_yema(self):
|
||||
"""
|
||||
验证专用解析器按自身页容量判断,避免 Yema 的 100 条分页被误停。
|
||||
验证专用解析器按自身页容量判断,避免 Yema 的 40 条分页被误停。
|
||||
"""
|
||||
chain = self._make_chain()
|
||||
requested_pages = []
|
||||
|
||||
def search_torrents(**kwargs):
|
||||
"""
|
||||
模拟 Yema 第一页满 100 条,第二页不足 100 条后停止。
|
||||
模拟 Yema 第一页满 40 条,第二页不足 40 条后停止。
|
||||
"""
|
||||
page = kwargs["page"]
|
||||
requested_pages.append(page)
|
||||
count = 100 if page == 0 else 99
|
||||
count = 40 if page == 0 else 39
|
||||
return [
|
||||
SimpleNamespace(title=f"Result Page {page}-{index}", description="")
|
||||
for index in range(count)
|
||||
@@ -294,14 +294,14 @@ class SearchChainAIRecommendTest(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
|
||||
self.assertEqual([0, 1], requested_pages)
|
||||
self.assertEqual(199, len(results))
|
||||
self.assertEqual(79, len(results))
|
||||
|
||||
def test_indexer_module_search_page_size_uses_spider_metadata(self):
|
||||
"""
|
||||
验证站点单页容量由索引器模块统一读取,避免搜索链写死 parser 容量。
|
||||
"""
|
||||
self.assertEqual(
|
||||
100,
|
||||
40,
|
||||
IndexerModule.get_search_page_size({"parser": "Yema"}, keyword="keyword")
|
||||
)
|
||||
self.assertEqual(
|
||||
|
||||
@@ -119,6 +119,132 @@ def test_media_detail_forwards_custom_plugin_source(monkeypatch) -> None:
|
||||
assert captured["mediaid"] == "custom-1"
|
||||
|
||||
|
||||
def test_media_detail_falls_back_to_title_for_legacy_discover_source(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""仅提供探索列表的旧插件应在原生 ID 识别失败后按标题年份兜底。"""
|
||||
media = MediaInfo(
|
||||
source="themoviedb",
|
||||
type=MediaType.TV,
|
||||
title="旧版剧集",
|
||||
tmdb_id=12345,
|
||||
)
|
||||
media_chain = Mock()
|
||||
media_chain.async_recognize_media = AsyncMock(return_value=None)
|
||||
media_chain.async_recognize_by_meta = AsyncMock(return_value=media)
|
||||
media_chain.async_obtain_images = AsyncMock(return_value=None)
|
||||
monkeypatch.setattr(media_endpoint, "MediaChain", Mock(return_value=media_chain))
|
||||
monkeypatch.setattr(
|
||||
media_endpoint.eventmanager,
|
||||
"async_send_event",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
|
||||
result = asyncio.run(
|
||||
media_endpoint.detail(
|
||||
mediaid="tvdb:81189",
|
||||
type_name=MediaType.TV.value,
|
||||
title="旧版剧集",
|
||||
year="2026",
|
||||
_=None,
|
||||
)
|
||||
)
|
||||
|
||||
assert result["tmdb_id"] == 12345
|
||||
media_chain.async_recognize_media.assert_awaited_once_with(
|
||||
source="tvdb",
|
||||
mediaid="81189",
|
||||
mtype=MediaType.TV,
|
||||
)
|
||||
fallback_meta = media_chain.async_recognize_by_meta.await_args.args[0]
|
||||
assert fallback_meta.name == "旧版剧集"
|
||||
assert fallback_meta.year == "2026"
|
||||
assert fallback_meta.type == MediaType.TV
|
||||
|
||||
|
||||
def test_media_detail_uses_convert_event_for_legacy_discover_source(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""旧探索插件提供 ID 转换事件时应优先转换到系统识别源。"""
|
||||
media = MediaInfo(
|
||||
source="themoviedb",
|
||||
type=MediaType.MOVIE,
|
||||
title="转换电影",
|
||||
tmdb_id=54321,
|
||||
)
|
||||
event_data = media_endpoint.MediaRecognizeConvertEventData(
|
||||
mediaid="legacy:42",
|
||||
convert_type="themoviedb",
|
||||
)
|
||||
event_data.media_dict["id"] = 54321
|
||||
media_chain = Mock()
|
||||
media_chain.async_recognize_media = AsyncMock(side_effect=[None, media])
|
||||
media_chain.async_recognize_by_meta = AsyncMock(
|
||||
side_effect=AssertionError("转换成功后不应再按标题识别")
|
||||
)
|
||||
media_chain.async_obtain_images = AsyncMock(return_value=None)
|
||||
monkeypatch.setattr(media_endpoint, "MediaChain", Mock(return_value=media_chain))
|
||||
monkeypatch.setattr(
|
||||
media_endpoint.eventmanager,
|
||||
"async_send_event",
|
||||
AsyncMock(return_value=Mock(event_data=event_data)),
|
||||
)
|
||||
|
||||
result = asyncio.run(
|
||||
media_endpoint.detail(
|
||||
mediaid="legacy:42",
|
||||
type_name=MediaType.MOVIE.value,
|
||||
title="转换电影",
|
||||
year="2026",
|
||||
_=None,
|
||||
)
|
||||
)
|
||||
|
||||
assert result["tmdb_id"] == 54321
|
||||
assert media_chain.async_recognize_media.await_count == 2
|
||||
assert media_chain.async_recognize_media.await_args_list[0].kwargs == {
|
||||
"source": "legacy",
|
||||
"mediaid": "42",
|
||||
"mtype": MediaType.MOVIE,
|
||||
}
|
||||
assert media_chain.async_recognize_media.await_args_list[1].kwargs == {
|
||||
"source": "themoviedb",
|
||||
"mediaid": "54321",
|
||||
"mtype": MediaType.MOVIE,
|
||||
}
|
||||
media_chain.async_recognize_by_meta.assert_not_awaited()
|
||||
|
||||
|
||||
def test_media_detail_does_not_fallback_for_builtin_source(monkeypatch) -> None:
|
||||
"""内置来源的明确 ID 查询失败时不应被标题识别替换身份。"""
|
||||
media_chain = Mock()
|
||||
media_chain.async_recognize_media = AsyncMock(return_value=None)
|
||||
media_chain.async_recognize_by_meta = AsyncMock(
|
||||
side_effect=AssertionError("不应按标题切换识别源")
|
||||
)
|
||||
convert_event = AsyncMock(return_value=None)
|
||||
monkeypatch.setattr(media_endpoint, "MediaChain", Mock(return_value=media_chain))
|
||||
monkeypatch.setattr(
|
||||
media_endpoint.eventmanager,
|
||||
"async_send_event",
|
||||
convert_event,
|
||||
)
|
||||
|
||||
result = asyncio.run(
|
||||
media_endpoint.detail(
|
||||
mediaid="tmdb:999999",
|
||||
type_name=MediaType.MOVIE.value,
|
||||
title="错误兜底电影",
|
||||
year="2026",
|
||||
_=None,
|
||||
)
|
||||
)
|
||||
|
||||
assert isinstance(result, media_endpoint.schemas.MediaInfo)
|
||||
media_chain.async_recognize_by_meta.assert_not_awaited()
|
||||
convert_event.assert_not_awaited()
|
||||
|
||||
|
||||
def test_media_seasons_builds_anilist_season_response(monkeypatch) -> None:
|
||||
"""AniList 详情应能通过统一季信息接口返回剧集季。"""
|
||||
captured = {}
|
||||
|
||||
@@ -94,7 +94,7 @@ def test_yemapt_search_uses_open_api_auth_and_maps_fields(monkeypatch):
|
||||
"url": "https://www.yemapt.org/openApi/torrent/fetchOpenTorrentList.json",
|
||||
"json": {
|
||||
"keyword": "Movie",
|
||||
"pageParam": {"current": 3, "pageSize": 100},
|
||||
"pageParam": {"current": 3, "pageSize": 40},
|
||||
"sorter": {},
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = 'v2.15.3'
|
||||
FRONTEND_VERSION = 'v2.15.3'
|
||||
APP_VERSION = 'v2.15.4'
|
||||
FRONTEND_VERSION = 'v2.15.4'
|
||||
|
||||
Reference in New Issue
Block a user