project push

This commit is contained in:
Anton Voylenko 2024-11-28 21:07:25 +02:00
commit 2399a31aac
31 changed files with 20580 additions and 0 deletions

17
src/app.js Normal file
View file

@ -0,0 +1,17 @@
require('./routes')
const express = require('express')
const { restoreSessions } = require('./sessions')
const { routes } = require('./routes')
const { maxAttachmentSize } = require('./config')
const app = express()
// Initialize Express app
app.disable('x-powered-by')
app.use(express.json({ limit: maxAttachmentSize + 1000000 }))
app.use(express.urlencoded({ limit: maxAttachmentSize + 1000000, extended: true }))
app.use('/', routes)
restoreSessions()
module.exports = app

37
src/config.js Normal file
View file

@ -0,0 +1,37 @@
// Load environment variables from .env file
require('dotenv').config()
// setup global const
const sessionFolderPath = process.env.SESSIONS_PATH || './sessions'
const enableLocalCallbackExample = (process.env.ENABLE_LOCAL_CALLBACK_EXAMPLE || '').toLowerCase() === 'true'
const globalApiKey = process.env.API_KEY
const baseWebhookURL = process.env.BASE_WEBHOOK_URL
const maxAttachmentSize = parseInt(process.env.MAX_ATTACHMENT_SIZE) || 10000000
const setMessagesAsSeen = (process.env.SET_MESSAGES_AS_SEEN || '').toLowerCase() === 'true'
const disabledCallbacks = process.env.DISABLED_CALLBACKS ? process.env.DISABLED_CALLBACKS.split('|') : []
const enableSwaggerEndpoint = (process.env.ENABLE_SWAGGER_ENDPOINT || '').toLowerCase() === 'true'
const webVersion = process.env.WEB_VERSION
const webVersionCacheType = process.env.WEB_VERSION_CACHE_TYPE || 'none'
const rateLimitMax = parseInt(process.env.RATE_LIMIT_MAX) || 1000
const rateLimitWindowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 1000
const recoverSessions = (process.env.RECOVER_SESSIONS || '').toLowerCase() === 'true'
const chromeBin = process.env.CHROME_BIN || null
const headless = process.env.HEADLESS ? (process.env.HEADLESS).toLowerCase() === 'true' : true
module.exports = {
sessionFolderPath,
enableLocalCallbackExample,
globalApiKey,
baseWebhookURL,
maxAttachmentSize,
setMessagesAsSeen,
disabledCallbacks,
enableSwaggerEndpoint,
webVersion,
webVersionCacheType,
rateLimitMax,
rateLimitWindowMs,
recoverSessions,
chromeBin,
headless
}

View file

@ -0,0 +1,237 @@
const { sessions } = require('../sessions')
const { sendErrorResponse } = require('../utils')
/**
* @function
* @async
* @name getClassInfo
* @description Gets information about a chat using the chatId and sessionId
* @param {Object} req - Request object
* @param {Object} res - Response object
* @param {string} req.body.chatId - The ID of the chat to get information for
* @param {string} req.params.sessionId - The ID of the session to use
* @returns {Object} - Returns a JSON object with the success status and chat information
* @throws {Error} - Throws an error if chat is not found or if there is a server error
*/
const getClassInfo = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
res.json({ success: true, chat })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Clears all messages in a chat.
*
* @function
* @async
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {string} req.params.sessionId - The ID of the session.
* @param {string} req.body.chatId - The ID of the chat to clear messages from.
* @throws {Error} If the chat is not found or there is an internal server error.
* @returns {Object} The success status and the cleared messages.
*/
const clearMessages = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
const clearMessages = await chat.clearMessages()
res.json({ success: true, clearMessages })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Stops typing or recording in chat immediately.
*
* @function
* @async
* @param {Object} req - Request object.
* @param {Object} res - Response object.
* @param {string} req.body.chatId - ID of the chat to clear the state for.
* @param {string} req.params.sessionId - ID of the session the chat belongs to.
* @returns {Promise<void>} - A Promise that resolves with a JSON object containing a success flag and the result of clearing the state.
* @throws {Error} - If there was an error while clearing the state.
*/
const clearState = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
const clearState = await chat.clearState()
res.json({ success: true, clearState })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Delete a chat.
*
* @async
* @function
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {string} req.body.chatId - The ID of the chat to be deleted.
* @returns {Object} A JSON response indicating whether the chat was deleted successfully.
* @throws {Object} If there is an error while deleting the chat, an error response is sent with a status code of 500.
* @throws {Object} If the chat is not found, an error response is sent with a status code of 404.
*/
const deleteChat = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
const deleteChat = await chat.delete()
res.json({ success: true, deleteChat })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Fetches messages from a specified chat.
*
* @function
* @async
*
* @param {Object} req - The request object containing sessionId, chatId, and searchOptions.
* @param {string} req.params.sessionId - The ID of the session associated with the chat.
* @param {Object} req.body - The body of the request containing chatId and searchOptions.
* @param {string} req.body.chatId - The ID of the chat from which to fetch messages.
* @param {Object} req.body.searchOptions - The search options to use when fetching messages.
*
* @param {Object} res - The response object to send the fetched messages.
* @returns {Promise<Object>} A JSON object containing the success status and fetched messages.
*
* @throws {Error} If the chat is not found or there is an error fetching messages.
*/
const fetchMessages = async (req, res) => {
try {
/*
#swagger.requestBody = {
required: true,
schema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Unique whatsApp identifier for the given Chat (either group or personnal)',
example: '6281288888888@c.us'
},
searchOptions: {
type: 'object',
description: 'Search options for fetching messages',
example: '{}'
}
}
}
}
*/
const { chatId, searchOptions } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
const messages = await chat.fetchMessages(searchOptions)
res.json({ success: true, messages })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Gets the contact for a chat
* @async
* @function
* @param {Object} req - The HTTP request object
* @param {Object} res - The HTTP response object
* @param {string} req.params.sessionId - The ID of the current session
* @param {string} req.body.chatId - The ID of the chat to get the contact for
* @returns {Promise<void>} - Promise that resolves with the chat's contact information
* @throws {Error} - Throws an error if chat is not found or if there is an error getting the contact information
*/
const getContact = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
const contact = await chat.getContact()
res.json({ success: true, contact })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Send a recording state to a WhatsApp chat.
* @async
* @function
* @param {object} req - The request object.
* @param {object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {object} req.body - The request body.
* @param {string} req.body.chatId - The ID of the chat to send the recording state to.
* @returns {object} - An object containing a success message and the result of the sendStateRecording method.
* @throws {object} - An error object containing a status code and error message if an error occurs.
*/
const sendStateRecording = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
const sendStateRecording = await chat.sendStateRecording()
res.json({ success: true, sendStateRecording })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Send a typing state to a WhatsApp chat.
* @async
* @function
* @param {object} req - The request object.
* @param {object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {object} req.body - The request body.
* @param {string} req.body.chatId - The ID of the chat to send the typing state to.
* @returns {object} - An object containing a success message and the result of the sendStateTyping method.
* @throws {object} - An error object containing a status code and error message if an error occurs.
*/
const sendStateTyping = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
const sendStateTyping = await chat.sendStateTyping()
res.json({ success: true, sendStateTyping })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
module.exports = {
getClassInfo,
clearMessages,
clearState,
deleteChat,
fetchMessages,
getContact,
sendStateRecording,
sendStateTyping
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,218 @@
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.
*/
const getClassInfo = 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')
}
res.json({ success: true, result: contact })
} catch (error) {
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.
*/
const block = 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.block()
res.json({ success: true, result })
} catch (error) {
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.
*/
const getAbout = 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.getAbout()
res.json({ success: true, result })
} catch (error) {
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<void>} A promise that resolves with the chat information of the contact.
*/
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 })
} catch (error) {
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<void>} A promise that resolves with the formatted number of the contact.
*/
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 })
} catch (error) {
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<void>} A promise that resolves with the country code of the contact.
*/
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 })
} catch (error) {
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<void>} A promise that resolves with the profile picture url of the contact.
*/
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 })
} catch (error) {
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<void>} A promise that resolves with the result of unblocking the contact.
*/
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 })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
module.exports = {
getClassInfo,
block,
getAbout,
getChat,
unblock,
getFormattedNumber,
getCountryCode,
getProfilePicUrl
}

