weflow-cli 1.8.2 โ†’ 1.10.0

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 (185) hide show
  1. package/ARCHITECTURE.md +62 -26
  2. package/CHANGELOG.md +1290 -0
  3. package/OPERATIONS.md +144 -11
  4. package/README.en.md +20 -4
  5. package/README.md +30 -5
  6. package/bin/weflow-cli-electron.cjs +2 -0
  7. package/bin/weflow-cli.ts +996 -118
  8. package/dist/bin/weflow-cli.js +1038 -146
  9. package/dist/bin/weflow-cli.js.map +1 -1
  10. package/dist/mcp-server/http.d.ts +14 -0
  11. package/dist/mcp-server/http.d.ts.map +1 -0
  12. package/dist/mcp-server/http.js +84 -0
  13. package/dist/mcp-server/http.js.map +1 -0
  14. package/dist/mcp-server/httpConfig.d.ts +50 -0
  15. package/dist/mcp-server/httpConfig.d.ts.map +1 -0
  16. package/dist/mcp-server/httpConfig.js +104 -0
  17. package/dist/mcp-server/httpConfig.js.map +1 -0
  18. package/dist/mcp-server/index.d.ts +13 -1
  19. package/dist/mcp-server/index.d.ts.map +1 -1
  20. package/dist/mcp-server/index.js +58 -374
  21. package/dist/mcp-server/index.js.map +1 -1
  22. package/dist/src/core/dbPathService.d.ts +12 -0
  23. package/dist/src/core/dbPathService.d.ts.map +1 -1
  24. package/dist/src/core/dbPathService.js +20 -3
  25. package/dist/src/core/dbPathService.js.map +1 -1
  26. package/dist/src/panel/server.d.ts.map +1 -1
  27. package/dist/src/panel/server.js +61 -1
  28. package/dist/src/panel/server.js.map +1 -1
  29. package/dist/src/services/assistantEval.d.ts +69 -0
  30. package/dist/src/services/assistantEval.d.ts.map +1 -1
  31. package/dist/src/services/assistantEval.js +270 -8
  32. package/dist/src/services/assistantEval.js.map +1 -1
  33. package/dist/src/services/assistantScenes.d.ts +81 -0
  34. package/dist/src/services/assistantScenes.d.ts.map +1 -0
  35. package/dist/src/services/assistantScenes.js +322 -0
  36. package/dist/src/services/assistantScenes.js.map +1 -0
  37. package/dist/src/services/assistantService.d.ts +33 -4
  38. package/dist/src/services/assistantService.d.ts.map +1 -1
  39. package/dist/src/services/assistantService.js +102 -6
  40. package/dist/src/services/assistantService.js.map +1 -1
  41. package/dist/src/services/assistantSkills.d.ts +94 -0
  42. package/dist/src/services/assistantSkills.d.ts.map +1 -0
  43. package/dist/src/services/assistantSkills.js +330 -0
  44. package/dist/src/services/assistantSkills.js.map +1 -0
  45. package/dist/src/services/assistantTools.d.ts +34 -1
  46. package/dist/src/services/assistantTools.d.ts.map +1 -1
  47. package/dist/src/services/assistantTools.js +931 -88
  48. package/dist/src/services/assistantTools.js.map +1 -1
  49. package/dist/src/services/assistantTrace.d.ts +4 -0
  50. package/dist/src/services/assistantTrace.d.ts.map +1 -1
  51. package/dist/src/services/assistantTrace.js +9 -0
  52. package/dist/src/services/assistantTrace.js.map +1 -1
  53. package/dist/src/services/configService.d.ts +9 -0
  54. package/dist/src/services/configService.d.ts.map +1 -1
  55. package/dist/src/services/configService.js +5 -2
  56. package/dist/src/services/configService.js.map +1 -1
  57. package/dist/src/services/messageQuery.d.ts +18 -0
  58. package/dist/src/services/messageQuery.d.ts.map +1 -1
  59. package/dist/src/services/messageQuery.js +29 -0
  60. package/dist/src/services/messageQuery.js.map +1 -1
  61. package/dist/src/services/outboundSend.d.ts +53 -0
  62. package/dist/src/services/outboundSend.d.ts.map +1 -0
  63. package/dist/src/services/outboundSend.js +90 -0
  64. package/dist/src/services/outboundSend.js.map +1 -0
  65. package/dist/src/services/pythonBridge.d.ts.map +1 -1
  66. package/dist/src/services/pythonBridge.js +1 -0
  67. package/dist/src/services/pythonBridge.js.map +1 -1
  68. package/dist/src/services/wechatClient.d.ts +125 -0
  69. package/dist/src/services/wechatClient.d.ts.map +1 -0
  70. package/dist/src/services/wechatClient.js +344 -0
  71. package/dist/src/services/wechatClient.js.map +1 -0
  72. package/dist/src/services/wechatMedia.d.ts +44 -0
  73. package/dist/src/services/wechatMedia.d.ts.map +1 -0
  74. package/dist/src/services/wechatMedia.js +155 -0
  75. package/dist/src/services/wechatMedia.js.map +1 -0
  76. package/dist/src/services/wechatMessageService.d.ts +11 -0
  77. package/dist/src/services/wechatMessageService.d.ts.map +1 -1
  78. package/dist/src/services/wechatMessageService.js +55 -11
  79. package/dist/src/services/wechatMessageService.js.map +1 -1
  80. package/dist/src/types.d.ts +4 -0
  81. package/dist/src/types.d.ts.map +1 -1
  82. package/dist/src/utils/python.js +2 -2
  83. package/dist/src/utils/python.js.map +1 -1
  84. package/docs/BRANCHES.md +37 -0
  85. package/docs/CONTACT_DB_SCHEMA.md +1085 -0
  86. package/docs/DECISIONS.md +1624 -0
  87. package/docs/EXTENDING.md +98 -10
  88. package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +6 -0
  89. package/docs/HEALTH-CHECK.md +12 -7
  90. package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +2 -2
  91. package/docs/MCP.md +58 -4
  92. package/docs/PROJECT_STATE.md +180 -11
  93. package/docs/RELEASING.md +115 -1
  94. package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
  95. package/logo.png +0 -0
  96. package/mcp-server/http.ts +108 -0
  97. package/mcp-server/httpConfig.ts +126 -0
  98. package/mcp-server/index.ts +359 -681
  99. package/package.json +5 -3
  100. package/resources/js/graph3d/NOTICE.txt +35 -0
  101. package/resources/js/graph3d/d3-binarytree.min.js +2 -0
  102. package/resources/js/graph3d/d3-dispatch.min.js +2 -0
  103. package/resources/js/graph3d/d3-force-3d.min.js +2 -0
  104. package/resources/js/graph3d/d3-octree.min.js +2 -0
  105. package/resources/js/graph3d/d3-quadtree.min.js +2 -0
  106. package/resources/js/graph3d/d3-timer.min.js +2 -0
  107. package/resources/js/graph3d/layout.mjs +63 -0
  108. package/resources/js/graph3d/three.min.js +6 -0
  109. package/resources/js/graph3d/viewer2d.js +448 -0
  110. package/resources/panel/ball-position.cjs +48 -2
  111. package/resources/panel/index.html +18 -4
  112. package/resources/panel/main.cjs +137 -5
  113. package/resources/panel/mascot-base.png +0 -0
  114. package/resources/panel/mascot-blink-1.png +0 -0
  115. package/resources/panel/mascot-blink-2.png +0 -0
  116. package/resources/panel/mascot-iris.png +0 -0
  117. package/resources/panel/mascot-lift-down-1.png +0 -0
  118. package/resources/panel/mascot-lift-down-2.png +0 -0
  119. package/resources/panel/mascot-lift-down-3.png +0 -0
  120. package/resources/panel/mascot-lift-down-4.png +0 -0
  121. package/resources/panel/mascot-lift-down-5.png +0 -0
  122. package/resources/panel/mascot-lift-down-6.png +0 -0
  123. package/resources/panel/mascot-lift-up-1.png +0 -0
  124. package/resources/panel/mascot-lift-up-2.png +0 -0
  125. package/resources/panel/mascot-lift-up-3.png +0 -0
  126. package/resources/panel/mascot-lift-up-4.png +0 -0
  127. package/resources/panel/mascot-lift-up-5.png +0 -0
  128. package/resources/panel/mascot-lift-up-6.png +0 -0
  129. package/resources/panel/mascot-peek.png +0 -0
  130. package/resources/panel/mascot-tickle-1.png +0 -0
  131. package/resources/panel/mascot-tickle-2.png +0 -0
  132. package/resources/panel/mascot-tickle-3.png +0 -0
  133. package/resources/panel/mascot-tickle-4.png +0 -0
  134. package/resources/panel/panel.css +205 -59
  135. package/resources/panel/preload.cjs +26 -0
  136. package/resources/panel/renderer.js +646 -17
  137. package/scripts/_utils.py +1203 -931
  138. package/scripts/backfill_articles.py +202 -21
  139. package/scripts/biz_daily.py +26 -34
  140. package/scripts/bonds.py +331 -0
  141. package/scripts/chat_notes.py +561 -470
  142. package/scripts/chat_stats.py +8 -7
  143. package/scripts/check_readme_commands.py +426 -0
  144. package/scripts/compile_wiki.py +187 -59
  145. package/scripts/contact_schema.py +339 -0
  146. package/scripts/data/wechat_emoji.json +225 -0
  147. package/scripts/export_chat_html.py +3 -1
  148. package/scripts/fav_notes.py +4 -1
  149. package/scripts/graph_2d.py +151 -0
  150. package/scripts/graph_3d.py +646 -0
  151. package/scripts/health_check.py +163 -26
  152. package/scripts/mcp_bridge.py +6 -7
  153. package/scripts/nt_decrypt.py +3 -3
  154. package/scripts/panel_frames.py +683 -0
  155. package/scripts/pipeline.py +21 -10
  156. package/scripts/quality_eval.py +5 -0
  157. package/scripts/rag_chat.py +33 -16
  158. package/scripts/reply_debt.py +26 -6
  159. package/scripts/semantic_search.py +130 -22
  160. package/scripts/vault_rag.py +42 -16
  161. package/scripts/vault_search.py +44 -8
  162. package/scripts/wechat_emoticon.py +6 -0
  163. package/scripts/wechat_voice.py +9 -0
  164. package/scripts/wiki_lint.py +124 -24
  165. package/src/core/dbPathService.ts +22 -4
  166. package/src/panel/server.ts +58 -1
  167. package/src/services/assistantEval.ts +323 -12
  168. package/src/services/assistantScenes.ts +342 -0
  169. package/src/services/assistantService.ts +107 -6
  170. package/src/services/assistantSkills.ts +364 -0
  171. package/src/services/assistantTools.ts +2452 -1640
  172. package/src/services/assistantTrace.ts +9 -0
  173. package/src/services/configService.ts +415 -403
  174. package/src/services/messageQuery.ts +29 -0
  175. package/src/services/outboundSend.ts +126 -0
  176. package/src/services/pythonBridge.ts +1 -0
  177. package/src/services/wechatClient.ts +380 -0
  178. package/src/services/wechatMedia.ts +166 -0
  179. package/src/services/wechatMessageService.ts +62 -11
  180. package/src/types.ts +4 -0
  181. package/src/utils/python.ts +2 -2
  182. package/dist/src/utils/wechatEmoji.d.ts +0 -5
  183. package/dist/src/utils/wechatEmoji.d.ts.map +0 -1
  184. package/dist/src/utils/wechatEmoji.js +0 -35
  185. package/dist/src/utils/wechatEmoji.js.map +0 -1
