add RSS订阅插件

This commit is contained in:
jxxghp 2023-08-17 16:24:24 +08:00
parent 406bff1a3b
commit 0903730ab6
6 changed files with 771 additions and 31 deletions

View File

@ -10,6 +10,7 @@ from app.chain.download import DownloadChain
from app.chain.search import SearchChain
from app.core.config import settings
from app.core.context import TorrentInfo, Context, MediaInfo
from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo
from app.db.models.subscribe import Subscribe
from app.db.subscribe_oper import SubscribeOper
@ -33,7 +34,7 @@ class SubscribeChain(ChainBase):
super().__init__(db)
self.downloadchain = DownloadChain(self._db)
self.searchchain = SearchChain(self._db)
self.subscribehelper = SubscribeOper(self._db)
self.subscribeoper = SubscribeOper(self._db)
self.siteshelper = SitesHelper()
self.message = MessageHelper()
self.systemconfig = SystemConfigOper(self._db)
@ -98,8 +99,8 @@ class SubscribeChain(ChainBase):
'lack_episode': kwargs.get('total_episode')
})
# 添加订阅
sid, err_msg = self.subscribehelper.add(mediainfo, doubanid=doubanid,
season=season, username=username, **kwargs)
sid, err_msg = self.subscribeoper.add(mediainfo, doubanid=doubanid,
season=season, username=username, **kwargs)
if not sid:
logger.error(f'{mediainfo.title_year} {err_msg}')
if not exist_ok and message:
@ -126,6 +127,15 @@ class SubscribeChain(ChainBase):
# 返回结果
return sid, ""
def exists(self, mediainfo: MediaInfo, meta: MetaBase = None):
"""
判断订阅是否已存在
"""
if self.subscribeoper.exists(tmdbid=mediainfo.tmdb_id,
season=meta.begin_season if meta else None):
return True
return False
def remote_refresh(self, channel: MessageChannel, userid: Union[str, int] = None):
"""
远程刷新订阅发送消息
@ -147,7 +157,7 @@ class SubscribeChain(ChainBase):
return
if arg_str:
sid = int(arg_str)
subscribe = self.subscribehelper.get(sid)
subscribe = self.subscribeoper.get(sid)
if not subscribe:
self.post_message(Notification(channel=channel,
title=f"订阅编号 {sid} 不存在!", userid=userid))
@ -174,15 +184,15 @@ class SubscribeChain(ChainBase):
:return: 更新订阅状态为R或删除订阅
"""
if sid:
subscribes = [self.subscribehelper.get(sid)]
subscribes = [self.subscribeoper.get(sid)]
else:
subscribes = self.subscribehelper.list(state)
subscribes = self.subscribeoper.list(state)
# 遍历订阅
for subscribe in subscribes:
logger.info(f'开始搜索订阅,标题:{subscribe.name} ...')
# 如果状态为N则更新为R
if subscribe.state == 'N':
self.subscribehelper.update(subscribe.id, {'state': 'R'})
self.subscribeoper.update(subscribe.id, {'state': 'R'})
# 生成元数据
meta = MetaInfo(subscribe.name)
meta.year = subscribe.year
@ -200,7 +210,7 @@ class SubscribeChain(ChainBase):
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
if exist_flag:
logger.info(f'{mediainfo.title_year} 媒体库中已存在,完成订阅')
self.subscribehelper.delete(subscribe.id)
self.subscribeoper.delete(subscribe.id)
# 发送通知
self.post_message(Notification(mtype=NotificationType.Subscribe,
title=f'{mediainfo.title_year}{meta.season} 已完成订阅',
@ -328,7 +338,7 @@ class SubscribeChain(ChainBase):
if not subscribe.best_version:
# 全部下载完成
logger.info(f'{mediainfo.title_year} 下载完成,完成订阅')
self.subscribehelper.delete(subscribe.id)
self.subscribeoper.delete(subscribe.id)
# 发送通知
self.post_message(Notification(mtype=NotificationType.Subscribe,
title=f'{mediainfo.title_year}{meta.season} 已完成订阅',
@ -338,7 +348,7 @@ class SubscribeChain(ChainBase):
priority = max([item.torrent_info.pri_order for item in downloads])
if priority == 100:
logger.info(f'{mediainfo.title_year} 洗版完成,删除订阅')
self.subscribehelper.delete(subscribe.id)
self.subscribeoper.delete(subscribe.id)
# 发送通知
self.post_message(Notification(mtype=NotificationType.Subscribe,
title=f'{mediainfo.title_year}{meta.season} 已洗版完成',
@ -346,7 +356,7 @@ class SubscribeChain(ChainBase):
else:
# 正在洗版,更新资源优先级
logger.info(f'{mediainfo.title_year} 正在洗版,更新资源优先级')
self.subscribehelper.update(subscribe.id, {
self.subscribeoper.update(subscribe.id, {
"current_priority": priority
})
@ -355,7 +365,7 @@ class SubscribeChain(ChainBase):
刷新站点最新资源
"""
# 所有订阅
subscribes = self.subscribehelper.list('R')
subscribes = self.subscribeoper.list('R')
if not subscribes:
# 没有订阅不运行
return
@ -428,7 +438,7 @@ class SubscribeChain(ChainBase):
logger.warn('没有缓存资源,无法匹配订阅')
return
# 所有订阅
subscribes = self.subscribehelper.list('R')
subscribes = self.subscribeoper.list('R')
# 遍历订阅
for subscribe in subscribes:
logger.info(f'开始匹配订阅,标题:{subscribe.name} ...')
@ -448,7 +458,7 @@ class SubscribeChain(ChainBase):
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
if exist_flag:
logger.info(f'{mediainfo.title_year} 媒体库中已存在,完成订阅')
self.subscribehelper.delete(subscribe.id)
self.subscribeoper.delete(subscribe.id)
# 发送通知
self.post_message(Notification(mtype=NotificationType.Subscribe,
title=f'{mediainfo.title_year}{meta.season} 已完成订阅',
@ -610,7 +620,7 @@ class SubscribeChain(ChainBase):
# 合并已下载集
note = list(set(note).union(set(episodes)))
# 更新订阅
self.subscribehelper.update(subscribe.id, {
self.subscribeoper.update(subscribe.id, {
"note": json.dumps(note)
})
@ -643,12 +653,12 @@ class SubscribeChain(ChainBase):
logger.info(f'{mediainfo.title_year}{season} 更新缺失集数为{len(left_episodes)} ...')
if update_date:
# 同时更新最后时间
self.subscribehelper.update(subscribe.id, {
self.subscribeoper.update(subscribe.id, {
"lack_episode": len(left_episodes),
"last_update": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
})
else:
self.subscribehelper.update(subscribe.id, {
self.subscribeoper.update(subscribe.id, {
"lack_episode": len(left_episodes)
})
@ -656,7 +666,7 @@ class SubscribeChain(ChainBase):
"""
查询订阅并发送消息
"""
subscribes = self.subscribehelper.list()
subscribes = self.subscribeoper.list()
if not subscribes:
self.post_message(Notification(channel=channel,
title='没有任何订阅!', userid=userid))
@ -695,13 +705,13 @@ class SubscribeChain(ChainBase):
if not arg_str.isdigit():
continue
subscribe_id = int(arg_str)
subscribe = self.subscribehelper.get(subscribe_id)
subscribe = self.subscribeoper.get(subscribe_id)
if not subscribe:
self.post_message(Notification(channel=channel,
title=f"订阅编号 {subscribe_id} 不存在!", userid=userid))
return
# 删除订阅
self.subscribehelper.delete(subscribe_id)
self.subscribeoper.delete(subscribe_id)
# 重新发送消息
self.remote_list(channel, userid)

View File

@ -32,6 +32,12 @@ class SubscribeOper(DbOper):
else:
return subscribe.id, "订阅已存在"
def exists(self, tmdbid: int, season: int):
"""
判断是否存在
"""
return True if Subscribe.exists(self._db, tmdbid=tmdbid, season=season) else False
def get(self, sid: int) -> Subscribe:
"""
获取订阅

View File

@ -59,6 +59,7 @@ class AutoSignIn(_PluginBase):
# 配置属性
_enabled: bool = False
_cron: str = ""
_onlyonce: bool = False
_notify: bool = False
_queue_cnt: int = 5
_sign_sites: list = []
@ -74,12 +75,13 @@ class AutoSignIn(_PluginBase):
if config:
self._enabled = config.get("enabled")
self._cron = config.get("cron")
self._onlyonce = config.get("onlyonce")
self._notify = config.get("notify")
self._queue_cnt = config.get("queue_cnt") or 5
self._sign_sites = config.get("sign_sites")
# 加载模块
if self._enabled:
if self._enabled or self._onlyonce:
self._site_schema = ModuleHelper.load('app.plugins.autosignin.sites',
filter_func=lambda _, obj: hasattr(obj, 'match'))
@ -108,6 +110,21 @@ class AutoSignIn(_PluginBase):
hour=trigger.hour, minute=trigger.minute,
name="站点自动签到")
if self._onlyonce:
# 关闭一次性开关
self._onlyonce = False
# 保存配置
self.update_config(
{
"enabled": self._enabled,
"notify": self._notify,
"cron": self._cron,
"onlyonce": self._onlyonce,
"queue_cnt": self._queue_cnt,
"sign_sites": self._sign_sites
}
)
# 启动任务
if self._scheduler.get_jobs():
self._scheduler.print_jobs()
@ -165,7 +182,7 @@ class AutoSignIn(_PluginBase):
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
'md': 4
},
'content': [
{
@ -181,7 +198,7 @@ class AutoSignIn(_PluginBase):
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
'md': 4
},
'content': [
{
@ -192,6 +209,22 @@ class AutoSignIn(_PluginBase):
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}
]
},
@ -259,6 +292,7 @@ class AutoSignIn(_PluginBase):
"enabled": False,
"notify": True,
"cron": "",
"onlyonce": False,
"queue_cnt": 5,
"sign_sites": []
}

View File

@ -57,9 +57,12 @@ class DoubanRank(_PluginBase):
}
_enabled = False
_cron = ""
_onlyonce = False
_rss_addrs = []
_ranks = []
_vote = 0
_clear = False
_clearflag = False
def init_plugin(self, config: dict = None):
self.downloadchain = DownloadChain()
@ -68,6 +71,7 @@ class DoubanRank(_PluginBase):
if config:
self._enabled = config.get("enabled")
self._cron = config.get("cron")
self._onlyonce = config.get("onlyonce")
self._vote = float(config.get("vote")) if config.get("vote") else 0
rss_addrs = config.get("rss_addrs")
if rss_addrs:
@ -78,12 +82,13 @@ class DoubanRank(_PluginBase):
else:
self._rss_addrs = []
self._ranks = config.get("ranks") or []
self._clear = config.get("clear")
# 停止现有任务
self.stop_service()
# 启动服务
if self._enabled:
if self._enabled or self._onlyonce:
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
if self._cron:
logger.info(f"豆瓣榜单订阅服务启动,周期:{self._cron}")
@ -100,6 +105,16 @@ class DoubanRank(_PluginBase):
name="豆瓣榜单订阅")
logger.info("豆瓣榜单订阅服务启动,周期:每天 08:00")
if self._onlyonce or self._clear:
# 关闭一次性开关
self._onlyonce = False
# 记录缓存清理标志
self._clearflag = self._clear
# 关闭清理缓存
self._clear = False
# 保存配置
self.__update_config()
if self._scheduler.get_jobs():
# 启动服务
self._scheduler.print_jobs()
@ -126,7 +141,8 @@ class DoubanRank(_PluginBase):
{
'component': 'VCol',
'props': {
'cols': 12
'cols': 12,
'md': 6
},
'content': [
{
@ -137,6 +153,22 @@ class DoubanRank(_PluginBase):
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}
]
},
@ -224,15 +256,38 @@ class DoubanRank(_PluginBase):
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'clear',
'label': '清理历史记录',
}
}
]
}
]
}
]
}
], {
"enabled": False,
"cron": "",
"onlyonce": False,
"vote": "",
"ranks": [],
"rss_addrs": "",
"clear": False
}
def get_page(self) -> List[dict]:
@ -353,6 +408,20 @@ class DoubanRank(_PluginBase):
except Exception as e:
print(str(e))
def __update_config(self):
"""
列新配置
"""
self.update_config({
"enabled": self._enabled,
"cron": self._cron,
"onlyonce": self._onlyonce,
"vote": self._vote,
"ranks": self._ranks,
"rss_addrs": self._rss_addrs,
"clear": self._clear
})
def __refresh_rss(self):
"""
刷新RSS
@ -366,7 +435,10 @@ class DoubanRank(_PluginBase):
logger.info(f"{len(addr_list)} 个榜单RSS地址需要刷新")
# 读取历史记录
history: List[dict] = self.get_data('history') or []
if self._clearflag:
history = []
else:
history: List[dict] = self.get_data('history') or []
for addr in addr_list:
if not addr:
@ -440,7 +512,8 @@ class DoubanRank(_PluginBase):
# 保存历史记录
self.save_data('history', history)
# 缓存只清理一次
self._clearflag = False
logger.info(f"所有榜单RSS刷新完成")
@staticmethod

View File

@ -3,6 +3,7 @@ from pathlib import Path
from threading import Lock
from typing import Optional, Any, List, Dict, Tuple
import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
@ -55,10 +56,13 @@ class DoubanSync(_PluginBase):
# 配置属性
_enabled: bool = False
_onlyonce: bool = False
_cron: str = ""
_notify: bool = False
_days: int = 7
_users: str = ""
_clear: bool = False
_clearflag: bool = False
def init_plugin(self, config: dict = None):
self.rsshelper = RssHelper()
@ -76,8 +80,10 @@ class DoubanSync(_PluginBase):
self._notify = config.get("notify")
self._days = config.get("days")
self._users = config.get("users")
self._onlyonce = config.get("onlyonce")
self._clear = config.get("clear")
if self._enabled:
if self._enabled or self._onlyonce:
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
if self._cron:
@ -92,6 +98,23 @@ class DoubanSync(_PluginBase):
else:
self._scheduler.add_job(self.sync, "interval", minutes=30, name="豆瓣想看")
if self._onlyonce:
logger.info(f"豆瓣想看服务启动,立即运行一次")
self._scheduler.add_job(func=self.sync, trigger='date',
run_date=datetime.datetime.now(
tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3)
)
if self._onlyonce or self._clear:
# 关闭一次性开关
self._onlyonce = False
# 记录缓存清理标志
self._clearflag = self._clear
# 关闭清理缓存
self._clear = False
# 保存配置
self.__update_config()
# 启动任务
if self._scheduler.get_jobs():
self._scheduler.print_jobs()
@ -140,7 +163,7 @@ class DoubanSync(_PluginBase):
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
'md': 4
},
'content': [
{
@ -156,7 +179,7 @@ class DoubanSync(_PluginBase):
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
'md': 4
},
'content': [
{
@ -167,6 +190,22 @@ class DoubanSync(_PluginBase):
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}
]
},
@ -225,15 +264,38 @@ class DoubanSync(_PluginBase):
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'clear',
'label': '清理历史记录',
}
}
]
}
]
}
]
}
], {
"enabled": False,
"notify": True,
"onlyonce": False,
"cron": "*/30 * * * *",
"days": 7,
"users": "",
"clear": False
}
def get_page(self) -> List[dict]:
@ -339,6 +401,20 @@ class DoubanSync(_PluginBase):
}
]
def __update_config(self):
"""
更新配置
"""
self.update_config({
"enabled": self._enabled,
"notify": self._notify,
"onlyonce": self._onlyonce,
"cron": self._cron,
"days": self._days,
"users": self._users,
"clear": self._clear
})
def stop_service(self):
"""
退出插件
@ -359,7 +435,10 @@ class DoubanSync(_PluginBase):
if not self._users:
return
# 读取历史记录
history: List[dict] = self.get_data('history') or []
if self._clearflag:
history = []
else:
history: List[dict] = self.get_data('history') or []
for user_id in self._users.split(","):
# 同步每个用户的豆瓣数据
if not user_id:
@ -460,6 +539,8 @@ class DoubanSync(_PluginBase):
logger.info(f"用户 {user_id} 豆瓣想看同步完成")
# 保存历史记录
self.save_data('history', history)
# 缓存只清理一次
self._clearflag = False
@eventmanager.register(EventType.DoubanSync)
def remote_sync(self, event: Event):

