Compare commits

...

17 Commits

Author SHA1 Message Date
jxxghp
b04181fed9 更新 version.py 2024-12-19 20:24:11 +08:00
jxxghp
eee843bafd Merge pull request #3567 from InfinityPacer/feature/cache 2024-12-19 20:21:00 +08:00
InfinityPacer
134fd0761d refactor(cache): split douban cache into recommend and search 2024-12-19 20:00:29 +08:00
InfinityPacer
669481af06 feat(cache): unify bangumi cache strategy 2024-12-19 19:42:17 +08:00
jxxghp
b5640b3179 Merge pull request #3564 from InfinityPacer/feature/subscribe 2024-12-19 16:17:14 +08:00
InfinityPacer
9abb305dbb fix(subscribe): ensure best version is empty set 2024-12-19 15:41:51 +08:00
InfinityPacer
0fd4791479 fix(event): align field names with SubscribeComplete 2024-12-19 10:58:11 +08:00
jxxghp
ce2ecdf44c Merge pull request #3562 from InfinityPacer/feature/subscribe 2024-12-19 07:02:26 +08:00
InfinityPacer
949c0d3b76 feat(subscribe): optimize best version to support multiple states 2024-12-19 00:51:53 +08:00
jxxghp
316915842a Merge pull request #3559 from InfinityPacer/feature/site 2024-12-18 19:24:34 +08:00
jxxghp
1dd7dc36c3 Merge pull request #3557 from InfinityPacer/feature/subscribe 2024-12-18 19:24:00 +08:00
InfinityPacer
fca763b814 fix(site): avoid err_msg cannot be updated when it's None 2024-12-18 16:39:14 +08:00
InfinityPacer
9311125c72 fix(subscribe): avoid reinitializing the dictionary 2024-12-18 15:49:21 +08:00
InfinityPacer
3f1d4933c1 Merge pull request #3553 from InfinityPacer/feature/subscribe
fix(dependencies): pin python-115 version
2024-12-18 12:47:51 +08:00
InfinityPacer
7fb23b5069 fix(dependencies): pin python-115 version 2024-12-18 12:46:28 +08:00
DDSRem
d74ad343f1 Merge pull request #3551 from InfinityPacer/feature/subscribe
Revert "chore(deps): update dependency python-115 to v0.0.9.8.8.3"
2024-12-18 10:42:17 +08:00
InfinityPacer
c0a8351e58 Revert "chore(deps): update dependency python-115 to v0.0.9.8.8.3"
This reverts commit d182a7079d.
2024-12-18 10:39:37 +08:00
9 changed files with 124 additions and 93 deletions

View File

