aiodouyu 0.1.0__tar.gz

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.
@@ -0,0 +1,33 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: ["master"]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint-test:
10
+ name: Lint & test (py${{ matrix.python-version }})
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, windows-latest]
16
+ python-version: ["3.10", "3.13"]
17
+ steps:
18
+ - name: Checkout repository
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install package with dev dependencies
27
+ run: pip install -e . pytest pytest-asyncio ruff
28
+
29
+ - name: Lint
30
+ run: ruff check .
31
+
32
+ - name: Test
33
+ run: pytest -q
@@ -0,0 +1,55 @@
1
+ name: release
2
+
3
+ # 打 tag(v*)时构建并发布到 PyPI。
4
+ # 使用 PyPI Trusted Publishing(OIDC),无需在仓库配置 token:
5
+ # 需先在 https://pypi.org/manage/account/publishing/ 添加本仓库为
6
+ # pending publisher(project: aiodouyu, workflow: release.yml,
7
+ # environment: pypi)。
8
+
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout repository
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.13"
24
+
25
+ - name: Build sdist and wheel
26
+ run: |
27
+ pip install build
28
+ python -m build
29
+
30
+ - name: Check metadata
31
+ run: |
32
+ pip install twine
33
+ twine check dist/*
34
+
35
+ - name: Upload artifacts
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment: pypi
45
+ permissions:
46
+ id-token: write # Trusted Publishing 必需
47
+ steps:
48
+ - name: Download artifacts
49
+ uses: actions/download-artifact@v4
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ - name: Publish to PyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ 本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## [0.1.0] - 2026-07-26
6
+
7
+ 首个公开版本。
8
+
9
+ ### Added
10
+
11
+ - `DanmakuClient`:斗鱼弹幕 asyncio 客户端
12
+ - 异步迭代(`async for`)与回调注册(`@client.on`)两种消费方式
13
+ - 自动重连(指数退避 + 抖动),空闲超时检测半开连接
14
+ - `types` 过滤、`EVENT_CONNECTED`/`EVENT_DISCONNECTED` 连接生命周期伪事件
15
+ - `close()` 干净停止,不遗留任务
16
+ - `stt` 模块:斗鱼 STT 序列化格式编解码
17
+ - `packet` 模块:弹幕协议二进制成帧
18
+ - `web` 模块:房间信息 HTTP 接口(零依赖)
19
+ - `fetch_room()` 支持 betard / open API 双数据源与自动回退
20
+ - `RoomInfo` 归一化快照,`videoLoop` 轮播识别
21
+ - 命令行工具 `python -m aiodouyu`(弹幕冒烟测试、`--info` 房间查询)
22
+ - 完整类型标注(`py.typed`)
aiodouyu-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GEMILUXVII
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiodouyu
3
+ Version: 0.1.0
4
+ Summary: Asyncio client for Douyu live danmaku & room status / 斗鱼弹幕与房间状态 asyncio 客户端
5
+ Project-URL: Homepage, https://github.com/GEMILUXVII/aiodouyu
6
+ Project-URL: Repository, https://github.com/GEMILUXVII/aiodouyu
7
+ Project-URL: Issues, https://github.com/GEMILUXVII/aiodouyu/issues
8
+ Project-URL: Changelog, https://github.com/GEMILUXVII/aiodouyu/blob/master/CHANGELOG.md
9
+ Author: GEMILUXVII
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: asyncio,barrage,danmaku,douyu,live,弹幕,斗鱼
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Communications :: Chat
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+
28
+ # aiodouyu
29
+
30
+ 斗鱼弹幕与房间状态的 **asyncio** 客户端库 —— 零依赖、自动重连、干净停止。
31
+
32
+ [English](#english) | [背景](#为什么有这个库) | [安装](#安装) | [用法](#用法) | [房间信息](#房间信息http) | [协议说明](#协议说明) | [局限与路线图](#局限与路线图)
33
+
34
+ ## 为什么有这个库
35
+
36
+ Python 生态里的斗鱼弹幕库已全部停止维护:[pydouyu](https://github.com/Kexiii/pydouyu)(2022 年后无更新)、[danmu](https://github.com/littlecodersh/danmu)(2017)、[danmaku](https://github.com/IsoaSFlus/danmaku)(已归档)。aiodouyu 是这个空缺的现代替代品:
37
+
38
+ - **asyncio 优先**:每个房间一个协程,不再是"每房间三个线程"
39
+ - **零运行时依赖**:纯标准库 TCP 实现
40
+ - **自动重连**:指数退避 + 抖动;空闲超时检测半开连接
41
+ - **干净停止**:`close()` 立即中止,不遗留线程/任务(pydouyu 的停止会泄漏线程)
42
+ - **完整消息流**:rss(开播状态)、chatmsg(弹幕)、dgb(礼物)、uenter(进房)……全部透出
43
+ - **可测试**:附带完整测试套件与假服务器测试基建
44
+
45
+ ## 安装
46
+
47
+ ```bash
48
+ pip install aiodouyu
49
+ ```
50
+
51
+ 要求 Python >= 3.10。
52
+
53
+ ## 用法
54
+
55
+ ### 异步迭代(推荐)
56
+
57
+ ```python
58
+ import asyncio
59
+ from aiodouyu import DanmakuClient
60
+
61
+ async def main():
62
+ async with DanmakuClient(room_id=9999) as client:
63
+ async for msg in client:
64
+ if msg["type"] == "chatmsg":
65
+ print(f'{msg.get("nn")}: {msg.get("txt")}')
66
+ elif msg["type"] == "rss":
67
+ print("开播" if msg.get("ss") == "1" else "下播")
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ ### 回调注册
73
+
74
+ ```python
75
+ client = DanmakuClient(room_id=9999)
76
+
77
+ @client.on("rss")
78
+ def on_status(msg):
79
+ print("直播状态变化:", msg)
80
+
81
+ @client.on("*") # 通配符匹配所有消息
82
+ async def on_any(msg): # 同步/异步回调均可
83
+ ...
84
+
85
+ await client.run() # 运行直到 client.close()
86
+ ```
87
+
88
+ ### 只订阅关心的消息类型
89
+
90
+ ```python
91
+ # 只做开播提醒?只要 rss,其他消息在解码后直接丢弃
92
+ client = DanmakuClient(room_id=9999, types={"rss"})
93
+ ```
94
+
95
+ ### 感知断连窗口
96
+
97
+ ```python
98
+ from aiodouyu import EVENT_CONNECTED, EVENT_DISCONNECTED
99
+
100
+ client = DanmakuClient(room_id=9999, emit_connection_events=True)
101
+ async for msg in client:
102
+ if msg["type"] == EVENT_DISCONNECTED:
103
+ ... # 断连期间可能错过状态变化,可在重连后主动校准
104
+ ```
105
+
106
+ > 斗鱼只在状态**变化**时推送 rss。断连窗口内的变化不会补发,
107
+ > 建议消费方在收到 `EVENT_CONNECTED` 后通过 `fetch_room()` 校准一次当前状态。
108
+
109
+ ### 命令行冒烟测试
110
+
111
+ ```bash
112
+ python -m aiodouyu 9999 --types rss,chatmsg --duration 30
113
+ python -m aiodouyu 9999 --info # 查询房间信息后退出
114
+ ```
115
+
116
+ ## 房间信息(HTTP)
117
+
118
+ `web` 模块提供房间信息拉取,同样零依赖。典型用途:重连后校准直播状态、
119
+ 获取标题/主播名/分类/封面用于通知富化。
120
+
121
+ ```python
122
+ from aiodouyu import fetch_room, RoomNotFound
123
+
124
+ info = await fetch_room(9999)
125
+ print(info.title, info.owner, info.category) # 标题 / 主播名 / 分类
126
+ print(info.is_live) # 是否真实开播(视频轮播不算,见 is_loop)
127
+ print(info.started_at) # 本场开播 epoch 秒;未开播为 None
128
+ print(info.cover_url) # 直播间封面
129
+ ```
130
+
131
+ 两个数据源,`source` 参数选择:
132
+
133
+ | source | 端点 | 特点 |
134
+ |---|---|---|
135
+ | `"betard"`(默认优先) | `www.douyu.com/betard/{rid}` | 字段最全,`videoLoop` 可识别视频轮播 |
136
+ | `"open"` | `open.douyucdn.cn/api/RoomApi/room/{rid}` | 公开 API 更稳定,但无法识别轮播 |
137
+ | `"auto"`(默认) | 先 betard,传输失败回退 open | 房间不存在(`RoomNotFound`)不回退 |
138
+
139
+ > open 源无法判定视频轮播:其 `is_loop` 为 `None`,轮播房会被报告为开播。
140
+ > 做开播状态校准时建议显式 `source="betard"`(失败则跳过本轮校准),
141
+ > 或在 `is_loop is None` 时自行决定是否信任 `is_live`。
142
+
143
+ 配合弹幕客户端做状态校准:
144
+
145
+ ```python
146
+ from aiodouyu import EVENT_CONNECTED, DanmakuClient, fetch_room
147
+
148
+ client = DanmakuClient(room_id=9999, types={"rss"}, emit_connection_events=True)
149
+ async for msg in client:
150
+ if msg["type"] == EVENT_CONNECTED:
151
+ info = await fetch_room(9999) # 断连窗口内的变化在这里补上
152
+ print("当前状态:", info.is_live)
153
+ elif msg["type"] == "rss":
154
+ print("状态变化:", msg.get("ss") == "1" and msg.get("ivl") == "0")
155
+ ```
156
+
157
+ ## 协议说明
158
+
159
+ 连接 `danmuproxy.douyu.com:8601`(TCP),STT 序列化(`@=` 键值、`/` 分隔、
160
+ `@A`/`@S` 转义),小端长度前缀成帧(发 689 / 收 690),`loginreq` + `joingroup`
161
+ 握手,每 45 秒 `mrkl` 心跳。
162
+
163
+ **注意**:弹幕协议是斗鱼的非官方公开接口,端点与字段可能随时变更
164
+ (历史上已发生过一次域名迁移)。本库不隶属于斗鱼,请合理使用、避免滥用。
165
+
166
+ ## 局限与路线图
167
+
168
+ 当前局限:
169
+
170
+ - STT 嵌套结构(如 ranklist 的分组数据)不展开,值以原始字符串返回
171
+ - 仅 TCP 端点;WebSocket 端点(`wss://danmuproxy.douyu.com:850x`)未实现
172
+ - 弹幕消息字段无类型化模型,以 `dict[str, str]` 返回
173
+ - `web` 模块的 betard 数据源是网页端内部接口,字段可能随时变更
174
+ (`auto` 模式会自动回退到公开 API)
175
+
176
+ 路线图:WebSocket 传输、常见消息类型的 typed model。欢迎 issue / PR。
177
+
178
+ ## English
179
+
180
+ Asyncio client for Douyu (斗鱼) live-stream danmaku and room-status events.
181
+ Zero runtime dependencies, automatic reconnection with exponential backoff,
182
+ idle-timeout detection for half-open connections, and clean shutdown.
183
+ Consume messages via `async for` or callback registration. A zero-dependency
184
+ `web` module (`fetch_room`) retrieves room metadata and live status over HTTP
185
+ for state calibration after reconnects. The barrage protocol is an unofficial
186
+ Douyu interface and may change without notice.
187
+
188
+ ```python
189
+ async with DanmakuClient(room_id=9999) as client:
190
+ async for msg in client:
191
+ print(msg)
192
+
193
+ info = await fetch_room(9999) # title / owner / category / is_live ...
194
+ ```
195
+
196
+ ## 许可证
197
+
198
+ [MIT](https://github.com/GEMILUXVII/aiodouyu/blob/master/LICENSE)
199
+
200
+ ## 致谢
201
+
202
+ 协议实现参考了 [pydouyu](https://github.com/Kexiii/pydouyu)(MIT)与社区对
203
+ 斗鱼弹幕协议的公开分析文章。
@@ -0,0 +1,176 @@
1
+ # aiodouyu
2
+
3
+ 斗鱼弹幕与房间状态的 **asyncio** 客户端库 —— 零依赖、自动重连、干净停止。
4
+
5
+ [English](#english) | [背景](#为什么有这个库) | [安装](#安装) | [用法](#用法) | [房间信息](#房间信息http) | [协议说明](#协议说明) | [局限与路线图](#局限与路线图)
6
+
7
+ ## 为什么有这个库
8
+
9
+ Python 生态里的斗鱼弹幕库已全部停止维护:[pydouyu](https://github.com/Kexiii/pydouyu)(2022 年后无更新)、[danmu](https://github.com/littlecodersh/danmu)(2017)、[danmaku](https://github.com/IsoaSFlus/danmaku)(已归档)。aiodouyu 是这个空缺的现代替代品:
10
+
11
+ - **asyncio 优先**:每个房间一个协程,不再是"每房间三个线程"
12
+ - **零运行时依赖**:纯标准库 TCP 实现
13
+ - **自动重连**:指数退避 + 抖动;空闲超时检测半开连接
14
+ - **干净停止**:`close()` 立即中止,不遗留线程/任务(pydouyu 的停止会泄漏线程)
15
+ - **完整消息流**:rss(开播状态)、chatmsg(弹幕)、dgb(礼物)、uenter(进房)……全部透出
16
+ - **可测试**:附带完整测试套件与假服务器测试基建
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pip install aiodouyu
22
+ ```
23
+
24
+ 要求 Python >= 3.10。
25
+
26
+ ## 用法
27
+
28
+ ### 异步迭代(推荐)
29
+
30
+ ```python
31
+ import asyncio
32
+ from aiodouyu import DanmakuClient
33
+
34
+ async def main():
35
+ async with DanmakuClient(room_id=9999) as client:
36
+ async for msg in client:
37
+ if msg["type"] == "chatmsg":
38
+ print(f'{msg.get("nn")}: {msg.get("txt")}')
39
+ elif msg["type"] == "rss":
40
+ print("开播" if msg.get("ss") == "1" else "下播")
41
+
42
+ asyncio.run(main())
43
+ ```
44
+
45
+ ### 回调注册
46
+
47
+ ```python
48
+ client = DanmakuClient(room_id=9999)
49
+
50
+ @client.on("rss")
51
+ def on_status(msg):
52
+ print("直播状态变化:", msg)
53
+
54
+ @client.on("*") # 通配符匹配所有消息
55
+ async def on_any(msg): # 同步/异步回调均可
56
+ ...
57
+
58
+ await client.run() # 运行直到 client.close()
59
+ ```
60
+
61
+ ### 只订阅关心的消息类型
62
+
63
+ ```python
64
+ # 只做开播提醒?只要 rss,其他消息在解码后直接丢弃
65
+ client = DanmakuClient(room_id=9999, types={"rss"})
66
+ ```
67
+
68
+ ### 感知断连窗口
69
+
70
+ ```python
71
+ from aiodouyu import EVENT_CONNECTED, EVENT_DISCONNECTED
72
+
73
+ client = DanmakuClient(room_id=9999, emit_connection_events=True)
74
+ async for msg in client:
75
+ if msg["type"] == EVENT_DISCONNECTED:
76
+ ... # 断连期间可能错过状态变化,可在重连后主动校准
77
+ ```
78
+
79
+ > 斗鱼只在状态**变化**时推送 rss。断连窗口内的变化不会补发,
80
+ > 建议消费方在收到 `EVENT_CONNECTED` 后通过 `fetch_room()` 校准一次当前状态。
81
+
82
+ ### 命令行冒烟测试
83
+
84
+ ```bash
85
+ python -m aiodouyu 9999 --types rss,chatmsg --duration 30
86
+ python -m aiodouyu 9999 --info # 查询房间信息后退出
87
+ ```
88
+
89
+ ## 房间信息(HTTP)
90
+
91
+ `web` 模块提供房间信息拉取,同样零依赖。典型用途:重连后校准直播状态、
92
+ 获取标题/主播名/分类/封面用于通知富化。
93
+
94
+ ```python
95
+ from aiodouyu import fetch_room, RoomNotFound
96
+
97
+ info = await fetch_room(9999)
98
+ print(info.title, info.owner, info.category) # 标题 / 主播名 / 分类
99
+ print(info.is_live) # 是否真实开播(视频轮播不算,见 is_loop)
100
+ print(info.started_at) # 本场开播 epoch 秒;未开播为 None
101
+ print(info.cover_url) # 直播间封面
102
+ ```
103
+
104
+ 两个数据源,`source` 参数选择:
105
+
106
+ | source | 端点 | 特点 |
107
+ |---|---|---|
108
+ | `"betard"`(默认优先) | `www.douyu.com/betard/{rid}` | 字段最全,`videoLoop` 可识别视频轮播 |
109
+ | `"open"` | `open.douyucdn.cn/api/RoomApi/room/{rid}` | 公开 API 更稳定,但无法识别轮播 |
110
+ | `"auto"`(默认) | 先 betard,传输失败回退 open | 房间不存在(`RoomNotFound`)不回退 |
111
+
112
+ > open 源无法判定视频轮播:其 `is_loop` 为 `None`,轮播房会被报告为开播。
113
+ > 做开播状态校准时建议显式 `source="betard"`(失败则跳过本轮校准),
114
+ > 或在 `is_loop is None` 时自行决定是否信任 `is_live`。
115
+
116
+ 配合弹幕客户端做状态校准:
117
+
118
+ ```python
119
+ from aiodouyu import EVENT_CONNECTED, DanmakuClient, fetch_room
120
+
121
+ client = DanmakuClient(room_id=9999, types={"rss"}, emit_connection_events=True)
122
+ async for msg in client:
123
+ if msg["type"] == EVENT_CONNECTED:
124
+ info = await fetch_room(9999) # 断连窗口内的变化在这里补上
125
+ print("当前状态:", info.is_live)
126
+ elif msg["type"] == "rss":
127
+ print("状态变化:", msg.get("ss") == "1" and msg.get("ivl") == "0")
128
+ ```
129
+
130
+ ## 协议说明
131
+
132
+ 连接 `danmuproxy.douyu.com:8601`(TCP),STT 序列化(`@=` 键值、`/` 分隔、
133
+ `@A`/`@S` 转义),小端长度前缀成帧(发 689 / 收 690),`loginreq` + `joingroup`
134
+ 握手,每 45 秒 `mrkl` 心跳。
135
+
136
+ **注意**:弹幕协议是斗鱼的非官方公开接口,端点与字段可能随时变更
137
+ (历史上已发生过一次域名迁移)。本库不隶属于斗鱼,请合理使用、避免滥用。
138
+
139
+ ## 局限与路线图
140
+
141
+ 当前局限:
142
+
143
+ - STT 嵌套结构(如 ranklist 的分组数据)不展开,值以原始字符串返回
144
+ - 仅 TCP 端点;WebSocket 端点(`wss://danmuproxy.douyu.com:850x`)未实现
145
+ - 弹幕消息字段无类型化模型,以 `dict[str, str]` 返回
146
+ - `web` 模块的 betard 数据源是网页端内部接口,字段可能随时变更
147
+ (`auto` 模式会自动回退到公开 API)
148
+
149
+ 路线图:WebSocket 传输、常见消息类型的 typed model。欢迎 issue / PR。
150
+
151
+ ## English
152
+
153
+ Asyncio client for Douyu (斗鱼) live-stream danmaku and room-status events.
154
+ Zero runtime dependencies, automatic reconnection with exponential backoff,
155
+ idle-timeout detection for half-open connections, and clean shutdown.
156
+ Consume messages via `async for` or callback registration. A zero-dependency
157
+ `web` module (`fetch_room`) retrieves room metadata and live status over HTTP
158
+ for state calibration after reconnects. The barrage protocol is an unofficial
159
+ Douyu interface and may change without notice.
160
+
161
+ ```python
162
+ async with DanmakuClient(room_id=9999) as client:
163
+ async for msg in client:
164
+ print(msg)
165
+
166
+ info = await fetch_room(9999) # title / owner / category / is_live ...
167
+ ```
168
+
169
+ ## 许可证
170
+
171
+ [MIT](https://github.com/GEMILUXVII/aiodouyu/blob/master/LICENSE)
172
+
173
+ ## 致谢
174
+
175
+ 协议实现参考了 [pydouyu](https://github.com/Kexiii/pydouyu)(MIT)与社区对
176
+ 斗鱼弹幕协议的公开分析文章。
@@ -0,0 +1,19 @@
1
+ """示例:打印直播间实时弹幕
2
+
3
+ 用法: python examples/print_danmaku.py <房间号>
4
+ """
5
+
6
+ import asyncio
7
+ import sys
8
+
9
+ from aiodouyu import DanmakuClient
10
+
11
+
12
+ async def main(room_id: int) -> None:
13
+ async with DanmakuClient(room_id=room_id, types={"chatmsg"}) as client:
14
+ async for msg in client:
15
+ print(f'[{msg.get("nn", "?")}] {msg.get("txt", "")}')
16
+
17
+
18
+ if __name__ == "__main__":
19
+ asyncio.run(main(int(sys.argv[1])))
@@ -0,0 +1,38 @@
1
+ """示例:监控直播间开播/下播状态
2
+
3
+ 弹幕连接只在状态变化时收到 rss 推送,断连窗口内的变化会丢失;
4
+ 本示例在每次连接建立后用 fetch_room() 校准一次当前状态。
5
+
6
+ 用法: python examples/room_status.py <房间号>
7
+ """
8
+
9
+ import asyncio
10
+ import sys
11
+
12
+ from aiodouyu import EVENT_CONNECTED, ApiError, DanmakuClient, fetch_room
13
+
14
+
15
+ async def main(room_id: int) -> None:
16
+ client = DanmakuClient(
17
+ room_id=room_id,
18
+ types={"rss"},
19
+ emit_connection_events=True,
20
+ )
21
+ async with client:
22
+ async for msg in client:
23
+ if msg["type"] == EVENT_CONNECTED:
24
+ # rss 只在状态变化时推送,重连后主动校准当前状态
25
+ try:
26
+ info = await fetch_room(room_id)
27
+ except ApiError as exc:
28
+ print(f"已连接(状态校准失败: {exc})")
29
+ continue
30
+ print(f"已连接,当前{'🟢 直播中' if info.is_live else '⚪ 未开播'}: "
31
+ f"{info.owner} - {info.title}")
32
+ elif msg["type"] == "rss":
33
+ is_live = msg.get("ss") == "1" and msg.get("ivl") == "0"
34
+ print("🟢 开播了!" if is_live else "⚪ 下播了")
35
+
36
+
37
+ if __name__ == "__main__":
38
+ asyncio.run(main(int(sys.argv[1])))
@@ -0,0 +1,65 @@
1
+ [project]
2
+ name = "aiodouyu"
3
+ dynamic = ["version"]
4
+ description = "Asyncio client for Douyu live danmaku & room status / 斗鱼弹幕与房间状态 asyncio 客户端"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "GEMILUXVII" }]
9
+ requires-python = ">=3.10"
10
+ dependencies = []
11
+ keywords = ["douyu", "danmaku", "asyncio", "live", "barrage", "斗鱼", "弹幕"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Framework :: AsyncIO",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Communications :: Chat",
23
+ "Topic :: Software Development :: Libraries",
24
+ "Typing :: Typed",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/GEMILUXVII/aiodouyu"
29
+ Repository = "https://github.com/GEMILUXVII/aiodouyu"
30
+ Issues = "https://github.com/GEMILUXVII/aiodouyu/issues"
31
+ Changelog = "https://github.com/GEMILUXVII/aiodouyu/blob/master/CHANGELOG.md"
32
+
33
+ [dependency-groups]
34
+ dev = ["pytest>=8", "pytest-asyncio>=0.24", "ruff"]
35
+
36
+ [build-system]
37
+ requires = ["hatchling"]
38
+ build-backend = "hatchling.build"
39
+
40
+ [tool.hatch.version]
41
+ path = "src/aiodouyu/__init__.py"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/aiodouyu"]
45
+
46
+ [tool.pytest.ini_options]
47
+ asyncio_mode = "auto"
48
+ testpaths = ["tests"]
49
+
50
+ [tool.ruff]
51
+ line-length = 88
52
+ target-version = "py310"
53
+
54
+ [tool.ruff.lint]
55
+ select = [
56
+ "F", # Pyflakes
57
+ "W", # pycodestyle warnings
58
+ "E", # pycodestyle errors
59
+ "ASYNC", # flake8-async
60
+ "C4", # flake8-comprehensions
61
+ "Q", # flake8-quotes
62
+ "I", # import-order
63
+ "UP", # pyupgrade
64
+ ]
65
+ ignore = ["E501"]