omp-wechat 1.2.1 → 1.3.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.
- package/README.md +6 -2
- package/dist/index.js +7 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ For boot-time persistence, install a launchd/systemd service via `/wechat instal
|
|
|
31
31
|
- **Singleton**: port lock guarantees one poll loop across all concurrent OMP/Pi processes — no duplicate replies
|
|
32
32
|
- **Failover**: 30s timer takes over automatically if the lock holder crashes
|
|
33
33
|
- **Bidirectional**: receive and reply to WeChat text messages
|
|
34
|
+
- **Image recognition**: inbound images are downloaded from WeChat CDN, AES-decrypted, and passed to the vision model
|
|
34
35
|
- **Per-chat sessions**: each WeChat chat gets an independent AI session (concurrent, isolated)
|
|
35
36
|
- **LRU pool**: caps memory usage by evicting least-recently-used sessions (default: 50)
|
|
36
37
|
- **Typing indicator**: native WeChat "Typing..." shown during AI processing
|
|
@@ -108,6 +109,8 @@ systemPrompt: |
|
|
|
108
109
|
| `systemPrompt` | Built-in | System prompt for WeChat chat sessions |
|
|
109
110
|
|
|
110
111
|
> **Model and tools are managed by OMP/Pi.** `createAgentSession()` automatically calls `discoverAuthStorage()`, reusing your existing `omp login` / `pi login` OAuth, `~/.omp/agent/agent.db` API keys, or `models.yml` config. This project never touches API keys.
|
|
112
|
+
>
|
|
113
|
+
> **Image recognition** requires a vision model role configured in OMP (e.g. `omp model role vision xfyun/xopkimik25`). If no vision role is set, inbound images are skipped — only the text placeholder is sent to the AI.
|
|
111
114
|
|
|
112
115
|
## Slash Commands
|
|
113
116
|
|
|
@@ -188,11 +191,12 @@ OMP-Wechat/
|
|
|
188
191
|
- **Reply-only**: iLink requires `context_token` from an inbound message; you cannot initiate conversations
|
|
189
192
|
- **1:1 only**: iLink Bot API does not support group chats
|
|
190
193
|
- **Single instance**: iLink allows only one bot connection per account
|
|
191
|
-
- **
|
|
194
|
+
- **Media**: inbound images are downloaded from WeChat CDN, AES-decrypted, and passed to the vision model (if `modelRoles.vision` is configured); voice/video remain as placeholders
|
|
192
195
|
|
|
193
196
|
## Roadmap
|
|
194
197
|
|
|
195
|
-
- [
|
|
198
|
+
- [x] **Phase 2a**: Inbound image support (CDN download + AES decrypt + vision model)
|
|
199
|
+
- [ ] **Phase 2b**: Voice transcription / video support
|
|
196
200
|
- [x] **Phase 3**: Persistent sessions — `SessionManager.continueRecent()` per chat, context survives restarts
|
|
197
201
|
- [x] **Phase 4**: Per-chat model selection — `/model` `/models` chat commands for manual switching
|
|
198
202
|
- [ ] **Phase 5**: Fine-grained permissions (per-user tool restrictions, bash approval via WeChat)
|
package/dist/index.js
CHANGED
|
@@ -1758,17 +1758,15 @@ function decryptAesEcb(ciphertext, key) {
|
|
|
1758
1758
|
const decipher = createDecipheriv("aes-128-ecb", key, null);
|
|
1759
1759
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1760
1760
|
}
|
|
1761
|
-
function buildCdnUrl(
|
|
1761
|
+
function buildCdnUrl(encryptQueryParam, fullUrl) {
|
|
1762
1762
|
if (fullUrl)
|
|
1763
1763
|
return fullUrl;
|
|
1764
|
-
if (!
|
|
1764
|
+
if (!encryptQueryParam)
|
|
1765
1765
|
return null;
|
|
1766
|
-
|
|
1767
|
-
return fileUrl;
|
|
1768
|
-
return `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(fileUrl)}`;
|
|
1766
|
+
return `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(encryptQueryParam)}`;
|
|
1769
1767
|
}
|
|
1770
|
-
async function downloadAndDecrypt(
|
|
1771
|
-
const url = buildCdnUrl(
|
|
1768
|
+
async function downloadAndDecrypt(encryptQueryParam, fullUrl, aeskeyHex, aesKeyBase64, label = "media") {
|
|
1769
|
+
const url = buildCdnUrl(encryptQueryParam, fullUrl);
|
|
1772
1770
|
if (!url) {
|
|
1773
1771
|
logger.warn(`[${label}] No CDN URL available`);
|
|
1774
1772
|
return null;
|
|
@@ -2411,7 +2409,8 @@ class WeChatBridge {
|
|
|
2411
2409
|
const results = [];
|
|
2412
2410
|
for (const item of imageItems) {
|
|
2413
2411
|
const img = item.image_item;
|
|
2414
|
-
|
|
2412
|
+
logger.debug(`[${chatId}] image_item raw: ${JSON.stringify(img)}`);
|
|
2413
|
+
const buf = await downloadAndDecrypt(img.media?.encrypt_query_param, img.media?.full_url, img.aeskey, img.media?.aes_key, `image[${chatId}]`);
|
|
2415
2414
|
if (buf) {
|
|
2416
2415
|
const mimeType = buf.length > 4 && buf[0] === 137 && buf[1] === 80 ? "image/png" : "image/jpeg";
|
|
2417
2416
|
results.push({
|