summaryrefslogtreecommitdiff
path: root/ATRI
diff options
context:
space:
mode:
authorKyomotoi <[email protected]>2022-09-18 15:27:03 +0800
committerKyomotoi <[email protected]>2022-09-18 15:27:03 +0800
commit165ca069b017bf2f483de933ea5bd106c17d66b8 (patch)
tree103a773a40bc74c65b3d25b9ac0c81e9adc228bc /ATRI
parent4006dce2581e7d6597958180021974a6de01294e (diff)
downloadATRI-165ca069b017bf2f483de933ea5bd106c17d66b8.tar.gz
ATRI-165ca069b017bf2f483de933ea5bd106c17d66b8.tar.bz2
ATRI-165ca069b017bf2f483de933ea5bd106c17d66b8.zip
✨ 新增插件: Rss订阅
Diffstat (limited to 'ATRI')
-rw-r--r--ATRI/database/db.py14
-rw-r--r--ATRI/database/models.py25
-rw-r--r--ATRI/exceptions.py4
-rw-r--r--ATRI/plugins/rss/__init__.py39
-rw-r--r--ATRI/plugins/rss/rss_mikanan/__init__.py0
-rw-r--r--ATRI/plugins/rss/rss_mikanan/data_source.py0
-rw-r--r--ATRI/plugins/rss/rss_mikanan/db.py0
-rw-r--r--ATRI/plugins/rss/rss_rsshub/__init__.py160
-rw-r--r--ATRI/plugins/rss/rss_rsshub/data_source.py115
-rw-r--r--ATRI/plugins/rss/rss_rsshub/db.py26
10 files changed, 383 insertions, 0 deletions
diff --git a/ATRI/database/db.py b/ATRI/database/db.py
index a5a04c8..0917440 100644
--- a/ATRI/database/db.py
+++ b/ATRI/database/db.py
@@ -35,6 +35,18 @@ async def run():
"file_path": f"{DB_DIR}/thesaurusauditlist.sqlite3"
},
},
+ "rrs": {
+ "engine": "tortoise.backends.sqlite",
+ "credentials": {
+ "file_path": f"{DB_DIR}/rssrsshubsubscription.sqlite3"
+ },
+ },
+ "rms": {
+ "engine": "tortoise.backends.sqlite",
+ "credentials": {
+ "file_path": f"{DB_DIR}/rssmikananisubscription.sqlite3"
+ },
+ },
},
"apps": {
"bilibili": {
@@ -47,6 +59,8 @@ async def run():
},
"ts": {"models": [locals()["models"]], "default_connection": "ts"},
"tal": {"models": [locals()["models"]], "default_connection": "tal"},
+ "rrs": {"models": [locals()["models"]], "default_connection": "rrs"},
+ "rms": {"models": [locals()["models"]], "default_connection": "rms"},
},
}
)
diff --git a/ATRI/database/models.py b/ATRI/database/models.py
index d326151..9b009e4 100644
--- a/ATRI/database/models.py
+++ b/ATRI/database/models.py
@@ -57,3 +57,28 @@ class ThesaurusAuditList(Model):
class Meta:
app = "tal"
+
+
+class RssRsshubSubcription(Model):
+ _id = fields.TextField()
+ group_id = fields.IntField(null=True)
+ title = fields.TextField(null=True)
+ raw_link = fields.TextField(null=True)
+ rss_link = fields.TextField(null=True)
+ discription = fields.TextField(null=True)
+ update_time = fields.DatetimeField(default=datetime.fromordinal(1))
+
+ class Meta:
+ app = "rrs"
+
+
+class RssMikananiSubcription(Model):
+ _id = fields.TextField()
+ group_id = fields.IntField(null=True)
+ title = fields.TextField(null=True)
+ rss_link = fields.TextField(null=True)
+ discription = fields.TextField(null=True)
+ update_time = fields.DatetimeField(default=datetime.fromordinal(1))
+
+ class Meta:
+ app = "rms"
diff --git a/ATRI/exceptions.py b/ATRI/exceptions.py
index db6265f..4d97799 100644
--- a/ATRI/exceptions.py
+++ b/ATRI/exceptions.py
@@ -98,6 +98,10 @@ class ThesaurusError(BaseBotException):
prompt = "词库相关错误"
+class RssError(BaseBotException):
+ prompt = "RSS订阅错误"
+
+
@run_postprocessor
async def _track_error(
bot: Bot, event, matcher: Matcher, exception: Optional[Exception]
diff --git a/ATRI/plugins/rss/__init__.py b/ATRI/plugins/rss/__init__.py
new file mode 100644
index 0000000..a29f385
--- /dev/null
+++ b/ATRI/plugins/rss/__init__.py
@@ -0,0 +1,39 @@
+from pathlib import Path
+
+from nonebot.adapters.onebot.v11 import MessageEvent
+from nonebot.permission import SUPERUSER
+from nonebot.adapters.onebot.v11 import GROUP_OWNER, GROUP_ADMIN
+
+from ATRI.service import Service
+
+
+RSS_PLUGIN_DIR = Path(".") / "ATRI" / "plugins" / "rss"
+
+
+class RssHelper(Service):
+ def __init__(self):
+ Service.__init__(
+ self,
+ "rss",
+ "Rss系插件助手",
+ True,
+ permission=SUPERUSER | GROUP_OWNER | GROUP_ADMIN,
+ main_cmd="/rss",
+ )
+
+
+rss_menu = RssHelper().on_command("/rss", "Rss帮助菜单")
+
+
+@rss_menu.handle()
+async def _rss_menu(event: MessageEvent):
+ raw_rss_list = RSS_PLUGIN_DIR.glob("rss_*")
+ rss_list = [str(i).split("/")[-1] for i in raw_rss_list]
+ if not rss_list:
+ rss_list = [str(i).split("\\")[-1] for i in raw_rss_list]
+
+ result = f"""Rss Helper:
+ 可用订阅源: {"、".join(map(str, rss_list)).replace("rss_", str())}
+ 命令: /rss.(订阅源名称)
+ """.strip()
+ await rss_menu.finish(result)
diff --git a/ATRI/plugins/rss/rss_mikanan/__init__.py b/ATRI/plugins/rss/rss_mikanan/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ATRI/plugins/rss/rss_mikanan/__init__.py
diff --git a/ATRI/plugins/rss/rss_mikanan/data_source.py b/ATRI/plugins/rss/rss_mikanan/data_source.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ATRI/plugins/rss/rss_mikanan/data_source.py
diff --git a/ATRI/plugins/rss/rss_mikanan/db.py b/ATRI/plugins/rss/rss_mikanan/db.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ATRI/plugins/rss/rss_mikanan/db.py
diff --git a/ATRI/plugins/rss/rss_rsshub/__init__.py b/ATRI/plugins/rss/rss_rsshub/__init__.py
new file mode 100644
index 0000000..5b2fb61
--- /dev/null
+++ b/ATRI/plugins/rss/rss_rsshub/__init__.py
@@ -0,0 +1,160 @@
+import pytz
+import asyncio
+from tabulate import tabulate
+from datetime import timedelta, datetime
+
+from apscheduler.triggers.base import BaseTrigger
+from apscheduler.triggers.combining import AndTrigger
+from apscheduler.triggers.interval import IntervalTrigger
+
+from nonebot import get_bot
+from nonebot.matcher import Matcher
+from nonebot.params import CommandArg, ArgPlainText
+from nonebot.permission import Permission
+from nonebot.adapters.onebot.v11 import Message, GroupMessageEvent
+
+from ATRI.log import logger as log
+from ATRI.utils import timestamp2datetime
+from ATRI.utils.apscheduler import scheduler
+from ATRI.database import RssRsshubSubcription
+
+from .data_source import RssHubSubscriptor
+
+
+add_sub = RssHubSubscriptor().cmd_as_group("add", "为本群添加 RSSHub 订阅")
+
+
+@add_sub.handle()
+async def _(matcher: Matcher, args: Message = CommandArg()):
+ msg = args.extract_plain_text()
+ if msg:
+ matcher.set_arg("rrh_add_url", args)
+
+
+@add_sub.got("rrh_add_url", "RSSHub 链接呢?速速")
+async def _(event: GroupMessageEvent, _url: str = ArgPlainText("rrh_add_url")):
+ group_id = event.group_id
+ sub = RssHubSubscriptor()
+
+ result = await sub.add_sub(_url, group_id)
+ await add_sub.finish(result)
+
+
+del_sub = RssHubSubscriptor().cmd_as_group("del", "删除本群 RSSHub 订阅")
+
+
+@del_sub.handle()
+async def _(event: GroupMessageEvent):
+ group_id = event.group_id
+ sub = RssHubSubscriptor()
+
+ query_result = await sub.get_sub_list({"group_id": group_id})
+ if not query_result:
+ await del_sub.finish("本群还没有任何订阅呢...")
+
+ subs = list()
+ for i in query_result:
+ subs.append([i._id, i.title])
+
+ output = "本群的 RSSHub 订阅列表如下~\n" + tabulate(
+ subs, headers=["ID", "title"], tablefmt="plain"
+ )
+ await del_sub.send(output)
+
+
+@del_sub.got("rrh_del_sub_id", "要取消的ID呢? 速速\n(键入 q 以取消)")
+async def _(event: GroupMessageEvent, _id: str = ArgPlainText("rrh_del_sub_id")):
+ if _id == "q":
+ await del_sub.finish("已取消操作~")
+
+ group_id = event.group_id
+ sub = RssHubSubscriptor()
+
+ result = await sub.del_sub(_id, group_id)
+ await del_sub.finish(result)
+
+
+get_sub_list = RssHubSubscriptor().cmd_as_group(
+ "list", "获取本群 RSSHub 订阅列表", permission=Permission()
+)
+
+
+@get_sub_list.handle()
+async def _(event: GroupMessageEvent):
+ group_id = event.group_id
+ sub = RssHubSubscriptor()
+
+ query_result = await sub.get_sub_list({"group_id": group_id})
+ if not query_result:
+ await get_sub_list.finish("本群还没有任何订阅呢...")
+
+ subs = list()
+ for i in query_result:
+ subs.append([i.update_time, i.title])
+
+ output = "本群的 RSSHub 订阅列表如下~\n" + tabulate(
+ subs, headers=["最后更新时间", "标题"], tablefmt="plain"
+ )
+ await get_sub_list.send(output)
+
+
+tq = asyncio.Queue()
+
+
+class RssHubDynamicChecker(BaseTrigger):
+ def get_next_fire_time(self, previous_fire_time, now):
+ conf = RssHubSubscriptor().load_service("rss.rsshub")
+ if conf["enabled"]:
+ return now
+
+
+ AndTrigger([IntervalTrigger(seconds=120), RssHubDynamicChecker()]),
+ name="RssHub 订阅检查",
+ max_instances=3, # type: ignore
+ misfire_grace_time=60, # type: ignore
+)
+async def _():
+ sub = RssHubSubscriptor()
+ try:
+ all_dy = await sub.get_all_subs()
+ except Exception:
+ log.debug("RssHub 订阅列表为空 跳过")
+ return
+
+ if tq.empty():
+ for i in all_dy:
+ await tq.put(i)
+ else:
+ m: RssRsshubSubcription = tq.get_nowait()
+ log.info(f"准备查询 RssHub: {m.rss_link} 的动态, 队列剩余 {tq.qsize()}")
+
+ raw_ts = m.update_time.replace(
+ tzinfo=pytz.timezone("Asia/Shanghai")
+ ) + timedelta(hours=8)
+ ts = raw_ts.timestamp()
+
+ info: dict = await sub.get_rsshub_info(m.rss_link)
+ if not info:
+ log.warning(f"无法获取 RssHub: {m.rss_link} 的动态")
+ return
+
+ t_time = info["item"][0]["pubDate"]
+ time_patt = "%a, %d %b %Y %H:%M:%S GMT"
+
+ raw_t = datetime.strptime(t_time, time_patt) + timedelta(hours=8)
+ ts_t = raw_t.timestamp()
+
+ if ts < ts_t:
+ item = info["item"][0]
+ title = item["title"]
+ link = item["link"]
+
+ repo = f"""本群订阅的 RssHub 更新啦!
+ {title}
+ {link}
+ """
+
+ bot = get_bot()
+ await bot.send_group_msg(group_id=m.group_id, message=repo)
+ await sub.update_sub(m._id, m.group_id, {"update_time": timestamp2datetime(ts_t)})
diff --git a/ATRI/plugins/rss/rss_rsshub/data_source.py b/ATRI/plugins/rss/rss_rsshub/data_source.py
new file mode 100644
index 0000000..0dc0ebd
--- /dev/null
+++ b/ATRI/plugins/rss/rss_rsshub/data_source.py
@@ -0,0 +1,115 @@
+import xmltodict
+
+from nonebot.permission import SUPERUSER
+from nonebot.adapters.onebot.v11 import GROUP_OWNER, GROUP_ADMIN
+
+from ATRI.service import Service
+from ATRI.rule import is_in_service
+from ATRI.exceptions import RssError
+from ATRI.utils import request, gen_random_str
+
+from .db import DB
+
+
+class RssHubSubscriptor(Service):
+ def __init__(self):
+ Service.__init__(
+ self,
+ "rss.rsshub",
+ "Rss的Rsshub支持",
+ rule=is_in_service("rss.rsshub"),
+ permission=SUPERUSER | GROUP_OWNER | GROUP_ADMIN,
+ main_cmd="/rss.rsshub",
+ )
+
+ async def __add_sub(self, _id: str, group_id: int):
+ try:
+ async with DB() as db:
+ await db.add_sub(_id, group_id)
+ except Exception:
+ raise RssError("rss.rsshub: 添加订阅失败")
+
+ async def update_sub(self, _id: str, group_id: int, update_map: dict):
+ try:
+ async with DB() as db:
+ await db.update_sub(_id, group_id, update_map)
+ except Exception:
+ raise RssError("rss.rsshub: 更新订阅失败")
+
+ async def __del_sub(self, _id: str, group_id: int):
+ try:
+ async with DB() as db:
+ await db.del_sub({"_id": _id, "group_id": group_id})
+ except Exception:
+ raise RssError("rss.rsshub: 删除订阅失败")
+
+ async def get_sub_list(self, query_map: dict) -> list:
+ try:
+ async with DB() as db:
+ return await db.get_sub_list(query_map)
+ except Exception:
+ raise RssError("rss.rsshub: 获取订阅列表失败")
+
+ async def get_all_subs(self) -> list:
+ try:
+ async with DB() as db:
+ return await db.get_all_subs()
+ except Exception:
+ raise RssError("rss.rsshub: 获取所有订阅失败")
+
+ async def add_sub(self, url: str, group_id: int) -> str:
+ try:
+ resp = await request.get(url)
+ except Exception:
+ raise RssError("rss.rsshub: 请求链接失败")
+
+ if "RSSHub" not in resp.text:
+ return "该链接不含RSSHub内容"
+
+ xml_data = resp.read()
+ data = xmltodict.parse(xml_data)
+ check_url = data["rss"]["channel"]["link"]
+
+ query_result = await self.get_sub_list(
+ {"raw_link": check_url, "group_id": group_id}
+ )
+ if query_result:
+ _id = query_result[0]._id
+ return f"该链接已经订阅过啦! ID: {_id}"
+
+ _id = gen_random_str(6)
+ title = data["rss"]["channel"]["title"]
+ disc = data["rss"]["channel"]["description"]
+
+ await self.__add_sub(_id, group_id)
+ await self.update_sub(
+ _id,
+ group_id,
+ {
+ "title": title,
+ "rss_link": url,
+ "discription": disc,
+ },
+ )
+ return f"订阅成功! ID: {_id}"
+
+ async def del_sub(self, _id: str, group_id: int) -> str:
+ query_result = await self.get_sub_list({"_id": _id, "group_id": group_id})
+ if not query_result:
+ return "没有找到该订阅..."
+
+ await self.__del_sub(_id, group_id)
+ return f"成功取消ID为 {_id} 的订阅"
+
+ async def get_rsshub_info(self, url: str) -> dict:
+ try:
+ resp = await request.get(url)
+ except Exception:
+ raise RssError("rss.rsshub: 请求链接失败")
+
+ if "RSSHub" not in resp.text:
+ return dict()
+
+ xml_data = resp.read()
+ data = xmltodict.parse(xml_data)
+ return data["rss"]["channel"]
diff --git a/ATRI/plugins/rss/rss_rsshub/db.py b/ATRI/plugins/rss/rss_rsshub/db.py
new file mode 100644
index 0000000..3f614a3
--- /dev/null
+++ b/ATRI/plugins/rss/rss_rsshub/db.py
@@ -0,0 +1,26 @@
+from ATRI.database import RssRsshubSubcription
+
+
+class DB:
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ pass
+
+ async def add_sub(self, _id: str, group_id: int):
+ await RssRsshubSubcription.create(_id=_id, group_id=group_id)
+
+ async def update_sub(self, _id, group_id, update_map: dict):
+ await RssRsshubSubcription.filter(_id=_id, group_id=group_id).update(
+ **update_map
+ )
+
+ async def del_sub(self, query_map: dict):
+ await RssRsshubSubcription.filter(**query_map).delete()
+
+ async def get_sub_list(self, query_map: dict) -> list:
+ return await RssRsshubSubcription.filter(**query_map)
+
+ async def get_all_subs(self) -> list:
+ return await RssRsshubSubcription.all()