mirror of
https://github.com/jxxghp/MoviePilot-Plugins.git
synced 2026-06-13 23:16:49 +00:00
Compare commits
33 Commits
AutoSignIn
...
ClashRuleP
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88b29169fc | ||
|
|
2c9e108ac4 | ||
|
|
73b2d778a0 | ||
|
|
bf67d6e567 | ||
|
|
5e9da0802d | ||
|
|
2811021996 | ||
|
|
8c0a05b2de | ||
|
|
bb070bf83e | ||
|
|
21aec36ea5 | ||
|
|
6019cf92ac | ||
|
|
42d5dd1e89 | ||
|
|
0b3313e078 | ||
|
|
5684ba056a | ||
|
|
44af7dbb78 | ||
|
|
2102a03740 | ||
|
|
0a9cadf7ab | ||
|
|
279efe8000 | ||
|
|
fd92e58f81 | ||
|
|
fe93e46e02 | ||
|
|
cbf541992f | ||
|
|
8e1d336250 | ||
|
|
12e0e2b9f5 | ||
|
|
ac914f70f3 | ||
|
|
a07b8a4f4a | ||
|
|
6960b3f7aa | ||
|
|
fe83ff1be8 | ||
|
|
6357dc8e4a | ||
|
|
f1d94d0aa3 | ||
|
|
53dd3bc796 | ||
|
|
a9d528fc05 | ||
|
|
0388c437b1 | ||
|
|
ac4b53e745 | ||
|
|
53297fccaf |
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -90,15 +90,18 @@ jobs:
|
||||
rm -f "$asset"
|
||||
(cd "$(dirname "$plugin_dir")" && zip -r "$GITHUB_WORKSPACE/$asset" "$(basename "$plugin_dir")" -x "*/__pycache__/*" -x "*.pyc") >/dev/null
|
||||
|
||||
# If same tag exists, delete release and remote tag first
|
||||
# If same tag exists, delete release and both remote/local tag first
|
||||
if gh release view "$tag" >/dev/null 2>&1; then
|
||||
echo "Release $tag exists, deleting..."
|
||||
gh release delete "$tag" -y
|
||||
git push origin :refs/tags/"$tag" || true
|
||||
fi
|
||||
|
||||
# Ensure no stale local tag remains
|
||||
git tag -d "$tag" >/dev/null 2>&1 || true
|
||||
|
||||
echo "Creating release $tag"
|
||||
gh release create "$tag" "$asset" --title "$tag" --notes "Automated release of $plugin_id $plugin_version" --latest
|
||||
gh release create "$tag" "$asset" --title "$tag" --notes "Automated release of $plugin_id $plugin_version" --latest --target "$GITHUB_SHA"
|
||||
|
||||
echo "$tag" >> processed_tags.txt
|
||||
done
|
||||
|
||||
185
README.md
185
README.md
@@ -23,6 +23,7 @@ MoviePilot官方插件市场:https://github.com/jxxghp/MoviePilot-Plugins
|
||||
- [12. 如何通过插件扩展支持的存储类型?](#12-如何通过插件扩展支持的存储类型)
|
||||
- [13. 如何将插件功能集成到工作流?](#13-如何将插件功能集成到工作流)
|
||||
- [14. 如何在插件中通过消息持续与用户交互?](#14-如何在插件中通过消息持续与用户交互)
|
||||
- [15. 如何在插件中使用系统级统一缓存?](#15-如何在插件中使用系统级统一缓存)
|
||||
- [版本发布](#版本发布)
|
||||
- [1. 如何发布插件版本?](#1-如何发布插件版本)
|
||||
- [2. 如何开发V2版本的插件以及实现插件多版本兼容?](#2-如何开发v2版本的插件以及实现插件多版本兼容)
|
||||
@@ -1167,6 +1168,190 @@ def get_actions(self) -> List[Dict[str, Any]]:
|
||||
- 建议在交互中保存用户状态数据,以支持复杂的多步骤操作
|
||||
- 可以结合插件数据存储功能保存用户的交互历史和偏好设置
|
||||
|
||||
### 15. 如何在插件中使用系统级统一缓存?
|
||||
**(仅支持 `v2.7.4+` 版本)**
|
||||
- MoviePilot提供了统一的缓存系统,支持内存缓存、文件系统缓存和Redis缓存自动管理,当有Redis时优先使用Redis,否则使用内存或文件系统。插件可以通过系统提供的缓存接口实现高效的缓存管理,无需关心系统设置。
|
||||
|
||||
- 1. 使用缓存装饰器:
|
||||
```python
|
||||
from app.core.cache import cached
|
||||
|
||||
class MyPlugin(_PluginBase):
|
||||
@cached(region="my_plugin", ttl=3600)
|
||||
def get_data(self, key: str):
|
||||
"""
|
||||
使用缓存装饰器,缓存结果1小时
|
||||
"""
|
||||
# 复杂的计算或网络请求
|
||||
return expensive_operation(key)
|
||||
|
||||
@cached(region="my_plugin_async", ttl=1800, skip_none=True)
|
||||
async def get_async_data(self, key: str):
|
||||
"""
|
||||
异步函数缓存,跳过None值
|
||||
"""
|
||||
return await async_expensive_operation(key)
|
||||
```
|
||||
|
||||
- 2. 使用TTLCache类:
|
||||
```python
|
||||
from app.core.cache import TTLCache
|
||||
|
||||
class MyPlugin(_PluginBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 创建缓存实例,最大128项,TTL 30分钟
|
||||
self.cache = TTLCache(region="my_plugin", maxsize=128, ttl=1800)
|
||||
|
||||
def process_data(self, key: str):
|
||||
# 检查缓存
|
||||
if key in self.cache:
|
||||
return self.cache[key]
|
||||
|
||||
# 计算并缓存结果
|
||||
result = expensive_operation(key)
|
||||
self.cache[key] = result
|
||||
return result
|
||||
|
||||
def clear_cache(self):
|
||||
"""
|
||||
清理插件缓存
|
||||
"""
|
||||
self.cache.clear()
|
||||
```
|
||||
|
||||
- 3. 使用文件缓存后端(适用于大文件缓存):
|
||||
```python
|
||||
from app.core.cache import FileCache, AsyncFileCache
|
||||
from pathlib import Path
|
||||
|
||||
class MyPlugin(_PluginBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 获取文件缓存后端,支持Redis和文件系统
|
||||
self.file_cache = FileCache(
|
||||
base=Path("/tmp/my_plugin_cache"),
|
||||
ttl=86400 # 24小时
|
||||
)
|
||||
|
||||
def cache_large_file(self, key: str, data: bytes):
|
||||
"""
|
||||
缓存大文件数据
|
||||
"""
|
||||
self.file_cache.set(key, data, region="large_files")
|
||||
|
||||
def get_cached_file(self, key: str) -> Optional[bytes]:
|
||||
"""
|
||||
获取缓存的文件数据
|
||||
"""
|
||||
return self.file_cache.get(key, region="large_files")
|
||||
|
||||
async def async_cache_operations(self):
|
||||
"""
|
||||
异步文件缓存操作
|
||||
"""
|
||||
async_cache = AsyncFileCache(
|
||||
base=Path("/tmp/my_plugin_async_cache"),
|
||||
ttl=3600
|
||||
)
|
||||
|
||||
# 异步设置缓存
|
||||
await async_cache.set("async_key", b"async_data", region="async_files")
|
||||
|
||||
# 异步获取缓存
|
||||
data = await async_cache.get("async_key", region="async_files")
|
||||
|
||||
await async_cache.close()
|
||||
```
|
||||
|
||||
- 4. 直接使用缓存后端(高级用法):
|
||||
```python
|
||||
from app.core.cache import Cache
|
||||
|
||||
class MyPlugin(_PluginBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 直接获取缓存后端实例,系统自动选择Redis或内存缓存
|
||||
self.cache_backend = Cache(maxsize=256, ttl=3600)
|
||||
|
||||
def custom_cache_operation(self, key: str, value: Any):
|
||||
"""
|
||||
自定义缓存操作
|
||||
"""
|
||||
# 设置缓存
|
||||
self.cache_backend.set(key, value, region="custom_region")
|
||||
|
||||
# 检查缓存是否存在
|
||||
if self.cache_backend.exists(key, region="custom_region"):
|
||||
# 获取缓存
|
||||
cached_value = self.cache_backend.get(key, region="custom_region")
|
||||
return cached_value
|
||||
|
||||
return None
|
||||
|
||||
def iterate_cache_items(self):
|
||||
"""
|
||||
遍历缓存项
|
||||
"""
|
||||
for key, value in self.cache_backend.items(region="custom_region"):
|
||||
print(f"缓存键: {key}, 值: {value}")
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
清理缓存
|
||||
"""
|
||||
self.cache_backend.clear(region="custom_region")
|
||||
self.cache_backend.close()
|
||||
```
|
||||
|
||||
- 5. 缓存装饰器参数说明:
|
||||
```python
|
||||
@cached(
|
||||
region="my_plugin", # 缓存区域,用于隔离不同插件的缓存
|
||||
maxsize=512, # 最大缓存条目数(仅内存缓存有效)
|
||||
ttl=1800, # 缓存存活时间(秒)
|
||||
skip_none=True, # 是否跳过None值缓存
|
||||
skip_empty=False # 是否跳过空值缓存(空列表、空字典等)
|
||||
)
|
||||
def my_function(self, param):
|
||||
pass
|
||||
```
|
||||
|
||||
- 6. 缓存管理功能:
|
||||
```python
|
||||
class MyPlugin(_PluginBase):
|
||||
@cached(region="my_plugin")
|
||||
def cached_function(self, param):
|
||||
return expensive_operation(param)
|
||||
|
||||
def clear_my_cache(self):
|
||||
"""
|
||||
清理指定区域的缓存
|
||||
"""
|
||||
self.cached_function.cache_clear()
|
||||
|
||||
def get_cache_info(self):
|
||||
"""
|
||||
获取缓存信息
|
||||
"""
|
||||
cache_region = self.cached_function.cache_region
|
||||
return f"缓存区域: {cache_region}"
|
||||
```
|
||||
|
||||
- 7. 缓存后端自动选择:
|
||||
- 系统会根据配置自动选择缓存后端:
|
||||
- `CACHE_BACKEND_TYPE=redis`:使用Redis作为缓存后端
|
||||
- `CACHE_BACKEND_TYPE=memory`:使用内存缓存(cachetools)
|
||||
- 插件代码无需修改,系统会自动处理缓存后端的切换
|
||||
|
||||
- 8. 最佳实践:
|
||||
- 为每个插件使用独立的缓存区域(region),避免缓存键冲突
|
||||
- 合理设置TTL,避免缓存过期时间过长导致数据过期
|
||||
- 对于频繁访问的数据使用较长的TTL,对于实时性要求高的数据使用较短的TTL
|
||||
- 使用`skip_none=True`避免缓存无意义的None值
|
||||
- 大文件或二进制数据建议使用文件缓存后端
|
||||
- 在插件卸载时清理相关缓存,避免内存泄漏
|
||||
|
||||
|
||||
## 版本发布
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 35 KiB |
@@ -943,11 +943,14 @@
|
||||
"name": "钉钉机器人",
|
||||
"description": "支持使用钉钉机器人发送消息通知。",
|
||||
"labels": "消息通知,钉钉机器人",
|
||||
"version": "1.12",
|
||||
"version": "1.13",
|
||||
"icon": "Dingding_A.png",
|
||||
"author": "nnlegenda",
|
||||
"level": 1,
|
||||
"v2": true
|
||||
"v2": true,
|
||||
"history": {
|
||||
"v1.13": "优化钉钉消息换行"
|
||||
}
|
||||
},
|
||||
"DynamicWeChat": {
|
||||
"name": "动态企微可信IP",
|
||||
|
||||
@@ -42,12 +42,13 @@
|
||||
"name": "站点自动签到",
|
||||
"description": "自动模拟登录、签到站点。",
|
||||
"labels": "站点",
|
||||
"version": "2.6",
|
||||
"version": "2.7",
|
||||
"icon": "signin.png",
|
||||
"author": "thsrite",
|
||||
"level": 2,
|
||||
"release": true,
|
||||
"history": {
|
||||
"v2.7": "站点请求使用站点设置的超时时间",
|
||||
"v2.6": "感谢madrays佬提供的UI!",
|
||||
"v2.5.4": "增加保号风险提示",
|
||||
"v2.5.3": "优化执行周期输入,需要MoviePilot v2.2.1+",
|
||||
@@ -182,11 +183,12 @@
|
||||
"name": "演职人员刮削",
|
||||
"description": "刮削演职人员图片以及中文名称。",
|
||||
"labels": "媒体库,刮削",
|
||||
"version": "2.2",
|
||||
"version": "2.2.1",
|
||||
"icon": "actor.png",
|
||||
"author": "jxxghp",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v2.2.1": "优化错误数据兼容处理",
|
||||
"v2.2": "修改使用自定义图片域名时无法下载图片的问题",
|
||||
"v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+",
|
||||
"v2.0": "兼容MoviePilot V2 版本",
|
||||
@@ -350,6 +352,18 @@
|
||||
"v2.0": "适配新的目录结构变化,短剧分类名称调整为配置目录路径,升级后需要重新调整设置后才能使用。"
|
||||
}
|
||||
},
|
||||
"MultiClass": {
|
||||
"name": "视频多级分类",
|
||||
"description": "支持视频多级分类",
|
||||
"labels": "文件整理",
|
||||
"version": "0.1",
|
||||
"icon": "Calibreweb_B.png",
|
||||
"author": "liuhangbin",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v0.1": "视频多级分类插件, 目前仅支持电影按评分,年代,系列分类。"
|
||||
}
|
||||
},
|
||||
"MoviePilotUpdateNotify": {
|
||||
"name": "MoviePilot更新推送",
|
||||
"description": "MoviePilot推送release更新通知、自动重启。",
|
||||
@@ -419,11 +433,12 @@
|
||||
"name": "绕过Trackers",
|
||||
"description": "提供tracker服务器IP地址列表,帮助IPv6连接绕过OpenClash。",
|
||||
"labels": "工具",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"icon": "Clash_A.png",
|
||||
"author": "wumode",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v1.4.3": "修复 bug",
|
||||
"v1.4.2": "修复插件动作",
|
||||
"v1.4.1": "修复通知类型错误",
|
||||
"v1.4": "异步查询DNS",
|
||||
@@ -437,11 +452,13 @@
|
||||
"name": "IMDb源",
|
||||
"description": "让探索,推荐和媒体识别支持IMDb数据源。",
|
||||
"labels": "探索",
|
||||
"version": "1.5.6",
|
||||
"version": "1.5.8",
|
||||
"icon": "IMDb_IOS-OSX_App.png",
|
||||
"author": "wumode",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.5.8": "修改UA",
|
||||
"v1.5.7": "改进异常处理",
|
||||
"v1.5.6": "固定仪表盘组件海报比例; 修复 bug",
|
||||
"v1.5.5": "修复初始化错误",
|
||||
"v1.5.4": "改进媒体识别",
|
||||
@@ -467,12 +484,15 @@
|
||||
"name": "Clash Rule Provider",
|
||||
"description": "随时为Clash添加一些额外的规则。",
|
||||
"labels": "工具",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.1",
|
||||
"icon": "Mihomo_Meta_A.png",
|
||||
"author": "wumode",
|
||||
"level": 1,
|
||||
"release": true,
|
||||
"history": {
|
||||
"v1.4.1": "修复配置模板保存错误, 请重新配置Clash模板",
|
||||
"v1.4.0": "优化 UI; 支持连接多个 Clash Dashboards",
|
||||
"v1.3.3": "通过emoji识别国家; 按国家分组节点; mrs格式支持",
|
||||
"v1.3.2": "注册插件动作",
|
||||
"v1.3.1": "支持配置 Hosts",
|
||||
"v1.2.8": "改进导入界面",
|
||||
@@ -495,11 +515,14 @@
|
||||
"name": "美剧生词标注",
|
||||
"description": "根据CEFR等级,为英语影视剧标注高级词汇。",
|
||||
"labels": "英语",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.2",
|
||||
"icon": "LexiAnnot.png",
|
||||
"author": "wumode",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.1.2": "使用子进程避免 spaCy 模型常驻内存",
|
||||
"v1.1.1": "添加任务页面; 改进 spaCy 模型加载逻辑",
|
||||
"v1.1.0": "支持考试词汇标注; 优化分词处理; 修复错误",
|
||||
"v1.0.1": "合并连字符词; 避免ARM平台依赖问题",
|
||||
"v1.0": "新增LexiAnnot"
|
||||
}
|
||||
@@ -516,5 +539,18 @@
|
||||
"v1.0.0": "首个版本,新增MeoW消息通知",
|
||||
"v1.0.1": "优化代码,修复运行一次按钮没办法自动关闭的问题"
|
||||
}
|
||||
},
|
||||
"BugReporter": {
|
||||
"name": "Bug反馈",
|
||||
"description": "自动上报异常,协助开发者发现和解决问题。",
|
||||
"labels": "开发",
|
||||
"version": "1.2",
|
||||
"icon": "Alist_encrypt_A.png",
|
||||
"author": "jxxghp",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.2": "优化上报信息量",
|
||||
"v1.1": "加强脱敏处理"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,6 @@ from typing import Any, List, Dict, Tuple, Optional
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pytz
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app import schemas
|
||||
from app.core.config import settings
|
||||
from app.core.event import eventmanager, Event
|
||||
@@ -26,6 +22,9 @@ from app.utils.http import RequestUtils
|
||||
from app.utils.site import SiteUtils
|
||||
from app.utils.string import StringUtils
|
||||
from app.utils.timer import TimerUtils
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
|
||||
class AutoSignIn(_PluginBase):
|
||||
@@ -36,7 +35,7 @@ class AutoSignIn(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "signin.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.6"
|
||||
plugin_version = "2.7"
|
||||
# 插件作者
|
||||
plugin_author = "thsrite"
|
||||
# 作者主页
|
||||
@@ -1545,6 +1544,7 @@ class AutoSignIn(_PluginBase):
|
||||
render = site_info.get("render")
|
||||
proxies = settings.PROXY if site_info.get("proxy") else None
|
||||
proxy_server = settings.PROXY_SERVER if site_info.get("proxy") else None
|
||||
timeout = site_info.get("timeout") or 60
|
||||
if not site_url or not site_cookie:
|
||||
logger.warn(f"未配置 {site} 的站点地址或Cookie,无法签到")
|
||||
return False, ""
|
||||
@@ -1560,7 +1560,8 @@ class AutoSignIn(_PluginBase):
|
||||
page_source = PlaywrightHelper().get_page_source(url=checkin_url,
|
||||
cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxy_server)
|
||||
proxies=proxy_server,
|
||||
timeout=timeout)
|
||||
if not SiteUtils.is_logged_in(page_source):
|
||||
if under_challenge(page_source):
|
||||
return False, f"无法通过Cloudflare!"
|
||||
@@ -1574,13 +1575,15 @@ class AutoSignIn(_PluginBase):
|
||||
else:
|
||||
res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
timeout=timeout
|
||||
).get_res(url=checkin_url)
|
||||
if not res and site_url != checkin_url:
|
||||
logger.info(f"开始站点模拟登录:{site},地址:{site_url}...")
|
||||
res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
timeout=timeout
|
||||
).get_res(url=site_url)
|
||||
# 判断登录状态
|
||||
if res and res.status_code in [200, 500, 403]:
|
||||
@@ -1647,6 +1650,7 @@ class AutoSignIn(_PluginBase):
|
||||
render = site_info.get("render")
|
||||
proxies = settings.PROXY if site_info.get("proxy") else None
|
||||
proxy_server = settings.PROXY_SERVER if site_info.get("proxy") else None
|
||||
timeout = site_info.get("timeout") or 60
|
||||
if not site_url or not site_cookie:
|
||||
logger.warn(f"未配置 {site} 的站点地址或Cookie,无法签到")
|
||||
return False, ""
|
||||
@@ -1659,7 +1663,8 @@ class AutoSignIn(_PluginBase):
|
||||
page_source = PlaywrightHelper().get_page_source(url=site_url,
|
||||
cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxy_server)
|
||||
proxies=proxy_server,
|
||||
timeout=timeout)
|
||||
if not SiteUtils.is_logged_in(page_source):
|
||||
if under_challenge(page_source):
|
||||
return False, f"无法通过Cloudflare!"
|
||||
@@ -1669,7 +1674,8 @@ class AutoSignIn(_PluginBase):
|
||||
else:
|
||||
res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
timeout=timeout
|
||||
).get_res(url=site_url)
|
||||
# 判断登录状态
|
||||
if res and res.status_code in [200, 500, 403]:
|
||||
|
||||
@@ -2,13 +2,12 @@ import random
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.http import RequestUtils
|
||||
from app.utils.string import StringUtils
|
||||
from lxml import etree
|
||||
|
||||
|
||||
class Pt52(_ISiteSigninHandler):
|
||||
@@ -46,14 +45,16 @@ class Pt52(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
render = site_info.get("render")
|
||||
proxy = site_info.get("proxy")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 判断今日是否已签到
|
||||
html_text = self.get_page_source(url='https://52pt.site/bakatest.php',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
@@ -97,14 +98,16 @@ class Pt52(_ISiteSigninHandler):
|
||||
site_cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
site=site)
|
||||
site=site,
|
||||
timeout=timeout)
|
||||
|
||||
def __signin(self, questionid: str,
|
||||
choice: list,
|
||||
site: str,
|
||||
site_cookie: str,
|
||||
ua: str,
|
||||
proxy: bool) -> Tuple[bool, str]:
|
||||
proxy: bool,
|
||||
timeout: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
签到请求
|
||||
questionid: 450
|
||||
@@ -124,7 +127,8 @@ class Pt52(_ISiteSigninHandler):
|
||||
|
||||
sign_res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=settings.PROXY if proxy else None
|
||||
proxies=settings.PROXY if proxy else None,
|
||||
timeout=timeout
|
||||
).post_res(url='https://52pt.site/bakatest.php', data=data)
|
||||
if not sign_res or sign_res.status_code != 200:
|
||||
logger.error(f"{site} 签到失败,签到接口请求失败")
|
||||
|
||||
@@ -42,7 +42,8 @@ class _ISiteSigninHandler(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_page_source(url: str, cookie: str, ua: str, proxy: bool, render: bool, token: str = None) -> str:
|
||||
def get_page_source(url: str, cookie: str, ua: str, proxy: bool, render: bool,
|
||||
token: str = None, timeout: int = None) -> str:
|
||||
"""
|
||||
获取页面源码
|
||||
:param url: Url地址
|
||||
@@ -51,13 +52,15 @@ class _ISiteSigninHandler(metaclass=ABCMeta):
|
||||
:param proxy: 是否使用代理
|
||||
:param render: 是否渲染
|
||||
:param token: JWT Token
|
||||
:param timeout: 请求超时时间,单位秒
|
||||
:return: 页面源码,错误信息
|
||||
"""
|
||||
if render:
|
||||
return PlaywrightHelper().get_page_source(url=url,
|
||||
cookies=cookie,
|
||||
ua=ua,
|
||||
proxies=settings.PROXY_SERVER if proxy else None)
|
||||
proxies=settings.PROXY_SERVER if proxy else None,
|
||||
timeout=timeout or 60)
|
||||
else:
|
||||
if token:
|
||||
headers = {
|
||||
@@ -70,7 +73,8 @@ class _ISiteSigninHandler(metaclass=ABCMeta):
|
||||
"Cookie": cookie
|
||||
}
|
||||
res = RequestUtils(headers=headers,
|
||||
proxies=settings.PROXY if proxy else None).get_res(url=url)
|
||||
proxies=settings.PROXY if proxy else None,
|
||||
timeout=timeout or 20).get_res(url=url)
|
||||
if res is not None:
|
||||
# 使用chardet检测字符编码
|
||||
raw_data = res.content
|
||||
|
||||
@@ -37,6 +37,7 @@ class BTSchool(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
render = site_info.get("render")
|
||||
proxy = site_info.get("proxy")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
logger.info(f"{site} 开始签到")
|
||||
# 判断今日是否已签到
|
||||
@@ -44,7 +45,8 @@ class BTSchool(_ISiteSigninHandler):
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
@@ -63,7 +65,8 @@ class BTSchool(_ISiteSigninHandler):
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,签到接口请求失败")
|
||||
|
||||
@@ -47,13 +47,15 @@ class CHDBits(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 判断今日是否已签到
|
||||
html_text = self.get_page_source(url='https://ptchdbits.co/bakatest.php',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
|
||||
@@ -37,21 +37,24 @@ class HaiDan(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 签到
|
||||
# 签到页会重定向到index.php,由于302重定向特性,导致index.php没有携带cookie
|
||||
self.get_page_source(url='https://www.haidan.video/signin.php',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
# 重新携带cookie获取index.php查看签到结果
|
||||
html_text = self.get_page_source(url='https://www.haidan.video/index.php',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
|
||||
@@ -40,13 +40,15 @@ class Hares(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 获取页面html
|
||||
html_text = self.get_page_source(url='https://club.hares.top',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
if not html_text:
|
||||
logger.error(f"{site} 模拟访问失败,请检查站点连通性")
|
||||
@@ -66,7 +68,8 @@ class Hares(_ISiteSigninHandler):
|
||||
}
|
||||
sign_res = RequestUtils(cookies=site_cookie,
|
||||
headers=headers,
|
||||
proxies=settings.PROXY if proxy else None
|
||||
proxies=settings.PROXY if proxy else None,
|
||||
timeout=timeout
|
||||
).get_res(url="https://club.hares.top/attendance.php?action=sign")
|
||||
if not sign_res or sign_res.status_code != 200:
|
||||
logger.error(f"{site} 签到失败,签到接口请求失败")
|
||||
|
||||
@@ -40,6 +40,7 @@ class HDArea(_ISiteSigninHandler):
|
||||
site_cookie = site_info.get("cookie")
|
||||
ua = site_info.get("ua")
|
||||
proxies = settings.PROXY if site_info.get("proxy") else None
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 获取页面html
|
||||
data = {
|
||||
@@ -47,7 +48,8 @@ class HDArea(_ISiteSigninHandler):
|
||||
}
|
||||
html_res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
timeout=timeout
|
||||
).post_res(url="https://hdarea.club/sign_in.php", data=data)
|
||||
if not html_res or html_res.status_code != 200:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
|
||||
@@ -40,6 +40,7 @@ class HDChina(_ISiteSigninHandler):
|
||||
site_cookie = site_info.get("cookie")
|
||||
ua = site_info.get("ua")
|
||||
proxies = settings.PROXY if site_info.get("proxy") else None
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 尝试解决瓷器cookie每天签到后过期,只保留hdchina=部分
|
||||
cookie = ""
|
||||
@@ -59,7 +60,8 @@ class HDChina(_ISiteSigninHandler):
|
||||
# 获取页面html
|
||||
html_res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
timeout=timeout
|
||||
).get_res(url="https://hdchina.org/index.php")
|
||||
if not html_res or html_res.status_code != 200:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
@@ -99,7 +101,8 @@ class HDChina(_ISiteSigninHandler):
|
||||
}
|
||||
sign_res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
timeout=timeout
|
||||
).post_res(url="https://hdchina.org/plugin_sign-in.php?cmd=signin", data=data)
|
||||
if not sign_res or sign_res.status_code != 200:
|
||||
logger.error(f"{site} 签到失败,签到接口请求失败")
|
||||
|
||||
@@ -39,13 +39,15 @@ class HDCity(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 获取页面html
|
||||
html_text = self.get_page_source(url='https://hdcity.city/sign',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
|
||||
@@ -43,13 +43,15 @@ class HDSky(_ISiteSigninHandler):
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
referer = site_info.get("url")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 判断今日是否已签到
|
||||
html_text = self.get_page_source(url='https://hdsky.me',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
@@ -73,7 +75,8 @@ class HDSky(_ISiteSigninHandler):
|
||||
content_type='application/x-www-form-urlencoded; charset=UTF-8',
|
||||
referer="https://hdsky.me/index.php",
|
||||
accept_type="*/*",
|
||||
proxies=settings.PROXY if proxy else None
|
||||
proxies=settings.PROXY if proxy else None,
|
||||
timeout=timeout
|
||||
).post_res(url='https://hdsky.me/image_code_ajax.php',
|
||||
data={'action': 'new'})
|
||||
if image_res and image_res.status_code == 200:
|
||||
|
||||
@@ -41,13 +41,15 @@ class HDUpt(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 获取页面html
|
||||
html_text = self.get_page_source(url='https://pt.hdupt.com',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
@@ -67,7 +69,8 @@ class HDUpt(_ISiteSigninHandler):
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from typing import Tuple
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
@@ -38,10 +37,11 @@ class MTorrent(_ISiteSigninHandler):
|
||||
"Authorization": site_info.get("token")
|
||||
}
|
||||
url = site_info.get('url')
|
||||
timeout = site_info.get("timeout")
|
||||
domain = StringUtils.get_url_domain(url)
|
||||
# 更新最后访问时间
|
||||
res = RequestUtils(headers=headers,
|
||||
timeout=60,
|
||||
timeout=timeout,
|
||||
proxies=settings.PROXY if site_info.get("proxy") else None,
|
||||
referer=f"{url}index"
|
||||
).post_res(url=f"https://api.{domain}/api/member/updateLastBrowse")
|
||||
|
||||
@@ -40,6 +40,7 @@ class NexusHD(_ISiteSigninHandler):
|
||||
site_cookie = site_info.get("cookie")
|
||||
ua = site_info.get("ua")
|
||||
proxies = settings.PROXY if site_info.get("proxy") else None
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 获取页面html
|
||||
data = {
|
||||
@@ -48,7 +49,8 @@ class NexusHD(_ISiteSigninHandler):
|
||||
}
|
||||
html_res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
timeout=timeout
|
||||
).post_res(url="https://v6.nexushd.org/signin.php", data=data)
|
||||
if not html_res or html_res.status_code != 200:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
|
||||
@@ -43,13 +43,15 @@ class Opencd(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 判断今日是否已签到
|
||||
html_text = self.get_page_source(url='https://www.open.cd',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
|
||||
@@ -35,13 +35,15 @@ class PTerClub(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 签到
|
||||
html_text = self.get_page_source(url='https://pterclub.com/attendance-ajax.php',
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
|
||||
@@ -37,6 +37,7 @@ class PTTime(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 签到
|
||||
# 签到返回:<html><head></head><body>签到成功</body></html>
|
||||
@@ -44,7 +45,8 @@ class PTTime(_ISiteSigninHandler):
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
|
||||
@@ -57,6 +57,7 @@ class Tjupt(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 创建正确答案存储目录
|
||||
if not os.path.exists(os.path.dirname(self._answer_file)):
|
||||
@@ -67,7 +68,8 @@ class Tjupt(_ISiteSigninHandler):
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
|
||||
# 获取签到后返回html,判断是否签到成功
|
||||
if not html_text:
|
||||
|
||||
@@ -44,13 +44,15 @@ class TTG(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 获取页面html
|
||||
html_text = self.get_page_source(url="https://totheglory.im",
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
|
||||
@@ -50,6 +50,7 @@ class U2(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
now = datetime.datetime.now()
|
||||
# 判断当前时间是否小于9点
|
||||
@@ -62,7 +63,8 @@ class U2(_ISiteSigninHandler):
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 签到失败,请检查站点连通性")
|
||||
return False, '签到失败,请检查站点连通性'
|
||||
|
||||
@@ -37,7 +37,7 @@ class YemaPT(_ISiteSigninHandler):
|
||||
}
|
||||
# 获取用户信息,更新最后访问时间
|
||||
res = (RequestUtils(headers=headers,
|
||||
timeout=15,
|
||||
timeout=site_info.get("timeout"),
|
||||
cookies=site_info.get("cookie"),
|
||||
proxies=settings.PROXY if site_info.get("proxy") else None,
|
||||
referer=site_info.get('url')
|
||||
@@ -64,7 +64,7 @@ class YemaPT(_ISiteSigninHandler):
|
||||
}
|
||||
# 获取用户信息,更新最后访问时间
|
||||
res = (RequestUtils(headers=headers,
|
||||
timeout=15,
|
||||
timeout=site_info.get("timeout"),
|
||||
cookies=site_info.get("cookie"),
|
||||
proxies=settings.PROXY if site_info.get("proxy") else None,
|
||||
referer=site_info.get('url')
|
||||
|
||||
@@ -38,13 +38,15 @@ class ZhuQue(_ISiteSigninHandler):
|
||||
ua = site_info.get("ua")
|
||||
proxy = site_info.get("proxy")
|
||||
render = site_info.get("render")
|
||||
timeout = site_info.get("timeout")
|
||||
|
||||
# 获取页面html
|
||||
html_text = self.get_page_source(url="https://zhuque.in",
|
||||
cookie=site_cookie,
|
||||
ua=ua,
|
||||
proxy=proxy,
|
||||
render=render)
|
||||
render=render,
|
||||
timeout=timeout)
|
||||
if not html_text:
|
||||
logger.error(f"{site} 模拟登录失败,请检查站点连通性")
|
||||
return False, '模拟登录失败,请检查站点连通性'
|
||||
@@ -73,7 +75,8 @@ class ZhuQue(_ISiteSigninHandler):
|
||||
}
|
||||
skill_res = RequestUtils(cookies=site_cookie,
|
||||
headers=headers,
|
||||
proxies=settings.PROXY if proxy else None
|
||||
proxies=settings.PROXY if proxy else None,
|
||||
timeout=timeout
|
||||
).post_res(url="https://zhuque.in/api/gaming/fireGenshinCharacterMagic", json=data)
|
||||
if not skill_res or skill_res.status_code != 200:
|
||||
logger.error(f"模拟登录失败,释放技能失败")
|
||||
|
||||
262
plugins.v2/bugreporter/__init__.py
Normal file
262
plugins.v2/bugreporter/__init__.py
Normal file
@@ -0,0 +1,262 @@
|
||||
import re
|
||||
import socket
|
||||
import ssl
|
||||
from typing import Any, Dict
|
||||
from typing import List, Tuple
|
||||
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
|
||||
|
||||
import sentry_sdk
|
||||
from app.plugins import _PluginBase
|
||||
from version import APP_VERSION
|
||||
|
||||
|
||||
class SentrySanitizer:
|
||||
# 常见敏感字段名(可自行扩展)
|
||||
SENSITIVE_KEYS = {
|
||||
"password", "passwd", "pwd",
|
||||
"secret", "token", "access_token", "refresh_token",
|
||||
"authorization", "api_key", "apikey",
|
||||
"cookie", "set-cookie", "passkey",
|
||||
"key", "credential", "auth", "login", "user", "username",
|
||||
"email", "phone", "address", "ip", "host", "domain"
|
||||
}
|
||||
|
||||
# 匹配包含敏感关键词的正则
|
||||
SENSITIVE_PATTERN = re.compile(
|
||||
"|".join(re.escape(key) for key in SENSITIVE_KEYS), re.IGNORECASE
|
||||
)
|
||||
|
||||
# 网络连接错误类异常(不上报)
|
||||
NETWORK_ERRORS = {
|
||||
"ConnectionError", "ConnectionRefusedError", "ConnectionAbortedError",
|
||||
"ConnectionResetError", "TimeoutError", "socket.timeout", "socket.error",
|
||||
"ssl.SSLError", "ssl.SSLCertVerificationError", "ssl.SSLWantReadError",
|
||||
"ssl.SSLWantWriteError", "ssl.SSLZeroReturnError", "ssl.SSLSyscallError",
|
||||
"urllib.error.URLError", "urllib.error.HTTPError", "requests.exceptions.ConnectionError",
|
||||
"requests.exceptions.Timeout", "requests.exceptions.ConnectTimeout",
|
||||
"requests.exceptions.ReadTimeout", "requests.exceptions.SSLError",
|
||||
"aiohttp.ClientConnectionError", "aiohttp.ClientTimeout", "aiohttp.ServerTimeoutError",
|
||||
"aiohttp.ServerDisconnectedError", "aiohttp.ClientOSError"
|
||||
}
|
||||
|
||||
# 网络连接错误关键词
|
||||
NETWORK_ERROR_KEYWORDS = [
|
||||
"connection", "timeout", "network", "dns", "ssl", "certificate",
|
||||
"refused", "reset", "aborted", "unreachable", "no route to host",
|
||||
"name or service not known", "temporary failure", "network is unreachable"
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def scrub_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
递归清洗字典中的敏感信息
|
||||
"""
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
sanitized = {}
|
||||
for key, value in data.items():
|
||||
if isinstance(value, dict):
|
||||
sanitized[key] = cls.scrub_dict(value)
|
||||
elif isinstance(value, list):
|
||||
sanitized[key] = [cls.scrub_dict(v) if isinstance(v, dict) else v for v in value]
|
||||
else:
|
||||
if cls.SENSITIVE_PATTERN.search(str(key)):
|
||||
sanitized[key] = "[Filtered]"
|
||||
else:
|
||||
sanitized[key] = value
|
||||
return sanitized
|
||||
|
||||
@classmethod
|
||||
def scrub_url(cls, url: str) -> str:
|
||||
"""
|
||||
清理 URL 中的敏感 query 参数
|
||||
"""
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
query = parse_qs(parsed.query, keep_blank_values=True)
|
||||
for key in query:
|
||||
if cls.SENSITIVE_PATTERN.search(key):
|
||||
query[key] = ["[Filtered]"]
|
||||
new_query = urlencode(query, doseq=True)
|
||||
return urlunparse(parsed._replace(query=new_query))
|
||||
except Exception as err:
|
||||
print(str(err))
|
||||
return url
|
||||
|
||||
@classmethod
|
||||
def is_network_error(cls, event) -> bool:
|
||||
"""
|
||||
判断是否为网络连接错误类异常
|
||||
"""
|
||||
# 检查异常类型
|
||||
if "exception" in event:
|
||||
for exc in event["exception"].get("values", []):
|
||||
if "type" in exc:
|
||||
exc_type = exc["type"]
|
||||
if exc_type in cls.NETWORK_ERRORS:
|
||||
return True
|
||||
|
||||
# 检查异常消息是否包含网络错误关键词
|
||||
if "value" in exc:
|
||||
exc_value = exc["value"].lower()
|
||||
for keyword in cls.NETWORK_ERROR_KEYWORDS:
|
||||
if keyword in exc_value:
|
||||
return True
|
||||
|
||||
# 检查日志消息
|
||||
if "message" in event:
|
||||
message = event["message"].lower()
|
||||
for keyword in cls.NETWORK_ERROR_KEYWORDS:
|
||||
if keyword in message:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def before_send(cls, event, hint):
|
||||
"""
|
||||
在发送到 Sentry 之前脱敏和过滤
|
||||
"""
|
||||
# 如果是网络连接错误,直接返回 None 不上报
|
||||
if cls.is_network_error(event):
|
||||
return None
|
||||
|
||||
# 处理 request 数据
|
||||
request = event.get("request", {})
|
||||
if "url" in request:
|
||||
request["url"] = cls.scrub_url(request["url"])
|
||||
if "headers" in request:
|
||||
request["headers"] = cls.scrub_dict(request["headers"])
|
||||
if "data" in request:
|
||||
request["data"] = cls.scrub_dict(request["data"])
|
||||
if "cookies" in request:
|
||||
request["cookies"] = cls.scrub_dict(request["cookies"])
|
||||
|
||||
# 处理 user 数据
|
||||
if "user" in event:
|
||||
event["user"] = cls.scrub_dict(event["user"])
|
||||
|
||||
# 处理 extra 数据
|
||||
if "extra" in event:
|
||||
event["extra"] = cls.scrub_dict(event["extra"])
|
||||
|
||||
# 处理异常信息(避免敏感数据出现在 message 中)
|
||||
if "exception" in event:
|
||||
for exc in event["exception"].get("values", []):
|
||||
if "value" in exc and cls.SENSITIVE_PATTERN.search(exc["value"]):
|
||||
exc["value"] = "[Filtered Exception Message]"
|
||||
|
||||
# 清理异常堆栈中的敏感信息
|
||||
if "stacktrace" in exc and "frames" in exc["stacktrace"]:
|
||||
for frame in exc["stacktrace"]["frames"]:
|
||||
if "vars" in frame:
|
||||
frame["vars"] = cls.scrub_dict(frame["vars"])
|
||||
if "context_line" in frame and cls.SENSITIVE_PATTERN.search(frame["context_line"]):
|
||||
frame["context_line"] = "[Filtered]"
|
||||
|
||||
# 清理消息中的敏感信息
|
||||
if "message" in event and cls.SENSITIVE_PATTERN.search(event["message"]):
|
||||
event["message"] = "[Filtered Message]"
|
||||
|
||||
return event
|
||||
|
||||
|
||||
class BugReporter(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "Bug反馈"
|
||||
# 插件描述
|
||||
plugin_desc = "自动上报异常,协助开发者发现和解决问题。"
|
||||
# 插件图标
|
||||
plugin_icon = "Alist_encrypt_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.2"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp"
|
||||
# 作者主页
|
||||
author_url = "https://github.com/jxxghp"
|
||||
# 插件配置项ID前缀
|
||||
plugin_config_prefix = "bugreporter_"
|
||||
# 加载顺序
|
||||
plugin_order = 99
|
||||
# 可使用的用户级别
|
||||
auth_level = 1
|
||||
|
||||
_enable: bool = False
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
self._enable = config.get("enable")
|
||||
if self._enable:
|
||||
sentry_sdk.init("https://88da01ad33b4423cb0380620de53efa8@glitchtip.movie-pilot.org/1",
|
||||
before_send=SentrySanitizer.before_send,
|
||||
release=APP_VERSION,
|
||||
send_default_pii=False)
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
def get_api(self) -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
'component': 'VForm',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'enable',
|
||||
'label': '启用插件',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'warning',
|
||||
'variant': 'tonal',
|
||||
'text': '注意:开启插件即代表你同意将部分异常信息自动发送给开发者,以帮助改进软件;如果你不希望自动发送任何数据,请关闭或卸载此插件;仅上报系统异常信息,不会包含任何个人隐私信息或敏感数据;网络连接错误类异常不会上报;异常信息采集为使用开源项目解决方案:GlitchTip。',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
], {
|
||||
"enable": self._enable,
|
||||
}
|
||||
|
||||
def get_page(self) -> List[dict]:
|
||||
pass
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enable
|
||||
|
||||
def stop_service(self):
|
||||
pass
|
||||
1
plugins.v2/bugreporter/requirements.txt
Normal file
1
plugins.v2/bugreporter/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
sentry_sdk~=2.35.1
|
||||
@@ -3,16 +3,22 @@ import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import pytz
|
||||
import re
|
||||
import time
|
||||
import urllib
|
||||
import yaml
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Optional, List, Dict, Tuple, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import pytz
|
||||
import websockets
|
||||
import yaml
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from fastapi import HTTPException, Request, status, Body, Response
|
||||
from fastapi.responses import PlainTextResponse
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
|
||||
from app import schemas
|
||||
from app.core.config import settings
|
||||
from app.core.event import eventmanager, Event
|
||||
@@ -25,10 +31,6 @@ from app.schemas.types import EventType
|
||||
from app.schemas.types import NotificationType
|
||||
from app.utils.http import RequestUtils, AsyncRequestUtils
|
||||
from app.utils.ip import IpUtils
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from fastapi import HTTPException, Request, status, Body, Response
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
|
||||
|
||||
class ClashRuleProvider(_PluginBase):
|
||||
@@ -39,7 +41,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Mihomo_Meta_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.3.2"
|
||||
plugin_version = "1.4.1"
|
||||
# 插件作者
|
||||
plugin_author = "wumode"
|
||||
# 作者主页
|
||||
@@ -58,12 +60,9 @@ class ClashRuleProvider(_PluginBase):
|
||||
_notify = False
|
||||
# 订阅链接
|
||||
_sub_links = []
|
||||
# Clash 面板 URL
|
||||
_clash_dashboard_url = None
|
||||
# Clash 面板密钥
|
||||
_clash_dashboard_secret = None
|
||||
|
||||
# MoviePilot URL
|
||||
_movie_pilot_url = ''
|
||||
_movie_pilot_url: str = ''
|
||||
_cron = ''
|
||||
_timeout = 10
|
||||
_retry_times = 3
|
||||
@@ -71,14 +70,17 @@ class ClashRuleProvider(_PluginBase):
|
||||
_auto_update_subscriptions = True
|
||||
_ruleset_prefix: str = '📂<='
|
||||
_group_by_region: bool = False
|
||||
_group_by_country: bool = False
|
||||
_refresh_delay: int = 5
|
||||
_discard_rules: bool = False
|
||||
_discard_proxy_groups: bool = False
|
||||
_enable_acl4ssr: bool = False
|
||||
_dashboard_components: List[str] = []
|
||||
_clash_template_yaml: str = ''
|
||||
_clash_template: str = ''
|
||||
_hint_geo_dat: bool = False
|
||||
# Cloudflare 优选 IPs 可通过外部设置
|
||||
_best_cf_ip: List[str] = []
|
||||
_apikey: Optional[str] = None
|
||||
|
||||
# 插件数据
|
||||
_top_rules: List[str] = []
|
||||
@@ -87,21 +89,25 @@ class ClashRuleProvider(_PluginBase):
|
||||
_extra_rule_providers: Dict[str, Any] = {}
|
||||
_subscription_info = {}
|
||||
_ruleset_names: Dict[str, str] = {}
|
||||
_proxy_groups = []
|
||||
_proxy_groups: List[Dict[str, Any]] = []
|
||||
_extra_proxies = []
|
||||
_acl4ssr_providers: Dict[str, Any] = {}
|
||||
_acl4ssr_prefix: str = '🗂️=>'
|
||||
# 保存每个订阅文件的原始内容
|
||||
_clash_configs: Dict[str, Any] = {}
|
||||
_hosts: List[Dict[str, Any]] = []
|
||||
_clash_dashboards: List[Dict[str, str]] = []
|
||||
_active_dashboard: Optional[int] = None
|
||||
|
||||
# protected variables
|
||||
_clash_rule_parser = None
|
||||
_ruleset_rule_parser = None
|
||||
_clash_template: Optional[Dict[str, Any]] = None
|
||||
_clash_template_dict: Optional[Dict[str, Any]] = None
|
||||
_scheduler: Optional[BackgroundScheduler] = None
|
||||
_countries: Optional[List[Dict[str, str]]] = None
|
||||
_countries: List[Dict[str, str]] = []
|
||||
_geo_rules: Dict[str, List[str]] = {'geoip': [], 'geosite': []}
|
||||
_clash_dashboard_url: str = ''
|
||||
_clash_dashboard_secret: str = ''
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
self.stop_service()
|
||||
@@ -119,18 +125,22 @@ class ClashRuleProvider(_PluginBase):
|
||||
if config:
|
||||
self._enabled = config.get("enabled")
|
||||
self._proxy = config.get("proxy")
|
||||
self._notify = config.get("notify"),
|
||||
self._notify = bool(config.get("notify"))
|
||||
self._sub_links = config.get("sub_links") or []
|
||||
self._clash_dashboard_url = config.get("clash_dashboard_url") or ''
|
||||
if self._clash_dashboard_url and self._clash_dashboard_url[-1] == '/':
|
||||
self._clash_dashboard_url = self._clash_dashboard_url[:-1]
|
||||
if not (self._clash_dashboard_url.startswith('http://') or
|
||||
self._clash_dashboard_url.startswith('https://')):
|
||||
self._clash_dashboard_url = 'http://' + self._clash_dashboard_url
|
||||
self._clash_dashboard_secret = config.get("clash_dashboard_secret")
|
||||
self._movie_pilot_url = config.get("movie_pilot_url")
|
||||
if self._movie_pilot_url and self._movie_pilot_url[-1] == '/':
|
||||
self._movie_pilot_url = self._movie_pilot_url[:-1]
|
||||
clash_dashboards = config.get("clash_dashboards")
|
||||
if clash_dashboards is None:
|
||||
clash_dashboards = [{'url': config.get('clash_dashboard_url') or '',
|
||||
'secret': config.get('clash_dashboard_secret') or ''}]
|
||||
self._clash_dashboards = []
|
||||
for clash_dashboard in clash_dashboards:
|
||||
url = (clash_dashboard.get("url") or '').rstrip('/')
|
||||
if not (url.startswith('http://') or url.startswith('https://')):
|
||||
url = 'http://' + url
|
||||
self._clash_dashboards.append({'url': url, 'secret': clash_dashboard.get('secret') or ''})
|
||||
|
||||
self._movie_pilot_url = config.get("movie_pilot_url") or ''
|
||||
if self._movie_pilot_url:
|
||||
self._movie_pilot_url = self._movie_pilot_url.rstrip('/')
|
||||
self._cron = config.get("cron_string") or '30 12 * * *'
|
||||
self._timeout = config.get("timeout")
|
||||
self._retry_times = config.get("retry_times") or 3
|
||||
@@ -139,39 +149,53 @@ class ClashRuleProvider(_PluginBase):
|
||||
self._acl4ssr_prefix = config.get("acl4ssr_prefix", "🗂️=>")
|
||||
self._auto_update_subscriptions = config.get("auto_update_subscriptions")
|
||||
self._group_by_region = config.get("group_by_region")
|
||||
self._group_by_country = config.get("group_by_country") or False
|
||||
self._refresh_delay = config.get("refresh_delay") or 5
|
||||
self._discard_rules = config.get("discard_rules") or False
|
||||
self._discard_proxy_groups = config.get("discard_proxy_groups") or False
|
||||
self._enable_acl4ssr = config.get("enable_acl4ssr") or False
|
||||
self._dashboard_components = config.get("dashboard_components") or []
|
||||
self._clash_template_yaml = config.get("clash_template") or ''
|
||||
self._clash_template = config.get("clash_template") or ''
|
||||
self._hint_geo_dat = config.get("hint_geo_dat", False)
|
||||
self._best_cf_ip = config.get("best_cf_ip") or []
|
||||
self._active_dashboard = config.get("active_dashboard")
|
||||
if self._active_dashboard is None and self._clash_dashboards:
|
||||
self._active_dashboard = 0
|
||||
self._apikey = config.get("apikey")
|
||||
|
||||
self.__update_config()
|
||||
self._clash_rule_parser = ClashRuleParser()
|
||||
self._ruleset_rule_parser = ClashRuleParser()
|
||||
self._clash_template = {}
|
||||
self._clash_template_dict = {}
|
||||
self._countries = []
|
||||
if self._active_dashboard is not None and self._active_dashboard in range(len(self._clash_dashboards)):
|
||||
self._clash_dashboard_url = self._clash_dashboards[self._active_dashboard].get("url")
|
||||
self._clash_dashboard_secret = self._clash_dashboards[self._active_dashboard].get("secret")
|
||||
if self._enabled:
|
||||
try:
|
||||
self._clash_template = yaml.load(self._clash_template_yaml, Loader=yaml.SafeLoader) or {}
|
||||
if not isinstance(self._clash_template, dict):
|
||||
self._clash_template = {}
|
||||
self._clash_template_dict = yaml.load(self._clash_template, Loader=yaml.SafeLoader) or {}
|
||||
if not isinstance(self._clash_template_dict, dict):
|
||||
self._clash_template_dict = {}
|
||||
logger.error(f"Invalid clash template yaml")
|
||||
# 规范配置模板
|
||||
self._clash_template['proxies'] = self._clash_template.get('proxies') or []
|
||||
self._clash_template['proxy-groups'] = self._clash_template.get('proxy-groups') or []
|
||||
self._clash_template['rule-providers'] = self._clash_template.get('rule-providers') or {}
|
||||
self._clash_template['rules'] = self._clash_template.get('rules') or []
|
||||
self._clash_template_dict['proxies'] = self._clash_template_dict.get('proxies') or []
|
||||
self._clash_template_dict['proxy-groups'] = self._clash_template_dict.get('proxy-groups') or []
|
||||
self._clash_template_dict['rule-providers'] = self._clash_template_dict.get('rule-providers') or {}
|
||||
self._clash_template_dict['rules'] = self._clash_template_dict.get('rules') or []
|
||||
except yaml.YAMLError as exc:
|
||||
logger.error(f"Error loading clash template yaml: {exc}")
|
||||
if self._group_by_region:
|
||||
self._countries = ClashRuleProvider.__load_countries(
|
||||
f"{settings.ROOT_PATH}/app/plugins/clashruleprovider/countries.json")
|
||||
settings.ROOT_PATH / 'app' / 'plugins' / 'clashruleprovider' / 'countries.json')
|
||||
self.__parse_config()
|
||||
# 清理不存在的 URL
|
||||
self._subscription_info = {url: self._subscription_info.get(url)
|
||||
for url in self._sub_links if self._subscription_info.get(url)}
|
||||
self._subscription_info = {url: self._subscription_info.get(url) or {} for url in self._sub_links}
|
||||
for _, sub_info in self._subscription_info.items():
|
||||
if 'enabled' not in sub_info:
|
||||
sub_info['enabled'] = True
|
||||
self._clash_configs = {url: self._clash_configs[url] for url in self._sub_links if
|
||||
self._clash_configs.get(url)}
|
||||
|
||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||
self._scheduler.start()
|
||||
# 更新订阅
|
||||
@@ -362,6 +386,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
"path": "/ruleset",
|
||||
"endpoint": self.get_ruleset,
|
||||
"methods": ["GET"],
|
||||
"allow_anonymous": True if self._apikey else False,
|
||||
"summary": "获取规则集规则",
|
||||
"description": "获取规则集规则"
|
||||
},
|
||||
@@ -397,10 +422,19 @@ class ClashRuleProvider(_PluginBase):
|
||||
"summary": "删除一条 Host",
|
||||
"description": "删除一条 Host"
|
||||
},
|
||||
{
|
||||
"path": "/subscription-info",
|
||||
"endpoint": self.update_subscription_info,
|
||||
"methods": ["POST"],
|
||||
"auth": "bear",
|
||||
"summary": "更新订阅信息",
|
||||
"description": "更新订阅信息"
|
||||
},
|
||||
{
|
||||
"path": "/config",
|
||||
"endpoint": self.get_clash_config,
|
||||
"methods": ["GET"],
|
||||
"allow_anonymous": True if self._apikey else False,
|
||||
"summary": "获取 Clash 配置",
|
||||
"description": "获取 Clash 配置"
|
||||
},
|
||||
@@ -508,6 +542,35 @@ class ClashRuleProvider(_PluginBase):
|
||||
}]
|
||||
return []
|
||||
|
||||
def __update_config(self):
|
||||
config = {
|
||||
'enabled': self._enabled,
|
||||
'proxy': self._proxy,
|
||||
'notify': self._notify,
|
||||
'sub_links': self._sub_links,
|
||||
'clash_dashboards': self._clash_dashboards,
|
||||
'movie_pilot_url': self._movie_pilot_url,
|
||||
'cron': self._cron,
|
||||
'timeout': self._timeout,
|
||||
'retry_times': self._retry_times,
|
||||
'filter_keywords': self._filter_keywords,
|
||||
'auto_update_subscriptions': self._auto_update_subscriptions,
|
||||
'ruleset_prefix': self._ruleset_prefix,
|
||||
'group_by_region': self._group_by_region,
|
||||
'group_by_country': self._group_by_country,
|
||||
'refresh_delay': self._refresh_delay,
|
||||
'discard_rules': self._discard_rules,
|
||||
'discard_proxy_groups': self._discard_proxy_groups,
|
||||
'enable_acl4ssr': self._enable_acl4ssr,
|
||||
'dashboard_components': self._dashboard_components,
|
||||
'clash_template': self._clash_template,
|
||||
'hint_geo_dat': self._hint_geo_dat,
|
||||
'best_cf_ip': self._best_cf_ip,
|
||||
'active_dashboard': self._active_dashboard,
|
||||
'apikey': self._apikey
|
||||
}
|
||||
self.update_config(config)
|
||||
|
||||
def update_best_cf_ip(self, ips: List[str]):
|
||||
"""
|
||||
通过深拷贝更新 Cloudflare 优选 IPs
|
||||
@@ -597,49 +660,81 @@ class ClashRuleProvider(_PluginBase):
|
||||
async def test_connectivity(self, params: Dict[str, Any]) -> schemas.Response:
|
||||
if not self._enabled:
|
||||
return schemas.Response(success=False, message="")
|
||||
if not params.get('clash_dashboard_url') or not params.get('clash_dashboard_secret') \
|
||||
or not params.get('sub_links'):
|
||||
return schemas.Response(success=True, message="missing params")
|
||||
clash_version_url = f"{params.get('clash_dashboard_url')}/version"
|
||||
ret = await AsyncRequestUtils(accept_type="application/json",
|
||||
headers={"authorization": f"Bearer {params.get('clash_dashboard_secret')}"}
|
||||
).get(clash_version_url)
|
||||
if ret is None:
|
||||
return schemas.Response(success=False, message="无法连接到Clash")
|
||||
if params.get('clash_dashboards') is None or not params.get('sub_links'):
|
||||
return schemas.Response(success=True, message="Missing params")
|
||||
|
||||
tasks = []
|
||||
for clash_dashboard in params['clash_dashboards']:
|
||||
url = clash_dashboard.get('url') or ''
|
||||
secret = clash_dashboard.get('secret') or ''
|
||||
tasks.append(asyncio.create_task(ClashRuleProvider.async_fetch_clash_version(url, secret)))
|
||||
results = await asyncio.gather(*tasks)
|
||||
for i, result in enumerate(results):
|
||||
if not result:
|
||||
return schemas.Response(success=False,
|
||||
message=f"无法连接到 Clash {params['clash_dashboards'][i].get('url')}")
|
||||
for sub_link in (params.get('sub_links') or []):
|
||||
ret = await AsyncRequestUtils(accept_type="text/html",
|
||||
proxies=settings.PROXY if self._proxy else None
|
||||
).get(sub_link)
|
||||
if ret is None:
|
||||
return schemas.Response(success=False, message=f"Unable to fetch {sub_link}")
|
||||
return schemas.Response(success=False, message=f"无法获取 {sub_link}")
|
||||
return schemas.Response(success=True, message="测试连接成功")
|
||||
|
||||
def get_ruleset(self, name):
|
||||
if not self._ruleset_names.get(name):
|
||||
return None
|
||||
name = self._ruleset_names.get(name)
|
||||
rules = self.__get_ruleset(name)
|
||||
@staticmethod
|
||||
async def async_fetch_clash_version(url: str, secret: str) -> Optional[str]:
|
||||
url = url.rstrip('/')
|
||||
clash_version_url = f"{url}/version"
|
||||
ret = await AsyncRequestUtils(accept_type="application/json",
|
||||
headers={"authorization": f"Bearer {secret}"},
|
||||
timeout=5
|
||||
).get_json(clash_version_url)
|
||||
return ret
|
||||
|
||||
def get_ruleset(self, name: str, apikey: str) -> Response:
|
||||
_apikey = self._apikey or settings.API_TOKEN
|
||||
if apikey != _apikey:
|
||||
raise HTTPException(status_code=403, detail="Invalid API Key")
|
||||
ruleset_name = self._ruleset_names.get(name)
|
||||
if ruleset_name is None:
|
||||
raise HTTPException(status_code=404, detail=f"Ruleset '{name}' not found")
|
||||
|
||||
rules = self.__get_ruleset(ruleset_name)
|
||||
res = yaml.dump({"payload": rules}, allow_unicode=True)
|
||||
return Response(content=res, media_type="text/yaml")
|
||||
|
||||
return PlainTextResponse(content=res, media_type="application/x-yaml")
|
||||
|
||||
def get_clash_outbound(self) -> schemas.Response:
|
||||
outbound = self.clash_outbound()
|
||||
return schemas.Response(success=True, message="", data={"outbound": outbound})
|
||||
|
||||
def get_status(self):
|
||||
def get_status(self) -> schemas.Response:
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
rule_size = len(first_config.get("rules", []))
|
||||
return {"success": True, "message": "",
|
||||
"data": {"state": self._enabled,
|
||||
"ruleset_prefix": self._ruleset_prefix,
|
||||
"clash": {"rule_size": rule_size},
|
||||
"best_cf_ip": self._best_cf_ip,
|
||||
"geoRules": self._geo_rules,
|
||||
"subscription_info": self._subscription_info,
|
||||
"sub_url": f"{self._movie_pilot_url}/api/v1/plugin/ClashRuleProvider/config?"
|
||||
f"apikey={settings.API_TOKEN}"}}
|
||||
data = {"state": self._enabled,
|
||||
"ruleset_prefix": self._ruleset_prefix,
|
||||
"clash": {"rule_size": rule_size},
|
||||
"best_cf_ip": self._best_cf_ip,
|
||||
"geoRules": self._geo_rules,
|
||||
"subscription_info": self._subscription_info,
|
||||
"sub_url": f"{self._movie_pilot_url}/api/v1/plugin/ClashRuleProvider/config?"
|
||||
f"apikey={self._apikey or settings.API_TOKEN}"}
|
||||
return schemas.Response(success=True, message="", data=data)
|
||||
|
||||
def get_clash_config(self, request: Request):
|
||||
def update_subscription_info(self, params: Dict[str, Any]) -> schemas.Response:
|
||||
url = params.get('url')
|
||||
field = params.get('field')
|
||||
if 'value' not in params or url not in self._subscription_info or field not in ['name', 'enabled']:
|
||||
return schemas.Response(success=False, message="Missing params")
|
||||
value = params.get('value')
|
||||
self._subscription_info[url][field] = value
|
||||
self.save_data('subscription_info', self._subscription_info)
|
||||
return schemas.Response(success=True)
|
||||
|
||||
def get_clash_config(self, apikey: str, request: Request):
|
||||
_apikey = self._apikey or settings.API_TOKEN
|
||||
if apikey != _apikey:
|
||||
raise HTTPException(status_code=403, detail="Invalid API Key")
|
||||
logger.info(f"{request.client.host} 正在获取配置")
|
||||
config = self.clash_config()
|
||||
if not config:
|
||||
@@ -794,7 +889,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
if not config:
|
||||
return schemas.Response(success=False, message=f"订阅链接 {url} 更新失败")
|
||||
self._clash_configs[url] = config
|
||||
self._subscription_info[url] = info
|
||||
self._subscription_info[url] = {**info, 'enabled': self._subscription_info.get(url, {}).get('enabled', False)}
|
||||
self.save_data('clash_configs', self._clash_configs)
|
||||
self.save_data('subscription_info', self._subscription_info)
|
||||
return schemas.Response(success=True, message='订阅更新成功')
|
||||
@@ -816,6 +911,8 @@ class ClashRuleProvider(_PluginBase):
|
||||
for key, value in new_value.items():
|
||||
if key == 'name' or value is None:
|
||||
continue
|
||||
if key == 'payload' and params.get('type') != 'inline':
|
||||
continue
|
||||
if value == '' or value is None:
|
||||
continue
|
||||
item[key] = value
|
||||
@@ -852,7 +949,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
proxy_groups = []
|
||||
sources = ('Manual', 'Template', urlparse(self._sub_links[0]).hostname if self._sub_links else '', 'Region')
|
||||
groups = (self._proxy_groups, self._clash_template.get('proxy-groups', []),
|
||||
groups = (self._proxy_groups, self._clash_template_dict.get('proxy-groups', []),
|
||||
first_config.get('proxy-groups', []), self.proxy_groups_by_region())
|
||||
for i, group in enumerate(groups):
|
||||
for proxy_group in group:
|
||||
@@ -875,7 +972,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
proxy_copy = copy.deepcopy(proxy)
|
||||
proxy_copy['source'] = hostname
|
||||
proxies.append(proxy_copy)
|
||||
for proxy in self._clash_template.get('proxies', []):
|
||||
for proxy in self._clash_template_dict.get('proxies', []):
|
||||
proxy_copy = copy.deepcopy(proxy)
|
||||
proxy_copy['source'] = 'Template'
|
||||
proxies.append(proxy_copy)
|
||||
@@ -999,15 +1096,15 @@ class ClashRuleProvider(_PluginBase):
|
||||
self.save_data('proxy_groups', self._proxy_groups)
|
||||
return schemas.Response(success=True, message='')
|
||||
|
||||
def clash_outbound(self) -> Optional[List]:
|
||||
def clash_outbound(self) -> List[Dict[str, Any]]:
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
outbound = [{'name': proxy_group.get("name")} for proxy_group in first_config.get("proxy-groups", [])]
|
||||
outbound.extend([{'name': proxy.get("name")} for proxy in first_config.get("proxies", [])])
|
||||
if self._clash_template:
|
||||
if 'proxy-groups' in self._clash_template:
|
||||
outbound.extend(self._clash_template.get('proxy-groups') or [])
|
||||
if 'proxies' in self._clash_template:
|
||||
outbound.extend(self._clash_template.get('proxies') or [])
|
||||
if self._clash_template_dict:
|
||||
if 'proxy-groups' in self._clash_template_dict:
|
||||
outbound.extend(self._clash_template_dict.get('proxy-groups') or [])
|
||||
if 'proxies' in self._clash_template_dict:
|
||||
outbound.extend(self._clash_template_dict.get('proxies') or [])
|
||||
if self._group_by_region:
|
||||
outbound.extend([{'name': proxy_group.get("name")} for proxy_group in self.proxy_groups_by_region()])
|
||||
outbound.extend([{'name': proxy.get("name")} for proxy in self._extra_proxies])
|
||||
@@ -1020,7 +1117,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
rule_providers = []
|
||||
provider_sources = (self._extra_rule_providers,
|
||||
first_config.get('rule-providers', {}),
|
||||
self._clash_template.get('rule-providers', {}),
|
||||
self._clash_template_dict.get('rule-providers', {}),
|
||||
self._acl4ssr_providers)
|
||||
source_names = ('Manual', hostname, 'Template', 'Auto', 'Acl4ssr')
|
||||
for i, provider in enumerate(provider_sources):
|
||||
@@ -1119,8 +1216,6 @@ class ClashRuleProvider(_PluginBase):
|
||||
|
||||
def delete_rule_by_priority(self, priority: int, rule_parser: ClashRuleParser
|
||||
) -> Optional[Union[ClashRule, LogicRule, MatchRule]]:
|
||||
if not isinstance(priority, int):
|
||||
return None
|
||||
res = rule_parser.remove_rule_at_priority(priority)
|
||||
self.__save_data()
|
||||
return res
|
||||
@@ -1162,7 +1257,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
f"总量: {ClashRuleProvider.format_bytes(sub_info.get('total', 0))}\n"
|
||||
f"过期时间: {ClashRuleProvider.format_expire_time(sub_info.get('expire', 0))}")
|
||||
else:
|
||||
info = ""
|
||||
info = f"节点数量: {sub_info.get('proxy_num', 0)}\n"
|
||||
message += f"订阅更新成功\n{info}"
|
||||
else:
|
||||
message += '订阅更新失败'
|
||||
@@ -1225,11 +1320,13 @@ class ClashRuleProvider(_PluginBase):
|
||||
all_proxies = []
|
||||
res = {}
|
||||
for index, url in enumerate(self._sub_links):
|
||||
if not self._subscription_info.get(url, {}).get('enabled'):
|
||||
continue
|
||||
config, sub_info = self.__get_subscription(url)
|
||||
self._subscription_info[url] = sub_info or {}
|
||||
if not config:
|
||||
res[url] = False
|
||||
continue
|
||||
self._subscription_info[url] = {**sub_info, 'enabled': True}
|
||||
res[url] = True
|
||||
self._clash_configs[url] = config
|
||||
all_proxies.extend(config.get("proxies", []))
|
||||
@@ -1244,7 +1341,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
logger.info(f"正在更新: {url}")
|
||||
ret = None
|
||||
for i in range(0, self._retry_times):
|
||||
ret = RequestUtils(accept_type="text/html",
|
||||
ret = RequestUtils(accept_type="text/html", timeout=self._timeout,
|
||||
proxies=settings.PROXY if self._proxy else None
|
||||
).get_res(url)
|
||||
if ret:
|
||||
@@ -1263,6 +1360,8 @@ class ClashRuleProvider(_PluginBase):
|
||||
logger.info(f"已更新: {url}. 节点数量: {len(rs['proxies'])}")
|
||||
if rs.get('rules') is None:
|
||||
rs['rules'] = []
|
||||
if self._discard_proxy_groups:
|
||||
rs['proxy-groups'] = []
|
||||
rs = self.__remove_nodes_by_keywords(rs)
|
||||
except Exception as e:
|
||||
logger.error(f"解析配置出错: {e}")
|
||||
@@ -1284,13 +1383,16 @@ class ClashRuleProvider(_PluginBase):
|
||||
"""
|
||||
通知 Clash 刷新规则集
|
||||
"""
|
||||
url = f'{self._clash_dashboard_url}/providers/rules/{ruleset}'
|
||||
RequestUtils(content_type="application/json",
|
||||
headers={"authorization": f"Bearer {self._clash_dashboard_secret}"}
|
||||
).put(url)
|
||||
for clash_dashboard in self._clash_dashboards:
|
||||
clash_dashboard_url = clash_dashboard.get('url', '')
|
||||
clash_dashboard_secret = clash_dashboard.get('secret', '')
|
||||
url = f'{clash_dashboard_url}/providers/rules/{ruleset}'
|
||||
RequestUtils(content_type="application/json", timeout=self._timeout,
|
||||
headers={"authorization": f"Bearer {clash_dashboard_secret}"}
|
||||
).put(url)
|
||||
|
||||
def proxy_groups_by_region(self) -> List[Dict[str, Any]]:
|
||||
return ClashRuleProvider.__group_by_region(self._countries, self.all_proxies())
|
||||
return self.__group_by_region(self._countries, self.all_proxies())
|
||||
|
||||
@staticmethod
|
||||
def __load_countries(file_path: str) -> List:
|
||||
@@ -1301,40 +1403,71 @@ class ClashRuleProvider(_PluginBase):
|
||||
return []
|
||||
return countries
|
||||
|
||||
@staticmethod
|
||||
def __group_by_region(countries: List, proxies) -> List[Dict[str, Any]]:
|
||||
continents_nodes = {'Asia': [], 'Europe': [], 'SouthAmerica': [], 'NorthAmerica': [], 'Africa': [],
|
||||
'Oceania': [], 'AsiaExceptChina': []}
|
||||
def __group_by_region(self, countries: List, proxies) -> List[Dict[str, Any]]:
|
||||
continent_groups = {}
|
||||
country_groups = {}
|
||||
continent_map = {
|
||||
'欧洲': 'Europe',
|
||||
'亚洲': 'Asia',
|
||||
'大洋洲': 'Oceania',
|
||||
'非洲': 'Africa',
|
||||
'北美洲': 'NorthAmerica',
|
||||
'南美洲': 'SouthAmerica'
|
||||
}
|
||||
proxy_groups = []
|
||||
hk = next(filter(lambda c: c['abbr'] == 'HK', countries),
|
||||
{"abbr": "HK", "chinese": "中国香港特别行政区", "emoji": "🇭🇰"})
|
||||
tw = next(filter(lambda c: c['abbr'] == 'TW', countries),
|
||||
{"abbr": "TW", "chinese": "中国台湾", "emoji": "🇹🇼"})
|
||||
for proxy_node in proxies:
|
||||
continent = ClashRuleProvider.__continent_name_from_node(countries, proxy_node['name'])
|
||||
if not continent:
|
||||
country = ClashRuleProvider.__country_from_node(countries, proxy_node['name'])
|
||||
if not country:
|
||||
continue
|
||||
continents_nodes[continent].append(proxy_node['name'])
|
||||
for continent_nodes in continents_nodes:
|
||||
if len(continents_nodes[continent_nodes]):
|
||||
proxy_group = {'name': continent_nodes, 'type': 'select', 'proxies': continents_nodes[continent_nodes]}
|
||||
if country.get("abbr") == "CN":
|
||||
if any(key in proxy_node["name"] for key in ("🇭🇰", "HK", "香港")):
|
||||
country = hk
|
||||
if any(key in proxy_node["name"] for key in ("🇹🇼", "TW", "台湾")):
|
||||
country = tw
|
||||
continent = continent_map[country.get('continent')]
|
||||
if self._group_by_region:
|
||||
continent_groups.setdefault(continent, []).append(proxy_node['name'])
|
||||
if self._group_by_country:
|
||||
country_groups.setdefault(f"{country.get('emoji')} {country.get('chinese')}", []).append(
|
||||
proxy_node['name'])
|
||||
for continent, nodes in continent_groups.items():
|
||||
if len(nodes):
|
||||
proxy_group = {'name': continent, 'type': 'select', 'proxies': nodes}
|
||||
proxy_groups.append(proxy_group)
|
||||
for continent_node in continents_nodes['Asia']:
|
||||
if any(x in continent_node for x in ('中国', '香港', 'CN')):
|
||||
excluded = ('中国', '香港', 'CN', 'HK', '🇨🇳', '🇭🇰')
|
||||
for continent_node in continent_groups.get('Asia', []):
|
||||
if any(x in continent_node for x in excluded):
|
||||
continue
|
||||
continents_nodes['AsiaExceptChina'].append(continent_node)
|
||||
if continents_nodes['AsiaExceptChina']:
|
||||
proxy_group = {'name': 'AsiaExceptChina', 'type': 'select', 'proxies': continents_nodes['AsiaExceptChina']}
|
||||
continent_groups.setdefault('AsiaExceptChina', []).append(continent_node)
|
||||
if continent_groups.get('AsiaExceptChina'):
|
||||
proxy_group = {'name': 'AsiaExceptChina', 'type': 'select', 'proxies': continent_groups['AsiaExceptChina']}
|
||||
proxy_groups.append(proxy_group)
|
||||
for country, nodes in country_groups.items():
|
||||
if len(nodes):
|
||||
proxy_group = {'name': country, 'type': 'select', 'proxies': nodes}
|
||||
proxy_groups.append(proxy_group)
|
||||
country_group = list(country_groups.keys())
|
||||
if country_group:
|
||||
proxy_groups.append({'name': '🏴☠️国家分组', 'type': 'select', 'proxies': country_group})
|
||||
|
||||
return proxy_groups
|
||||
|
||||
@staticmethod
|
||||
def __continent_name_from_node(countries: List[Dict[str, str]], node_name: str) -> Optional[str]:
|
||||
continents_names = {'欧洲': 'Europe',
|
||||
'亚洲': 'Asia',
|
||||
'大洋洲': 'Oceania',
|
||||
'非洲': 'Africa',
|
||||
'北美洲': 'NorthAmerica',
|
||||
'南美洲': 'SouthAmerica'}
|
||||
def __country_from_node(countries: List[Dict[str, str]], node_name: str) -> Optional[Dict[str, str]]:
|
||||
node_name_lower = node_name.lower()
|
||||
for country in countries:
|
||||
if country['chinese'] in node_name or country['english'].lower() in node_name.lower():
|
||||
return continents_names[country['continent']]
|
||||
if country['emoji'] and country['emoji'] in node_name:
|
||||
return country
|
||||
elif (
|
||||
country['chinese'] in node_name
|
||||
or country['english'].lower() in node_name_lower
|
||||
):
|
||||
return country
|
||||
|
||||
return None
|
||||
|
||||
def __add_notification_job(self, ruleset_names: List[str]):
|
||||
@@ -1376,7 +1509,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
for index, url in enumerate(self._sub_links):
|
||||
config = self._clash_configs.get(url, {})
|
||||
all_proxies.extend(config.get("proxies", []))
|
||||
all_proxies.extend(self._clash_template.get("proxies", []))
|
||||
all_proxies.extend(self._clash_template_dict.get("proxies", []))
|
||||
all_proxies.extend(self._extra_proxies)
|
||||
return all_proxies
|
||||
|
||||
@@ -1393,6 +1526,27 @@ class ClashRuleProvider(_PluginBase):
|
||||
to_list.append(item)
|
||||
return to_list
|
||||
|
||||
@staticmethod
|
||||
def remove_invalid_outbounds(proxies: List[Dict[str, Any]], proxy_groups: List[Dict[str, Any]]
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
从代理组中移除无效的出站
|
||||
"""
|
||||
outbounds = {proxy.get('name') for proxy in proxies if proxy.get('name')} | \
|
||||
{proxy_group.get('name') for proxy_group in proxy_groups if proxy_group.get('name')} | \
|
||||
{action.value for action in Action}
|
||||
outbounds.add('GLOBAL')
|
||||
for proxy_group in proxy_groups:
|
||||
ps = []
|
||||
if proxy_group.get('proxies'):
|
||||
for proxy in proxy_group.get('proxies', []):
|
||||
if proxy in outbounds:
|
||||
ps.append(proxy)
|
||||
else:
|
||||
logger.warn(f"Proxy {proxy!r} in {proxy_group.get('name')!r} doesn't exist. Skipping...")
|
||||
proxy_group['proxies'] = ps
|
||||
return proxy_groups
|
||||
|
||||
def clash_config(self) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
整理 clash 配置,返回配置字典
|
||||
@@ -1400,13 +1554,13 @@ class ClashRuleProvider(_PluginBase):
|
||||
# 使用模板或第一个订阅
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
proxies = []
|
||||
if not self._clash_template:
|
||||
if not self._clash_template_dict:
|
||||
clash_config = copy.deepcopy(first_config)
|
||||
clash_config['proxy-groups'] = []
|
||||
clash_config['rule-providers'] = {}
|
||||
clash_config['rules'] = []
|
||||
else:
|
||||
clash_config = copy.deepcopy(self._clash_template)
|
||||
clash_config = copy.deepcopy(self._clash_template_dict)
|
||||
clash_config['proxy-groups'] = ClashRuleProvider.extend_with_name_checking(clash_config.get('proxy-groups', []),
|
||||
first_config.get('proxy-groups', []),
|
||||
)
|
||||
@@ -1432,12 +1586,14 @@ class ClashRuleProvider(_PluginBase):
|
||||
clash_config['proxy-groups'] = ClashRuleProvider.extend_with_name_checking(clash_config['proxy-groups'],
|
||||
proxy_groups)
|
||||
# 添加按大洲代理组
|
||||
if self._group_by_region:
|
||||
if self._group_by_region or self._group_by_country:
|
||||
groups_by_region = self.proxy_groups_by_region()
|
||||
if groups_by_region:
|
||||
clash_config['proxy-groups'] = ClashRuleProvider.extend_with_name_checking(clash_config['proxy-groups'],
|
||||
groups_by_region)
|
||||
|
||||
groups_by_region)
|
||||
# 移除无效出站, 避免配置错误
|
||||
clash_config['proxy-groups'] = ClashRuleProvider.remove_invalid_outbounds(clash_config.get('proxies', []),
|
||||
clash_config.get('proxy-groups', []))
|
||||
top_rules = []
|
||||
outbound_names = list(x.get("name") for x in self.clash_outbound())
|
||||
|
||||
@@ -1454,7 +1610,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
path_name = hashlib.sha256(action_str.encode('utf-8')).hexdigest()[:10]
|
||||
self._ruleset_names[path_name] = rule_provider_name
|
||||
sub_url = (f"{self._movie_pilot_url}/api/v1/plugin/ClashRuleProvider/ruleset?"
|
||||
f"name={path_name}&apikey={settings.API_TOKEN}")
|
||||
f"name={path_name}&apikey={self._apikey or settings.API_TOKEN}")
|
||||
self._rule_provider[rule_provider_name] = {"behavior": "classical",
|
||||
"format": "yaml",
|
||||
"interval": 3600,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
|
||||
.plugin-config[data-v-929102b8] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
4
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CkZHWVJE.css
vendored
Normal file
4
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CkZHWVJE.css
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
.plugin-config[data-v-3939efa1] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
|
||||
.plugin-page[data-v-d6db167c] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 使卡片等宽并适应移动端 */
|
||||
.d-flex.flex-wrap[data-v-d6db167c] {
|
||||
gap: 16px;
|
||||
}
|
||||
.url-display[data-v-d6db167c] {
|
||||
word-break: break-all;
|
||||
padding: 8px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 移动端堆叠布局 */
|
||||
@media (max-width: 768px) {
|
||||
.d-flex.flex-wrap[data-v-d6db167c] {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add visual distinction between sections */
|
||||
.ruleset-section[data-v-d6db167c] {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.top-section[data-v-d6db167c] {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
/* Optional: Add different border colors to further distinguish */
|
||||
.ruleset-section[data-v-d6db167c] {
|
||||
border-left: 4px solid #2196F3; /* Blue accent */
|
||||
}
|
||||
.top-section[data-v-d6db167c] {
|
||||
border-left: 4px solid #4CAF50; /* Green accent */
|
||||
}
|
||||
.drag-handle[data-v-d6db167c] {
|
||||
cursor: move;
|
||||
}
|
||||
.gap-2[data-v-d6db167c] {
|
||||
gap: 8px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
69
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-efdkIdKV.css
vendored
Normal file
69
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-efdkIdKV.css
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
.plugin-page[data-v-ad6ce99d] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 使卡片等宽并适应移动端 */
|
||||
.d-flex.flex-wrap[data-v-ad6ce99d] {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 移动端堆叠布局 */
|
||||
@media (max-width: 768px) {
|
||||
.d-flex.flex-wrap[data-v-ad6ce99d] {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.drag-handle[data-v-ad6ce99d] {
|
||||
cursor: move;
|
||||
}
|
||||
.toggle-container[data-v-ad6ce99d] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.625rem;
|
||||
margin-left: 0.75rem;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
.subscription-card[data-v-ad6ce99d] {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
background: white;
|
||||
}
|
||||
.subscription-card[data-v-ad6ce99d]:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.card-title[data-v-ad6ce99d] {
|
||||
color: whitesmoke;
|
||||
}
|
||||
.card-header[data-v-ad6ce99d] {
|
||||
padding: 0.625rem;
|
||||
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 1) 0%, rgba(var(--v-theme-primary), 0.7) 100%);
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.card-refresh-button[data-v-ad6ce99d] {
|
||||
background-color: rgba(var(--v-theme-primary), 0.9);
|
||||
color: whitesmoke;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.625rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.search-field[data-v-ad6ce99d] {
|
||||
max-width: 25rem;
|
||||
}
|
||||
.clash-data-table[data-v-ad6ce99d] {
|
||||
max-height: 40rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ const currentImports = {};
|
||||
const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']);
|
||||
let moduleMap = {
|
||||
"./Page":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Page-BOym_1fV.css"], false, './Page');
|
||||
return __federation_import('./__federation_expose_Page-D5l2MyNA.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
dynamicLoadingCss(["__federation_expose_Page-efdkIdKV.css"], false, './Page');
|
||||
return __federation_import('./__federation_expose_Page-DEabfqvu.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
"./Config":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Config-BrXQaadr.css"], false, './Config');
|
||||
return __federation_import('./__federation_expose_Config-NH09p1Am.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
dynamicLoadingCss(["__federation_expose_Config-CkZHWVJE.css"], false, './Config');
|
||||
return __federation_import('./__federation_expose_Config-yV-dGVgm.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
"./Dashboard":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Dashboard-vS9Qm2ZB.css"], false, './Dashboard');
|
||||
return __federation_import('./__federation_expose_Dashboard-BDSt5WaH.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},};
|
||||
|
||||
@@ -29,7 +29,7 @@ class ImdbSource(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "IMDb_IOS-OSX_App.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.5.6"
|
||||
plugin_version = "1.5.8"
|
||||
# 插件作者
|
||||
plugin_author = "wumode"
|
||||
# 作者主页
|
||||
@@ -60,7 +60,7 @@ class ImdbSource(_PluginBase):
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
|
||||
plugin_instance = self
|
||||
plugin_instance: ImdbSource = self
|
||||
|
||||
def patched_recognize_media(chain_self, meta: MetaBase = None,
|
||||
mtype: Optional[MediaType] = None,
|
||||
@@ -822,6 +822,14 @@ class ImdbSource(_PluginBase):
|
||||
ChainBase.async_recognize_media._patched_by == id(self) and
|
||||
self._original_async_method):
|
||||
ChainBase.async_recognize_media = self._original_async_method
|
||||
if self._scheduler:
|
||||
try:
|
||||
self._scheduler.remove_all_jobs()
|
||||
if self._scheduler.running:
|
||||
self._scheduler.shutdown()
|
||||
self._scheduler = None
|
||||
except Exception as e:
|
||||
logger.error(f"退出插件失败:{e}")
|
||||
|
||||
def get_module(self) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Gemini
|
||||
|
||||
@@ -38,7 +39,7 @@ CEFR全称是Common European Framework of Reference for Languages。
|
||||
# 计划
|
||||
|
||||
- 双语字幕支持
|
||||
- 考试词汇标注
|
||||
- ~~考试词汇标注~~
|
||||
|
||||
# FAQ
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
pysubs2~=1.8.0
|
||||
langdetect~=1.0.9
|
||||
pymediainfo~=7.0.1
|
||||
pymediainfo~=7.0.1
|
||||
thinc==8.3.4
|
||||
spacy==3.8.7
|
||||
84
plugins.v2/lexiannot/spacyworker.py
Normal file
84
plugins.v2/lexiannot/spacyworker.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from multiprocessing import Process, Queue
|
||||
from typing import Dict, List
|
||||
|
||||
import spacy
|
||||
from spacy.tokenizer import Tokenizer
|
||||
|
||||
from app.core.cache import cached
|
||||
from app.log import logger
|
||||
|
||||
|
||||
class SpacyWorker:
|
||||
|
||||
def __init__(self, model='en_core_web_sm'):
|
||||
self.task_q = Queue()
|
||||
self.result_q = Queue()
|
||||
self.status_q = Queue()
|
||||
self.model = model
|
||||
|
||||
# 启动子进程
|
||||
logger.info(f"正在启动 SpacyWorker 子进程...")
|
||||
self.proc = Process(target=self.run, args=(self.model,))
|
||||
self.proc.start()
|
||||
|
||||
# 等待子进程返回模型加载状态
|
||||
status, info = self.status_q.get()
|
||||
if status == 'error':
|
||||
self.proc.join()
|
||||
raise RuntimeError(f"spaCy 模型加载失败: {info}")
|
||||
else:
|
||||
logger.info(f"spaCy 模型 `{self.model}` 加载成功")
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def run(self, model: str):
|
||||
try:
|
||||
nlp = SpacyWorker.load_nlp(model)
|
||||
infixes = list(nlp.Defaults.infixes)
|
||||
infixes = [i for i in infixes if '-' not in i]
|
||||
infix_re = spacy.util.compile_infix_regex(infixes)
|
||||
nlp.tokenizer = Tokenizer(
|
||||
nlp.vocab,
|
||||
prefix_search=nlp.tokenizer.prefix_search,
|
||||
suffix_search=nlp.tokenizer.suffix_search,
|
||||
infix_finditer=infix_re.finditer,
|
||||
token_match=nlp.tokenizer.token_match
|
||||
)
|
||||
except Exception as e:
|
||||
self.status_q.put(('error', str(e)))
|
||||
return
|
||||
|
||||
# 告诉主进程加载成功
|
||||
self.status_q.put(('ok', None))
|
||||
|
||||
while True:
|
||||
text = self.task_q.get()
|
||||
if text is None:
|
||||
break
|
||||
doc = nlp(text)
|
||||
self.result_q.put([{'text': token.text, 'pos_': token.pos_, 'lemma_': token.lemma_} for token in doc])
|
||||
|
||||
@staticmethod
|
||||
@cached(maxsize=1, ttl=3600 * 6)
|
||||
def load_nlp(model: str) -> spacy.Language:
|
||||
return spacy.load(model)
|
||||
|
||||
def submit(self, text: str) -> List[Dict[str, str]]:
|
||||
"""
|
||||
提交任务并等待结果
|
||||
"""
|
||||
self.task_q.put(text)
|
||||
return self.result_q.get()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭子进程
|
||||
"""
|
||||
if self.proc.is_alive():
|
||||
self.task_q.put(None)
|
||||
self.proc.join()
|
||||
logger.info(f"SpacyWorker 子进程退出")
|
||||
326
plugins.v2/multiclass/__init__.py
Normal file
326
plugins.v2/multiclass/__init__.py
Normal file
@@ -0,0 +1,326 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Dict, Tuple
|
||||
|
||||
from app.core.context import MediaInfo
|
||||
from app.core.event import eventmanager, Event
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas.types import ChainEventType, MediaType, NotificationType
|
||||
|
||||
class MultiClass(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "视频多级分类"
|
||||
# 插件描述
|
||||
plugin_desc = "支持电影按照评分,年代和系列分类"
|
||||
# 插件图标
|
||||
plugin_icon = "Calibreweb_B.png"
|
||||
# 插件版本
|
||||
plugin_version = "0.1"
|
||||
# 插件作者
|
||||
plugin_author = "liuhangbin"
|
||||
# 作者主页
|
||||
author_url = "https://github.com/liuhangbin"
|
||||
# 插件配置项ID前缀
|
||||
plugin_config_prefix = "multiclass_"
|
||||
# 加载顺序
|
||||
plugin_order = 1
|
||||
# 可使用的用户级别
|
||||
auth_level = 1
|
||||
|
||||
_enabled = False
|
||||
_notify = False
|
||||
_year_class = False
|
||||
_vote_class = False
|
||||
_collection_class = False
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
|
||||
if config:
|
||||
self._enabled = config.get("enabled", False)
|
||||
self._notify = config.get("notify", False)
|
||||
self._year_class = config.get("year_class", False)
|
||||
self._vote_class = config.get("vote_class", False)
|
||||
self._collection_class = config.get("collection_class", False)
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def get_api(self) -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||
"""
|
||||
拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'component': 'VForm',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'enabled',
|
||||
'label': '启用插件',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'year_class',
|
||||
'label': '按照年代分类',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'vote_class',
|
||||
'label': '按照评分分类',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'collection_class',
|
||||
'label': '按照系列分类',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'notify',
|
||||
'label': '发送消息',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '插件目前仅支持电影(需要开启智能重命名)。如果按评分分类,7-9 高分,4-6 一般,1-3 垃圾。 系列电影不参与评分, 不按年代分类。'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
], {
|
||||
"enabled": False,
|
||||
"notify": False,
|
||||
"year_class": False,
|
||||
"vote_class": False,
|
||||
"collection_class": False
|
||||
}
|
||||
|
||||
def get_page(self) -> List[dict]:
|
||||
pass
|
||||
|
||||
@eventmanager.register(ChainEventType.TransferRename)
|
||||
def category_handler(self, event: Event):
|
||||
"""
|
||||
根据多级分类规则重新分类组装地址
|
||||
"""
|
||||
logger.debug(f"多级分类插件触发!")
|
||||
|
||||
# 基础验证
|
||||
if not self.get_state():
|
||||
logger.debug(f"多级分类插件未启用!")
|
||||
return
|
||||
if not event:
|
||||
logger.warning(f"多级分类异常:事件对象为空")
|
||||
return
|
||||
if not hasattr(event, 'event_data'):
|
||||
logger.warning(f"多级分类异常:事件数据为空")
|
||||
return
|
||||
|
||||
try:
|
||||
data = event.event_data
|
||||
|
||||
# 验证必要的数据字段
|
||||
if not hasattr(data, 'render_str') or not data.render_str:
|
||||
logger.warning(f"多级分类异常:render_str为空")
|
||||
return
|
||||
else:
|
||||
render_str = data.render_str
|
||||
|
||||
# 暂时只支持电影分类
|
||||
if not hasattr(data, 'rename_dict') or not data.rename_dict:
|
||||
logger.warning(f"多级分类异常:rename_dict为空")
|
||||
return
|
||||
else:
|
||||
rename_dict = data.rename_dict
|
||||
video_type = rename_dict.get("type", "")
|
||||
if video_type != "电影":
|
||||
logger.debug(f"多级分类异常:不支持的媒体类型: {video_type}, 只支持电影分类")
|
||||
return
|
||||
|
||||
# 安全获取数据字段
|
||||
title = rename_dict.get("title", "")
|
||||
en_title = rename_dict.get("en_title", "")
|
||||
year = rename_dict.get("year")
|
||||
vote_average = rename_dict.get("vote_average")
|
||||
media_info = rename_dict.get("__mediainfo__")
|
||||
|
||||
# 初始化默认值
|
||||
vote_count = 0
|
||||
c_name = None
|
||||
vote_path = "未知评分"
|
||||
decade = 0
|
||||
|
||||
# 安全处理媒体信息
|
||||
if media_info and hasattr(media_info, 'vote_count'):
|
||||
try:
|
||||
vote_count = int(media_info.vote_count) if media_info.vote_count else 0
|
||||
except (ValueError, TypeError):
|
||||
vote_count = 0
|
||||
|
||||
if hasattr(media_info, 'tmdb_info') and media_info.tmdb_info:
|
||||
collection = media_info.tmdb_info.get("belongs_to_collection")
|
||||
if collection and isinstance(collection, dict):
|
||||
c_name = collection.get("name")
|
||||
|
||||
# 安全处理评分数据
|
||||
try:
|
||||
if vote_average is not None:
|
||||
vote_average = float(vote_average)
|
||||
else:
|
||||
vote_average = 0
|
||||
except (ValueError, TypeError):
|
||||
vote_average = 0
|
||||
|
||||
# 评分分类逻辑
|
||||
if vote_count < 10:
|
||||
vote_average = 0
|
||||
vote_path = "评分不足"
|
||||
elif vote_average >= 7:
|
||||
vote_path = "高分电影"
|
||||
elif vote_average >= 4:
|
||||
vote_path = "一般电影"
|
||||
else:
|
||||
vote_path = "垃圾电影"
|
||||
|
||||
# 安全处理年份数据
|
||||
try:
|
||||
if year and str(year).isdigit():
|
||||
year_int = int(year)
|
||||
if 1900 <= year_int <= 2100: # 合理的年份范围
|
||||
decade = (year_int // 10) * 10
|
||||
else:
|
||||
decade = 0
|
||||
logger.warning(f"年份超出合理范围: {year}")
|
||||
else:
|
||||
decade = 0
|
||||
except (ValueError, TypeError):
|
||||
decade = 0
|
||||
logger.warning(f"年份转换失败: {year}")
|
||||
|
||||
|
||||
# 构建分类路径
|
||||
path_parts = []
|
||||
|
||||
if self._collection_class and c_name:
|
||||
# 当collection为true时,只添加collection name
|
||||
# 清理collection名称,移除特殊字符
|
||||
clean_c_name = str(c_name).strip()
|
||||
if clean_c_name:
|
||||
path_parts.append("系列电影")
|
||||
path_parts.append(clean_c_name)
|
||||
else:
|
||||
# 当collection不为true时,根据其他配置添加路径
|
||||
if self._vote_class and vote_path:
|
||||
path_parts.append(vote_path)
|
||||
if self._year_class and decade > 0:
|
||||
path_parts.append(f"{decade}s")
|
||||
|
||||
# 构建最终的路径
|
||||
if path_parts:
|
||||
# 确保render_str不为空
|
||||
safe_render_str = str(render_str).strip() if render_str else ""
|
||||
event.event_data.updated_str = f"{'/'.join(path_parts)}/{safe_render_str}"
|
||||
# 更新事件数据
|
||||
event.event_data.updated = True
|
||||
event.event_data.source = "MultiClass"
|
||||
|
||||
# 发送消息
|
||||
if self._notify:
|
||||
self.post_message(
|
||||
mtype=NotificationType.Organize,
|
||||
title="多级分类完成",
|
||||
text=f"已重新分类: {event.event_data.updated_str}",
|
||||
)
|
||||
else:
|
||||
event.event_data.updated = False
|
||||
logger.warning(f"多级分类失败: 未找到分类路径,请检查配置是否已开启")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"多级分类异常: {str(e)}", exc_info=True)
|
||||
# 确保即使出错也不会影响原始数据
|
||||
if hasattr(event, 'event_data') and event.event_data:
|
||||
event.event_data.updated = False
|
||||
event.event_data.updated_str = getattr(data, 'render_str', '') if data else ''
|
||||
|
||||
def stop_service(self):
|
||||
"""
|
||||
停止服务
|
||||
"""
|
||||
pass
|
||||
@@ -38,7 +38,7 @@ class PersonMeta(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "actor.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.2"
|
||||
plugin_version = "2.2.1"
|
||||
# 插件作者
|
||||
plugin_author = "jxxghp"
|
||||
# 作者主页
|
||||
@@ -337,6 +337,9 @@ class PersonMeta(_PluginBase):
|
||||
if not self._enabled:
|
||||
return
|
||||
# 事件数据
|
||||
if not event or not event.event_data:
|
||||
logger.warn("TransferComplete事件数据为空")
|
||||
return
|
||||
mediainfo: MediaInfo = event.event_data.get("mediainfo")
|
||||
meta: MetaBase = event.event_data.get("meta")
|
||||
if not mediainfo or not meta:
|
||||
@@ -406,7 +409,7 @@ class PersonMeta(_PluginBase):
|
||||
"""
|
||||
peoples = []
|
||||
# 更新当前媒体项人物
|
||||
for people in iteminfo["People"] or []:
|
||||
for people in iteminfo.get("People", []) or []:
|
||||
if self._event.is_set():
|
||||
logger.info(f"演职人员刮削服务停止")
|
||||
return
|
||||
@@ -488,7 +491,7 @@ class PersonMeta(_PluginBase):
|
||||
if not seasons:
|
||||
logger.warn(f"{item.title} 未找到季媒体项")
|
||||
return
|
||||
for season in seasons["Items"]:
|
||||
for season in seasons.get("Items", []):
|
||||
# 获取豆瓣演员信息
|
||||
season_actors = self.__get_douban_actors(mediainfo=mediainfo, season=season.get("IndexNumber"))
|
||||
# 如果是Jellyfin,更新季的人物,Emby/Plex季没有人物
|
||||
@@ -514,7 +517,7 @@ class PersonMeta(_PluginBase):
|
||||
logger.warn(f"{item.title} 未找到集媒体项")
|
||||
continue
|
||||
# 更新集媒体项人物
|
||||
for episode in episodes["Items"]:
|
||||
for episode in episodes.get("Items", []):
|
||||
# 获取集媒体项详情
|
||||
episodeinfo = self.get_iteminfo(server=server, server_type=server_type,
|
||||
itemid=episode.get("Id"))
|
||||
@@ -664,9 +667,13 @@ class PersonMeta(_PluginBase):
|
||||
|
||||
# 锁定人物信息
|
||||
if updated_name:
|
||||
if "LockedFields" not in personinfo:
|
||||
personinfo["LockedFields"] = []
|
||||
if "Name" not in personinfo["LockedFields"]:
|
||||
personinfo["LockedFields"].append("Name")
|
||||
if updated_overview:
|
||||
if "LockedFields" not in personinfo:
|
||||
personinfo["LockedFields"] = []
|
||||
if "Overview" not in personinfo["LockedFields"]:
|
||||
personinfo["LockedFields"].append("Overview")
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class ToBypassTrackers(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Clash_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.4.2"
|
||||
plugin_version = "1.4.3"
|
||||
# 插件作者
|
||||
plugin_author = "wumode"
|
||||
# 作者主页
|
||||
@@ -68,7 +68,8 @@ class ToBypassTrackers(_PluginBase):
|
||||
self.ipv6_txt = self.get_data("ipv6_txt") if self.get_data("ipv6_txt") else ""
|
||||
self.ipv4_txt = self.get_data("ipv4_txt") if self.get_data("ipv4_txt") else ""
|
||||
try:
|
||||
with open(f"{settings.ROOT_PATH}/app/plugins/tobypasstrackers/sites/trackers", "r", encoding="utf-8") as f:
|
||||
site_file = settings.ROOT_PATH/'app'/'plugins'/'tobypasstrackers'/'sites'/'trackers'
|
||||
with open(site_file, "r", encoding="utf-8") as f:
|
||||
base64_str = f.read()
|
||||
self.trackers = json.loads(base64.b64decode(base64_str).decode("utf-8"))
|
||||
except Exception as e:
|
||||
@@ -101,7 +102,6 @@ class ToBypassTrackers(_PluginBase):
|
||||
)
|
||||
self._onlyonce = False
|
||||
self.__update_config()
|
||||
# self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
def get_state(self) -> bool:
|
||||
@@ -619,16 +619,14 @@ class ToBypassTrackers(_PluginBase):
|
||||
# Load Chnroute6 Lists
|
||||
res = RequestUtils().get_res(url=chnroute6_lists_url)
|
||||
if res is not None and res.status_code == 200:
|
||||
chnroute6_lists = res.text[:-1].split('\n')
|
||||
for ipr in chnroute6_lists:
|
||||
ipv6_list.append(ipr)
|
||||
chnroute6_lists = res.text.strip().split('\n')
|
||||
ipv6_list = [*chnroute6_lists]
|
||||
if self._china_ip_route:
|
||||
# Load Chnroute Lists
|
||||
res = RequestUtils().get_res(url=chnroute_lists_url)
|
||||
if res is not None and res.status_code == 200:
|
||||
chnroute_lists = res.text[:-1].split('\n')
|
||||
for ipr in chnroute_lists:
|
||||
ip_list.append(ipr)
|
||||
chnroute_lists = res.text.strip().split('\n')
|
||||
ip_list = [*chnroute_lists]
|
||||
do_sites = {site.domain: site.name for site in SiteOper().list_order_by_pri() if
|
||||
site.id in self._bypassed_sites}
|
||||
domain_name_map = {}
|
||||
|
||||
@@ -1 +1 @@
|
||||
eyJoZGRvbGJ5LmNvbSI6IFsidC5oZGRvbGJ5LmNvbSJdLCAidGp1cHQub3JnIjogWyJ0cmFja2VyLXB1YmxpYy50anVwdC5vcmciXSwgIm5pY2VwdC5uZXQiOiBbInd3dy5uaWNlcHQubmV0Il0sICJyb3VzaS56aXAiOiBbImhpdHB0LmNvbSJdLCAicHRob21lLm5ldCI6IFsicHRob21lLm5ldCJdLCAiaGR0aW1lLm9yZyI6IFsiaGR0aW1lLm9yZyJdLCAiZWFzdGdhbWUub3JnIjogWyJwdC5lYXN0Z2FtZS5vcmciXSwgInB0dGltZS5vcmciOiBbInd3dy5wdHRpbWUub3JnIl0sICJtLXRlYW0uY2MiOiBbInRyYWNrZXIubS10ZWFtLmNjIiwgInRyYWNrZXIubS10ZWFtLmlvIl0sICI1MnB0LnNpdGUiOiBbIjUycHQuc2l0ZSJdLCAicWluZ3dhcHQuY29tIjogWyJ0cmFja2VyLnFpbmd3YS5wcm8iLCAidHJhY2tlci5xaW5nd2FwdC5jb20iXSwgImhka3lsLmluIjogWyJ0cmFja2VyLmhka3lsLmluIl0sICJyYWluZ2ZoLnRvcCI6IFsicmFpbmdmaC50b3AiXSwgImhkZmFucy5vcmciOiBbImhkZmFucy5vcmciXSwgInB0bGdzLm9yZyI6IFsicHRsLmdzIiwgInJlbGF5MDEucHRsLmdzIl0sICJtb25pa2FkZXNpZ24udWsiOiBbInRyYWNrZXIubW9uaWthZGVzaWduLnVrIiwgImRhaWtpcmFpLm1vbmlrYWRlc2lnbi51ayIsICJhbmltZS1uby1pbmRleC5jb20iXSwgInB0c2Jhby5jbHViIjogWyJwdHNiYW8uY2x1YiJdLCAidG90aGVnbG9yeS5pbSI6IFsidHJhY2tlci50b3RoZWdsb3J5LmltIl0sICJ1Mi5kbWh5Lm9yZyI6IFsiZGF5ZHJlYW0uZG1oeS5iZXN0Il0sICJieXIucHQiOiBbInRyYWNrZXIuYnlyLnB0Il0sICJodWRidC5odXN0LmVkdS5jbiI6IFsiaHVkYnQuaHVzdC5lZHUuY24iXSwgImlsb2xpY29uLmNvbSI6IFsidHJhY2tlci5pbG9saWNvbi5jYyJdLCAiaGl0cHQuY29tIjogWyJoaXRwdC5jb20iXSwgImJ0c2Nob29sLmNsdWIiOiBbInB0LmJ0c2Nob29sLmNsdWIiXSwgImhkYXJlYS5jbHViIjogWyJ0cmFja2VyLmhkYXJlYS5jbHViIl0sICJzcHJpbmdzdW5kYXkubmV0IjogWyJvbjYuc3ByaW5nc3VuZGF5Lm5ldCIsICJvbi5zcHJpbmdzdW5kYXkubmV0Il0sICJ6bXB0LmNjIjogWyJ6bXB0LmNjIl0sICJjYXJwdC5uZXQiOiBbInRyYWNrZXIuY2FycHQubmV0Il0sICJpY2MyMDIyLmNvbSI6IFsidHJhY2tlci5pY2MyMDIyLnh5eiJdLCAia2VlcGZyZHMuY29tIjogWyJ0cmFja2VyLmtlZXBmcmRzLmNvbSJdLCAicHR6b25lLnh5eiI6IFsicHR6b25lLnh5eiJdLCAiY3NwdC50b3AiOiBbInRyYWNrZXIuY3NwdC50b3AiLCAidHJhY2tlci5jc3B0LmNjIiwgInRyYWNrZXIuY3NwdC5kYXRlIl0sICJjcmFicHQudmlwIjogWyJjcmFicHQudmlwIl0sICJva3B0Lm5ldCI6IFsid3d3Lm9rcHQubmV0Il0sICJnYW1lZ2FtZXB0LmNvbSI6IFsid3d3LmdhbWVnYW1lcHQuY29tIl0sICJhdWRpZW5jZXMubWUiOiBbInQuYXVkaWVuY2VzLm1lIiwgInRyYWNrZXIuY2luZWZpbGVzLmluZm8iXSwgInhpbmd5dW5nZS50b3AiOiBbInRyYWNrZXIueGluZ3l1bmdlLnRvcCIsICJ0cmFja2VyLnhpbmd5dW5nZS5zYnMiXSwgImV0OC5vcmciOiBbImV0OC5vcmciLCAidC5ldDgub3JnIl0sICJkaXNjZmFuLm5ldCI6IFsiZGlzY2Zhbi54eXoiXX0=
|
||||
eyI1MnB0LnNpdGUiOiBbIjUycHQuc2l0ZSJdLCAiYXVkaWVuY2VzLm1lIjogWyJ0LmF1ZGllbmNlcy5tZSIsICJ0cmFja2VyLmNpbmVmaWxlcy5pbmZvIl0sICJidHNjaG9vbC5jbHViIjogWyJwdC5idHNjaG9vbC5jbHViIl0sICJieXIucHQiOiBbInRyYWNrZXIuYnlyLnB0Il0sICJjYXJwdC5uZXQiOiBbInRyYWNrZXIuY2FycHQubmV0Il0sICJjcmFicHQudmlwIjogWyJjcmFicHQudmlwIl0sICJjc3B0LnRvcCI6IFsidHJhY2tlci5jc3B0LnRvcCIsICJ0cmFja2VyLmNzcHQuY2MiLCAidHJhY2tlci5jc3B0LmRhdGUiXSwgImRpc2NmYW4ubmV0IjogWyJkaXNjZmFuLnh5eiJdLCAiZWFzdGdhbWUub3JnIjogWyJwdC5lYXN0Z2FtZS5vcmciXSwgImV0OC5vcmciOiBbImV0OC5vcmciLCAidC5ldDgub3JnIl0sICJnYW1lZ2FtZXB0LmNvbSI6IFsid3d3LmdhbWVnYW1lcHQuY29tIl0sICJoZGFyZWEuY2x1YiI6IFsidHJhY2tlci5oZGFyZWEuY2x1YiJdLCAiaGRkb2xieS5jb20iOiBbInQuaGRkb2xieS5jb20iXSwgImhkZmFucy5vcmciOiBbImhkZmFucy5vcmciXSwgImhka3lsLmluIjogWyJ0cmFja2VyLmhka3lsLmluIl0sICJoZHRpbWUub3JnIjogWyJoZHRpbWUub3JnIl0sICJoaXRwdC5jb20iOiBbImhpdHB0LmNvbSJdLCAiaHVkYnQuaHVzdC5lZHUuY24iOiBbImh1ZGJ0Lmh1c3QuZWR1LmNuIl0sICJpY2MyMDIyLmNvbSI6IFsidHJhY2tlci5pY2MyMDIyLnh5eiJdLCAiaWxvbGljb24uY29tIjogWyJ0cmFja2VyLmlsb2xpY29uLmNjIl0sICJrZWVwZnJkcy5jb20iOiBbInRyYWNrZXIua2VlcGZyZHMuY29tIl0sICJtLXRlYW0uY2MiOiBbInRyYWNrZXIubS10ZWFtLmNjIiwgInRyYWNrZXIubS10ZWFtLmlvIl0sICJtb25pa2FkZXNpZ24udWsiOiBbInRyYWNrZXIubW9uaWthZGVzaWduLnVrIiwgImRhaWtpcmFpLm1vbmlrYWRlc2lnbi51ayIsICJhbmltZS1uby1pbmRleC5jb20iXSwgIm5pY2VwdC5uZXQiOiBbInd3dy5uaWNlcHQubmV0Il0sICJva3B0Lm5ldCI6IFsid3d3Lm9rcHQubmV0Il0sICJwdGhvbWUubmV0IjogWyJwdGhvbWUubmV0Il0sICJwdGxncy5vcmciOiBbInB0bC5ncyIsICJyZWxheTAxLnB0bC5ncyJdLCAicHRzYmFvLmNsdWIiOiBbInB0c2Jhby5jbHViIl0sICJwdHRpbWUub3JnIjogWyJ3d3cucHR0aW1lLm9yZyJdLCAicHR6b25lLnh5eiI6IFsicHR6b25lLnh5eiJdLCAicWluZ3dhcHQuY29tIjogWyJ0cmFja2VyLnFpbmd3YS5wcm8iLCAidHJhY2tlci5xaW5nd2FwdC5jb20iXSwgInJhaW5nZmgudG9wIjogWyJyYWluZ2ZoLnRvcCJdLCAicm91c2kuemlwIjogWyJoaXRwdC5jb20iXSwgInNwcmluZ3N1bmRheS5uZXQiOiBbIm9uNi5zcHJpbmdzdW5kYXkubmV0IiwgIm9uLnNwcmluZ3N1bmRheS5uZXQiXSwgInRqdXB0Lm9yZyI6IFsidHJhY2tlci1wdWJsaWMudGp1cHQub3JnIl0sICJ0b3RoZWdsb3J5LmltIjogWyJ0cmFja2VyLnRvdGhlZ2xvcnkuaW0iXSwgInUyLmRtaHkub3JnIjogWyJkYXlkcmVhbS5kbWh5LmJlc3QiXSwgInhpbmd5dW5nZS50b3AiOiBbInRyYWNrZXIueGluZ3l1bmdlLnRvcCIsICJ0cmFja2VyLnhpbmd5dW5nZS5zYnMiXSwgInptcHQuY2MiOiBbInptcHQuY2MiXSwgImhoYW5jbHViLnRvcCI6IFsidHJhY2tlci5oaGFuY2x1Yi50b3AiXSwgImhkY2l0eS5jaXR5IjogWyJzeW5jLmxlbml0ZXIub3JnIl19
|
||||
@@ -4,7 +4,6 @@ from abc import ABCMeta, abstractmethod
|
||||
from typing import Tuple
|
||||
|
||||
import chardet
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.helper.browser import PlaywrightHelper
|
||||
@@ -33,7 +32,7 @@ class _ISiteSigninHandler(metaclass=ABCMeta):
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.string import StringUtils
|
||||
@@ -26,7 +24,7 @@ class BTSchool(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -3,7 +3,6 @@ import re
|
||||
from typing import Tuple
|
||||
|
||||
from lxml import etree
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
@@ -36,7 +35,7 @@ class CHDBits(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.string import StringUtils
|
||||
@@ -26,7 +24,7 @@ class HaiDan(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import json
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
@@ -29,7 +27,7 @@ class Hares(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
@@ -30,7 +28,7 @@ class HDArea(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -2,7 +2,6 @@ import json
|
||||
from typing import Tuple
|
||||
|
||||
from lxml import etree
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
@@ -30,7 +29,7 @@ class HDChina(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.string import StringUtils
|
||||
@@ -28,7 +26,7 @@ class HDCity(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -2,8 +2,6 @@ import json
|
||||
import time
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.helper.ocr import OcrHelper
|
||||
from app.log import logger
|
||||
@@ -31,7 +29,7 @@ class HDSky(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.string import StringUtils
|
||||
@@ -30,7 +28,7 @@ class HDUpt(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
from typing import Tuple
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
@@ -25,7 +22,7 @@ class MTorrent(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if cls.site_url in url.split(".") else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作,馒头实际没有签到,非仿真模式下需要更新访问时间
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
@@ -52,7 +49,7 @@ class MTorrent(_ISiteSigninHandler):
|
||||
else:
|
||||
return False, "模拟登录失败,无法打开网站"
|
||||
|
||||
def login(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def login(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行登录操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
@@ -30,7 +28,7 @@ class NexusHD(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -3,7 +3,6 @@ import time
|
||||
from typing import Tuple
|
||||
|
||||
from lxml import etree
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.helper.ocr import OcrHelper
|
||||
@@ -32,7 +31,7 @@ class Opencd(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import json
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.string import StringUtils
|
||||
@@ -24,7 +22,7 @@ class PTerClub(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.string import StringUtils
|
||||
@@ -26,7 +24,7 @@ class PTTime(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -6,7 +6,6 @@ from typing import Tuple
|
||||
|
||||
from PIL import Image
|
||||
from lxml import etree
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
@@ -46,7 +45,7 @@ class Tjupt(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
@@ -33,7 +31,7 @@ class TTG(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -4,7 +4,6 @@ import re
|
||||
from typing import Tuple
|
||||
|
||||
from lxml import etree
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
@@ -39,7 +38,7 @@ class U2(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from typing import Tuple
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.plugins.autosignin.sites import _ISiteSigninHandler
|
||||
from app.utils.http import RequestUtils
|
||||
@@ -24,7 +22,7 @@ class YemaPT(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if cls.site_url in url else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
@@ -50,7 +48,7 @@ class YemaPT(_ISiteSigninHandler):
|
||||
else:
|
||||
return False, "签到失败,无法打开网站"
|
||||
|
||||
def login(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def login(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行登录操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -2,7 +2,6 @@ import json
|
||||
from typing import Tuple
|
||||
|
||||
from lxml import etree
|
||||
from ruamel.yaml import CommentedMap
|
||||
|
||||
from app.core.config import settings
|
||||
from app.log import logger
|
||||
@@ -27,7 +26,7 @@ class ZhuQue(_ISiteSigninHandler):
|
||||
"""
|
||||
return True if StringUtils.url_equal(url, cls.site_url) else False
|
||||
|
||||
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
|
||||
def signin(self, site_info: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行签到操作
|
||||
:param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
|
||||
|
||||
@@ -21,7 +21,7 @@ class DingdingMsg(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Dingding_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.12"
|
||||
plugin_version = "1.13"
|
||||
# 插件作者
|
||||
plugin_author = "nnlegenda"
|
||||
# 作者主页
|
||||
@@ -209,6 +209,8 @@ class DingdingMsg(_PluginBase):
|
||||
if text:
|
||||
# 对text进行Markdown特殊字符转义
|
||||
text = re.sub(r"([_`])", r"\\\1", text)
|
||||
# 钉钉中需要在换行前有两个空格,才能够正常换行
|
||||
text = re.sub(r"\n", r" \n", text)
|
||||
else:
|
||||
text = ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user