From 165ca069b017bf2f483de933ea5bd106c17d66b8 Mon Sep 17 00:00:00 2001
From: Kyomotoi <0w0@imki.moe>
Date: Sun, 18 Sep 2022 15:27:03 +0800
Subject: =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E6=8F=92=E4=BB=B6:=20Rss?=
 =?UTF-8?q?=E8=AE=A2=E9=98=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ATRI/plugins/rss/rss_rsshub/__init__.py    | 160 +++++++++++++++++++++++++++++
 ATRI/plugins/rss/rss_rsshub/data_source.py | 115 +++++++++++++++++++++
 ATRI/plugins/rss/rss_rsshub/db.py          |  26 +++++
 3 files changed, 301 insertions(+)
 create mode 100644 ATRI/plugins/rss/rss_rsshub/__init__.py
 create mode 100644 ATRI/plugins/rss/rss_rsshub/data_source.py
 create mode 100644 ATRI/plugins/rss/rss_rsshub/db.py

(limited to 'ATRI/plugins/rss/rss_rsshub')

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
+
+
+@scheduler.scheduled_job(
+    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()
-- 
cgit v1.2.3