butterbot-python 3.1.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. butterbot/__init__.py +15 -0
  2. butterbot/app/__init__.py +41 -0
  3. butterbot/app/bot_app.py +374 -0
  4. butterbot/app/config.py +156 -0
  5. butterbot/app/source_manager.py +367 -0
  6. butterbot/core/__init__.py +46 -0
  7. butterbot/core/api/__init__.py +9 -0
  8. butterbot/core/api/base_api.py +32 -0
  9. butterbot/core/context/README.md +1 -0
  10. butterbot/core/context/__init__.py +9 -0
  11. butterbot/core/context/api_registry.py +109 -0
  12. butterbot/core/context/app_context.py +51 -0
  13. butterbot/core/context/config_provider.py +14 -0
  14. butterbot/core/data/__init__.py +15 -0
  15. butterbot/core/data/base_data.py +29 -0
  16. butterbot/core/data/base_model.py +197 -0
  17. butterbot/core/event/README.md +1 -0
  18. butterbot/core/event/__init__.py +10 -0
  19. butterbot/core/event/event.py +27 -0
  20. butterbot/core/event/event_bus.py +276 -0
  21. butterbot/core/event/subscriber.py +130 -0
  22. butterbot/core/exceptions.py +90 -0
  23. butterbot/core/filter/__init__.py +11 -0
  24. butterbot/core/filter/base_filter.py +74 -0
  25. butterbot/core/source/README.md +1 -0
  26. butterbot/core/source/__init__.py +9 -0
  27. butterbot/core/source/base_source.py +134 -0
  28. butterbot/core/types/__init__.py +9 -0
  29. butterbot/core/types/base_type.py +74 -0
  30. butterbot/sources/__init__.py +1 -0
  31. butterbot/sources/bilibili/README.md +10 -0
  32. butterbot/sources/bilibili/__init__.py +20 -0
  33. butterbot/sources/bilibili/api/__init__.py +5 -0
  34. butterbot/sources/bilibili/api/bili_api.py +123 -0
  35. butterbot/sources/bilibili/data/__init__.py +52 -0
  36. butterbot/sources/bilibili/data/danmaku_gift_data.py +135 -0
  37. butterbot/sources/bilibili/data/danmaku_guard_data.py +54 -0
  38. butterbot/sources/bilibili/data/danmaku_msg_data.py +71 -0
  39. butterbot/sources/bilibili/data/dto/__init__.py +59 -0
  40. butterbot/sources/bilibili/data/dto/danmaku_gift_dto.py +193 -0
  41. butterbot/sources/bilibili/data/dto/danmaku_guard_buy_dto.py +54 -0
  42. butterbot/sources/bilibili/data/dto/danmaku_msg_dto.py +123 -0
  43. butterbot/sources/bilibili/data/dto/dynamic_dto.py +276 -0
  44. butterbot/sources/bilibili/data/dto/live_room_dto.py +169 -0
  45. butterbot/sources/bilibili/data/dto/video_part_dto.py +18 -0
  46. butterbot/sources/bilibili/data/dynamic_data.py +362 -0
  47. butterbot/sources/bilibili/data/live_room_data.py +162 -0
  48. butterbot/sources/bilibili/data/video_part.py +46 -0
  49. butterbot/sources/bilibili/source/__init__.py +15 -0
  50. butterbot/sources/bilibili/source/base_polling_source.py +130 -0
  51. butterbot/sources/bilibili/source/bili_danmaku_source.py +230 -0
  52. butterbot/sources/bilibili/source/bili_dynamic_source.py +135 -0
  53. butterbot/sources/bilibili/source/bili_live_source.py +137 -0
  54. butterbot/sources/bilibili/types/__init__.py +11 -0
  55. butterbot/sources/bilibili/types/bili_type.py +32 -0
  56. butterbot/sources/napcat/README.md +10 -0
  57. butterbot/sources/napcat/__init__.py +20 -0
  58. butterbot/sources/napcat/api/__init__.py +3 -0
  59. butterbot/sources/napcat/api/napcat_api.py +316 -0
  60. butterbot/sources/napcat/data/__init__.py +179 -0
  61. butterbot/sources/napcat/data/event_data.py +441 -0
  62. butterbot/sources/napcat/data/segment_data.py +432 -0
  63. butterbot/sources/napcat/events.py +91 -0
  64. butterbot/sources/napcat/filters/__init__.py +19 -0
  65. butterbot/sources/napcat/filters/filters.py +202 -0
  66. butterbot/sources/napcat/source/__init__.py +5 -0
  67. butterbot/sources/napcat/source/napcat_source.py +58 -0
  68. butterbot/sources/napcat/types/__init__.py +5 -0
  69. butterbot/sources/napcat/types/napcat_type.py +61 -0
  70. butterbot/utils/README.md +15 -0
  71. butterbot/utils/__init__.py +11 -0
  72. butterbot/utils/data_pair.py +27 -0
  73. butterbot/utils/logging_config.py +521 -0
  74. butterbot/utils/terminal.py +308 -0
  75. butterbot/utils/websocket.py +1270 -0
  76. butterbot_python-3.1.0.dev1.dist-info/METADATA +769 -0
  77. butterbot_python-3.1.0.dev1.dist-info/RECORD +79 -0
  78. butterbot_python-3.1.0.dev1.dist-info/WHEEL +4 -0
  79. butterbot_python-3.1.0.dev1.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,90 @@
