5

I'm studying Gmail API. Let's say I'm interested to see which mail contains the string "foobar2000".

This is my code so far:

Main.java

package manhattan.email.bot;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.services.gmail.model.Message;
import com.google.api.services.gmail.model.MessagePart;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Properties;

import manhattan.email.bot.google.GmailCredentials;
import manhattan.email.bot.google.GmailService;
import manhattan.email.bot.google.GmailServiceImpl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;

public class Main {

    static final String MY_EMAIL = "xxxxx@gmail.com";
    static final String MY_CLIENT_ID = "xxxxxapps.googleusercontent.com";    
    static final String MY_CLIENT_SECRET = "xxxxx";
    static final String MY_ACCESS_TOKEN = "xxxxx";
    static final String MY_REFRESH_TOKEN = "xxxxx";

    public static void main(String[] args) {
        try {
            GmailService gmailService = new GmailServiceImpl(GoogleNetHttpTransport.newTrustedTransport());
            gmailService.setGmailCredentials(GmailCredentials.builder()
                    .userEmail(MY_EMAIL)
                    .clientId(MY_CLIENT_ID)
                    .clientSecret(MY_CLIENT_SECRET)
                    .accessToken(MY_ACCESS_TOKEN)
                    .refreshToken(MY_REFRESH_TOKEN)
                    .build());

           List<com.google.api.services.gmail.model.Message> messageList = gmailService.listMessagesMatchingQuery(MY_EMAIL,"foobar2000");
           System.out.println("Search result: ");

           for (Message m:messageList){
               System.out.println(m.toPrettyString());
            }

        }
        catch (GeneralSecurityException gse){
            System.err.println(">>> GSE: "+gse.getMessage());
        }
        catch (IOException ioe){
            System.err.println(">>> IOE: "+ioe.getMessage());
        }
    }
}

GmailCredentials.java

package manhattan.email.bot.google;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class GmailCredentials {
    private final String userEmail;
    private final String clientId;
    private final String clientSecret;
    private final String accessToken;
    private final String refreshToken;
}

GmailService.java

package manhattan.email.bot.google;

import com.google.api.services.gmail.model.Message;
import java.io.IOException;
import java.util.List;
import javax.mail.MessagingException;

public interface GmailService {

    void setGmailCredentials(GmailCredentials credentials);
    boolean sendMessage(String recipientAddress, String subject, String body) throws MessagingException, IOException;
    List<Message> listMessagesMatchingQuery(String userId, String query) throws IOException; 
}

GmailServiceImpl.java

package manhattan.email.bot.google;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Base64;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.model.ListMessagesResponse;
import com.google.api.services.gmail.model.Message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
 *
 * @author OCIN
 */
public class GmailServiceImpl implements GmailService {

    private static final String APPLICATION_NAME = "mail-bot";
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

    private final HttpTransport httpTransport;
    private GmailCredentials gmailCredentials;

    public GmailServiceImpl(HttpTransport transport){
        this.httpTransport = transport;
    }

    private Gmail createGmail() {
        Credential credential = authorize();
        return new Gmail.Builder(httpTransport, JSON_FACTORY, credential)
                .setApplicationName(APPLICATION_NAME)
                .build();
    }

     private Credential authorize() {
        return new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(JSON_FACTORY)
                .setClientSecrets(gmailCredentials.getClientId(), gmailCredentials.getClientSecret())
                .build()
                .setAccessToken(gmailCredentials.getAccessToken())
                .setRefreshToken(gmailCredentials.getRefreshToken());
    }

    @Override
    public void setGmailCredentials(GmailCredentials credentials) {
        this.gmailCredentials = credentials;
    }

