// WhatsApp Cloud API client (Meta Business)
// Requires: Meta Business Account, App, registered phone number ID, permanent access token.
// Docs: https://developers.facebook.com/docs/whatsapp/cloud-api
#include "FiveWin.ch"
#include "hbcurl.ch"
//----------------------------------------------------------------------------//
CLASS TWhatsApp
DATA cToken // Bearer access token
DATA cPhoneId // sender phone number ID (Meta numeric ID, not the phone)
DATA cVersion INIT "v18.0" // Graph API version
DATA cBaseUrl INIT "https://graph.facebook.com"
DATA hCurl
DATA cResponse
DATA nError INIT 0
DATA nHttpCode INIT 0
METHOD New( cToken, cPhoneId, cVersion ) CONSTRUCTOR
METHOD End()
METHOD SendText( cTo, cMsg, lPreviewUrl )
METHOD SendTemplate( cTo, cName, cLang, aBodyParams )
METHOD SendImage( cTo, cLinkOrId, cCaption )
METHOD SendDocument( cTo, cLinkOrId, cFilename, cCaption )
METHOD SendAudio( cTo, cLinkOrId )
METHOD SendVideo( cTo, cLinkOrId, cCaption )
METHOD SendLocation( cTo, nLat, nLong, cName, cAddress )
METHOD UploadMedia( cFileName, cMimeType ) // returns media id or nil
METHOD DownloadMedia( cMediaId, cTargetFile )
METHOD Post( cUrl, cJson )
METHOD MessagesUrl() INLINE ::cBaseUrl + "/" + ::cVersion + "/" + ::cPhoneId + "/messages"
METHOD MediaUrl() INLINE ::cBaseUrl + "/" + ::cVersion + "/" + ::cPhoneId + "/media"
METHOD GetError()
METHOD GetMessageId()
ENDCLASS
//----------------------------------------------------------------------------//
METHOD New( cToken, cPhoneId, cVersion ) CLASS TWhatsApp
::cToken := cToken
::cPhoneId := cPhoneId
if ! Empty( cVersion )
::cVersion := cVersion
endif
::hCurl := curl_easy_init()
return Self
//----------------------------------------------------------------------------//
METHOD End() CLASS TWhatsApp
if ::hCurl != nil
curl_easy_cleanup( ::hCurl )
::hCurl := nil
endif
return nil
//----------------------------------------------------------------------------//
METHOD Post( cUrl, cJson ) CLASS TWhatsApp
local aHeaders
curl_easy_reset( ::hCurl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, cUrl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POST, .T. )
curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .T. )
aHeaders := { "Authorization: Bearer " + ::cToken, ;
"Content-Type: application/json" }
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, aHeaders )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POSTFIELDS, cJson )
::nError = curl_easy_perform( ::hCurl )
curl_easy_getinfo( ::hCurl, HB_CURLINFO_RESPONSE_CODE, @::nHttpCode )
if ::nError == HB_CURLE_OK
::cResponse := curl_easy_dl_buff_get( ::hCurl )
else
::cResponse := '{"error":{"message":"curl error ' + LTrim( Str( ::nError ) ) + '"}}'
endif
return ::cResponse
//----------------------------------------------------------------------------//
METHOD SendText( cTo, cMsg, lPreviewUrl ) CLASS TWhatsApp
local hReq := { => }, hText := { => }
DEFAULT lPreviewUrl := .F.
hReq[ "messaging_product" ] := "whatsapp"
hReq[ "recipient_type" ] := "individual"
hReq[ "to" ] := cTo
hReq[ "type" ] := "text"
hText[ "preview_url" ] := lPreviewUrl
hText[ "body" ] := cMsg
hReq[ "text" ] := hText
return ::Post( ::MessagesUrl(), hb_jsonEncode( hReq ) )
//----------------------------------------------------------------------------//
METHOD SendTemplate( cTo, cName, cLang, aBodyParams ) CLASS TWhatsApp
local hReq := { => }, hTpl := { => }, hLang := { => }
local aComponents, hComp, aParams, n
DEFAULT cLang := "en_US"
hReq[ "messaging_product" ] := "whatsapp"
hReq[ "to" ] := cTo
hReq[ "type" ] := "template"
hLang[ "code" ] := cLang
hTpl[ "name" ] := cName
hTpl[ "language" ] := hLang
if HB_ISARRAY( aBodyParams ) .and. Len( aBodyParams ) > 0
aParams := {}
for n := 1 to Len( aBodyParams )
AAdd( aParams, { "type" => "text", "text" => aBodyParams[ n ] } )
next
hComp := { "type" => "body", "parameters" => aParams }
aComponents := { hComp }
hTpl[ "components" ] := aComponents
endif
hReq[ "template" ] := hTpl
return ::Post( ::MessagesUrl(), hb_jsonEncode( hReq ) )
//----------------------------------------------------------------------------//
METHOD SendImage( cTo, cLinkOrId, cCaption ) CLASS TWhatsApp
local hReq := { => }, hImg := { => }
hReq[ "messaging_product" ] := "whatsapp"
hReq[ "to" ] := cTo
hReq[ "type" ] := "image"
if Left( Lower( cLinkOrId ), 4 ) == "http"
hImg[ "link" ] := cLinkOrId
else
hImg[ "id" ] := cLinkOrId
endif
if ! Empty( cCaption )
hImg[ "caption" ] := cCaption
endif
hReq[ "image" ] := hImg
return ::Post( ::MessagesUrl(), hb_jsonEncode( hReq ) )
//----------------------------------------------------------------------------//
METHOD SendDocument( cTo, cLinkOrId, cFilename, cCaption ) CLASS TWhatsApp
local hReq := { => }, hDoc := { => }
hReq[ "messaging_product" ] := "whatsapp"
hReq[ "to" ] := cTo
hReq[ "type" ] := "document"
if Left( Lower( cLinkOrId ), 4 ) == "http"
hDoc[ "link" ] := cLinkOrId
else
hDoc[ "id" ] := cLinkOrId
endif
if ! Empty( cFilename )
hDoc[ "filename" ] := cFilename
endif
if ! Empty( cCaption )
hDoc[ "caption" ] := cCaption
endif
hReq[ "document" ] := hDoc
return ::Post( ::MessagesUrl(), hb_jsonEncode( hReq ) )
//----------------------------------------------------------------------------//
METHOD SendAudio( cTo, cLinkOrId ) CLASS TWhatsApp
local hReq := { => }, hAudio := { => }
hReq[ "messaging_product" ] := "whatsapp"
hReq[ "to" ] := cTo
hReq[ "type" ] := "audio"
if Left( Lower( cLinkOrId ), 4 ) == "http"
hAudio[ "link" ] := cLinkOrId
else
hAudio[ "id" ] := cLinkOrId
endif
hReq[ "audio" ] := hAudio
return ::Post( ::MessagesUrl(), hb_jsonEncode( hReq ) )
//----------------------------------------------------------------------------//
METHOD SendVideo( cTo, cLinkOrId, cCaption ) CLASS TWhatsApp
local hReq := { => }, hVideo := { => }
hReq[ "messaging_product" ] := "whatsapp"
hReq[ "to" ] := cTo
hReq[ "type" ] := "video"
if Left( Lower( cLinkOrId ), 4 ) == "http"
hVideo[ "link" ] := cLinkOrId
else
hVideo[ "id" ] := cLinkOrId
endif
if ! Empty( cCaption )
hVideo[ "caption" ] := cCaption
endif
hReq[ "video" ] := hVideo
return ::Post( ::MessagesUrl(), hb_jsonEncode( hReq ) )
//----------------------------------------------------------------------------//
METHOD SendLocation( cTo, nLat, nLong, cName, cAddress ) CLASS TWhatsApp
local hReq := { => }, hLoc := { => }
hReq[ "messaging_product" ] := "whatsapp"
hReq[ "to" ] := cTo
hReq[ "type" ] := "location"
hLoc[ "latitude" ] := nLat
hLoc[ "longitude" ] := nLong
if ! Empty( cName )
hLoc[ "name" ] := cName
endif
if ! Empty( cAddress )
hLoc[ "address" ] := cAddress
endif
hReq[ "location" ] := hLoc
return ::Post( ::MessagesUrl(), hb_jsonEncode( hReq ) )
//----------------------------------------------------------------------------//
METHOD UploadMedia( cFileName, cMimeType ) CLASS TWhatsApp
local aHeaders, hResp, cMediaId
if ! File( cFileName )
::cResponse := '{"error":{"message":"file not found"}}'
return nil
endif
DEFAULT cMimeType := "application/octet-stream"
curl_easy_reset( ::hCurl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, ::MediaUrl() )
curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .T. )
aHeaders := { "Authorization: Bearer " + ::cToken }
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, aHeaders )
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPPOST, { ;
{ "name" => "messaging_product", "contents" => "whatsapp" }, ;
{ "name" => "type", "contents" => cMimeType }, ;
{ "name" => "file", "file" => cFileName, "type" => cMimeType } } )
::nError = curl_easy_perform( ::hCurl )
curl_easy_getinfo( ::hCurl, HB_CURLINFO_RESPONSE_CODE, @::nHttpCode )
if ::nError == HB_CURLE_OK
::cResponse := curl_easy_dl_buff_get( ::hCurl )
hb_jsonDecode( ::cResponse, @hResp )
if HB_ISHASH( hResp ) .and. hb_HHasKey( hResp, "id" )
cMediaId := hResp[ "id" ]
endif
else
::cResponse := '{"error":{"message":"curl error ' + LTrim( Str( ::nError ) ) + '"}}'
endif
return cMediaId
//----------------------------------------------------------------------------//
METHOD DownloadMedia( cMediaId, cTargetFile ) CLASS TWhatsApp
local aHeaders, hResp, cUrl, cBin, hFile
curl_easy_reset( ::hCurl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, ::cBaseUrl + "/" + ::cVersion + "/" + cMediaId )
curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .T. )
aHeaders := { "Authorization: Bearer " + ::cToken }
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, aHeaders )
::nError := curl_easy_perform( ::hCurl )
if ::nError != HB_CURLE_OK
return .F.
endif
::cResponse := curl_easy_dl_buff_get( ::hCurl )
hb_jsonDecode( ::cResponse, @hResp )
if ! HB_ISHASH( hResp ) .or. ! hb_HHasKey( hResp, "url" )
return .F.
endif
cUrl := hResp[ "url" ]
curl_easy_reset( ::hCurl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, cUrl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .T. )
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, { "Authorization: Bearer " + ::cToken } )
::nError := curl_easy_perform( ::hCurl )
if ::nError != HB_CURLE_OK
return .F.
endif
cBin := curl_easy_dl_buff_get( ::hCurl )
hFile := FCreate( cTargetFile )
if FError() != 0
return .F.
endif
FWrite( hFile, cBin )
FClose( hFile )
return .T.
//----------------------------------------------------------------------------//
METHOD GetError() CLASS TWhatsApp
local hResp, cMsg := ""
if Empty( ::cResponse )
return ""
endif
hb_jsonDecode( ::cResponse, @hResp )
if HB_ISHASH( hResp ) .and. hb_HHasKey( hResp, "error" )
if HB_ISHASH( hResp[ "error" ] ) .and. hb_HHasKey( hResp[ "error" ], "message" )
cMsg := hResp[ "error" ][ "message" ]
endif
endif
return cMsg
//----------------------------------------------------------------------------//
METHOD GetMessageId() CLASS TWhatsApp
local hResp, cId := ""
if Empty( ::cResponse )
return ""
endif
hb_jsonDecode( ::cResponse, @hResp )
if HB_ISHASH( hResp ) .and. hb_HHasKey( hResp, "messages" )
if HB_ISARRAY( hResp[ "messages" ] ) .and. Len( hResp[ "messages" ] ) > 0
if hb_HHasKey( hResp[ "messages" ][ 1 ], "id" )
cId := hResp[ "messages" ][ 1 ][ "id" ]
endif
endif
endif
return cId
//----------------------------------------------------------------------------//