1
+ """框架异常层级.
2
+
3
+ 框架主动抛出的异常都继承 :class:`ButterError`,用户代码既可以用一个
4
+ ``except ButterError`` 兜住框架内部的全部错误,也可以按子类精确区分错误来源::
5
+
6
+ from butterbot.app import ButterError, ConfigError
7
+
8
+ try:
9
+ app = BotApp()
10
+ app.run()
11
+ except ConfigError as e:
12
+ print("配置有问题:", e)
13
+ except ButterError as e:
14
+ print("框架错误:", e)
15
+
16
+ 部分异常同时继承内置异常(如 :class:`ConfigError` 继承 ``ValueError``),
17
+ 这样既能被新的类型精确捕获,也不会让原本 ``except ValueError`` 的调用方漏掉。
18
+ """
19
+
20
+ from collections.abc import Mapping
21
+
22
+
23
+ class ButterError(Exception):
24
+ """butterbot 所有框架异常的基类."""
25
+
26
+
27
+ class ConfigError(ButterError, ValueError):
28
+ """配置错误.
29
+
30
+ 触发场景:配置文件格式非法、配置项构建失败、
31
+ 以及事件源/API 所需的配置键缺失。
32
+ """
33
+
34
+
35
+ class LifecycleError(ButterError, RuntimeError):
36
+ """生命周期状态错误.
37
+
38
+ 触发场景:在已关闭的 ``SourceManager`` 上添加事件源、
39
+ 在已关闭的 ``EventBus`` 上发布事件等。
40
+ """
41
+
42
+
43
+ class SourceError(ButterError):
44
+ """事件源相关错误的基类."""
45
+
46
+
47
+ class SourceStartError(SourceError):
48
+ """一个或多个事件源启动失败.
49
+
50
+ 由 ``SourceManager.start()`` 在批量启动结束后抛出。抛出前所有已成功启动的
51
+ 事件源都已被回滚(stop),因此捕获到该异常时应用处于"未启动"状态。
52
+
53
+ Attributes:
54
+ failures: ``{事件源描述: 原始异常}``,保留每个失败源的真实异常对象
55
+ """
56
+
57
+ def __init__(self, failures: Mapping[str, BaseException]) -> None:
58
+ """初始化.
59
+
60
+ Args:
61
+ failures: 事件源描述到原始异常的映射
62
+ """
63
+ self.failures: dict[str, BaseException] = dict(failures)
64
+ detail = "; ".join(
65
+ "%s: %s" % (name, exc) for name, exc in self.failures.items()
66
+ )
67
+ super().__init__("以下事件源启动失败: %s" % detail)
68
+
69
+
70
+ class ApiError(ButterError):
71
+ """API 构建或调用错误."""
72
+
73
+
74
+ class SubscriptionError(ButterError, ValueError):
75
+ """订阅注册错误.
76
+
77
+ 触发场景:订阅规则在事件源声明的 ``supported_types`` 中无任何匹配的具体状态
78
+ —— 这种订阅永远不会被触发,绝大多数情况下是状态值或正则写错了。
79
+ """
80
+
81
+
82
+ __all__ = [
83
+ "ApiError",
84
+ "ButterError",
85
+ "ConfigError",
86
+ "LifecycleError",
87
+ "SourceError",
88
+ "SourceStartError",
89
+ "SubscriptionError",
90
+ ]
@@ -0,0 +1,11 @@
1
+ from .base_filter import (
2
+ AndFilter,
3
+ BaseFilter,
4
+ OrFilter,
5
+ )
6
+
7
+ __all__ = [
8
+ "AndFilter",
9
+ "BaseFilter",
10
+ "OrFilter",
11
+ ]
@@ -0,0 +1,74 @@
1
+ from abc import ABC, abstractmethod
2
+ from logging import getLogger
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from butterbot.core.event import Event
7
+
8
+ _log = getLogger(__name__)
9
+
10
+
11
+ class BaseFilter(ABC):
12
+ """过滤器基类."""
13
+
14
+ filters: Any
15
+
16
+ @abstractmethod
17
+ def check(self, event: "Event") -> bool:
18
+ """检查事件是否通过过滤器
19
+
20
+ Args:
21
+ event: 消息事件
22
+
23
+ Returns:
24
+ bool: True 表示通过过滤器,False 表示被拦截
25
+ """
26
+ pass
27
+
28
+ def __repr__(self) -> str:
29
+ return "<%s filters=%s>" % (self.__class__.__name__, self.filters)
30
+
31
+ def __str__(self) -> str:
32
+ return self.__repr__()
33
+
34
+ def __and__(self, other: "BaseFilter") -> "BaseFilter":
35
+ """使用 & 合并过滤器, 并支持链式调用, 处理顺序为传入顺序."""
36
+ return AndFilter(self, other)
37
+
38
+ def __or__(self, other: "BaseFilter") -> "BaseFilter":
39
+ """使用 | 合并过滤器, 并支持链式调用, 处理顺序为传入顺序."""
40
+ return OrFilter(self, other)
41
+
42
+
43
+ class AndFilter(BaseFilter):
44
+ """与过滤器."""
45
+
46
+ def __init__(self, *filters: BaseFilter):
47
+ self.filters = list(filters)
48
+
49
+ def check(self, event: "Event") -> bool:
50
+ """检查事件是否通过所有过滤器."""
51
+ for f in self.filters:
52
+ if not f.check(event):
53
+ _log.debug(
54
+ "事件%s被过滤器参数 %s 拦截, 不再继续检查", event.id, f.filters
55
+ )
56
+ return False
57
+ _log.debug("事件%s通过所有%s与过滤器", event.id, self)
58
+ return True
59
+
60
+
61
+ class OrFilter(BaseFilter):
62
+ """或过滤器."""
63
+
64
+ def __init__(self, *filters: BaseFilter):
65
+ self.filters = list(filters)
66
+
67
+ def check(self, event: "Event") -> bool:
68
+ """检查事件是否通过任一过滤器."""
69
+ for f in self.filters:
70
+ if f.check(event):
71
+ _log.debug("事件%s通过过滤器参数 %s, 不再继续检查", event.id, f.filters)
72
+ return True
73
+ _log.debug("事件%s未通过%s或过滤器", event.id, self)
74
+ return False
@@ -0,0 +1 @@
1
+ --- 待施工 ---
@@ -0,0 +1,9 @@
1
+ from .base_source import (
2
+ BaseSource,
3
+ BaseSourceT,
4
+ )
5
+
6
+ __all__ = [
7
+ "BaseSource",
8
+ "BaseSourceT",
9
+ ]
@@ -0,0 +1,134 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TYPE_CHECKING, ClassVar, TypeVar
3
+ from uuid import UUID, uuid4
4
+
5
+ if TYPE_CHECKING:
6
+ from butterbot.core.context import AppContext
7
+ from butterbot.core.types import BaseType
8
+
9
+
10
+ class BaseSource(ABC):
11
+ """事件源基类.
12
+
13
+ Source 负责:
14
+ - 产生事件数据
15
+ - 通过 EventBus 发布事件
16
+ - 管理自己的生命周期(start/stop)
17
+
18
+ 子类只需实现 :meth:`on_start` 和 :meth:`on_stop`,
19
+ 无需手动管理 ``self.running`` 状态——
20
+ :meth:`start` 和 :meth:`stop` 已自动处理。
21
+
22
+ Attributes:
23
+ uuid: 唯一标识符,由 SourceManager 内部管理
24
+ running: 运行状态(由 start/stop 自动管理,子类不应直接修改)
25
+ config_key: 配置键,子类可覆盖此类属性作为默认值
26
+ supported_types: 事件源支持的 ``BaseType`` 枚举类,用于订阅规则编译
27
+ """
28
+
29
+ config_key: str = ""
30
+
31
+ # 事件源支持的 BaseType 枚举类。子类必须覆盖此属性,
32
+ # 例如 supported_types = DynamicType。
33
+ # 未声明时订阅将抛出 TypeError。
34
+ supported_types: ClassVar[type["BaseType"] | None] = None
35
+
36
+ def __init_subclass__(cls) -> None:
37
+ """子类初始化检查."""
38
+ super().__init_subclass__()
39
+
40
+ if cls.__name__ == "BaseSource":
41
+ return
42
+
43
+ if not hasattr(cls, "supported_types"):
44
+ raise TypeError(f"{cls.__name__} must define supported_types")
45
+
46
+ def __init__(
47
+ self,
48
+ uuid: UUID | None = None,
49
+ *,
50
+ config_key: str | None = None,
51
+ ) -> None:
52
+ """初始化事件源.
53
+
54
+ Args:
55
+ uuid: 可选,指定 UUID,默认自动生成
56
+ config_key: 可选,覆盖类级默认配置键
57
+ """
58
+ self.uuid: UUID = uuid or uuid4()
59
+ self.running: bool = False
60
+ self._ctx: "AppContext | None" = None
61
+ if config_key is not None:
62
+ self.config_key = config_key
63
+
64
+ async def start(self) -> None:
65
+ """启动事件源(模板方法).
66
+
67
+ 自动管理 ``running`` 状态,然后委托给 :meth:`on_start`。
68
+ 子类不应重写此方法,应实现 :meth:`on_start`。
69
+
70
+ :meth:`on_start` 抛出异常(含 ``CancelledError``)时,
71
+ ``running`` 会回滚为 ``False`` 后再向上传播——否则启动失败的事件源
72
+ 会以"运行中"的假象留在应用里,而它的资源其实从未建立起来。
73
+
74
+ Raises:
75
+ Exception: :meth:`on_start` 抛出的任何异常,原样向上传播
76
+ """
77
+ if self.running:
78
+ return
79
+ self.running = True
80
+ try:
81
+ await self.on_start()
82
+ except BaseException:
83
+ self.running = False
84
+ raise
85
+
86
+ async def stop(self) -> None:
87
+ """停止事件源(模板方法).
88
+
89
+ 自动管理 ``running`` 状态,然后委托给 :meth:`on_stop`。
90
+ 子类不应重写此方法,应实现 :meth:`on_stop`。
91
+ """
92
+ if not self.running:
93
+ return
94
+ self.running = False
95
+ await self.on_stop()
96
+
97
+ @abstractmethod
98
+ async def on_start(self) -> None:
99
+ """子类实现:事件源启动逻辑.
100
+
101
+ 在 :meth:`start` 中被调用,此时 ``self.running`` 已为 ``True``。
102
+ """
103
+
104
+ @abstractmethod
105
+ async def on_stop(self) -> None:
106
+ """子类实现:事件源停止逻辑.
107
+
108
+ 在 :meth:`stop` 中被调用,此时 ``self.running`` 已为 ``False``。
109
+ """
110
+
111
+ def bind(self, ctx: "AppContext") -> None:
112
+ """绑定应用上下文.
113
+
114
+ 由 SourceManager 在启动时调用,注入依赖。
115
+
116
+ Args:
117
+ ctx: 应用上下文
118
+ """
119
+ self._ctx = ctx
120
+
121
+ @property
122
+ def ctx(self) -> "AppContext":
123
+ """获取应用上下文."""
124
+ if self._ctx is None:
125
+ raise RuntimeError("Source 尚未绑定上下文,请先调用 bind()")
126
+ return self._ctx
127
+
128
+ @property
129
+ def is_running(self) -> bool:
130
+ """检查是否正在运行."""
131
+ return self.running
132
+
133
+
134
+ BaseSourceT = TypeVar("BaseSourceT", bound=BaseSource)
@@ -0,0 +1,9 @@
1
+ from .base_type import (
2
+ BaseType,
3
+ BaseTypeT,
4
+ )
5
+
6
+ __all__ = [
7
+ "BaseType",
8
+ "BaseTypeT",
9
+ ]
@@ -0,0 +1,74 @@
1
+ import re
2
+ from enum import Enum
3
+ from typing import TypeVar, Union
4
+
5
+ _BaseTypeSelf = Union[str, re.Pattern[str], "BaseType"]
6
+
7
+
8
+ class BaseType(str, Enum):
9
+ """标签枚举基类,提供通用的匹配方法."""
10
+
11
+ @property
12
+ def scope(self) -> str:
13
+ """返回标签的作用域."""
14
+ return self.value.split(".", 1)[0]
15
+
16
+ @property
17
+ def state(self) -> str:
18
+ """返回标签的状态."""
19
+ return self.value.split(".", 1)[1]
20
+
21
+ def matches(self, rule: _BaseTypeSelf) -> bool:
22
+ """判断状态是否匹配.
23
+
24
+ 支持三种匹配方式:
25
+
26
+ - ``BaseType``: 按枚举类型匹配(需同 type、同 scope)
27
+ - state 为 "all" 时通配同 scope 下所有状态
28
+ - 支持层级匹配:父状态匹配子状态(如 "message" 匹配 "message.group")
29
+ - ``str``: 作为正则表达式,用 ``re.fullmatch`` 与 ``self.value`` 匹配
30
+ - ``re.Pattern[str]``: 编译好的正则对象,直接调用其 ``fullmatch`` 方法
31
+
32
+ Args:
33
+ rule: 状态过滤器(``BaseType`` 枚举,``str`` 正则,或编译好的 ``re.Pattern``)
34
+
35
+ Returns:
36
+ bool: 匹配结果
37
+ """
38
+ if isinstance(rule, BaseType):
39
+ # 枚举匹配:需同 type、同 scope;state="all" 通配;层级父匹配子
40
+ if type(self) is type(rule):
41
+ if self.scope == rule.scope:
42
+ if (
43
+ self.state == rule.state
44
+ or rule.state == "all"
45
+ or self.state.startswith(rule.state + ".")
46
+ ):
47
+ return True
48
+ return False
49
+ return False
50
+ return False
51
+
52
+ if isinstance(rule, re.Pattern):
53
+ return bool(rule.fullmatch(self.value))
54
+
55
+ # str 正则匹配:直接与 self.value 全量匹配
56
+ return bool(re.fullmatch(rule, self.value))
57
+
58
+ @classmethod
59
+ def matching_statuses(cls, rule: _BaseTypeSelf) -> list["BaseType"]:
60
+ """返回当前枚举类中所有匹配给定规则的成员.
61
+
62
+ 遍历当前枚举类的所有成员,筛掉通配标签(state="all"),
63
+ 返回余下成员中能通过 ``matches(rule)`` 的元素列表。
64
+
65
+ Args:
66
+ rule: 状态过滤器(``BaseType`` 枚举、``str`` 或 ``re.Pattern`` 正则)
67
+
68
+ Returns:
69
+ 匹配的枚举成员列表(不含 state="all" 的通配成员)
70
+ """
71
+ return [m for m in cls if m.state != "all" and m.matches(rule)]
72
+
73
+
74
+ BaseTypeT = TypeVar("BaseTypeT", bound=BaseType)
@@ -0,0 +1 @@
1
+ """事件源实现包:每个子包适配一个外部平台(napcat、bilibili 等)."""
@@ -0,0 +1,10 @@
1
+ # Bilibili 适配文档
2
+
3
+ Bilibili 的使用与 API 文档已迁移到统一文档站:
4
+
5
+ - [Bilibili 功能指南](../../../docs/features/bilibili.md)
6
+ - [内置事件源 API](../../../docs/api/builtin-sources.md)
7
+ - [Bilibili 示例](../../../docs/examples/bilibili.md)
8
+
9
+ 本文件保留用于兼容仓库旧链接。新文档以当前三个 Source、`BilibiliApi`、类型、
10
+ 测试和示例为准。
@@ -0,0 +1,20 @@
1
+ from .api import BilibiliApi
2
+ from .source import (
3
+ BiliDanmakuSource,
4
+ BiliDynamicSource,
5
+ BiliLiveSource,
6
+ )
7
+ from .types import DanmakuType, DynamicType, LiveType
8
+
9
+ __all__ = [
10
+ "BiliDanmakuSource",
11
+ # 事件源类
12
+ "BiliDynamicSource",
13
+ "BiliLiveSource",
14
+ # API类
15
+ "BilibiliApi",
16
+ # 数据类型类
17
+ "DanmakuType",
18
+ "DynamicType",
19
+ "LiveType",
20
+ ]
@@ -0,0 +1,5 @@
1
+ from .bili_api import BilibiliApi
2
+
3
+ __all__ = [
4
+ "BilibiliApi",
5
+ ]
@@ -0,0 +1,123 @@
1
+ from logging import getLogger
2
+ from typing import TYPE_CHECKING
3
+
4
+ from bilibili_api import Credential
5
+ from bilibili_api.dynamic import get_dynamic_page_info
6
+ from bilibili_api.live import LiveDanmaku, LiveRoom
7
+ from bilibili_api.user import User
8
+
9
+ from butterbot.core.api import BaseApi
10
+
11
+ from ..data import DynamicData, LiveRoomData, get_max_id
12
+ from ..data.dto import DynamicDTO, LiveRoomDTO
13
+
14
+ if TYPE_CHECKING:
15
+ from butterbot.core.context import ApiRegistry
16
+
17
+ _log = getLogger("BilibiliApi")
18
+
19
+
20
+ class BilibiliApi(BaseApi):
21
+ def __init__(self, credential: Credential | None) -> None:
22
+ """初始化BilibiliApi客户端
23
+ Args:
24
+ credential: Optional[Credential]: B站用户凭证
25
+ """
26
+ self._credential = credential
27
+
28
+ @classmethod
29
+ def create(cls, ctx: "ApiRegistry", config_key: str = "bilibili") -> "BilibiliApi":
30
+ """
31
+ 从上下文创建 BilibiliApi 实例
32
+ Args:
33
+ ctx: API 上下文
34
+ config_key: 配置键
35
+ """
36
+ return cls(ctx.config.get_config(config_key))
37
+
38
+ @property
39
+ def credential(self) -> Credential | None:
40
+ """获取B站用户凭证
41
+ Returns:
42
+ Optional[Credential]: B站用户凭证对象,如果未配置则返回None
43
+ """
44
+ return self._credential
45
+
46
+ async def get_all_dynamic(self, uid: int, offset: str = "") -> list[DynamicData]:
47
+ """
48
+ 获取单页动态
49
+ Args:
50
+ uid (int): 用户uid
51
+ offset (str): 分页偏移参数
52
+ Returns:
53
+ list[DynamicData]: 动态信息对象
54
+ """
55
+ user = User(credential=self._credential, uid=uid)
56
+ dict_info = await user.get_dynamics_new(offset=offset)
57
+ if dict_info.get("items", None) is None or not dict_info.get("items"):
58
+ raise ValueError("未获取到动态数据或者动态数据为不完整")
59
+ info_dto = DynamicDTO.from_list(dict_info["items"])
60
+ info = [DynamicData.from_dto(dto) for dto in info_dto if dto is not None]
61
+ return info
62
+
63
+ async def get_new_dynamic(self, uid: int) -> DynamicData:
64
+ """
65
+ 获取最新的一条动态
66
+ Args:
67
+ uid (int): 用户uid
68
+ Returns:
69
+ DynamicData: 动态信息对象
70
+ """
71
+ user = User(credential=self._credential, uid=uid)
72
+ dict_info = await user.get_dynamics_new()
73
+ max_id = get_max_id(dict_info)
74
+ _log.debug("获取到最大时间戳的索引为'%s'", max_id)
75
+ if max_id is None:
76
+ raise ValueError("未获取到动态数据或者动态数据为不完整")
77
+ dynamic_info = dict_info["items"][max_id]
78
+ dto = DynamicDTO.from_raw(dynamic_info)
79
+ if not dto:
80
+ raise ValueError("构造 %s 用户动态DTO对象失败" % uid)
81
+ info = DynamicData.from_dto(dto)
82
+ return info
83
+
84
+ async def get_new_dynamic_list(self) -> list[DynamicData]:
85
+ """获取动态主页的动态列表
86
+ Returns:
87
+ list[DynamicData]: 动态信息对象
88
+ """
89
+ if self._credential is None: # pyright: ignore
90
+ raise ValueError("获取动态主页需要 B 站凭证,请配置 credential")
91
+ dynamic_info = await get_dynamic_page_info(self._credential)
92
+ if dynamic_info.get("items", None) is None or not dynamic_info.get("items"):
93
+ raise ValueError("未获取到动态数据或者动态数据为不完整")
94
+ info_dto = DynamicDTO.from_list(dynamic_info["items"])
95
+ info = [DynamicData.from_dto(dto) for dto in info_dto if dto is not None]
96
+ return info
97
+
98
+ async def get_room_info(self, room_id: int) -> LiveRoomData:
99
+ """
100
+ 获取直播间信息
101
+ Args:
102
+ room_id (int): 直播间ID
103
+ Returns:
104
+ LiveRoomData: 直播间信息对象
105
+ """
106
+ live_room = LiveRoom(credential=self._credential, room_display_id=room_id)
107
+ live = await live_room.get_room_info()
108
+ dto = LiveRoomDTO.from_raw(live)
109
+ if dto is None:
110
+ raise ValueError("构造直播间 %s DTO对象失败" % room_id)
111
+ info = LiveRoomData.from_dto(dto)
112
+ return info
113
+
114
+ def get_live_danmaku(self, room_id: int) -> LiveDanmaku:
115
+ """
116
+ 获取直播间弹幕对象
117
+ Args:
118
+ room_id (int): 直播间ID
119
+ Returns:
120
+ LiveDanmaku: 直播间弹幕对象
121
+ """
122
+ live_danmaku = LiveDanmaku(credential=self._credential, room_display_id=room_id)
123
+ return live_danmaku
@@ -0,0 +1,52 @@
1
+ from .danmaku_gift_data import (
2
+ BlindGiftData,
3
+ DanmakuGiftData,
4
+ GiftMedalData,
5
+ )
6
+ from .danmaku_guard_data import (
7
+ DanmakuGuardData,
8
+ )
9
+ from .danmaku_msg_data import DanmakuMsgData, MedalData
10
+ from .dynamic_data import (
11
+ ArticleData,
12
+ AuthorData,
13
+ DynamicData,
14
+ LiveRcmdData,
15
+ MusicData,
16
+ StatData,
17
+ VideoData,
18
+ get_max_id,
19
+ )
20
+ from .live_room_data import (
21
+ AnchorInfoData,
22
+ LiveRoomData,
23
+ NoticeBoardData,
24
+ RoomInfoData,
25
+ WatchedShowData,
26
+ )
27
+ from .video_part import (
28
+ VideoPartData,
29
+ )
30
+
31
+ __all__ = [
32
+ "AnchorInfoData",
33
+ "ArticleData",
34
+ "AuthorData",
35
+ "BlindGiftData",
36
+ "DanmakuGiftData",
37
+ "DanmakuGuardData",
38
+ "DanmakuMsgData",
39
+ "DynamicData",
40
+ "GiftMedalData",
41
+ "LiveRcmdData",
42
+ "LiveRoomData",
43
+ "MedalData",
44
+ "MusicData",
45
+ "NoticeBoardData",
46
+ "RoomInfoData",
47
+ "StatData",
48
+ "VideoData",
49
+ "VideoPartData",
50
+ "WatchedShowData",
51
+ "get_max_id",
52
+ ]