Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #66

Merged
merged 24 commits into from
Nov 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
89117bc
Fixed #42 #43 tests. Removed counter from platform. This shouldn't be…
taidopurason Oct 14, 2020
06c6ba1
Additional checks to #42 #43 #14 #15 tests.
taidopurason Oct 14, 2020
a94cf2d
Removed unnecessary configuration property
taidopurason Oct 14, 2020
bdf5d81
Implemented initial General Templates. Implemented URL Button. Due to…
AndreasTeder Oct 18, 2020
f58b8b5
Added GenericTemplate for easier template creation on the user side, …
Raud0 Oct 18, 2020
b28ba0a
Implemented initial Media and Button Templates, including a easier wa…
AndreasTeder Oct 19, 2020
16c844c
Fixed a typo and standardized the whitespace
AndreasTeder Oct 19, 2020
8a9b7c8
Refactored code. see #21 #58 #33 #56
taidopurason Oct 20, 2020
71855ab
Added postback handling. closes #28
taidopurason Oct 20, 2020
cf582db
Refactored MessengerPlatform and added some basic unit tests.
taidopurason Oct 20, 2020
706af32
worked on sending files see #20 The infrastructure is now there, BUT …
Raud0 Oct 27, 2020
eb97e9b
Uploading file works. see #20
taidopurason Oct 28, 2020
1c0ca59
So I finished the other end of the file-sending, which is sending att…
Raud0 Oct 29, 2020
1081eed
Refactored code. Added returning responses. Currently sendFile doesn'…
taidopurason Oct 29, 2020
17efe06
Updated file uploading and sending. All requests now return responses…
taidopurason Oct 31, 2020
fc2b0ce
Reverted previous file sending implementation
taidopurason Oct 31, 2020
8f78dc1
Merge branch 'upload' into develop
taidopurason Oct 31, 2020
26a5434
Renamed FilePost + some minor changes. see #20
taidopurason Oct 31, 2020
077c125
Readded the DialogFlow fix from the other branch
AndreasTeder Nov 1, 2020
8140b0f
weird that i hadn't pushed these changes to the templates
Raud0 Nov 3, 2020
b4669b5
forgot about tests
Raud0 Nov 3, 2020
236d9e3
Changed the structure of the platform, so it sends out events by defa…
Raud0 Nov 3, 2020
3dbb299
Added docuementation to the bot that examplifies most of our function…
Raud0 Nov 7, 2020
ddbfdef
Added resending message on IOException. This is a quick and dirty sol…
taidopurason Nov 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
package com.xatkit.plugins.messenger.platform;

import com.google.gson.JsonElement;
import com.xatkit.core.XatkitBot;
import com.xatkit.core.platform.RuntimePlatform;
import com.xatkit.core.server.*;
import com.xatkit.execution.StateContext;
import com.xatkit.plugins.messenger.platform.action.*;
import com.xatkit.plugins.messenger.platform.entity.Message;
import com.xatkit.plugins.messenger.platform.entity.Messaging;
import com.xatkit.plugins.messenger.platform.entity.Recipient;
import com.xatkit.plugins.messenger.platform.entity.SenderAction;
import com.xatkit.plugins.messenger.platform.entity.*;
import com.xatkit.plugins.messenger.platform.entity.payloads.AttachmentIdPayload;
import com.xatkit.plugins.messenger.platform.entity.response.ErrorResponse;
import com.xatkit.plugins.messenger.platform.entity.response.Response;
import com.xatkit.plugins.messenger.platform.entity.response.SendResponse;
import com.xatkit.plugins.rest.platform.RestPlatform;
import com.xatkit.plugins.rest.platform.action.JsonRestRequest;
import com.xatkit.plugins.rest.platform.utils.ApiResponse;
import lombok.NonNull;
import fr.inria.atlanmod.commons.log.Log;
import lombok.val;
import lombok.var;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.http.entity.StringEntity;