@@ -0,0 +1,339 @@
1
+ # -*- coding: utf-8 -*-
2
+ """่งฃ `contact.db` / `chat_room.ext_buffer` ้‡Œ**ๅทฒ็ป้ชŒ่ฏ่ฟ‡**็š„้‚ฃๅ‡ ไธชๅญ—ๆฎต๏ผˆๅช่ฏป๏ผ‰ใ€‚
3
+
4
+ ไธบไป€ไนˆๅช่งฃ"้ชŒ่ฏ่ฟ‡็š„"๏ผš้กถๅฑ‚ `#3`/`#4` ็š„่ฏญไน‰ๅœจๆœฌๆœบๅฎžๆต‹้‡Œ่ขซไธ‰ๆฌก่ฏไผชโ€”โ€”ไธๆ˜ฏ
5
+ `chatroom_member.member_id` ็š„ๆœ€ๅคง/ๆœ€ๅฐๅ€ผใ€ไธๆ˜ฏ `chat_room.id`ใ€ไนŸไธๅœจๆถˆๆฏๅบ“
6
+ `message_*.db` ็š„ไปปไฝ• id ๅˆ—้‡Œใ€‚่ฏญไน‰ๆฒกๅฎšๅฐฑไธ็ป™ๅฎƒ็ผ–ๅๅญ—๏ผšๆœฌ่„šๆœฌๆŠŠๅฎƒไปฌๅŽŸๆ ทๆ”พ่ฟ›
7
+ `unrecognized`๏ผŒ่ฐไนŸๅˆซ็…ง็€ไธ€ไธช็Œœๅ‡บๆฅ็š„ๅๅญ—ๅ†™้€ป่พ‘ใ€‚็ป“ๆž„็ฌ”่ฎฐ่ง
8
+ `docs/CONTACT_DB_SCHEMA.md`๏ผŒๅ–่ˆ่ง D-067 / D-068ใ€‚
9
+
10
+ ๅทฒ้ชŒ่ฏใ€ๅ› ่€Œ่พ“ๅ‡บ็š„ๅญ—ๆฎต๏ผš
11
+
12
+ RoomData
13
+ #1 (้‡ๅค) ๆˆๅ‘˜ โ€”โ€” ๆฏไธชๆˆๅ‘˜๏ผš1=userName 2=displayName 3=status 4=inviter
14
+ #5 (้‡ๅค) = ๆˆๅ‘˜้‡Œ **status ไฝ 11 ็ฝฎไฝ**็š„้‚ฃๆ‰น id๏ผˆๆœฌๆœบ 77/77 ไธŽ่ฏฅ้›†ๅˆๅฎŒๅ…จ็›ธ็ญ‰๏ผ›
15
+ ๅฎƒ**ไธๆ˜ฏ**"้ขๅค–็š„ๅ‚ไธŽ่€…"โ€”โ€”ๆ—ฉๅ‰้‚ฃไนˆๅซๆ˜ฏ้”™็š„๏ผŒ่ง DECISIONS D-070๏ผ‰
16
+
17
+ ่”็ณปไบบๆจกๅผ๏ผˆ`--contacts`๏ผ‰๏ผš่ฏป `contact.extra_buffer` โ€”โ€” ๅŒๆ ทๅช่พ“ๅ‡บ**ๅทฒ้ชŒ่ฏ**็š„ๅญ—ๆฎต๏ผš
18
+
19
+ #5 (ld) ๅ›ฝๅฎถ/ๅœฐๅŒบไปฃ็ ๏ผˆISO 3166-1 alpha-2๏ผ‰โ€”โ€” ๆœฌๆœบ 25/25 ่ฝๅœจ็ ่กจใ€ไผ—ๆ•ฐ CN ๅ  94%
20
+ #13 (varint) = ่ฏฅ่กŒ `biz_info.type`๏ผˆ่ดฆๅทๆœๅŠก็ฑปๅž‹๏ผ‰โ€”โ€” ๆœฌๆœบ 687/687 ้€่กŒ็›ธ็ญ‰
21
+ #41 (varint) = ่ฏฅ่กŒ่ต„ๆ–™็š„"ๆœ€่ฟ‘ๆ›ดๆ–ฐๆ—ถ้—ด"๏ผˆepoch ็ง’๏ผ›0 ่กจ็คบๆ˜พๅผๆœช่ฎพ็ฝฎ๏ผŒๆŒ‰็ผบ็œๅค„็†๏ผ‰
22
+
23
+ **ๆ•…ๆ„ไธ่พ“ๅ‡บ** `#4`/`#9`๏ผšๅฎƒไปฌๅทฒๅฎšๆ€งไธบ"่ฏฅ่ดฆๅท่‡ช่บซ่ต„ๆ–™ๆ–‡ๆœฌ็š„ๆฑ‡้›†"๏ผˆๅซไธปไฝ“ๅ/่œๅ•ๆŒ‰้’ฎๅ๏ผ‰๏ผŒ
24
+ ๆ˜ฏ**ๅ†…ๅฎนไธๆ˜ฏๅญ—ๆฎตๅ**๏ผŒๆ‰“่ฟ› CLI ่พ“ๅ‡บ็ญ‰ไบŽๆŠŠ่ดฆๅท่ต„ๆ–™ๆผๅ‡บๅŽป โ€”โ€” ๅฎƒไปฌๅชไปฅ้•ฟๅบฆๅ‡บ็Žฐๅœจ `unrecognized` ้‡Œใ€‚
25
+ ๅ…ถไฝ™ๅญ—ๆฎตๆœชๅฎšๅ๏ผŒไธ€ๅพ‹ๅŽŸๆ ท่ฟ› `unrecognized`๏ผˆ้”ฎๅฐฑๆ˜ฏๅญ—ๆฎตๅท๏ผ‰ใ€‚ๅ–่ˆ่ง D-068 / D-082 / D-084 / D-088ใ€‚
26
+
27
+ ็”จๆณ•๏ผš
28
+ python scripts/contact_schema.py --json
29
+ python scripts/contact_schema.py --limit 5 # ๅช็œ‹ๅ‰ 5 ไธช็พค
30
+ python scripts/contact_schema.py --room <็พค username ๆˆ– id>
31
+ python scripts/contact_schema.py --contacts --limit 5 # ๆ”น็œ‹่”็ณปไบบ๏ผˆ#5 ๅœฐๅŒบ็  / #13 ๆœๅŠก็ฑปๅž‹ / #41 ๆ›ดๆ–ฐๆ—ถ้—ด๏ผ‰
32
+ """
33
+ import argparse
34
+ import json
35
+ import os
36
+ import re
37
+ import sys
38
+ import time
39
+
40
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
41
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
42
+
43
+ # ๆ‰‹่ท‘ๆ—ถ stdout ๆ˜ฏๆœฌๆœบ็ผ–็ ๏ผˆWindows ๆ˜ฏ GBK๏ผ‰๏ผšไธญๆ–‡ไผšไนฑ็ ใ€้ž GBK ๅญ—็ฌฆ็›ดๆŽฅๅดฉๆމๆ•ดๆฌก่พ“ๅ‡บ
44
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
45
+
46
+
47
+ def varint(buf, i):
48
+ val = 0
49
+ shift = 0
50
+ while True:
51
+ if i >= len(buf):
52
+ raise ValueError('varint ่ถŠ็•Œ')
53
+ b = buf[i]
54
+ i += 1
55
+ val |= (b & 0x7F) << shift
56
+ if not (b & 0x80):
57
+ return val, i
58
+ shift += 7
59
+ if shift > 63:
60
+ raise ValueError('varint ่ฟ‡้•ฟ')
61
+
62
+
63
+ def walk(buf):
64
+ """่ตฐไธ€้ wire format๏ผŒ่ฟ”ๅ›ž [(ๅญ—ๆฎตๅท, wire type, ๅ€ผ)]ใ€‚
65
+
66
+ **ไธ่ฎค่ฏ†็š„ wire type ไธ€ๅพ‹ๆŠ›้”™**๏ผšๅฎๅฏๆ•ดๆกๆŠฅ"่งฃไธๅŠจ"๏ผŒไนŸไธๆŠŠ้”™ไฝ็š„ๅญ—่Š‚ๅฝ“ๆˆๅญ—ๆฎตๅ€ผ
67
+ โ€”โ€”้‚ฃๆญฃๆ˜ฏ"็œ‹็€ๅƒ"็š„ๆฅๆบใ€‚
68
+ """
69
+ i = 0
70
+ out = []
71
+ while i < len(buf):
72
+ tag, i = varint(buf, i)
73
+ fno, wt = tag >> 3, tag & 7
74
+ if fno == 0 or wt in (3, 4):
75
+ raise ValueError('ๅญ—ๆฎตๅท/wire type ๅผ‚ๅธธ๏ผˆfno=%d wt=%d๏ผ‰' % (fno, wt))
76
+ if wt == 0:
77
+ v, i = varint(buf, i)
78
+ out.append((fno, wt, v))
79
+ elif wt == 2:
80
+ ln, i = varint(buf, i)
81
+ if i + ln > len(buf):
82
+ raise ValueError('้•ฟๅบฆ่ถŠ็•Œ')
83
+ out.append((fno, wt, buf[i:i + ln]))
84
+ i += ln
85
+ elif wt == 5:
86
+ if i + 4 > len(buf):
87
+ raise ValueError('fixed32 ่ถŠ็•Œ')
88
+ out.append((fno, wt, buf[i:i + 4]))
89
+ i += 4
90
+ elif wt == 1:
91
+ if i + 8 > len(buf):
92
+ raise ValueError('fixed64 ่ถŠ็•Œ')
93
+ out.append((fno, wt, buf[i:i + 8]))
94
+ i += 8
95
+ else:
96
+ raise ValueError('ๆœช็Ÿฅ wire type %d' % wt)
97
+ return out
98
+
99
+
100
+ def _text(raw):
101
+ if raw is None:
102
+ return None
103
+ try:
104
+ return raw.decode('utf-8')
105
+ except UnicodeDecodeError:
106
+ return None # ไธๆ˜ฏ UTF-8 ๅฐฑๅˆซ็กฌๅฝ“ๅญ—็ฌฆไธฒ็”จ๏ผˆ่ฟ”ๅ›ž None๏ผŒ่ฐƒ็”จๆ–นๆŒ‰ๆœช็Ÿฅๅค„็†๏ผ‰
107
+
108
+
109
+ def decode_room(blob):
110
+ """ๆŠŠไธ€ไธช ext_buffer ่งฃๆˆ {members, statusBit11Ids, unrecognized}๏ผ›่งฃไธๅŠจๅฐฑๆŠ›้”™ใ€‚"""
111
+ members = []
112
+ extra_ids = []
113
+ unrecognized = {}
114
+ for fno, wt, value in walk(blob):
115
+ if fno == 1 and wt == 2:
116
+ inner = walk(value)
117
+ # **้”ฎๅ›บๅฎš**๏ผš็ผบ #2 ็š„ๆˆๅ‘˜ไนŸ่ฆๆœ‰ displayName ่ฟ™ไธช้”ฎ๏ผˆๅ€ผไธบ None๏ผ‰๏ผŒ
118
+ # ๅฆๅˆ™ JSON ้‡Œๆ—ถๆœ‰ๆ—ถๆ— ๏ผŒไธ‹ๆธธๆฏๆฌก้ƒฝๅพ—ๅ…ˆ้—ฎ"้”ฎๅœจไธๅœจ"โ€”โ€”้‚ฃ็งๅฝข็Šถ่ฟŸๆ—ฉ่ขซๆผๅˆคๆˆ"ๆฒกๅๅญ—"ใ€‚
119
+ member = {'userName': None, 'displayName': None, 'status': None, 'inviter': None}
120
+ for m_fno, m_wt, m_val in inner:
121
+ if m_fno in (1, 2, 4) and m_wt == 2:
122
+ key = {1: 'userName', 2: 'displayName', 4: 'inviter'}[m_fno]
123
+ member[key] = _text(m_val)
124
+ elif m_fno == 3 and m_wt == 0:
125
+ member['status'] = m_val
126
+ else:
127
+ member.setdefault('unrecognized', {})['%d' % m_fno] = (
128
+ len(m_val) if m_wt == 2 else m_val)
129
+ members.append(member)
130
+ elif fno == 5 and wt == 2:
131
+ extra_ids.append(_text(value))
132
+ else:
133
+ # ไธ็Œœ๏ผšๅŽŸๆ ทๆ”พ่ฟ›ๆฅ๏ผŒ้”ฎๅฐฑๆ˜ฏๅญ—ๆฎตๅท
134
+ unrecognized['%d' % fno] = (len(value) if wt == 2 else value)
135
+ return {'members': members, 'statusBit11Ids': extra_ids, 'unrecognized': unrecognized}
136
+
137
+
138
+ # `contact.extra_buffer` ็š„ `#5` ๅชๅœจๅฎƒๆ˜ฏ"ๆฐๅฅฝไธคไธชๅคงๅ†™ ASCII ๅญ—ๆฏ"ๆ—ถๆ‰ๅฝ“ๅœฐๅŒบ็ ่พ“ๅ‡บใ€‚
139
+ REGION_RE = re.compile(r'^[A-Z]{2}$')
140
+
141
+
142
+ def decode_contact(blob):
143
+ """่งฃ `contact.extra_buffer` ้‡Œ**ๅทฒ็ป้ชŒ่ฏ่ฟ‡**็š„้‚ฃๅ‡ ไธชๅญ—ๆฎต๏ผ›่งฃไธๅŠจๅฐฑๆŠ›้”™ใ€‚
144
+
145
+ ๅช่งฃ"้ชŒ่ฏ่ฟ‡็š„"๏ผˆD-068 / D-082 / D-084๏ผ‰๏ผŒไธ‰ไธช๏ผš
146
+
147
+ #5 (ld) ๅ›ฝๅฎถ/ๅœฐๅŒบไปฃ็ ๏ผˆISO 3166-1 alpha-2๏ผ‰โ€”โ€” ๆœฌๆœบ 25/25 ่ฝๅœจ็ ่กจใ€ไผ—ๆ•ฐ CN ๅ  94%
148
+ #13 (varint) = ่ฏฅ่กŒ `biz_info.type`๏ผˆ่ดฆๅทๆœๅŠก็ฑปๅž‹๏ผ‰โ€”โ€” ๆœฌๆœบ 687/687 ้€่กŒ็›ธ็ญ‰
149
+ #41 (varint) epoch ็ง’ = ่ฏฅ่กŒ่ต„ๆ–™็š„"ๆœ€่ฟ‘ๆ›ดๆ–ฐๆ—ถ้—ด"โ€”โ€” 0 ่กจ็คบๆ˜พๅผๆœช่ฎพ็ฝฎ๏ผŒๆŒ‰็ผบ็œๅค„็†
150
+
151
+ **ๆ•…ๆ„ไธ่พ“ๅ‡บ็š„**๏ผˆ่ฏดๆธ…ๆฅš๏ผŒๅ…ๅพ—ๅŽไบบไปฅไธบๆ˜ฏๆผไบ†๏ผ‰๏ผš
152
+
153
+ - `#4` / `#9`๏ผšๅทฒๅฎšๆ€งไธบ"่ฏฅ่ดฆๅท่‡ช่บซ่ต„ๆ–™ๆ–‡ๆœฌ็š„ๆฑ‡้›†"๏ผˆๅซไธปไฝ“ๅ/่œๅ•ๆŒ‰้’ฎๅ๏ผ‰๏ผŒๅฎƒๆ˜ฏ**ๅ†…ๅฎนไธๆ˜ฏๅญ—ๆฎตๅ** โ€”โ€”
154
+ ๅกž่ฟ› CLI ่พ“ๅ‡บ็ญ‰ไบŽๆŠŠ่ดฆๅท่ต„ๆ–™ๆผๅ‡บๅŽปใ€‚ๅฎƒไปฌๅœจ `unrecognized` ้‡Œ**ๅช็•™้•ฟๅบฆ**ใ€‚
155
+ - ้กถๅฑ‚ `#1`/`#2` ้‚ฃไธ€ๆ—๏ผˆ`contact.extra_buffer` ้‡Œ 44 ่กŒ็š„ OpenIM ๅฐๅฝข็Šถ๏ผ‰๏ผšๅชๆŠฅ `kind='openim'`๏ผŒ
156
+ ๅ…ถไฝ™ๆŒ‰้”ฎๅŽŸๆ ทๆ”พ่ฟ› `unrecognized`ใ€‚้‚ฃไธค้”ฎๅœจๆ–‡ๆกฃ้‡Œๆ˜ฏ openim ๅบ“็š„ๅค–้”ฎ๏ผŒๅฏน"่ฏป่”็ณปไบบ"ๆฒก็”จใ€‚
157
+ - ๅ…ถไฝ™ 30 ๆฅไธชๅญ—ๆฎต๏ผš**่ฏญไน‰ๆœชๅฎš๏ผŒไธ€ๅพ‹ๅŽŸๆ ท่ฟ› `unrecognized`**๏ผŒ้”ฎๅฐฑๆ˜ฏๅญ—ๆฎตๅท๏ผˆๅŒ `decode_room`๏ผ‰ใ€‚
158
+ """
159
+ fields = {}
160
+ for fno, wt, value in walk(blob):
161
+ fields.setdefault(fno, (wt, value))
162
+ if not fields:
163
+ return {'kind': None, 'region': None, 'bizType': None, 'updatedAt': None,
164
+ 'unrecognized': {}}
165
+ raw = {'%d' % fno: (len(v) if wt == 2 else v) for fno, (wt, v) in fields.items()}
166
+ # ๅฐๅฝข็Šถ = **ๅญ—ๆฎตๅทๅ…จ <= 9**๏ผˆๅคง proto ไธ€ๅฎšๆœ‰ #2..#38๏ผŒไธๅฏ่ƒฝๅ…จ <= 9๏ผ‰ใ€‚
167
+ # โš ๏ธ ๅˆซๅ†่ฆๆฑ‚"ๅฟ…้กปๆœ‰ #1"๏ผš้‚ฃๆ—้‡Œๆœ‰ไธ€่กŒๆฒกๆœ‰ #1๏ผŒๅŠ ไธŠ่ฟ™ไธชๆกไปถไผšๆŠŠๅฎƒ้”™ๅˆคๆˆ่”็ณปไบบ
168
+ # ๏ผˆๅ…จ้‡ๆ ธๅฏนๆ—ถๆŠ“ๅˆฐ๏ผškind ๅˆ†ๅธƒๅ˜ๆˆ 944/43๏ผŒๆ–‡ๆกฃๆ˜ฏ 940/44 + 2 ไธชๆฎ‹่กŒ๏ผ‰ใ€‚
169
+ if max(fields) <= 9:
170
+ return {'kind': 'openim', 'region': None, 'bizType': None, 'updatedAt': None,
171
+ 'unrecognized': raw}
172
+
173
+ out = {'kind': 'contact', 'region': None, 'bizType': None, 'updatedAt': None,
174
+ 'unrecognized': raw}
175
+ wt5, v5 = fields.get(5, (None, None))
176
+ if wt5 == 2 and isinstance(v5, bytes):
177
+ text = _text(v5)
178
+ if text and REGION_RE.match(text):
179
+ out['region'] = text
180
+ wt13, v13 = fields.get(13, (None, None))
181
+ if wt13 == 0:
182
+ out['bizType'] = v13
183
+ wt41, v41 = fields.get(41, (None, None))
184
+ if wt41 == 0 and v41: # 0 = ๆ˜พๅผ"ๆœช่ฎพ็ฝฎ"๏ผŒไธๆ˜ฏ 1970 ๅนด
185
+ out['updatedAt'] = v41
186
+ return out
187
+
188
+
189
+ def load_rows(args):
190
+ from _utils import get_db_config # noqa: E402
191
+ from nt_decrypt import require_sqlcipher # noqa: E402
192
+
193
+ cfg = get_db_config()
194
+ db = args.db or cfg['contact_db']
195
+ key = args.key or cfg['contact_key']
196
+ salt = args.salt or cfg['contact_salt']
197
+ if not db or not os.path.isfile(db):
198
+ raise SystemExit('ๆ‰พไธๅˆฐ contact ๅบ“๏ผš%s' % (db or '(ๆœช้…็ฝฎ)'))
199
+ if not key or not salt:
200
+ raise SystemExit('็ผบๅฐ‘ๅบ“ๅฏ†้’ฅ๏ผˆๅ…ˆ่ท‘ weflow-cli init๏ผŒๆˆ–ๆ˜พๅผ็ป™ --key/--salt๏ผ‰')
201
+ conn = require_sqlcipher().connect(db)
202
+ cur = conn.cursor()
203
+ cur.execute('PRAGMA key = "x\'%s%s\'";' % (key, salt))
204
+ sql = ('SELECT id, username, owner, ext_buffer FROM chat_room '
205
+ 'WHERE ext_buffer IS NOT NULL AND length(ext_buffer) > 0')
206
+ params = []
207
+ if args.room:
208
+ sql += ' AND (username = ? OR id = ?)'
209
+ params = [args.room, args.room]
210
+ sql += ' ORDER BY id LIMIT ?'
211
+ params.append(args.limit)
212
+ rows = cur.execute(sql, params).fetchall()
213
+ conn.close()
214
+ return rows
215
+
216
+
217
+ def load_contacts(args):
218
+ from _utils import get_db_config # noqa: E402
219
+ from nt_decrypt import require_sqlcipher # noqa: E402
220
+
221
+ cfg = get_db_config()
222
+ db = args.db or cfg['contact_db']
223
+ key = args.key or cfg['contact_key']
224
+ salt = args.salt or cfg['contact_salt']
225
+ if not db or not os.path.isfile(db):
226
+ raise SystemExit('ๆ‰พไธๅˆฐ contact ๅบ“๏ผš%s' % (db or '(ๆœช้…็ฝฎ)'))
227
+ if not key or not salt:
228
+ raise SystemExit('็ผบๅฐ‘ๅบ“ๅฏ†้’ฅ๏ผˆๅ…ˆ่ท‘ weflow-cli init๏ผŒๆˆ–ๆ˜พๅผ็ป™ --key/--salt๏ผ‰')
229
+ conn = require_sqlcipher().connect(db)
230
+ cur = conn.cursor()
231
+ cur.execute('PRAGMA key = "x\'%s%s\'";' % (key, salt))
232
+ sql = ('SELECT id, username, local_type, extra_buffer FROM contact '
233
+ 'WHERE extra_buffer IS NOT NULL AND length(extra_buffer) > 0')
234
+ params = []
235
+ if args.room:
236
+ sql += ' AND (username = ? OR id = ?)'
237
+ params = [args.room, args.room]
238
+ sql += ' ORDER BY id LIMIT ?'
239
+ params.append(args.limit)
240
+ rows = cur.execute(sql, params).fetchall()
241
+ conn.close()
242
+ return rows
243
+
244
+
245
+ def main(argv=None):
246
+ ap = argparse.ArgumentParser(
247
+ description='่ฏป contact.db ็š„ ext_buffer๏ผˆๅช่ฏป๏ผŒไธ่”็ฝ‘ใ€ไธ่ฐƒๆจกๅž‹๏ผ‰๏ผš้ป˜่ฎค่ฏป**็พค**๏ผŒ--contacts ๆ”น่ฏป**่”็ณปไบบ**')
248
+ ap.add_argument('--limit', type=int, default=20, help='ๆœ€ๅคš็œ‹ๅ‡ ไธช๏ผˆ็พค / ่”็ณปไบบ๏ผŒ้ป˜่ฎค 20๏ผ‰')
249
+ ap.add_argument('--room', help='ๅช็œ‹ๆŸไธช๏ผˆ็พค / ่”็ณปไบบ ็š„ username ๆˆ– id๏ผ‰')
250
+ ap.add_argument('--contacts', action='store_true',
251
+ help='ๆ”น่ฏป่”็ณปไบบ็š„ extra_buffer๏ผˆ#5 ๅœฐๅŒบ็  / #13 ๆœๅŠก็ฑปๅž‹ / #41 ่ต„ๆ–™ๆ›ดๆ–ฐๆ—ถ้—ด๏ผ‰๏ผŒ่€Œไธๆ˜ฏ็พค')
252
+ ap.add_argument('--db', help='contact.db ่ทฏๅพ„๏ผˆ้ป˜่ฎคๅ–้…็ฝฎ๏ผ‰')
253
+ ap.add_argument('--key', help='ๅบ“ๅฏ†้’ฅ๏ผˆ้ป˜่ฎคๅ–้…็ฝฎ๏ผ‰')
254
+ ap.add_argument('--salt', help='ๅบ“็›๏ผˆ้ป˜่ฎคๅ–้…็ฝฎ๏ผ‰')
255
+ ap.add_argument('--json', action='store_true', help='่พ“ๅ‡บๆœบๅ™จๅฏ่ฏป็ป“ๆžœ')
256
+ args = ap.parse_args(argv)
257
+
258
+ try:
259
+ rows = load_contacts(args) if args.contacts else load_rows(args)
260
+ except SystemExit:
261
+ raise
262
+ except Exception as exc:
263
+ if args.json:
264
+ print(json.dumps({'success': False, 'error': str(exc)[:200]}, ensure_ascii=False))
265
+ else:
266
+ print('่ฏปๅบ“ๅคฑ่ดฅ๏ผš%s' % str(exc)[:200], file=sys.stderr)
267
+ return 1
268
+
269
+ if args.contacts:
270
+ contacts = []
271
+ failures = []
272
+ for rid, username, local_type, blob in rows:
273
+ try:
274
+ decoded = decode_contact(bytes(blob))
275
+ except Exception as exc:
276
+ # ๅŒ็พค้‚ฃๆก๏ผš่งฃไธๅŠจๅฐฑ่ฏด่งฃไธๅŠจ๏ผŒไธ่ฟ”ๅ›žไธ€ๆก"ไป€ไนˆ้ƒฝๆฒกๆœ‰"็š„่”็ณปไบบ
277
+ failures.append({'id': rid, 'username': username, 'error': str(exc)[:80]})
278
+ continue
279
+ contacts.append({'id': rid, 'username': username, 'localType': local_type,
280
+ 'kind': decoded['kind'], 'region': decoded['region'],
281
+ 'bizType': decoded['bizType'], 'updatedAt': decoded['updatedAt'],
282
+ 'unrecognized': decoded['unrecognized']})
283
+ if args.json:
284
+ print(json.dumps({'success': True, 'action': 'contact-schema',
285
+ 'contacts': contacts, 'failures': failures,
286
+ 'readsLocalData': True, 'invokesAI': False,
287
+ 'sendsNothing': True}, ensure_ascii=False))
288
+ else:
289
+ print('็œ‹ไบ† %d ไธช่”็ณปไบบ๏ผ›่งฃไธๅŠจ %d ไธช' % (len(contacts), len(failures)))
290
+ print('๏ผˆๅช่พ“ๅ‡บๅทฒ้ชŒ่ฏๅญ—ๆฎต๏ผš#5 ๅœฐๅŒบ็  / #13 ๆœๅŠก็ฑปๅž‹ / #41 ่ต„ๆ–™ๆ›ดๆ–ฐๆ—ถ้—ด๏ผ›'
291
+ '#4/#9 ๆ˜ฏ่ดฆๅท่ต„ๆ–™ๆ–‡ๆœฌ๏ผŒๅฑžๅ†…ๅฎน๏ผŒไธๆ‰“ๅฐ๏ผŒๅชๅœจ"ๆœช่ฏ†ๅˆซ"้‡Œ็•™้•ฟๅบฆ๏ผ‰')
292
+ for c in contacts:
293
+ when = time.strftime('%Y-%m-%d', time.gmtime(c['updatedAt'])) if c['updatedAt'] else '-'
294
+ print(' %-28s local_type=%-3s kind=%-8s ๅœฐๅŒบ=%-3s ๆœๅŠก็ฑปๅž‹=%-4s ๆ›ดๆ–ฐ=%-11s ๆœช่ฏ†ๅˆซ %d ไธช'
295
+ % (c['username'], c['localType'], c['kind'], c['region'] or '-',
296
+ c['bizType'] if c['bizType'] is not None else '-', when,
297
+ len(c['unrecognized'])))
298
+ for f in failures:
299
+ print('่งฃไธๅŠจ: id=%s %s' % (f['id'], f['error']), file=sys.stderr)
300
+ return 0
301
+
302
+ rooms = []
303
+ failures = []
304
+ for rid, username, owner, blob in rows:
305
+ try:
306
+ decoded = decode_room(bytes(blob))
307
+ except Exception as exc:
308
+ # **่งฃไธๅŠจๅฐฑ่ฏด่งฃไธๅŠจ**๏ผŒไธ่ฟ”ๅ›ž็ฉบๆˆๅ‘˜่กจๅ†’ๅ……"่ฟ™ไธช็พคๆฒกไบบ"
309
+ failures.append({'id': rid, 'username': username, 'error': str(exc)[:80]})
310
+ continue
311
+ rooms.append({'id': rid, 'username': username, 'owner': owner,
312
+ 'memberCount': len(decoded['members']),
313
+ 'members': decoded['members'],
314
+ 'statusBit11Ids': decoded['statusBit11Ids'],
315
+ 'unrecognized': decoded['unrecognized']})
316
+
317
+ if args.json:
318
+ print(json.dumps({'success': True, 'action': 'contact-schema', 'rooms': rooms,
319
+ 'failures': failures, 'readsLocalData': True,
320
+ 'invokesAI': False, 'sendsNothing': True}, ensure_ascii=False))
321
+ else:
322
+ print('็œ‹ไบ† %d ไธช็พค๏ผ›่งฃไธๅŠจ %d ไธช' % (len(rooms), len(failures)))
323
+ for room in rooms:
324
+ print('\n็พค %s๏ผˆid=%s๏ผ‰ๆˆๅ‘˜ %d ไธช๏ผ›ๆœช่ฏ†ๅˆซๅญ—ๆฎต %s' % (
325
+ room['username'], room['id'], room['memberCount'],
326
+ ','.join(sorted(room['unrecognized'])) or 'ๆ— '))
327
+ for m in room['members'][:5]:
328
+ print(' %-28s %-12s status=%-8s inviter=%s' % (
329
+ m.get('userName'), m.get('displayName'), m.get('status'), m.get('inviter')))
330
+ if room['statusBit11Ids']:
331
+ print(' #5๏ผˆstatus ไฝ 11 ็š„ๆˆๅ‘˜๏ผ‰: %s'
332
+ % ', '.join(x or '?' for x in room['statusBit11Ids'][:5]))
333
+ for f in failures:
334
+ print('่งฃไธๅŠจ: id=%s %s' % (f['id'], f['error']), file=sys.stderr)
335
+ return 0
336
+
337
+
338
+ if __name__ == '__main__':
339
+ sys.exit(main())
@@ -0,0 +1,225 @@
1
+ {
2
+ "_comment": "WeChat face codes -> Unicode emoji. WeChat ships its own artwork (private-use code points such as U+E414 plus smileys/*.png), so several entries here are the closest standard equivalent rather than an exact match. Unknown codes are left untouched by the converters.",
3
+ "_aliases_note": "Chinese face names as they appear in WeChat message text, e.g. [ๅฎณ็พž].",
4
+
5
+ "ๅพฎ็ฌ‘": "๐Ÿ˜Š",
6
+ "ๆ’‡ๅ˜ด": "๐Ÿ˜–",
7
+ "่‰ฒ": "๐Ÿ˜",
8
+ "ๅ‘ๅ‘†": "๐Ÿ˜ณ",
9
+ "ๅพ—ๆ„": "๐Ÿ˜Ž",
10
+ "ๆตๆณช": "๐Ÿ˜ญ",
11
+ "ๅฎณ็พž": "๐Ÿ˜ณ",
12
+ "้—ญๅ˜ด": "๐Ÿค",
13
+ "็ก": "๐Ÿ˜ด",
14
+ "ๅคงๅ“ญ": "๐Ÿ˜ญ",
15
+ "ๅฐดๅฐฌ": "๐Ÿ˜…",
16
+ "ๅ‘ๆ€’": "๐Ÿ˜ก",
17
+ "่ฐƒ็šฎ": "๐Ÿ˜œ",
18
+ "ๅ‘ฒ็‰™": "๐Ÿ˜",
19
+ "ๆƒŠ่ฎถ": "๐Ÿ˜ฒ",
20
+ "้šพ่ฟ‡": "๐Ÿ˜”",
21
+ "้…ท": "๐Ÿ˜Ž",
22
+ "ๅ†ทๆฑ—": "๐Ÿ˜ฐ",
23
+ "ๆŠ“็‹‚": "๐Ÿ˜ซ",
24
+ "ๅ": "๐Ÿคฎ",
25
+ "ๅท็ฌ‘": "๐Ÿคญ",
26
+ "ๅฏ็ˆฑ": "๐Ÿ˜Š",
27
+ "็™ฝ็œผ": "๐Ÿ™„",
28
+ "ๅ‚ฒๆ…ข": "๐Ÿ˜ค",
29
+ "้ฅฅ้ฅฟ": "๐Ÿคค",
30
+ "ๅ›ฐ": "๐Ÿ˜ช",
31
+ "ๆƒŠๆ": "๐Ÿ˜ฑ",
32
+ "ๆตๆฑ—": "๐Ÿ˜“",
33
+ "ๆ†จ็ฌ‘": "๐Ÿ˜„",
34
+ "ๅคงๅ…ต": "๐Ÿ˜Ž",
35
+ "ๅฅ‹ๆ–—": "๐Ÿ˜ค",
36
+ "ๅ’’้ช‚": "๐Ÿคฌ",
37
+ "็–‘้—ฎ": "โ“",
38
+ "ๅ˜˜": "๐Ÿคซ",
39
+ "ๆ™•": "๐Ÿ˜ต",
40
+ "ๆŠ˜็ฃจ": "๐Ÿ˜ฉ",
41
+ "่กฐ": "๐Ÿ˜ž",
42
+ "้ชท้ซ…": "๐Ÿ’€",
43
+ "ๆ•ฒๆ‰“": "๐Ÿ”จ",
44
+ "ๅ†่ง": "๐Ÿ‘‹",
45
+ "ๆ“ฆๆฑ—": "๐Ÿ˜…",
46
+ "ๆŠ ้ผป": "๐Ÿคง",
47
+ "้ผ“ๆŽŒ": "๐Ÿ‘",
48
+ "็ณ—ๅคงไบ†": "๐Ÿ˜ณ",
49
+ "ๅ็ฌ‘": "๐Ÿ˜",
50
+ "ๅทฆๅ“ผๅ“ผ": "๐Ÿ˜ค",
51
+ "ๅณๅ“ผๅ“ผ": "๐Ÿ˜ค",
52
+ "ๅ“ˆๆฌ ": "๐Ÿฅฑ",
53
+ "้„™่ง†": "๐Ÿ–•",
54
+ "ๅง”ๅฑˆ": "๐Ÿฅบ",
55
+ "ๅฟซๅ“ญไบ†": "๐Ÿ˜ข",
56
+ "้˜ด้™ฉ": "๐Ÿ˜",
57
+ "ไบฒไบฒ": "๐Ÿ˜˜",
58
+ "ๅ“": "๐Ÿ˜ฑ",
59
+ "ๅฏๆ€œ": "๐Ÿฅบ",
60
+ "่œๅˆ€": "๐Ÿ”ช",
61
+ "่ฅฟ็“œ": "๐Ÿ‰",
62
+ "ๅ•ค้…’": "๐Ÿบ",
63
+ "็ฏฎ็ƒ": "๐Ÿ€",
64
+ "ไน’ไน“": "๐Ÿ“",
65
+ "ๅ’–ๅ•ก": "โ˜•",
66
+ "้ฅญ": "๐Ÿš",
67
+ "็Œชๅคด": "๐Ÿท",
68
+ "็Žซ็‘ฐ": "๐ŸŒน",
69
+ "ๅ‡‹่ฐข": "๐Ÿฅ€",
70
+ "็คบ็ˆฑ": "โค๏ธ",
71
+ "็ˆฑๅฟƒ": "โค๏ธ",
72
+ "ๅฟƒ็ขŽ": "๐Ÿ’”",
73
+ "่›‹็ณ•": "๐ŸŽ‚",
74
+ "้—ช็”ต": "โšก",
75
+ "็‚ธๅผน": "๐Ÿ’ฃ",
76
+ "ๅˆ€": "๐Ÿ”ช",
77
+ "่ถณ็ƒ": "โšฝ",
78
+ "็“ข่™ซ": "๐Ÿž",
79
+ "ไพฟไพฟ": "๐Ÿ’ฉ",
80
+ "ๆœˆไบฎ": "๐ŸŒ™",
81
+ "ๅคช้˜ณ": "โ˜€๏ธ",
82
+ "็คผ็‰ฉ": "๐ŸŽ",
83
+ "ๆ‹ฅๆŠฑ": "๐Ÿค—",
84
+ "ๅผบ": "๐Ÿ‘",
85
+ "ๅผฑ": "๐Ÿ‘Ž",
86
+ "ๆกๆ‰‹": "๐Ÿค",
87
+ "่ƒœๅˆฉ": "โœŒ๏ธ",
88
+ "ๆŠฑๆ‹ณ": "๐Ÿ™",
89
+ "ๅ‹พๅผ•": "๐Ÿ’…",
90
+ "ๆ‹ณๅคด": "๐Ÿ‘Š",
91
+ "ๅทฎๅŠฒ": "๐Ÿ‘Ž",
92
+ "็ˆฑไฝ ": "๐ŸคŸ",
93
+ "NO": "๐Ÿ™…",
94
+ "OK": "๐Ÿ‘Œ",
95
+ "็ˆฑๆƒ…": "๐Ÿ’‘",
96
+ "้ฃžๅป": "๐Ÿ˜˜",
97
+ "่ทณ่ทณ": "๐Ÿ•บ",
98
+ "ๅ‘ๆŠ–": "๐Ÿ˜จ",
99
+ "ๆ€„็ซ": "๐Ÿ˜ ",
100
+ "่ฝฌๅœˆ": "๐ŸŒ€",
101
+ "็ฃ•ๅคด": "๐Ÿ™‡",
102
+ "ๅ›žๅคด": "๐Ÿ”™",
103
+ "่ทณ็ปณ": "๐Ÿƒ",
104
+ "ๆŒฅๆ‰‹": "๐Ÿ‘‹",
105
+ "ๆฟ€ๅŠจ": "๐Ÿ˜†",
106
+ "่ก—่ˆž": "๐Ÿ•บ",
107
+ "็Œฎๅป": "๐Ÿ˜˜",
108
+ "ๅทฆๅคชๆž": "โ˜ฏ๏ธ",
109
+ "ๅณๅคชๆž": "โ˜ฏ๏ธ",
110
+
111
+ "ๆ‚่„ธ": "๐Ÿคฆ",
112
+ "ๅฅธ็ฌ‘": "๐Ÿ˜",
113
+ "ๆœบๆ™บ": "๐Ÿค“",
114
+ "็šฑ็œ‰": "๐Ÿ˜Ÿ",
115
+ "่€ถ": "โœŒ๏ธ",
116
+ "ๅƒ็“œ": "๐Ÿ‰",
117
+ "ๅŠ ๆฒน": "๐Ÿ’ช",
118
+ "ๆฑ—": "๐Ÿ˜…",
119
+ "ๅคฉๅ•Š": "๐Ÿ˜ฑ",
120
+ "็คพไผš็คพไผš": "๐Ÿค",
121
+ "ๅ“‡": "๐Ÿ˜ฒ",
122
+ "666": "๐Ÿ‘",
123
+ "่ฎฉๆˆ‘็œ‹็œ‹": "๐Ÿ‘€",
124
+ "ๅนๆฐ”": "๐Ÿ˜ฎโ€๐Ÿ’จ",
125
+ "่‹ฆๆถฉ": "๐Ÿ˜–",
126
+ "่ฃ‚ๅผ€": "๐Ÿคฏ",
127
+ "็ฟป็™ฝ็œผ": "๐Ÿ™„",
128
+ "็ ดๆถ•ไธบ็ฌ‘": "๐Ÿ˜‚",
129
+ "ๆ— ่ฏญ": "๐Ÿ˜‘",
130
+ "ๅคฑๆœ›": "๐Ÿ˜ž",
131
+ "ๆๆƒง": "๐Ÿ˜ฑ",
132
+ "ๅบ†็ฅ": "๐ŸŽ‰",
133
+ "็บขๅŒ…": "๐Ÿงง",
134
+ "็ฆ": "๐Ÿงง",
135
+ "็™ผ": "๐Ÿ€„",
136
+ "็ƒŸ่Šฑ": "๐ŸŽ†",
137
+ "็ˆ†็ซน": "๐Ÿงจ",
138
+ "ๅˆๅ": "๐Ÿ™",
139
+
140
+ "ๆ—บๆŸด": "๐Ÿถ",
141
+ "ๅ›ง": "๐Ÿ˜…",
142
+ "ๅ˜ดๅ”‡": "๐Ÿ’‹",
143
+ "ๅฅฝ็š„": "๐Ÿ‘Œ",
144
+ "ๅ˜ฟๅ“ˆ": "๐Ÿ˜†",
145
+ "ๆ‰“่„ธ": "๐Ÿคฆ",
146
+ "ๆ„‰ๅฟซ": "๐Ÿ˜Š",
147
+ "็ฌ‘่„ธ": "๐Ÿ˜Š",
148
+ "็”Ÿ็—…": "๐Ÿค’",
149
+ "่„ธ็บข": "๐Ÿ˜Š",
150
+ "ๆ‚ ้—ฒ": "๐Ÿ˜Œ",
151
+ "Emm": "๐Ÿค”",
152
+ "้–‹็บขๅŒ…": "๐Ÿงง",
153
+ "ๅผ€็บขๅŒ…": "๐Ÿงง",
154
+ "Salute": "๐Ÿซก",
155
+
156
+ "Smile": "๐Ÿ˜Š",
157
+ "Grimace": "๐Ÿ˜–",
158
+ "Drool": "๐Ÿ˜",
159
+ "Scowl": "๐Ÿ˜ณ",
160
+ "CoolGuy": "๐Ÿ˜Ž",
161
+ "Sob": "๐Ÿ˜ญ",
162
+ "Shy": "๐Ÿ˜ณ",
163
+ "Silent": "๐Ÿค",
164
+ "Sleep": "๐Ÿ˜ด",
165
+ "Cry": "๐Ÿ˜ญ",
166
+ "Awkward": "๐Ÿ˜…",
167
+ "Angry": "๐Ÿ˜ก",
168
+ "Tongue": "๐Ÿ˜œ",
169
+ "Grin": "๐Ÿ˜",
170
+ "Surprise": "๐Ÿ˜ฒ",
171
+ "Frown": "๐Ÿ˜”",
172
+ "Ruthless": "๐Ÿ˜Ž",
173
+ "Blush": "๐Ÿ˜“",
174
+ "Scream": "๐Ÿ˜ซ",
175
+ "Puke": "๐Ÿคฎ",
176
+ "Chuckle": "๐Ÿคญ",
177
+ "Joyful": "๐Ÿ˜„",
178
+ "Slight": "๐Ÿ˜",
179
+ "Smug": "๐Ÿ˜ค",
180
+ "Hungry": "๐Ÿคค",
181
+ "Drowsy": "๐Ÿ˜ช",
182
+ "Panic": "๐Ÿ˜ฑ",
183
+ "Sweat": "๐Ÿ˜“",
184
+ "Laugh": "๐Ÿ˜„",
185
+ "Commando": "๐Ÿ˜Ž",
186
+ "Determined": "๐Ÿ˜ค",
187
+ "Scold": "๐Ÿคฌ",
188
+ "Shocked": "โ“",
189
+ "Shhh": "๐Ÿคซ",
190
+ "Dizzy": "๐Ÿ˜ต",
191
+ "Tormented": "๐Ÿ˜ฉ",
192
+ "Toasted": "๐Ÿ˜ž",
193
+ "Skull": "๐Ÿ’€",
194
+ "Hammer": "๐Ÿ”จ",
195
+ "Wave": "๐Ÿ‘‹",
196
+ "Speechless": "๐Ÿ˜…",
197
+ "NosePick": "๐Ÿคง",
198
+ "Clap": "๐Ÿ‘",
199
+ "Shame": "๐Ÿ˜ณ",
200
+ "Trick": "๐Ÿ˜",
201
+ "Yawn": "๐Ÿฅฑ",
202
+ "Pooh-pooh": "๐Ÿ™„",
203
+ "Shrunken": "๐Ÿฅบ",
204
+ "TearingUp": "๐Ÿ˜ข",
205
+ "Sly": "๐Ÿ˜",
206
+ "Kiss": "๐Ÿ˜˜",
207
+ "Wrath": "๐Ÿ˜ ",
208
+ "Whimper": "๐Ÿฅบ",
209
+ "Cleaver": "๐Ÿ”ช",
210
+ "Watermelon": "๐Ÿ‰",
211
+ "Beer": "๐Ÿบ",
212
+ "Basketball": "๐Ÿ€",
213
+ "PingPong": "๐Ÿ“",
214
+ "Coffee": "โ˜•",
215
+ "Rice": "๐Ÿš",
216
+ "Pig": "๐Ÿท",
217
+ "Rose": "๐ŸŒน",
218
+ "Wilt": "๐Ÿฅ€",
219
+ "Lips": "โค๏ธ",
220
+ "Heart": "โค๏ธ",
221
+ "BrokenHeart": "๐Ÿ’”",
222
+ "Cake": "๐ŸŽ‚",
223
+ "Lightning": "โšก",
224
+ "ThumbsUp": "๐Ÿ‘"
225
+ }
@@ -2379,7 +2379,9 @@ def main():
2379
2379
  # Voice: load the payloads so a cached transcript can be shown. Anything
