n8n-nodes-zalo-nnt
Version:
Unofficial Zalo integration for n8n - Send messages, manage groups, user operations with QR login. No API key required, works via browser simulation.
317 lines • 13.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFFmpegAvailable = isFFmpegAvailable;
exports.isFFprobeAvailable = isFFprobeAvailable;
exports.downloadVideo = downloadVideo;
exports.getVideoMetadata = getVideoMetadata;
exports.extractThumbnail = extractThumbnail;
exports.processVideo = processVideo;
exports.cleanupVideoFiles = cleanupVideoFiles;
exports.uploadThumbnailToCatbox = uploadThumbnailToCatbox;
exports.uploadThumbnailToLitterbox = uploadThumbnailToLitterbox;
exports.getPublicThumbnailUrl = getPublicThumbnailUrl;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const child_process_1 = require("child_process");
const util_1 = require("util");
const axios_1 = __importDefault(require("axios"));
const execAsync = (0, util_1.promisify)(child_process_1.exec);
function getTempDir() {
const n8nUserFolder = process.env.N8N_USER_FOLDER || path.join(os.homedir(), '.n8n');
const tempDir = path.join(n8nUserFolder, 'temp_videos');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
return tempDir;
}
function isFFmpegAvailable() {
try {
(0, child_process_1.execSync)('ffmpeg -version', { stdio: 'ignore' });
return true;
}
catch {
return false;
}
}
function isFFprobeAvailable() {
try {
(0, child_process_1.execSync)('ffprobe -version', { stdio: 'ignore' });
return true;
}
catch {
return false;
}
}
async function downloadVideo(url, logger) {
try {
logger.info(`[VideoProcessor] Downloading video from: ${url}`);
const tempDir = getTempDir();
const timestamp = Date.now();
let ext = '.mp4';
try {
const parsedUrl = new URL(url);
const urlPath = parsedUrl.pathname;
const urlExt = path.extname(urlPath).toLowerCase();
if (urlExt && ['.mp4', '.mov', '.avi', '.webm', '.mkv'].includes(urlExt)) {
ext = urlExt;
}
}
catch {
}
const videoPath = path.join(tempDir, `video-${timestamp}${ext}`);
const response = await axios_1.default.get(url, {
responseType: 'arraybuffer',
timeout: 120000,
maxContentLength: 100 * 1024 * 1024,
});
fs.writeFileSync(videoPath, response.data);
const fileSize = fs.statSync(videoPath).size;
logger.info(`[VideoProcessor] Video downloaded: ${videoPath} (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
return videoPath;
}
catch (error) {
logger.error(`[VideoProcessor] Failed to download video: ${error.message}`);
return null;
}
}
async function getVideoMetadata(videoPath, logger) {
var _a, _b, _c;
if (!isFFprobeAvailable()) {
logger.warn('[VideoProcessor] ffprobe not available, cannot get video metadata');
return null;
}
try {
const cmd = `ffprobe -v quiet -print_format json -show_format -show_streams "${videoPath}"`;
const { stdout } = await execAsync(cmd);
const probeData = JSON.parse(stdout);
const videoStream = (_a = probeData.streams) === null || _a === void 0 ? void 0 : _a.find((s) => s.codec_type === 'video');
const duration = ((_b = probeData.format) === null || _b === void 0 ? void 0 : _b.duration)
? Math.round(parseFloat(probeData.format.duration) * 1000)
: 0;
const width = (videoStream === null || videoStream === void 0 ? void 0 : videoStream.width) || 1280;
const height = (videoStream === null || videoStream === void 0 ? void 0 : videoStream.height) || 720;
const fileSize = ((_c = probeData.format) === null || _c === void 0 ? void 0 : _c.size) ? parseInt(probeData.format.size) : 0;
logger.info(`[VideoProcessor] Video metadata: ${width}x${height}, duration=${duration}ms, size=${fileSize}`);
return { duration, width, height, fileSize };
}
catch (error) {
logger.error(`[VideoProcessor] Failed to get video metadata: ${error.message}`);
return null;
}
}
async function extractThumbnail(videoPath, logger) {
if (!isFFmpegAvailable()) {
logger.warn('[VideoProcessor] ffmpeg not available, cannot extract thumbnail');
return null;
}
try {
const tempDir = getTempDir();
const timestamp = Date.now();
const thumbnailPath = path.join(tempDir, `thumb-${timestamp}.jpg`);
const cmd = `ffmpeg -ss 1 -i "${videoPath}" -vframes 1 -q:v 2 -y "${thumbnailPath}"`;
logger.info(`[VideoProcessor] Extracting thumbnail with command: ${cmd}`);
await execAsync(cmd, { timeout: 30000 });
if (fs.existsSync(thumbnailPath)) {
const thumbSize = fs.statSync(thumbnailPath).size;
logger.info(`[VideoProcessor] Thumbnail extracted: ${thumbnailPath} (${thumbSize} bytes)`);
return thumbnailPath;
}
else {
const cmdFirstFrame = `ffmpeg -i "${videoPath}" -vframes 1 -q:v 2 -y "${thumbnailPath}"`;
await execAsync(cmdFirstFrame, { timeout: 30000 });
if (fs.existsSync(thumbnailPath)) {
logger.info(`[VideoProcessor] Thumbnail extracted (first frame): ${thumbnailPath}`);
return thumbnailPath;
}
}
logger.error('[VideoProcessor] Failed to extract thumbnail - file not created');
return null;
}
catch (error) {
logger.error(`[VideoProcessor] Failed to extract thumbnail: ${error.message}`);
return null;
}
}
async function processVideo(videoSource, logger) {
const result = {
thumbnailPath: null,
metadata: null,
videoPath: null,
errors: [],
};
const ffmpegAvailable = isFFmpegAvailable();
const ffprobeAvailable = isFFprobeAvailable();
if (!ffmpegAvailable) {
result.errors.push('ffmpeg not installed - cannot extract thumbnail automatically');
logger.warn('[VideoProcessor] ffmpeg not available');
}
if (!ffprobeAvailable) {
result.errors.push('ffprobe not installed - cannot get video metadata automatically');
logger.warn('[VideoProcessor] ffprobe not available');
}
if (!ffmpegAvailable && !ffprobeAvailable) {
return result;
}
const isLocalFile = fs.existsSync(videoSource);
let videoPath = null;
if (isLocalFile) {
logger.info(`[VideoProcessor] Using local file: ${videoSource}`);
videoPath = videoSource;
result.videoPath = null;
}
else {
videoPath = await downloadVideo(videoSource, logger);
if (!videoPath) {
result.errors.push('Failed to download video');
return result;
}
result.videoPath = videoPath;
}
if (ffprobeAvailable && videoPath) {
result.metadata = await getVideoMetadata(videoPath, logger);
}
if (ffmpegAvailable && videoPath) {
result.thumbnailPath = await extractThumbnail(videoPath, logger);
}
return result;
}
function cleanupVideoFiles(files, logger) {
for (const filePath of files) {
if (filePath && fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath);
logger.info(`[VideoProcessor] Cleaned up: ${filePath}`);
}
catch (error) {
logger.warn(`[VideoProcessor] Failed to cleanup ${filePath}: ${error.message}`);
}
}
}
}
async function uploadThumbnailToCatbox(localPath, logger) {
try {
const FormData = (await Promise.resolve().then(() => __importStar(require('form-data')))).default;
logger.info(`[VideoProcessor] Uploading thumbnail to catbox.moe: ${localPath}`);
const formData = new FormData();
formData.append('reqtype', 'fileupload');
formData.append('fileToUpload', fs.createReadStream(localPath));
const response = await axios_1.default.post('https://catbox.moe/user/api.php', formData, {
headers: formData.getHeaders(),
timeout: 60000,
});
const uploadedUrl = response.data;
if (uploadedUrl && typeof uploadedUrl === 'string' && uploadedUrl.startsWith('https://')) {
logger.info(`[VideoProcessor] Thumbnail uploaded successfully: ${uploadedUrl}`);
return uploadedUrl;
}
logger.error(`[VideoProcessor] Unexpected catbox response: ${uploadedUrl}`);
return null;
}
catch (error) {
logger.error(`[VideoProcessor] Failed to upload thumbnail to catbox: ${error.message}`);
return null;
}
}
async function uploadThumbnailToLitterbox(localPath, logger) {
try {
const FormData = (await Promise.resolve().then(() => __importStar(require('form-data')))).default;
logger.info(`[VideoProcessor] Uploading thumbnail to litterbox (temp): ${localPath}`);
const formData = new FormData();
formData.append('reqtype', 'fileupload');
formData.append('time', '1h');
formData.append('fileToUpload', fs.createReadStream(localPath));
const response = await axios_1.default.post('https://litterbox.catbox.moe/resources/internals/api.php', formData, {
headers: formData.getHeaders(),
timeout: 60000,
});
const uploadedUrl = response.data;
if (uploadedUrl && typeof uploadedUrl === 'string' && uploadedUrl.startsWith('https://')) {
logger.info(`[VideoProcessor] Thumbnail uploaded to litterbox: ${uploadedUrl}`);
return uploadedUrl;
}
logger.error(`[VideoProcessor] Unexpected litterbox response: ${uploadedUrl}`);
return null;
}
catch (error) {
logger.error(`[VideoProcessor] Failed to upload to litterbox: ${error.message}`);
return uploadThumbnailToCatbox(localPath, logger);
}
}
async function getPublicThumbnailUrl(videoUrl, userProvidedUrl, logger) {
const result = {
url: null,
localPath: null,
videoPath: null,
metadata: null,
diagnosis: null,
};
if (userProvidedUrl && userProvidedUrl.trim() !== '') {
result.url = userProvidedUrl.trim();
logger.info(`[VideoProcessor] Using user-provided thumbnail URL: ${result.url}`);
return result;
}
logger.info('[VideoProcessor] No thumbnail provided, attempting to extract from video...');
const processed = await processVideo(videoUrl, logger);
result.videoPath = processed.videoPath;
result.metadata = processed.metadata;
if (!processed.thumbnailPath) {
result.diagnosis =
processed.errors.length > 0
? `Could not extract a thumbnail from the video: ${processed.errors.join('; ')}`
: 'Could not extract a thumbnail from the video (unknown reason — check server logs).';
logger.error(`[VideoProcessor] ${result.diagnosis}`);
return result;
}
result.localPath = processed.thumbnailPath;
const uploadedUrl = await uploadThumbnailToLitterbox(processed.thumbnailPath, logger);
if (uploadedUrl) {
result.url = uploadedUrl;
return result;
}
result.diagnosis =
'A thumbnail was extracted from the video successfully with ffmpeg, but uploading it to ' +
'the public image host failed (litterbox.catbox.moe and catbox.moe both rejected the ' +
'upload). These free anonymous hosts often block requests from cloud/VPS server IPs, ' +
'which is the common case for self-hosted n8n. Provide a "Thumbnail URL" manually ' +
'(e.g. host it on your own storage/CDN) to avoid depending on them.';
logger.warn(`[VideoProcessor] ${result.diagnosis}`);
return result;
}
//# sourceMappingURL=videoProcessor.js.map