From 96d82470a39c8d64caf3871898c3afb947932c7a Mon Sep 17 00:00:00 2001 From: colin <98445953+colinmccloskey@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:02:22 -0500 Subject: [PATCH] Adding Basic IG Message Handler --- .gitignore | 1 + .../meta/cp4m/message/FBMessageHandler.java | 2 +- .../meta/cp4m/message/FBMessengerConfig.java | 8 +- .../meta/cp4m/message/IGMessageHandler.java | 122 ++++++++++++++++++ .../meta/cp4m/message/IGMessengerConfig.java | 30 +++++ 5 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/meta/cp4m/message/IGMessageHandler.java create mode 100644 src/main/java/com/meta/cp4m/message/IGMessengerConfig.java diff --git a/.gitignore b/.gitignore index 04a5cb4..e425386 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ build/ ### custom ### src/main/java/com/meta/cp4m/mccloskeytest.java /dependency-reduced-pom.xml +CP4M.toml diff --git a/src/main/java/com/meta/cp4m/message/FBMessageHandler.java b/src/main/java/com/meta/cp4m/message/FBMessageHandler.java index 2f869f0..5f630ac 100644 --- a/src/main/java/com/meta/cp4m/message/FBMessageHandler.java +++ b/src/main/java/com/meta/cp4m/message/FBMessageHandler.java @@ -88,7 +88,7 @@ public List processRequest(Context ctx) { } catch (JsonProcessingException | NullPointerException e) { LOGGER .atWarn() - .setMessage("Unable to parse message form Meta webhook") + .setMessage("Unable to parse message from Meta webhook") .setCause(e) .addKeyValue("body", ctx.body()) .addKeyValue("headers", ctx.headerMap()) diff --git a/src/main/java/com/meta/cp4m/message/FBMessengerConfig.java b/src/main/java/com/meta/cp4m/message/FBMessengerConfig.java index e790c44..12de132 100644 --- a/src/main/java/com/meta/cp4m/message/FBMessengerConfig.java +++ b/src/main/java/com/meta/cp4m/message/FBMessengerConfig.java @@ -19,7 +19,7 @@ public class FBMessengerConfig implements HandlerConfig { private final String appSecret; private final String pageAccessToken; - private FBMessengerConfig( + public FBMessengerConfig( @JsonProperty("name") String name, @JsonProperty("verify_token") String verifyToken, @JsonProperty("app_secret") String appSecret, @@ -39,6 +39,7 @@ private FBMessengerConfig( this.pageAccessToken = pageAccessToken; } + public static FBMessengerConfig of(String verifyToken, String appSecret, String pageAccessToken) { // human readability of the name only matters when it's coming from a config return new FBMessengerConfig( @@ -65,5 +66,6 @@ public FBMessageHandler toMessageHandler() { public String pageAccessToken() { return pageAccessToken; - } -} + } // EAAICn2ZAELtYBO2pwykJbOqqEra8msZAjp98AZCFDr5raSYQBgkG5RVq40N0txHFnxMuPdmZArB3gY2R6YwQEk8Gk1LDeO98DZCZCmbyLg6S6conUSBdRgGTz99fBIPtn41ZAhSulBhQ5cg5Fu3Tw1zjlrnUZCNp2sBMuTZAsp1C3jfrIEMbMvZBR9ml7iksNbRZCFrEjvUlWeyZAPJsAiPdQyeRM00sqCDqSya2hB8R7QIc +} // EAAICn2ZAELtYBO3tZB7c3sObbkw6aHxrkCuZA7k2ECfZAunCJVkrAmgbvlDSAEMRjLl4FDPOiuBkZCkGYayMZBJrGCPJDkgdBqcf21StChoE4uOexspURdDiZCPTtdcxXGljsT4Xh0uieoM19M2Evm7O5LWjKPluRoGwUZBZC4lX4iF3TEw8JCa9npkIRnV823ZCvOyPP9QBo2uGScZCX1ZA + diff --git a/src/main/java/com/meta/cp4m/message/IGMessageHandler.java b/src/main/java/com/meta/cp4m/message/IGMessageHandler.java new file mode 100644 index 0000000..a555d13 --- /dev/null +++ b/src/main/java/com/meta/cp4m/message/IGMessageHandler.java @@ -0,0 +1,122 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.message; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.meta.cp4m.Identifier; +import io.javalin.http.Context; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class IGMessageHandler extends FBMessageHandler{ + + private final String verifyToken; + private final String appSecret; + + private final String accessToken; + private static final JsonMapper MAPPER = new JsonMapper(); + private static final Logger LOGGER = LoggerFactory.getLogger(IGMessageHandler.class); + private final Deduplicator messageDeduplicator = new Deduplicator<>(10_000); + + public IGMessageHandler(String verifyToken, String pageAccessToken, String appSecret) { + super(verifyToken, appSecret, pageAccessToken); + this.verifyToken = verifyToken; + this.appSecret = appSecret; + this.accessToken = pageAccessToken; + + } + + IGMessageHandler(IGMessengerConfig config) { + super(config); + this.verifyToken = config.verifyToken(); + this.appSecret = config.appSecret(); + this.accessToken = config.pageAccessToken(); + + } + + private List postHandler(Context ctx) throws JsonProcessingException { + MetaHandlerUtils.postHeaderValidator(ctx, appSecret); + + String bodyString = ctx.body(); + JsonNode body = MAPPER.readTree(bodyString); + String object = body.get("object").textValue(); + if (!object.equals("instagram")) { + LOGGER + .atWarn() + .setMessage("received body that has a different value for 'object' than 'page'") + .addKeyValue("body", bodyString) + .log(); + return Collections.emptyList(); + } + // TODO: need better validation + JsonNode entries = body.get("entry"); + ArrayList output = new ArrayList<>(); + for (JsonNode entry : entries) { + @Nullable JsonNode messaging = entry.get("messaging"); + if (messaging == null) { + continue; + } + for (JsonNode message : messaging) { + @Nullable JsonNode messageObject = message.get("message"); + + if (messageObject != null) { + if(messageObject.get("is_echo").asText().equals("true")){ + return Collections.emptyList(); + } + + Identifier senderId = Identifier.from(message.get("sender").get("id").asLong()); + Identifier recipientId = Identifier.from(message.get("recipient").get("id").asLong()); + Instant timestamp = Instant.ofEpochMilli(message.get("timestamp").asLong()); + + // https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messages + Identifier messageId = Identifier.from(messageObject.get("mid").textValue()); + if (messageDeduplicator.addAndGetIsDuplicate(messageId)) { + continue; + } + + @Nullable JsonNode textObject = messageObject.get("text"); + if (textObject != null && textObject.isTextual()) { + FBMessage m = + new FBMessage( + timestamp, + messageId, + senderId, + recipientId, + textObject.textValue(), + Message.Role.USER); + output.add(m); + } else { + LOGGER + .atWarn() + .setMessage("received message without text, unable to handle this") + .addKeyValue("body", bodyString) + .log(); + } + } else { + LOGGER + .atWarn() + .setMessage( + "received a message without a 'message' key, unable to handle this message type") + .addKeyValue("body", bodyString) + .log(); + } + } + } + + return output; + } +} diff --git a/src/main/java/com/meta/cp4m/message/IGMessengerConfig.java b/src/main/java/com/meta/cp4m/message/IGMessengerConfig.java new file mode 100644 index 0000000..f2d72e9 --- /dev/null +++ b/src/main/java/com/meta/cp4m/message/IGMessengerConfig.java @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.message; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; + +import java.util.UUID; + +public class IGMessengerConfig extends FBMessengerConfig { + + IGMessengerConfig( @JsonProperty("name") String name, + @JsonProperty("verify_token") String verifyToken, + @JsonProperty("app_secret") String appSecret, + @JsonProperty("page_access_token") String pageAccessToken){ + super(name, verifyToken, appSecret, pageAccessToken); + } + + @Override + public IGMessageHandler toMessageHandler() { + return new IGMessageHandler(this); + } + +}