diff --git a/app/api/endpoints/subscribe.py b/app/api/endpoints/subscribe.py index 5836373a..b19c36bf 100644 --- a/app/api/endpoints/subscribe.py +++ b/app/api/endpoints/subscribe.py @@ -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({ diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index dd92559f..e2da7762 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -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 diff --git a/app/core/config.py b/app/core/config.py index 96ac58bd..9d04c6af 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -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 } diff --git a/app/core/security.py b/app/core/security.py index c802c11b..fbde303b 100644 --- a/app/core/security.py +++ b/app/core/security.py @@ -286,7 +286,7 @@ def decrypt(data: bytes, key: bytes) -> Optional[bytes]: return None -def encrypt_message(message: str, key: bytes): +def encrypt_message(message: str, key: bytes) -> str: """ 使用给定的key对消息进行加密,并返回加密后的字符串 """ @@ -295,14 +295,14 @@ def encrypt_message(message: str, key: bytes): return encrypted_message.decode() -def hash_sha256(message): +def hash_sha256(message: str) -> str: """ 对字符串做hash运算 """ return hashlib.sha256(message.encode()).hexdigest() -def aes_decrypt(data, key): +def aes_decrypt(data: str, key: str) -> str: """ AES解密 """ @@ -322,7 +322,7 @@ def aes_decrypt(data, key): return result.decode('utf-8') -def aes_encrypt(data, key): +def aes_encrypt(data: str, key: str) -> str: """ AES加密 """ @@ -338,7 +338,7 @@ def aes_encrypt(data, key): return base64.b64encode(cipher.iv + result).decode('utf-8') -def nexusphp_encrypt(data_str: str, key): +def nexusphp_encrypt(data_str: str, key: bytes) -> str: """ NexusPHP加密 """ diff --git a/app/db/models/siteuserdata.py b/app/db/models/siteuserdata.py index e2f13e8d..3c97ded0 100644 --- a/app/db/models/siteuserdata.py +++ b/app/db/models/siteuserdata.py @@ -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() ) diff --git a/app/db/site_oper.py b/app/db/site_oper.py index 5b7c1410..42ec3564 100644 --- a/app/db/site_oper.py +++ b/app/db/site_oper.py @@ -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) diff --git a/app/modules/bangumi/bangumi.py b/app/modules/bangumi/bangumi.py index 440017ab..df8c92d0 100644 --- a/app/modules/bangumi/bangumi.py +++ b/app/modules/bangumi/bangumi.py @@ -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 = {} diff --git a/app/modules/douban/apiv2.py b/app/modules/douban/apiv2.py index ec884b2d..56a21b2e 100644 --- a/app/modules/douban/apiv2.py +++ b/app/modules/douban/apiv2.py @@ -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: diff --git a/app/modules/filemanager/__init__.py b/app/modules/filemanager/__init__.py index 38efe6bb..ff4c711d 100644 --- a/app/modules/filemanager/__init__.py +++ b/app/modules/filemanager/__init__.py @@ -609,12 +609,12 @@ class FileManagerModule(_ModuleBase): r"|chinese|(cn|ch[si]|sg|zho?|eng)[-_&]?(cn|ch[si]|sg|zho?|eng)" \ r"|简[体中]?)[.\])])" \ r"|([\u4e00-\u9fa5]{0,3}[中双][\u4e00-\u9fa5]{0,2}[字文语][\u4e00-\u9fa5]{0,3})" \ - r"|简体|简中|JPSC" \ + r"|简体|简中|JPSC|sc_jp" \ r"|(? schemas.FileItem: """ 获取rclone文件项 @@ -146,12 +130,12 @@ class Rclone(StorageBase): retcode = subprocess.run( [ 'rclone', 'mkdir', - f'MP:{fileitem.path}/{name}' + f'MP:{Path(fileitem.path) / name}' ], startupinfo=self.__get_hidden_shell() ).returncode if retcode == 0: - return self.get_item(Path(f"{fileitem.path}/{name}")) + return self.get_item(Path(fileitem.path) / name) except Exception as err: logger.error(f"rclone创建目录失败:{err}") return None @@ -200,16 +184,19 @@ class Rclone(StorageBase): ret = subprocess.run( [ 'rclone', 'lsjson', - f'MP:{path}' + f'MP:{path.parent}' ], capture_output=True, startupinfo=self.__get_hidden_shell() ) if ret.returncode == 0: items = json.loads(ret.stdout) - return self.__get_rcloneitem(items[0]) + for item in items: + if item.get("Name") == path.name: + return self.__get_rcloneitem(item, parent=str(path.parent) + "/") + return None except Exception as err: - logger.error(f"rclone获取文件失败:{err}") + logger.debug(f"rclone获取文件项失败:{err}") return None def delete(self, fileitem: schemas.FileItem) -> bool: @@ -239,7 +226,7 @@ class Rclone(StorageBase): [ 'rclone', 'moveto', f'MP:{fileitem.path}', - f'MP:{Path(fileitem.path).parent}/{name}' + f'MP:{Path(fileitem.path).parent / name}' ], startupinfo=self.__get_hidden_shell() ).returncode @@ -287,7 +274,7 @@ class Rclone(StorageBase): startupinfo=self.__get_hidden_shell() ).returncode if retcode == 0: - return self.__get_fileitem(new_path) + return self.get_item(new_path) except Exception as err: logger.error(f"rclone上传文件失败:{err}") return None diff --git a/requirements.in b/requirements.in index 9b831c3e..59d54324 100644 --- a/requirements.in +++ b/requirements.in @@ -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 \ No newline at end of file diff --git a/version.py b/version.py index bab66fec..2236e4c1 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -APP_VERSION = 'v2.1.2' -FRONTEND_VERSION = 'v2.1.3' +APP_VERSION = 'v2.1.5' +FRONTEND_VERSION = 'v2.1.5'