Skip to content

Commit 7e61bb0

Browse files
author
hcren
committed
添加小蜜蜂接入
1 parent a68a355 commit 7e61bb0

16 files changed

Lines changed: 1291 additions & 44 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ LobsterAI can bridge the Agent to multiple IM platforms. Send a message from you
249249
| Telegram | grammY | Bot API integration |
250250
| Discord | discord.js | Discord bot integration |
251251
| NetEase IM | node-nim V2 SDK | NetEase IM P2P messaging |
252+
| NetEase Bee | node-nim V2 SDK | NetEase Bee Personal Digital Assistant |
252253

253254
Configure the corresponding platform Token/Secret in the Settings panel to enable. Once set up, you can send instructions directly to the Agent from your phone IM (e.g., "analyze this dataset", "make a weekly summary PPT"), and the Agent will execute on the desktop and return results.
254255

README_zh.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ LobsterAI 支持将 Agent 桥接到多种 IM 平台。在手机上通过 IM 发
250250
| Telegram | grammY | Bot API 接入 |
251251
| Discord | discord.js | Discord Bot 接入 |
252252
| 云信 IM | node-nim V2 SDK | 网易云信 IM P2P 私聊 |
253+
| 网易小蜜蜂 | node-nim V2 SDK | [网易小蜜蜂个人数字助理](https://wp.m.163.com/163/html/bee/lobsterai_guide/index.html) |
253254

254255
在设置面板中配置对应平台的 Token/密钥即可启用。配置完成后,你可以在手机 IM 中直接对 Agent 下达指令(如「帮我分析这份数据」「做一份本周工作汇报 PPT」),Agent 会在桌面端自动执行并返回结果。
255256

electron-builder.json

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@
3030
"filter": [
3131
"**/*"
3232
]
33-
},
34-
{
35-
"from": "resources/mingit",
36-
"to": "mingit",
37-
"filter": [
38-
"**/*"
39-
]
4033
}
4134
],
4235
"mac": {
@@ -64,7 +57,16 @@
6457
"target": [
6558
"nsis"
6659
],
67-
"icon": "build/icons/win/icon.ico"
60+
"icon": "build/icons/win/icon.ico",
61+
"extraResources": [
62+
{
63+
"from": "resources/mingit",
64+
"to": "mingit",
65+
"filter": [
66+
"**/*"
67+
]
68+
}
69+
]
6870
},
6971
"linux": {
7072
"target": [
@@ -88,4 +90,4 @@
8890
"node_modules/node-nim/**"
8991
],
9092
"asar": true
91-
}
93+
}

public/xiaomifeng.png

77.6 KB
Loading

