1
This commit is contained in:
@@ -14,6 +14,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# 内容源配置(域名轮换时只需改这里)
|
||||
SOURCE_KEY = "mdcmai4"
|
||||
SOURCE_CODE = "madou" # sources 表中的 code,用于给导入内容打「来源」标
|
||||
UPSTREAM = "https://mdcmai4.xyz"
|
||||
PRODUCT_ID = 1
|
||||
CATEGORY_ID = 12 # 本站分类:AI短剧
|
||||
@@ -37,6 +38,21 @@ def _proxy_url(path: str) -> str:
|
||||
return f"{SELF_API_BASE}/api/v1/m3u8/proxy?path={quote(path, safe='')}"
|
||||
|
||||
|
||||
async def default_source_id(db: AsyncSession) -> int:
|
||||
"""麻豆等内容源的来源 id(sources.code 匹配;缺失时取第一个启用来源)"""
|
||||
from app.models.video import Source
|
||||
|
||||
sid = (
|
||||
await db.execute(select(Source.id).where(Source.code == SOURCE_CODE))
|
||||
).scalar_one_or_none()
|
||||
if sid:
|
||||
return sid
|
||||
sid = (
|
||||
await db.execute(select(Source.id).where(Source.status == 1).order_by(Source.sort, Source.id))
|
||||
).scalars().first()
|
||||
return sid or 0
|
||||
|
||||
|
||||
async def _import_drama(client: httpx.AsyncClient, db: AsyncSession, drama_id: int) -> tuple[int, int]:
|
||||
"""导入单部短剧,返回 (新增集数, 跳过集数)"""
|
||||
detail = await _fetch_json(client, f"{UPSTREAM}/api/v1/short-dramas/{drama_id}", {"productId": PRODUCT_ID})
|
||||
@@ -63,6 +79,7 @@ async def _import_drama(client: httpx.AsyncClient, db: AsyncSession, drama_id: i
|
||||
description=(detail.get("description") or "")[:500],
|
||||
author_id=AUTHOR_USER_ID,
|
||||
category_id=CATEGORY_ID,
|
||||
source_id=await default_source_id(db),
|
||||
total_episodes=0,
|
||||
view_count=int(detail.get("heatCount") or 0),
|
||||
status=1,
|
||||
@@ -94,6 +111,7 @@ async def _import_drama(client: httpx.AsyncClient, db: AsyncSession, drama_id: i
|
||||
Video(
|
||||
user_id=AUTHOR_USER_ID,
|
||||
category_id=CATEGORY_ID,
|
||||
source_id=series.source_id,
|
||||
series_id=series.id,
|
||||
episode_no=ep_no,
|
||||
title=(ep.get("titleOverride") or ep.get("title") or f"第{ep_no}集")[:100],
|
||||
@@ -209,6 +227,7 @@ async def _import_video_item(db: AsyncSession, source: CollectionSource, item: d
|
||||
v = Video(
|
||||
user_id=AUTHOR_USER_ID,
|
||||
category_id=source.category_id,
|
||||
source_id=source.source_id or await default_source_id(db),
|
||||
title=(item.get("title") or f"视频{vid}")[:100],
|
||||
description=(item.get("description") or "")[:500],
|
||||
cover_url=_abs_cover(item)[:500],
|
||||
@@ -232,13 +251,23 @@ async def sync_source(
|
||||
max_pages: int = 0,
|
||||
stop_after_existing: int = 30,
|
||||
delay: float = 0.25,
|
||||
should_cancel=None,
|
||||
) -> dict:
|
||||
"""按源的 URL 模板翻页采集;连续 stop_after_existing 条已存在则提前停止(增量)"""
|
||||
stat = {"created": 0, "skipped": 0, "pages": 0, "stopped_early": False}
|
||||
"""按源的 URL 模板翻页采集;连续 stop_after_existing 条已存在则提前停止(增量)。
|
||||
should_cancel 返回 True 时每页/每条边界退出(用于手动取消)"""
|
||||
stat = {"created": 0, "skipped": 0, "pages": 0, "stopped_early": False, "cancelled": False}
|
||||
consecutive_existing = 0
|
||||
|
||||
def _cancelled() -> bool:
|
||||
return bool(should_cancel and should_cancel())
|
||||
|
||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
||||
page = 1
|
||||
while True:
|
||||
if _cancelled():
|
||||
stat["cancelled"] = True
|
||||
logger.info("通用采集源 %s 同步被取消(第 %s 页前)", source.id, page)
|
||||
return stat
|
||||
url = source.list_url.replace("{{page}}", str(page))
|
||||
data = await _fetch_json(client, url)
|
||||
items = data.get("items", [])
|
||||
@@ -246,6 +275,10 @@ async def sync_source(
|
||||
break
|
||||
stat["pages"] = page
|
||||
for item in items:
|
||||
if _cancelled():
|
||||
stat["cancelled"] = True
|
||||
logger.info("通用采集源 %s 同步被取消(处理 item 中)", source.id)
|
||||
return stat
|
||||
if await _import_video_item(db, source, item):
|
||||
stat["created"] += 1
|
||||
consecutive_existing = 0
|
||||
|
||||
Reference in New Issue
Block a user