diff --git a/package-lock.json b/package-lock.json index 307d62a2..8cc914fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "qr-image": "^3.2.0", "qrcode-terminal": "^0.12.0", "swagger-ui-express": "^5.0.1", - "whatsapp-web.js": "^1.26.0" + "whatsapp-web.js": "1.31.0" }, "devDependencies": { "eslint": "^8.38.0", @@ -1727,7 +1727,8 @@ "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "optional": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -3606,21 +3607,29 @@ "dev": true }, "node_modules/fluent-ffmpeg": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", - "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", "dependencies": { - "async": ">=0.2.9", + "async": "^0.2.9", "which": "^1.1.1" }, "engines": { - "node": ">=0.8.0" + "node": ">=18" } }, + "node_modules/fluent-ffmpeg/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" + }, "node_modules/fluent-ffmpeg/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -7426,13 +7435,13 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/whatsapp-web.js": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/whatsapp-web.js/-/whatsapp-web.js-1.26.0.tgz", - "integrity": "sha512-JUbpwVmJE427KgYIXju+ycHq65SNnXkAsoWwnEUpyI4E0O45S5aG1stRML7aGJxVb6aycTR+hLrTlEiygsNJug==", + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/whatsapp-web.js/-/whatsapp-web.js-1.31.0.tgz", + "integrity": "sha512-oUfrgSx7s906flFmATA0Hqb8DJYv0tdB28KMQ7dWiua8NqcO1+8IFHE278YvSuFkBqRqH+fSfQrGmwh/4Mx/LQ==", "license": "Apache-2.0", "dependencies": { "@pedroslopez/moduleraid": "^5.0.2", - "fluent-ffmpeg": "2.1.2", + "fluent-ffmpeg": "2.1.3", "mime": "^3.0.0", "node-fetch": "^2.6.9", "node-webpmux": "3.1.7", diff --git a/package.json b/package.json index 4eac7d6b..f208fdc1 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "qr-image": "^3.2.0", "qrcode-terminal": "^0.12.0", "swagger-ui-express": "^5.0.1", - "whatsapp-web.js": "^1.26.0" + "whatsapp-web.js": "1.31.0" }, "devDependencies": { "eslint": "^8.38.0", diff --git a/src/controllers/contactController.js b/src/controllers/contactController.js index dfa874f1..335b8a6a 100644 --- a/src/controllers/contactController.js +++ b/src/controllers/contactController.js @@ -1,218 +1,255 @@ -const { sessions } = require('../sessions') -const { sendErrorResponse } = require('../utils') +// controllers/contactController.js + +const { sessions } = require('../sessions'); +const { sendErrorResponse } = require('../utils'); /** - * Retrieves information about a WhatsApp contact by ID. - * - * @async - * @function - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The ID of the current session. - * @param {string} req.body.contactId - The ID of the contact to retrieve information for. - * @throws {Error} If there is an error retrieving the contact information. - * @returns {Object} The contact information object. + * Recupera informações de um contato do WhatsApp por ID. */ const getClassInfo = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); if (!contact) { - sendErrorResponse(res, 404, 'Contact not Found') + return sendErrorResponse(res, 404, 'Contact not Found'); } - res.json({ success: true, result: contact }) + res.json({ success: true, result: contact }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; /** - * Blocks a WhatsApp contact by ID. - * - * @async - * @function - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The ID of the current session. - * @param {string} req.body.contactId - The ID of the contact to block. - * @throws {Error} If there is an error blocking the contact. - * @returns {Object} The result of the blocking operation. + * Bloqueia um contato do WhatsApp por ID. */ const block = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); if (!contact) { - sendErrorResponse(res, 404, 'Contact not Found') + return sendErrorResponse(res, 404, 'Contact not Found'); } - const result = await contact.block() - res.json({ success: true, result }) + const result = await contact.block(); + res.json({ success: true, result }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; /** - * Retrieves the 'About' information of a WhatsApp contact by ID. - * - * @async - * @function - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The ID of the current session. - * @param {string} req.body.contactId - The ID of the contact to retrieve 'About' information for. - * @throws {Error} If there is an error retrieving the contact information. - * @returns {Object} The 'About' information of the contact. + * Recupera o campo "About" de um contato. */ const getAbout = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); if (!contact) { - sendErrorResponse(res, 404, 'Contact not Found') + return sendErrorResponse(res, 404, 'Contact not Found'); } - const result = await contact.getAbout() - res.json({ success: true, result }) + const result = await contact.getAbout(); + res.json({ success: true, result }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; /** - * Retrieves the chat information of a contact with a given contactId. - * - * @async - * @function getChat - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The session ID. - * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. - * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. - * @returns {Promise} A promise that resolves with the chat information of the contact. + * Recupera o chat de um contato. */ const getChat = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) - if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } - const result = await contact.getChat() - res.json({ success: true, result }) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); + if (!contact) { + return sendErrorResponse(res, 404, 'Contact not Found'); + } + const result = await contact.getChat(); + res.json({ success: true, result }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; /** - * Retrieves the formatted number of a contact with a given contactId. - * - * @async - * @function getFormattedNumber - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The session ID. - * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. - * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. - * @returns {Promise} A promise that resolves with the formatted number of the contact. + * Recupera o número formatado de um contato. */ const getFormattedNumber = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) - if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } - const result = await contact.getFormattedNumber() - res.json({ success: true, result }) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); + if (!contact) { + return sendErrorResponse(res, 404, 'Contact not Found'); + } + const result = await contact.getFormattedNumber(); + res.json({ success: true, result }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; /** - * Retrieves the country code of a contact with a given contactId. - * - * @async - * @function getCountryCode - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The session ID. - * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. - * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. - * @returns {Promise} A promise that resolves with the country code of the contact. + * Recupera o código de país de um contato. */ const getCountryCode = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) - if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } - const result = await contact.getCountryCode() - res.json({ success: true, result }) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); + if (!contact) { + return sendErrorResponse(res, 404, 'Contact not Found'); + } + const result = await contact.getCountryCode(); + res.json({ success: true, result }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; /** - * Retrieves the profile picture url of a contact with a given contactId. - * - * @async - * @function getProfilePicUrl - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The session ID. - * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. - * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. - * @returns {Promise} A promise that resolves with the profile picture url of the contact. + * Recupera a URL da foto de perfil de um contato. */ const getProfilePicUrl = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) - if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } - const result = await contact.getProfilePicUrl() || null - res.json({ success: true, result }) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); + if (!contact) { + return sendErrorResponse(res, 404, 'Contact not Found'); + } + // catch para caso não haja foto + const result = await contact.getProfilePicUrl().catch(() => null); + res.json({ success: true, result }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; /** - * Unblocks the contact with a given contactId. - * - * @async - * @function unblock - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {string} req.params.sessionId - The session ID. - * @param {string} req.body.contactId - The ID of the client whose contact is being unblocked. - * @throws {Error} If the contact with the given contactId is not found or if there is an error unblocking the contact. - * @returns {Promise} A promise that resolves with the result of unblocking the contact. + * Desbloqueia um contato do WhatsApp por ID. */ const unblock = async (req, res) => { try { - const { contactId } = req.body - const client = sessions.get(req.params.sessionId) - const contact = await client.getContactById(contactId) - if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } - const result = await contact.unblock() - res.json({ success: true, result }) + const { contactId } = req.body; + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + const contact = await client.getContactById(contactId); + if (!contact) { + return sendErrorResponse(res, 404, 'Contact not Found'); + } + const result = await contact.unblock(); + res.json({ success: true, result }); } catch (error) { - sendErrorResponse(res, 500, error.message) + sendErrorResponse(res, 500, error.message); } -} +}; + +/** + * Recupera todos os grupos ativos dos quais a sessão ainda faz parte, + * trazendo informações detalhadas e regras de participação e envio. + * + * Usa apenas UMA chamada a client.getChats() e filtra diretamente + * pelos metadados disponíveis no array de Chat, sem iterar chamando + * getChatById em cada um. + */ +const getActiveGroups = async (req, res) => { + try { + const client = sessions.get(req.params.sessionId); + if (!client) { + return sendErrorResponse(res, 404, 'Session not Found'); + } + + // 1 única chamada a todos os chats + const chats = await client.getChats(); + const myJid = client.info.wid._serialized; + + // para cada chat de grupo, pega os metadados já carregados + const groups = await Promise.all( + chats + .filter(chat => chat.isGroup && chat.groupMetadata) // só grupos com metadados + .map(async chat => { + const meta = chat.groupMetadata; + const participants = meta.participants || []; + + // só retorna se eu ainda estiver no grupo + const me = participants.find(p => p.id._serialized === myJid); + if (!me) return null; + + // foto do grupo (usa client, não chat) + let picture = null; + try { + picture = await client.getProfilePicUrl(meta.id._serialized); + } catch {} + + return { + id: meta.id._serialized, + name: chat.name || meta.subject, + subject: meta.subject, + owner: meta.owner?._serialized, + createdAt: meta.createdAt, + description: meta.description, + picture, + // regras + announcementOnly: Boolean(chat.isAnnounceGroup), // só admin envia + restrictInfo: Boolean(chat.isRestricted), // só admin altera + participantCount: participants.length, + participants: participants.map(p => ({ + id: p.id._serialized, + isAdmin: p.isAdmin, + isSuperAdmin: p.isSuperAdmin + })), + myRole: { + isAdmin: me.isAdmin, + isSuperAdmin: me.isSuperAdmin + }, + // posso enviar? + canIMessage: !chat.isAnnounceGroup || me.isAdmin || me.isSuperAdmin + }; + }) + ); + + // filtra eventuais nulos (casos de saída do grupo) + res.json({ success: true, result: groups.filter(g => g) }); + } catch (error) { + sendErrorResponse(res, 500, error.message); + } +}; module.exports = { getClassInfo, block, getAbout, getChat, - unblock, getFormattedNumber, getCountryCode, - getProfilePicUrl -} + getProfilePicUrl, + unblock, + getActiveGroups +}; diff --git a/src/routes.js b/src/routes.js index 8f0ffdd6..65f7326e 100644 --- a/src/routes.js +++ b/src/routes.js @@ -178,6 +178,12 @@ contactRouter.post('/unblock/:sessionId', [middleware.sessionNameValidation, mid contactRouter.post('/getFormattedNumber/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getFormattedNumber) contactRouter.post('/getCountryCode/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getCountryCode) contactRouter.post('/getProfilePicUrl/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getProfilePicUrl) +// novo endpoint para buscar grupos ativos +contactRouter.get( + '/activeGroups/:sessionId', + [middleware.sessionNameValidation, middleware.sessionValidation], + contactController.getActiveGroups +) /** * ================ * SWAGGER ENDPOINTS