2380
2380
  # not yet transcribed simply renders as `[่ฏญ้Ÿณ Nโ€ณ]`.
2381
2381
  if _WECHAT_VOICE:
2382
- VOICE_STATE['cache'] = wechat_voice.TranscriptCache(os.path.join(args.out, '.voice-cache'))
2382
+ # ็ผ“ๅญ˜็š„ๅฎถ็”ฑ `wechat_voice` ๅฎšไน‰๏ผšไปฅๅ‰่ฟ™้‡ŒๆŒ‰ `--out` ๆ‹ผ๏ผŒไบŽๆ˜ฏๅŒไธ€ๆฎต้Ÿณ้ข‘ๅœจไธๅŒ
2383
+ # ่พ“ๅ‡บ็›ฎๅฝ•ไธ‹ไผš่ขซๅ„่ฝฌๅ†™ไธ€้๏ผŒ่€Œๅก็‰‡้‚ฃๆก็บฟ่ฏป็š„ๅˆๆ˜ฏๅฆไธ€ๅค„๏ผˆ2026-10-01๏ผ‰ใ€‚
2384
+ VOICE_STATE['cache'] = wechat_voice.TranscriptCache(wechat_voice.DEFAULT_CACHE_DIR)
2383
2385
  try:
2384
2386
  VOICE_STATE['map'] = wechat_voice.load_voice_map(
2385
2387
  args.db, args.key, args.salt, args.passphrase, args.talker)
