This commit is contained in:
InfinityPacer
2024-06-01 01:26:26 +08:00
18 changed files with 1200 additions and 288 deletions

View File

@@ -80,6 +80,8 @@ class EventType(Enum):
SubscribeAdded = "subscribe.added"
# 订阅已完成
SubscribeComplete = "subscribe.complete"
# 系统错误
SystemError = "system.error"
```
### 2. 如何在插件中实现远程命令响应?
@@ -426,7 +428,7 @@ class EventType(Enum):
- **请不要添加对黄赌毒站点的支持,否则随时封闭接口。**
### 7. 如何在插件中调用API接口
- 目前仅在插件的数据页面支持`GET/POST`API接口调用可调用插件自身、主程序或其它插件的APIv1.8.4+
- `v1.8.4+` 在插件的数据页面支持`GET/POST`API接口调用可调用插件自身、主程序或其它插件的API。
- 在`get_page`中定义好元素的事件以及相应的API参数具体可参考插件`豆瓣想看`
```json
{
@@ -448,11 +450,27 @@ class EventType(Enum):
### 8. 如何将插件内容显示到仪表板?
- `v1.8.7+` 支持将插件的内容显示到仪表盘,并支持定义占据的单元格大小,插件产生的仪表板仅管理员可见。
- 1. 根据插件需要展示的Widget内容规划展示内容的样式和规格也可设计多个规格样式并提供配置项供用户选择。
- 2. 实现 `get_dashboard` 方法,返回仪表盘的配置信息包括仪表盘的cols列配置适配不同屏幕以及仪表盘的页面配置json具体可参考插件`站点数据统计`
- 2. 实现 `get_dashboard_meta` 方法,定义仪表板key及名称支持一个插件有多个仪表板
```python
def get_dashboard(self, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]:
def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]:
"""
获取插件仪表盘页面需要返回1、仪表板cols配置字典2、全局配置自动刷新等2、仪表板页面元素配置json含数据
获取插件仪表盘元信息
返回示例:
[{
"key": "dashboard1", // 仪表盘的key在当前插件范围唯一
"name": "仪表盘1" // 仪表盘的名称
}, {
"key": "dashboard2",
"name": "仪表盘2"
}]
"""
pass
```
- 3. 实现 `get_dashboard` 方法根据key返回仪表盘的详细配置信息包括仪表盘的cols列配置适配不同屏幕以及仪表盘的页面配置json具体可参考插件`站点数据统计`
```python
def get_dashboard(self, key: str, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]:
"""
获取插件仪表盘页面需要返回1、仪表板col配置字典2、全局配置自动刷新等3、仪表板页面元素配置json含数据
1、col配置参考
{
"cols": 12, "md": 6
@@ -465,8 +483,10 @@ def get_dashboard(self, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, An
"subtitle": "组件子标题", // 组件子标题,缺省时不展示子标题
}
3、页面配置使用Vuetify组件拼装参考https://vuetifyjs.com/
kwargs参数可获取的值1、user_agent浏览器UA
:param key: 仪表盘key根据指定的key返回相应的仪表盘数据缺省时返回一个固定的仪表盘数据兼容旧版
"""
pass
```

BIN
icons/TrendingShow.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -3,11 +3,12 @@
"name": "站点自动签到",
"description": "自动模拟登录、签到站点。",
"labels": "站点",
"version": "2.3.1",
"version": "2.3.2",
"icon": "signin.png",
"author": "thsrite",
"level": 2,
"history": {
"v2.3.2": "修复YemaPT登录失败支持YemaPT自动签到",
"v2.3.1": "修复签到报错问题",
"v2.3": "优化模拟登录逻辑支持YemaPT模拟登录",
"v2.2": "适配馒头最新变化需要升级至v1.8.5+版本且维护好Authorization",
@@ -74,11 +75,13 @@
"name": "目录监控",
"description": "监控目录文件发生变化时实时整理到媒体库。",
"labels": "文件整理",
"version": "2.0",
"version": "2.2",
"icon": "directory.png",
"author": "jxxghp",
"level": 1,
"history": {
"v2.2": "更新目录设置说明",
"v2.1": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削",
"v2.0": "增强API安全性",
"v1.9": "修复目录监控不能正确获取下载历史记录进行识别的问题"
}
@@ -110,10 +113,13 @@
"name": "媒体库刮削",
"description": "定时对媒体库进行刮削,补齐缺失元数据和图片。",
"labels": "刮削",
"version": "1.4",
"version": "1.4.1",
"icon": "scraper.png",
"author": "jxxghp",
"level": 1
"level": 1,
"history": {
"v1.4.1": "修复nfo文件读取失败时任务中断问题"
}
},
"TorrentRemover": {
"name": "自动删种",
@@ -128,11 +134,12 @@
"name": "媒体文件同步删除",
"description": "同步删除历史记录、源文件和下载任务。",
"labels": "文件整理",
"version": "1.5",
"version": "1.6",
"icon": "mediasyncdel.png",
"author": "thsrite",
"level": 1,
"history": {
"v1.6": "修复删除辅种",
"v1.5": "支持手动删除订阅历史记录(本次更新之后的历史)"
}
},
@@ -278,11 +285,12 @@
"name": "整理VCB动漫压制组作品",
"description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理",
"labels": "文件整理,识别",
"version": "1.7.1",
"version": "1.8",
"icon": "vcbmonitor.png",
"author": "pixel@qingwa",
"level": 2,
"history": {
"v1.8": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削",
"v1.7.1": "修复偶尔安装失败问题"
}
},
@@ -302,11 +310,12 @@
"name": "自定义订阅",
"description": "定时刷新RSS报文识别内容后添加订阅或直接下载。",
"labels": "订阅",
"version": "1.3",
"version": "1.4",
"icon": "rss.png",
"author": "jxxghp",
"level": 2,
"history": {
"v1.4": "修复剧集本地是否存在的判断错误问题",
"v1.3": "支持手动删除订阅历史记录"
}
},
@@ -529,7 +538,7 @@
"name": "二级分类策略",
"description": "编辑下载目录和媒体库目录的二级分类规则。",
"labels": "文件整理",
"version": "1.1",
"version": "1.2",
"icon": "Bookstack_A.png",
"author": "jxxghp",
"level": 1
@@ -631,11 +640,15 @@
"name": "下载器助手",
"description": "自动做种、站点标签、自动删种。",
"labels": "下载管理,仪表板",
"version": "2.2",
"version": "2.6",
"icon": "DownloaderHelper.png",
"author": "hotlcc",
"level": 2,
"level": 1,
"history": {
"v2.6": "新增仪表板实时速率组件支持单独展示qb和tr的实时速率tr未测试有问题提Issue并@hotlcc。",
"v2.5": "优化通知类型降低认证级别要求使MP非认证用户可用但无法使用【站点名称优先】功能。主程序需升级至v1.9.2及以上版本,否则插件功能异常!",
"v2.4": "修复tr活动种子仪表板的种子排序的bug优化插件的消息发送。",
"v2.3": "仪表板支持多个下载器活动种子组件主程序版本需大于v1.9.1)。",
"v2.2": "优化仪表板组件标题;优化仪表板下载剩余时间描述。",
"v2.1": "优化了初始配置建议优化了配置Tracker的弹窗大小。",
"v2.0": "优化了仪表板种子状态提升仪表板对TR的适配度。",
@@ -679,11 +692,12 @@
"name": "插件自动升级",
"description": "定时检测、升级插件。",
"labels": "自动更新",
"version": "1.8",
"version": "1.9",
"icon": "PluginAutoUpgrade.png",
"author": "hotlcc",
"level": 1,
"history": {
"v1.9": "优化通知类型。主程序需升级至v1.9.2及以上版本,否则插件功能异常!",
"v1.8": "修复重置插件后丢失配置建议的问题。",
"v1.7": "修复了一些BUG。",
"v1.6": "修正数字配置值提交为字符串导致的问题。",
@@ -727,11 +741,14 @@
"name": "清理QB无效做种",
"description": "清理已经被站点删除的种子及对应源文件仅支持QB",
"labels": "Qbittorrent",
"version": "1.5",
"version": "1.8",
"icon": "clean_a.png",
"author": "DzAvril",
"level": 1,
"history": {
"v1.8": "增加远程命令切换全量通知修复bug",
"v1.7": "修复因消息内容包含'_'导致telegram API调用失败的问题",
"v1.6": "修复当种子有多个标签时,通过标签过滤不删除种子会失效的问题",
"v1.5": "1. 增加通过分类、标签过滤不删除种子功能2. 全量通知提供更多信息",
"v1.4": "修复插件功能失效的问题",
"v1.3": "1. 增加远程命令 2. 根据tracker error_message字段进行过滤,避免误删",
@@ -745,7 +762,16 @@
"description": "在仪表板中显示流行趋势海报轮播图。",
"labels": "仪表板",
"version": "1.0",
"icon": "Dsphoto_A.png",
"icon": "TrendingShow.jpg",
"author": "jxxghp",
"level": 1
},
"DailyWord": {
"name": "每日一言",
"description": "在仪表板中显示每日一言卡片。",
"labels": "仪表板",
"version": "1.1",
"icon": "Calibre_B.png",
"author": "jxxghp",
"level": 1
}

View File

@@ -38,7 +38,7 @@ class AutoSignIn(_PluginBase):
# 插件图标
plugin_icon = "signin.png"
# 插件版本
plugin_version = "2.3.1"
plugin_version = "2.3.2"
# 插件作者
plugin_author = "thsrite"
# 作者主页

View File

@@ -8,9 +8,9 @@ from app.plugins.autosignin.sites import _ISiteSigninHandler
from app.utils.http import RequestUtils
class MTorrent(_ISiteSigninHandler):
class YemaPT(_ISiteSigninHandler):
"""
m-team签到
YemaPT 签到
"""
# 匹配的站点Url每一个实现类都需要设置为自己的站点Url
site_url = "yemapt.org"
@@ -26,7 +26,7 @@ class MTorrent(_ISiteSigninHandler):
def signin(self, site_info: CommentedMap) -> Tuple[bool, str]:
"""
执行签到操作,馒头实际没有签到,非仿真模式下需要更新访问时间
执行签到操作
:param site_info: 站点信息含有站点Url、站点Cookie、UA等信息
:return: 签到结果信息
"""
@@ -35,19 +35,20 @@ class MTorrent(_ISiteSigninHandler):
"User-Agent": site_info.get("ua"),
"Accept": "application/json, text/plain, */*",
}
# 更新最后访问时间
res = RequestUtils(headers=headers,
timeout=15,
cookies=site_info.get("cookie"),
proxies=settings.PROXY if site_info.get("proxy") else None,
referer=site_info.get('url')
).post_res(url=urljoin(site_info.get('url'), "api/user/profile"))
# 获取用户信息,更新最后访问时间
res = (RequestUtils(headers=headers,
timeout=15,
cookies=site_info.get("cookie"),
proxies=settings.PROXY if site_info.get("proxy") else None,
referer=site_info.get('url')
).get_res(urljoin(site_info.get('url'), "api/consumer/checkIn")))
if res and res.json().get("success"):
return True, "模拟登录成功"
return True, "签到成功"
elif res is not None:
return False, f"模拟登录失败,状态码{res.status_code}"
return False, f"签到失败,签到结果{res.json().get('errorMessage')}"
else:
return False, "模拟登录失败,无法打开网站"
return False, "签到失败,无法打开网站"
def login(self, site_info: CommentedMap) -> Tuple[bool, str]:
"""
@@ -55,4 +56,23 @@ class MTorrent(_ISiteSigninHandler):
:param site_info: 站点信息含有站点Url、站点Cookie、UA等信息
:return: 登录结果信息
"""
return self.signin(site_info)
headers = {
"Content-Type": "application/json",
"User-Agent": site_info.get("ua"),
"Accept": "application/json, text/plain, */*",
}
# 获取用户信息,更新最后访问时间
res = (RequestUtils(headers=headers,
timeout=15,
cookies=site_info.get("cookie"),
proxies=settings.PROXY if site_info.get("proxy") else None,
referer=site_info.get('url')
).get_res(urljoin(site_info.get('url'), "api/user/profile")))
if res and res.json().get("success"):
return True, "模拟登录成功"
elif res is not None:
return False, f"模拟登录失败,状态码:{res.status_code}"
else:
return False, "模拟登录失败,无法打开网站"

View File

@@ -13,7 +13,7 @@ class CategoryEditor(_PluginBase):
# 插件图标
plugin_icon = "Bookstack_A.png"
# 插件版本
plugin_version = "1.1"
plugin_version = "1.2"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -39,12 +39,9 @@ class CategoryEditor(_PluginBase):
# 写入文件
if self._enabled:
self.user_yaml.write_text(self._content, encoding="utf-8")
if not settings.LIBRARY_CATEGORY:
self.systemmessage.put("二级分类未开启,策略已保存但未生效!", title="二级分类策略")
return
# 立即生效
CategoryHelper().init()
self.systemmessage.put("二级分类策略已更新!", title="二级分类策略")
self.systemmessage.put("二级分类策略已更新,请注意同步调整目录设置", title="二级分类策略")
def get_state(self) -> bool:
return self._enabled

View File

@@ -28,7 +28,7 @@ class CleanInvalidSeed(_PluginBase):
# 插件图标
plugin_icon = "clean_a.png"
# 插件版本
plugin_version = "1.5"
plugin_version = "1.8"
# 插件作者
plugin_author = "DzAvril"
# 作者主页
@@ -94,22 +94,7 @@ class CleanInvalidSeed(_PluginBase):
)
# 关闭一次性开关
self._onlyonce = False
self.update_config(
{
"onlyonce": False,
"cron": self._cron,
"enabled": self._enabled,
"notify": self._notify,
"delete_invalid_torrents": self._delete_invalid_torrents,
"delete_invalid_files": self._delete_invalid_files,
"detect_invalid_files": self._detect_invalid_files,
"notify_all": self._notify_all,
"download_dirs": self._download_dirs,
"exclude_keywords": self._exclude_keywords,
"exclude_categories": self._exclude_categories,
"exclude_labels": self._exclude_labels,
}
)
self._update_config()
# 启动任务
if self._scheduler.get_jobs():
@@ -119,6 +104,24 @@ class CleanInvalidSeed(_PluginBase):
def get_state(self) -> bool:
return self._enabled
def _update_config(self):
self.update_config(
{
"onlyonce": False,
"cron": self._cron,
"enabled": self._enabled,
"notify": self._notify,
"delete_invalid_torrents": self._delete_invalid_torrents,
"delete_invalid_files": self._delete_invalid_files,
"detect_invalid_files": self._detect_invalid_files,
"notify_all": self._notify_all,
"download_dirs": self._download_dirs,
"exclude_keywords": self._exclude_keywords,
"exclude_categories": self._exclude_categories,
"exclude_labels": self._exclude_labels,
}
)
@staticmethod
def get_command() -> List[Dict[str, Any]]:
"""
@@ -154,6 +157,13 @@ class CleanInvalidSeed(_PluginBase):
"category": "QB",
"data": {"action": "delete_invalid_files"},
},
{
"cmd": "/toggle_notify_all",
"event": EventType.PluginAction,
"desc": "QB清理插件切换全量通知",
"category": "QB",
"data": {"action": "toggle_notify_all"},
},
]
@eventmanager.register(EventType.PluginAction)
@@ -189,6 +199,22 @@ class CleanInvalidSeed(_PluginBase):
logger.info("收到远程命令,开始清理无效源文件")
self._delete_invalid_files = True
self.detect_invalid_files()
elif event_data.get("action") == "toggle_notify_all":
self._notify_all = not self._notify_all
self._update_config()
if self._notify_all:
self.post_message(
channel=event.event_data.get("channel"),
title="已开启全量通知",
userid=event.event_data.get("user"),
)
else:
self.post_message(
channel=event.event_data.get("channel"),
title="已关闭全量通知",
userid=event.event_data.get("user"),
)
return
else:
logger.error("收到未知远程命令")
return
@@ -256,8 +282,8 @@ class CleanInvalidSeed(_PluginBase):
# tracker未工作但暂时不能判定为失效做种需人工判断
tracker_not_working_torrents = []
working_tracker_set = set()
exclude_categories = self._exclude_categories.split("\n")
exclude_labels = self._exclude_labels.split("\n")
exclude_categories = self._exclude_categories.split("\n") if self._exclude_categories else []
exclude_labels = self._exclude_labels.split("\n") if self._exclude_labels else []
# 第一轮筛选出所有未工作的种子
for torrent in all_torrents:
trackers = torrent.trackers
@@ -307,7 +333,7 @@ class CleanInvalidSeed(_PluginBase):
if torrent.category in exclude_categories:
is_excluded = True
invalid_torrents_exclude_categories.append(torrent)
torrent_labels = torrent.tags.split(",")
torrent_labels = [tag.strip() for tag in torrent.tags.split(",")]
for label in torrent_labels:
if label in exclude_labels:
is_excluded = True
@@ -398,29 +424,34 @@ class CleanInvalidSeed(_PluginBase):
logger.info(exclude_labels_msg)
# 通知
if self._notify:
invalid_msg = invalid_msg.replace('_', '\_')
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=invalid_msg,
)
if self._notify_all:
tracker_not_working_msg = tracker_not_working_msg.replace('_', '\_')
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=tracker_not_working_msg,
)
if self._delete_invalid_torrents:
deleted_msg = deleted_msg.replace('_', '\_')
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=deleted_msg,
)
if self._notify_all:
exclude_categories_msg = exclude_categories_msg.replace('_', '\_')
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=exclude_categories_msg,
)
exclude_labels_msg = exclude_labels_msg.replace('_', '\_')
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
@@ -437,7 +468,15 @@ class CleanInvalidSeed(_PluginBase):
source_paths = []
total_size = 0
deleted_file_cnt = 0
exclude_key_words = self._exclude_keywords.split("\n")
exclude_key_words = self._exclude_keywords.split("\n") if self._exclude_keywords else []
if not self._download_dirs:
logger.error("未配置下载目录,无法检测未做种无效源文件")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【检测无效源文件】",
text="未配置下载目录,无法检测未做种无效源文件",
)
return
for path in self._download_dirs.split("\n"):
mp_path, qb_path = path.split(":")
source_path_map[mp_path] = qb_path
@@ -489,6 +528,7 @@ class CleanInvalidSeed(_PluginBase):
message += f"***已删除无效源文件,释放{StringUtils.str_filesize(total_size)}空间!***\n"
logger.info(message)
if self._notify:
message = message.replace('_', '\_')
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
@@ -754,3 +794,23 @@ class CleanInvalidSeed(_PluginBase):
self._scheduler = None
except Exception as e:
logger.error("退出插件失败:%s" % str(e))
if __name__ == "__main__":
clean = CleanInvalidSeed()
config = {
"enabled": True,
"notify": True,
"download_dirs": "/sata16t/春天:/保种/春天\n/sata16t/观众:/保种/观众\n/sata16t/UB:/保种/UB\n/sata16t/听听歌:/保种/听听歌\n/ssd/Download/shualiu:/Downloads/shualiu",
"delete_invalid_torrents": False,
"delete_invalid_files": False,
"detect_invalid_files": True,
"notify_all": False,
"onlyonce": False,
"cron": "0 0 * * *",
"exclude_keywords": "ABF-075\nIPZZ-002-C_GG5\nIPZZ-061\n.!qB",
"exclude_categories": "电影",
"exclude_labels": "春天",
}
clean.init_plugin(config)
clean.clean_invalid_seed()

View File

@@ -294,7 +294,8 @@ class CrossSeed(_PluginBase):
for site_key in self._token.strip().split("\n"):
site_key_arr = re.split(r"[\s:]+", site_key.strip())
site_name = site_key_arr[0]
site_name_key_map[site_name] = site_key_arr[1]
if len(site_key_arr) > 1:
site_name_key_map[site_name] = site_key_arr[1]
if len(site_key_arr) > 2:
if str.isdigit(site_key_arr[2]):
site_name_gap_map[site_name] = int(site_key_arr[2])

View File

@@ -0,0 +1,250 @@
from datetime import datetime
from functools import lru_cache
from typing import List, Tuple, Dict, Any, Optional
from app.plugins import _PluginBase
from app.utils.http import RequestUtils
class DailyWord(_PluginBase):
# 插件名称
plugin_name = "每日一言"
# 插件描述
plugin_desc = "在仪表板中显示每日一言卡片。"
# 插件图标
plugin_icon = "Calibre_B.png"
# 插件版本
plugin_version = "1.1"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
author_url = "https://github.com/jxxghp"
# 插件配置项ID前缀
plugin_config_prefix = "dailyowrd_"
# 加载顺序
plugin_order = 99
# 可使用的用户级别
auth_level = 1
_enable: bool = False
_size: str = "mini"
def init_plugin(self, config: dict = None):
self._enable = config.get("enable")
self._size = config.get("size")
@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,
'md': 4
},
'content': [
{
'component': 'VSelect',
'props': {
'model': 'size',
'label': '组件规格',
'items': [
{"title": "迷你", "value": "mini"},
{"title": "小型", "value": "small"},
{"title": "中型", "value": "medium"},
{"title": "大型", "value": "large"}
]
}
}
]
}
]
}
]
}
], {
"enable": self._enable,
"size": self._size
}
def get_page(self) -> List[dict]:
pass
def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]:
"""
获取插件仪表盘元信息
返回示例:
[{
"key": "dashboard1", // 仪表盘的key在当前插件范围唯一
"name": "仪表盘1" // 仪表盘的名称
}, {
"key": "dashboard2",
"name": "仪表盘2"
}]
"""
return [{
"key": "dailyword_dashboard",
"name": "每日一言"
}]
@lru_cache(maxsize=1)
def __get_youngam(self, **kwargs) -> Optional[dict]:
"""
获取每日一言缓存12小时
"""
res = RequestUtils().get_res("https://apier.youngam.cn/essay/one")
if res:
datalist = res.json().get("dataList")
return datalist[0] if datalist else {}
return {}
def get_dashboard(self, key: str = None, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]:
"""
获取插件仪表盘页面需要返回1、仪表板col配置字典2、全局配置自动刷新等3、仪表板页面元素配置json含数据
1、col配置参考
{
"cols": 12, "md": 6
}
2、全局配置参考
{
"refresh": 10 // 自动刷新时间,单位秒
}
3、页面配置使用Vuetify组件拼装参考https://vuetifyjs.com/
"""
# 列配置
if self._size == "mini":
cols = {
"cols": 12,
"md": 4
}
height = 160
elif self._size == "small":
cols = {
"cols": 12,
"md": 6
}
height = 262
elif self._size == "medium":
cols = {
"cols": 12,
"md": 8
}
height = 335
else:
cols = {
"cols": 12,
"md": 12
}
height = 500
# 全局配置
attrs = {
"border": False
}
# 获取流行越势数据
data = self.__get_youngam(today=datetime.now().strftime("%Y-%m-%d"))
if not data:
elements = [
{
'component': 'VCard',
'content': [
{
'component': 'VCardText',
'props': {
'class': 'text-center',
},
'content': [
{
'component': 'span',
'props': {
'class': 'text-h6'
},
'text': '无数据'
}
]
}
]
}
]
else:
elements = [
{
'component': 'VCard',
'props': {
'class': 'p-0'
},
'content': [
{
'component': 'VImg',
'props': {
'src': data.get('src'),
'cover': True,
'height': height
},
'content': [
{
'component': 'VCardText',
'props': {
'class': 'w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 pa-4',
},
'content': [
{
'component': 'h1',
'props': {
'class': 'mb-1 text-white text-shadow text-xl line-clamp-4 overflow-hidden text-ellipsis ...'
},
'html': data.get('text'),
},
{
'component': 'span',
'props': {
'class': 'text-right text-shadow line-clamp-2 overflow-hidden text-ellipsis ...'
},
'text': f"{data.get('year')}{data.get('month')}{data.get('day')}",
}
]
}
]
}
]
}]
return cols, attrs, elements
def get_state(self) -> bool:
return self._enable
def stop_service(self):
pass

View File

@@ -59,7 +59,7 @@ class DirMonitor(_PluginBase):
# 插件图标
plugin_icon = "directory.png"
# 插件版本
plugin_version = "2.0"
plugin_version = "2.2"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -83,10 +83,11 @@ class DirMonitor(_PluginBase):
_onlyonce = False
_cron = None
_size = 0
_scrape = True
# 模式 compatibility/fast
_mode = "fast"
# 转移方式
_transfer_type = settings.TRANSFER_TYPE
_transfer_type = "link"
_monitor_dirs = ""
_exclude_keywords = ""
_interval: int = 10
@@ -119,6 +120,7 @@ class DirMonitor(_PluginBase):
self._interval = config.get("interval") or 10
self._cron = config.get("cron")
self._size = config.get("size") or 0
self._scrape = config.get("scrape") or False
# 停止现有任务
self.stop_service()
@@ -235,7 +237,8 @@ class DirMonitor(_PluginBase):
"exclude_keywords": self._exclude_keywords,
"interval": self._interval,
"cron": self._cron,
"size": self._size
"size": self._size,
"scrape": self._scrape
})
@eventmanager.register(EventType.PluginAction)
@@ -457,7 +460,7 @@ class DirMonitor(_PluginBase):
)
# 刮削单个文件
if settings.SCRAP_METADATA:
if self._scrape:
self.chain.scrape_metadata(path=transferinfo.target_path,
mediainfo=mediainfo,
transfer_type=transfer_type)
@@ -756,7 +759,7 @@ class DirMonitor(_PluginBase):
'component': 'VSelect',
'props': {
'model': 'transfer_type',
'label': '转移方式',
'label': '整理方式',
'items': [
{'title': '移动', 'value': 'move'},
{'title': '复制', 'value': 'copy'},
@@ -824,6 +827,22 @@ class DirMonitor(_PluginBase):
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'scrape',
'label': '刮削元数据',
}
}
]
}
]
},
@@ -844,9 +863,9 @@ class DirMonitor(_PluginBase):
'rows': 5,
'placeholder': '每一行一个目录,支持以下几种配置方式,转移方式支持 move、copy、link、softlink、rclone_copy、rclone_move\n'
'监控目录\n'
'监控目录#转移方式\n'
'监控目录:转移目的目录\n'
'监控目录:转移目的目录#转移方式'
'监控目录#整理方式\n'
'监控目录:整理目的目录\n'
'监控目录:整理目的目录#转移方式'
}
}
]
@@ -889,7 +908,7 @@ class DirMonitor(_PluginBase):
'props': {
'type': 'info',
'variant': 'tonal',
'text': '监控目录不指定目的目录时,将转移到媒体库目录,并自动创建一级分类目录,同时按配置创建二级分类目录;监控目录指定了目的目录时,不会自动创建一级目录,但会根据配置创建二级分类目录'
'text': '支持4种配置方式1、监控目录2、监控目录#整理方式3、监控目录:整理目的目录4、监控目录:整理目的目录#转移方式。监控目录不指定目的目录时,将按媒体库目录设置整理到媒体库目录,并根据目录的分类设置自动创建一二级分类目录;监控目录指定了目的目录时,会尝试在媒体库目录设定中查找对应路径的目录配置,如存在则以目录设定的分类选项创建子目录,否则直接整理到该目的目录下。建议不设置目的目录,由系统根据目录设定自动分类整理'
}
}
]
@@ -945,12 +964,13 @@ class DirMonitor(_PluginBase):
"notify": False,
"onlyonce": False,
"mode": "fast",
"transfer_type": settings.TRANSFER_TYPE,
"transfer_type": "link",
"monitor_dirs": "",
"exclude_keywords": "",
"interval": 10,
"cron": "",
"size": 0
"size": 0,
"scrape": True
}
def get_page(self) -> List[dict]:

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,10 @@ class Downloader(Enum):
self.short_name: str = short_name
# Downloader 映射
DownloaderMap = dict((d.id, d) for d in Downloader)
class TaskResult:
"""
任务执行结果
@@ -304,3 +308,20 @@ class TorrentField(Enum):
# TorrentField 映射
TorrentFieldMap = dict((field.name, field) for field in TorrentField)
class DownloaderTransferInfo():
"""
下载器传输信息
"""
# 下载速度
download_speed: Optional[str] = '0.00B/s'
# 上传速度
upload_speed: Optional[str] = '0.00B/s'
# 下载量
download_size: Optional[str] = '0.00B'
# 上传量
upload_size: Optional[str] = '0.00B'
# 剩余空间
free_space: Optional[str] = '0.00B'

