@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +29 -4
  2. package/README.md +86 -6
  3. package/dist/cli.js +141 -4
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/config.d.ts +4 -0
  6. package/dist/core/config.d.ts.map +1 -1
  7. package/dist/core/config.js +21 -1
  8. package/dist/core/config.js.map +1 -1
  9. package/dist/core/daemon.d.ts.map +1 -1
  10. package/dist/core/daemon.js +2 -1
  11. package/dist/core/daemon.js.map +1 -1
  12. package/dist/core/dedup.d.ts +29 -0
  13. package/dist/core/dedup.d.ts.map +1 -0
  14. package/dist/core/dedup.js +69 -0
  15. package/dist/core/dedup.js.map +1 -0
  16. package/dist/core/file-downloader.d.ts +24 -0
  17. package/dist/core/file-downloader.d.ts.map +1 -0
  18. package/dist/core/file-downloader.js +79 -0
  19. package/dist/core/file-downloader.js.map +1 -0
  20. package/dist/core/message-handler.d.ts +6 -0
  21. package/dist/core/message-handler.d.ts.map +1 -1
  22. package/dist/core/message-handler.js +66 -7
  23. package/dist/core/message-handler.js.map +1 -1
  24. package/dist/core/profile-manager.d.ts +27 -0
  25. package/dist/core/profile-manager.d.ts.map +1 -0
  26. package/dist/core/profile-manager.js +149 -0
  27. package/dist/core/profile-manager.js.map +1 -0
  28. package/dist/core/types.d.ts +2 -0
  29. package/dist/core/types.d.ts.map +1 -1
  30. package/dist/feishu/api.d.ts +12 -0
  31. package/dist/feishu/api.d.ts.map +1 -1
  32. package/dist/feishu/api.js +89 -0
  33. package/dist/feishu/api.js.map +1 -1
  34. package/dist/feishu/card.d.ts +1 -1
  35. package/dist/feishu/card.d.ts.map +1 -1
  36. package/dist/feishu/card.js +3 -1
  37. package/dist/feishu/card.js.map +1 -1
  38. package/dist/feishu/event-source.d.ts.map +1 -1
  39. package/dist/feishu/event-source.js +3 -0
  40. package/dist/feishu/event-source.js.map +1 -1
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/opencode/client.d.ts +5 -1
  46. package/dist/opencode/client.d.ts.map +1 -1
  47. package/dist/opencode/client.js +15 -2
  48. package/dist/opencode/client.js.map +1 -1
  49. package/dist/opencode/event-handler.d.ts.map +1 -1
  50. package/dist/opencode/event-handler.js +3 -0
  51. package/dist/opencode/event-handler.js.map +1 -1
  52. package/dist/services/approval-service.d.ts +20 -0
  53. package/dist/services/approval-service.d.ts.map +1 -0
  54. package/dist/services/approval-service.js +123 -0
  55. package/dist/services/approval-service.js.map +1 -0
  56. package/dist/services/base-service.d.ts +22 -0
  57. package/dist/services/base-service.d.ts.map +1 -0
  58. package/dist/services/base-service.js +47 -0
  59. package/dist/services/base-service.js.map +1 -0
  60. package/dist/services/calendar-service.d.ts +25 -0
  61. package/dist/services/calendar-service.d.ts.map +1 -0
  62. package/dist/services/calendar-service.js +220 -0
  63. package/dist/services/calendar-service.js.map +1 -0
  64. package/dist/services/chat-service.d.ts +52 -0
  65. package/dist/services/chat-service.d.ts.map +1 -0
  66. package/dist/services/chat-service.js +170 -0
  67. package/dist/services/chat-service.js.map +1 -0
  68. package/dist/services/contact-service.d.ts +40 -0
  69. package/dist/services/contact-service.d.ts.map +1 -0
  70. package/dist/services/contact-service.js +137 -0
  71. package/dist/services/contact-service.js.map +1 -0
  72. package/dist/services/doc-service.d.ts +135 -0
  73. package/dist/services/doc-service.d.ts.map +1 -0
  74. package/dist/services/doc-service.js +509 -0
  75. package/dist/services/doc-service.js.map +1 -0
  76. package/dist/services/im-service.d.ts +64 -0
  77. package/dist/services/im-service.d.ts.map +1 -0
  78. package/dist/services/im-service.js +215 -0
  79. package/dist/services/im-service.js.map +1 -0
  80. package/dist/services/index.d.ts +9 -0
  81. package/dist/services/index.d.ts.map +1 -0
  82. package/dist/services/index.js +10 -0
  83. package/dist/services/index.js.map +1 -0
  84. package/dist/services/task-service.d.ts +19 -0
  85. package/dist/services/task-service.d.ts.map +1 -0
  86. package/dist/services/task-service.js +126 -0
  87. package/dist/services/task-service.js.map +1 -0
  88. package/dist/setup/device-flow.d.ts +31 -0
  89. package/dist/setup/device-flow.d.ts.map +1 -0
  90. package/dist/setup/device-flow.js +133 -0
  91. package/dist/setup/device-flow.js.map +1 -0
  92. package/dist/setup/preflight.d.ts +1 -0
  93. package/dist/setup/preflight.d.ts.map +1 -1
  94. package/dist/setup/preflight.js +167 -1
  95. package/dist/setup/preflight.js.map +1 -1
  96. package/dist/setup/wizard.d.ts +1 -1
  97. package/dist/setup/wizard.d.ts.map +1 -1
  98. package/dist/setup/wizard.js +95 -125
  99. package/dist/setup/wizard.js.map +1 -1
  100. package/dist/standalone.d.ts.map +1 -1
  101. package/dist/standalone.js +17 -2
  102. package/dist/standalone.js.map +1 -1
  103. package/dist/types/extended.d.ts +196 -0
  104. package/dist/types/extended.d.ts.map +1 -0
  105. package/dist/types/extended.js +6 -0
  106. package/dist/types/extended.js.map +1 -0
  107. 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;AAM5D,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAAiB;gBAG/B,MAAM,EAAE,YAAY,EACpB,cAAc,EAAE,cAAc,EAC9B,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,cAAc;IAQpB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CA2I3D"}
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
- // Extract text content
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
- text = content.text || '';
77
- log.info({ text: text.substring(0, 100) }, 'Extracted text');
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":"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"}
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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"}
@@ -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;
@@ -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;CACtB;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"}
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"}
@@ -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
@@ -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;AAOjF,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;gBAEf,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;CAStE"}
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"}
@@ -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