Merge PR #292: feat(whatsapp): native media attachments for images, videos and documents
Authored by satelerd. Adds native WhatsApp media sending for images, videos, and documents via MEDIA: tags. Also includes conflict resolution with edit_message feature, Telegram hint fix (only advertise supported media types), and import cleanup.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
* GET /messages - Long-poll for new incoming messages
|
||||
* POST /send - Send a message { chatId, message, replyTo? }
|
||||
* POST /edit - Edit a sent message { chatId, messageId, message }
|
||||
* POST /send-media - Send media natively { chatId, filePath, mediaType?, caption?, fileName? }
|
||||
* POST /typing - Send typing indicator { chatId }
|
||||
* GET /chat/:id - Get chat info
|
||||
* GET /health - Health check
|
||||
@@ -22,7 +23,7 @@ import express from 'express';
|
||||
import { Boom } from '@hapi/boom';
|
||||
import pino from 'pino';
|
||||
import path from 'path';
|
||||
import { mkdirSync } from 'fs';
|
||||
import { mkdirSync, readFileSync, existsSync } from 'fs';
|
||||
import qrcode from 'qrcode-terminal';
|
||||
|
||||
// Parse CLI args
|
||||
@@ -238,6 +239,76 @@ app.post('/edit', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// MIME type map and media type inference for /send-media
|
||||
const MIME_MAP = {
|
||||
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
|
||||
webp: 'image/webp', gif: 'image/gif',
|
||||
mp4: 'video/mp4', mov: 'video/quicktime', avi: 'video/x-msvideo',
|
||||
mkv: 'video/x-matroska', '3gp': 'video/3gpp',
|
||||
pdf: 'application/pdf',
|
||||
doc: 'application/msword',
|
||||
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
};
|
||||
|
||||
function inferMediaType(ext) {
|
||||
if (['jpg', 'jpeg', 'png', 'webp', 'gif'].includes(ext)) return 'image';
|
||||
if (['mp4', 'mov', 'avi', 'mkv', '3gp'].includes(ext)) return 'video';
|
||||
if (['ogg', 'opus', 'mp3', 'wav', 'm4a'].includes(ext)) return 'audio';
|
||||
return 'document';
|
||||
}
|
||||
|
||||
// Send media (image, video, document) natively
|
||||
app.post('/send-media', async (req, res) => {
|
||||
if (!sock || connectionState !== 'connected') {
|
||||
return res.status(503).json({ error: 'Not connected to WhatsApp' });
|
||||
}
|
||||
|
||||
const { chatId, filePath, mediaType, caption, fileName } = req.body;
|
||||
if (!chatId || !filePath) {
|
||||
return res.status(400).json({ error: 'chatId and filePath are required' });
|
||||
}
|
||||
|
||||
try {
|
||||
if (!existsSync(filePath)) {
|
||||
return res.status(404).json({ error: `File not found: ${filePath}` });
|
||||
}
|
||||
|
||||
const buffer = readFileSync(filePath);
|
||||
const ext = filePath.toLowerCase().split('.').pop();
|
||||
const type = mediaType || inferMediaType(ext);
|
||||
let msgPayload;
|
||||
|
||||
switch (type) {
|
||||
case 'image':
|
||||
msgPayload = { image: buffer, caption: caption || undefined, mimetype: MIME_MAP[ext] || 'image/jpeg' };
|
||||
break;
|
||||
case 'video':
|
||||
msgPayload = { video: buffer, caption: caption || undefined, mimetype: MIME_MAP[ext] || 'video/mp4' };
|
||||
break;
|
||||
case 'audio': {
|
||||
const audioMime = (ext === 'ogg' || ext === 'opus') ? 'audio/ogg; codecs=opus' : 'audio/mpeg';
|
||||
msgPayload = { audio: buffer, mimetype: audioMime, ptt: ext === 'ogg' || ext === 'opus' };
|
||||
break;
|
||||
}
|
||||
case 'document':
|
||||
default:
|
||||
msgPayload = {
|
||||
document: buffer,
|
||||
fileName: fileName || path.basename(filePath),
|
||||
caption: caption || undefined,
|
||||
mimetype: MIME_MAP[ext] || 'application/octet-stream',
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
const sent = await sock.sendMessage(chatId, msgPayload);
|
||||
res.json({ success: true, messageId: sent?.key?.id });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Typing indicator
|
||||
app.post('/typing', async (req, res) => {
|
||||
if (!sock || connectionState !== 'connected') {
|
||||
|
||||
Reference in New Issue
Block a user