@@ -99,7 +99,10 @@ def classify_source(item: dict) -> str:
99
99
  def favorite_text(item: dict, fetch=None):
100
100
  """ไธ€ๆกๆ”ถ่—็š„ๆญฃๆ–‡๏ผšๆœฌๅœฐๆ–‡ๆœฌไผ˜ๅ…ˆ๏ผŒๆฒกๆœ‰๏ผˆๆˆ–ๅคช็Ÿญ๏ผ‰ๅฐฑๆŠ“้“พๆŽฅใ€‚่ฟ”ๅ›ž (ๆญฃๆ–‡, ๆฅๆบ่ฏดๆ˜Ž)ใ€‚
101
101
 
102
- ๆ”ถ่—ๅˆ†ไธค็ง๏ผš็ฌ”่ฎฐ/ๆ–‡ๆœฌ็ฑป**่‡ชๅธฆๅ†…ๅฎน**๏ผˆ`desc`/`content`๏ผ‰๏ผŒๆ–‡็ซ ็ฑปๅชๆœ‰ `link`ใ€‚
102
+ ๆ”ถ่—ๅˆ†ไธค็ง๏ผš็ฌ”่ฎฐ/ๆ–‡ๆœฌ็ฑป**่‡ชๅธฆๅ†…ๅฎน**๏ผˆ`desc`/`content`๏ผ‰๏ผŒๆ–‡็ซ ็ฑป**ๅŸบๆœฌ**ๅชๆœ‰ `link`โ€”โ€”
103
+ "ๅŸบๆœฌ"ๆ˜ฏ้‡ๅ‡บๆฅ็š„๏ผšๆœฌๆœบ 1,234 ็ฏ‡ๆ–‡็ซ ้‡Œๆœ‰ **7 ๆก**็š„็ฝ‘้กตๆ่ฟฐ๏ผˆ`weburlitem/pagedesc`๏ผ‰่ถ…่ฟ‡
104
+ `MIN_ARTICLE_CHARS`๏ผŒไผš่ขซๅฝ“ๆˆ"่‡ชๅธฆๆญฃๆ–‡"๏ผˆ้‚ฃๆ˜ฏ**ๆ‘˜่ฆไธๆ˜ฏๆญฃๆ–‡**๏ผ‰ใ€‚้—จๆง› 200 ๅญ—ๆŒกไฝไบ†็Ÿญๅ™ชๅฃฐ
105
+ ๏ผˆไพ‹ๅฆ‚ๆœฌๆœบ 20 ๆก `type=1` ็š„ `desc` ๅชๆœ‰ 49 ๅญ—ใ€ไธ”ๆ˜ฏ่ทฏๅพ„ๅผๅญ—็ฌฆไธฒ โ‡’ ๅ…จ้ƒจ่ฝ none๏ผ‰ใ€‚
103
106
  ๆŠ“ๅ–ๅค็”จๆ—ฅๆŠฅ้‚ฃๆก็บฟ็š„ `fetch_article_cached`โ€”โ€”ไธ้‡ๅ†™็ฌฌไบŒไปฝ๏ผˆ้‚ฃๅฅ—ๅพฎไฟก UA ็ป• WAFใ€
104
107
  gzipใ€้‡่ฏ•ๆ˜ฏ่ธฉๅ‡บๆฅ็š„๏ผ‰๏ผŒ่€Œไธ”**ๅธฆ็ผ“ๅญ˜**๏ผšๆ—ฅๆŠฅๆŠ“่ฟ‡็š„ๆ–‡็ซ ่ฟ™้‡Œ้›ถ็ฝ‘็ปœใ€‚
105
108