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.
- package/CHANGELOG.md +15 -0
- package/CONTRIBUTING.md +161 -161
- package/INSTALL.md +314 -311
- package/README.md +18 -13
- package/client/index.js +20 -1
- package/lib/compactionSwap.d.ts +29 -0
- package/lib/compactionSwap.js +56 -0
- package/lib/console/routes.d.ts +2 -0
- package/lib/console/routes.js +6 -0
- package/lib/console/snapshot.d.ts +1 -0
- package/lib/console/snapshot.js +1 -0
- package/lib/ecosystem/qq-native/home.js +2 -2
- package/lib/ecosystem/shared/context.d.ts +2 -1
- package/lib/ecosystem/shared/context.js +8 -1
- package/lib/ecosystem/wechat/home.js +4 -4
- package/lib/ecosystem/wechat/index.js +13 -4
- package/lib/ecosystem/wechat/messages.js +1 -1
- package/lib/ecosystem/wechat/reading.d.ts +9 -0
- package/lib/ecosystem/wechat/reading.js +61 -12
- package/lib/ecosystem/wechat/sending.d.ts +1 -0
- package/lib/ecosystem/wechat/sending.js +3 -0
- package/lib/ecosystem/wechat/stickerLibrary.d.ts +50 -0
- package/lib/ecosystem/wechat/stickerLibrary.js +167 -0
- package/lib/ecosystem/wechat/stickerTools.d.ts +18 -0
- package/lib/ecosystem/wechat/stickerTools.js +69 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.js +9 -2
- package/lib/life/activity.js +5 -0
- package/lib/life/runtime.d.ts +2 -0
- package/lib/life/runtime.js +4 -2
- package/lib/runtime.js +36 -37
- package/lib/types.d.ts +2 -0
- package/lib/worldSwitch.d.ts +7 -1
- package/lib/worldSwitch.js +12 -1
- package/package.json +115 -114
- package/scripts/import-stickers.mjs +17 -0
- package/src/ecosystem/wechat/windows/README.md +22 -5
- package/src/ecosystem/wechat/windows/adapter.py +24 -5
- package/src/ecosystem/wechat/windows/groups.py +2 -2
- package/src/ecosystem/wechat/windows/native.py +4 -3
- package/src/ecosystem/wechat/windows/outgoing.py +14 -7
- package/src/ecosystem/wechat/windows/reading.py +58 -1
- package/src/ecosystem/wechat/windows/snapshot.py +8 -0
- 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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|