mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-05-11 10:35:50 +08:00
feat: fix search, poster serving, and add hover overlay UI for cards
- Fix search store exports to match component expectations (inputValue, bangumiList, onSearch) and transform data to SearchResult format - Fix poster endpoint path check that incorrectly blocked all requests - Add resolvePosterUrl utility to handle both external URLs and local paths - Move tags into hover overlay on homepage cards and calendar cards - Show title and tags on poster hover with dark semi-transparent styling - Add downloader API, store, and page - Update backend to async patterns and uv migration changes - Remove .claude/settings.local.json from tracking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,16 +9,17 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SeasonCollector(DownloadClient):
|
||||
def collect_season(self, bangumi: Bangumi, link: str = None):
|
||||
async def collect_season(self, bangumi: Bangumi, link: str = None):
|
||||
logger.info(
|
||||
f"Start collecting {bangumi.official_title} Season {bangumi.season}..."
|
||||
)
|
||||
with SearchTorrent() as st, RSSEngine() as engine:
|
||||
async with SearchTorrent() as st:
|
||||
if not link:
|
||||
torrents = st.search_season(bangumi)
|
||||
torrents = await st.search_season(bangumi)
|
||||
else:
|
||||
torrents = st.get_torrents(link, bangumi.filter.replace(",", "|"))
|
||||
if self.add_torrent(torrents, bangumi):
|
||||
torrents = await st.get_torrents(link, bangumi.filter.replace(",", "|"))
|
||||
with RSSEngine() as engine:
|
||||
if await self.add_torrent(torrents, bangumi):
|
||||
logger.info(
|
||||
f"Collections of {bangumi.official_title} Season {bangumi.season} completed."
|
||||
)
|
||||
@@ -46,29 +47,29 @@ class SeasonCollector(DownloadClient):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def subscribe_season(data: Bangumi, parser: str = "mikan"):
|
||||
async def subscribe_season(data: Bangumi, parser: str = "mikan"):
|
||||
with RSSEngine() as engine:
|
||||
data.added = True
|
||||
data.eps_collect = True
|
||||
engine.add_rss(
|
||||
await engine.add_rss(
|
||||
rss_link=data.rss_link,
|
||||
name=data.official_title,
|
||||
aggregate=False,
|
||||
parser=parser,
|
||||
)
|
||||
result = engine.download_bangumi(data)
|
||||
result = await engine.download_bangumi(data)
|
||||
engine.bangumi.add(data)
|
||||
return result
|
||||
|
||||
|
||||
def eps_complete():
|
||||
async def eps_complete():
|
||||
with RSSEngine() as engine:
|
||||
datas = engine.bangumi.not_complete()
|
||||
if datas:
|
||||
logger.info("Start collecting full season...")
|
||||
for data in datas:
|
||||
if not data.eps_collect:
|
||||
with SeasonCollector() as collector:
|
||||
collector.collect_season(data)
|
||||
async with SeasonCollector() as collector:
|
||||
await collector.collect_season(data)
|
||||
data.eps_collect = True
|
||||
engine.bangumi.update_all(datas)
|
||||
|
||||
@@ -48,7 +48,7 @@ class Renamer(DownloadClient):
|
||||
logger.error(f"[Renamer] Unknown rename method: {method}")
|
||||
return file_info.media_path
|
||||
|
||||
def rename_file(
|
||||
async def rename_file(
|
||||
self,
|
||||
torrent_name: str,
|
||||
media_path: str,
|
||||
@@ -67,7 +67,7 @@ class Renamer(DownloadClient):
|
||||
new_path = self.gen_path(ep, bangumi_name, method=method)
|
||||
if media_path != new_path:
|
||||
if new_path not in self.check_pool.keys():
|
||||
if self.rename_torrent_file(
|
||||
if await self.rename_torrent_file(
|
||||
_hash=_hash, old_path=media_path, new_path=new_path
|
||||
):
|
||||
return Notification(
|
||||
@@ -78,10 +78,10 @@ class Renamer(DownloadClient):
|
||||
else:
|
||||
logger.warning(f"[Renamer] {media_path} parse failed")
|
||||
if settings.bangumi_manage.remove_bad_torrent:
|
||||
self.delete_torrent(hashes=_hash)
|
||||
await self.delete_torrent(hashes=_hash)
|
||||
return None
|
||||
|
||||
def rename_collection(
|
||||
async def rename_collection(
|
||||
self,
|
||||
media_list: list[str],
|
||||
bangumi_name: str,
|
||||
@@ -99,17 +99,17 @@ class Renamer(DownloadClient):
|
||||
if ep:
|
||||
new_path = self.gen_path(ep, bangumi_name, method=method)
|
||||
if media_path != new_path:
|
||||
renamed = self.rename_torrent_file(
|
||||
renamed = await self.rename_torrent_file(
|
||||
_hash=_hash, old_path=media_path, new_path=new_path
|
||||
)
|
||||
if not renamed:
|
||||
logger.warning(f"[Renamer] {media_path} rename failed")
|
||||
# Delete bad torrent.
|
||||
if settings.bangumi_manage.remove_bad_torrent:
|
||||
self.delete_torrent(_hash)
|
||||
await self.delete_torrent(_hash)
|
||||
break
|
||||
|
||||
def rename_subtitles(
|
||||
async def rename_subtitles(
|
||||
self,
|
||||
subtitle_list: list[str],
|
||||
torrent_name: str,
|
||||
@@ -130,17 +130,17 @@ class Renamer(DownloadClient):
|
||||
if sub:
|
||||
new_path = self.gen_path(sub, bangumi_name, method=method)
|
||||
if subtitle_path != new_path:
|
||||
renamed = self.rename_torrent_file(
|
||||
renamed = await self.rename_torrent_file(
|
||||
_hash=_hash, old_path=subtitle_path, new_path=new_path
|
||||
)
|
||||
if not renamed:
|
||||
logger.warning(f"[Renamer] {subtitle_path} rename failed")
|
||||
|
||||
def rename(self) -> list[Notification]:
|
||||
async def rename(self) -> list[Notification]:
|
||||
# Get torrent info
|
||||
logger.debug("[Renamer] Start rename process.")
|
||||
rename_method = settings.bangumi_manage.rename_method
|
||||
torrents_info = self.get_torrent_info()
|
||||
torrents_info = await self.get_torrent_info()
|
||||
renamed_info: list[Notification] = []
|
||||
for info in torrents_info:
|
||||
media_list, subtitle_list = self.check_files(info)
|
||||
@@ -154,19 +154,19 @@ class Renamer(DownloadClient):
|
||||
}
|
||||
# Rename single media file
|
||||
if len(media_list) == 1:
|
||||
notify_info = self.rename_file(media_path=media_list[0], **kwargs)
|
||||
notify_info = await self.rename_file(media_path=media_list[0], **kwargs)
|
||||
if notify_info:
|
||||
renamed_info.append(notify_info)
|
||||
# Rename subtitle file
|
||||
if len(subtitle_list) > 0:
|
||||
self.rename_subtitles(subtitle_list=subtitle_list, **kwargs)
|
||||
await self.rename_subtitles(subtitle_list=subtitle_list, **kwargs)
|
||||
# Rename collection
|
||||
elif len(media_list) > 1:
|
||||
logger.info("[Renamer] Start rename collection")
|
||||
self.rename_collection(media_list=media_list, **kwargs)
|
||||
await self.rename_collection(media_list=media_list, **kwargs)
|
||||
if len(subtitle_list) > 0:
|
||||
self.rename_subtitles(subtitle_list=subtitle_list, **kwargs)
|
||||
self.set_category(info.hash, "BangumiCollection")
|
||||
await self.rename_subtitles(subtitle_list=subtitle_list, **kwargs)
|
||||
await self.set_category(info.hash, "BangumiCollection")
|
||||
else:
|
||||
logger.warning(f"[Renamer] {info.name} has no media file")
|
||||
logger.debug("[Renamer] Rename process finished.")
|
||||
@@ -177,12 +177,3 @@ class Renamer(DownloadClient):
|
||||
pass
|
||||
else:
|
||||
self.delete_torrent(hashes=torrent_hash)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from module.conf import setup_logger
|
||||
|
||||
settings.log.debug_enable = True
|
||||
setup_logger()
|
||||
with Renamer() as renamer:
|
||||
renamer.rename()
|
||||
|
||||
@@ -11,17 +11,19 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class TorrentManager(Database):
|
||||
@staticmethod
|
||||
def __match_torrents_list(data: Bangumi | BangumiUpdate) -> list:
|
||||
with DownloadClient() as client:
|
||||
torrents = client.get_torrent_info(status_filter=None)
|
||||
async def __match_torrents_list(data: Bangumi | BangumiUpdate) -> list:
|
||||
async with DownloadClient() as client:
|
||||
torrents = await client.get_torrent_info(status_filter=None)
|
||||
return [
|
||||
torrent.hash for torrent in torrents if torrent.save_path == data.save_path
|
||||
torrent.get("hash", torrent.get("infohash_v1", ""))
|
||||
for torrent in torrents
|
||||
if torrent.get("save_path") == data.save_path
|
||||
]
|
||||
|
||||
def delete_torrents(self, data: Bangumi, client: DownloadClient):
|
||||
hash_list = self.__match_torrents_list(data)
|
||||
async def delete_torrents(self, data: Bangumi, client: DownloadClient):
|
||||
hash_list = await self.__match_torrents_list(data)
|
||||
if hash_list:
|
||||
client.delete_torrent(hash_list)
|
||||
await client.delete_torrent(hash_list)
|
||||
logger.info(f"Delete rule and torrents for {data.official_title}")
|
||||
return ResponseModel(
|
||||
status_code=200,
|
||||
@@ -37,20 +39,21 @@ class TorrentManager(Database):
|
||||
msg_zh=f"无法找到 {data.official_title} 的种子",
|
||||
)
|
||||
|
||||
def delete_rule(self, _id: int | str, file: bool = False):
|
||||
async def delete_rule(self, _id: int | str, file: bool = False):
|
||||
data = self.bangumi.search_id(int(_id))
|
||||
if isinstance(data, Bangumi):
|
||||
with DownloadClient() as client:
|
||||
async with DownloadClient() as client:
|
||||
self.rss.delete(data.official_title)
|
||||
self.bangumi.delete_one(int(_id))
|
||||
torrent_message = None
|
||||
if file:
|
||||
torrent_message = self.delete_torrents(data, client)
|
||||
torrent_message = await self.delete_torrents(data, client)
|
||||
logger.info(f"[Manager] Delete rule for {data.official_title}")
|
||||
return ResponseModel(
|
||||
status_code=200,
|
||||
status=True,
|
||||
msg_en=f"Delete rule for {data.official_title}. {torrent_message.msg_en if file else ''}",
|
||||
msg_zh=f"删除 {data.official_title} 规则。{torrent_message.msg_zh if file else ''}",
|
||||
msg_en=f"Delete rule for {data.official_title}. {torrent_message.msg_en if file and torrent_message else ''}",
|
||||
msg_zh=f"删除 {data.official_title} 规则。{torrent_message.msg_zh if file and torrent_message else ''}",
|
||||
)
|
||||
else:
|
||||
return ResponseModel(
|
||||
@@ -60,15 +63,14 @@ class TorrentManager(Database):
|
||||
msg_zh=f"无法找到 id {_id}",
|
||||
)
|
||||
|
||||
def disable_rule(self, _id: str | int, file: bool = False):
|
||||
async def disable_rule(self, _id: str | int, file: bool = False):
|
||||
data = self.bangumi.search_id(int(_id))
|
||||
if isinstance(data, Bangumi):
|
||||
with DownloadClient() as client:
|
||||
# client.remove_rule(data.rule_name)
|
||||
async with DownloadClient() as client:
|
||||
data.deleted = True
|
||||
self.bangumi.update(data)
|
||||
if file:
|
||||
torrent_message = self.delete_torrents(data, client)
|
||||
torrent_message = await self.delete_torrents(data, client)
|
||||
return torrent_message
|
||||
logger.info(f"[Manager] Disable rule for {data.official_title}")
|
||||
return ResponseModel(
|
||||
@@ -105,15 +107,15 @@ class TorrentManager(Database):
|
||||
msg_zh=f"无法找到 id {_id}",
|
||||
)
|
||||
|
||||
def update_rule(self, bangumi_id, data: BangumiUpdate):
|
||||
async def update_rule(self, bangumi_id, data: BangumiUpdate):
|
||||
old_data: Bangumi = self.bangumi.search_id(bangumi_id)
|
||||
if old_data:
|
||||
# Move torrent
|
||||
match_list = self.__match_torrents_list(old_data)
|
||||
with DownloadClient() as client:
|
||||
match_list = await self.__match_torrents_list(old_data)
|
||||
async with DownloadClient() as client:
|
||||
path = client._gen_save_path(data)
|
||||
if match_list:
|
||||
client.move_torrent(match_list, path)
|
||||
await client.move_torrent(match_list, path)
|
||||
data.save_path = path
|
||||
self.bangumi.update(data, bangumi_id)
|
||||
return ResponseModel(
|
||||
@@ -131,11 +133,11 @@ class TorrentManager(Database):
|
||||
msg_zh=f"无法找到 id {bangumi_id} 的数据",
|
||||
)
|
||||
|
||||
def refresh_poster(self):
|
||||
async def refresh_poster(self):
|
||||
bangumis = self.bangumi.search_all()
|
||||
for bangumi in bangumis:
|
||||
if not bangumi.poster_link:
|
||||
TitleParser().tmdb_poster_parser(bangumi)
|
||||
await TitleParser().tmdb_poster_parser(bangumi)
|
||||
self.bangumi.update_all(bangumis)
|
||||
return ResponseModel(
|
||||
status_code=200,
|
||||
@@ -144,9 +146,9 @@ class TorrentManager(Database):
|
||||
msg_zh="刷新海报链接成功。",
|
||||
)
|
||||
|
||||
def refind_poster(self, bangumi_id: int):
|
||||
async def refind_poster(self, bangumi_id: int):
|
||||
bangumi = self.bangumi.search_id(bangumi_id)
|
||||
TitleParser().tmdb_poster_parser(bangumi)
|
||||
await TitleParser().tmdb_poster_parser(bangumi)
|
||||
self.bangumi.update(bangumi)
|
||||
return ResponseModel(
|
||||
status_code=200,
|
||||
@@ -155,9 +157,9 @@ class TorrentManager(Database):
|
||||
msg_zh="刷新海报链接成功。",
|
||||
)
|
||||
|
||||
def refresh_calendar(self):
|
||||
async def refresh_calendar(self):
|
||||
"""Fetch Bangumi.tv calendar and update air_weekday for all bangumi."""
|
||||
calendar_items = fetch_bgm_calendar()
|
||||
calendar_items = await fetch_bgm_calendar()
|
||||
if not calendar_items:
|
||||
return ResponseModel(
|
||||
status_code=500,
|
||||
@@ -204,8 +206,3 @@ class TorrentManager(Database):
|
||||
)
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with TorrentManager() as manager:
|
||||
manager.refresh_poster()
|
||||
|
||||
Reference in New Issue
Block a user