View File

@@ -25,7 +25,7 @@ class LibraryScraper(_PluginBase):
# 插件图标
plugin_icon = "scraper.png"
# 插件版本
plugin_version = "1.4"
plugin_version = "1.4.1"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -410,14 +410,14 @@ class LibraryScraper(_PluginBase):
"uniqueid[@type='TMDB']",
"tmdbid"
]
reader = NfoReader(file_path)
for xpath in xpaths:
try:
try:
reader = NfoReader(file_path)
for xpath in xpaths:
tmdbid = reader.get_element_value(xpath)
if tmdbid:
return tmdbid
except Exception as err:
print(str(err))
except Exception as err:
logger.warn(f"从nfo文件中获取tmdbid失败{str(err)}")
return None
def stop_service(self):

View File

@@ -17,9 +17,6 @@ from app.db.models.transferhistory import TransferHistory
from app.log import logger
from app.modules.emby import Emby
from app.modules.jellyfin import Jellyfin
from app.modules.qbittorrent import Qbittorrent
from app.modules.themoviedb.tmdbv3api import Episode
from app.modules.transmission import Transmission
from app.plugins import _PluginBase
from app.schemas.types import NotificationType, EventType, MediaType, MediaImageType
@@ -32,7 +29,7 @@ class MediaSyncDel(_PluginBase):
# 插件图标
plugin_icon = "mediasyncdel.png"
# 插件版本
plugin_version = "1.5"
plugin_version = "1.6"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -45,7 +42,6 @@ class MediaSyncDel(_PluginBase):
auth_level = 1
# 私有属性
episode = None
_scheduler: Optional[BackgroundScheduler] = None
_enabled = False
_sync_type: str = ""
@@ -58,16 +54,11 @@ class MediaSyncDel(_PluginBase):
_transferchain = None
_transferhis = None
_downloadhis = None
qb = None
tr = None
def init_plugin(self, config: dict = None):
self._transferchain = TransferChain()
self._transferhis = self._transferchain.transferhis
self._downloadhis = self._transferchain.downloadhis
self.episode = Episode()
self.qb = Qbittorrent()
self.tr = Transmission()
# 停止现有任务
self.stop_service()
@@ -1199,12 +1190,8 @@ class MediaSyncDel(_PluginBase):
# 删除转种后任务
logger.info(f"删除转种后下载任务:{download} - {download_id}")
# 删除转种后下载任务
if download == "transmission":
self.tr.delete_torrents(delete_file=True,
ids=download_id)
else:
self.qb.delete_torrents(delete_file=True,
ids=download_id)
self.chain.remove_torrents(hashs=torrent_hash,
downloader=download)
handle_torrent_hashs.append(download_id)
else:
# 暂停种子
@@ -1219,10 +1206,7 @@ class MediaSyncDel(_PluginBase):
logger.info(f"暂停转种后下载任务:{download} - {download_id}")
# 删除转种后下载任务
if download == "transmission":
self.tr.stop_torrents(ids=download_id)
else:
self.qb.stop_torrents(ids=download_id)
self.chain.stop_torrents(hashs=download_id, downloader=download)
handle_torrent_hashs.append(download_id)
else:
# 未转种de情况
@@ -1237,8 +1221,7 @@ class MediaSyncDel(_PluginBase):
handle_torrent_hashs.append(download_id)
# 处理辅种
handle_torrent_hashs = self.__del_seed(download=download,
download_id=download_id,
handle_torrent_hashs = self.__del_seed(download_id=download_id,
delete_flag=delete_flag,
handle_torrent_hashs=handle_torrent_hashs)
# 处理合集
@@ -1284,27 +1267,19 @@ class MediaSyncDel(_PluginBase):
# 删除合集种子
if delete_flag:
if str(download_file.downloader) == "transmission":
self.tr.delete_torrents(delete_file=True,
ids=download_file.download_hash)
else:
self.qb.delete_torrents(delete_file=True,
ids=download_file.download_hash)
self.chain.remove_torrents(hashs=download_file.download_hash,
downloader=download_file.downloader)
logger.info(f"删除合集种子 {download_file.downloader} {download_file.download_hash}")
else:
# 暂停合集种子
if str(download_file.downloader) == "transmission":
self.tr.stop_torrents(ids=download_file.download_hash)
else:
self.qb.stop_torrents(ids=download_file.download_hash)
self.chain.stop_torrents(hashs=download_file.download_hash,
downloader=download_file.downloader)
logger.info(f"暂停合集种子 {download_file.downloader} {download_file.download_hash}")
# 已处理种子+1
handle_torrent_hashs.append(download_file.download_hash)
# 处理合集辅种
handle_torrent_hashs = self.__del_seed(download=download_file.downloader,
download_id=download_file.download_hash,
handle_torrent_hashs = self.__del_seed(download_id=download_file.download_hash,
delete_flag=delete_flag,
handle_torrent_hashs=handle_torrent_hashs)
except Exception as e:
@@ -1313,7 +1288,7 @@ class MediaSyncDel(_PluginBase):
return handle_torrent_hashs
def __del_seed(self, download, download_id, delete_flag, handle_torrent_hashs):
def __del_seed(self, download_id, delete_flag, handle_torrent_hashs):
"""
删除辅种
"""
@@ -1337,30 +1312,18 @@ class MediaSyncDel(_PluginBase):
# 删除辅种历史
for torrent in torrents:
handle_torrent_hashs.append(torrent)
if str(download) == "qbittorrent":
# 删除辅种
if delete_flag:
logger.info(f"删除辅种:{downloader} - {torrent}")
self.qb.delete_torrents(delete_file=True,
ids=torrent)
# 暂停辅种
else:
self.qb.stop_torrents(ids=torrent)
logger.info(f"辅种:{downloader} - {torrent} 暂停")
# 删除辅种
if delete_flag:
logger.info(f"删除辅种:{downloader} - {torrent}")
self.chain.remove_torrents(hashs=torrent,
downloader=downloader)
# 暂停辅种
else:
# 删除辅种
if delete_flag:
logger.info(f"删除辅种:{downloader} - {torrent}")
self.tr.delete_torrents(delete_file=True,
ids=torrent)
# 暂停辅种
else:
self.tr.stop_torrents(ids=torrent)
logger.info(f"辅种:{downloader} - {torrent} 暂停")
self.chain.stop_torrents(hashs=torrent, download=downloader)
logger.info(f"辅种:{downloader} - {torrent} 暂停")
# 处理辅种的辅种
handle_torrent_hashs = self.__del_seed(download=downloader,
download_id=torrent,
handle_torrent_hashs = self.__del_seed(download_id=torrent,
delete_flag=delete_flag,
handle_torrent_hashs=handle_torrent_hashs)

View File

@@ -12,6 +12,7 @@ from app.helper.plugin import PluginHelper
from app.log import logger
from app.plugins import _PluginBase
from app.scheduler import Scheduler
from app.schemas import NotificationType
from app.schemas.types import SystemConfigKey
@@ -23,7 +24,7 @@ class PluginAutoUpgrade(_PluginBase):
# 插件图标
plugin_icon = "PluginAutoUpgrade.png"
# 插件版本
plugin_version = "1.8"
plugin_version = "1.9"
# 插件作者
plugin_author = "hotlcc"
# 作者主页
@@ -665,7 +666,7 @@ class PluginAutoUpgrade(_PluginBase):
text = self.__build_notify_message(results=results)
if not text:
return
self.post_message(title=f'{self.plugin_name}任务执行结果', text=text)
self.post_message(title=f'{self.plugin_name}任务执行结果', text=text, mtype=NotificationType.Plugin)
@staticmethod
def __build_notify_message(results: List[Dict[str, Any]]) -> str:

View File

@@ -19,6 +19,7 @@ from app.core.metainfo import MetaInfo
from app.helper.rss import RssHelper
from app.log import logger
from app.plugins import _PluginBase
from app.schemas import ExistMediaInfo
from app.schemas.types import SystemConfigKey, MediaType
lock = Lock()
@@ -32,7 +33,7 @@ class RssSubscribe(_PluginBase):
# 插件图标
plugin_icon = "rss.png"
# 插件版本
plugin_version = "1.3"
plugin_version = "1.4"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -656,51 +657,49 @@ class RssSubscribe(_PluginBase):
if not result:
logger.info(f"{title} {description} 不匹配过滤规则")
continue
# 查询缺失的媒体信息
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
if exist_flag:
logger.info(f'{mediainfo.title_year} 媒体库中已存在')
# 媒体库已存在的剧集
exist_info: Optional[ExistMediaInfo] = self.chain.media_exists(mediainfo=mediainfo)
if mediainfo.type == MediaType.TV:
if exist_info:
exist_season = exist_info.seasons
if exist_season:
exist_episodes = exist_season.get(meta.begin_season)
if exist_episodes and set(meta.episode_list).issubset(set(exist_episodes)):
logger.info(f'{mediainfo.title_year} {meta.season_episode} 己存在')
continue
elif exist_info:
# 电影已存在
logger.info(f'{mediainfo.title_year} 己存在')
continue
# 下载或订阅
if self._action == "download":
# 添加下载
result = self.downloadchain.download_single(
context=Context(
meta_info=meta,
media_info=mediainfo,
torrent_info=torrentinfo,
),
save_path=self._save_path,
username="RSS订阅"
)
if not result:
logger.error(f'{title} 下载失败')
continue
else:
if self._action == "download":
if mediainfo.type == MediaType.TV:
if no_exists:
exist_info = no_exists.get(mediainfo.tmdb_id)
season_info = exist_info.get(meta.begin_season or 1)
if not season_info:
logger.info(f'{mediainfo.title_year} {meta.season} 己存在')
continue
if (season_info.episodes
and not set(meta.episode_list).issubset(set(season_info.episodes))):
logger.info(f'{mediainfo.title_year} {meta.season_episode} 己存在')
continue
# 添加下载
result = self.downloadchain.download_single(
context=Context(
meta_info=meta,
media_info=mediainfo,
torrent_info=torrentinfo,
),
save_path=self._save_path,
username="RSS订阅"
)
if not result:
logger.error(f'{title} 下载失败')
continue
else:
# 检查是否在订阅中
subflag = self.subscribechain.exists(mediainfo=mediainfo, meta=meta)
if subflag:
logger.info(f'{mediainfo.title_year} {meta.season} 正在订阅中')
continue
# 添加订阅
self.subscribechain.add(title=mediainfo.title,
year=mediainfo.year,
mtype=mediainfo.type,
tmdbid=mediainfo.tmdb_id,
season=meta.begin_season,
exist_ok=True,
username="RSS订阅")
# 检查是否在订阅中
subflag = self.subscribechain.exists(mediainfo=mediainfo, meta=meta)
if subflag:
logger.info(f'{mediainfo.title_year} {meta.season} 正在订阅中')
continue
# 添加订阅
self.subscribechain.add(title=mediainfo.title,
year=mediainfo.year,
mtype=mediainfo.type,
tmdbid=mediainfo.tmdb_id,
season=meta.begin_season,
exist_ok=True,
username="RSS订阅")
# 存储历史记录
history.append({
"title": f"{mediainfo.title} {meta.season}",

View File

@@ -10,7 +10,7 @@ class TrendingShow(_PluginBase):
# 插件描述
plugin_desc = "在仪表板中显示流行趋势海报轮播图。"
# 插件图标
plugin_icon = "Dsphoto_A.png"
plugin_icon = "TrendingShow.jpg"
# 插件版本
plugin_version = "1.0"
# 插件作者
@@ -183,7 +183,7 @@ class TrendingShow(_PluginBase):
'show-arrows': 'hover',
'hide-delimiters': True,
'cycle': True,
'interval': 5000,
'interval': 10000,
'height': height
},
'content': [
@@ -197,7 +197,7 @@ class TrendingShow(_PluginBase):
{
'component': 'VCardText',
'props': {
'class': 'w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-4',
'class': 'w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 pa-4',
},
'content': [
{

View File

@@ -77,7 +77,7 @@ class VCBAnimeMonitor(_PluginBase):
# 插件图标
plugin_icon = "vcbmonitor.png"
# 插件版本
plugin_version = "1.7.1"
plugin_version = "1.8"
# 插件作者
plugin_author = "pixel@qingwa"
# 作者主页
@@ -106,6 +106,7 @@ class VCBAnimeMonitor(_PluginBase):
_onlyonce = False
_cron = None
_size = 0
_scrape = True
# 模式 compatibility/fast
_mode = "fast"
# 转移方式
@@ -142,6 +143,7 @@ class VCBAnimeMonitor(_PluginBase):
self._interval = config.get("interval") or 10
self._cron = config.get("cron")
self._size = config.get("size") or 0
self._scrape = config.get("scrape")
self._switch_ova = config.get("ova")
self._high_mode = config.get("high_mode")
self._torrents_path = config.get("torrents_path") or ""
@@ -286,6 +288,7 @@ class VCBAnimeMonitor(_PluginBase):
"interval": self._interval,
"cron": self._cron,
"size": self._size,
"scrape": self._scrape,
"ova": self._switch_ova,
"high_mode": self._high_mode,
"torrents_path": self._torrents_path
@@ -508,7 +511,7 @@ class VCBAnimeMonitor(_PluginBase):
)
# 刮削单个文件
if settings.SCRAP_METADATA:
if self._scrape:
self.chain.scrape_metadata(path=transferinfo.target_path,
mediainfo=mediainfo,
transfer_type=transfer_type)
@@ -826,6 +829,22 @@ class VCBAnimeMonitor(_PluginBase):
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'scrape',
'label': '刮削元数据',
}
}
]
}
]
},
{