nonebot-plugin-bili-dynamic 0.2.2__tar.gz → 0.2.4__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.
Files changed (25) hide show
  1. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/PKG-INFO +2 -2
  2. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/qrcode_login.py +56 -20
  3. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/renderer.py +6 -3
  4. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic.egg-info/PKG-INFO +2 -2
  5. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic.egg-info/SOURCES.txt +2 -1
  6. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic.egg-info/requires.txt +1 -1
  7. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/pyproject.toml +2 -2
  8. nonebot_plugin_bili_dynamic-0.2.4/tests/test_qrcode_login.py +46 -0
  9. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/LICENSE +0 -0
  10. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/README.md +0 -0
  11. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/__init__.py +0 -0
  12. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/api.py +0 -0
  13. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/commands.py +0 -0
  14. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/config.py +0 -0
  15. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/cookie_store.py +0 -0
  16. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/database.py +0 -0
  17. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/link_parser.py +0 -0
  18. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/models.py +0 -0
  19. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/monitor.py +0 -0
  20. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic/translator.py +0 -0
  21. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic.egg-info/dependency_links.txt +0 -0
  22. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic.egg-info/entry_points.txt +0 -0
  23. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/nonebot_plugin_bili_dynamic.egg-info/top_level.txt +0 -0
  24. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/setup.cfg +0 -0
  25. {nonebot_plugin_bili_dynamic-0.2.2 → nonebot_plugin_bili_dynamic-0.2.4}/tests/test_dynamic_expire.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-bili-dynamic
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: B站动态/直播推送 NoneBot2 插件(基于 Colter23/mirai 插件移植)
5
5
  Author: Tony
6
6
  License: MIT
@@ -27,7 +27,7 @@ Requires-Dist: httpx>=0.27.0
27
27
  Requires-Dist: pillow>=10.0.0
28
28
  Requires-Dist: skia-python>=87.5
29
29
  Requires-Dist: qrcode[pil]>=7.4.2
30
- Requires-Dist: tortoise-orm[accel]>=0.21.0
30
+ Requires-Dist: tortoise-orm[accel]<1.0,>=0.21.0
31
31
  Requires-Dist: aerich>=0.7.0
32
32
  Requires-Dist: pydantic>=2.0.0
33
33
  Requires-Dist: typing-extensions>=4.0.0
@@ -13,6 +13,46 @@ from nonebot.adapters.onebot.v11 import Bot, MessageEvent, MessageSegment
13
13
  import httpx
14
14
 
15
15
 
16
+ def _extract_login_cookies(set_cookie_headers: list, login_url: str) -> Dict:
17
+ """从扫码成功响应中提取登录 Cookie。
18
+
19
+ 优先解析 Set-Cookie 响应头(B站官方下发凭证的通道),
20
+ 部分情况下 data.url 的 query 里不再携带 SESSDATA,
21
+ 因此 URL query 只作为兜底。
22
+ """
23
+ import http.cookies
24
+ from urllib.parse import urlparse, parse_qs
25
+
26
+ wanted = ("SESSDATA", "BILI_JCT", "DEDEUSERID")
27
+ cookies: Dict[str, str] = {}
28
+
29
+ # 1) Set-Cookie 响应头(可能有多个,逐个解析)
30
+ for header in set_cookie_headers or []:
31
+ try:
32
+ morsel = http.cookies.SimpleCookie()
33
+ morsel.load(header)
34
+ for key, m in morsel.items():
35
+ if key.upper() in wanted and m.value:
36
+ cookies.setdefault(key, m.value)
37
+ except Exception as e:
38
+ logger.warning(f"[QRLogin] parse set-cookie failed: {e}")
39
+
40
+ # 2) 兜底:从 data.url 的 query 参数中提取
41
+ # 注意:parse_qs 已做过一次 URL 解码,不要再 unquote,
42
+ # 真实 SESSDATA 字面量里本就包含 %2C,二次解码会损坏值
43
+ if login_url:
44
+ try:
45
+ parsed = urlparse(login_url)
46
+ query_params = parse_qs(parsed.query)
47
+ for key, values in query_params.items():
48
+ if key.upper() in wanted and values:
49
+ cookies.setdefault(key, values[0])
50
+ except Exception as e:
51
+ logger.error(f"[QRLogin] parse url failed: {e}")
52
+
53
+ return cookies
54
+
55
+
16
56
  class BiliQRCodeLogin:
17
57
  """B站扫码登录器"""
18
58
 
@@ -94,27 +134,23 @@ class BiliQRCodeLogin:
94
134
  poll_code = poll_data.get("code", 86083)
95
135
 
96
136
  if poll_code == 0:
97
- # 登录成功!从 data.url 的 query 参数中提取 cookies
98
- # 参考 Colter23 实现: url 中包含 "SESSDATA=xxx&bili_jct=xxx"
137
+ # 登录成功!凭证通过 Set-Cookie 响应头下发,
138
+ # data.url 的 query 参数只是兜底(部分情况下不再携带 SESSDATA)
99
139
  login_url = poll_data.get("url", "")
100
- cookies = {}
101
-
102
- if login_url:
103
- try:
104
- from urllib.parse import urlparse, parse_qs
105
- parsed = urlparse(login_url)
106
- query_params = parse_qs(parsed.query)
107
-
108
- for key, values in query_params.items():
109
- if key.upper() in ("SESSDATA", "BILI_JCT", "DEDEUSERID"):
110
- # URL decode 并取第一个值
111
- import urllib.parse
112
- cookies[key] = urllib.parse.unquote(values[0])
113
-
114
- logger.info(f"[QRLogin] extracted from url query: {list(cookies.keys())}")
115
- except Exception as e:
116
- logger.error(f"[QRLogin] parse url failed: {e}")
117
-
140
+ cookies = _extract_login_cookies(
141
+ resp.headers.get_list("set-cookie"),
142
+ login_url,
143
+ )
144
+
145
+ if cookies:
146
+ logger.info(f"[QRLogin] extracted cookies: {list(cookies.keys())}")
147
+ else:
148
+ logger.error(
149
+ f"[QRLogin] 登录成功但未提取到 Cookie, "
150
+ f"set-cookie headers: {len(resp.headers.get_list('set-cookie'))} 条, "
151
+ f"data.url: {login_url[:80]}"
152
+ )
153
+
118
154
  return 0, cookies
119
155
 
120
156
  return poll_code, None
@@ -375,7 +375,8 @@ async def _download_image(url: str) -> Optional[skia.Image]:
375
375
  client = await _client()
376
376
  resp = await client.get(url)
377
377
  if resp.status_code == 200 and resp.content:
378
- img = skia.Image.MakeFromEncoded(resp.content)
378
+ # MakeWithCopy:MakeFromEncoded 零拷贝不持有数据,resp.content 释放后即成悬垂指针
379
+ img = skia.Image.MakeFromEncoded(skia.Data.MakeWithCopy(resp.content))
379
380
  if img:
380
381
  try:
381
382
  img = img.makeRasterImage() or img
@@ -549,7 +550,8 @@ def _make_pil_processed_image(
549
550
  pil = pil.convert("RGB")
550
551
  buf = io.BytesIO()
551
552
  pil.save(buf, format="PNG")
552
- return skia.Image.MakeFromEncoded(buf.getvalue())
553
+ # MakeWithCopy:MakeFromEncoded 零拷贝不持有数据,防止临时 buffer 被 GC 后悬垂
554
+ return skia.Image.MakeFromEncoded(skia.Data.MakeWithCopy(buf.getvalue()))
553
555
  except Exception as e:
554
556
  logger.debug(f"[BiliDynamic] Pillow 图片预处理失败: {e}")
555
557
  return None
@@ -687,7 +689,8 @@ def _draw_fake_qr(canvas: skia.Canvas, link: str, color: int, x: float, y: float
687
689
  img = qr.make_image(fill_color="black", back_color="white").convert("RGB")
688
690
  buf = io.BytesIO()
689
691
  img.save(buf, format="PNG")
690
- qimg = skia.Image.MakeFromEncoded(buf.getvalue())
692
+ # MakeWithCopy:MakeFromEncoded 零拷贝不持有数据,防止临时 buffer 被 GC 后悬垂
693
+ qimg = skia.Image.MakeFromEncoded(skia.Data.MakeWithCopy(buf.getvalue()))
691
694
  if qimg:
692
695
  _draw_image_rrect(canvas, qimg, skia.RRect.MakeRectXY(skia.Rect.MakeXYWH(x, y, size, size), 0, 0))
693
696
  except Exception:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-bili-dynamic
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: B站动态/直播推送 NoneBot2 插件(基于 Colter23/mirai 插件移植)
5
5
  Author: Tony
6
6
  License: MIT
@@ -27,7 +27,7 @@ Requires-Dist: httpx>=0.27.0
27
27
  Requires-Dist: pillow>=10.0.0
28
28
  Requires-Dist: skia-python>=87.5
29
29
  Requires-Dist: qrcode[pil]>=7.4.2
30
- Requires-Dist: tortoise-orm[accel]>=0.21.0
30
+ Requires-Dist: tortoise-orm[accel]<1.0,>=0.21.0
31
31
  Requires-Dist: aerich>=0.7.0
32
32
  Requires-Dist: pydantic>=2.0.0
33
33
  Requires-Dist: typing-extensions>=4.0.0
@@ -19,4 +19,5 @@ nonebot_plugin_bili_dynamic.egg-info/dependency_links.txt
19
19
  nonebot_plugin_bili_dynamic.egg-info/entry_points.txt
20
20
  nonebot_plugin_bili_dynamic.egg-info/requires.txt
21
21
  nonebot_plugin_bili_dynamic.egg-info/top_level.txt
22
- tests/test_dynamic_expire.py
22
+ tests/test_dynamic_expire.py
23
+ tests/test_qrcode_login.py
@@ -6,7 +6,7 @@ httpx>=0.27.0
6
6
  pillow>=10.0.0
7
7
  skia-python>=87.5
8
8
  qrcode[pil]>=7.4.2
9
- tortoise-orm[accel]>=0.21.0
9
+ tortoise-orm[accel]<1.0,>=0.21.0
10
10
  aerich>=0.7.0
11
11
  pydantic>=2.0.0
12
12
  typing-extensions>=4.0.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-bili-dynamic"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "B站动态/直播推送 NoneBot2 插件(基于 Colter23/mirai 插件移植)"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -29,7 +29,7 @@ dependencies = [
29
29
  "pillow>=10.0.0",
30
30
  "skia-python>=87.5",
31
31
  "qrcode[pil]>=7.4.2",
32
- "tortoise-orm[accel]>=0.21.0",
32
+ "tortoise-orm[accel]>=0.21.0,<1.0",
33
33
  "aerich>=0.7.0",
34
34
  "pydantic>=2.0.0",
35
35
  "typing-extensions>=4.0.0",
@@ -0,0 +1,46 @@
1
+ """扫码登录 Cookie 提取测试"""
2
+ import nonebot
3
+ import pytest
4
+
5
+
6
+ @pytest.fixture(scope="module", autouse=True)
7
+ def init_nonebot():
8
+ nonebot.init()
9
+
10
+
11
+ def test_extract_from_set_cookie_only():
12
+ """data.url query 不含凭证时,能从 Set-Cookie 头提取(真实失败场景)"""
13
+ from nonebot_plugin_bili_dynamic.qrcode_login import _extract_login_cookies
14
+
15
+ headers = [
16
+ "SESSDATA=abc123%2C456*7; Domain=.bilibili.com; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Path=/",
17
+ "bili_jct=token789; Domain=.bilibili.com; Path=/",
18
+ "DedeUserID=123456; Domain=.bilibili.com; Path=/",
19
+ "sid=othersid; Domain=.bilibili.com; Path=/",
20
+ ]
21
+ cookies = _extract_login_cookies(headers, "https://www.bilibili.com/")
22
+ assert cookies["SESSDATA"] == "abc123%2C456*7"
23
+ assert cookies["bili_jct"] == "token789"
24
+ assert cookies["DedeUserID"] == "123456"
25
+ assert "sid" not in cookies
26
+
27
+
28
+ def test_extract_from_url_query_fallback():
29
+ """旧格式:凭证在 data.url 的 query 里"""
30
+ from nonebot_plugin_bili_dynamic.qrcode_login import _extract_login_cookies
31
+
32
+ url = (
33
+ "https://passport.biligame.com/x/passport-login/web/crossDomain"
34
+ "?DedeUserID=123456&DedeUserID__ckMd5=deadbeef&Expires=15551000"
35
+ "&SESSDATA=abc123%252C456&bili_jct=token789&gourl=https%3A%2F%2Fwww.bilibili.com"
36
+ )
37
+ cookies = _extract_login_cookies([], url)
38
+ assert cookies["DedeUserID"] == "123456"
39
+ assert cookies["SESSDATA"] == "abc123%2C456"
40
+ assert cookies["bili_jct"] == "token789"
41
+
42
+
43
+ def test_extract_empty_when_nothing():
44
+ from nonebot_plugin_bili_dynamic.qrcode_login import _extract_login_cookies
45
+
46
+ assert _extract_login_cookies([], "") == {}