diff options
| author | Kyomotoi <kyomotoiowo@gmail.com> | 2022-06-13 19:37:34 +0800 | 
|---|---|---|
| committer | Kyomotoi <kyomotoiowo@gmail.com> | 2022-06-13 19:37:34 +0800 | 
| commit | 59c923bbc3e91ad5a3482ec81c9192297ebbebe8 (patch) | |
| tree | d74da226542a8b6f54200bd6d55e8d459315c71d /ATRI/plugins | |
| parent | 4a876e2e64a690dcf01c385e273052e1e22caacd (diff) | |
| download | ATRI-59c923bbc3e91ad5a3482ec81c9192297ebbebe8.tar.gz ATRI-59c923bbc3e91ad5a3482ec81c9192297ebbebe8.tar.bz2 ATRI-59c923bbc3e91ad5a3482ec81c9192297ebbebe8.zip | |
♻️ 重构插件: b站动态订阅
Diffstat (limited to 'ATRI/plugins')
| -rw-r--r-- | ATRI/plugins/bilibili_dynamic/__init__.py | 287 | ||||
| -rw-r--r-- | ATRI/plugins/bilibili_dynamic/api.py | 36 | ||||
| -rw-r--r-- | ATRI/plugins/bilibili_dynamic/data_source.py | 373 | ||||
| -rw-r--r-- | ATRI/plugins/bilibili_dynamic/database/__init__.py | 1 | ||||
| -rw-r--r-- | ATRI/plugins/bilibili_dynamic/database/db.py | 49 | ||||
| -rw-r--r-- | ATRI/plugins/bilibili_dynamic/database/models.py | 14 | ||||
| -rw-r--r-- | ATRI/plugins/bilibili_dynamic/user.json | 244 | 
7 files changed, 327 insertions, 677 deletions
| diff --git a/ATRI/plugins/bilibili_dynamic/__init__.py b/ATRI/plugins/bilibili_dynamic/__init__.py index a42a0e3..0a8afc3 100644 --- a/ATRI/plugins/bilibili_dynamic/__init__.py +++ b/ATRI/plugins/bilibili_dynamic/__init__.py @@ -1,192 +1,167 @@  import re +import pytz +import asyncio  from tabulate import tabulate  from datetime import datetime, timedelta -import pytz  from apscheduler.triggers.base import BaseTrigger  from apscheduler.triggers.combining import AndTrigger  from apscheduler.triggers.interval import IntervalTrigger -from nonebot.params import State -from nonebot.adapters.onebot.v11 import MessageSegment, GroupMessageEvent, Message -from nonebot.typing import T_State  from nonebot import get_bot +from nonebot.matcher import Matcher +from nonebot.params import CommandArg, ArgPlainText +from nonebot.adapters.onebot.v11 import Message, GroupMessageEvent -from ATRI.utils.apscheduler import scheduler +from ATRI.log import logger as log  from ATRI.utils import timestamp2datetime -from ATRI.log import logger +from ATRI.utils.apscheduler import scheduler  from .data_source import BilibiliDynamicSubscriptor +from .database.models import Subscription -bilibili_dynamic = BilibiliDynamicSubscriptor().on_command( -    "/bilibili_dynamic", "b站动态订阅助手", aliases={"/bd", "b站动态"} -) +add_sub = BilibiliDynamicSubscriptor().cmd_as_group("add", "添加b站up主订阅") -__help__ = """好哦!是b站动态订阅诶~ -目前支持的功能如下...请键入对应关键词: -1.添加订阅 -2.取消订阅 -3.订阅列表 ------------------------------------ -用法示例1:/bd 添加订阅 -用法示例2:/bd 取消订阅 401742377(数字uid) -用法示例3:/bd 订阅列表""" - - -def help() -> str: -    return __help__ - - -@bilibili_dynamic.handle() -async def _menu(event: GroupMessageEvent, state: T_State = State()): -    args = str(event.get_plaintext()).strip().lower().split()[1:] -    if not args: -        await bilibili_dynamic.finish(help()) -    elif args and len(args) == 1: -        state["sub_command"] = args[0] -    elif args and len(args) == 2: -        state["sub_command"] = args[0] -        state["uid"] = args[1] -    else: -        await bilibili_dynamic.finish("参数错误QAQ 请检查您的输入~") - - -@bilibili_dynamic.got("sub_command", prompt="您要执行操作是?\n【添加订阅/取消订阅/订阅列表】") -async def handle_subcommand(event: GroupMessageEvent, state: T_State = State()): -    if state["sub_command"] not in ["添加订阅", "取消订阅", "订阅列表"]: -        await bilibili_dynamic.finish("没有这个命令哦, 请在【添加订阅/取消订阅/订阅列表】中选择并重新发送") - -    if state["sub_command"] == "订阅列表": -        subscriptor = BilibiliDynamicSubscriptor() -        r = await subscriptor.get_subscriptions(query_map={"groupid": event.group_id}) -        subs = [] -        for s in r: -            tm = s.last_update.replace(tzinfo=pytz.timezone("Asia/Shanghai")) -            subs.append([s.nickname, s.uid, tm + timedelta(hours=8)]) -        output = "本群订阅的UP列表如下~\n" + tabulate( -            subs, headers=["up名称", "UID", "上次更新时间"], tablefmt="plain", showindex=True -        ) -        await bilibili_dynamic.finish(output) - - -@bilibili_dynamic.got("uid", prompt="请输入b站UID(输入-1取消):") -async def handle_uid(event: GroupMessageEvent, state: T_State = State()): -    sub_command = state["sub_command"] -    if isinstance(state["uid"], list): -        uid = str(state["uid"][0]) -    else: -        uid = state["uid"] - -    if uid == "-1": -        await bilibili_dynamic.finish("已经成功退出订阅~") -    if not re.match(r"^\d+$", uid): -        await bilibili_dynamic.reject("这似乎不是UID呢, 请重新输入:") -    uid = int(uid) -    subscriptor = BilibiliDynamicSubscriptor() -    up_name = await subscriptor.get_upname_by_uid(uid) -    if up_name == "": -        await bilibili_dynamic.finish(f"无法获取uid={uid}的up信息...订阅失败了".format(uid=uid)) -    else: -        await bilibili_dynamic.send( -            f"uid为{uid}的UP主是【{up_name}】\n{sub_command}操作中...".format( -                uid=uid, up_name=up_name, sub_command=sub_command -            ) -        ) -    query_result = await subscriptor.get_subscriptions( -        query_map={"uid": uid, "groupid": event.group_id} + +@add_sub.handle() +async def _bd_add_sub(matcher: Matcher, args: Message = CommandArg()): +    msg = args.extract_plain_text() +    if msg: +        matcher.set_arg("bd_add_sub_id", args) + + +@add_sub.got("bd_add_sub_id", "up主id呢?速速") +async def _bd_deal_add_sub( +    event: GroupMessageEvent, _id: str = ArgPlainText("bd_add_sub_id") +): +    patt = r"^\d+$" +    if not re.match(patt, _id): +        await add_sub.reject("这似乎不是id呢,请重新输入:") + +    __id = int(_id) +    group_id = event.group_id +    sub = BilibiliDynamicSubscriptor() + +    up_nickname = await sub.get_up_nickname(__id) +    if not up_nickname: +        await add_sub.finish(f"无法获取id为 {_id} 的up主信息...操作失败了") + +    query_result = await sub.get_sub_list(__id, group_id) +    if len(query_result): +        await add_sub.finish(f"该up主[{up_nickname}]已在本群订阅列表中啦!") + +    await sub.add_sub(__id, group_id) +    await sub.update_sub( +        __id, {"up_nickname": up_nickname, "last_update": datetime.utcnow()}      ) -    success = True -    if sub_command == "添加订阅": -        if len(query_result) > 0: -            await bilibili_dynamic.finish( -                f"订阅失败,因为uid={uid}的UP主【{up_name}】已在本群订阅列表中".format( -                    uid=uid, up_name=up_name -                ) -            ) -        success = await subscriptor.add_subscription(uid, event.group_id) -        success = success and ( -            await subscriptor.update_subscription_by_uid( -                uid=uid, -                update_map={"nickname": up_name, "last_update": datetime.utcnow()}, -            ) -        ) -    elif sub_command == "取消订阅": -        if len(query_result) == 0: -            await bilibili_dynamic.finish( -                f"取消订阅失败,因为uid={uid}的UP主【{up_name}】不在本群订阅列表中".format( -                    uid=uid, up_name=up_name -                ) -            ) -        success = await subscriptor.remove_subscription(uid, event.group_id) -    if success: -        await bilibili_dynamic.finish( -            f"成功{sub_command}【{up_name}】的动态!".format( -                sub_command=sub_command, up_name=up_name -            ) -        ) -    else: -        await bilibili_dynamic.finish("诶...因为神奇的原因失败了") +    await add_sub.finish(f"成功订阅名为[{up_nickname}]up主的动态~!") + +del_sub = BilibiliDynamicSubscriptor().cmd_as_group("del", "删除b站up主订阅") -from queue import Queue -# 任务队列(taskQueue) -tq = Queue() +@del_sub.handle() +async def _bd_del_sub(matcher: Matcher, args: Message = CommandArg()): +    msg = args.extract_plain_text() +    if msg: +        matcher.set_arg("bd_del_sub_id", args) -class BilibiliDynamicCheckEnabledTrigger(BaseTrigger): -    # 自定义trigger 保证服务开启 -    # 实现abstract方法 <get_next_fire_time> +@del_sub.got("bd_del_sub_id", "up主id呢?速速") +async def _bd_deal_del_sub( +    event: GroupMessageEvent, _id: str = ArgPlainText("bd_del_sub_id") +): +    patt = r"^\d+$" +    if not re.match(patt, _id): +        await add_sub.reject("这似乎不是id呢,请重新输入:") + +    __id = int(_id) +    group_id = event.group_id +    sub = BilibiliDynamicSubscriptor() + +    up_nickname = await sub.get_up_nickname(__id) +    if not up_nickname: +        await add_sub.finish(f"无法获取id为 {__id} 的up主信息...操作失败了") + +    query_result = await sub.get_sub_list(__id, group_id) +    if not query_result: +        await del_sub.finish(f"取消订阅失败...该up主[{up_nickname}]并不在本群订阅列表中") + +    await sub.del_sub(__id, group_id) +    await del_sub.finish(f"成功取消该up主[{up_nickname}]的订阅~") + + +get_sub_list = BilibiliDynamicSubscriptor().cmd_as_group("list", "获取b站up主订阅列表") + + +@get_sub_list.handle() +async def _get_sub_list(event: GroupMessageEvent): +    group_id = event.group_id +    sub = BilibiliDynamicSubscriptor() + +    query_result = await sub.get_sub_list(group_id=group_id) +    if not query_result: +        await get_sub_list.finish("本群还未订阅任何up主呢...") + +    subs = list() +    for i in query_result: +        tm = i.last_update.replace(tzinfo=pytz.timezone("Asia/Shanghai")) +        subs.append([i.up_nickname, i.uid, tm + timedelta(hours=8)]) + +    output = "本群订阅的up列表如下~\n" + tabulate( +        subs, headers=["up主", "uid", "最后更新时间"], tablefmt="plain", showindex=True +    ) +    await get_sub_list.finish(output) + + +tq = asyncio.Queue() + + +class BilibiliDynamicChecker(BaseTrigger):      def get_next_fire_time(self, previous_fire_time, now): -        subscriptor = BilibiliDynamicSubscriptor() -        config = subscriptor.load_service("b站动态订阅") -        if config["enabled"] == False: -            return None -        else: +        sub = BilibiliDynamicSubscriptor() +        conf = sub.load_service("b站动态订阅-rebu") +        if conf.get("enabled"):              return now -# 业务逻辑 -# 每10s从任务队列中拉一个uid出来,调用api进行查询 -# 当任务队列为空时,从数据库读取订阅列表,并塞入任务队列tq中  @scheduler.scheduled_job( -    AndTrigger([IntervalTrigger(seconds=10), BilibiliDynamicCheckEnabledTrigger()]), -    name="b站动态检查", +    AndTrigger([IntervalTrigger(seconds=10), BilibiliDynamicChecker()]), +    name="b站动态更新检查",      max_instances=3,  # type: ignore      misfire_grace_time=60,  # type: ignore  ) -async def _check_dynamic(): -    from ATRI.database.models import Subscription - -    subscriptor = BilibiliDynamicSubscriptor() -    all_dynamic = await subscriptor.get_all_subscriptions() +async def _check_dynamic_rebu(): +    sub = BilibiliDynamicSubscriptor() +    all_dy = await sub.get_all_subs()      if tq.empty(): -        for d in all_dynamic: -            tq.put(d) +        for i in all_dy: +            await tq.put(i)      else: -        d: Subscription = tq.get() -        logger.info("准备查询UP【{up}】的动态 队列剩余{size}".format(up=d.nickname, size=tq.qsize())) -        ts = int(d.last_update.timestamp()) -        info: dict = await subscriptor.get_recent_dynamic_by_uid(d.uid) -        res = [] -        if info: -            if info.get("cards") is not None: -                res = subscriptor.extract_dynamics_detail(info.get("cards")) - -        if len(res) == 0: -            logger.warning("获取UP【{up}】的动态为空".format(up=d.nickname)) -        for i in res: -            i["name"] = d.nickname +        m: Subscription = tq.get_nowait() +        log.info(f"准备查询up主[{m.up_nickname}]的动态,队列剩余 {tq.qsize()}") + +        ts = int(m.last_update.timestamp()) +        info: dict = await sub.get_up_recent_dynamic(m.uid) +        result = list() +        if info.get("cards", list()): +            result = sub.extract_dyanmic(info["cards"]) +        if not result: +            log.warning(f"无法获取up主[{m.up_nickname}]的动态") + +        for i in result: +            i["name"] = m.up_nickname              if ts < i["timestamp"]: -                text, pic_url = subscriptor.generate_output(pattern=i) -                output = Message( -                    [MessageSegment.text(text), MessageSegment.image(pic_url)] -                ) +                content = Message(sub.gen_output(i)) +                  bot = get_bot() -                await bot.send_group_msg(group_id=d.groupid, message=output) -                _ = await subscriptor.update_subscription_by_uid( -                    uid=d.uid, -                    update_map={"last_update": timestamp2datetime(i["timestamp"])}, +                await bot.send_group_msg(group_id=m.group_id, message=content) +                await sub.update_sub( +                    m.uid, +                    { +                        "group_id": m.group_id, +                        "last_update": timestamp2datetime(i["timestamp"]), +                    },                  )                  break diff --git a/ATRI/plugins/bilibili_dynamic/api.py b/ATRI/plugins/bilibili_dynamic/api.py new file mode 100644 index 0000000..a455805 --- /dev/null +++ b/ATRI/plugins/bilibili_dynamic/api.py @@ -0,0 +1,36 @@ +from ATRI.utils import request +from ATRI.exceptions import RequestError + + +class API: +    def __init__(self, uid: int): +        self.uid = uid + +    async def _request(self, url: str, params: dict = dict()) -> dict: +        headers = { +            "Referer": "https://www.bilibili.com", +            "User-Agent": "Mozilla/5.0", +        } + +        try: +            resp = await request.get(url, params=params, headers=headers) +        except RequestError: +            raise RequestError("Request failed!") + +        return resp.json() + +    async def get_user_info(self) -> dict: +        url = "https://api.bilibili.com/x/space/acc/info" +        params = {"mid": self.uid} +        return await self._request(url, params) + +    async def get_user_dynamics( +        self, offset: int = int(), need_top: bool = False +    ) -> dict: +        url = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history" +        params = { +            "host_uid": self.uid, +            "offset_dynamic_id": offset, +            "need_top": 1 if need_top else 0, +        } +        return await self._request(url, params) diff --git a/ATRI/plugins/bilibili_dynamic/data_source.py b/ATRI/plugins/bilibili_dynamic/data_source.py index 14143e6..5233692 100644 --- a/ATRI/plugins/bilibili_dynamic/data_source.py +++ b/ATRI/plugins/bilibili_dynamic/data_source.py @@ -1,286 +1,99 @@ -from ATRI.service import Service -from ATRI.rule import is_in_service -from ATRI.database.db import DB -from ATRI.utils import timestamp2datetime -  import json -import aiohttp -import os -import re -import asyncio -from typing import Any  from operator import itemgetter -__session_pool = {} - - -def get_api(field: str) -> dict: -    """ -    获取 API。 - -    Args: -        field (str): API 所属分类,即 data/api 下的文件名(不含后缀名) - -    Returns: -        dict, 该 API 的内容。 -    """ -    path = os.path.abspath( -        os.path.join(os.path.dirname(__file__), f"{field.lower()}.json") -    ) -    if os.path.exists(path): -        with open(path, encoding="utf8") as f: -            return json.loads(f.read()) -    else: -        return dict() - - -API: dict = get_api("user") - - -def get_session(): -    """ -    获取当前模块的 aiohttp.ClientSession 对象,用于自定义请求 - -    Returns: -        aiohttp.ClientSession -    """ -    loop = asyncio.get_event_loop() -    session = __session_pool.get(loop, None) -    if session is None: -        session = aiohttp.ClientSession(loop=loop) -        __session_pool[loop] = session - -    return session - - -async def bilibili_request( -    method: str, -    url: str, -    params: dict = dict(), -    data: Any = None, -    no_csrf: bool = False, -    json_body: bool = False, -    **kwargs, -) -> dict: -    """ -    向接口发送请求。 - -    Args: -        method     (str)                 : 请求方法。 -        url        (str)                 : 请求 URL。 -        params     (dict, optional)      : 请求参数。 -        data       (Any, optional)       : 请求载荷。 -        no_csrf    (bool, optional)      : 不要自动添加 CSRF。 -        json_body (bool, optional) 载荷是否为 JSON - -    Returns: -        接口未返回数据时,返回 None,否则返回该接口提供的 data 或 result 字段的数据。 -    """ - -    method = method.upper() - -    # 使用 Referer 和 UA 请求头以绕过反爬虫机制 -    DEFAULT_HEADERS = { -        "Referer": "https://www.bilibili.com", -        "User-Agent": "Mozilla/5.0", -    } -    headers = DEFAULT_HEADERS +from nonebot.adapters.onebot.v11 import MessageSegment +from nonebot.adapters.onebot.v11 import GROUP_OWNER, GROUP_ADMIN -    if params is None: -        params = {} - -    # 自动添加 csrf -    if not no_csrf and method in ["POST", "DELETE", "PATCH"]: -        if data is None: -            data = {} -        data["csrf"] = "" -        data["csrf_token"] = "" - -    # jsonp - -    if params.get("jsonp", "") == "jsonp": -        params["callback"] = "callback" +from ATRI.service import Service +from ATRI.rule import is_in_service +from ATRI.utils import timestamp2datetime +from ATRI.exceptions import BilibiliDynamicError -    config = { -        "method": method, -        "url": url, -        "params": params, -        "data": data, -        "headers": headers, -        "cookies": "", -    } +from .database import DB +from .api import API -    config.update(kwargs) -    if json_body: -        config["headers"]["Content-Type"] = "application/json" -        config["data"] = json.dumps(config["data"]) +_OUTPUT_FORMAT = """ +{up_nickname} 的{up_dy_type}更新了! +{up_dy_content} +{up_dy_media} +链接: {up_dy_link} +""".strip() -    session = get_session() -    async with session.request(**config) as resp: +class BilibiliDynamicSubscriptor(Service): +    def __init__(self): +        Service.__init__( +            self, +            "b站动态订阅", +            "b站动态订阅助手~", +            rule=is_in_service("b站动态订阅"), +            permission=GROUP_OWNER | GROUP_ADMIN, +            main_cmd="/bd", +        ) -        # 检查状态码 +    async def add_sub(self, uid: int, group_id: int):          try: -            resp.raise_for_status() -        except aiohttp.ClientResponseError as e: -            raise Exception(e.message) - -        # 检查响应头 Content-Length -        content_length = resp.headers.get("content-length") -        if content_length and int(content_length) == 0: -            return dict() +            async with DB() as db: +                await db.add_sub(uid, group_id) +        except BilibiliDynamicError: +            raise BilibiliDynamicError("添加订阅失败") -        # 检查响应头 Content-Type -        content_type = resp.headers.get("content-type") - -        # 不是 application/json -        if content_type.lower().index("application/json") == -1:  # type: ignore -            raise Exception("响应不是 application/json 类型") - -        raw_data = await resp.text() -        resp_data: dict = dict() +    async def update_sub(self, uid: int, update_map: dict): +        try: +            async with DB() as db: +                await db.update_sub(uid, update_map) +        except BilibiliDynamicError: +            BilibiliDynamicError("更新订阅失败") -        if "callback" in params: -            # JSONP 请求 -            resp_data = json.loads(re.match("^.*?({.*}).*$", raw_data, re.S).group(1))  # type: ignore +    async def del_sub(self, uid: int, group_id: int): +        try: +            async with DB() as db: +                await db.del_sub({"uid": uid, "group_id": group_id}) +        except BilibiliDynamicError: +            raise BilibiliDynamicError("删除订阅失败") + +    async def get_sub_list(self, uid: int = int(), group_id: int = int()) -> list: +        if not uid: +            query_map = {"group_id": group_id}          else: -            # JSON -            resp_data = json.loads(raw_data) - -        # 检查 code -        code = resp_data.get("code", None) - -        if code is None: -            raise Exception("API 返回数据未含 code 字段") - -        if code != 0: -            msg = resp_data.get("msg", None) -            if msg is None: -                msg = resp_data.get("message", None) -            if msg is None: -                msg = "接口未返回错误信息" -            raise Exception(msg) - -        real_data = resp_data.get("data", None) -        if real_data is None: -            real_data = resp_data.get("result", None) -        return real_data - +            query_map = {"uid": uid, "group_id": group_id} -class User: -    """ -    b站用户相关 -    """ - -    def __init__(self, uid: int): -        """ -        Args: -            uid        (int)                 : 用户 UID -        """ -        self.uid = uid - -        self.__self_info = None  # 暂时无用 - -    async def get_user_info(self) -> dict: -        """ -        获取用户信息(昵称,性别,生日,签名,头像 URL,空间横幅 URL 等) - -        Returns: -            dict: 调用接口返回的内容。 -        """ -        api = API["info"]["info"] -        params = {"mid": self.uid} -        return await bilibili_request("GET", url=api["url"], params=params) - -    async def get_dynamics(self, offset: int = 0, need_top: bool = False): -        """ -        获取用户动态。 - -        Args: -            offset (str, optional):     该值为第一次调用本方法时,数据中会有个 next_offset 字段, -                                        指向下一动态列表第一条动态(类似单向链表)。 -                                        根据上一次获取结果中的 next_offset 字段值, -                                        循环填充该值即可获取到全部动态。 -                                        0 为从头开始。 -                                        Defaults to 0. -            need_top (bool, optional):  显示置顶动态. Defaults to False. +        try: +            async with DB() as db: +                return await db.get_sub_list(query_map) +        except BilibiliDynamicError: +            raise BilibiliDynamicError("获取订阅列表失败") -        Returns: -            dict: 调用接口返回的内容。 -        """ -        api = API["info"]["dynamic"] -        params = { -            "host_uid": self.uid, -            "offset_dynamic_id": offset, -            "need_top": 1 if need_top else 0, -        } -        data: dict = await bilibili_request("GET", url=api["url"], params=params) -        # card 字段自动转换成 JSON。 +    async def get_all_subs(self) -> list: +        try: +            async with DB() as db: +                return await db.get_all_subs() +        except BilibiliDynamicError: +            raise BilibiliDynamicError("获取全部订阅列表失败") + +    async def get_up_nickname(self, uid: int) -> str: +        api = API(uid) +        resp = await api.get_user_info() +        data = resp.get("data", dict()) +        return data.get("name", "unknown") + +    async def get_up_recent_dynamic(self, uid: int) -> dict: +        api = API(uid) +        resp = await api.get_user_dynamics() +        data = resp.get("data", dict())          if "cards" in data:              for card in data["cards"]:                  card["card"] = json.loads(card["card"])                  card["extend_json"] = json.loads(card["extend_json"])          return data - -class BilibiliDynamicSubscriptor(Service): -    def __init__(self): -        Service.__init__(self, "b站动态订阅", "b站订阅动态助手", rule=is_in_service("b站动态订阅")) - -    async def add_subscription(self, uid: int, groupid: int) -> bool: -        async with DB() as db: -            res = await db.add_subscription(uid=uid, groupid=groupid) -            return res - -    async def remove_subscription(self, uid: int, groupid: int) -> bool: -        async with DB() as db: -            res = await db.remove_subscription( -                query_map={"uid": uid, "groupid": groupid} -            ) -            return res - -    async def get_subscriptions(self, query_map: dict) -> list: -        async with DB() as db: -            res = await db.get_subscriptions(query_map=query_map) -            return res - -    async def update_subscription_by_uid(self, uid: int, update_map: dict) -> bool: -        async with DB() as db: -            res = await db.update_subscriptions_by_uid(uid=uid, update_map=update_map) -            return res - -    async def get_all_subscriptions(self) -> list: -        async with DB() as db: -            res = await db.get_all_subscriptions() -            return res - -    # bilibili network function - -    async def get_upname_by_uid(self, uid: int) -> str: -        try: -            u = User(uid) -            info: dict = await u.get_user_info() -            return info.get("name") -        except: -            return "" - -    async def get_recent_dynamic_by_uid(self, uid: int) -> dict: -        try: -            u = User(uid) -            info = await u.get_dynamics() -            return info -        except: -            return {} - -    def extract_dynamics_detail(self, dynamic_list: list) -> list: -        import time - -        ret = [] -        for d in dynamic_list: +    def extract_dyanmic(self, data: list) -> list: +        result = list() +        for i in data:              pattern = {} -            desc = d["desc"] -            card = d["card"] +            desc = i["desc"] +            card = i["card"]              type = desc["type"]              # common 部分 @@ -292,11 +105,11 @@ class BilibiliDynamicSubscriptor(Service):              pattern["dynamic_id"] = desc["dynamic_id"]              pattern["timestamp"] = desc["timestamp"]              pattern["time"] = timestamp2datetime(desc["timestamp"]) -            pattern["type_zh"] = "" +            pattern["type_zh"] = str()              # alternative 部分 -            pattern["content"] = "" -            pattern["pic"] = "" +            pattern["content"] = str() +            pattern["pic"] = str()              # 根据type区分 提取content              if type == 1:  # 转发动态 @@ -329,19 +142,25 @@ class BilibiliDynamicSubscriptor(Service):                  if len(card["image_urls"]) > 0:                      pattern["pic"] = card["image_urls"][0] -            ret.append(pattern) -        ret = sorted(ret, key=itemgetter("timestamp")) -        return ret +            result.append(pattern) +        return sorted(result, key=itemgetter("timestamp")) + +    def gen_output(self, data: dict, limit_content: int = 100) -> str: +        """生成动态信息 -    def generate_output(self, pattern: dict) -> tuple: -        # 限制摘要的字数 -        abstractLimit = 40 -        text_part = """【UP名称】{name}\n【动态类型】{dynamic_type}\n【时间】{time}\n【内容摘要】{content}\n【链接】{url}\n""".format( -            name=pattern["name"], -            dynamic_type=pattern["type_zh"], -            time=pattern["time"], -            content=pattern["content"][:abstractLimit], -            url="https://t.bilibili.com/" + str(pattern["dynamic_id"]), +        Args: +            data (dict): dict形式的动态数据. +            limit_content (int, optional): 内容字数限制. 默认 100. + +        Returns: +            str: 动态信息 +        """ +        return _OUTPUT_FORMAT.format( +            up_nickname=data["name"], +            up_dy_type=data["type_zh"], +            up_dy_content=str(data["content"][:limit_content] + "...") +            .replace("https://", str()) +            .replace("http://", str()), +            up_dy_media=MessageSegment.image(data["pic"]) if data.get("pic") else str(), +            up_dy_link="https://t.bilibili.com/" + str(data["dynamic_id"]),          ) -        pic_part = pattern["pic"] -        return text_part, pic_part diff --git a/ATRI/plugins/bilibili_dynamic/database/__init__.py b/ATRI/plugins/bilibili_dynamic/database/__init__.py new file mode 100644 index 0000000..6840881 --- /dev/null +++ b/ATRI/plugins/bilibili_dynamic/database/__init__.py @@ -0,0 +1 @@ +from .db import DB diff --git a/ATRI/plugins/bilibili_dynamic/database/db.py b/ATRI/plugins/bilibili_dynamic/database/db.py new file mode 100644 index 0000000..37ed223 --- /dev/null +++ b/ATRI/plugins/bilibili_dynamic/database/db.py @@ -0,0 +1,49 @@ +from pathlib import Path +from tortoise import Tortoise + +from ATRI import driver +from .models import Subscription + + +DB_DIR = Path(".") / "data" / "database" / "bilibili_dynamic" +DB_DIR.mkdir(parents=True, exist_ok=True) + + +class DB: +    async def __aenter__(self): +        return self + +    async def __aexit__(self, exc_type, exc_val, exc_tb): +        pass + +    async def init(self): +        from . import models + +        await Tortoise.init( +            db_url=f"sqlite://{DB_DIR}/db.sqlite3", +            modules={"models": [locals()["models"]]}, +        ) +        await Tortoise.generate_schemas() + +    async def add_sub(self, uid: int, group_id: int): +        await Subscription.create(uid=uid, group_id=group_id) + +    async def update_sub(self, uid: int, update_map: dict): +        await Subscription.filter(uid=uid).update(**update_map) + +    async def del_sub(self, query_map: dict): +        await Subscription.filter(**query_map).delete() + +    async def get_sub_list(self, query_map: dict) -> list: +        return await Subscription.filter(**query_map) + +    async def get_all_subs(self) -> list: +        return await Subscription.all() + + +async def init(): +    async with DB() as db: +        await db.init() + + +driver().on_startup(init) diff --git a/ATRI/plugins/bilibili_dynamic/database/models.py b/ATRI/plugins/bilibili_dynamic/database/models.py new file mode 100644 index 0000000..d0cb3dd --- /dev/null +++ b/ATRI/plugins/bilibili_dynamic/database/models.py @@ -0,0 +1,14 @@ +from datetime import datetime + +from tortoise import fields +from tortoise.models import Model + + +class Subscription(Model): +    uid = fields.IntField() +    group_id = fields.IntField() +    up_nickname = fields.TextField(null=True) +    last_update = fields.DatetimeField(default=datetime.fromordinal(1)) + +    def __str__(self) -> str: +        return f"[{self.uid}|{self.group_id}|{self.up_nickname}|{self.last_update}]" diff --git a/ATRI/plugins/bilibili_dynamic/user.json b/ATRI/plugins/bilibili_dynamic/user.json deleted file mode 100644 index ff4f49b..0000000 --- a/ATRI/plugins/bilibili_dynamic/user.json +++ /dev/null @@ -1,244 +0,0 @@ -{ -  "info": { -    "my_info": { -      "url": "https://api.bilibili.com/x/space/myinfo", -      "method": "GET", -      "verify": true, -      "comment": "获取自己的信息" -    }, -    "info": { -      "url": "https://api.bilibili.com/x/space/acc/info", -      "method": "GET", -      "verify": false, -      "params": { -        "mid": "int: uid" -      }, -      "comment": "用户基本信息" -    }, -    "relation": { -      "url": "https://api.bilibili.com/x/relation/stat", -      "method": "GET", -      "verify": false, -      "params": { -        "vmid": "int: uid" -      }, -      "comment": "关注数,粉丝数" -    }, -    "upstat": { -      "url": "https://api.bilibili.com/x/space/upstat", -      "method": "GET", -      "verify": false, -      "params": { -        "mid": "int: uid" -      }, -      "comment": "视频播放量,文章阅读量,总点赞数" -    }, -    "live": { -      "url": "https://api.bilibili.com/x/space/acc/info", -      "method": "GET", -      "verify": false, -      "params": { -        "mid": "int: uid" -      }, -      "comment": "直播间基本信息" -    }, -    "video": { -      "url": "https://api.bilibili.com/x/space/arc/search", -      "method": "GET", -      "verify": false, -      "params": { -        "mid": "int: uid", -        "ps": "const int: 30", -        "tid": "int: 分区 ID,0 表示全部", -        "pn": "int: 页码", -        "keyword": "str: 关键词,可为空", -        "order": "str: pubdate 上传日期,pubdate 播放量,pubdate 收藏量" -      }, -      "comment": "搜索用户视频" -    }, -    "audio": { -      "url": "https://api.bilibili.com/audio/music-service/web/song/upper", -      "method": "GET", -      "verify": false, -      "params": { -        "uid": "int: uid", -        "ps": "const int: 30", -        "pn": "int: 页码", -        "order": "int: 1 最新发布,2 最多播放,3 最多收藏" -      }, -      "comment": "音频" -    }, -    "article": { -      "url": "https://api.bilibili.com/x/space/article", -      "method": "GET", -      "verify": false, -      "params": { -        "mid": "int: uid", -        "ps": "const int: 30", -        "pn": "int: 页码", -        "sort": "str: publish_time 最新发布,publish_time 最多阅读,publish_time 最多收藏" -      }, -      "comment": "专栏" -    }, -    "article_lists": { -      "url": "https://api.bilibili.com/x/article/up/lists", -      "method": "GET", -      "verify": false, -      "params": { -        "mid": "int: uid", -        "sort": "int: 0 最近更新,1 最多阅读" -      }, -      "comment": "专栏文集" -    }, -    "dynamic": { -      "url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history", -      "method": "GET", -      "verify": false, -      "params": { -        "host_uid": "int: uid", -        "offset_dynamic_id": "int: 动态偏移用,第一页为 0", -        "need_top": "int bool: 是否显示置顶动态" -      }, -      "comment": "用户动态信息" -    }, -    "bangumi": { -      "url": "https://api.bilibili.com/x/space/bangumi/follow/list", -      "method": "GET", -      "verify": false, -      "params": { -        "vmid": "int: uid", -        "pn": "int: 页码", -        "ps": "const int: 15", -        "type": "int: 1 追番,2 追剧" -      }, -      "comment": "用户追番列表" -    }, -    "followings": { -      "url": "https://api.bilibili.com/x/relation/followings", -      "method": "GET", -      "verify": true, -      "params": { -        "vmid": "int: uid", -        "ps": "const int: 20", -        "pn": "int: 页码", -        "order": "str: desc 倒序, asc 正序" -      }, -      "comment": "获取用户关注列表(不是自己只能访问前 5 页)" -    }, -    "followers": { -      "url": "https://api.bilibili.com/x/relation/followers", -      "method": "GET", -      "verify": true, -      "params": { -        "vmid": "int: uid", -        "ps": "const int: 20", -        "pn": "int: 页码", -        "order": "str: desc 倒序, asc 正序" -      }, -      "comment": "获取用户粉丝列表(不是自己只能访问前 5 页,是自己也不能获取全部的样子)" -    }, -    "overview": { -      "url": "https://api.bilibili.com/x/space/navnum", -      "method": "GET", -      "verify": false, -      "params": { -        "mid": "int: uid", -        "jsonp": "const str: jsonp" -      }, -      "comment": "获取用户的简易订阅和投稿信息(主要是这些的数量统计)" -    }, -    "self_subscribe_group": { -      "url": "https://api.bilibili.com/x/relation/tags", -      "method": "GET", -      "verify": true, -      "params": {}, -      "comment": "获取自己的关注分组列表,用于操作关注" -    }, -    "get_user_in_which_subscribe_groups": { -      "url": "https://api.bilibili.com/x/relation/tag/user", -      "method": "GET", -      "verify": true, -      "params": { -        "fid": "int: uid" -      }, -      "comment": "获取用户在哪一个分组" -    }, -    "history": { -      "url": "https://api.bilibili.com/x/v2/history", -      "method": "GET", -      "verify": true, -      "params": { -        "pn": "int: 页码", -        "ps": "const int: 100" -      }, -      "comment": "用户浏览历史记录" -    } -  }, -  "operate": { -    "modify": { -      "url": "https://api.bilibili.com/x/relation/modify", -      "method": "POST", -      "verify": true, -      "data": { -        "fid": "int: UID", -        "act": "int: 1 关注 2 取关 3 悄悄关注 5 拉黑 6 取消拉黑 7 移除粉丝", -        "re_src": "const int: 11" -      }, -      "comment": "用户关系操作" -    }, -    "send_msg": { -      "url": "https://api.vc.bilibili.com/web_im/v1/web_im/send_msg", -      "method": "POST", -      "verify": true, -      "data": { -        "msg[sender_uid]": "int: 自己的 UID", -        "msg[receiver_id]": "int: 对方 UID", -        "msg[receiver_type]": "const int: 1", -        "msg[msg_type]": "const int: 1", -        "msg[msg_status]": "const int: 0", -        "msg[content]": { -          "content": "str: 文本内容" -        } -      }, -      "comment": "给用户发信息" -    }, -    "create_subscribe_group": { -      "url": "https://api.bilibili.com/x/relation/tag/create", -      "method": "POST", -      "verify": true, -      "data": { -        "tag": "str: 分组名" -      }, -      "comment": "添加关注分组" -    }, -    "del_subscribe_group": { -      "url": "https://api.bilibili.com/x/relation/tag/del", -      "method": "POST", -      "verify": true, -      "data": { -        "tagid": "int: 分组 id" -      }, -      "comment": "删除关注分组" -    }, -    "rename_subscribe_group": { -      "url": "https://api.bilibili.com/x/relation/tag/update", -      "method": "POST", -      "verify": true, -      "data": { -        "tagid": "int: 分组 id", -        "name": "str: 新的分组名" -      }, -      "comment": "重命名分组" -    }, -    "set_user_subscribe_group": { -      "url": "https://api.bilibili.com/x/relation/tags/addUsers", -      "method": "POST", -      "verify": true, -      "data": { -        "fids": "int: UID", -        "tagids": "commaSeparatedList[int]: 分组的 tagids,逗号分隔" -      }, -      "comment": "移动用户到关注分组" -    } -  } -}
\ No newline at end of file | 
