Click here to Skip to main content
13,768,718 members
Click here to Skip to main content
Add your own
alternative version

Stats

4.1K views
232 downloads
16 bookmarked
Posted 6 Nov 2018
Licenced CPOL

Sending WhatsApp Messages from a Win32 C++ Program - Part 2

, 6 Nov 2018
Rate this:
Please Sign up or sign in to vote.
A simple way for sending WhatsApp documents and images to an individual or to a group in C++

Introduction

This article is the second part, following the first part. In this part, I will explain how to send images and documents to a group. As mentioned in part 1, there are several service providers and we have chosen one of them (WhatsAppMate) and started a free trial. However, their code samples for using their services are in almost any programming language except for C++. So we wrote our own C++ class for that purpose. Sending documents and files is a bit more complicated and will be explained in this article.

Background

WhatsApp is a multi-platform free service for chatting via video or voice and for sending messages to individuals or groups, including files, media, etc. WhatsApp is better than the old SMS because it is free and has more features. During our day to day work, we need to set up all sort of alerts to be sent to a group who share a job or work on a feature and when that's done automatically, it makes life easier to be notified.

Using the Code

For the GitHub repo, see this one (the class) and this one (the MFC based tool).

There are several types of messages that can be sent, including individual or group messages and with or without a photo or PDF file attached to them. You can read about it in this page.

Here are the building blocks you need to have. Alternatively, I will later attach a compiled (.exe) test application.

//
#define    GroupAdmin            <YOUR GROUP ADMIN MOBILE PHONE>
#define GroupName                <YOUR GROUP NAME>
#define CLIENT_ID                <YOUR CLIENT ID>
#define CLIENT_SECRET            <YOUR CLIENT SECRET>

#define GROUP_API_SERVER        L"api.whatsmate.net"
#define GROUP_API_PATH          L"/v3/whatsapp/group/text/message/12"
#define IMAGE_SINGLE_API_URL    L"http://api.whatsmate.net/v3/whatsapp/group/image/message/12"
//

Our User Interface

For the purpose of this article, we added some User Interface.

As you can see, you can enter a message and select a document or an image to be sent with it. When you select a document or an image, the message appears as the caption of the image / document sent.

Sending Images and Documents

When images or documents are sent, we can add a "caption" to them which can be our message.

There is a different API for sending documents, as they appear as "attachment" on the recipient's phone. Images, however appear inline as part of the message. So in terms of the API, there is a difference between:

Our tool, checks the file's type and uses the API accordingly.

Converting an Image or a Document to base64

Here is how we convert an image or a document into base64 string. We will be using CryptBinaryToString().

std::wstring document = _T("");
DWORD desiredLength = 0;
CryptBinaryToString(attachment, length, CRYPT_STRING_BASE64, NULL, &desiredLength);
desiredLength++;
TCHAR* base64content = (TCHAR*)malloc(desiredLength * sizeof(TCHAR));
CryptBinaryToString(attachment, length, CRYPT_STRING_BASE64, base64content, &desiredLength);

Connecting to the Internet

First, we open an Internet connection and connect to the API service:

