diff options
Diffstat (limited to 'ATRI')
| -rw-r--r-- | ATRI/database/db.py | 14 | ||||
| -rw-r--r-- | ATRI/database/models.py | 25 | ||||
| -rw-r--r-- | ATRI/exceptions.py | 4 | ||||
| -rw-r--r-- | ATRI/plugins/rss/__init__.py | 39 | ||||
| -rw-r--r-- | ATRI/plugins/rss/rss_mikanan/__init__.py | 0 | ||||
| -rw-r--r-- | ATRI/plugins/rss/rss_mikanan/data_source.py | 0 | ||||
| -rw-r--r-- | ATRI/plugins/rss/rss_mikanan/db.py | 0 | ||||
| -rw-r--r-- | ATRI/plugins/rss/rss_rsshub/__init__.py | 160 | ||||
| -rw-r--r-- | ATRI/plugins/rss/rss_rsshub/data_source.py | 115 | ||||
| -rw-r--r-- | ATRI/plugins/rss/rss_rsshub/db.py | 26 | 
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 + + +@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() | 