src/main/im/imGatewayManager.ts

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { FeishuGateway } from './feishuGateway';
99
import { TelegramGateway } from './telegramGateway';
1010
import { DiscordGateway } from './discordGateway';
1111
import { NimGateway } from './nimGateway';
12+
import { XiaomifengGateway } from './xiaomifengGateway';
1213
import { IMChatHandler } from './imChatHandler';
1314
import { IMCoworkHandler } from './imCoworkHandler';
1415
import { IMStore } from './imStore';
@@ -53,6 +54,7 @@ export class IMGatewayManager extends EventEmitter {
5354
private telegramGateway: TelegramGateway;
5455
private discordGateway: DiscordGateway;
5556
private nimGateway: NimGateway;
57+
private xiaomifengGateway: XiaomifengGateway;
5658
private imStore: IMStore;
5759
private chatHandler: IMChatHandler | null = null;
5860
private coworkHandler: IMCoworkHandler | null = null;
@@ -72,6 +74,7 @@ export class IMGatewayManager extends EventEmitter {
7274
this.telegramGateway = new TelegramGateway();
7375
this.discordGateway = new DiscordGateway();
7476
this.nimGateway = new NimGateway();
77+
this.xiaomifengGateway = new XiaomifengGateway();
7578

7679
// Store Cowork dependencies if provided
7780
if (options?.coworkRunner && options?.coworkStore) {
@@ -167,6 +170,24 @@ export class IMGatewayManager extends EventEmitter {
167170
this.nimGateway.on('message', (message: IMMessage) => {
168171
this.emit('message', message);
169172
});
173+
174+
// Xiaomifeng events
175+
this.xiaomifengGateway.on('status', () => {
176+
this.emit('statusChange', this.getStatus());
177+
});
178+
this.xiaomifengGateway.on('connected', () => {
179+
this.emit('statusChange', this.getStatus());
180+
});
181+
this.xiaomifengGateway.on('disconnected', () => {
182+
this.emit('statusChange', this.getStatus());
183+
});
184+
this.xiaomifengGateway.on('error', (error) => {
185+
this.emit('error', { platform: 'xiaomifeng', error });
186+
this.emit('statusChange', this.getStatus());
187+
});
188+
this.xiaomifengGateway.on('message', (message: IMMessage) => {
189+
this.emit('message', message);
190+
});
170191
}
171192

172193
/**
@@ -200,6 +221,11 @@ export class IMGatewayManager extends EventEmitter {
200221
console.log('[IMGatewayManager] Reconnecting NIM...');
201222
this.nimGateway.reconnectIfNeeded();
202223
}
224+
225+
if (this.xiaomifengGateway && !this.xiaomifengGateway.isConnected()) {
226+
console.log('[IMGatewayManager] Reconnecting Xiaomifeng...');
227+
this.xiaomifengGateway.reconnectIfNeeded();
228+
}
203229
}
204230

205231
/**
@@ -268,6 +294,7 @@ export class IMGatewayManager extends EventEmitter {
268294
this.telegramGateway.setMessageCallback(messageHandler);
269295
this.discordGateway.setMessageCallback(messageHandler);
270296
this.nimGateway.setMessageCallback(messageHandler);
297+
this.xiaomifengGateway.setMessageCallback(messageHandler);
271298
}
272299

273300
/**
@@ -396,6 +423,7 @@ export class IMGatewayManager extends EventEmitter {
396423
telegram: this.telegramGateway.getStatus(),
397424
discord: this.discordGateway.getStatus(),
398425
nim: this.nimGateway.getStatus(),
426+
xiaomifeng: this.xiaomifengGateway.getStatus(),
399427
};
400428
}
401429

@@ -616,6 +644,8 @@ export class IMGatewayManager extends EventEmitter {
616644
await this.discordGateway.start(config.discord);
617645
} else if (platform === 'nim') {
618646
await this.nimGateway.start(config.nim);
647+
} else if (platform === 'xiaomifeng') {
648+
await this.xiaomifengGateway.start(config.xiaomifeng);
619649
}
620650

621651
// Restore persisted notification target
@@ -636,6 +666,8 @@ export class IMGatewayManager extends EventEmitter {
636666
await this.discordGateway.stop();
637667
} else if (platform === 'nim') {
638668
await this.nimGateway.stop();
669+
} else if (platform === 'xiaomifeng') {
670+
await this.xiaomifengGateway.stop();
639671
}
640672
}
641673

@@ -684,6 +716,14 @@ export class IMGatewayManager extends EventEmitter {
684716
console.error(`[IMGatewayManager] Failed to start NIM: ${error.message}`);
685717
}
686718
}
719+
720+
if (config.xiaomifeng?.enabled && config.xiaomifeng?.clientId && config.xiaomifeng?.secret) {
721+
try {
722+
await this.startGateway('xiaomifeng');
723+
} catch (error: any) {
724+
console.error(`[IMGatewayManager] Failed to start Xiaomifeng: ${error.message}`);
725+
}
726+
}
687727
}
688728

689729
/**
@@ -696,14 +736,15 @@ export class IMGatewayManager extends EventEmitter {
696736
this.telegramGateway.stop(),
697737
this.discordGateway.stop(),
698738
this.nimGateway.stop(),
739+
this.xiaomifengGateway.stop(),
699740
]);
700741
}
701742

702743
/**
703744
* Check if any gateway is connected
704745
*/
705746
isAnyConnected(): boolean {
706-
return this.dingtalkGateway.isConnected() || this.feishuGateway.isConnected() || this.telegramGateway.isConnected() || this.discordGateway.isConnected() || this.nimGateway.isConnected();
747+
return this.dingtalkGateway.isConnected() || this.feishuGateway.isConnected() || this.telegramGateway.isConnected() || this.discordGateway.isConnected() || this.nimGateway.isConnected() || this.xiaomifengGateway.isConnected();
707748
}
708749

709750
/**
@@ -722,6 +763,9 @@ export class IMGatewayManager extends EventEmitter {
722763
if (platform === 'nim') {
723764
return this.nimGateway.isConnected();
724765
}
766+
if (platform === 'xiaomifeng') {
767+
return this.xiaomifengGateway.isConnected();
768+
}
725769
return this.feishuGateway.isConnected();
726770
}
727771

@@ -793,6 +837,7 @@ export class IMGatewayManager extends EventEmitter {
793837
telegram: { ...current.telegram, ...(configOverride.telegram || {}) },
794838
discord: { ...current.discord, ...(configOverride.discord || {}) },
795839
nim: { ...current.nim, ...(configOverride.nim || {}) },
840+
xiaomifeng: { ...current.xiaomifeng, ...(configOverride.xiaomifeng || {}) },
796841
settings: { ...current.settings, ...(configOverride.settings || {}) },
797842
};
798843
}
@@ -820,6 +865,12 @@ export class IMGatewayManager extends EventEmitter {
820865
if (!config.nim.token) fields.push('token');
821866
return fields;
822867
}
868+
if (platform === 'xiaomifeng') {
869+
const fields: string[] = [];
870+
if (!config.xiaomifeng?.clientId) fields.push('clientId');
871+
if (!config.xiaomifeng?.secret) fields.push('secret');
872+
return fields;
873+
}
823874
return config.discord.botToken ? [] : ['botToken'];
824875
}
825876

@@ -872,13 +923,28 @@ export class IMGatewayManager extends EventEmitter {
872923
// check will happen when the user enables the gateway and the SDK logs in.
873924
return `云信配置已填写(Account: ${config.nim.account})。请启用渠道,SDK 登录时将完成实际凭证验证。`;
874925
}
875-
const response = await fetchJsonWithTimeout<DiscordUserResponse>('https://discord.com/api/v10/users/@me', {
876-
headers: {
877-
Authorization: `Bot ${config.discord.botToken}`,
878-
},
879-
}, CONNECTIVITY_TIMEOUT_MS);
880-
const username = response.username ? `${response.username}#${response.discriminator || '0000'}` : 'unknown';
881-
return `Discord 鉴权通过(Bot: ${username})。`;
926+
927+
if (platform === 'xiaomifeng') {
928+
// 小蜜蜂使用网易云信 NIM SDK,鉴权是通过 SDK 登录验证的
929+
// 这里我们只做配置完整性检查,实际登录验证在 start 时进行
930+
const { clientId, secret } = config.xiaomifeng;
931+
if (!clientId || !secret) {
932+
throw new Error('配置不完整');
933+
}
934+
return `小蜜蜂配置已就绪(Client ID: ${clientId})。`;
935+
}
936+
937+
if (platform === 'discord') {
938+
const response = await fetchJsonWithTimeout<DiscordUserResponse>('https://discord.com/api/v10/users/@me', {
939+
headers: {
940+
Authorization: `Bot ${config.discord.botToken}`,
941+
},
942+
}, CONNECTIVITY_TIMEOUT_MS);
943+
const username = response.username ? `${response.username}#${response.discriminator || '0000'}` : 'unknown';
944+
return `Discord 鉴权通过(Bot: ${username})。`;
945+
}
946+
947+
return '未知平台。';
882948
}
883949

884950
private resolveFeishuDomain(domain: string, Lark: any): any {
@@ -906,6 +972,7 @@ export class IMGatewayManager extends EventEmitter {
906972
if (platform === 'dingtalk') return status.dingtalk.startedAt;
907973
if (platform === 'telegram') return status.telegram.startedAt;
908974
if (platform === 'nim') return status.nim.startedAt;
975+
if (platform === 'xiaomifeng') return status.xiaomifeng.startedAt;
909976
return status.discord.startedAt;
910977
}
911978

@@ -914,6 +981,7 @@ export class IMGatewayManager extends EventEmitter {
914981
if (platform === 'feishu') return status.feishu.lastInboundAt;
915982
if (platform === 'telegram') return status.telegram.lastInboundAt;
916983
if (platform === 'nim') return status.nim.lastInboundAt;
984+
if (platform === 'xiaomifeng') return status.xiaomifeng.lastInboundAt;
917985
return status.discord.lastInboundAt;
918986
}
919987

@@ -922,6 +990,7 @@ export class IMGatewayManager extends EventEmitter {
922990
if (platform === 'feishu') return status.feishu.lastOutboundAt;
923991
if (platform === 'telegram') return status.telegram.lastOutboundAt;
924992
if (platform === 'nim') return status.nim.lastOutboundAt;
993+
if (platform === 'xiaomifeng') return status.xiaomifeng.lastOutboundAt;
925994
return status.discord.lastOutboundAt;
926995
}
927996

@@ -930,6 +999,7 @@ export class IMGatewayManager extends EventEmitter {
930999
if (platform === 'feishu') return status.feishu.error;
9311000
if (platform === 'telegram') return status.telegram.lastError;
9321001
if (platform === 'nim') return status.nim.lastError;
1002+
if (platform === 'xiaomifeng') return status.xiaomifeng.lastError;
9331003
return status.discord.lastError;
9341004
}
9351005

src/main/im/imStore.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
TelegramConfig,
1212
DiscordConfig,
1313
NimConfig,
14+
XiaomifengConfig,
1415
IMSettings,
1516
IMPlatform,
1617
IMSessionMapping,
@@ -19,6 +20,7 @@ import {
1920
DEFAULT_TELEGRAM_CONFIG,
2021
DEFAULT_DISCORD_CONFIG,
2122
DEFAULT_NIM_CONFIG,
23+
DEFAULT_XIAOMIFENG_CONFIG,
2224
DEFAULT_IM_SETTINGS,
2325
} from './types';
2426

@@ -61,7 +63,7 @@ export class IMStore {
6163
* Migrate existing IM configs to ensure stable defaults.
6264
*/
6365
private migrateDefaults(): void {
64-
const platforms = ['dingtalk', 'feishu', 'telegram', 'discord', 'nim'] as const;
66+
const platforms = ['dingtalk', 'feishu', 'telegram', 'discord', 'nim', 'xiaomifeng'] as const;
6567
let changed = false;
6668

6769
for (const platform of platforms) {
@@ -162,6 +164,7 @@ export class IMStore {
162164
const telegram = this.getConfigValue<TelegramConfig>('telegram') ?? DEFAULT_TELEGRAM_CONFIG;
163165
const discord = this.getConfigValue<DiscordConfig>('discord') ?? DEFAULT_DISCORD_CONFIG;
164166
const nim = this.getConfigValue<NimConfig>('nim') ?? DEFAULT_NIM_CONFIG;
167+
const xiaomifeng = this.getConfigValue<XiaomifengConfig>('xiaomifeng') ?? DEFAULT_XIAOMIFENG_CONFIG;
165168
const settings = this.getConfigValue<IMSettings>('settings') ?? DEFAULT_IM_SETTINGS;
166169

167170
// Resolve enabled field: default to false for safety
@@ -181,6 +184,7 @@ export class IMStore {
181184
telegram: resolveEnabled(telegram, DEFAULT_TELEGRAM_CONFIG),
182185
discord: resolveEnabled(discord, DEFAULT_DISCORD_CONFIG),
183186
nim: resolveEnabled(nim, DEFAULT_NIM_CONFIG),
187+
xiaomifeng: resolveEnabled(xiaomifeng, DEFAULT_XIAOMIFENG_CONFIG),
184188
settings: { ...DEFAULT_IM_SETTINGS, ...settings },
185189
};
186190
}
@@ -201,6 +205,9 @@ export class IMStore {
201205
if (config.nim) {
202206
this.setNimConfig(config.nim);
203207
}
208+
if (config.xiaomifeng) {
209+
this.setXiaomifengConfig(config.xiaomifeng);
210+
}
204211
if (config.settings) {
205212
this.setIMSettings(config.settings);
206213
}
@@ -266,6 +273,18 @@ export class IMStore {
266273
this.setConfigValue('nim', { ...current, ...config });
267274
}
268275

276+
// ==================== Xiaomifeng Config ====================
277+
278+
getXiaomifengConfig(): XiaomifengConfig {
279+
const stored = this.getConfigValue<XiaomifengConfig>('xiaomifeng');
280+
return { ...DEFAULT_XIAOMIFENG_CONFIG, ...stored };
281+
}
282+
283+
setXiaomifengConfig(config: Partial<XiaomifengConfig>): void {
284+
const current = this.getXiaomifengConfig();
285+
this.setConfigValue('xiaomifeng', { ...current, ...config });
286+
}
287+
269288
// ==================== IM Settings ====================
270289

271290
getIMSettings(): IMSettings {
@@ -298,7 +317,8 @@ export class IMStore {
298317
const hasTelegram = !!config.telegram.botToken;
299318
const hasDiscord = !!config.discord.botToken;
300319
const hasNim = !!(config.nim.appKey && config.nim.account && config.nim.token);
301-
return hasDingTalk || hasFeishu || hasTelegram || hasDiscord || hasNim;
320+
const hasXiaomifeng = !!(config.xiaomifeng?.clientId && config.xiaomifeng?.secret);
321+
return hasDingTalk || hasFeishu || hasTelegram || hasDiscord || hasNim || hasXiaomifeng;
302322
}
303323

304324
// ==================== Notification Target Persistence ====================

0 commit comments

Comments
 (0)