@@ -294,7 +294,7 @@ def delete_subscribe_by_mediaid(
# 发送事件
eventmanager.send_event(EventType.SubscribeDeleted, {
"subscribe_id": subscribe.id,
"subscribe": subscribe.to_dict()
"subscribe_info": subscribe.to_dict()
})
return schemas.Response(success=True)
@@ -521,7 +521,7 @@ def delete_subscribe(
# 发送事件
eventmanager.send_event(EventType.SubscribeDeleted, {
"subscribe_id": subscribe_id,
"subscribe": subscribe.to_dict()
"subscribe_info": subscribe.to_dict()
})
# 统计订阅
SubscribeHelper().sub_done_async({

View File

@@ -395,15 +395,17 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
return
# 当前下载资源的优先级
priority = max([item.torrent_info.pri_order for item in downloads])
# 订阅存在待定策略,不管是否已完成,均需更新订阅信息
self.subscribeoper.update(subscribe.id, {
"current_priority": priority,
"last_update": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
})
if priority == 100:
# 洗版完成
self.__finish_subscribe(subscribe=subscribe, meta=meta, mediainfo=mediainfo, bestversion=True)
self.__finish_subscribe(subscribe=subscribe, meta=meta, mediainfo=mediainfo)
else:
# 正在洗版,更新资源优先级
logger.info(f'{mediainfo.title_year} 正在洗版,更新资源优先级为 {priority}')
self.subscribeoper.update(subscribe.id, {
"current_priority": priority
})
def finish_subscribe_or_not(self, subscribe: Subscribe, meta: MetaInfo, mediainfo: MediaInfo,
downloads: List[Context] = None,
@@ -432,9 +434,12 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
# 未下载到内容且不完整
logger.info(f'{mediainfo.title_year} 未下载完整,继续订阅 ...')
elif downloads:
# 洗板,下载到了内容,更新资源优先级
# 洗下载到了内容,更新资源优先级
self.update_subscribe_priority(subscribe=subscribe, meta=meta,
mediainfo=mediainfo, downloads=downloads)
elif subscribe.current_priority == 100:
# 洗版完成
self.__finish_subscribe(subscribe=subscribe, meta=meta, mediainfo=mediainfo)
else:
# 洗版,未下载到内容
logger.info(f'{mediainfo.title_year} 继续洗版 ...')
@@ -818,6 +823,8 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
"""
获取已下载过的集数或电影
"""
if subscribe.best_version:
return []
note = subscribe.note or []
if not note:
return []
@@ -861,13 +868,12 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
lack_episode = len(left_episodes)
logger.info(f"{mediainfo.title_year}{season} 更新缺失集数为{lack_episode} ...")
break
update_data = {"lack_episode": lack_episode}
update_data["lack_episode"] = lack_episode
# 更新数据库
if update_data:
self.subscribeoper.update(subscribe.id, update_data)
def __finish_subscribe(self, subscribe: Subscribe, mediainfo: MediaInfo,
meta: MetaBase, bestversion: bool = False):
def __finish_subscribe(self, subscribe: Subscribe, mediainfo: MediaInfo, meta: MetaBase):
"""
完成订阅
"""
@@ -875,9 +881,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
if subscribe.state == "P":
return
# 完成订阅
msgstr = "订阅"
if bestversion:
msgstr = "洗版"
msgstr = "订阅" if not subscribe.best_version else "洗版"
logger.info(f'{mediainfo.title_year} 完成{msgstr}')
# 新增订阅历史
self.subscribeoper.add_history(**subscribe.to_dict())
@@ -1291,25 +1295,30 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
totals=totals
)
else:
# 洗版
exist_flag = False
if meta.type == MediaType.TV:
# 对于电视剧,构造缺失的媒体信息
no_exists = {
mediakey: {
subscribe.season: NotExistMediaInfo(
season=subscribe.season,
episodes=[],
total_episode=subscribe.total_episode,
start_episode=subscribe.start_episode or 1)
}
}
else:
# 洗版,如果已经满足了优先级,则认为已经洗版完成
if subscribe.current_priority == 100:
exist_flag = True
no_exists = {}
else:
exist_flag = False
if meta.type == MediaType.TV:
# 对于电视剧,构造缺失的媒体信息
no_exists = {
mediakey: {
subscribe.season: NotExistMediaInfo(
season=subscribe.season,
episodes=[],
total_episode=subscribe.total_episode,
start_episode=subscribe.start_episode or 1)
}
}
else:
no_exists = {}
# 如果媒体已存在,执行订阅完成操作
if exist_flag:
logger.info(f'{mediainfo.title_year} 媒体库中已存在')
if not subscribe.best_version:
logger.info(f'{mediainfo.title_year} 媒体库中已存在')
self.finish_subscribe_or_not(subscribe=subscribe, meta=meta, mediainfo=mediainfo, force=True)
return True, no_exists

View File

@@ -481,6 +481,7 @@ class Settings(BaseSettings, ConfigModel):
"refresh": 100,
"tmdb": 1024,
"douban": 512,
"bangumi": 512,
"fanart": 512,
"meta": (self.META_CACHE_EXPIRE or 24) * 3600
}
@@ -489,6 +490,7 @@ class Settings(BaseSettings, ConfigModel):
"refresh": 50,
"tmdb": 256,
"douban": 256,
"bangumi": 256,
"fanart": 128,
"meta": (self.META_CACHE_EXPIRE or 2) * 3600
}

View File

@@ -1,6 +1,6 @@
from datetime import datetime
from sqlalchemy import Column, Integer, String, Sequence, Float, JSON, func
from sqlalchemy import Column, Integer, String, Sequence, Float, JSON, func, or_
from sqlalchemy.orm import Session
from app.db import db_query, Base
@@ -81,7 +81,7 @@ class SiteUserData(Base):
func.max(SiteUserData.updated_day).label('latest_update_day')
)
.group_by(SiteUserData.domain)
.filter(SiteUserData.err_msg.is_(None))
.filter(or_(SiteUserData.err_msg.is_(None), SiteUserData.err_msg == ""))
.subquery()
)

View File

@@ -114,7 +114,8 @@ class SiteOper(DbOper):
"domain": domain,
"name": name,
"updated_day": current_day,
"updated_time": current_time
"updated_time": current_time,
"err_msg": payload.get("err_msg") or ""
})
# 按站点+天判断是否存在数据
siteuserdatas = SiteUserData.get_by_domain(self._db, domain=domain, workdate=current_day)

View File

@@ -2,7 +2,9 @@ from datetime import datetime
from functools import lru_cache
import requests
from cachetools import TTLCache, cached
from app.core.config import settings
from app.utils.http import RequestUtils
@@ -28,7 +30,7 @@ class BangumiApi(object):
pass
@classmethod
@lru_cache(maxsize=128)
@cached(cache=TTLCache(maxsize=settings.CACHE_CONF["bangumi"], ttl=settings.CACHE_CONF["meta"]))
def __invoke(cls, url, **kwargs):
req_url = cls._base_url + url
params = {}

