Writing Messenger bot in Java with Spring Framework


Previously I wrote about setting Messenger bot environment on own VPS. Today I’m writing about setting basic bot with Java and Spring Framework. To do so I’m needed:

Aftere setting up basic Facebook page and creation of Facebook app, I had  to select Messenger product, which will be used with my application. After that there is no much to do. Wee need to get:

  • Page access token, which is needed to send message
  • Set webhook for Messenger to receive messages
  • Set submissions for: messaging and messaging subscriptions

First submission is neeed to reply on messages, which is possible only up to 24h after receiving one. Second submission is needed to reply after 24h and reply periodically. But let’s do some coding first.

I need web controller for receiving messages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Controller
@RequestMapping("/webhook")
public class WebhookController {
    private final static String TOKEN = "SUPER_SECRET_TOKEN";
 
    @GetMapping
    public @ResponseBody String getHook(
            @RequestParam("hub.mode") String mode,
            @RequestParam("hub.verify_token") String token,
            @RequestParam("hub.challenge") String challenge,
            HttpServletResponse response) {
        if (TOKEN.equals(token) && "subscribe".equals(mode)) {
            response.setStatus(HttpServletResponse.SC_OK);
            return challenge;
        } else {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return "";
        }
    }
}

Main class

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Messenger {
 
    public static void main(String... args) throws Exception {
        SpringApplication.run(Messenger.class, args);
    }
 
}

And application.properties file in resources directory

1
2
3
4
5
6
7
server.port=4321
server.ssl.key-store=classpath:keystore.jks
server.ssl.keyStoreType=JKS
server.ssl.key-store-password=secret
server.ssl.key-password=secret
server.ssl.keyAlias=tomcat
logging.level.root=DEBUG

After building project and running it on VPS, comes time to set webhook in Facebook app configuration

And create page token to return messages

Now when hook is ready, there is time to create code for receiving messages in WebhookControlelr class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@PostMapping
    public @ResponseBody
    String onEvent(@RequestBody Event event, HttpServletResponse response) {
        if ("page".equals(event.getObject())) {
            messageFromEvent(event).ifPresent(m -> 
                messengerController.sendMessage(Message.of(m.getSender().getId(), reverse(m.getMessage().getText()))));
            response.setStatus(HttpStatus.OK.value());
            return "EVENT_RECEIVED";
        }
 
        response.setStatus(HttpStatus.NOT_FOUND.value());
        return "";
    }
 
 
    private static Optional<Message> messageFromEvent(Event event) {
        Message message = null;
        List<Entry> entry = event.getEntry();
 
        if (isNonEmpty(entry)) {
            List<Message> messaging = entry.get(0).getMessaging();
            if (isNonEmpty(messaging)) {
                message = messaging.get(0);
            }
        }
 
        return Optional.of(message);
    }
 
    private static boolean isNonEmpty(List list) {
        return list != null && list.size() > 0;
    }
 
    private static String reverse(String in)  {
        byte[] buf = in.getBytes();
        int pivot = buf.length / 2;
        int len = buf.length - 1;
        for (int i = 0; i < pivot; i++) {
            buf[i] ^= buf[len-i];
            buf[len-i] ^= buf[i];
            buf[i] ^= buf[len-i];
        }
        return new String(buf);
    }

Well, that escalated quickly. Let me explain code above. Facebook sends POST request to mine webhook endpoint, which is deserialized as an Event class object, which JSON representation looks like that

1
{"object":"page","entry":[{"id":"1774684282840000","time":1515003458200,"messaging":[{"sender":{"id":"6515631598500000"},"recipient":{"id":"17746742828000"},"timestamp":1515003457419,"message":{"mid":"mid.$cAAYU8xm2sYBm7oN3i1gvT4v","seq":1123008,"text":"hello bot"}}]}]}]

I needed to create POJO classes to handle all parts of message. Here what’s interesting for us, is sender->id and message->text. Rest can be easily ignored. Here I used messengerController bean to handle reply containing reversed message (test on ASCII chars only plx or it fails :<). There is also a contract with Facebook, that after properly receiving message, application should return HTTP status 200. To send message back to Facebook I used retrofit2 library again. Simple message must only contain recipient and text components.

1
{"recipient":{"id":"17746742828000"},"message":{"text":"hello human"}}

Message sender code looks like below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface MessengerService {
    @POST("/v2.6/me/messages")
    Call<Map<String, String>> sendMessage(@Query("access_token") String token, @Body Message message);
}
 
@Component
public class MessengerController {
    private final static String TOKEN = "PAGE_TOKEN_HERE";
    private final static Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://graph.facebook.com/")
            .client(new OkHttpClient())
            .addConverterFactory(JacksonConverterFactory.create())
            .build();
 
    public void sendMessage(Message message) {
        MessengerService service = retrofit.create(MessengerService.class);
 
        try {
            service.sendMessage(TOKEN, message).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

And that’s it. You can test your bot, by writing private message to facebook page connected with bot application as long as you are page / app admin. After that, when everything works properly, you apply for review and describe your bot commands. Later on one of Facebook non technical (!!!) employees will send described commands and some random ones, and if everything will work properly, your bot will be accepted and available for public.

Full project is available at https://github.com/felix-catus/simple-messenger-bot
Please put your keystore in resources directory. If you don’t have one yet, you can learn how to get it here.

Leave a Reply

Your email address will not be published. Required fields are marked *