summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ATRI/plugins/bilibili_dynamic/__init__.py287
-rw-r--r--ATRI/plugins/bilibili_dynamic/api.py36
-rw-r--r--ATRI/plugins/bilibili_dynamic/data_source.py373
-rw-r--r--ATRI/plugins/bilibili_dynamic/database/__init__.py1
-rw-r--r--ATRI/plugins/bilibili_dynamic/database/db.py49
-rw-r--r--ATRI/plugins/bilibili_dynamic/database/models.py14
-rw-r--r--ATRI/plugins/bilibili_dynamic/user.json244
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