     private MimeMessage createEmail(String to, String from, String subject, String bodyText) throws MessagingException {
        MimeMessage email = new MimeMessage(Session.getDefaultInstance(new Properties(), null));
        email.setFrom(new InternetAddress(from));
        email.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to));
        email.setSubject(subject);
        email.setText(bodyText);
        return email;
    }

    private Message createMessageWithEmail(MimeMessage emailContent) throws MessagingException, IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        emailContent.writeTo(buffer);

        return new Message().setRaw(Base64.encodeBase64URLSafeString(buffer.toByteArray()));
    }

    @Override
    public boolean sendMessage(String recipientAddress, String subject, String body) throws MessagingException, IOException {
        Message message = createMessageWithEmail(
            createEmail(recipientAddress, gmailCredentials.getUserEmail(), subject, body));

        return createGmail().users()
            .messages()
            .send(gmailCredentials.getUserEmail(), message)
            .execute()
            .getLabelIds().contains("SENT");
    }

    public List<Message> listMessagesMatchingQuery(String userId, String query) throws IOException {
        ListMessagesResponse response = createGmail().users().messages().list(userId).setQ(query).execute();
        List<Message> messages = new ArrayList<Message>();

        while (response.getMessages() != null) {
            messages.addAll(response.getMessages());
            if (response.getNextPageToken() != null) {
                String pageToken = response.getNextPageToken();
                response = createGmail().users().messages().list(userId).setQ(query).setPageToken(pageToken).execute();
            } 
            else {
                break;
            }
        }

       // for (Message message : messages) {
       //     System.out.println(message.toPrettyString());
       // }

        return messages;
    }
}

The output of my code is:

Search result: { "id" : "16d929a8524a6bbd", "threadId" : "16d929a8524a6bbd" }

Which is true, because there's only 1 mail with "foobar2000" in the body. The full body content is:

Bla bla bla foobar2000 Hello world

How to get that? I tried using this inside the loop:

System.out.println(StringUtils.newStringUtf8(Base64.decodeBase64(m.getPayload().getParts().get(0).getBody().getData())));

Gave me a NullPointerException...

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
anta40
  • 6,511
  • 7
  • 46
  • 73
  • I understand that the error only appears on the last piece of code that you posted. There are many methods chained together, so I strongly recommend to divide them and log them all step by step; starting from the `m` itself until the `newStringUtf8`. That way we all can see clearly where the `NullPointerException` comes from. Another approach is to save the message ID to use the `messages.get` [ https://developers.google.com/gmail/api/v1/reference/users/messages/get ] method. – Jacques-Guzel Heron Oct 08 '19 at 09:30
  • After some debugging... I found that `m.getPayload()` returns null. So this is the cause of NPE. Hmm... – anta40 Oct 08 '19 at 09:42
  • Interestingly, `m.getSnippet()` returns this: `Message snippet: Bla bla bla foobar2000 Hello world -- Clive Stevenson Android Developer M: +62 822 44 5555 E: clive.stevenson@test123.com South Jakarta, Indonesia. Bla bla bla foobar2000 Hello world -- Clive Stevenson Android Developer M: +62 822 44 5555 E: clive.stevenson@test123.com South Jakarta, Indonesia.` The content I'm looking for is `Bla bla bla foobar2000 Hello world`. How to get that? – anta40 Oct 08 '19 at 09:51

1 Answers1

2

If I understood your situation well, you said on your last comment, m.getSnippet() gives you back this text:

Message snippet: Bla bla bla foobar2000 Hello world -- Clive Stevenson Android Developer M: +62 822 44 5555 E: clive.stevenson@test123.com South Jakarta, Indonesia.

And you want to end up only with the message body:

Bla bla bla foobar2000 Hello world

If that is your situation, you can use the following function to end up with the message body:

public String getContent(Message message) {
    StringBuilder stringBuilder = new StringBuilder();
    try {
        getPlainTextFromMessageParts(message.getPayload().getParts(), stringBuilder);
        byte[] bodyBytes = Base64.decodeBase64(stringBuilder.toString());
        String text = new String(bodyBytes, StandardCharsets.UTF_8);
        return text;
    } catch (UnsupportedEncodingException e) {
        logger.error("UnsupportedEncoding: " + e.toString());
        return message.getSnippet();
    }
}

private void getPlainTextFromMessageParts(List<MessagePart> messageParts, StringBuilder stringBuilder) {
    for (MessagePart messagePart : messageParts) {
        if (messagePart.getMimeType().equals("text/plain")) {
            stringBuilder.append(messagePart.getBody().getData());
        }

        if (messagePart.getParts() != null) {
            getPlainTextFromMessageParts(messagePart.getParts(), stringBuilder);
        }
    }
}

That code will extract the body as you requested. Please, let me know if you need further clarifications or you need more help.

Kudos to Gerard Verbeek for sharing the previous code.

Jacques-Guzel Heron
  • 2,480
  • 1
  • 7
  • 16
  • ty, works great! I did decoding this way: Base64 base64Url = new Base64(true); String bodyDecoded = StringUtils.newStringUtf8( base64Url.decodeBase64( stringBuilder.toString() )); – Boris Gafurov Jul 02 '20 at 17:52