Hello, I would like to know how I can send a Whatsapp message with attachment from within an xHarbour program.
Thanks.
Hello, I would like to know how I can send a Whatsapp message with attachment from within an xHarbour program.
Thanks.
Hello, I would like to know if there is a way to download and read messages and attachments received on Whatsapp within an Xharbour program.
Thanks.
We are trying to get whatsapp.prg sample to work, but the text message never shows up on whatsapp Windows app. The phone number is received by the app, the text message is not. Any hints?
IF lWhatsapp_escr //Si configurĂł que tiene whatsapp de escritorio
ShellExecute( oDlg:hwnd,'open',"whatsapp://send?phone="+ALLTRIM(oQryComWeb:prefijo)+ALLTRIM(cTelefono)+"&text=")
Sleep(1000)
ShellExecute( oDlg:hwnd,'open',"whatsapp://send?phone="+ALLTRIM(oQryComWeb:prefijo)+ALLTRIM(cTelefono)+"&text="+cTexto)
ELSE
// Whatsapp Web
ShellExecute( oDlg:hwnd,'open',"https://api.whatsapp.com/send?phone="+ALLTRIM(oQryComWeb:prefijo)+ALLTRIM(cTelefono)+"&text="+cTexto)
ENDIFThank you. Can you provide a little complete sample, please?
#include "FiveWin.ch"
FUNCTION Main()
LOCAL oDlg1, oBrw, oBot1, lWhatsapp_escr := .f., oChe, aCols := {;
{'5492324463274','Jose','Venezuela'},;
{'5492324463274','Enrico','Italia'},;
{'5492324463274','Cesar','Argentina'}}
DEFINE DIALOG oDlg1 TITLE "Enviar Mensaje por Whatsapp" FROM 05,15 TO 22,72
@ 25, 05 XBROWSE oBrw SIZE 200,90 pixel OF oDlg1 ARRAY aCols ;
HEADERS "Telefono","Nombre", "Texto";
COLUMNS 1,2,3;
SIZES 100, 100,200;
CELL LINES NOBORDER
WITH OBJECT oBrw
:CreateFromCode()
END
@ 5, 5 BUTTON oBot1 PROMPT "&Enviar" OF oDlg1 SIZE 50,15 ;
ACTION EnviarWhatsapp(aCols[oBrw:nRowSel],lWhatsapp_escr,oDlg1) PIXEL
@ 5,160 CHECKBOX oChe VAR lWhatsapp_escr PROMPT "Whatsapp Escritorio" OF oDlg1 SIZE 100,12 PIXEL
ACTIVATE DIALOG oDlg1 CENTERED
RETURN nil
STATIC FUNCTION EnviarWhatsapp(a,lWhatsapp_escr, oDlg)
LOCAL cTexto
cTexto:="Hola *"+ALLTRIM(a[2])+"*!"+CHR(13)+;
"Queremos avisarte que tu detectamos que eres de *"+ALLTRIM(a[3])+"* "
cTexto := STRTRAN(cTexto," ","%20")
cTexto := STRTRAN(cTexto,"&","%26")
cTexto := STRTRAN(cTexto,chr(13),"%0D%0A")
IF lWhatsapp_escr
ShellExecute( oDlg:hwnd,'open',"whatsapp://send?phone="+ALLTRIM(a[1])+"&text=")
Sleep(1000)
ShellExecute( oDlg:hwnd,'open',"whatsapp://send?phone="+ALLTRIM(a[1])+"&text="+cTexto)
ELSE
ShellExecute( oDlg:hwnd,'open',"https://api.whatsapp.com/send?phone="+ALLTRIM(a[1])+"&text="+cTexto)
ENDIF
RETURN nil
###📱 **Do you need a mobile phone for this to work?**
**Yes, you do.** The FiveWin program you've shown works by launching WhatsApp either through the **WhatsApp Desktop App** or the **WhatsApp Web API**, and both require a **linked mobile phone with WhatsApp installed and active**.
---
###🔗 How the system works
The code uses one of two methods to send a message:
#### 1. `https://api.whatsapp.com/send?phone=...&text=...`
* Opens **WhatsApp Web** in your browser.
* Requires the user to have previously scanned a QR code using their phone to link it with the browser session.
* A real phone with WhatsApp is needed for this to work.
#### 2. `whatsapp://send?phone=...&text=...`
* Opens the **WhatsApp Desktop App** (if installed).
* Again, this desktop app must be linked to a **real phone via QR code pairing**.
---
###âś… What does WhatsApp provide?
* The “clearing” or delivery mechanism is **WhatsApp itself**, via the URL scheme or API entry point.
* There is **no external clearing server** involved.
* WhatsApp ensures that messages are sent from a real phone account – it **does not allow sending messages without a linked mobile device**.
---
###🔄 Can it be automated?
**No, not fully**, unless you use the **WhatsApp Business API**. Here's a comparison:
| Method | Requires Phone | Fully Automated | Easy Setup | For Business Use |
| ------------------------------- | ----------------------------------- | --------------- | ---------- | --------------------------- |
| `https://api.whatsapp.com/send` |✅ Yes |❌ No |✅ Yes |✅ Personal / Small Business |
| `whatsapp://send` |✅ Yes |❌ No |✅ Yes |✅ Personal / Small Business |
| WhatsApp Business API |❌ No (but requires official number) |✅ Yes |❌ Complex |✅ Medium / Large Business |
---
###⚠️ Important Notes
* The number must be in **international format** (e.g. `5492324...` for Argentina).
* The phone must be **online** and have WhatsApp active.
* Users will usually need to **click “Send”** manually after the message is opened, unless advanced automation tools are used (which are often against WhatsApp's terms).
---
###💡 Summary
You **cannot send WhatsApp messages without a real phone** — whether using the browser, the desktop app, or even via automation. WhatsApp's infrastructure requires all messages to go through a **verified phone account** unless you use the official Business API with approval and a provider like Twilio or 360dialog.
Would you like a version that cycles through a list and opens WhatsApp for each contact with a delay and user guidance? I can create that as well.
Great question — and yes, **some degree of automation is possible**, *but only partially and with important limitations*.
---
###âś… **What you *can* automate using WhatsApp Desktop + Timer**
You can:
* Automatically **launch the WhatsApp Desktop app** using `ShellExecute`.
* Automatically **open a chat with a specific phone number** using `whatsapp://send?phone=...&text=...`.
* Use a **timer or delay** (`Sleep()`) between launches.
* Potentially **simulate user input** (like keyboard events) via:
* Windows API (e.g., `keybd_event`, `SendInput`),
* AutoHotKey scripts,
* `INPUT` struct via low-level Windows automation.
---
###⚠️ **What you *cannot* reliably automate**
WhatsApp Desktop is:
* **Not designed to be automated** (there is no official API for the desktop app).
* Subject to **UI changes** (button positions, window titles, etc.).
* Dependent on **human interaction** — WhatsApp intentionally makes it difficult to *fully* automate sending messages to prevent spam and abuse.
Also:
* You **cannot detect if the message was actually sent**.
* If the chat is already open, `whatsapp://send?phone=...` won't pre-fill the message reliably.
* **Simulating the Enter key to send** is fragile (it may send the wrong message or fail if focus is lost).
---
###🧪 âś… Feasible hybrid solution for semi-automation
You can build a **loop with a timer** like this:
```xbase
STATIC FUNCTION LoopSendWhatsApp( aData, oDlg )
LOCAL i, cTexto, lWhatsapp_escr := .t.
FOR i := 1 TO LEN(aData)
cTexto := "Hello *" + aData[2] + "* from *" + aData[3] + "*!"
cTexto := STRTRAN(cTexto, " ", "%20")
cTexto := STRTRAN(cTexto, CHR(13), "%0D%0A")
ShellExecute( oDlg:hwnd, "open", ;
"whatsapp://send?phone=" + ALLTRIM(aData[1]) + "&text=" + cTexto )
Sleep(3000) // wait before next
NEXT
```
Then combine it with user instructions like:
> *"Click 'Send' manually for each message. The next message will open in 3 seconds."*
---
### 🛠 Advanced Option: AutoHotKey + WhatsApp Desktop
With [AutoHotKey](https://www.autohotkey.com/) you can:
* Wait for the WhatsApp window.
* Send keys (`Send Hello!{Enter}`).
* Close the window, go to the next.
But this is brittle and **not officially supported**.
---
###âś… Summary
| Approach | Fully Automated? | Stable? | WhatsApp-compliant? | Notes |
| ------------------------------------- | ---------------- | --------- | ------------------- | ------------------------- |
| WhatsApp Web / Desktop + Timer |❌ Partial |✅ Yes |✅ Yes | Requires user interaction |
| Simulated keyboard/mouse (AutoHotKey) |✅ Mostly |❌ Fragile |❌ Risky | Can break on updates |
| WhatsApp Business API |âś… Yes |âś… Yes |âś… Official | Paid, complex setup |
---
🔁 Explanation: Sending and Receiving SMS via USB GSM Modem
You can use a USB GSM modem or dongle (like Huawei E3372 or SIM800C) for both sending and receiving SMS, including two-factor authentication (2FA) codes.
These devices support AT commands (standard modem instructions), such as:
Receiving:
AT+CMGF=1 – Set text mode
AT+CMGL="ALL" – List all messages
AT+CMGR=1 – Read specific message
Sending:
AT+CMGS="+43660..." – Send SMS to a number
Followed by the message text and Ctrl+Z (\x1A) to send
💡 What your script or application can do:
Monitor the COM port (e.g., COM3 or /dev/ttyUSB0)
Detect incoming messages (e.g., with 2FA codes)
Extract codes using pattern matching (e.g., Your code is 123456)
Send SMS replies or notifications, for example:
Confirm registration
Send alerts
Notify users of login attempts or system messages
âś… Key Benefits:
Works without internet
Fully scriptable (Harbour, PHP, Python, etc.)
Uses a regular SIM card — no third-party provider needed
Reliable fallback when smartphones or internet are not available
cmsoft wrote: Si, aqui va:Thank you. And what about attachments?
#include "FiveWin.ch"
FUNCTION Main()
//SendToWhatsApp('5492324463274')
//SendToWhatsApp('5492324463274','Esto es una prueba')
SendToWhatsApp('5492324463274','Primer Prueba, un archivo adjunto y una foto de la web',;
{'c:\fwh21\samples\whatsapp3.prg'})
SendToWhatsApp('5492324463274','Segunda Prueba, 2 adjunotos en un array, y dos imagenes desde la web en un array',;
{'c:\fwh21\bitmaps\agua2.bmp','c:\fwh21\bitmaps\cara3.bmp'})
RETURN nil
FUNC SendToWhatsApp( cPhone, cMsg, aAttach )
LOCAL oShell, aFiles := {}, aOthers := {}, lRet
If (lRet := !Empty( cPhone ))
If !Empty( aAttach )
If( !HB_ISARRAY( aAttach ), aAttach := { aAttach }, )
AEval( aAttach, {|cFile| If( HB_ISSTRING( cFile ) .AND. File( cFile ), AAdd( aFiles, cFile), AAdd( aOthers, cFile ) ) } )
EndIf
cMsg := StrTran( cMsg, CRLF, "%0D%0A" )
oShell := CreateObject( "WScript.Shell" )
ShellExecute( 0, "Open", "whatsapp://send?phone="+cPhone+"&text="+cMsg)
SysWait( 0.2 )
If !Empty( aFiles )
If FW_1CopyToClipBoard( aFiles, 15 )
SysWait( 2 )
oShell:SendKeys( "^v" )
SysWait( 1 )
EndIf
EndIf
AEval( aOthers, <|u|
If FW_1CopyToClipBoard( u, 2 ) .OR. FW_CopyToClipBoard( u )
SysWait( 1 )
oShell:SendKeys( "^v" )
SysWait( 1 )
EndIf
RETURN NIL
> )
oShell:SendKeys("~")
EndIf
RETURN lRet
function FW_1CopyToClipBoard( uValue, nFormat )
local lCopied := .f.
local aBmp
if HB_ISPOINTER( uValue ) // assumes GDI+ Image object
GDIPLUSImageToClipBoard( uValue, GetDesktopWindow() )
return .t.
endif
if uValue != nil
if nFormat == 15
if !HB_ISARRAY( uValue ); uValue := { uValue }; endif
_fwhtmp_ := uValue
uValue := {}
AEval( _fwhtmp_, { |c| If( File( c ), AAdd( uValue, TrueName( c ) ), nil ) } )
_fwhtmp_ := nil
if Empty( uValue )
return .f.
endif
endif
if Empty( nFormat ) .and. HB_ISARRAY( uValue ) .and. ;
AScan( uValue, { |u| !( ValType( u ) == "C" .and. File( u ) ) } ) == 0
AEval( uValue, { |c,i| uValue[ i ] := TrueName( c ) } )
nFormat := 15
endif
if nFormat == 2 .and. HB_ISSTRING( uValue )
aBmp := FW_ReadImage( nil, uValue, , .f. )
if !Empty( aBmp[ 1 ] )
uValue := aBmp[ 1 ]
else
return .f.
endif
endif
if Empty( nFormat )
if HB_ISSTRING( uValue )
if IsBinaryData( uValue )
if Left( MemoryBufferType( uValue ), 4 ) == "IMG."
aBmp := FW_ReadImage( nil, uValue, , .f. )
if !Empty( aBmp[ 1 ] )
uValue := aBmp[ 1 ]
nFormat := 2
endif
endif
if Empty( nFormat )
return .f.
endif
elseif Left( uValue, 4 ) == "http"
aBmp := FW_ReadImage( nil, uValue, , .f. )
if !Empty( aBmp[ 1 ] )
uValue := aBmp[ 1 ]
nFormat := 2
else
nFormat := 1
endif
else
nFormat := 1
endif
elseif HB_ISNUMERIC( uValue )
if ISHBITMAP( uValue )
nFormat := 2
elseif ISENHMETA( uValue )
nFormat := 14
endif
endif
endif
if Empty( nFormat )
if ValType( uValue ) $ "AH"
uValue := FW_ValToExp( uValue )
elseif ValType( uValue ) $ "DT"
uValue := CharRem( "'", FW_DateToSQL( uValue ) )
else
uValue := cValToChar( uValue )
endif
nFormat := 1
endif
if nFormat != nil
if nFormat == 2
if HB_ISNUMERIC( uValue ) .and. ISHBITMAP( uValue )
GDIPLUSHBITMAPTOCLIPBOARD( uValue, GetDeskTopWindow() )
lCopied := .t.
endif
elseif OpenClipBoard( GetDeskTopWindow() )
EmptyClipboard()
if nFormat == 1 .and. IsUtf8( uValue )
uValue := utf8toutf16( uValue )
if !( Right( uValue, 2 ) == Chr( 0 ) + Chr( 0 ) )
uValue += ( Chr( 0 ) + Chr( 0 ) )
endif
lCopied := SetClipboardData( 13, uValue )
elseif nFormat == 15
lCopied := SetClipboardData( 15, uValue )
else
lCopied := SetClipboardData( nFormat, uValue )
endif
CloseClipboard()
endif
endif
endif
if aBmp != nil
PalBmpFree( aBmp )
endif
return lCopiedBuen dia,,,
Para whatsapp de escritorio lo he trabajado sin problemas con los ejemplos publicados en el foro,,,
pero como en windows server, no funciona esta version, creo que deberĂamos pensar en la opciĂłn web usando el api de whatsapp
Saludos
JONSSON RUSSI
Let's make a little step back: we are still not able to send a simple text message through Whatsapp (only phone number and text). Can you provide a little working sample? Or some configuration is needed on the Whatsapp side?
// REQUIERE VERSION WhatsApp - Escritorio
FUNCTION PRUEBA__WhatsApp()
LOCAL cPhone := "57"+"325874510" // NO HACER PRUEBAS CON EL MISMO NUMERO DE QUIEN ENVIA
LOCAL cMenEnv := "Anexo factura de venta en formato PDF"
LOCAL cNomFil := "e:\temp\FACTURA_SP1012.PDF"
enviaLinkWhatsApp(ALLTRIM(cPhone),cMenEnv,cNomFil)
RETURN
//***********************************************************
function enviaLinkWhatsApp(cPhone,cMsg,cFilPdf)
LOCAL aImgEnv := {}
IF !FILE(cFilPdf)
ALERTA("Aviso. Archivo a transferir no existe : "+cFilPdf)
ENDIF
IF cFilPdf <> NIL .AND. !EMPTY(cFilPdf)
aImgEnv := {cFilPdf}
ENDIF
IF !EMPTY(cPhone)
_SendToWhatsApp( AllTrim( cPhone ), AllTrim( cMsg ), aImgEnv )
ELSE
ALERTA("Aviso. Falto el nÂŁmero para WhatSapp ")
ENDIF
return nil
//****************************************************
function _SendToWhatsApp( cPhone, cMsg, aAttach )
local oShell
local aFiles := {}
local aOthers := {}
if Empty( cPhone ); return .f.; endif
DEFAULT cMsg := "Hello"
if !Empty( aAttach )
if !HB_ISARRAY( aAttach ); aAttach := { aAttach }; endif
AEval( aAttach, {|c| if( HB_ISSTRING( c ) .and. File( c ), ;
AAdd( aFiles, c ), AAdd( aOthers, c ) ) } )
endif
cMsg := StrTran( cMsg, CRLF, "%0D%0A" )
oShell := CreateObject( "WScript.Shell" )
ShellExecute( 0, "Open", "whatsapp://send?phone="+cPhone+"&text="+cMsg )
SysWait( 0.2 )
if !Empty( aFiles )
if FW_CopyToClipBoard( aFiles, 15 )
SysWait( 2 ); oShell:SendKeys( "^v" ); SysWait( 1 )
endif
endif
AEval( aOthers, <|u|
if FW_CopyToClipBoard( u, 2 ) .or. FW_CopyToClipBoard( u )
SysWait( 1 ); oShell:SendKeys( "^v" ); SysWait( 1 )
endif
return nil
> )
SysWait( 1 )
oShell:SendKeys("~")
return nil
//********************************************