View File

@ -0,0 +1,536 @@
import datetime
import re
from pathlib import Path
from threading import Lock
from typing import Optional, Any, List, Dict, Tuple
import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from app.chain.download import DownloadChain
from app.chain.search import SearchChain
from app.chain.subscribe import SubscribeChain
from app.core.config import settings
from app.core.context import MediaInfo
from app.core.metainfo import MetaInfo
from app.helper.rss import RssHelper
from app.log import logger
from app.plugins import _PluginBase
lock = Lock()
class RssSubscribe(_PluginBase):
# 插件名称
plugin_name = "RSS订阅"
# 插件描述
plugin_desc = "定时刷新RSS报文识别报文内容并自动添加订阅。"
# 插件图标
plugin_icon = "rss.png"
# 主题色
plugin_color = "#F78421"
# 插件版本
plugin_version = "1.0"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
author_url = "https://github.com/jxxghp"
# 插件配置项ID前缀
plugin_config_prefix = "rsssubscribe_"
# 加载顺序
plugin_order = 19
# 可使用的用户级别
auth_level = 2
# 私有变量
_scheduler: Optional[BackgroundScheduler] = None
_cache_path: Optional[Path] = None
rsshelper = None
downloadchain = None
searchchain = None
subscribechain = None
# 配置属性
_enabled: bool = False
_cron: str = ""
_notify: bool = False
_onlyonce: bool = False
_address: str = ""
_include: str = ""
_exclude: str = ""
_proxy: bool = False
_clear: bool = False
_clearflag: bool = False
def init_plugin(self, config: dict = None):
self.rsshelper = RssHelper()
self.downloadchain = DownloadChain()
self.searchchain = SearchChain()
self.subscribechain = SubscribeChain()
# 停止现有任务
self.stop_service()
# 配置
if config:
self._enabled = config.get("enabled")
self._cron = config.get("cron")
self._notify = config.get("notify")
self._onlyonce = config.get("onlyonce")
self._address = config.get("address")
self._include = config.get("include")
self._exclude = config.get("exclude")
self._proxy = config.get("proxy")
self._clear = config.get("clear")
if self._enabled or self._onlyonce:
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
if self._cron:
try:
self._scheduler.add_job(func=self.check,
trigger=CronTrigger.from_crontab(self._cron),
name="RSS订阅")
except Exception as err:
logger.error(f"定时任务配置错误:{err}")
# 推送实时消息
self.systemmessage.put(f"执行周期配置错误:{err}")
else:
self._scheduler.add_job(self.check, "interval", minutes=30, name="RSS订阅")
if self._onlyonce:
logger.info(f"RSS订阅服务启动立即运行一次")
self._scheduler.add_job(func=self.check, trigger='date',
run_date=datetime.datetime.now(
tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3)
)
if self._onlyonce or self._clear:
# 关闭一次性开关
self._onlyonce = False
# 记录清理缓存设置
self._clearflag = self._clear
# 关闭清理缓存开关
self._clearflag = False
# 保存设置
self.__update_config()
# 启动任务
if self._scheduler.get_jobs():
self._scheduler.print_jobs()
self._scheduler.start()
def get_state(self) -> bool:
return self._enabled
@staticmethod
def get_command() -> List[Dict[str, Any]]:
"""
定义远程控制命令
:return: 命令关键字事件描述附带数据
"""
pass
def get_api(self) -> List[Dict[str, Any]]:
"""
获取插件API
[{
"path": "/xx",
"endpoint": self.xxx,
"methods": ["GET", "POST"],
"summary": "API说明"
}]
"""
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': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'enabled',
'label': '启用插件',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'notify',
'label': '发送通知',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '立即运行一次',
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'cron',
'label': '执行周期',
'placeholder': '5位cron表达式留空自动'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'address',
'label': 'RSS地址',
'rows': 5,
'placeholder': '每行一个RSS地址'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'content': [
{
'component': 'VTextField',
'props': {
'model': 'include',
'label': '包含',
'placeholder': '支持正则表达式'
}
}
]
},
{
'component': 'VCol',
'content': [
{
'component': 'VTextField',
'props': {
'model': 'exclude',
'label': '排除',
'placeholder': '支持正则表达式'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'proxy',
'label': '使用代理服务器',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'clear',
'label': '清理历史记录',
}
}
]
}
]
}
]
}
], {
"enabled": False,
"notify": True,
"onlyonce": False,
"cron": "*/30 * * * *",
"address": "",
"include": "",
"exclude": "",
"proxy": False,
"clear": False
}
def get_page(self) -> List[dict]:
"""
拼装插件详情页面需要返回页面配置同时附带数据
"""
# 查询同步详情
historys = self.get_data('history')
if not historys:
return [
{
'component': 'div',
'text': '暂无数据',
'props': {
'class': 'text-center',
}
}
]
# 数据按时间降序排序
historys = sorted(historys, key=lambda x: x.get('time'), reverse=True)
# 拼装页面
contents = []
for history in historys:
title = history.get("title")
poster = history.get("poster")
mtype = history.get("type")
time_str = history.get("time")
contents.append(
{
'component': 'VCard',
'content': [
{
'component': 'div',
'props': {
'class': 'd-flex justify-space-start flex-nowrap flex-row',
},
'content': [
{
'component': 'div',
'content': [
{
'component': 'VImg',
'props': {
'src': poster,
'height': 120,
'width': 80,
'aspect-ratio': '2/3',
'class': 'object-cover shadow ring-gray-500',
'cover': True
}
}
]
},
{
'component': 'div',
'content': [
{
'component': 'VCardSubtitle',
'props': {
'class': 'pa-2 font-bold break-words whitespace-break-spaces'
},
'text': title
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'类型:{mtype}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'时间:{time_str}'
}
]
}
]
}
]
}
)
return [
{
'component': 'div',
'props': {
'class': 'grid gap-3 grid-info-card',
},
'content': contents
}
]
def stop_service(self):
"""
退出插件
"""
try:
if self._scheduler:
self._scheduler.remove_all_jobs()
if self._scheduler.running:
self._scheduler.shutdown()
self._scheduler = None
except Exception as e:
logger.error("退出插件失败:%s" % str(e))
def __update_config(self):
"""
更新设置
"""
self.update_config({
"enabled": self._enabled,
"notify": self._notify,
"onlyonce": self._onlyonce,
"cron": self._cron,
"address": self._address,
"include": self._include,
"exclude": self._exclude,
"proxy": self._proxy,
"clear": self._clear
})
def check(self):
"""
通过用户RSS同步豆瓣想看数据
"""
if not self._address:
return
# 读取历史记录
if self._clearflag:
history = []
else:
history: List[dict] = self.get_data('history') or []
for url in self._address.split("\n"):
# 处理每一个RSS链接
if not url:
continue
logger.info(f"开始刷新RSS{url} ...")
results = self.rsshelper.parse(url, proxy=self._proxy)
if not results:
logger.error(f"未获取到RSS数据{url}")
return
# 解析数据
for result in results:
try:
title = result.get("title")
description = result.get("description")
# 检查是否处理过
if not title or title in [h.get("title") for h in history]:
continue
# 检查规则
if self._include and not re.search(r"%s" % self._include,
f"{title} {description}", re.IGNORECASE):
logger.info(f"{title} - {description} 不符合包含规则")
continue
if self._exclude and re.search(r"%s" % self._exclude,
f"{title} {description}", re.IGNORECASE):
logger.info(f"{title} - {description} 不符合排除规则")
continue
# 识别媒体信息
meta = MetaInfo(title=title, subtitle=description)
if not meta.name:
logger.warn(f"{title} 未识别到有效数据")
continue
mediainfo: MediaInfo = self.chain.recognize_media(meta=meta)
if not mediainfo:
logger.warn(f'未识别到媒体信息,标题:{title}')
continue
# 查询缺失的媒体信息
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
if exist_flag:
logger.info(f'{mediainfo.title_year} 媒体库中已存在')
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订阅")
# 存储历史记录
history.append({
"title": f"{mediainfo.title} {meta.season}",
"type": mediainfo.type.value,
"year": mediainfo.year,
"poster": mediainfo.get_poster_image(),
"overview": mediainfo.overview,
"tmdbid": mediainfo.tmdb_id,
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
except Exception as err:
logger.error(f'刷新RSS数据出错{err}')
logger.info(f"RSS {url} 刷新完成")
# 保存历史记录
self.save_data('history', history)
# 缓存只清理一次
self._clearflag = False