@neomei/opencode-feishu 0.1.2 → 0.2.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/CHANGELOG.md +29 -4
- package/README.md +86 -6
- package/dist/cli.js +141 -4
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +21 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/daemon.d.ts.map +1 -1
- package/dist/core/daemon.js +2 -1
- package/dist/core/daemon.js.map +1 -1
- package/dist/core/dedup.d.ts +29 -0
- package/dist/core/dedup.d.ts.map +1 -0
- package/dist/core/dedup.js +69 -0
- package/dist/core/dedup.js.map +1 -0
- package/dist/core/file-downloader.d.ts +24 -0
- package/dist/core/file-downloader.d.ts.map +1 -0
- package/dist/core/file-downloader.js +79 -0
- package/dist/core/file-downloader.js.map +1 -0
- package/dist/core/message-handler.d.ts +6 -0
- package/dist/core/message-handler.d.ts.map +1 -1
- package/dist/core/message-handler.js +66 -7
- package/dist/core/message-handler.js.map +1 -1
- package/dist/core/profile-manager.d.ts +27 -0
- package/dist/core/profile-manager.d.ts.map +1 -0
- package/dist/core/profile-manager.js +149 -0
- package/dist/core/profile-manager.js.map +1 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/feishu/api.d.ts +12 -0
- package/dist/feishu/api.d.ts.map +1 -1
- package/dist/feishu/api.js +89 -0
- package/dist/feishu/api.js.map +1 -1
- package/dist/feishu/card.d.ts +1 -1
- package/dist/feishu/card.d.ts.map +1 -1
- package/dist/feishu/card.js +3 -1
- package/dist/feishu/card.js.map +1 -1
- package/dist/feishu/event-source.d.ts.map +1 -1
- package/dist/feishu/event-source.js +3 -0
- package/dist/feishu/event-source.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/opencode/client.d.ts +5 -1
- package/dist/opencode/client.d.ts.map +1 -1
- package/dist/opencode/client.js +15 -2
- package/dist/opencode/client.js.map +1 -1
- package/dist/opencode/event-handler.d.ts.map +1 -1
- package/dist/opencode/event-handler.js +3 -0
- package/dist/opencode/event-handler.js.map +1 -1
- package/dist/services/approval-service.d.ts +20 -0
- package/dist/services/approval-service.d.ts.map +1 -0
- package/dist/services/approval-service.js +123 -0
- package/dist/services/approval-service.js.map +1 -0
- package/dist/services/base-service.d.ts +22 -0
- package/dist/services/base-service.d.ts.map +1 -0
- package/dist/services/base-service.js +47 -0
- package/dist/services/base-service.js.map +1 -0
- package/dist/services/calendar-service.d.ts +25 -0
- package/dist/services/calendar-service.d.ts.map +1 -0
- package/dist/services/calendar-service.js +220 -0
- package/dist/services/calendar-service.js.map +1 -0
- package/dist/services/chat-service.d.ts +52 -0
- package/dist/services/chat-service.d.ts.map +1 -0
- package/dist/services/chat-service.js +170 -0
- package/dist/services/chat-service.js.map +1 -0
- package/dist/services/contact-service.d.ts +40 -0
- package/dist/services/contact-service.d.ts.map +1 -0
- package/dist/services/contact-service.js +137 -0
- package/dist/services/contact-service.js.map +1 -0
- package/dist/services/doc-service.d.ts +135 -0
- package/dist/services/doc-service.d.ts.map +1 -0
- package/dist/services/doc-service.js +509 -0
- package/dist/services/doc-service.js.map +1 -0
- package/dist/services/im-service.d.ts +64 -0
- package/dist/services/im-service.d.ts.map +1 -0
- package/dist/services/im-service.js +215 -0
- package/dist/services/im-service.js.map +1 -0
- package/dist/services/index.d.ts +9 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +10 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/task-service.d.ts +19 -0
- package/dist/services/task-service.d.ts.map +1 -0
- package/dist/services/task-service.js +126 -0
- package/dist/services/task-service.js.map +1 -0
- package/dist/setup/device-flow.d.ts +31 -0
- package/dist/setup/device-flow.d.ts.map +1 -0
- package/dist/setup/device-flow.js +133 -0
- package/dist/setup/device-flow.js.map +1 -0
- package/dist/setup/preflight.d.ts +1 -0
- package/dist/setup/preflight.d.ts.map +1 -1
- package/dist/setup/preflight.js +167 -1
- package/dist/setup/preflight.js.map +1 -1
- package/dist/setup/wizard.d.ts +1 -1
- package/dist/setup/wizard.d.ts.map +1 -1
- package/dist/setup/wizard.js +95 -125
- package/dist/setup/wizard.js.map +1 -1
- package/dist/standalone.d.ts.map +1 -1
- package/dist/standalone.js +17 -2
- package/dist/standalone.js.map +1 -1
- package/dist/types/extended.d.ts +196 -0
- package/dist/types/extended.d.ts.map +1 -0
- package/dist/types/extended.js +6 -0
- package/dist/types/extended.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface DownloadedFile {
|
|
2
|
+
filePath: string;
|
|
3
|
+
fileName: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Download files from Feishu to local storage.
|
|
8
|
+
* Files are saved to ~/.config/opencode/feishu-attachments/
|
|
9
|
+
*/
|
|
10
|
+
export declare class FileDownloader {
|
|
11
|
+
private baseDir;
|
|
12
|
+
constructor(baseDir?: string);
|
|
13
|
+
/**
|
|
14
|
+
* Download a file from a URL and save it locally.
|
|
15
|
+
* Returns the local file path.
|
|
16
|
+
*/
|
|
17
|
+
downloadFromUrl(url: string, fileName: string, mimeType: string): Promise<DownloadedFile>;
|
|
18
|
+
/**
|
|
19
|
+
* Save a buffer directly to a file.
|
|
20
|
+
*/
|
|
21
|
+
saveBuffer(buffer: Buffer, fileName: string, mimeType: string): Promise<DownloadedFile>;
|
|
22
|
+
private sanitizeFileName;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=file-downloader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-downloader.d.ts","sourceRoot":"","sources":["../../src/core/file-downloader.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM;IAO5B;;;OAGG;IACG,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA0C/F;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAmB7F,OAAO,CAAC,gBAAgB;CAOzB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { createLogger } from './logger.js';
|
|
5
|
+
const log = createLogger('FileDownloader');
|
|
6
|
+
const DEFAULT_ATTACHMENTS_DIR = join(homedir(), '.config', 'opencode', 'feishu-attachments');
|
|
7
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB limit
|
|
8
|
+
/**
|
|
9
|
+
* Download files from Feishu to local storage.
|
|
10
|
+
* Files are saved to ~/.config/opencode/feishu-attachments/
|
|
11
|
+
*/
|
|
12
|
+
export class FileDownloader {
|
|
13
|
+
baseDir;
|
|
14
|
+
constructor(baseDir) {
|
|
15
|
+
this.baseDir = baseDir || DEFAULT_ATTACHMENTS_DIR;
|
|
16
|
+
if (!existsSync(this.baseDir)) {
|
|
17
|
+
mkdirSync(this.baseDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Download a file from a URL and save it locally.
|
|
22
|
+
* Returns the local file path.
|
|
23
|
+
*/
|
|
24
|
+
async downloadFromUrl(url, fileName, mimeType) {
|
|
25
|
+
try {
|
|
26
|
+
log.info({ url, fileName }, 'Downloading file from URL');
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timeout = setTimeout(() => controller.abort(), 30000); // 30s timeout
|
|
29
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
30
|
+
clearTimeout(timeout);
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
const contentLength = response.headers.get('content-length');
|
|
35
|
+
if (contentLength && parseInt(contentLength) > MAX_FILE_SIZE) {
|
|
36
|
+
throw new Error(`File too large: ${contentLength} bytes (max ${MAX_FILE_SIZE})`);
|
|
37
|
+
}
|
|
38
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
39
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
40
|
+
throw new Error(`File too large: ${buffer.length} bytes (max ${MAX_FILE_SIZE})`);
|
|
41
|
+
}
|
|
42
|
+
// Sanitize filename
|
|
43
|
+
const safeName = this.sanitizeFileName(fileName);
|
|
44
|
+
const timestamp = Date.now();
|
|
45
|
+
const uniqueName = `${timestamp}_${safeName}`;
|
|
46
|
+
const filePath = join(this.baseDir, uniqueName);
|
|
47
|
+
writeFileSync(filePath, buffer);
|
|
48
|
+
log.info({ filePath, size: buffer.length }, 'File downloaded successfully');
|
|
49
|
+
return { filePath, fileName: safeName, mimeType };
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
log.error({ err, url, fileName }, 'Failed to download file');
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Save a buffer directly to a file.
|
|
58
|
+
*/
|
|
59
|
+
async saveBuffer(buffer, fileName, mimeType) {
|
|
60
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
61
|
+
throw new Error(`File too large: ${buffer.length} bytes (max ${MAX_FILE_SIZE})`);
|
|
62
|
+
}
|
|
63
|
+
const safeName = this.sanitizeFileName(fileName);
|
|
64
|
+
const timestamp = Date.now();
|
|
65
|
+
const uniqueName = `${timestamp}_${safeName}`;
|
|
66
|
+
const filePath = join(this.baseDir, uniqueName);
|
|
67
|
+
writeFileSync(filePath, buffer);
|
|
68
|
+
log.info({ filePath, size: buffer.length }, 'Buffer saved to file');
|
|
69
|
+
return { filePath, fileName: safeName, mimeType };
|
|
70
|
+
}
|
|
71
|
+
sanitizeFileName(name) {
|
|
72
|
+
// Remove path traversal characters and limit length
|
|
73
|
+
return name
|
|
74
|
+
.replace(/[\x00-\x1f\x7f]/g, '') // Control chars
|
|
75
|
+
.replace(/[/\\?%<>|":]/g, '_') // Invalid path chars
|
|
76
|
+
.substring(0, 200); // Limit length
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=file-downloader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-downloader.js","sourceRoot":"","sources":["../../src/core/file-downloader.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,13 @@ export declare class MessageHandler {
|
|
|
7
7
|
private sessionManager;
|
|
8
8
|
private feishuApi;
|
|
9
9
|
private opencode;
|
|
10
|
+
private dedup;
|
|
11
|
+
private fileDownloader;
|
|
10
12
|
constructor(config: FeishuConfig, sessionManager: SessionManager, feishuApi: FeishuAPI, opencode: OpenCodeClient);
|
|
11
13
|
handleMessage(message: FeishuMessage): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Unified media download handler for images, files, audio, and video.
|
|
16
|
+
*/
|
|
17
|
+
private downloadMedia;
|
|
12
18
|
}
|
|
13
19
|
//# sourceMappingURL=message-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-handler.d.ts","sourceRoot":"","sources":["../../src/core/message-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"message-handler.d.ts","sourceRoot":"","sources":["../../src/core/message-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAQ5D,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,cAAc,CAAiB;gBAGrC,MAAM,EAAE,YAAY,EACpB,cAAc,EAAE,cAAc,EAC9B,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,cAAc;IAUpB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA+M1D;;OAEG;YACW,aAAa;CAsB5B"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { FeishuCard } from '../feishu/card.js';
|
|
2
|
+
import { MessageDeduplicator } from './dedup.js';
|
|
3
|
+
import { FileDownloader } from './file-downloader.js';
|
|
2
4
|
import { createLogger } from './logger.js';
|
|
3
5
|
const log = createLogger('MessageHandler');
|
|
4
6
|
export class MessageHandler {
|
|
@@ -6,11 +8,15 @@ export class MessageHandler {
|
|
|
6
8
|
sessionManager;
|
|
7
9
|
feishuApi;
|
|
8
10
|
opencode;
|
|
11
|
+
dedup;
|
|
12
|
+
fileDownloader;
|
|
9
13
|
constructor(config, sessionManager, feishuApi, opencode) {
|
|
10
14
|
this.config = config;
|
|
11
15
|
this.sessionManager = sessionManager;
|
|
12
16
|
this.feishuApi = feishuApi;
|
|
13
17
|
this.opencode = opencode;
|
|
18
|
+
this.dedup = new MessageDeduplicator(config.dedupTtl || 600_000);
|
|
19
|
+
this.fileDownloader = new FileDownloader();
|
|
14
20
|
}
|
|
15
21
|
async handleMessage(message) {
|
|
16
22
|
try {
|
|
@@ -30,6 +36,11 @@ export class MessageHandler {
|
|
|
30
36
|
log.info('Skipping message from app/bot itself');
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
39
|
+
// Deduplicate: skip if we've seen this message before
|
|
40
|
+
if (this.dedup.isDuplicate(message.message_id)) {
|
|
41
|
+
log.info({ messageId: message.message_id }, 'Duplicate message, skipping');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
33
44
|
const chatId = message.chat_id;
|
|
34
45
|
const chatType = message.chat_type;
|
|
35
46
|
log.info({ chatType, sender: message.sender.sender_id?.union_id || 'unknown' }, 'Processing message');
|
|
@@ -69,16 +80,42 @@ export class MessageHandler {
|
|
|
69
80
|
await this.feishuApi.sendText(chatId, '⏳ 正在处理上一条消息,请稍候...');
|
|
70
81
|
return;
|
|
71
82
|
}
|
|
72
|
-
//
|
|
83
|
+
// Resolve sender name (with cache)
|
|
84
|
+
const senderUnionId = message.sender.sender_id?.union_id || 'unknown';
|
|
85
|
+
const senderName = await this.feishuApi.getUserName(senderUnionId);
|
|
86
|
+
log.info({ senderName, senderId: senderUnionId }, 'Resolved sender name');
|
|
87
|
+
// Extract content and download files based on message type
|
|
73
88
|
let text = '';
|
|
89
|
+
const files = [];
|
|
74
90
|
try {
|
|
75
91
|
const content = JSON.parse(message.content);
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
switch (message.message_type) {
|
|
93
|
+
case 'text':
|
|
94
|
+
text = content.text || '';
|
|
95
|
+
break;
|
|
96
|
+
case 'image':
|
|
97
|
+
text = await this.downloadMedia(message.message_id, content.image_key, 'image', 'image.jpg', 'image/jpeg', '图片');
|
|
98
|
+
break;
|
|
99
|
+
case 'file':
|
|
100
|
+
text = await this.downloadMedia(message.message_id, content.file_key, 'file', content.file_name || 'unknown', 'application/octet-stream', '文件');
|
|
101
|
+
break;
|
|
102
|
+
case 'audio':
|
|
103
|
+
text = await this.downloadMedia(message.message_id, content.file_key, 'file', 'audio.opus', 'audio/opus', '语音');
|
|
104
|
+
break;
|
|
105
|
+
case 'media':
|
|
106
|
+
text = await this.downloadMedia(message.message_id, content.file_key, 'file', content.file_name || 'video.mp4', 'video/mp4', '视频');
|
|
107
|
+
break;
|
|
108
|
+
case 'sticker':
|
|
109
|
+
text = `[表情消息]`;
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
text = `[不支持的消息类型: ${message.message_type}]`;
|
|
113
|
+
}
|
|
114
|
+
log.info({ text: text.substring(0, 100), type: message.message_type, files: files.length }, 'Extracted content');
|
|
78
115
|
}
|
|
79
116
|
catch {
|
|
80
|
-
log.warn({ content: message.content }, 'Failed to parse message content');
|
|
81
|
-
text =
|
|
117
|
+
log.warn({ content: message.content, type: message.message_type }, 'Failed to parse message content');
|
|
118
|
+
text = `[不支持的消息类型: ${message.message_type}]`;
|
|
82
119
|
}
|
|
83
120
|
// Remove @mention from text if present
|
|
84
121
|
if (message.mentions) {
|
|
@@ -87,6 +124,10 @@ export class MessageHandler {
|
|
|
87
124
|
}
|
|
88
125
|
log.info({ text: text.substring(0, 100) }, 'Text after removing mentions');
|
|
89
126
|
}
|
|
127
|
+
// Prepend sender name in group chats for context
|
|
128
|
+
if (chatType === 'group' && senderName && senderName !== senderUnionId) {
|
|
129
|
+
text = `[${senderName}]: ${text}`;
|
|
130
|
+
}
|
|
90
131
|
if (!text) {
|
|
91
132
|
log.info('Empty text content, ignoring');
|
|
92
133
|
return;
|
|
@@ -101,8 +142,8 @@ export class MessageHandler {
|
|
|
101
142
|
this.sessionManager.updateStatus(chatId, 'busy');
|
|
102
143
|
// Send message to OpenCode
|
|
103
144
|
try {
|
|
104
|
-
log.info({ sessionId: session.id }, 'Sending prompt to OpenCode');
|
|
105
|
-
await this.opencode.sendPrompt(session.id, text);
|
|
145
|
+
log.info({ sessionId: session.id, files: files.length }, 'Sending prompt to OpenCode');
|
|
146
|
+
await this.opencode.sendPrompt(session.id, text, files.length > 0 ? files : undefined);
|
|
106
147
|
log.info('Prompt sent successfully');
|
|
107
148
|
}
|
|
108
149
|
catch (err) {
|
|
@@ -122,5 +163,23 @@ export class MessageHandler {
|
|
|
122
163
|
}
|
|
123
164
|
}
|
|
124
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Unified media download handler for images, files, audio, and video.
|
|
168
|
+
*/
|
|
169
|
+
async downloadMedia(messageId, fileKey, resourceType, fileName, mimeType, typeLabel) {
|
|
170
|
+
if (!fileKey) {
|
|
171
|
+
return `[${typeLabel}消息]`;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
log.info({ messageId, fileKey, fileName }, `Downloading ${typeLabel}...`);
|
|
175
|
+
const buffer = await this.feishuApi.downloadMedia(messageId, fileKey, resourceType);
|
|
176
|
+
const downloaded = await this.fileDownloader.saveBuffer(buffer, fileName, mimeType);
|
|
177
|
+
return `[${typeLabel}已上传: ${downloaded.filePath}]`;
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
log.error({ err, messageId, fileKey }, `Failed to download ${typeLabel}`);
|
|
181
|
+
return `[${typeLabel}消息(下载失败)]`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
125
184
|
}
|
|
126
185
|
//# sourceMappingURL=message-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-handler.js","sourceRoot":"","sources":["../../src/core/message-handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,GAAG,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;AAE3C,MAAM,OAAO,cAAc;IACjB,MAAM,CAAe;IACrB,cAAc,CAAiB;IAC/B,SAAS,CAAY;IACrB,QAAQ,CAAiB;
|
|
1
|
+
{"version":3,"file":"message-handler.js","sourceRoot":"","sources":["../../src/core/message-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FeishuConfig } from './types.js';
|
|
2
|
+
export interface ProfileInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
config?: FeishuConfig;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class ProfileManager {
|
|
9
|
+
private profilesDir;
|
|
10
|
+
constructor(profilesDir?: string);
|
|
11
|
+
private getProfilePath;
|
|
12
|
+
private readActiveProfile;
|
|
13
|
+
private writeActiveProfile;
|
|
14
|
+
private clearActiveProfile;
|
|
15
|
+
list(): ProfileInfo[];
|
|
16
|
+
get(name: string): FeishuConfig | null;
|
|
17
|
+
save(name: string, config: FeishuConfig): void;
|
|
18
|
+
delete(name: string): boolean;
|
|
19
|
+
use(name: string): boolean;
|
|
20
|
+
getActive(): {
|
|
21
|
+
name: string;
|
|
22
|
+
config: FeishuConfig;
|
|
23
|
+
} | null;
|
|
24
|
+
rename(oldName: string, newName: string): boolean;
|
|
25
|
+
clone(sourceName: string, targetName: string): boolean;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=profile-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-manager.d.ts","sourceRoot":"","sources":["../../src/core/profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,CAAC,EAAE,MAAM;IAOhC,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,kBAAkB;IAQ1B,IAAI,IAAI,WAAW,EAAE;IA6BrB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IActC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IAY9C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAgB7B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAU1B,SAAS,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI;IAc1D,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAoBjD,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;CAYvD"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join, dirname, basename } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { FeishuConfigSchema } from './config.js';
|
|
5
|
+
const DEFAULT_PROFILES_DIR = join(homedir(), '.config', 'opencode', 'feishu-profiles');
|
|
6
|
+
const ACTIVE_PROFILE_FILE = join(homedir(), '.config', 'opencode', 'feishu-active-profile');
|
|
7
|
+
export class ProfileManager {
|
|
8
|
+
profilesDir;
|
|
9
|
+
constructor(profilesDir) {
|
|
10
|
+
this.profilesDir = profilesDir || DEFAULT_PROFILES_DIR;
|
|
11
|
+
if (!existsSync(this.profilesDir)) {
|
|
12
|
+
mkdirSync(this.profilesDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
getProfilePath(name) {
|
|
16
|
+
return join(this.profilesDir, `${name}.json`);
|
|
17
|
+
}
|
|
18
|
+
readActiveProfile() {
|
|
19
|
+
try {
|
|
20
|
+
return readFileSync(ACTIVE_PROFILE_FILE, 'utf-8').trim();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
writeActiveProfile(name) {
|
|
27
|
+
const dir = dirname(ACTIVE_PROFILE_FILE);
|
|
28
|
+
if (!existsSync(dir)) {
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(ACTIVE_PROFILE_FILE, name);
|
|
32
|
+
}
|
|
33
|
+
clearActiveProfile() {
|
|
34
|
+
try {
|
|
35
|
+
unlinkSync(ACTIVE_PROFILE_FILE);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// ignore
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
list() {
|
|
42
|
+
const activeName = this.readActiveProfile();
|
|
43
|
+
try {
|
|
44
|
+
const files = readdirSync(this.profilesDir);
|
|
45
|
+
return files
|
|
46
|
+
.filter(f => f.endsWith('.json'))
|
|
47
|
+
.map(f => {
|
|
48
|
+
const name = basename(f, '.json');
|
|
49
|
+
const path = join(this.profilesDir, f);
|
|
50
|
+
let config;
|
|
51
|
+
try {
|
|
52
|
+
const content = readFileSync(path, 'utf-8');
|
|
53
|
+
config = FeishuConfigSchema.parse(JSON.parse(content));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// invalid config
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
name,
|
|
60
|
+
path,
|
|
61
|
+
config,
|
|
62
|
+
isActive: name === activeName,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
get(name) {
|
|
71
|
+
const path = this.getProfilePath(name);
|
|
72
|
+
if (!existsSync(path)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const content = readFileSync(path, 'utf-8');
|
|
77
|
+
return FeishuConfigSchema.parse(JSON.parse(content));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
save(name, config) {
|
|
84
|
+
const validated = FeishuConfigSchema.parse(config);
|
|
85
|
+
const path = this.getProfilePath(name);
|
|
86
|
+
const dir = dirname(path);
|
|
87
|
+
if (!existsSync(dir)) {
|
|
88
|
+
mkdirSync(dir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
writeFileSync(path, JSON.stringify(validated, null, 2));
|
|
91
|
+
}
|
|
92
|
+
delete(name) {
|
|
93
|
+
const path = this.getProfilePath(name);
|
|
94
|
+
if (!existsSync(path)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
unlinkSync(path);
|
|
98
|
+
// If this was the active profile, clear it
|
|
99
|
+
if (this.readActiveProfile() === name) {
|
|
100
|
+
this.clearActiveProfile();
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
use(name) {
|
|
105
|
+
const config = this.get(name);
|
|
106
|
+
if (!config) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
this.writeActiveProfile(name);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
getActive() {
|
|
113
|
+
const activeName = this.readActiveProfile();
|
|
114
|
+
if (!activeName) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const config = this.get(activeName);
|
|
118
|
+
if (!config) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return { name: activeName, config };
|
|
122
|
+
}
|
|
123
|
+
rename(oldName, newName) {
|
|
124
|
+
const oldPath = this.getProfilePath(oldName);
|
|
125
|
+
const newPath = this.getProfilePath(newName);
|
|
126
|
+
if (!existsSync(oldPath) || existsSync(newPath)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const content = readFileSync(oldPath, 'utf-8');
|
|
130
|
+
writeFileSync(newPath, content);
|
|
131
|
+
unlinkSync(oldPath);
|
|
132
|
+
// Update active profile if needed
|
|
133
|
+
if (this.readActiveProfile() === oldName) {
|
|
134
|
+
this.writeActiveProfile(newName);
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
clone(sourceName, targetName) {
|
|
139
|
+
const sourcePath = this.getProfilePath(sourceName);
|
|
140
|
+
const targetPath = this.getProfilePath(targetName);
|
|
141
|
+
if (!existsSync(sourcePath) || existsSync(targetPath)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
145
|
+
writeFileSync(targetPath, content);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=profile-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-manager.js","sourceRoot":"","sources":["../../src/core/profile-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface FeishuConfig {
|
|
|
11
11
|
requireMention: boolean;
|
|
12
12
|
groupPolicy: 'open' | 'allowlist' | 'disabled';
|
|
13
13
|
allowlist?: string[];
|
|
14
|
+
/** Message deduplication TTL in milliseconds (default: 600000 = 10 min) */
|
|
15
|
+
dedupTtl?: number;
|
|
14
16
|
}
|
|
15
17
|
export interface FeishuMessage {
|
|
16
18
|
message_id: string;
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,KAAK,GAAG,OAAO,CAAC;IAC3B,MAAM,EAAE;QACN,SAAS,EAAE;YACT,QAAQ,EAAE,MAAM,CAAC;YACjB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,EAAE,EAAE;YACF,QAAQ,EAAE,MAAM,CAAC;YACjB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE;QACP,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;IACF,MAAM,CAAC,EAAE;QACP,KAAK,EAAE;YACL,GAAG,EAAE,YAAY,GAAG,SAAS,CAAC;YAC9B,OAAO,EAAE,MAAM,CAAC;SACjB,CAAC;KACH,CAAC;IACF,QAAQ,EAAE,KAAK,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
package/dist/feishu/api.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare class FeishuAPI {
|
|
|
6
6
|
private appSecret;
|
|
7
7
|
private domain;
|
|
8
8
|
private botOpenId?;
|
|
9
|
+
private userNameCache;
|
|
9
10
|
constructor(config: FeishuConfig);
|
|
10
11
|
initialize(): Promise<void>;
|
|
11
12
|
/** SDK client — exposed so FeishuEventSource can mount WSClient on the same credentials. */
|
|
@@ -19,5 +20,16 @@ export declare class FeishuAPI {
|
|
|
19
20
|
sendText(chatId: string, text: string): Promise<FeishuMessage>;
|
|
20
21
|
sendCard(chatId: string, card: CardContent): Promise<FeishuMessage>;
|
|
21
22
|
updateCard(messageId: string, card: CardContent): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get user name by user_id with 24h cache.
|
|
25
|
+
* Falls back to user_id if the API call fails.
|
|
26
|
+
*/
|
|
27
|
+
getUserName(userId: string): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Download media (image, file, audio, video) from Feishu message resources.
|
|
30
|
+
* Uses the correct Feishu API: /open-apis/im/v1/messages/{message_id}/resources/{file_key}
|
|
31
|
+
* Returns the raw Buffer.
|
|
32
|
+
*/
|
|
33
|
+
downloadMedia(messageId: string, fileKey: string, type: 'image' | 'file'): Promise<Buffer>;
|
|
22
34
|
}
|
|
23
35
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/feishu/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/feishu/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,yBAAyB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/feishu/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,yBAAyB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAcjF,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAyC;gBAElD,MAAM,EAAE,YAAY;IAa1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC,4FAA4F;IAC5F,SAAS,IAAI,IAAI,CAAC,MAAM;IAIxB,cAAc,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAA;KAAE;IAI3E,YAAY,IAAI,MAAM,GAAG,SAAS;IAI5B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkB9D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBnE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAUrE;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsBlD;;;;OAIG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA8DjG"}
|
package/dist/feishu/api.js
CHANGED
|
@@ -3,12 +3,14 @@ import { resolveAppSecret } from '../core/config.js';
|
|
|
3
3
|
import { createLogger } from '../core/logger.js';
|
|
4
4
|
import { silentLogger } from './silent-logger.js';
|
|
5
5
|
const log = createLogger('FeishuAPI');
|
|
6
|
+
const USERNAME_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
6
7
|
export class FeishuAPI {
|
|
7
8
|
client;
|
|
8
9
|
appId;
|
|
9
10
|
appSecret;
|
|
10
11
|
domain;
|
|
11
12
|
botOpenId;
|
|
13
|
+
userNameCache = new Map();
|
|
12
14
|
constructor(config) {
|
|
13
15
|
this.appId = config.appId;
|
|
14
16
|
this.appSecret = resolveAppSecret(config);
|
|
@@ -93,5 +95,92 @@ export class FeishuAPI {
|
|
|
93
95
|
throw new Error(`Failed to update message: ${res.msg || 'Unknown error'}`);
|
|
94
96
|
}
|
|
95
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Get user name by user_id with 24h cache.
|
|
100
|
+
* Falls back to user_id if the API call fails.
|
|
101
|
+
*/
|
|
102
|
+
async getUserName(userId) {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const cached = this.userNameCache.get(userId);
|
|
105
|
+
if (cached && now - cached.timestamp < USERNAME_CACHE_TTL_MS) {
|
|
106
|
+
return cached.name;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const res = await this.client.request({
|
|
110
|
+
method: 'GET',
|
|
111
|
+
url: '/open-apis/contact/v3/users/' + userId,
|
|
112
|
+
params: { user_id_type: 'union_id' },
|
|
113
|
+
});
|
|
114
|
+
const name = res?.data?.user?.name || userId;
|
|
115
|
+
this.userNameCache.set(userId, { name, timestamp: now });
|
|
116
|
+
return name;
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
log.warn({ err, userId }, 'Failed to fetch user name');
|
|
120
|
+
return userId;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Download media (image, file, audio, video) from Feishu message resources.
|
|
125
|
+
* Uses the correct Feishu API: /open-apis/im/v1/messages/{message_id}/resources/{file_key}
|
|
126
|
+
* Returns the raw Buffer.
|
|
127
|
+
*/
|
|
128
|
+
async downloadMedia(messageId, fileKey, type) {
|
|
129
|
+
try {
|
|
130
|
+
log.info({ messageId, fileKey, type }, 'Downloading media from Feishu');
|
|
131
|
+
// Feishu resource download API
|
|
132
|
+
const res = await this.client.request({
|
|
133
|
+
method: 'GET',
|
|
134
|
+
url: `/open-apis/im/v1/messages/${messageId}/resources/${fileKey}`,
|
|
135
|
+
params: { type },
|
|
136
|
+
});
|
|
137
|
+
// For binary responses, the SDK might return the data directly
|
|
138
|
+
if (Buffer.isBuffer(res)) {
|
|
139
|
+
return res;
|
|
140
|
+
}
|
|
141
|
+
// Check if response is a string (binary data as string)
|
|
142
|
+
if (typeof res === 'string') {
|
|
143
|
+
return Buffer.from(res, 'binary');
|
|
144
|
+
}
|
|
145
|
+
// Check if response has data field
|
|
146
|
+
if (res.data) {
|
|
147
|
+
// If data is a buffer
|
|
148
|
+
if (Buffer.isBuffer(res.data)) {
|
|
149
|
+
return res.data;
|
|
150
|
+
}
|
|
151
|
+
// If data is a string (binary data as string)
|
|
152
|
+
if (typeof res.data === 'string') {
|
|
153
|
+
return Buffer.from(res.data, 'binary');
|
|
154
|
+
}
|
|
155
|
+
// If data contains a URL
|
|
156
|
+
if (typeof res.data === 'object') {
|
|
157
|
+
const downloadUrl = res.data.url || res.data.file_url || res.data.image_url;
|
|
158
|
+
if (downloadUrl) {
|
|
159
|
+
const response = await fetch(downloadUrl);
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw new Error(`Failed to fetch media from URL: ${response.status}`);
|
|
162
|
+
}
|
|
163
|
+
return Buffer.from(await response.arrayBuffer());
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// If the response itself might be binary data
|
|
168
|
+
if (res && typeof res === 'object') {
|
|
169
|
+
// Try to extract binary data from response
|
|
170
|
+
const responseData = res.data || res.body || res;
|
|
171
|
+
if (Buffer.isBuffer(responseData)) {
|
|
172
|
+
return responseData;
|
|
173
|
+
}
|
|
174
|
+
if (typeof responseData === 'string') {
|
|
175
|
+
return Buffer.from(responseData, 'binary');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
throw new Error(`Unexpected response format from media download: ${typeof res}`);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
log.error({ err, messageId, fileKey, type }, 'Failed to download media');
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
96
185
|
}
|
|
97
186
|
//# sourceMappingURL=api.js.map
|