import java.nio.charset.StandardCharsets;

import static java.util.Objects.requireNonNull;

// TODO: Add javadocs
Expand All @@ -28,69 +36,110 @@ public class MessengerPlatform extends RestPlatform {
private String verifyToken;
private String accessToken;
private String appSecret;
private int messagesSent;
private int messagesSentSuccessfully;

@Override
public void start(@NonNull XatkitBot xatkitBot, @NonNull Configuration configuration) {
verifyToken = requireNonNull(configuration.getString(MessengerUtils.VERIFY_TOKEN_KEY));
accessToken = requireNonNull(configuration.getString(MessengerUtils.ACCESS_TOKEN_KEY));
appSecret = requireNonNull(configuration.getString(MessengerUtils.APP_SECRET_KEY));
messagesSent = 0;
messagesSentSuccessfully = 0;
super.start(xatkitBot, configuration);

xatkitBot.getXatkitServer().registerRestEndpoint(HttpMethod.GET, "/messenger/webhook",
xatkitBot.getXatkitServer().registerRestEndpoint(HttpMethod.GET, MessengerUtils.WEBHOOK_URI,
RestHandlerFactory.createEmptyContentRestHandler((headers, params, content) -> {
val mode = requireNonNull(HttpUtils.getParameterValue("hub.mode", params), "Missing mode");
val token = requireNonNull(HttpUtils.getParameterValue("hub.verify_token", params), "Missing token");
val challenge = requireNonNull(HttpUtils.getParameterValue("hub.challenge", params), "Missing challenge");
if (!mode.equals("subscribe")) {
throw new RestHandlerException(403, "Mode is not 'subscribe'");
throw new RestHandlerException(HttpStatus.SC_FORBIDDEN, "Mode is not 'subscribe'");
}
if (!token.equals(verifyToken)) {
throw new RestHandlerException(403, "Token does not match verify token.");
throw new RestHandlerException(HttpStatus.SC_FORBIDDEN, "Token does not match verify token.");
}
return new StringEntity(challenge, "UTF-8");
return new StringEntity(challenge, StandardCharsets.UTF_8);
}));
}

public void markSeen(@NonNull StateContext context) {
sendAction(context, SenderAction.markSeen);
public Response markSeen(@NonNull StateContext context) {
return sendAction(context, SenderAction.markSeen);
}

public Response sendAction(@NonNull StateContext context, @NonNull SenderAction senderAction) {
val recipientId = MessengerUtils.extractContextId(context.getContextId());
Log.debug("Replying to {0} with a sender_action {1}", recipientId, senderAction.name());
val messaging = new Messaging(new Recipient(recipientId), senderAction);
return reply(new Reply(this, context, messaging));
}

public Response uploadFile(@NonNull StateContext context, File file) {
return excecuteRequest(new FileReply(this, context, file));
}

public Response sendFile(@NonNull StateContext context, @NonNull String attachmentId, @NonNull Attachment.AttachmentType attachmentType) {
val recipientId = MessengerUtils.extractContextId(context.getContextId());
val messaging = new Messaging(
new Recipient(recipientId),
new Message(new Attachment(attachmentType, new AttachmentIdPayload(attachmentId))));
Log.debug("SENDING FILE TO: {0}", recipientId);
return reply(new Reply(this, context, messaging)
);
}

public Response sendFile(@NonNull StateContext context, @NonNull File file) {
var attachmentId = file.getAttachmentId(); //I did not use the custom extractContextId here, so this is a potential error place
if (StringUtils.isEmpty(attachmentId)) {
val response = uploadFile(context, file);
if (!(response instanceof SendResponse)) {
Log.error("Could not upload the file.");
return response;
}
attachmentId = ((SendResponse) response).getAttachmentId();
}

return sendFile(context, attachmentId, file.getAttachment().getType());
}

public void sendAction(@NonNull StateContext context, @NonNull SenderAction senderAction) {
val senderId = context.getContextId();
Log.debug("Replying to {0} with a sender_action {1}", senderId, senderAction.name());
val messaging = new Messaging(new Recipient(senderId), senderAction);
excecuteReply(new Reply(this, context, messaging));
public Response reply(@NonNull StateContext context, @NonNull String text) {
return reply(context, new Message(text));
}

public void reply(@NonNull StateContext context, @NonNull String text) {
reply(context, new Message(text));
public Response reply(@NonNull StateContext context, @NonNull Message message) {
val recipientId = MessengerUtils.extractContextId(context.getContextId());
Log.debug("REPLYING TO: {0}", recipientId);
val messaging = new Messaging(new Recipient(recipientId), message);
return reply(new MessageReply(this, context, messaging));
}

public void reply(@NonNull StateContext context, @NonNull Message message) {
val senderId = context.getContextId();
Log.debug("REPLYING TO: {0}", senderId);
val messaging = new Messaging(new Recipient(senderId), message);
excecuteReply(new MessageReply(
this,
context,
messaging));
private Response reply(@NonNull Reply reply) {
return excecuteRequest(reply);
}

private void excecuteReply(Reply reply) {
messagesSent++;
val result = reply.call().getResult();
private Response excecuteRequest(JsonRestRequest<JsonElement> request) {
val result = request.call().getResult();

if (result instanceof ApiResponse) {
val apiResponse = (ApiResponse<?>) result;
val responseBody = ((JsonElement) apiResponse.getBody()).getAsJsonObject();
val status = apiResponse.getStatus();
if (status < 200 || status > 299) {
Log.error("REPLY RESPONSE STATUS: {0} {1}\n BODY: {2}", apiResponse.getStatus(), apiResponse.getStatusText(), apiResponse.getBody().toString());
if (responseBody.has("error")) {
val error = responseBody.get("error").getAsJsonObject();
val code = error.has("code") ? error.get("code").getAsInt() : null;
val subcode = error.has("error_subcode") ? error.get("error_subcode").getAsInt() : null;
val fbtraceId = error.has("fbtrace_id") ? error.get("fbtrace_id").getAsString() : null;
val message = error.has("message") ? error.get("message").getAsString() : null;
return new ErrorResponse(status, code, subcode, fbtraceId, message);
}
return null;
}
Log.debug("REPLY RESPONSE STATUS: {0} {1}\n BODY: {2}", apiResponse.getStatus(), apiResponse.getStatusText(), apiResponse.getBody().toString());
if (apiResponse.getStatus() == 200) messagesSentSuccessfully++;
} else {
Log.debug("Unexpected reply result: {0}", result);
val recipientId = responseBody.has("recipient_id") ? responseBody.get("recipient_id").getAsString() : null;
val attachmentId = responseBody.has("attachment_id") ? responseBody.get("attachment_id").getAsString() : null;
val messageId = responseBody.has("message_id") ? responseBody.get("message_id").getAsString() : null;
return new SendResponse(status, recipientId, messageId, attachmentId);
}
Log.error("Unexpected reply result: {0}", result);
return null;
}

public String getAppSecret() {
Expand All @@ -100,6 +149,4 @@ public String getAppSecret() {
public String getAccessToken() {
return accessToken;
}

public int getMessagesSent() { return messagesSent; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class MessengerUtils {
public static final String VERIFY_TOKEN_KEY = MESSENGER_CONTEXT + "verify_token";
public static final String ACCESS_TOKEN_KEY = MESSENGER_CONTEXT + "access_token";
public static final String APP_SECRET_KEY = MESSENGER_CONTEXT + "app_secret";
public static final String INTENT_FROM_POSTBACK = MESSENGER_CONTEXT + "intent_from_postback";
public static final String INTENT_FROM_REACTION = MESSENGER_CONTEXT + "intent_from_reaction";
public static final String HANDLE_REACTIONS_KEY = MESSENGER_CONTEXT + "handle_reactions";
public static final String HANDLE_DELIVERIES_KEY = MESSENGER_CONTEXT + "handle_deliveries";
public static final String HANDLE_READ_KEY = MESSENGER_CONTEXT + "handle_read";
Expand All @@ -21,10 +23,18 @@ public class MessengerUtils {
public static final String MESSAGE_ID_KEY = "mid";
public static final String MESSAGE_IDS_KEY = "mids";
public static final String WATERMARK_KEY = "watermark";
public static final String POSTBACK_TITLE_KEY = "title";
public static final String POSTBACK_PAYLOAD_KEY = "payload";
public static final String POSTBACK_REFFERAL_REF_KEY = "refferal.ref";
public static final String POSTBACK_REFFERAL_SOURCE_KEY = "refferal.source";
public static final String POSTBACK_REFFERAL_TYPE_KEY = "refferal.type";
public static final String EMOJI_KEY = "emoji";
public static final String REACTION_KEY = "reaction";
public static final String WEBHOOK_URI = "/messenger/webhook";
public static final String SEND_API_URL = "https://graph.facebook.com/v8.0/me/messages";
public static final String ATTACHMENT_UPLOAD_API_URL = "https://graph.facebook.com/v8.0/me/message_attachments";
public static final String USE_REACTION_TEXT = "use_reaction_text";
public static final String USE_TITLE_TEXT = "use_title_text";


public static String calculateRFC2104HMAC(String data, String key) throws NoSuchAlgorithmException, InvalidKeyException {
Expand All @@ -33,4 +43,11 @@ public static String calculateRFC2104HMAC(String data, String key) throws NoSuch
mac.init(signingKey);
return new String(Hex.encodeHex(mac.doFinal(data.getBytes())));
}

//If one adds dialogflow to the bot, dialogflow will add some prefix to the context id
public static String extractContextId(String contextIdWithDialogflow) {
String[] senderIdSplit = contextIdWithDialogflow.split("/"); //This removes the useless part and leaves only the id
return senderIdSplit[senderIdSplit.length - 1]; //A better solution is more than welcome

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.xatkit.plugins.messenger.platform.action;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.request.HttpRequest;
import com.xatkit.execution.StateContext;
import com.xatkit.plugins.messenger.platform.MessengerPlatform;
import com.xatkit.plugins.messenger.platform.MessengerUtils;
import com.xatkit.plugins.messenger.platform.entity.File;
import com.xatkit.plugins.messenger.platform.entity.Message;
import com.xatkit.plugins.rest.platform.action.JsonRestRequest;
import lombok.val;
import org.apache.http.HttpHeaders;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class FileReply extends JsonRestRequest<JsonElement> {
private static final Gson gson = new Gson();
private final File file;

/**
* Constructs a POST Json request with form data parameters
*
* @param platform the {@link MessengerPlatform} containing this action
* @param context the {@link StateContext} associated to this action
* @param file the information related to the file to be sent;
*/
public FileReply(MessengerPlatform platform, StateContext context, File file) {
super(platform, context, MethodKind.POST, MessengerUtils.ATTACHMENT_UPLOAD_API_URL, null, null, null, generateHeaders(platform), generateParams(file));
this.file = file;
}

private static Map<String, String> generateHeaders(MessengerPlatform platform) {
val headers = new HashMap<String, String>();
headers.put(HttpHeaders.AUTHORIZATION, "Bearer " + platform.getAccessToken());
return headers;
}

private static Map<String, Object> generateParams(File file) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("message", gson.toJsonTree(new Message(file.getAttachment())));
return params;
}

protected HttpRequest buildRequest() {
return Unirest
.post(this.restEndpoint)
.headers(this.headers)
.fields(this.formParameters)
.field("filedata", file.getFile(), file.getMimeType())
.getHttpRequest();
}
}
Loading