Merge pull request #4505 from wikrin/v2

This commit is contained in:
jxxghp
2025-06-29 23:12:08 +08:00
committed by GitHub
30 changed files with 75 additions and 73 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ body:
目的是让协作的开发者间清晰的知道「要做什么」和「具体会怎么做」,以及所有的开发者都能公开透明的参与讨论; 目的是让协作的开发者间清晰的知道「要做什么」和「具体会怎么做」,以及所有的开发者都能公开透明的参与讨论;
以便评估和讨论产生的影响 (遗漏的考虑、向后兼容性、与现有功能的冲突), 以便评估和讨论产生的影响 (遗漏的考虑、向后兼容性、与现有功能的冲突),
因此提案侧重在对解决问题的 **方案、设计、步骤** 的描述上。 因此提案侧重在对解决问题的 **方案、设计、步骤** 的描述上。
如果仅希望讨论是否添加或改进某功能本身,请使用 -> [Issue: 功能改进](https://github.com/jxxghp/MoviePilot/issues/new?assignees=&labels=feature+request&projects=&template=feature_request.yml&title=%5BFeature+Request%5D%3A+) 如果仅希望讨论是否添加或改进某功能本身,请使用 -> [Issue: 功能改进](https://github.com/jxxghp/MoviePilot/issues/new?assignees=&labels=feature+request&projects=&template=feature_request.yml&title=%5BFeature+Request%5D%3A+)
- type: textarea - type: textarea
id: background id: background
+12 -12
View File
@@ -8,17 +8,17 @@ jobs:
pylint: pylint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Pylint Code Quality Check name: Pylint Code Quality Check
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.12' python-version: '3.12'
cache: 'pip' cache: 'pip'
- name: Cache pip dependencies - name: Cache pip dependencies
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
@@ -26,7 +26,7 @@ jobs:
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements.in') }} key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements.in') }}
restore-keys: | restore-keys: |
${{ runner.os }}-pip- ${{ runner.os }}-pip-
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade pip setuptools wheel
@@ -41,7 +41,7 @@ jobs:
else else
echo "⚠️ 未找到依赖文件,仅安装 pylint" echo "⚠️ 未找到依赖文件,仅安装 pylint"
fi fi
- name: Verify pylint config - name: Verify pylint config
run: | run: |
# 检查项目中的pylint配置文件是否存在 # 检查项目中的pylint配置文件是否存在
@@ -57,35 +57,35 @@ jobs:
run: | run: |
# 运行pylint,检查主要的Python文件 # 运行pylint,检查主要的Python文件
echo "🚀 运行 Pylint 错误检查..." echo "🚀 运行 Pylint 错误检查..."
# 检查主要目录 - 只关注错误,如果有错误则退出 # 检查主要目录 - 只关注错误,如果有错误则退出
echo "📂 检查 app/ 目录..." echo "📂 检查 app/ 目录..."
pylint app/ --output-format=colorized --reports=yes --score=yes pylint app/ --output-format=colorized --reports=yes --score=yes
# 检查根目录的Python文件 # 检查根目录的Python文件
echo "📂 检查根目录 Python 文件..." echo "📂 检查根目录 Python 文件..."
for file in $(find . -name "*.py" -not -path "./.*" -not -path "./.venv/*" -not -path "./build/*" -not -path "./dist/*" -not -path "./tests/*" -not -path "./docs/*" -not -path "./__pycache__/*" -maxdepth 1); do for file in $(find . -name "*.py" -not -path "./.*" -not -path "./.venv/*" -not -path "./build/*" -not -path "./dist/*" -not -path "./tests/*" -not -path "./docs/*" -not -path "./__pycache__/*" -maxdepth 1); do
echo "检查文件: $file" echo "检查文件: $file"
pylint "$file" --output-format=colorized || exit 1 pylint "$file" --output-format=colorized || exit 1
done done
# 生成详细报告 # 生成详细报告
echo "📊 生成 Pylint 详细报告..." echo "📊 生成 Pylint 详细报告..."
pylint app/ --output-format=json > pylint-report.json || true pylint app/ --output-format=json > pylint-report.json || true
# 显示评分(仅供参考) # 显示评分(仅供参考)
echo "📈 Pylint 评分(仅供参考):" echo "📈 Pylint 评分(仅供参考):"
pylint app/ --score=yes --reports=no | tail -2 || true pylint app/ --score=yes --reports=no | tail -2 || true
- name: Upload pylint report - name: Upload pylint report
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: pylint-report name: pylint-report
path: pylint-report.json path: pylint-report.json
- name: Summary - name: Summary
run: | run: |
echo "🎉 Pylint 检查完成!" echo "🎉 Pylint 检查完成!"
echo "✅ 没有发现语法错误或严重问题" echo "✅ 没有发现语法错误或严重问题"
echo "📊 详细报告已保存为构建工件" echo "📊 详细报告已保存为构建工件"
+2 -2
View File
@@ -12,7 +12,7 @@ jobs=0
# 只关注错误级别的问题,禁用警告、约定和重构建议 # 只关注错误级别的问题,禁用警告、约定和重构建议
# E = Error (错误) - 会导致构建失败 # E = Error (错误) - 会导致构建失败
# W = Warning (警告) - 仅显示,不会失败 # W = Warning (警告) - 仅显示,不会失败
# R = Refactor (重构建议) - 仅显示,不会失败 # R = Refactor (重构建议) - 仅显示,不会失败
# C = Convention (约定) - 仅显示,不会失败 # C = Convention (约定) - 仅显示,不会失败
# I = Information (信息) - 仅显示,不会失败 # I = Information (信息) - 仅显示,不会失败
@@ -80,4 +80,4 @@ ignore-imports=yes
[TYPECHECK] [TYPECHECK]
# 生成缺失成员提示的类列表 # 生成缺失成员提示的类列表
generated-members=requests.packages.urllib3 generated-members=requests.packages.urllib3
+3 -1
View File
@@ -44,6 +44,8 @@ def download(
# 种子信息 # 种子信息
torrentinfo = TorrentInfo() torrentinfo = TorrentInfo()
torrentinfo.from_dict(torrent_in.dict()) torrentinfo.from_dict(torrent_in.dict())
# 手动下载始终使用选择的下载器
torrentinfo.site_downloader = downloader
# 上下文 # 上下文
context = Context( context = Context(
meta_info=metainfo, meta_info=metainfo,
@@ -51,7 +53,7 @@ def download(
torrent_info=torrentinfo torrent_info=torrentinfo
) )
did = DownloadChain().download_single(context=context, username=current_user.name, did = DownloadChain().download_single(context=context, username=current_user.name,
downloader=downloader, save_path=save_path, source="Manual") save_path=save_path, source="Manual")
if not did: if not did:
return schemas.Response(success=False, message="任务添加失败") return schemas.Response(success=False, message="任务添加失败")
return schemas.Response(success=True, data={ return schemas.Response(success=True, data={
+5 -5
View File
@@ -43,7 +43,7 @@ class MediaChain(ChainBase):
'movie_banner': True, # 电影横幅图 'movie_banner': True, # 电影横幅图
'movie_thumb': True, # 电影缩略图 'movie_thumb': True, # 电影缩略图
'tv_nfo': True, # 电视剧NFO 'tv_nfo': True, # 电视剧NFO
'tv_poster': True, # 电视剧海报 'tv_poster': True, # 电视剧海报
'tv_backdrop': True, # 电视剧背景图 'tv_backdrop': True, # 电视剧背景图
'tv_banner': True, # 电视剧横幅图 'tv_banner': True, # 电视剧横幅图
'tv_logo': True, # 电视剧Logo 'tv_logo': True, # 电视剧Logo
@@ -448,7 +448,7 @@ class MediaChain(ChainBase):
if not mediainfo: if not mediainfo:
logger.warn(f"{filepath} 无法识别文件媒体信息!") logger.warn(f"{filepath} 无法识别文件媒体信息!")
return return
# 获取刮削开关配置 # 获取刮削开关配置
scraping_switchs = self._get_scraping_switchs() scraping_switchs = self._get_scraping_switchs()
logger.info(f"开始刮削:{filepath} ...") logger.info(f"开始刮削:{filepath} ...")
@@ -520,7 +520,7 @@ class MediaChain(ChainBase):
should_scrape = scraping_switchs.get('movie_thumb', True) should_scrape = scraping_switchs.get('movie_thumb', True)
else: else:
should_scrape = True # 未知类型默认刮削 should_scrape = True # 未知类型默认刮削
if should_scrape: if should_scrape:
image_path = filepath.with_name(image_name) image_path = filepath.with_name(image_name)
if overwrite or not storagechain.get_file_item(storage=fileitem.storage, if overwrite or not storagechain.get_file_item(storage=fileitem.storage,
@@ -653,7 +653,7 @@ class MediaChain(ChainBase):
should_scrape = scraping_switchs.get('season_thumb', True) should_scrape = scraping_switchs.get('season_thumb', True)
else: else:
should_scrape = True # 未知类型默认刮削 should_scrape = True # 未知类型默认刮削
if should_scrape: if should_scrape:
image_path = filepath.with_name(image_name) image_path = filepath.with_name(image_name)
# 只下载当前刮削季的图片 # 只下载当前刮削季的图片
@@ -714,7 +714,7 @@ class MediaChain(ChainBase):
should_scrape = scraping_switchs.get('tv_thumb', True) should_scrape = scraping_switchs.get('tv_thumb', True)
else: else:
should_scrape = True # 未知类型默认刮削 should_scrape = True # 未知类型默认刮削
if should_scrape: if should_scrape:
image_path = filepath / image_name image_path = filepath / image_name
if overwrite or not storagechain.get_file_item(storage=fileitem.storage, if overwrite or not storagechain.get_file_item(storage=fileitem.storage,
+1 -1
View File
@@ -35,7 +35,7 @@ class SystemChain(ChainBase):
重启系统 重启系统
""" """
from app.core.config import global_vars from app.core.config import global_vars
if channel and userid: if channel and userid:
self.post_message(Notification(channel=channel, source=source, self.post_message(Notification(channel=channel, source=source,
title="系统正在重启,请耐心等候!", userid=userid)) title="系统正在重启,请耐心等候!", userid=userid))
+1 -1
View File
@@ -880,7 +880,7 @@ class TransferChain(ChainBase, metaclass=Singleton):
) -> List[Tuple[FileItem, bool]]: ) -> List[Tuple[FileItem, bool]]:
""" """
获取整理目录或文件列表 获取整理目录或文件列表
:param fileitem: 文件项 :param fileitem: 文件项
:param depth: 递归深度,默认为1 :param depth: 递归深度,默认为1
""" """
+2 -2
View File
@@ -150,7 +150,7 @@ class CacheToolsBackend(CacheBackend):
region = self.get_region(region) region = self.get_region(region)
return self._region_caches.get(region) return self._region_caches.get(region)
def set(self, key: str, value: Any, ttl: Optional[int] = None, def set(self, key: str, value: Any, ttl: Optional[int] = None,
region: Optional[str] = DEFAULT_CACHE_REGION, **kwargs) -> None: region: Optional[str] = DEFAULT_CACHE_REGION, **kwargs) -> None:
""" """
设置缓存值支持每个 key 独立配置 TTL 和 Maxsize 设置缓存值支持每个 key 独立配置 TTL 和 Maxsize
@@ -357,7 +357,7 @@ class RedisBackend(CacheBackend):
region = self.get_region(quote(region)) region = self.get_region(quote(region))
return f"{region}:key:{quote(key)}" return f"{region}:key:{quote(key)}"
def set(self, key: str, value: Any, ttl: Optional[int] = None, def set(self, key: str, value: Any, ttl: Optional[int] = None,
region: Optional[str] = DEFAULT_CACHE_REGION, **kwargs) -> None: region: Optional[str] = DEFAULT_CACHE_REGION, **kwargs) -> None:
""" """
设置缓存 设置缓存
+5 -5
View File
@@ -154,35 +154,35 @@ def find_metainfo(title: str) -> Tuple[str, dict]:
# 去除title中该部分 # 去除title中该部分
if tmdbid or mtype or begin_season or end_season or begin_episode or end_episode: if tmdbid or mtype or begin_season or end_season or begin_episode or end_episode:
title = title.replace(f"{{[{result}]}}", '') title = title.replace(f"{{[{result}]}}", '')
# 支持Emby格式的ID标签 # 支持Emby格式的ID标签
# 1. [tmdbid=xxxx] 或 [tmdbid-xxxx] 格式 # 1. [tmdbid=xxxx] 或 [tmdbid-xxxx] 格式
tmdb_match = re.search(r'\[tmdbid[=\-](\d+)\]', title) tmdb_match = re.search(r'\[tmdbid[=\-](\d+)\]', title)
if tmdb_match: if tmdb_match:
metainfo['tmdbid'] = tmdb_match.group(1) metainfo['tmdbid'] = tmdb_match.group(1)
title = re.sub(r'\[tmdbid[=\-](\d+)\]', '', title).strip() title = re.sub(r'\[tmdbid[=\-](\d+)\]', '', title).strip()
# 2. [tmdb=xxxx] 或 [tmdb-xxxx] 格式 # 2. [tmdb=xxxx] 或 [tmdb-xxxx] 格式
if not metainfo['tmdbid']: if not metainfo['tmdbid']:
tmdb_match = re.search(r'\[tmdb[=\-](\d+)\]', title) tmdb_match = re.search(r'\[tmdb[=\-](\d+)\]', title)
if tmdb_match: if tmdb_match:
metainfo['tmdbid'] = tmdb_match.group(1) metainfo['tmdbid'] = tmdb_match.group(1)
title = re.sub(r'\[tmdb[=\-](\d+)\]', '', title).strip() title = re.sub(r'\[tmdb[=\-](\d+)\]', '', title).strip()
# 3. {tmdbid=xxxx} 或 {tmdbid-xxxx} 格式 # 3. {tmdbid=xxxx} 或 {tmdbid-xxxx} 格式
if not metainfo['tmdbid']: if not metainfo['tmdbid']:
tmdb_match = re.search(r'\{tmdbid[=\-](\d+)\}', title) tmdb_match = re.search(r'\{tmdbid[=\-](\d+)\}', title)
if tmdb_match: if tmdb_match:
metainfo['tmdbid'] = tmdb_match.group(1) metainfo['tmdbid'] = tmdb_match.group(1)
title = re.sub(r'\{tmdbid[=\-](\d+)\}', '', title).strip() title = re.sub(r'\{tmdbid[=\-](\d+)\}', '', title).strip()
# 4. {tmdb=xxxx} 或 {tmdb-xxxx} 格式 # 4. {tmdb=xxxx} 或 {tmdb-xxxx} 格式
if not metainfo['tmdbid']: if not metainfo['tmdbid']:
tmdb_match = re.search(r'\{tmdb[=\-](\d+)\}', title) tmdb_match = re.search(r'\{tmdb[=\-](\d+)\}', title)
if tmdb_match: if tmdb_match:
metainfo['tmdbid'] = tmdb_match.group(1) metainfo['tmdbid'] = tmdb_match.group(1)
title = re.sub(r'\{tmdb[=\-](\d+)\}', '', title).strip() title = re.sub(r'\{tmdb[=\-](\d+)\}', '', title).strip()
# 计算季集总数 # 计算季集总数
if metainfo.get('begin_season') and metainfo.get('end_season'): if metainfo.get('begin_season') and metainfo.get('end_season'):
if metainfo['begin_season'] > metainfo['end_season']: if metainfo['begin_season'] > metainfo['end_season']:
+10 -10
View File
@@ -46,17 +46,17 @@ class PlaywrightHelper:
browser = playwright[self.browser_type].launch(headless=headless) browser = playwright[self.browser_type].launch(headless=headless)
context = browser.new_context(user_agent=ua, proxy=proxies) context = browser.new_context(user_agent=ua, proxy=proxies)
page = context.new_page() page = context.new_page()
if cookies: if cookies:
page.set_extra_http_headers({"cookie": cookies}) page.set_extra_http_headers({"cookie": cookies})
if not self.__pass_cloudflare(url, page): if not self.__pass_cloudflare(url, page):
logger.warn("cloudflare challenge fail") logger.warn("cloudflare challenge fail")
page.wait_for_load_state("networkidle", timeout=timeout * 1000) page.wait_for_load_state("networkidle", timeout=timeout * 1000)
# 回调函数 # 回调函数
result = callback(page) result = callback(page)
except Exception as e: except Exception as e:
logger.error(f"网页操作失败: {str(e)}") logger.error(f"网页操作失败: {str(e)}")
finally: finally:
@@ -69,7 +69,7 @@ class PlaywrightHelper:
browser.close() browser.close()
except Exception as e: except Exception as e:
logger.error(f"Playwright初始化失败: {str(e)}") logger.error(f"Playwright初始化失败: {str(e)}")
return result return result
def get_page_source(self, url: str, def get_page_source(self, url: str,
@@ -97,16 +97,16 @@ class PlaywrightHelper:
browser = playwright[self.browser_type].launch(headless=headless) browser = playwright[self.browser_type].launch(headless=headless)
context = browser.new_context(user_agent=ua, proxy=proxies) context = browser.new_context(user_agent=ua, proxy=proxies)
page = context.new_page() page = context.new_page()
if cookies: if cookies:
page.set_extra_http_headers({"cookie": cookies}) page.set_extra_http_headers({"cookie": cookies})
if not self.__pass_cloudflare(url, page): if not self.__pass_cloudflare(url, page):
logger.warn("cloudflare challenge fail") logger.warn("cloudflare challenge fail")
page.wait_for_load_state("networkidle", timeout=timeout * 1000) page.wait_for_load_state("networkidle", timeout=timeout * 1000)
source = page.content() source = page.content()
except Exception as e: except Exception as e:
logger.error(f"获取网页源码失败: {str(e)}") logger.error(f"获取网页源码失败: {str(e)}")
source = None source = None
@@ -120,7 +120,7 @@ class PlaywrightHelper:
browser.close() browser.close()
except Exception as e: except Exception as e:
logger.error(f"Playwright初始化失败: {str(e)}") logger.error(f"Playwright初始化失败: {str(e)}")
return source return source
+1 -1
View File
@@ -361,7 +361,7 @@ class MemoryHelper(metaclass=Singleton):
# 对于较大的对象,使用 asizeof 进行深度计算 # 对于较大的对象,使用 asizeof 进行深度计算
size_bytes = asizeof.asizeof(obj) size_bytes = asizeof.asizeof(obj)
# 只处理大于10KB的对象,提高分析效率 # 只处理大于10KB的对象,提高分析效率
if size_bytes < 10240: if size_bytes < 10240:
continue continue
+1 -1
View File
@@ -9,7 +9,7 @@ class OcrHelper:
_ocr_b64_url = f"{settings.OCR_HOST}/captcha/base64" _ocr_b64_url = f"{settings.OCR_HOST}/captcha/base64"
def get_captcha_text(self, image_url: Optional[str] = None, image_b64: Optional[str] = None, def get_captcha_text(self, image_url: Optional[str] = None, image_b64: Optional[str] = None,
cookie: Optional[str] = None, ua: Optional[str] = None): cookie: Optional[str] = None, ua: Optional[str] = None):
""" """
根据图片地址,获取验证码图片,并识别内容 根据图片地址,获取验证码图片,并识别内容
+3 -3
View File
@@ -53,10 +53,10 @@ class PluginHelper(metaclass=Singleton):
# 如果强制刷新,直接调用不带缓存的版本 # 如果强制刷新,直接调用不带缓存的版本
if force: if force:
return self._get_plugins_uncached(repo_url, package_version) return self._get_plugins_uncached(repo_url, package_version)
# 正常情况下调用带缓存的版本 # 正常情况下调用带缓存的版本
return self._get_plugins_cached(repo_url, package_version) return self._get_plugins_cached(repo_url, package_version)
@cached(maxsize=64, ttl=1800) @cached(maxsize=64, ttl=1800)
def _get_plugins_cached(self, repo_url: str, package_version: Optional[str] = None) -> Optional[Dict[str, dict]]: def _get_plugins_cached(self, repo_url: str, package_version: Optional[str] = None) -> Optional[Dict[str, dict]]:
""" """
@@ -65,7 +65,7 @@ class PluginHelper(metaclass=Singleton):
:param package_version: 首选插件版本 (如 "v2", "v3"),如果不指定则获取 v1 版本 :param package_version: 首选插件版本 (如 "v2", "v3"),如果不指定则获取 v1 版本
""" """
return self._get_plugins_uncached(repo_url, package_version) return self._get_plugins_uncached(repo_url, package_version)
def _get_plugins_uncached(self, repo_url: str, package_version: Optional[str] = None) -> Optional[Dict[str, dict]]: def _get_plugins_uncached(self, repo_url: str, package_version: Optional[str] = None) -> Optional[Dict[str, dict]]:
""" """
获取Github所有最新插件列表(不使用缓存) 获取Github所有最新插件列表(不使用缓存)
+1 -1
View File
@@ -289,7 +289,7 @@ class RssHelper:
if not ret_xml or not ret_xml.strip(): if not ret_xml or not ret_xml.strip():
logger.error("RSS内容为空") logger.error("RSS内容为空")
return False return False
# 检查是否包含基本的RSS/XML结构 # 检查是否包含基本的RSS/XML结构
ret_xml_stripped = ret_xml.strip() ret_xml_stripped = ret_xml.strip()
if not ret_xml_stripped.startswith('<'): if not ret_xml_stripped.startswith('<'):
+3 -3
View File
@@ -91,10 +91,10 @@ class SystemHelper:
# 检查是否有有效的重启策略 # 检查是否有有效的重启策略
auto_restart_policies = ['always', 'unless-stopped', 'on-failure'] auto_restart_policies = ['always', 'unless-stopped', 'on-failure']
has_restart_policy = policy_name in auto_restart_policies has_restart_policy = policy_name in auto_restart_policies
logger.info(f"容器重启策略: {policy_name}, 支持自动重启: {has_restart_policy}") logger.info(f"容器重启策略: {policy_name}, 支持自动重启: {has_restart_policy}")
return has_restart_policy return has_restart_policy
except Exception as e: except Exception as e:
logger.warning(f"检查重启策略失败: {str(e)}") logger.warning(f"检查重启策略失败: {str(e)}")
return False return False
@@ -106,7 +106,7 @@ class SystemHelper:
""" """
if not SystemUtils.is_docker(): if not SystemUtils.is_docker():
return False, "非Docker环境,无法重启!" return False, "非Docker环境,无法重启!"
try: try:
# 检查容器是否配置了自动重启策略 # 检查容器是否配置了自动重启策略
has_restart_policy = SystemHelper._check_restart_policy() has_restart_policy = SystemHelper._check_restart_policy()
+1 -1
View File
@@ -83,7 +83,7 @@ if __name__ == '__main__':
# 注册信号处理器 # 注册信号处理器
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
# 启动托盘 # 启动托盘
start_tray() start_tray()
# 初始化数据库 # 初始化数据库
+1 -1
View File
@@ -51,7 +51,7 @@ class BangumiModule(_ModuleBase):
获取模块子类型 获取模块子类型
""" """
return MediaRecognizeType.Bangumi return MediaRecognizeType.Bangumi
@staticmethod @staticmethod
def get_priority() -> int: def get_priority() -> int:
""" """
+1 -1
View File
@@ -54,7 +54,7 @@ class RuleParser:
if __name__ == '__main__': if __name__ == '__main__':
# 测试代码 # 测试代码
expression_str = """ expression_str = """
SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & CNVOI & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & CNVOI & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & CNVOI & 4K & WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & WEBDL & !DOLBY & !3D > SPECSUB & 4K & WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & WEBDL & !DOLBY & !3D > CNSUB & 4K & WEBDL & !DOLBY & !3D > SPECSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & !3D > SPECSUB & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & !BLU & !WEBDL & !DOLBY & !SDR & !3D > CNSUB & 4K & !BLU & !WEBDL & !DOLBY & !SDR & !3D > 4K & !BLU & !REMUX & !DOLBY & HDR & !3D > 4K & !BLURAY & !REMUX & !DOLBY & !3D > SPECSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & 1080P & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & !BLU & !WEBDL & !DOLBY & !3D > CNSUB & 1080P & !BLU & !WEBDL & !DOLBY & !3D > SPECSUB & 1080P & WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & WEBDL & !DOLBY & !3D > CNSUB & 1080P & WEBDL & !DOLBY & !3D > 1080P & !BLU & !REMUX & !DOLBY & HDR & !3D > 1080P & !BLU & !REMUX & !DOLBY & !3D SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & CNVOI & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & CNVOI & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & CNVOI & 4K & WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & WEBDL & !DOLBY & !3D > SPECSUB & 4K & WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & WEBDL & !DOLBY & !3D > CNSUB & 4K & WEBDL & !DOLBY & !3D > SPECSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & !3D > SPECSUB & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & !BLU & !WEBDL & !DOLBY & !SDR & !3D > CNSUB & 4K & !BLU & !WEBDL & !DOLBY & !SDR & !3D > 4K & !BLU & !REMUX & !DOLBY & HDR & !3D > 4K & !BLURAY & !REMUX & !DOLBY & !3D > SPECSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & 1080P & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & !BLU & !WEBDL & !DOLBY & !3D > CNSUB & 1080P & !BLU & !WEBDL & !DOLBY & !3D > SPECSUB & 1080P & WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & WEBDL & !DOLBY & !3D > CNSUB & 1080P & WEBDL & !DOLBY & !3D > 1080P & !BLU & !REMUX & !DOLBY & HDR & !3D > 1080P & !BLU & !REMUX & !DOLBY & !3D
""" """
for exp in expression_str.split('>'): for exp in expression_str.split('>'):
parsed_expr = RuleParser().parse(exp.strip()) parsed_expr = RuleParser().parse(exp.strip())
+1 -1
View File
@@ -122,7 +122,7 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
'text': '' 'text': ''
} }
} }
按钮回调格式: 按钮回调格式:
{ {
'callback_query': { 'callback_query': {
+3 -3
View File
@@ -59,7 +59,7 @@ class AsObj:
def __setitem__(self, key, value): def __setitem__(self, key, value):
return setattr(self, key, value) return setattr(self, key, value)
def __str__(self): def __str__(self):
return str(self._obj_list) if self._list_only else str(self._dict()) return str(self._obj_list) if self._list_only else str(self._dict())
@@ -91,10 +91,10 @@ class AsObj:
def pop(self, key, value=None): def pop(self, key, value=None):
return self.__dict__.pop(key, value) return self.__dict__.pop(key, value)
def popitem(self): def popitem(self):
return self.__dict__.popitem() return self.__dict__.popitem()
def setdefault(self, key, value=None): def setdefault(self, key, value=None):
return self.__dict__.setdefault(key, value) return self.__dict__.setdefault(key, value)
@@ -4,7 +4,7 @@ from ..tmdb import TMDb
class Collection(TMDb): class Collection(TMDb):
_urls = { _urls = {
"details": "/collection/%s", "details": "/collection/%s",
"images": "/collection/%s/images", "images": "/collection/%s/images",
"translations": "/collection/%s/translations" "translations": "/collection/%s/translations"
} }
@@ -3,7 +3,7 @@ from ..tmdb import TMDb
class Company(TMDb): class Company(TMDb):
_urls = { _urls = {
"details": "/company/%s", "details": "/company/%s",
"alternative_names": "/company/%s/alternative_names", "alternative_names": "/company/%s/alternative_names",
"images": "/company/%s/images", "images": "/company/%s/images",
"movies": "/company/%s/movies" "movies": "/company/%s/movies"
@@ -101,11 +101,11 @@ class Movie(TMDb):
:return: :return:
""" """
return self._request_obj(self._urls["external_ids"] % movie_id) return self._request_obj(self._urls["external_ids"] % movie_id)
def images(self, movie_id, include_image_language=None): def images(self, movie_id, include_image_language=None):
""" """
Get the images that belong to a movie. Get the images that belong to a movie.
Querying images with a language parameter will filter the results. Querying images with a language parameter will filter the results.
If you want to include a fallback language (especially useful for backdrops) If you want to include a fallback language (especially useful for backdrops)
you can use the include_image_language parameter. you can use the include_image_language parameter.
This should be a comma separated value like so: include_image_language=en,null. This should be a comma separated value like so: include_image_language=en,null.
@@ -55,7 +55,7 @@ class Search(TMDb):
params="query=%s&page=%s" % (quote(term), page), params="query=%s&page=%s" % (quote(term), page),
key="results" key="results"
) )
def movies(self, term, adult=None, region=None, year=None, release_year=None, page=1): def movies(self, term, adult=None, region=None, year=None, release_year=None, page=1):
""" """
Search for movies. Search for movies.
+1 -1
View File
@@ -19,7 +19,7 @@ class Transmission:
"peersGettingFromUs", "peersSendingToUs", "uploadRatio", "uploadedEver", "downloadedEver", "downloadDir", "peersGettingFromUs", "peersSendingToUs", "uploadRatio", "uploadedEver", "downloadedEver", "downloadDir",
"error", "errorString", "doneDate", "queuePosition", "activityDate", "trackers"] "error", "errorString", "doneDate", "queuePosition", "activityDate", "trackers"]
def __init__(self, host: Optional[str] = None, port: Optional[int] = None, def __init__(self, host: Optional[str] = None, port: Optional[int] = None,
username: Optional[str] = None, password: Optional[str] = None, **kwargs): username: Optional[str] = None, password: Optional[str] = None, **kwargs):
""" """
若不设置参数,则创建配置文件设置的下载器 若不设置参数,则创建配置文件设置的下载器
+2 -2
View File
@@ -128,7 +128,7 @@ class WechatModule(_ModuleBase, _MessageBase[WeChat]):
1、消息格式: 1、消息格式:
<xml> <xml>
<ToUserName><![CDATA[toUser]]></ToUserName> <ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName> <FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime> <CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType> <MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content> <Content><![CDATA[this is a test]]></Content>
@@ -143,7 +143,7 @@ class WechatModule(_ModuleBase, _MessageBase[WeChat]):
<MsgType><![CDATA[event]]></MsgType> <MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event> <Event><![CDATA[subscribe]]></Event>
<AgentID>1</AgentID> <AgentID>1</AgentID>
</xml> </xml>
""" """
dom_tree = xml.dom.minidom.parseString(sMsg.decode('UTF-8')) dom_tree = xml.dom.minidom.parseString(sMsg.decode('UTF-8'))
root_node = dom_tree.documentElement root_node = dom_tree.documentElement
+1 -1
View File
@@ -523,7 +523,7 @@ class RequestUtils:
def get_json(self, url: str, params: dict = None, **kwargs) -> Optional[dict]: def get_json(self, url: str, params: dict = None, **kwargs) -> Optional[dict]:
""" """
发送GET请求并返回JSON数据,自动关闭连接 发送GET请求并返回JSON数据,自动关闭连接
:param url: 请求的URL :param url: 请求的URL
:param params: 请求的参数 :param params: 请求的参数
:param kwargs: 其他请求参数 :param kwargs: 其他请求参数
:return: JSON数据,若发生异常则返回None :return: JSON数据,若发生异常则返回None
+1 -1
View File
@@ -1,7 +1,7 @@
"""2.0.0 """2.0.0
Revision ID: 294b007932ef Revision ID: 294b007932ef
Revises: Revises:
Create Date: 2024-07-20 08:43:40.741251 Create Date: 2024-07-20 08:43:40.741251
""" """
+6 -6
View File
@@ -15,25 +15,25 @@ http {
server { server {
listen 38379; listen 38379;
server_name localhost; server_name localhost;
access_log /dev/stdout combined; access_log /dev/stdout combined;
error_log /dev/stdout; error_log /dev/stdout;
location / { location / {
proxy_pass http://docker; proxy_pass http://docker;
proxy_redirect off; proxy_redirect off;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m; client_max_body_size 10m;
client_body_buffer_size 128k; client_body_buffer_size 128k;
proxy_connect_timeout 90; proxy_connect_timeout 90;
proxy_send_timeout 120; proxy_send_timeout 120;
proxy_read_timeout 120; proxy_read_timeout 120;
proxy_buffer_size 4k; proxy_buffer_size 4k;
proxy_buffers 4 32k; proxy_buffers 4 32k;
proxy_busy_buffers_size 64k; proxy_busy_buffers_size 64k;
+1 -1
View File
@@ -61,7 +61,7 @@ pip install pip-tools
```bash ```bash
pip-compile --upgrade-package requests requirements.in pip-compile --upgrade-package requests requirements.in
``` ```
3. **全量更新依赖项** 3. **全量更新依赖项**
如果你想更新 `requirements.in` 中的所有依赖包,运行以下命令生成或更新 `requirements.txt` 文件: 如果你想更新 `requirements.in` 中的所有依赖包,运行以下命令生成或更新 `requirements.txt` 文件: