dsh-world 0.6.4 → 0.6.5

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 (44) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CONTRIBUTING.md +161 -161
  3. package/INSTALL.md +314 -311
  4. package/README.md +18 -13
  5. package/client/index.js +20 -1
  6. package/lib/compactionSwap.d.ts +29 -0
  7. package/lib/compactionSwap.js +56 -0
  8. package/lib/console/routes.d.ts +2 -0
  9. package/lib/console/routes.js +6 -0
  10. package/lib/console/snapshot.d.ts +1 -0
  11. package/lib/console/snapshot.js +1 -0
  12. package/lib/ecosystem/qq-native/home.js +2 -2
  13. package/lib/ecosystem/shared/context.d.ts +2 -1
  14. package/lib/ecosystem/shared/context.js +8 -1
  15. package/lib/ecosystem/wechat/home.js +4 -4
  16. package/lib/ecosystem/wechat/index.js +13 -4
  17. package/lib/ecosystem/wechat/messages.js +1 -1
  18. package/lib/ecosystem/wechat/reading.d.ts +9 -0
  19. package/lib/ecosystem/wechat/reading.js +61 -12
  20. package/lib/ecosystem/wechat/sending.d.ts +1 -0
  21. package/lib/ecosystem/wechat/sending.js +3 -0
  22. package/lib/ecosystem/wechat/stickerLibrary.d.ts +50 -0
  23. package/lib/ecosystem/wechat/stickerLibrary.js +167 -0
  24. package/lib/ecosystem/wechat/stickerTools.d.ts +18 -0
  25. package/lib/ecosystem/wechat/stickerTools.js +69 -0
  26. package/lib/index.d.ts +3 -1
  27. package/lib/index.js +9 -2
  28. package/lib/life/activity.js +5 -0
  29. package/lib/life/runtime.d.ts +2 -0
  30. package/lib/life/runtime.js +4 -2
  31. package/lib/runtime.js +36 -37
  32. package/lib/types.d.ts +2 -0
  33. package/lib/worldSwitch.d.ts +7 -1
  34. package/lib/worldSwitch.js +12 -1
  35. package/package.json +115 -114
  36. package/scripts/import-stickers.mjs +17 -0
  37. package/src/ecosystem/wechat/windows/README.md +22 -5
  38. package/src/ecosystem/wechat/windows/adapter.py +24 -5
  39. package/src/ecosystem/wechat/windows/groups.py +2 -2
  40. package/src/ecosystem/wechat/windows/native.py +4 -3
  41. package/src/ecosystem/wechat/windows/outgoing.py +14 -7
  42. package/src/ecosystem/wechat/windows/reading.py +58 -1
  43. package/src/ecosystem/wechat/windows/snapshot.py +8 -0
  44. package/src/ecosystem/wechat/windows/stickers.py +69 -60
@@ -1,60 +1,69 @@
1
- """A received animated sticker is fetched from the CDN address in its own message and accepted only if its md5 matches."""
2
- import hashlib
3
- import urllib.error
4
- import urllib.request
5
- from urllib.parse import urlparse
6
- from xml.etree import ElementTree
7
-
8
- MAX_STICKER_BYTES = 10 * 1024 * 1024
9
- TIMEOUT_SECONDS = 15
10
-
11
-
12
- class StickerUnavailable(RuntimeError):
13
- pass
14
-
15
-
16
- class _NoRedirect(urllib.request.HTTPRedirectHandler):
17
- def redirect_request(self, *args, **kwargs):
18
- return None
19
-
20
-
21
- def parseSticker(content):
22
- """Return (md5, url) from a sticker message body, which is zstd-compressed XML in WeChat 4.x."""
23
- if isinstance(content, bytes):
24
- if content.startswith(bytes.fromhex('28b52ffd')):
25
- import zstandard
26
- content = zstandard.ZstdDecompressor().decompress(content, max_output_size=128 * 1024)
27
- content = content.decode('utf-8')
28
- if not isinstance(content, str) or len(content.encode('utf-8')) > 128 * 1024:
29
- raise StickerUnavailable('表情包消息格式无效')
30
- try:
31
- emoji = ElementTree.fromstring(content[content.index('<'):]).find('emoji')
32
- except (ValueError, ElementTree.ParseError) as error:
33
- raise StickerUnavailable('表情包消息无法解析') from error
34
- if emoji is None: raise StickerUnavailable('消息里没有表情包信息')
35
- md5, url = (emoji.get('md5') or '').lower(), emoji.get('cdnurl') or ''
36
- if len(md5) != 32 or any(c not in '0123456789abcdef' for c in md5): raise StickerUnavailable('表情包缺少有效标识')
37
- if not url: raise StickerUnavailable('表情包没有下载地址')
38
- return md5, url
39
-
40
-
41
- def checkUrl(url):
42
- parts = urlparse(url)
43
- host = parts.hostname or ''
44
- if parts.scheme not in ('http', 'https') or parts.username or parts.password or not (host == 'qq.com' or host.endswith('.qq.com')):
45
- raise StickerUnavailable('表情包地址不是腾讯 CDN,已拒绝')
46
-
47
-
48
- def fetchSticker(content, *, opener=None):
49
- """Download one sticker; the bytes are returned only when their md5 equals the one in the message."""
50
- md5, url = parseSticker(content)
51
- checkUrl(url)
52
- opener = opener or urllib.request.build_opener(_NoRedirect)
53
- try:
54
- with opener.open(urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}), timeout=TIMEOUT_SECONDS) as response:
55
- data = response.read(MAX_STICKER_BYTES + 1)
56
- except (urllib.error.URLError, OSError) as error:
57
- raise StickerUnavailable(f'表情包下载失败:{error}') from error
58
- if len(data) > MAX_STICKER_BYTES: raise StickerUnavailable('表情包超过当前大小限制')
59
- if hashlib.md5(data).hexdigest() != md5: raise StickerUnavailable('下载内容与消息中的 md5 不符,已丢弃')
60
- return data
1
+ """A received animated sticker is fetched from the CDN address in its own message and accepted only if its md5 matches."""
2
+ import hashlib
3
+ import urllib.error
4
+ import urllib.request
5
+ from urllib.parse import urlparse
6
+ from xml.etree import ElementTree
7
+
8
+ MAX_STICKER_BYTES = 10 * 1024 * 1024
9
+ TIMEOUT_SECONDS = 15
10
+
11
+
12
+ class StickerUnavailable(RuntimeError):
13
+ pass
14
+
15
+
16
+ class _NoRedirect(urllib.request.HTTPRedirectHandler):
17
+ def redirect_request(self, *args, **kwargs):
18
+ return None
19
+
20
+
21
+ def parseSticker(content):
22
+ """Return (md5, url) from a sticker message body, which is zstd-compressed XML in WeChat 4.x."""
23
+ if isinstance(content, bytes):
24
+ if content.startswith(bytes.fromhex('28b52ffd')):
25
+ import zstandard
26
+ content = zstandard.ZstdDecompressor().decompress(content, max_output_size=128 * 1024)
27
+ content = content.decode('utf-8')
28
+ if not isinstance(content, str) or len(content.encode('utf-8')) > 128 * 1024:
29
+ raise StickerUnavailable('表情包消息格式无效')
30
+ try:
31
+ emoji = ElementTree.fromstring(content[content.index('<'):]).find('emoji')
32
+ except (ValueError, ElementTree.ParseError) as error:
33
+ raise StickerUnavailable('表情包消息无法解析') from error
34
+ if emoji is None: raise StickerUnavailable('消息里没有表情包信息')
35
+ md5, url = (emoji.get('md5') or '').lower(), emoji.get('cdnurl') or ''
36
+ if len(md5) != 32 or any(c not in '0123456789abcdef' for c in md5): raise StickerUnavailable('表情包缺少有效标识')
37
+ if not url: raise StickerUnavailable('表情包没有下载地址')
38
+ return md5, url
39
+
40
+
41
+ def stickerKind(data):
42
+ """(extension, media type) of a sticker the library accepts, from its first bytes."""
43
+ if data[:6] in (b'GIF87a', b'GIF89a'): return 'gif', 'image/gif'
44
+ if data[:8] == bytes([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]): return 'png', 'image/png'
45
+ if data[:4] == b'RIFF' and data[8:12] == b'WEBP': return 'webp', 'image/webp'
46
+ if data[:3] == bytes([0xff, 0xd8, 0xff]): return 'jpg', 'image/jpeg'
47
+ raise StickerUnavailable('表情包不是 GIF、PNG、WebP 或 JPEG 图片')
48
+
49
+
50
+ def checkUrl(url):
51
+ parts = urlparse(url)
52
+ host = parts.hostname or ''
53
+ if parts.scheme not in ('http', 'https') or parts.username or parts.password or not (host == 'qq.com' or host.endswith('.qq.com')):
54
+ raise StickerUnavailable('表情包地址不是腾讯 CDN,已拒绝')
55
+
56
+
57
+ def fetchSticker(content, *, opener=None):
58
+ """Download one sticker; the bytes are returned only when their md5 equals the one in the message."""
59
+ md5, url = parseSticker(content)
60
+ checkUrl(url)
61
+ opener = opener or urllib.request.build_opener(_NoRedirect)
62
+ try:
63
+ with opener.open(urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}), timeout=TIMEOUT_SECONDS) as response:
64
+ data = response.read(MAX_STICKER_BYTES + 1)
65
+ except (urllib.error.URLError, OSError) as error:
66
+ raise StickerUnavailable(f'表情包下载失败:{error}') from error
67
+ if len(data) > MAX_STICKER_BYTES: raise StickerUnavailable('表情包超过当前大小限制')
68
+ if hashlib.md5(data).hexdigest() != md5: raise StickerUnavailable('下载内容与消息中的 md5 不符,已丢弃')
69
+ return data