diff options
author | Kyomotoi <[email protected]> | 2022-06-13 19:37:34 +0800 |
---|---|---|
committer | Kyomotoi <[email protected]> | 2022-06-13 19:37:34 +0800 |
commit | 59c923bbc3e91ad5a3482ec81c9192297ebbebe8 (patch) | |
tree | d74da226542a8b6f54200bd6d55e8d459315c71d | |
parent | 4a876e2e64a690dcf01c385e273052e1e22caacd (diff) | |
download | ATRI-59c923bbc3e91ad5a3482ec81c9192297ebbebe8.tar.gz ATRI-59c923bbc3e91ad5a3482ec81c9192297ebbebe8.tar.bz2 ATRI-59c923bbc3e91ad5a3482ec81c9192297ebbebe8.zip |
♻️ 重构插件: b站动态订阅
-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 |