Merge branch 'main' into feat/channel_enhancement
Keep the channel enhancements aligned with the current codebase while preserving a simpler product surface. This keeps QQ, Feishu, Telegram, and WhatsApp improvements together, removes the extra Telegram-only tool hint toggle, and makes WhatsApp mention-only groups actually work.
This commit is contained in:
@@ -12,6 +12,17 @@ interface SendCommand {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface SendMediaCommand {
|
||||
type: 'send_media';
|
||||
to: string;
|
||||
filePath: string;
|
||||
mimetype: string;
|
||||
caption?: string;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
type BridgeCommand = SendCommand | SendMediaCommand;
|
||||
|
||||
interface BridgeMessage {
|
||||
type: 'message' | 'status' | 'qr' | 'error';
|
||||
[key: string]: unknown;
|
||||
@@ -72,7 +83,7 @@ export class BridgeServer {
|
||||
|
||||
ws.on('message', async (data) => {
|
||||
try {
|
||||
const cmd = JSON.parse(data.toString()) as SendCommand;
|
||||
const cmd = JSON.parse(data.toString()) as BridgeCommand;
|
||||
await this.handleCommand(cmd);
|
||||
ws.send(JSON.stringify({ type: 'sent', to: cmd.to }));
|
||||
} catch (error) {
|
||||
@@ -92,9 +103,13 @@ export class BridgeServer {
|
||||
});
|
||||
}
|
||||
|
||||
private async handleCommand(cmd: SendCommand): Promise<void> {
|
||||
if (cmd.type === 'send' && this.wa) {
|
||||
private async handleCommand(cmd: BridgeCommand): Promise<void> {
|
||||
if (!this.wa) return;
|
||||
|
||||
if (cmd.type === 'send') {
|
||||
await this.wa.sendMessage(cmd.to, cmd.text);
|
||||
} else if (cmd.type === 'send_media') {
|
||||
await this.wa.sendMedia(cmd.to, cmd.filePath, cmd.mimetype, cmd.caption, cmd.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import makeWASocket, {
|
||||
import { Boom } from '@hapi/boom';
|
||||
import qrcode from 'qrcode-terminal';
|
||||
import pino from 'pino';
|
||||
import { writeFile, mkdir } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { readFile, writeFile, mkdir } from 'fs/promises';
|
||||
import { join, basename } from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
const VERSION = '0.1.0';
|
||||
@@ -29,6 +29,7 @@ export interface InboundMessage {
|
||||
content: string;
|
||||
timestamp: number;
|
||||
isGroup: boolean;
|
||||
wasMentioned?: boolean;
|
||||
media?: string[];
|
||||
}
|
||||
|
||||
@@ -48,6 +49,31 @@ export class WhatsAppClient {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
private normalizeJid(jid: string | undefined | null): string {
|
||||
return (jid || '').split(':')[0];
|
||||
}
|
||||
|
||||
private wasMentioned(msg: any): boolean {
|
||||
if (!msg?.key?.remoteJid?.endsWith('@g.us')) return false;
|
||||
|
||||
const candidates = [
|
||||
msg?.message?.extendedTextMessage?.contextInfo?.mentionedJid,
|
||||
msg?.message?.imageMessage?.contextInfo?.mentionedJid,
|
||||
msg?.message?.videoMessage?.contextInfo?.mentionedJid,
|
||||
msg?.message?.documentMessage?.contextInfo?.mentionedJid,
|
||||
msg?.message?.audioMessage?.contextInfo?.mentionedJid,
|
||||
];
|
||||
const mentioned = candidates.flatMap((items) => (Array.isArray(items) ? items : []));
|
||||
if (mentioned.length === 0) return false;
|
||||
|
||||
const selfIds = new Set(
|
||||
[this.sock?.user?.id, this.sock?.user?.lid, this.sock?.user?.jid]
|
||||
.map((jid) => this.normalizeJid(jid))
|
||||
.filter(Boolean),
|
||||
);
|
||||
return mentioned.some((jid: string) => selfIds.has(this.normalizeJid(jid)));
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const logger = pino({ level: 'silent' });
|
||||
const { state, saveCreds } = await useMultiFileAuthState(this.options.authDir);
|
||||
@@ -145,6 +171,7 @@ export class WhatsAppClient {
|
||||
if (!finalContent && mediaPaths.length === 0) continue;
|
||||
|
||||
const isGroup = msg.key.remoteJid?.endsWith('@g.us') || false;
|
||||
const wasMentioned = this.wasMentioned(msg);
|
||||
|
||||
this.options.onMessage({
|
||||
id: msg.key.id || '',
|
||||
@@ -153,6 +180,7 @@ export class WhatsAppClient {
|
||||
content: finalContent,
|
||||
timestamp: msg.messageTimestamp as number,
|
||||
isGroup,
|
||||
...(isGroup ? { wasMentioned } : {}),
|
||||
...(mediaPaths.length > 0 ? { media: mediaPaths } : {}),
|
||||
});
|
||||
}
|
||||
@@ -230,6 +258,32 @@ export class WhatsAppClient {
|
||||
await this.sock.sendMessage(to, { text });
|
||||
}
|
||||
|
||||
async sendMedia(
|
||||
to: string,
|
||||
filePath: string,
|
||||
mimetype: string,
|
||||
caption?: string,
|
||||
fileName?: string,
|
||||
): Promise<void> {
|
||||
if (!this.sock) {
|
||||
throw new Error('Not connected');
|
||||
}
|
||||
|
||||
const buffer = await readFile(filePath);
|
||||
const category = mimetype.split('/')[0];
|
||||
|
||||
if (category === 'image') {
|
||||
await this.sock.sendMessage(to, { image: buffer, caption: caption || undefined, mimetype });
|
||||
} else if (category === 'video') {
|
||||
await this.sock.sendMessage(to, { video: buffer, caption: caption || undefined, mimetype });
|
||||
} else if (category === 'audio') {
|
||||
await this.sock.sendMessage(to, { audio: buffer, mimetype });
|
||||
} else {
|
||||
const name = fileName || basename(filePath);
|
||||
await this.sock.sendMessage(to, { document: buffer, mimetype, fileName: name });
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
if (this.sock) {
|
||||
this.sock.end(undefined);
|
||||
|
||||
Reference in New Issue
Block a user