You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

488 lines
16 KiB

package whatsapp
import (
"bytes"
"io"
"mime/multipart"
"strconv"
"strings"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
pkgWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/whatsapp"
typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types"
typWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp/types"
)
func jwtPayload(c echo.Context) typAuth.AuthJWTClaimsPayload {
jwtToken := c.Get("user").(*jwt.Token)
jwtClaims := jwtToken.Claims.(*typAuth.AuthJWTClaims)
return jwtClaims.Data
}
func convertFileToBytes(file multipart.File) ([]byte, error) {
// Create Empty Buffer
buffer := bytes.NewBuffer(nil)
// Copy File Stream to Buffer
_, err := io.Copy(buffer, file)
if err != nil {
return bytes.NewBuffer(nil).Bytes(), err
}
return buffer.Bytes(), nil
}
// Login
// @Summary Generate QR Code for WhatsApp Multi-Device Login
// @Description Get QR Code for WhatsApp Multi-Device Login
// @Tags WhatsApp Authentication
// @Accept multipart/form-data
// @Produce json
// @Produce html
// @Param output formData string false "Change Output Format in HTML or JSON" Enums(html, json) default(html)
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/login [post]
func Login(c echo.Context) error {
var err error
jid := jwtPayload(c).JID
var reqLogin typWhatsApp.RequestLogin
reqLogin.Output = strings.TrimSpace(c.FormValue("output"))
if len(reqLogin.Output) == 0 {
reqLogin.Output = "html"
}
// Initialize WhatsApp Client
pkgWhatsApp.WhatsAppInitClient(nil, jid)
// Get WhatsApp QR Code Image
qrCodeImage, qrCodeTimeout, err := pkgWhatsApp.WhatsAppLogin(jid)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
// If Return is Not QR Code But Reconnected
// Then Return OK With Reconnected Status
if qrCodeImage == "WhatsApp Client is Reconnected" {
return router.ResponseSuccess(c, qrCodeImage)
}
var resLogin typWhatsApp.ResponseLogin
resLogin.QRCode = qrCodeImage
resLogin.Timeout = qrCodeTimeout
if reqLogin.Output == "html" {
htmlContent := `
<html>
<head>
<title>WhatsApp Multi-Device Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
</head>
<body>
<img src="` + resLogin.QRCode + `" />
<p>
<b>QR Code Scan</b>
<br/>
Timeout in ` + strconv.Itoa(resLogin.Timeout) + ` Second(s)
</p>
</body>
</html>`
return router.ResponseSuccessWithHTML(c, htmlContent)
}
return router.ResponseSuccessWithData(c, "Successfully Generated QR Code", resLogin)
}
// Logout
// @Summary Logout Device from WhatsApp Multi-Device
// @Description Make Device Logout from WhatsApp Multi-Device
// @Tags WhatsApp Authentication
// @Produce json
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/logout [post]
func Logout(c echo.Context) error {
var err error
jid := jwtPayload(c).JID
err = pkgWhatsApp.WhatsAppLogout(jid)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccess(c, "Successfully Logged Out")
}
// Registered
// @Summary Check If Phone Number is Registered in WhatsApp
// @Description Check Phone Number is Registered in WhatsApp
// @Tags WhatsApp Authentication
// @Produce json
// @Param msisdn query string true "Phone Number to Check"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/registered [get]
func Registered(c echo.Context) error {
jid := jwtPayload(c).JID
rjid := strings.TrimSpace(c.QueryParam("msisdn"))
if len(rjid) == 0 {
return router.ResponseInternalError(c, "Missing Param Value MSISDN")
}
rjid = pkgWhatsApp.WhatsAppDecomposeJID(rjid)
_, isJIDRegistered := pkgWhatsApp.WhatsAppCheckJID(jid, rjid)
if !isJIDRegistered {
return router.ResponseNotFound(c, "Phone Number is Not Registered")
}
return router.ResponseSuccess(c, "Phone Number is Registered")
}
// SendText
// @Summary Send Text Message
// @Description Send Text Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param message formData string true "Text Message"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/text [post]
func SendText(c echo.Context) error {
var err error
jid := jwtPayload(c).JID
var reqSendMessage typWhatsApp.RequestSendMessage
reqSendMessage.RJID = strings.TrimSpace(c.FormValue("msisdn"))
reqSendMessage.Message = strings.TrimSpace(c.FormValue("message"))
if len(reqSendMessage.RJID) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value MSISDN")
}
if len(reqSendMessage.Message) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value Message")
}
var resSendMessage typWhatsApp.ResponseSendMessage
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendText(jid, reqSendMessage.RJID, reqSendMessage.Message)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccessWithData(c, "Successfully Send Text Message", resSendMessage)
}
// SendLocation
// @Summary Send Location Message
// @Description Send Location Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param latitude formData number true "Location Latitude"
// @Param longitude formData number true "Location Longitude"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/location [post]
func SendLocation(c echo.Context) error {
var err error
jid := jwtPayload(c).JID
var reqSendLocation typWhatsApp.RequestSendLocation
reqSendLocation.RJID = strings.TrimSpace(c.FormValue("msisdn"))
reqSendLocation.Latitude, err = strconv.ParseFloat(strings.TrimSpace(c.FormValue("latitude")), 64)
if err != nil {
return router.ResponseInternalError(c, "Error While Decoding Latitude to Float64")
}
reqSendLocation.Longitude, err = strconv.ParseFloat(strings.TrimSpace(c.FormValue("longitude")), 64)
if err != nil {
return router.ResponseInternalError(c, "Error While Decoding Longitude to Float64")
}
if len(reqSendLocation.RJID) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value MSISDN")
}
var resSendMessage typWhatsApp.ResponseSendMessage
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendLocation(jid, reqSendLocation.RJID, reqSendLocation.Latitude, reqSendLocation.Longitude)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccessWithData(c, "Successfully Send Location Message", resSendMessage)
}
// SendContact
// @Summary Send Contact Message
// @Description Send Contact Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param name formData string true "Contact Name"
// @Param phone formData string true "Contact Phone"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/contact [post]
func SendContact(c echo.Context) error {
var err error
jid := jwtPayload(c).JID
var reqSendContact typWhatsApp.RequestSendContact
reqSendContact.RJID = strings.TrimSpace(c.FormValue("msisdn"))
reqSendContact.Name = strings.TrimSpace(c.FormValue("name"))
reqSendContact.Phone = strings.TrimSpace(c.FormValue("phone"))
if len(reqSendContact.RJID) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value MSISDN")
}
if len(reqSendContact.Name) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value Name")
}
if len(reqSendContact.Phone) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value Phone")
}
var resSendMessage typWhatsApp.ResponseSendMessage
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendContact(jid, reqSendContact.RJID, reqSendContact.Name, reqSendContact.Phone)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccessWithData(c, "Successfully Send Contact Message", resSendMessage)
}
// SendLink
// @Summary Send Link Message
// @Description Send Link Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param caption formData string false "Link Caption"
// @Param url formData string true "Link URL"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/link [post]
func SendLink(c echo.Context) error {
var err error
jid := jwtPayload(c).JID
var reqSendLink typWhatsApp.RequestSendLink
reqSendLink.RJID = strings.TrimSpace(c.FormValue("msisdn"))
reqSendLink.Caption = strings.TrimSpace(c.FormValue("caption"))
reqSendLink.URL = strings.TrimSpace(c.FormValue("url"))
if len(reqSendLink.RJID) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value MSISDN")
}
if len(reqSendLink.URL) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value URL")
}
var resSendMessage typWhatsApp.ResponseSendMessage
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendLink(jid, reqSendLink.RJID, reqSendLink.Caption, reqSendLink.URL)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccessWithData(c, "Successfully Send Link Message", resSendMessage)
}
// SendDocument
// @Summary Send Document Message
// @Description Send Document Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param document formData file true "Document File"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/document [post]
func SendDocument(c echo.Context) error {
return sendMedia(c, "document")
}
// SendImage
// @Summary Send Image Message
// @Description Send Image Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param caption formData string true "Caption Image Message"
// @Param image formData file true "Image File"
// @Param viewonce formData bool false "Is View Once" default(false)
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/image [post]
func SendImage(c echo.Context) error {
return sendMedia(c, "image")
}
// SendAudio
// @Summary Send Audio Message
// @Description Send Audio Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param audio formData file true "Audio File"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/audio [post]
func SendAudio(c echo.Context) error {
return sendMedia(c, "audio")
}
// SendVideo
// @Summary Send Video Message
// @Description Send Video Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param caption formData string true "Caption Video Message"
// @Param video formData file true "Video File"
// @Param viewonce formData bool false "Is View Once" default(false)
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/video [post]
func SendVideo(c echo.Context) error {
return sendMedia(c, "video")
}
// SendSticker
// @Summary Send Sticker Message
// @Description Send Sticker Message to Spesific Phone Number
// @Tags WhatsApp Message
// @Accept multipart/form-data
// @Produce json
// @Param msisdn formData string true "Destination Phone Number"
// @Param sticker formData file true "Sticker File"
// @Success 200
// @Security BearerAuth
// @Router /api/v1/whatsapp/send/sticker [post]
func SendSticker(c echo.Context) error {
return sendMedia(c, "sticker")
}
func sendMedia(c echo.Context, mediaType string) error {
var err error
jid := jwtPayload(c).JID
var reqSendMessage typWhatsApp.RequestSendMessage
reqSendMessage.RJID = strings.TrimSpace(c.FormValue("msisdn"))
// Read Uploaded File Based on Send Media Type
var fileStream multipart.File
var fileHeader *multipart.FileHeader
switch mediaType {
case "document":
fileStream, fileHeader, err = c.Request().FormFile("document")
reqSendMessage.Message = fileHeader.Filename
case "image":
fileStream, fileHeader, err = c.Request().FormFile("image")
reqSendMessage.Message = strings.TrimSpace(c.FormValue("caption"))
case "audio":
fileStream, fileHeader, err = c.Request().FormFile("audio")
case "video":
fileStream, fileHeader, err = c.Request().FormFile("video")
reqSendMessage.Message = strings.TrimSpace(c.FormValue("caption"))
case "sticker":
fileStream, fileHeader, err = c.Request().FormFile("sticker")
}
// Don't Forget to Close The File Stream
defer fileStream.Close()
// Get Uploaded File MIME Type
fileType := fileHeader.Header.Get("Content-Type")
// If There are Some Errors While Opeening The File Stream
// Return Bad Request with Original Error Message
if err != nil {
return router.ResponseBadRequest(c, err.Error())
}
// Make Sure RJID is Filled
if len(reqSendMessage.RJID) == 0 {
return router.ResponseBadRequest(c, "Missing Form Value MSISDN")
}
// Check if Media Type is "image" or "video"
// Then Parse ViewOnce Parameter
if mediaType == "image" || mediaType == "video" {
isViewOnce := strings.TrimSpace(c.FormValue("viewonce"))
if len(isViewOnce) == 0 {
// If ViewOnce Parameter Doesn't Exist or Empty String
// Then Set it Default to False
reqSendMessage.ViewOnce = false
} else {
// If ViewOnce Parameter is not Empty
// Then Parse it to Bool
reqSendMessage.ViewOnce, err = strconv.ParseBool(isViewOnce)
if err != nil {
return router.ResponseBadRequest(c, err.Error())
}
}
}
// Convert File Stream in to Bytes
// Since WhatsApp Proto for Media is only Accepting Bytes format
fileBytes, err := convertFileToBytes(fileStream)
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
// Send Media Message Based on Media Type
var resSendMessage typWhatsApp.ResponseSendMessage
switch mediaType {
case "document":
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendDocument(jid, reqSendMessage.RJID, fileBytes, fileType, reqSendMessage.Message)
case "image":
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendImage(jid, reqSendMessage.RJID, fileBytes, fileType, reqSendMessage.Message, reqSendMessage.ViewOnce)
case "audio":
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendAudio(jid, reqSendMessage.RJID, fileBytes, fileType)
case "video":
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendVideo(jid, reqSendMessage.RJID, fileBytes, fileType, reqSendMessage.Message, reqSendMessage.ViewOnce)
case "sticker":
resSendMessage.MsgID, err = pkgWhatsApp.WhatsAppSendSticker(jid, reqSendMessage.RJID, fileBytes)
}
// Return Internal Server Error
// When Detected There are Some Errors While Sending The Media Message
if err != nil {
return router.ResponseInternalError(c, err.Error())
}
return router.ResponseSuccessWithData(c, "Successfully Send Media Message", resSendMessage)
}