317 lines
9.4 KiB
TypeScript
317 lines
9.4 KiB
TypeScript
export type IMPlatform = 'discord' | 'telegram' | 'dingtalk' | 'feishu';
|
|
|
|
export interface IMConfig {
|
|
platform: IMPlatform;
|
|
enabled: boolean;
|
|
token?: string;
|
|
botToken?: string;
|
|
apiKey?: string;
|
|
apiSecret?: string;
|
|
webhookUrl?: string;
|
|
allowedUsers?: string[];
|
|
autoReply?: boolean;
|
|
}
|
|
|
|
export interface IMLogin {
|
|
platform: IMPlatform;
|
|
userId: string;
|
|
username: string;
|
|
displayName: string;
|
|
avatar?: string;
|
|
}
|
|
|
|
export interface IMMessage {
|
|
id: string;
|
|
platform: IMPlatform;
|
|
sender: IMLogin;
|
|
content: string;
|
|
timestamp: number;
|
|
channelId?: string;
|
|
groupId?: string;
|
|
isGroup: boolean;
|
|
replyTo?: string;
|
|
}
|
|
|
|
export interface IMSendOptions {
|
|
content: string;
|
|
channelId?: string;
|
|
userId?: string;
|
|
replyTo?: string;
|
|
markdown?: boolean;
|
|
}
|
|
|
|
class IMService {
|
|
private configs: Map<IMPlatform, IMConfig> = new Map();
|
|
private handlers: Map<IMPlatform, (message: IMMessage) => void> = new Map();
|
|
private connections: Map<IMPlatform, boolean> = new Map();
|
|
|
|
constructor() {
|
|
// Initialize with empty configs
|
|
this.configs.set('discord', { platform: 'discord', enabled: false });
|
|
this.configs.set('telegram', { platform: 'telegram', enabled: false });
|
|
this.configs.set('dingtalk', { platform: 'dingtalk', enabled: false });
|
|
this.configs.set('feishu', { platform: 'feishu', enabled: false });
|
|
}
|
|
|
|
getConfig(platform: IMPlatform): IMConfig | undefined {
|
|
return this.configs.get(platform);
|
|
}
|
|
|
|
setConfig(platform: IMPlatform, config: Partial<IMConfig>): void {
|
|
const current = this.configs.get(platform) || { platform, enabled: false };
|
|
this.configs.set(platform, { ...current, ...config });
|
|
}
|
|
|
|
getEnabledPlatforms(): IMPlatform[] {
|
|
return Array.from(this.configs.entries())
|
|
.filter(([_, config]) => config.enabled)
|
|
.map(([platform]) => platform);
|
|
}
|
|
|
|
isConnected(platform: IMPlatform): boolean {
|
|
return this.connections.get(platform) || false;
|
|
}
|
|
|
|
onMessage(platform: IMPlatform, handler: (message: IMMessage) => void): void {
|
|
this.handlers.set(platform, handler);
|
|
}
|
|
|
|
async connect(platform: IMPlatform): Promise<{ success: boolean; error?: string }> {
|
|
const config = this.configs.get(platform);
|
|
if (!config || !config.enabled) {
|
|
return { success: false, error: 'Platform not enabled' };
|
|
}
|
|
|
|
try {
|
|
// Platform-specific connection logic
|
|
switch (platform) {
|
|
case 'discord':
|
|
return await this.connectDiscord(config);
|
|
case 'telegram':
|
|
return await this.connectTelegram(config);
|
|
case 'dingtalk':
|
|
return await this.connectDingtalk(config);
|
|
case 'feishu':
|
|
return await this.connectFeishu(config);
|
|
default:
|
|
return { success: false, error: 'Unknown platform' };
|
|
}
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
async disconnect(platform: IMPlatform): Promise<void> {
|
|
this.connections.set(platform, false);
|
|
}
|
|
|
|
async sendMessage(platform: IMPlatform, options: IMSendOptions): Promise<{ success: boolean; error?: string }> {
|
|
if (!this.connections.get(platform)) {
|
|
return { success: false, error: 'Not connected' };
|
|
}
|
|
|
|
try {
|
|
switch (platform) {
|
|
case 'discord':
|
|
return await this.sendDiscordMessage(options);
|
|
case 'telegram':
|
|
return await this.sendTelegramMessage(options);
|
|
case 'dingtalk':
|
|
return await this.sendDingtalkMessage(options);
|
|
case 'feishu':
|
|
return await this.sendFeishuMessage(options);
|
|
default:
|
|
return { success: false, error: 'Unknown platform' };
|
|
}
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
// Discord implementation
|
|
private async connectDiscord(config: IMConfig): Promise<{ success: boolean; error?: string }> {
|
|
if (!config.botToken) {
|
|
return { success: false, error: 'Bot token required' };
|
|
}
|
|
|
|
// Test API connection
|
|
try {
|
|
const response = await fetch('https://discord.com/api/v10/users/@me', {
|
|
headers: { 'Authorization': `Bot ${config.botToken}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
this.connections.set('discord', true);
|
|
return { success: true };
|
|
}
|
|
return { success: false, error: 'Invalid bot token' };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
private async sendDiscordMessage(options: IMSendOptions): Promise<{ success: boolean; error?: string }> {
|
|
const config = this.configs.get('discord');
|
|
if (!config?.botToken || !options.channelId) {
|
|
return { success: false, error: 'Missing configuration' };
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`https://discord.com/api/v10/channels/${options.channelId}/messages`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bot ${config.botToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ content: options.content })
|
|
});
|
|
|
|
return { success: response.ok };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
// Telegram implementation
|
|
private async connectTelegram(config: IMConfig): Promise<{ success: boolean; error?: string }> {
|
|
if (!config.botToken) {
|
|
return { success: false, error: 'Bot token required' };
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`https://api.telegram.org/bot${config.botToken}/getMe`);
|
|
const data = await response.json();
|
|
|
|
if (data.ok) {
|
|
this.connections.set('telegram', true);
|
|
return { success: true };
|
|
}
|
|
return { success: false, error: 'Invalid bot token' };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
private async sendTelegramMessage(options: IMSendOptions): Promise<{ success: boolean; error?: string }> {
|
|
const config = this.configs.get('telegram');
|
|
if (!config?.botToken || !options.userId) {
|
|
return { success: false, error: 'Missing configuration' };
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`https://api.telegram.org/bot${config.botToken}/sendMessage`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
chat_id: options.userId,
|
|
text: options.content,
|
|
})
|
|
});
|
|
|
|
return { success: response.ok };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
// DingTalk implementation
|
|
private async connectDingtalk(config: IMConfig): Promise<{ success: boolean; error?: string }> {
|
|
if (!config.appKey || !config.appSecret) {
|
|
return { success: false, error: 'AppKey and AppSecret required' };
|
|
}
|
|
|
|
try {
|
|
// Get access token
|
|
const tokenResponse = await fetch('https://api.dingtalk.com/v1.0/oauth2/accessToken', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
appKey: config.appKey,
|
|
appSecret: config.appSecret,
|
|
})
|
|
});
|
|
|
|
const data = await tokenResponse.json();
|
|
|
|
if (data.accessToken) {
|
|
this.connections.set('dingtalk', true);
|
|
return { success: true };
|
|
}
|
|
return { success: false, error: 'Failed to get access token' };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
private async sendDingtalkMessage(options: IMSendOptions): Promise<{ success: boolean; error?: string }> {
|
|
// DingTalk webhook implementation
|
|
if (!options.userId) {
|
|
return { success: false, error: 'User ID required' };
|
|
}
|
|
|
|
return { success: true }; // Placeholder
|
|
}
|
|
|
|
// Feishu ( Lark ) implementation
|
|
private async connectFeishu(config: IMConfig): Promise<{ success: boolean; error?: string }> {
|
|
if (!config.appId || !config.appSecret) {
|
|
return { success: false, error: 'AppId and AppSecret required' };
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
app_id: config.appId,
|
|
app_secret: config.appSecret,
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.tenant_access_token) {
|
|
this.connections.set('feishu', true);
|
|
return { success: true };
|
|
}
|
|
return { success: false, error: 'Failed to get tenant token' };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
}
|
|
|
|
private async sendFeishuMessage(options: IMSendOptions): Promise<{ success: boolean; error?: string }> {
|
|
if (!options.userId) {
|
|
return { success: false, error: 'User ID required' };
|
|
}
|
|
|
|
return { success: true }; // Placeholder
|
|
}
|
|
|
|
// Load/save config
|
|
async loadConfigs(): Promise<void> {
|
|
try {
|
|
const saved = await window.electronAPI?.settings.get('im_configs');
|
|
if (saved) {
|
|
const configs = saved as IMConfig[];
|
|
for (const config of configs) {
|
|
this.configs.set(config.platform, config);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load IM configs:', error);
|
|
}
|
|
}
|
|
|
|
async saveConfigs(): Promise<void> {
|
|
try {
|
|
const configs = Array.from(this.configs.values());
|
|
await window.electronAPI?.settings.set('im_configs', configs);
|
|
} catch (error) {
|
|
console.error('Failed to save IM configs:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const imService = new IMService();
|