View file

@ -0,0 +1,355 @@
const { MessageMedia } = require('whatsapp-web.js')
const { sessions } = require('../sessions')
const { sendErrorResponse } = require('../utils')
/**
* Adds participants to a group chat.
* @async
* @function
* @param {Object} req - The request object containing the chatId and contactIds in the body.
* @param {string} req.body.chatId - The ID of the group chat.
* @param {Array<string>} req.body.contactIds - An array of contact IDs to be added to the group.
* @param {Object} res - The response object.
* @returns {Object} Returns a JSON object containing a success flag and the updated participants list.
* @throws {Error} Throws an error if the chat is not a group chat.
*/
const addParticipants = async (req, res) => {
try {
const { chatId, contactIds } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
await chat.addParticipants(contactIds)
res.json({ success: true, participants: chat.participants })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Removes participants from a group chat
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Returns a JSON object with success flag and updated participants list
* @throws {Error} If chat is not a group
*/
const removeParticipants = async (req, res) => {
try {
const { chatId, contactIds } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
await chat.removeParticipants(contactIds)
res.json({ success: true, participants: chat.participants })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Promotes participants in a group chat to admin
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Returns a JSON object with success flag and updated participants list
* @throws {Error} If chat is not a group
*/
const promoteParticipants = async (req, res) => {
try {
const { chatId, contactIds } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
await chat.promoteParticipants(contactIds)
res.json({ success: true, participants: chat.participants })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Demotes admin participants in a group chat
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Returns a JSON object with success flag and updated participants list
* @throws {Error} If chat is not a group
*/
const demoteParticipants = async (req, res) => {
try {
const { chatId, contactIds } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
await chat.demoteParticipants(contactIds)
res.json({ success: true, participants: chat.participants })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Gets the invite code for a group chat
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Returns a JSON object with success flag and invite code
* @throws {Error} If chat is not a group
*/
const getInviteCode = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const inviteCode = await chat.getInviteCode()
res.json({ success: true, inviteCode })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Sets the subject of a group chat
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Returns a JSON object with success flag and updated chat object
* @throws {Error} If chat is not a group
*/
const setSubject = async (req, res) => {
try {
const { chatId, subject } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const success = await chat.setSubject(subject)
res.json({ success, chat })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Sets the description of a group chat
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Returns a JSON object with success flag and updated chat object
* @throws {Error} If chat is not a group
*/
const setDescription = async (req, res) => {
try {
const { chatId, description } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const success = await chat.setDescription(description)
res.json({ success, chat })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Leaves a group chat
*
* @async
* @function
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Returns a JSON object with success flag and outcome of leaving the chat
* @throws {Error} If chat is not a group
*/
const leave = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const outcome = await chat.leave()
res.json({ success: true, outcome })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Retrieves information about a chat based on the provided chatId
*
* @async
* @function getClassInfo
* @param {object} req - The request object
* @param {object} res - The response object
* @param {string} req.body.chatId - The chatId of the chat to retrieve information about
* @param {string} req.params.sessionId - The sessionId of the client making the request
* @throws {Error} The chat is not a group.
* @returns {Promise<void>} - A JSON response with success true and chat object containing chat information
*/
const getClassInfo = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
res.json({ success: true, chat })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Revokes the invite link for a group chat based on the provided chatId
*
* @async
* @function revokeInvite
* @param {object} req - The request object
* @param {object} res - The response object
* @param {string} req.body.chatId - The chatId of the group chat to revoke the invite for
* @param {string} req.params.sessionId - The sessionId of the client making the request
* @throws {Error} The chat is not a group.
* @returns {Promise<void>} - A JSON response with success true and the new invite code for the group chat
*/
const revokeInvite = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const newInviteCode = await chat.revokeInvite()
res.json({ success: true, newInviteCode })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Sets admins-only status of a group chat's info or messages.
*
* @async
* @function setInfoAdminsOnly
* @param {Object} req - Request object.
* @param {Object} res - Response object.
* @param {string} req.params.sessionId - ID of the user's session.
* @param {Object} req.body - Request body.
* @param {string} req.body.chatId - ID of the group chat.
* @param {boolean} req.body.adminsOnly - Desired admins-only status.
* @returns {Promise<void>} Promise representing the success or failure of the operation.
* @throws {Error} If the chat is not a group.
*/
const setInfoAdminsOnly = async (req, res) => {
try {
const { chatId, adminsOnly } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const result = await chat.setInfoAdminsOnly(adminsOnly)
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Sets admins-only status of a group chat's messages.
*
* @async
* @function setMessagesAdminsOnly
* @param {Object} req - Request object.
* @param {Object} res - Response object.
* @param {string} req.params.sessionId - ID of the user's session.
* @param {Object} req.body - Request body.
* @param {string} req.body.chatId - ID of the group chat.
* @param {boolean} req.body.adminsOnly - Desired admins-only status.
* @returns {Promise<void>} Promise representing the success or failure of the operation.
* @throws {Error} If the chat is not a group.
*/
const setMessagesAdminsOnly = async (req, res) => {
try {
const { chatId, adminsOnly } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const result = await chat.setMessagesAdminsOnly(adminsOnly)
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Set the group Picture
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {Object} req.body.pictureMimetype - The mimetype of the image.
* @param {Object} req.body.pictureData - The new group picture in base64 format.
* @param {Object} req.body.chatId - ID of the group chat.
* @param {string} req.params.sessionId - The ID of the session for the user.
* @returns {Object} Returns a JSON object with a success status and the result of the function.
* @throws {Error} If there is an issue setting the group picture, an error will be thrown.
*/
const setPicture = async (req, res) => {
try {
const { pictureMimetype, pictureData, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const media = new MessageMedia(pictureMimetype, pictureData)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const result = await chat.setPicture(media)
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Delete the group Picture
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {Object} req.body.chatId - ID of the group chat.
* @param {string} req.params.sessionId - The ID of the session for the user.
* @returns {Object} Returns a JSON object with a success status and the result of the function.
* @throws {Error} If there is an issue setting the group picture, an error will be thrown.
*/
const deletePicture = async (req, res) => {
try {
const { chatId } = req.body
const client = sessions.get(req.params.sessionId)
const chat = await client.getChatById(chatId)
if (!chat.isGroup) { throw new Error('The chat is not a group') }
const result = await chat.deletePicture()
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
module.exports = {
getClassInfo,
addParticipants,
demoteParticipants,
getInviteCode,
leave,
promoteParticipants,
removeParticipants,
revokeInvite,
setDescription,
setInfoAdminsOnly,
setMessagesAdminsOnly,
setSubject,
setPicture,
deletePicture
}

View file

@ -0,0 +1,55 @@
const fsp = require('fs').promises
const qrcode = require('qrcode-terminal')
const { sessionFolderPath } = require('../config')
const { sendErrorResponse } = require('../utils')
/**
* Responds to ping request with 'pong'
*
* @function ping
* @async
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<void>} - Promise that resolves once response is sent
* @throws {Object} - Throws error if response fails
*/
const ping = async (req, res) => {
/*
#swagger.tags = ['Various']
*/
try {
res.json({ success: true, message: 'pong' })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Example local callback function that generates a QR code and writes a log file
*
* @function localCallbackExample
* @async
* @param {Object} req - Express request object containing a body object with dataType and data
* @param {string} req.body.dataType - Type of data (in this case, 'qr')
* @param {Object} req.body.data - Data to generate a QR code from
* @param {Object} res - Express response object
* @returns {Promise<void>} - Promise that resolves once response is sent
* @throws {Object} - Throws error if response fails
*/
const localCallbackExample = async (req, res) => {
/*
#swagger.tags = ['Various']
*/
try {
const { dataType, data } = req.body
if (dataType === 'qr') { qrcode.generate(data.qr, { small: true }) }
await fsp.writeFile(`${sessionFolderPath}/message_log.txt`, `${JSON.stringify(req.body)}\r\n`, { flag: 'a+' }, _ => _)
res.json({ success: true })
} catch (error) {
console.log(error)
await fsp.writeFile(`${sessionFolderPath}/message_log.txt`, `(ERROR) ${JSON.stringify(error)}\r\n`, { flag: 'a+' }, _ => _)
sendErrorResponse(res, 500, error.message)
}
}
module.exports = { ping, localCallbackExample }

View file

@ -0,0 +1,376 @@
const { sessions } = require('../sessions')
const { sendErrorResponse } = require('../utils')
/**
* Get message by its ID from a given chat using the provided client.
* @async
* @function
* @param {object} client - The chat client.
* @param {string} messageId - The ID of the message to get.
* @param {string} chatId - The ID of the chat to search in.
* @returns {Promise<object>} - A Promise that resolves with the message object that matches the provided ID, or undefined if no such message exists.
* @throws {Error} - Throws an error if the provided client, message ID or chat ID is invalid.
*/
const _getMessageById = async (client, messageId, chatId) => {
const chat = await client.getChatById(chatId)
const messages = await chat.fetchMessages({ limit: 100 })
const message = messages.find((message) => { return message.id.id === messageId })
return message
}
/**
* Gets information about a message's class.
* @async
* @function
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {string} req.body.messageId - The message ID.
* @param {string} req.body.chatId - The chat ID.
* @returns {Promise<void>} - A Promise that resolves with no value when the function completes.
*/
const getClassInfo = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
res.json({ success: true, message })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Deletes a message.
* @async
* @function
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {string} req.body.messageId - The message ID.
* @param {string} req.body.chatId - The chat ID.
* @param {boolean} req.body.everyone - Whether to delete the message for everyone or just the sender.
* @returns {Promise<void>} - A Promise that resolves with no value when the function completes.
*/
const deleteMessage = async (req, res) => {
try {
const { messageId, chatId, everyone } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const result = await message.delete(everyone)
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Downloads media from a message.
* @async
* @function
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {string} req.body.messageId - The message ID.
* @param {string} req.body.chatId - The chat ID.
* @param {boolean} req.body.everyone - Whether to download the media for everyone or just the sender.
* @returns {Promise<void>} - A Promise that resolves with no value when the function completes.
*/
const downloadMedia = async (req, res) => {
try {
const { messageId, chatId, everyone } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const messageMedia = await message.downloadMedia(everyone)
res.json({ success: true, messageMedia })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Forwards a message to a destination chat.
* @async
* @function forward
* @param {Object} req - The request object received by the server.
* @param {Object} req.body - The body of the request object.
* @param {string} req.body.messageId - The ID of the message to forward.
* @param {string} req.body.chatId - The ID of the chat that contains the message to forward.
* @param {string} req.body.destinationChatId - The ID of the chat to forward the message to.
* @param {string} req.params.sessionId - The ID of the session to use the Telegram API with.
* @param {Object} res - The response object to be sent back to the client.
* @returns {Object} - The response object with a JSON body containing the result of the forward operation.
* @throws Will throw an error if the message is not found or if there is an error during the forward operation.
*/
const forward = async (req, res) => {
try {
const { messageId, chatId, destinationChatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const result = await message.forward(destinationChatId)
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Gets information about a message.
* @async
* @function getInfo
* @param {Object} req - The request object received by the server.
* @param {Object} req.body - The body of the request object.
* @param {string} req.body.messageId - The ID of the message to get information about.
* @param {string} req.body.chatId - The ID of the chat that contains the message to get information about.
* @param {string} req.params.sessionId - The ID of the session to use the Telegram API with.
* @param {Object} res - The response object to be sent back to the client.
* @returns {Object} - The response object with a JSON body containing the information about the message.
* @throws Will throw an error if the message is not found or if there is an error during the get info operation.
*/
const getInfo = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const info = await message.getInfo()
res.json({ success: true, info })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Retrieves a list of contacts mentioned in a specific message
*
* @async
* @function
* @param {Object} req - The HTTP request object
* @param {Object} req.body - The request body
* @param {string} req.body.messageId - The ID of the message to retrieve mentions from
* @param {string} req.body.chatId - The ID of the chat where the message was sent
* @param {string} req.params.sessionId - The ID of the session for the client making the request
* @param {Object} res - The HTTP response object
* @returns {Promise<void>} - The JSON response with the list of contacts
* @throws {Error} - If there's an error retrieving the message or mentions
*/
const getMentions = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const contacts = await message.getMentions()
res.json({ success: true, contacts })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Retrieves the order information contained in a specific message
*
* @async
* @function
* @param {Object} req - The HTTP request object
* @param {Object} req.body - The request body
* @param {string} req.body.messageId - The ID of the message to retrieve the order from
* @param {string} req.body.chatId - The ID of the chat where the message was sent
* @param {string} req.params.sessionId - The ID of the session for the client making the request
* @param {Object} res - The HTTP response object
* @returns {Promise<void>} - The JSON response with the order information
* @throws {Error} - If there's an error retrieving the message or order information
*/
const getOrder = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const order = await message.getOrder()
res.json({ success: true, order })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Retrieves the payment information from a specific message identified by its ID.
*
* @async
* @function getPayment
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID associated with the client making the request.
* @param {Object} req.body - The message ID and chat ID associated with the message to retrieve payment information from.
* @param {string} req.body.messageId - The ID of the message to retrieve payment information from.
* @param {string} req.body.chatId - The ID of the chat the message is associated with.
* @returns {Object} An object containing a success status and the payment information for the specified message.
* @throws {Object} If the specified message is not found or if an error occurs during the retrieval process.
*/
const getPayment = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const payment = await message.getPayment()
res.json({ success: true, payment })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Retrieves the quoted message information from a specific message identified by its ID.
*
* @async
* @function getQuotedMessage
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID associated with the client making the request.
* @param {Object} req.body - The message ID and chat ID associated with the message to retrieve quoted message information from.
* @param {string} req.body.messageId - The ID of the message to retrieve quoted message information from.
* @param {string} req.body.chatId - The ID of the chat the message is associated with.
* @returns {Object} An object containing a success status and the quoted message information for the specified message.
* @throws {Object} If the specified message is not found or if an error occurs during the retrieval process.
*/
const getQuotedMessage = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const quotedMessage = await message.getQuotedMessage()
res.json({ success: true, quotedMessage })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* React to a specific message in a chat
*
* @async
* @function react
* @param {Object} req - The HTTP request object containing the request parameters and body.
* @param {Object} res - The HTTP response object to send the result.
* @param {string} req.params.sessionId - The ID of the session to use.
* @param {Object} req.body - The body of the request.
* @param {string} req.body.messageId - The ID of the message to react to.
* @param {string} req.body.chatId - The ID of the chat the message is in.
* @param {string} req.body.reaction - The reaction to add to the message.
* @returns {Object} The HTTP response containing the result of the operation.
* @throws {Error} If there was an error during the operation.
*/
const react = async (req, res) => {
try {
const { messageId, chatId, reaction } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const result = await message.react(reaction)
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* Reply to a specific message in a chat
*
* @async
* @function reply
* @param {Object} req - The HTTP request object containing the request parameters and body.
* @param {Object} res - The HTTP response object to send the result.
* @param {string} req.params.sessionId - The ID of the session to use.
* @param {string} req.body.messageId - The ID of the message to reply to.
* @param {string} req.body.chatId - The ID of the chat the message is in.
* @param {string} req.body.content - The content of the message to send.
* @param {string} req.body.destinationChatId - The ID of the chat to send the reply to.
* @param {Object} req.body.options - Additional options for sending the message.
* @returns {Object} The HTTP response containing the result of the operation.
* @throws {Error} If there was an error during the operation.
*/
const reply = async (req, res) => {
try {
const { messageId, chatId, content, destinationChatId, options } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const repliedMessage = await message.reply(content, destinationChatId, options)
res.json({ success: true, repliedMessage })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* @function star
* @async
* @description Stars a message by message ID and chat ID.
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {string} req.body.messageId - The message ID.
* @param {string} req.body.chatId - The chat ID.
* @returns {Promise} A Promise that resolves with the result of the message.star() call.
* @throws {Error} If message is not found, it throws an error with the message "Message not Found".
*/
const star = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const result = await message.star()
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
/**
* @function unstar
* @async
* @description Unstars a message by message ID and chat ID.
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {string} req.params.sessionId - The session ID.
* @param {string} req.body.messageId - The message ID.
* @param {string} req.body.chatId - The chat ID.
* @returns {Promise} A Promise that resolves with the result of the message.unstar() call.
* @throws {Error} If message is not found, it throws an error with the message "Message not Found".
*/
const unstar = async (req, res) => {
try {
const { messageId, chatId } = req.body
const client = sessions.get(req.params.sessionId)
const message = await _getMessageById(client, messageId, chatId)
if (!message) { throw new Error('Message not Found') }
const result = await message.unstar()
res.json({ success: true, result })
} catch (error) {
sendErrorResponse(res, 500, error.message)
}
}
module.exports = {
getClassInfo,
deleteMessage,
downloadMedia,
forward,
getInfo,
getMentions,
getOrder,
getPayment,
getQuotedMessage,
react,
reply,
star,
unstar
}

View file

@ -0,0 +1,374 @@
const qr = require('qr-image')
const { setupSession, deleteSession, reloadSession, validateSession, flushSessions, sessions } = require('../sessions')
const { sendErrorResponse, waitForNestedObject } = require('../utils')
/**
* Starts a session for the given session ID.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID to start.
* @returns {Promise<void>}
* @throws {Error} If there was an error starting the session.
*/
const startSession = async (req, res) => {
// #swagger.summary = 'Start new session'
// #swagger.description = 'Starts a session for the given session ID.'
try {
const sessionId = req.params.sessionId
const setupSessionReturn = setupSession(sessionId)
if (!setupSessionReturn.success) {
/* #swagger.responses[422] = {
description: "Unprocessable Entity.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
sendErrorResponse(res, 422, setupSessionReturn.message)
return
}
/* #swagger.responses[200] = {
description: "Status of the initiated session.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/StartSessionResponse" }
}
}
}
*/
// wait until the client is created
await waitForNestedObject(setupSessionReturn.client, 'pupPage')
res.json({ success: true, message: setupSessionReturn.message })
} catch (error) {
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
console.log('startSession ERROR', error)
sendErrorResponse(res, 500, error.message)
}
}
/**
* Status of the session with the given session ID.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID to start.
* @returns {Promise<void>}
* @throws {Error} If there was an error getting status of the session.
*/
const statusSession = async (req, res) => {
// #swagger.summary = 'Get session status'
// #swagger.description = 'Status of the session with the given session ID.'
try {
const sessionId = req.params.sessionId
const sessionData = await validateSession(sessionId)
/* #swagger.responses[200] = {
description: "Status of the session.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/StatusSessionResponse" }
}
}
}
*/
res.json(sessionData)
} catch (error) {
console.log('statusSession ERROR', error)
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
sendErrorResponse(res, 500, error.message)
}
}
/**
* QR code of the session with the given session ID.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID to start.
* @returns {Promise<void>}
* @throws {Error} If there was an error getting status of the session.
*/
const sessionQrCode = async (req, res) => {
// #swagger.summary = 'Get session QR code'
// #swagger.description = 'QR code of the session with the given session ID.'
try {
const sessionId = req.params.sessionId
const session = sessions.get(sessionId)
if (!session) {
return res.json({ success: false, message: 'session_not_found' })
}
if (session.qr) {
return res.json({ success: true, qr: session.qr })
}
return res.json({ success: false, message: 'qr code not ready or already scanned' })
} catch (error) {
console.log('sessionQrCode ERROR', error)
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
sendErrorResponse(res, 500, error.message)
}
}
/**
* QR code as image of the session with the given session ID.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID to start.
* @returns {Promise<void>}
* @throws {Error} If there was an error getting status of the session.
*/
const sessionQrCodeImage = async (req, res) => {
// #swagger.summary = 'Get session QR code as image'
// #swagger.description = 'QR code as image of the session with the given session ID.'
try {
const sessionId = req.params.sessionId
const session = sessions.get(sessionId)
if (!session) {
return res.json({ success: false, message: 'session_not_found' })
}
if (session.qr) {
const qrImage = qr.image(session.qr)
/* #swagger.responses[200] = {
description: "QR image.",
content: {
"image/png": {}
}
}
*/
res.writeHead(200, {
'Content-Type': 'image/png'
})
return qrImage.pipe(res)
}
return res.json({ success: false, message: 'qr code not ready or already scanned' })
} catch (error) {
console.log('sessionQrCodeImage ERROR', error)
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
sendErrorResponse(res, 500, error.message)
}
}
/**
* Restarts the session with the given session ID.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID to terminate.
* @returns {Promise<void>}
* @throws {Error} If there was an error terminating the session.
*/
const restartSession = async (req, res) => {
// #swagger.summary = 'Restart session'
// #swagger.description = 'Restarts the session with the given session ID.'
try {
const sessionId = req.params.sessionId
const validation = await validateSession(sessionId)
if (validation.message === 'session_not_found') {
return res.json(validation)
}
await reloadSession(sessionId)
/* #swagger.responses[200] = {
description: "Sessions restarted.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/RestartSessionResponse" }
}
}
}
*/
res.json({ success: true, message: 'Restarted successfully' })
} catch (error) {
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
console.log('restartSession ERROR', error)
sendErrorResponse(res, 500, error.message)
}
}
/**
* Terminates the session with the given session ID.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @param {string} req.params.sessionId - The session ID to terminate.
* @returns {Promise<void>}
* @throws {Error} If there was an error terminating the session.
*/
const terminateSession = async (req, res) => {
// #swagger.summary = 'Terminate session'
// #swagger.description = 'Terminates the session with the given session ID.'
try {
const sessionId = req.params.sessionId
const validation = await validateSession(sessionId)
if (validation.message === 'session_not_found') {
return res.json(validation)
}
await deleteSession(sessionId, validation)
/* #swagger.responses[200] = {
description: "Sessions terminated.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/TerminateSessionResponse" }
}
}
}
*/
res.json({ success: true, message: 'Logged out successfully' })
} catch (error) {
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
console.log('terminateSession ERROR', error)
sendErrorResponse(res, 500, error.message)
}
}
/**
* Terminates all inactive sessions.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @returns {Promise<void>}
* @throws {Error} If there was an error terminating the sessions.
*/
const terminateInactiveSessions = async (req, res) => {
// #swagger.summary = 'Terminate inactive sessions'
// #swagger.description = 'Terminates all inactive sessions.'
try {
await flushSessions(true)
/* #swagger.responses[200] = {
description: "Sessions terminated.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/TerminateSessionsResponse" }
}
}
}
*/
res.json({ success: true, message: 'Flush completed successfully' })
} catch (error) {
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
console.log('terminateInactiveSessions ERROR', error)
sendErrorResponse(res, 500, error.message)
}
}
/**
* Terminates all sessions.
*
* @function
* @async
* @param {Object} req - The HTTP request object.
* @param {Object} res - The HTTP response object.
* @returns {Promise<void>}
* @throws {Error} If there was an error terminating the sessions.
*/
const terminateAllSessions = async (req, res) => {
// #swagger.summary = 'Terminate all sessions'
// #swagger.description = 'Terminates all sessions.'
try {
await flushSessions(false)
/* #swagger.responses[200] = {
description: "Sessions terminated.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/TerminateSessionsResponse" }
}
}
}
*/
res.json({ success: true, message: 'Flush completed successfully' })
} catch (error) {
/* #swagger.responses[500] = {
description: "Server Failure.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
console.log('terminateAllSessions ERROR', error)
sendErrorResponse(res, 500, error.message)
}
}
module.exports = {
startSession,
statusSession,
sessionQrCode,
sessionQrCodeImage,
restartSession,
terminateSession,
terminateInactiveSessions,
terminateAllSessions
}

188
src/middleware.js Normal file
View file

@ -0,0 +1,188 @@
const { globalApiKey, rateLimitMax, rateLimitWindowMs } = require('./config')
const { sendErrorResponse } = require('./utils')
const { validateSession } = require('./sessions')
const rateLimiting = require('express-rate-limit')
const apikey = async (req, res, next) => {
/*
#swagger.security = [{
"apiKeyAuth": []
}]
*/
/* #swagger.responses[403] = {
description: "Forbidden.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ForbiddenResponse" }
}
}
}
*/
if (globalApiKey) {
const apiKey = req.headers['x-api-key']
if (!apiKey || apiKey !== globalApiKey) {
return sendErrorResponse(res, 403, 'Invalid API key')
}
}
next()
}
const sessionNameValidation = async (req, res, next) => {
/*
#swagger.parameters['sessionId'] = {
in: 'path',
description: 'Unique identifier for the session (alphanumeric and - allowed)',
required: true,
type: 'string',
example: 'f8377d8d-a589-4242-9ba6-9486a04ef80c'
}
*/
if ((!/^[\w-]+$/.test(req.params.sessionId))) {
/* #swagger.responses[422] = {
description: "Unprocessable Entity.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/ErrorResponse" }
}
}
}
*/
return sendErrorResponse(res, 422, 'Session should be alphanumerical or -')
}
next()
}
const sessionValidation = async (req, res, next) => {
const validation = await validateSession(req.params.sessionId)
if (validation.success !== true) {
/* #swagger.responses[404] = {
description: "Not Found.",
content: {
"application/json": {
schema: { "$ref": "#/definitions/NotFoundResponse" }
}
}
}
*/
return sendErrorResponse(res, 404, validation.message)
}
next()
}
const rateLimiter = rateLimiting({
limit: rateLimitMax,
windowMs: rateLimitWindowMs,
message: "You can't make any more requests at the moment. Try again later"
})
const sessionSwagger = async (req, res, next) => {
/*
#swagger.tags = ['Session']
*/
next()
}
const clientSwagger = async (req, res, next) => {
/*
#swagger.tags = ['Client']
*/
next()
}
const contactSwagger = async (req, res, next) => {
/*
#swagger.tags = ['Contact']
#swagger.requestBody = {
required: true,
schema: {
type: 'object',
properties: {
contactId: {
type: 'string',
description: 'Unique whatsApp identifier for the contact',
example: '6281288888888@c.us'
}
}
}
}
*/
next()
}
const messageSwagger = async (req, res, next) => {
/*
#swagger.tags = ['Message']
#swagger.requestBody = {
required: true,
schema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'The Chat id which contains the message',
example: '6281288888888@c.us'
},
messageId: {
type: 'string',
description: 'Unique whatsApp identifier for the message',
example: 'ABCDEF999999999'
}
}
}
}
*/
next()
}
const chatSwagger = async (req, res, next) => {
/*
#swagger.tags = ['Chat']
#swagger.requestBody = {
required: true,
schema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Unique whatsApp identifier for the given Chat (either group or personnal)',
example: '6281288888888@c.us'
}
}
}
}
*/
next()
}
const groupChatSwagger = async (req, res, next) => {
/*
#swagger.tags = ['Group Chat']
#swagger.requestBody = {
required: true,
schema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Unique whatsApp identifier for the given Chat (either group or personnal)',
example: '6281288888888@c.us'
}
}
}
}
*/
next()
}
module.exports = {
sessionValidation,
apikey,
sessionNameValidation,
sessionSwagger,
clientSwagger,
contactSwagger,
messageSwagger,
chatSwagger,
groupChatSwagger,
rateLimiter
}

191
src/routes.js Normal file
View file

@ -0,0 +1,191 @@
const express = require('express')
const routes = express.Router()
const swaggerUi = require('swagger-ui-express')
const swaggerDocument = require('../swagger.json')
const { enableLocalCallbackExample, enableSwaggerEndpoint } = require('./config')
const middleware = require('./middleware')
const healthController = require('./controllers/healthController')
const sessionController = require('./controllers/sessionController')
const clientController = require('./controllers/clientController')
const chatController = require('./controllers/chatController')
const groupChatController = require('./controllers/groupChatController')
const messageController = require('./controllers/messageController')
const contactController = require('./controllers/contactController')
/**
* ================
* HEALTH ENDPOINTS
* ================
*/
// API endpoint to check if server is alive
routes.get('/ping', healthController.ping)
// API basic callback
if (enableLocalCallbackExample) {
routes.post('/localCallbackExample', [middleware.apikey, middleware.rateLimiter], healthController.localCallbackExample)
}
/**
* ================
* SESSION ENDPOINTS
* ================
*/
const sessionRouter = express.Router()
sessionRouter.use(middleware.apikey)
sessionRouter.use(middleware.sessionSwagger)
routes.use('/session', sessionRouter)
sessionRouter.get('/start/:sessionId', middleware.sessionNameValidation, sessionController.startSession)
sessionRouter.get('/status/:sessionId', middleware.sessionNameValidation, sessionController.statusSession)
sessionRouter.get('/qr/:sessionId', middleware.sessionNameValidation, sessionController.sessionQrCode)
sessionRouter.get('/qr/:sessionId/image', middleware.sessionNameValidation, sessionController.sessionQrCodeImage)
sessionRouter.get('/restart/:sessionId', middleware.sessionNameValidation, sessionController.restartSession)
sessionRouter.get('/terminate/:sessionId', middleware.sessionNameValidation, sessionController.terminateSession)
sessionRouter.get('/terminateInactive', sessionController.terminateInactiveSessions)
sessionRouter.get('/terminateAll', sessionController.terminateAllSessions)
/**
* ================
* CLIENT ENDPOINTS
* ================
*/
const clientRouter = express.Router()
clientRouter.use(middleware.apikey)
sessionRouter.use(middleware.clientSwagger)
routes.use('/client', clientRouter)
clientRouter.get('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getClassInfo)
clientRouter.post('/acceptInvite/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.acceptInvite)
clientRouter.post('/archiveChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.archiveChat)
clientRouter.post('/createGroup/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.createGroup)
clientRouter.post('/getBlockedContacts/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getBlockedContacts)
clientRouter.post('/getChatById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatById)
clientRouter.post('/getChatLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatLabels)
clientRouter.get('/getChats/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChats)
clientRouter.post('/getChatsByLabelId/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatsByLabelId)
clientRouter.post('/getCommonGroups/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getCommonGroups)
clientRouter.post('/getContactById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getContactById)
clientRouter.get('/getContacts/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getContacts)
clientRouter.post('/getInviteInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getInviteInfo)
clientRouter.post('/getLabelById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getLabelById)
clientRouter.post('/getLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getLabels)
clientRouter.post('/addOrRemoveLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.addOrRemoveLabels)
clientRouter.post('/getNumberId/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getNumberId)
clientRouter.post('/isRegisteredUser/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.isRegisteredUser)
clientRouter.post('/getProfilePicUrl/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getProfilePictureUrl)
clientRouter.get('/getState/:sessionId', [middleware.sessionNameValidation], clientController.getState)
clientRouter.post('/markChatUnread/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.markChatUnread)
clientRouter.post('/muteChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.muteChat)
clientRouter.post('/pinChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.pinChat)
clientRouter.post('/searchMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.searchMessages)
clientRouter.post('/sendMessage/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendMessage)
clientRouter.post('/sendPresenceAvailable/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendPresenceAvailable)
clientRouter.post('/sendPresenceUnavailable/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendPresenceUnavailable)
clientRouter.post('/sendSeen/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendSeen)
clientRouter.post('/setDisplayName/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setDisplayName)
clientRouter.post('/setProfilePicture/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setProfilePicture)
clientRouter.post('/setStatus/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setStatus)
clientRouter.post('/unarchiveChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unarchiveChat)
clientRouter.post('/unmuteChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unmuteChat)
clientRouter.post('/unpinChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unpinChat)
clientRouter.get('/getWWebVersion/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getWWebVersion)
/**
* ================
* CHAT ENDPOINTS
* ================
*/
const chatRouter = express.Router()
chatRouter.use(middleware.apikey)
sessionRouter.use(middleware.chatSwagger)
routes.use('/chat', chatRouter)
chatRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.getClassInfo)
chatRouter.post('/clearMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.clearMessages)
chatRouter.post('/clearState/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.clearState)
chatRouter.post('/delete/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.deleteChat)
chatRouter.post('/fetchMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.fetchMessages)
chatRouter.post('/getContact/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.getContact)
chatRouter.post('/sendStateRecording/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.sendStateRecording)
chatRouter.post('/sendStateTyping/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.sendStateTyping)
/**
* ================
* GROUP CHAT ENDPOINTS
* ================
*/
const groupChatRouter = express.Router()
groupChatRouter.use(middleware.apikey)
sessionRouter.use(middleware.groupChatSwagger)
routes.use('/groupChat', groupChatRouter)
groupChatRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.getClassInfo)
groupChatRouter.post('/addParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.addParticipants)
groupChatRouter.post('/demoteParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.demoteParticipants)
groupChatRouter.post('/getInviteCode/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.getInviteCode)
groupChatRouter.post('/leave/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.leave)
groupChatRouter.post('/promoteParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.promoteParticipants)
groupChatRouter.post('/removeParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.removeParticipants)
groupChatRouter.post('/revokeInvite/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.revokeInvite)
groupChatRouter.post('/setDescription/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setDescription)
groupChatRouter.post('/setInfoAdminsOnly/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setInfoAdminsOnly)
groupChatRouter.post('/setMessagesAdminsOnly/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setMessagesAdminsOnly)
groupChatRouter.post('/setSubject/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setSubject)
groupChatRouter.post('/setPicture/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setPicture)
groupChatRouter.post('/deletePicture/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.deletePicture)
/**
* ================
* MESSAGE ENDPOINTS
* ================
*/
const messageRouter = express.Router()
messageRouter.use(middleware.apikey)
sessionRouter.use(middleware.messageSwagger)
routes.use('/message', messageRouter)
messageRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getClassInfo)
messageRouter.post('/delete/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.deleteMessage)
messageRouter.post('/downloadMedia/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.downloadMedia)
messageRouter.post('/forward/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.forward)
messageRouter.post('/getInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getInfo)
messageRouter.post('/getMentions/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getMentions)
messageRouter.post('/getOrder/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getOrder)
messageRouter.post('/getPayment/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getPayment)
messageRouter.post('/getQuotedMessage/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getQuotedMessage)
messageRouter.post('/react/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.react)
messageRouter.post('/reply/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.reply)
messageRouter.post('/star/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.star)
messageRouter.post('/unstar/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.unstar)
/**
* ================
* MESSAGE ENDPOINTS
* ================
*/
const contactRouter = express.Router()
contactRouter.use(middleware.apikey)
sessionRouter.use(middleware.contactSwagger)
routes.use('/contact', contactRouter)
contactRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getClassInfo)
contactRouter.post('/block/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.block)
contactRouter.post('/getAbout/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getAbout)
contactRouter.post('/getChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getChat)
contactRouter.post('/unblock/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.unblock)
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)
/**
* ================
* SWAGGER ENDPOINTS
* ================
*/
if (enableSwaggerEndpoint) {
routes.use('/api-docs', swaggerUi.serve)
routes.get('/api-docs', swaggerUi.setup(swaggerDocument) /* #swagger.ignore = true */)
}
module.exports = { routes }

474
src/sessions.js Normal file
View file

@ -0,0 +1,474 @@
const { Client, LocalAuth } = require('whatsapp-web.js')
const fs = require('fs')
const path = require('path')
const sessions = new Map()
const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless } = require('./config')
const { triggerWebhook, waitForNestedObject, checkIfEventisEnabled } = require('./utils')
// Function to validate if the session is ready
const validateSession = async (sessionId) => {
try {
const returnData = { success: false, state: null, message: '' }
// Session not Connected 😢
if (!sessions.has(sessionId) || !sessions.get(sessionId)) {
returnData.message = 'session_not_found'
return returnData
}
const client = sessions.get(sessionId)
// wait until the client is created
await waitForNestedObject(client, 'pupPage')
.catch((err) => { return { success: false, state: null, message: err.message } })
// Wait for client.pupPage to be evaluable
let maxRetry = 0
while (true) {
try {
if (client.pupPage.isClosed()) {
return { success: false, state: null, message: 'browser tab closed' }
}
await Promise.race([
client.pupPage.evaluate('1'),
new Promise(resolve => setTimeout(resolve, 1000))
])
break
} catch (error) {
if (maxRetry === 2) {
return { success: false, state: null, message: 'session closed' }
}
maxRetry++
}
}
const state = await client.getState()
returnData.state = state
if (state !== 'CONNECTED') {
returnData.message = 'session_not_connected'
return returnData
}
// Session Connected 🎉
returnData.success = true
returnData.message = 'session_connected'
return returnData
} catch (error) {
console.log(error)
return { success: false, state: null, message: error.message }
}
}
// Function to handle client session restoration
const restoreSessions = () => {
try {
if (!fs.existsSync(sessionFolderPath)) {
fs.mkdirSync(sessionFolderPath) // Create the session directory if it doesn't exist
}
// Read the contents of the folder
fs.readdir(sessionFolderPath, (_, files) => {
// Iterate through the files in the parent folder
for (const file of files) {
// Use regular expression to extract the string from the folder name
const match = file.match(/^session-(.+)$/)
if (match) {
const sessionId = match[1]
console.log('existing session detected', sessionId)
setupSession(sessionId)
}
}
})
} catch (error) {
console.log(error)
console.error('Failed to restore sessions:', error)
}
}
// Setup Session
const setupSession = (sessionId) => {
try {
if (sessions.has(sessionId)) {
return { success: false, message: `Session already exists for: ${sessionId}`, client: sessions.get(sessionId) }
}
// Disable the delete folder from the logout function (will be handled separately)
const localAuth = new LocalAuth({ clientId: sessionId, dataPath: sessionFolderPath })
delete localAuth.logout
localAuth.logout = () => { }
const clientOptions = {
puppeteer: {
executablePath: chromeBin,
headless,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--disable-dev-shm-usage']
},
authStrategy: localAuth
}
if (webVersion) {
clientOptions.webVersion = webVersion
switch (webVersionCacheType.toLowerCase()) {
case 'local':
clientOptions.webVersionCache = {
type: 'local'
}
break
case 'remote':
clientOptions.webVersionCache = {
type: 'remote',
remotePath: 'https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/' + webVersion + '.html'
}
break
default:
clientOptions.webVersionCache = {
type: 'none'
}
}
}
const client = new Client(clientOptions)
client.initialize().catch(err => console.log('Initialize error:', err.message))
initializeEvents(client, sessionId)
// Save the session to the Map
sessions.set(sessionId, client)
return { success: true, message: 'Session initiated successfully', client }
} catch (error) {
return { success: false, message: error.message, client: null }
}
}
const initializeEvents = (client, sessionId) => {
// check if the session webhook is overridden
const sessionWebhook = process.env[sessionId.toUpperCase() + '_WEBHOOK_URL'] || baseWebhookURL
if (recoverSessions) {
waitForNestedObject(client, 'pupPage').then(() => {
const restartSession = async (sessionId) => {
sessions.delete(sessionId)
await client.destroy().catch(e => { })
setupSession(sessionId)
}
client.pupPage.once('close', function () {
// emitted when the page closes
console.log(`Browser page closed for ${sessionId}. Restoring`)
restartSession(sessionId)
})
client.pupPage.once('error', function () {
// emitted when the page crashes
console.log(`Error occurred on browser page for ${sessionId}. Restoring`)
restartSession(sessionId)
})
}).catch(e => { })
}
checkIfEventisEnabled('auth_failure')
.then(_ => {
client.on('auth_failure', (msg) => {
triggerWebhook(sessionWebhook, sessionId, 'status', { msg })
})
})
checkIfEventisEnabled('authenticated')
.then(_ => {
client.on('authenticated', () => {
triggerWebhook(sessionWebhook, sessionId, 'authenticated')
})
})
checkIfEventisEnabled('call')
.then(_ => {
client.on('call', async (call) => {
triggerWebhook(sessionWebhook, sessionId, 'call', { call })
})
})
checkIfEventisEnabled('change_state')
.then(_ => {
client.on('change_state', state => {
triggerWebhook(sessionWebhook, sessionId, 'change_state', { state })
})
})
checkIfEventisEnabled('disconnected')
.then(_ => {
client.on('disconnected', (reason) => {
triggerWebhook(sessionWebhook, sessionId, 'disconnected', { reason })
})
})
checkIfEventisEnabled('group_join')
.then(_ => {
client.on('group_join', (notification) => {
triggerWebhook(sessionWebhook, sessionId, 'group_join', { notification })
})
})
checkIfEventisEnabled('group_leave')
.then(_ => {
client.on('group_leave', (notification) => {
triggerWebhook(sessionWebhook, sessionId, 'group_leave', { notification })
})
})
checkIfEventisEnabled('group_update')
.then(_ => {
client.on('group_update', (notification) => {
triggerWebhook(sessionWebhook, sessionId, 'group_update', { notification })
})
})
checkIfEventisEnabled('loading_screen')
.then(_ => {
client.on('loading_screen', (percent, message) => {
triggerWebhook(sessionWebhook, sessionId, 'loading_screen', { percent, message })
})
})
checkIfEventisEnabled('media_uploaded')
.then(_ => {
client.on('media_uploaded', (message) => {
triggerWebhook(sessionWebhook, sessionId, 'media_uploaded', { message })
})
})
checkIfEventisEnabled('message')
.then(_ => {
client.on('message', async (message) => {
triggerWebhook(sessionWebhook, sessionId, 'message', { message })
if (message.hasMedia && message._data?.size < maxAttachmentSize) {
// custom service event
checkIfEventisEnabled('media').then(_ => {
message.downloadMedia().then(messageMedia => {
triggerWebhook(sessionWebhook, sessionId, 'media', { messageMedia, message })
}).catch(e => {
console.log('Download media error:', e.message)
})
})
}
if (setMessagesAsSeen) {
const chat = await message.getChat()
chat.sendSeen()
}
})
})
checkIfEventisEnabled('message_ack')
.then(_ => {
client.on('message_ack', async (message, ack) => {
triggerWebhook(sessionWebhook, sessionId, 'message_ack', { message, ack })
if (setMessagesAsSeen) {
const chat = await message.getChat()
chat.sendSeen()
}
})
})
checkIfEventisEnabled('message_create')
.then(_ => {
client.on('message_create', async (message) => {
triggerWebhook(sessionWebhook, sessionId, 'message_create', { message })
if (setMessagesAsSeen) {
const chat = await message.getChat()
chat.sendSeen()
}
})
})
checkIfEventisEnabled('message_reaction')
.then(_ => {
client.on('message_reaction', (reaction) => {
triggerWebhook(sessionWebhook, sessionId, 'message_reaction', { reaction })
})
})
checkIfEventisEnabled('message_edit')
.then(_ => {
client.on('message_edit', (message, newBody, prevBody) => {
triggerWebhook(sessionWebhook, sessionId, 'message_edit', { message, newBody, prevBody })
})
})
checkIfEventisEnabled('message_ciphertext')
.then(_ => {
client.on('message_ciphertext', (message) => {
triggerWebhook(sessionWebhook, sessionId, 'message_ciphertext', { message })
})
})
checkIfEventisEnabled('message_revoke_everyone')
.then(_ => {
// eslint-disable-next-line camelcase
client.on('message_revoke_everyone', async (message) => {
// eslint-disable-next-line camelcase
triggerWebhook(sessionWebhook, sessionId, 'message_revoke_everyone', { message })
})
})
checkIfEventisEnabled('message_revoke_me')
.then(_ => {
client.on('message_revoke_me', async (message) => {
triggerWebhook(sessionWebhook, sessionId, 'message_revoke_me', { message })
})
})
client.on('qr', (qr) => {
// inject qr code into session
client.qr = qr
checkIfEventisEnabled('qr')
.then(_ => {
triggerWebhook(sessionWebhook, sessionId, 'qr', { qr })
})
})
checkIfEventisEnabled('ready')
.then(_ => {
client.on('ready', () => {
triggerWebhook(sessionWebhook, sessionId, 'ready')
})
})
checkIfEventisEnabled('contact_changed')
.then(_ => {
client.on('contact_changed', async (message, oldId, newId, isContact) => {
triggerWebhook(sessionWebhook, sessionId, 'contact_changed', { message, oldId, newId, isContact })
})
})
checkIfEventisEnabled('chat_removed')
.then(_ => {
client.on('chat_removed', async (chat) => {
triggerWebhook(sessionWebhook, sessionId, 'chat_removed', { chat })
})
})
checkIfEventisEnabled('chat_archived')
.then(_ => {
client.on('chat_archived', async (chat, currState, prevState) => {
triggerWebhook(sessionWebhook, sessionId, 'chat_archived', { chat, currState, prevState })
})
})
checkIfEventisEnabled('unread_count')
.then(_ => {
client.on('unread_count', async (chat) => {
triggerWebhook(sessionWebhook, sessionId, 'unread_count', { chat })
})
})
}
// Function to delete client session folder
const deleteSessionFolder = async (sessionId) => {
try {
const targetDirPath = path.join(sessionFolderPath, `session-${sessionId}`)
const resolvedTargetDirPath = await fs.promises.realpath(targetDirPath)
const resolvedSessionPath = await fs.promises.realpath(sessionFolderPath)
// Ensure the target directory path ends with a path separator
const safeSessionPath = `${resolvedSessionPath}${path.sep}`
// Validate the resolved target directory path is a subdirectory of the session folder path
if (!resolvedTargetDirPath.startsWith(safeSessionPath)) {
throw new Error('Invalid path: Directory traversal detected')
}
await fs.promises.rm(resolvedTargetDirPath, { recursive: true, force: true })
} catch (error) {
console.log('Folder deletion error', error)
throw error
}
}
// Function to reload client session without removing browser cache
const reloadSession = async (sessionId) => {
try {
const client = sessions.get(sessionId)
if (!client) {
return
}
client.pupPage?.removeAllListeners('close')
client.pupPage?.removeAllListeners('error')
try {
const pages = await client.pupBrowser.pages()
await Promise.all(pages.map((page) => page.close()))
await Promise.race([
client.pupBrowser.close(),
new Promise(resolve => setTimeout(resolve, 5000))
])
} catch (e) {
const childProcess = client.pupBrowser.process()
if (childProcess) {
childProcess.kill(9)
}
}
sessions.delete(sessionId)
setupSession(sessionId)
} catch (error) {
console.log(error)
throw error
}
}
const deleteSession = async (sessionId, validation) => {
try {
const client = sessions.get(sessionId)
if (!client) {
return
}
client.pupPage?.removeAllListeners('close')
client.pupPage?.removeAllListeners('error')
if (validation.success) {
// Client Connected, request logout
console.log(`Logging out session ${sessionId}`)
await client.logout()
} else if (validation.message === 'session_not_connected') {
// Client not Connected, request destroy
console.log(`Destroying session ${sessionId}`)
await client.destroy()
}
// Wait 10 secs for client.pupBrowser to be disconnected before deleting the folder
let maxDelay = 0
while (client.pupBrowser.isConnected() && (maxDelay < 10)) {
await new Promise(resolve => setTimeout(resolve, 1000))
maxDelay++
}
await deleteSessionFolder(sessionId)
sessions.delete(sessionId)
} catch (error) {
console.log(error)
throw error
}
}
// Function to handle session flush
const flushSessions = async (deleteOnlyInactive) => {
try {
// Read the contents of the sessions folder
const files = await fs.promises.readdir(sessionFolderPath)
// Iterate through the files in the parent folder
for (const file of files) {
// Use regular expression to extract the string from the folder name
const match = file.match(/^session-(.+)$/)
if (match) {
const sessionId = match[1]
const validation = await validateSession(sessionId)
if (!deleteOnlyInactive || !validation.success) {
await deleteSession(sessionId, validation)
}
}
}
} catch (error) {
console.log(error)
throw error
}
}
module.exports = {
sessions,
setupSession,
restoreSessions,
validateSession,
deleteSession,
reloadSession,
flushSessions
}

46
src/utils.js Normal file
View file

@ -0,0 +1,46 @@
const axios = require('axios')
const { globalApiKey, disabledCallbacks } = require('./config')
// Trigger webhook endpoint
const triggerWebhook = (webhookURL, sessionId, dataType, data) => {
axios.post(webhookURL, { dataType, data, sessionId }, { headers: { 'x-api-key': globalApiKey } })
.catch(error => console.error('Failed to send new message webhook:', sessionId, dataType, error.message, data || ''))
}
// Function to send a response with error status and message
const sendErrorResponse = (res, status, message) => {
res.status(status).json({ success: false, error: message })
}
// Function to wait for a specific item not to be null
const waitForNestedObject = (rootObj, nestedPath, maxWaitTime = 10000, interval = 100) => {
const start = Date.now()
return new Promise((resolve, reject) => {
const checkObject = () => {
const nestedObj = nestedPath.split('.').reduce((obj, key) => obj ? obj[key] : undefined, rootObj)
if (nestedObj) {
// Nested object exists, resolve the promise
resolve()
} else if (Date.now() - start > maxWaitTime) {
// Maximum wait time exceeded, reject the promise
console.log('Timed out waiting for nested object')
reject(new Error('Timeout waiting for nested object'))
} else {
// Nested object not yet created, continue waiting
setTimeout(checkObject, interval)
}
}
checkObject()
})
}
const checkIfEventisEnabled = (event) => {
return new Promise((resolve, reject) => { if (!disabledCallbacks.includes(event)) { resolve() } })
}
module.exports = {
triggerWebhook,
sendErrorResponse,
waitForNestedObject,
checkIfEventisEnabled
}