View File

@@ -175,6 +175,19 @@ class DoubanApi(metaclass=Singleton):
).decode()
@cached(cache=TTLCache(maxsize=settings.CACHE_CONF["douban"], ttl=settings.CACHE_CONF["meta"]))
def __invoke_recommend(self, url: str, **kwargs) -> dict:
"""
推荐/发现类API
"""
return self.__invoke(url, **kwargs)
@cached(cache=TTLCache(maxsize=settings.CACHE_CONF["douban"], ttl=settings.CACHE_CONF["meta"]))
def __invoke_search(self, url: str, **kwargs) -> dict:
"""
搜索类API
"""
return self.__invoke(url, **kwargs)
def __invoke(self, url: str, **kwargs) -> dict:
"""
GET请求
@@ -244,189 +257,189 @@ class DoubanApi(metaclass=Singleton):
"""
关键字搜索
"""
return self.__invoke(self._urls["search"], q=keyword,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["search"], q=keyword,
start=start, count=count, _ts=ts)
def movie_search(self, keyword: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
电影搜索
"""
return self.__invoke(self._urls["movie_search"], q=keyword,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["movie_search"], q=keyword,
start=start, count=count, _ts=ts)
def tv_search(self, keyword: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
电视搜索
"""
return self.__invoke(self._urls["tv_search"], q=keyword,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["tv_search"], q=keyword,
start=start, count=count, _ts=ts)
def book_search(self, keyword: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
书籍搜索
"""
return self.__invoke(self._urls["book_search"], q=keyword,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["book_search"], q=keyword,
start=start, count=count, _ts=ts)
def group_search(self, keyword: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
小组搜索
"""
return self.__invoke(self._urls["group_search"], q=keyword,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["group_search"], q=keyword,
start=start, count=count, _ts=ts)
def person_search(self, keyword: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
人物搜索
"""
return self.__invoke(self._urls["search_subject"], type="person", q=keyword,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["search_subject"], type="person", q=keyword,
start=start, count=count, _ts=ts)
def movie_showing(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
正在热映
"""
return self.__invoke(self._urls["movie_showing"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["movie_showing"],
start=start, count=count, _ts=ts)
def movie_soon(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
即将上映
"""
return self.__invoke(self._urls["movie_soon"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["movie_soon"],
start=start, count=count, _ts=ts)
def movie_hot_gaia(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
热门电影
"""
return self.__invoke(self._urls["movie_hot_gaia"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["movie_hot_gaia"],
start=start, count=count, _ts=ts)
def tv_hot(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
热门剧集
"""
return self.__invoke(self._urls["tv_hot"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_hot"],
start=start, count=count, _ts=ts)
def tv_animation(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
动画
"""
return self.__invoke(self._urls["tv_animation"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_animation"],
start=start, count=count, _ts=ts)
def tv_variety_show(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
综艺
"""
return self.__invoke(self._urls["tv_variety_show"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_variety_show"],
start=start, count=count, _ts=ts)
def tv_rank_list(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
电视剧排行榜
"""
return self.__invoke(self._urls["tv_rank_list"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_rank_list"],
start=start, count=count, _ts=ts)
def show_hot(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
综艺热门
"""
return self.__invoke(self._urls["show_hot"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["show_hot"],
start=start, count=count, _ts=ts)
def movie_detail(self, subject_id: str):
"""
电影详情
"""
return self.__invoke(self._urls["movie_detail"] + subject_id)
return self.__invoke_search(self._urls["movie_detail"] + subject_id)
def movie_celebrities(self, subject_id: str):
"""
电影演职员
"""
return self.__invoke(self._urls["movie_celebrities"] % subject_id)
return self.__invoke_search(self._urls["movie_celebrities"] % subject_id)
def tv_detail(self, subject_id: str):
"""
电视剧详情
"""
return self.__invoke(self._urls["tv_detail"] + subject_id)
return self.__invoke_search(self._urls["tv_detail"] + subject_id)
def tv_celebrities(self, subject_id: str):
"""
电视剧演职员
"""
return self.__invoke(self._urls["tv_celebrities"] % subject_id)
return self.__invoke_search(self._urls["tv_celebrities"] % subject_id)
def book_detail(self, subject_id: str):
"""
书籍详情
"""
return self.__invoke(self._urls["book_detail"] + subject_id)
return self.__invoke_search(self._urls["book_detail"] + subject_id)
def movie_top250(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
电影TOP250
"""
return self.__invoke(self._urls["movie_top250"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["movie_top250"],
start=start, count=count, _ts=ts)
def movie_recommend(self, tags='', sort='R', start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
电影探索
"""
return self.__invoke(self._urls["movie_recommend"], tags=tags, sort=sort,
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["movie_recommend"], tags=tags, sort=sort,
start=start, count=count, _ts=ts)
def tv_recommend(self, tags='', sort='R', start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
电视剧探索
"""
return self.__invoke(self._urls["tv_recommend"], tags=tags, sort=sort,
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_recommend"], tags=tags, sort=sort,
start=start, count=count, _ts=ts)
def tv_chinese_best_weekly(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
华语口碑周榜
"""
return self.__invoke(self._urls["tv_chinese_best_weekly"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_chinese_best_weekly"],
start=start, count=count, _ts=ts)
def tv_global_best_weekly(self, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
"""
全球口碑周榜
"""
return self.__invoke(self._urls["tv_global_best_weekly"],
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_global_best_weekly"],
start=start, count=count, _ts=ts)
def doulist_detail(self, subject_id: str):
"""
豆列详情
:param subject_id: 豆列id
"""
return self.__invoke(self._urls["doulist"] + subject_id)
return self.__invoke_search(self._urls["doulist"] + subject_id)
def doulist_items(self, subject_id: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
@@ -437,8 +450,8 @@ class DoubanApi(metaclass=Singleton):
:param count: 数量
:param ts: 时间戳
"""
return self.__invoke(self._urls["doulist_items"] % subject_id,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["doulist_items"] % subject_id,
start=start, count=count, _ts=ts)
def movie_recommendations(self, subject_id: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
@@ -449,8 +462,8 @@ class DoubanApi(metaclass=Singleton):
:param count: 数量
:param ts: 时间戳
"""
return self.__invoke(self._urls["movie_recommendations"] % subject_id,
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["movie_recommendations"] % subject_id,
start=start, count=count, _ts=ts)
def tv_recommendations(self, subject_id: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
@@ -461,8 +474,8 @@ class DoubanApi(metaclass=Singleton):
:param count: 数量
:param ts: 时间戳
"""
return self.__invoke(self._urls["tv_recommendations"] % subject_id,
start=start, count=count, _ts=ts)
return self.__invoke_recommend(self._urls["tv_recommendations"] % subject_id,
start=start, count=count, _ts=ts)
def movie_photos(self, subject_id: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
@@ -473,8 +486,8 @@ class DoubanApi(metaclass=Singleton):
:param count: 数量
:param ts: 时间戳
"""
return self.__invoke(self._urls["movie_photos"] % subject_id,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["movie_photos"] % subject_id,
start=start, count=count, _ts=ts)
def tv_photos(self, subject_id: str, start: int = 0, count: int = 20,
ts=datetime.strftime(datetime.now(), '%Y%m%d')):
@@ -485,8 +498,8 @@ class DoubanApi(metaclass=Singleton):
:param count: 数量
:param ts: 时间戳
"""
return self.__invoke(self._urls["tv_photos"] % subject_id,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["tv_photos"] % subject_id,
start=start, count=count, _ts=ts)
def person_detail(self, subject_id: int):
"""
@@ -494,7 +507,7 @@ class DoubanApi(metaclass=Singleton):
:param subject_id: 人物 id
:return:
"""
return self.__invoke(self._urls["person_detail"] + str(subject_id))
return self.__invoke_search(self._urls["person_detail"] + str(subject_id))
def person_work(self, subject_id: int, start: int = 0, count: int = 20, sort_by: str = "time",
collection_title: str = "影视",
@@ -509,14 +522,16 @@ class DoubanApi(metaclass=Singleton):
:param ts: 时间戳
:return:
"""
return self.__invoke(self._urls["person_work"] % subject_id, sortby=sort_by, collection_title=collection_title,
start=start, count=count, _ts=ts)
return self.__invoke_search(self._urls["person_work"] % subject_id, sortby=sort_by,
collection_title=collection_title,
start=start, count=count, _ts=ts)
def clear_cache(self):
"""
清空LRU缓存
"""
self.__invoke.cache_clear()
# 尚未支持缓存清理
pass
def close(self):
if self._session:

View File

@@ -58,6 +58,8 @@ pystray~=0.19.5
pyotp~=2.9.0
Pinyin2Hanzi~=0.1.1
pywebpush~=2.0.0
python-115~=0.0.9.8.8.3
python-115==0.0.9.8.8.2
p115client==0.0.3.8.3.3
python-cookietools==0.0.2.1
aligo~=6.2.4
aiofiles~=24.1.0
aiofiles~=24.1.0

View File

@@ -1,2 +1,2 @@
APP_VERSION = 'v2.1.3'
APP_VERSION = 'v2.1.4'
FRONTEND_VERSION = 'v2.1.4'