Merge pull request #3580 from jxxghp/v2

Sync
This commit is contained in:
DDSRem
2024-12-20 23:16:30 +08:00
committed by GitHub
12 changed files with 142 additions and 124 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

@@ -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加密
"""

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

@@ -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"|(?<![a-z0-9])gb(?![a-z0-9])"
_zhtw_sub_re = r"([.\[(](((zh[-_])?(hk|tw|cht|tc))" \
r"|(cht|eng)[-_&]?(cht|eng)" \
r"|繁[体中]?)[.\])])" \
r"|繁体中[文字]|中[文字]繁体|繁体|JPTC" \
r"|繁体中[文字]|中[文字]繁体|繁体|JPTC|tc_jp" \
r"|(?<![a-z0-9])big5(?![a-z0-9])"
_eng_sub_re = r"[.\[(]eng[.\])]"

View File

@@ -1,4 +1,3 @@
import copy
import json
import subprocess
from pathlib import Path
@@ -57,21 +56,6 @@ class Rclone(StorageBase):
else:
return None
def __get_fileitem(self, path: Path):
"""
获取文件项
"""
return schemas.FileItem(
storage=self.schema.value,
type="file",
path=str(path).replace("\\", "/"),
name=path.name,
basename=path.stem,
extension=path.suffix[1:],
size=path.stat().st_size,
modify_time=path.stat().st_mtime,
)
def __get_rcloneitem(self, item: dict, parent: str = "/") -> 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

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.2'
FRONTEND_VERSION = 'v2.1.3'
APP_VERSION = 'v2.1.5'
FRONTEND_VERSION = 'v2.1.5'