hOpenHandle = InternetOpen(_T("HTTP"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (hOpenHandle == NULL)
{
    return false;
}

hConnectHandle = InternetConnect(hOpenHandle,
    GROUP_API_SERVER,
    INTERNET_DEFAULT_HTTP_PORT,
    NULL, NULL, INTERNET_SERVICE_HTTP,
    0, 1);

if (hConnectHandle == NULL)
{
    InternetCloseHandle(hOpenHandle);
    return false;
}

Opening a Request

const wchar_t *AcceptTypes[] = { _T("application/json"),NULL };
HINTERNET hRequest = HttpOpenRequest(hConnectHandle, _T("POST"),
                     DOCUMENT_API_PATH, NULL, NULL, AcceptTypes, 0, 0);

if (hRequest == NULL)
{
    InternetCloseHandle(hConnectHandle);
    InternetCloseHandle(hOpenHandle);
    return false;
}

or when we wish to send an image:

const wchar_t *AcceptTypes[] = { _T("application/json"),NULL };
HINTERNET hRequest = HttpOpenRequest(hConnectHandle, _T("POST"),
                     IMAGE_API_PATH, NULL, NULL, AcceptTypes, 0, 0);

if (hRequest == NULL)
{
    InternetCloseHandle(hConnectHandle);
    InternetCloseHandle(hOpenHandle);
    return false;
}

while we define the API paths as follows:

#define DOCUMENT_API_PATH       L"/v3/whatsapp/group/document/message/12"
#define IMAGE_API_PATH          L"/v3/whatsapp/group/image/message/12"

The Header

Next, we post the Header which is composed using the following code:

std::wstring HeaderData;

HeaderData += _T("X-WM-CLIENT-ID: ");
HeaderData += _T(CLIENT_ID);
HeaderData += _T("\r\nX-WM-CLIENT-SECRET: ");
HeaderData += _T(CLIENT_SECRET);
HeaderData += _T("\r\n");
HttpAddRequestHeaders(hRequest, HeaderData.c_str(), HeaderData.size(), NULL);

The SendGroupDocument() Function

Here is the entire function for sending a document to a WhatsApp group:

bool SGWhatsApp::SendGroupDocument(LPCTSTR ClientID, LPCTSTR ClientSecret, 
    LPCTSTR groupAdmin, LPCTSTR groupName, /*LPCTSTR message,*/ LPCTSTR filename, 
    LPBYTE attachment, int length)
{
    BOOL bResults = FALSE;
    HINTERNET hOpenHandle, hConnectHandle;
    const TCHAR* szHeaders = _T("Content-Type:application/json; charset=utf-8\r\n");


    hOpenHandle = InternetOpen(_T("HTTP"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    if (hOpenHandle == NULL)
    {
        return bResults;
    }

    hConnectHandle = InternetConnect(hOpenHandle,
        DOCUMENT_API_SERVER,
        INTERNET_DEFAULT_HTTP_PORT,
        NULL, NULL, INTERNET_SERVICE_HTTP,
        0, 1);

    if (hConnectHandle == NULL)
    {
        InternetCloseHandle(hOpenHandle);
        return bResults;
    }

    const wchar_t *AcceptTypes[] = { _T("application/json"),NULL };
    HINTERNET hRequest = HttpOpenRequest(hConnectHandle, _T("POST"), DOCUMENT_API_PATH, 
                         NULL, NULL, AcceptTypes, 0, 0);

    if (hRequest == NULL)
    {
        InternetCloseHandle(hConnectHandle);
        InternetCloseHandle(hOpenHandle);
        return bResults;
    }

    std::wstring HeaderData;

    HeaderData += _T("X-WM-CLIENT-ID: ");
    HeaderData += ClientID;
    HeaderData += _T("\r\nX-WM-CLIENT-SECRET: ");
    HeaderData += ClientSecret;
    HeaderData += _T("\r\n");
    HttpAddRequestHeaders(hRequest, HeaderData.c_str(), HeaderData.size(), NULL);

    std::wstring document = _T("");
    DWORD desiredLength = 0;
    CryptBinaryToString(attachment, length, CRYPT_STRING_BASE64, NULL, &desiredLength);
    desiredLength++;
    TCHAR* base64content = (TCHAR*)malloc(desiredLength * sizeof(TCHAR));
    CryptBinaryToString(attachment, length, CRYPT_STRING_BASE64, base64content, &desiredLength);

    std::wstring WJsonData;
    WJsonData += _T("{");
    WJsonData += _T("\"group_admin\":\"");
    WJsonData += groupAdmin;
    WJsonData += _T("\",");
    WJsonData += _T("\"group_name\":\"");
    WJsonData += groupName;
    WJsonData += _T("\",");

    WJsonData += _T("\"filename\":\"");
    WJsonData += filename;
    WJsonData += _T("\",");

    WJsonData += _T("\"document\":\"");    
    // Needed to remove CRLF and all spaces symbols
    for(size_t i=0; i<lstrlen(base64content); i++)
        if (!isspace(base64content[i]))
            WJsonData += base64content[i];
    WJsonData += _T("\"");

    WJsonData += _T("}");

    free(base64content);
    const std::string JsonData(WJsonData.begin(), WJsonData.end());

    bResults = HttpSendRequest(hRequest, NULL, 0, (LPVOID)(JsonData.c_str()), JsonData.size());

    TCHAR StatusText[BUFFER_LENGTH] = { 0 };
    DWORD StatusTextLen = BUFFER_LENGTH;
    HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_TEXT, &StatusText, &StatusTextLen, NULL);
    bResults = (StatusTextLen && wcscmp(StatusText, L"OK") == FALSE);

    DWORD availableBytes = 0;
    DWORD downloadedBytes = 0;
    LPBYTE nextBytes = (LPBYTE)malloc((availableBytes +1) * sizeof(TCHAR));
    memset(nextBytes, 0, (availableBytes + 1) * sizeof(TCHAR));
    InternetQueryDataAvailable(hRequest, &availableBytes, 0, 0);
    InternetReadFile(hRequest, nextBytes, availableBytes, &downloadedBytes);
    free(nextBytes);

    InternetCloseHandle(hConnectHandle);
    InternetCloseHandle(hOpenHandle);

    return bResults;
}

The recipient will see a WhatsApp message like this:

But to be sure, at this point, we need to know if everything went well. That is done by querying the result of our Http Request.

The StatusText we expect if it worked is "OK".

TCHAR StatusText[BUFFER_LENGTH] = { 0 };
DWORD StatusTextLen = BUFFER_LENGTH;
HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_TEXT, &StatusText, &StatusTextLen, NULL);
bResults = (StatusTextLen && wcscmp(StatusText, L"OK")==FALSE);

The SendGroupImage() Function

Next, we have the SendGroupImage() function which is used to send an image to a WhatsApp group. An image can be a .png or .jpg, which will be displayed inline as a new message.

bool SGWhatsApp::SendGroupDocument(LPCTSTR ClientID, LPCTSTR ClientSecret, 
    LPCTSTR groupAdmin, LPCTSTR groupName, /*LPCTSTR message,*/ LPCTSTR filename, 
    LPBYTE attachment, int length)
{
    BOOL bResults = FALSE;
    HINTERNET hOpenHandle, hConnectHandle;
    const TCHAR* szHeaders = _T("Content-Type:application/json; charset=utf-8\r\n");

    hOpenHandle = InternetOpen(_T("HTTP"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    if (hOpenHandle == NULL)
    {
        return bResults;
    }

    hConnectHandle = InternetConnect(hOpenHandle,
        DOCUMENT_API_SERVER,
        INTERNET_DEFAULT_HTTP_PORT,
        NULL, NULL, INTERNET_SERVICE_HTTP,
        0, 1);

    if (hConnectHandle == NULL)
    {
        InternetCloseHandle(hOpenHandle);
        return bResults;
    }

    const wchar_t *AcceptTypes[] = { _T("application/json"),NULL };
    HINTERNET hRequest = HttpOpenRequest(hConnectHandle, _T("POST"), 
                         DOCUMENT_API_PATH, NULL, NULL, AcceptTypes, 0, 0);

    if (hRequest == NULL)
    {
        InternetCloseHandle(hConnectHandle);
        InternetCloseHandle(hOpenHandle);
        return bResults;
    }

    std::wstring HeaderData;

    HeaderData += _T("X-WM-CLIENT-ID: ");
    HeaderData += ClientID;
    HeaderData += _T("\r\nX-WM-CLIENT-SECRET: ");
    HeaderData += ClientSecret;
    HeaderData += _T("\r\n");
    HttpAddRequestHeaders(hRequest, HeaderData.c_str(), HeaderData.size(), NULL);

    std::wstring document = _T("");
    DWORD desiredLength = 0;
    CryptBinaryToString(attachment, length, CRYPT_STRING_BASE64, NULL, &desiredLength);
    desiredLength++;
    TCHAR* base64content = (TCHAR*)malloc(desiredLength * sizeof(TCHAR));
    CryptBinaryToString(attachment, length, CRYPT_STRING_BASE64, base64content, &desiredLength);

    std::wstring WJsonData;
    WJsonData += _T("{");
    WJsonData += _T("\"group_admin\":\"");
    WJsonData += groupAdmin;
    WJsonData += _T("\",");
    WJsonData += _T("\"group_name\":\"");
    WJsonData += groupName;
    WJsonData += _T("\",");
    //WJsonData += _T("\"message\":\"");
    //WJsonData += message;
    //WJsonData += _T("\"");

    WJsonData += _T("\"filename\":\"");
    WJsonData += filename;
    WJsonData += _T("\",");

    WJsonData += _T("\"document\":\"");    
    // Needed to remove CRLF and all spaces symbols
    for(size_t i=0; i<lstrlen(base64content); i++)
        if (!isspace(base64content[i]))
            WJsonData += base64content[i];
    WJsonData += _T("\"");

    WJsonData += _T("}");

    free(base64content);
    const std::string JsonData(WJsonData.begin(), WJsonData.end());

    bResults = HttpSendRequest(hRequest, NULL, 0, (LPVOID)(JsonData.c_str()), JsonData.size());

    TCHAR StatusText[BUFFER_LENGTH] = { 0 };
    DWORD StatusTextLen = BUFFER_LENGTH;
    HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_TEXT, &StatusText, &StatusTextLen, NULL);
    bResults = (StatusTextLen && wcscmp(StatusText, L"OK") == FALSE);

    DWORD availableBytes = 0;
    DWORD downloadedBytes = 0;
    LPBYTE nextBytes = (LPBYTE)malloc((availableBytes +1) * sizeof(TCHAR));
    memset(nextBytes, 0, (availableBytes + 1) * sizeof(TCHAR));
    InternetQueryDataAvailable(hRequest, &availableBytes, 0, 0);
    InternetReadFile(hRequest, nextBytes, availableBytes, &downloadedBytes);
    free(nextBytes);

    InternetCloseHandle(hConnectHandle);
    InternetCloseHandle(hOpenHandle);

    return bResults;
}

Thank you

Thanks for Ivan Voloschuk for his help. 

History

  • 6th November, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Haephrati
CEO Secured Globe, Inc.
United States United States
Michael Haephrati, CEO and co-founder of Secured Globe, Inc. Worked on many ventures starting from HarmonySoft, designing Rashumon, the first Graphical Multi-lingual word processor for Amiga computer. During 1995-1996 he worked as a Contractor with Apple at Cupertino.



You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.181116.1 | Last Updated 6 Nov 2018
Article Copyright 2018 by Michael Haephrati
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid