-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from facebookincubator/hunter/WA
Introduce Whatsapp Handler
- Loading branch information
Showing
47 changed files
with
2,881 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
src/main/java/com/meta/chatbridge/message/MetaHandlerUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* | ||
* 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.chatbridge.message; | ||
|
||
import io.javalin.http.Context; | ||
import io.javalin.http.ForbiddenResponse; | ||
import java.nio.charset.StandardCharsets; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import javax.crypto.Mac; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import org.apache.hc.client5.http.utils.Hex; | ||
|
||
class MetaHandlerUtils { | ||
static void subscriptionVerification(Context ctx, String verifyToken) { | ||
ctx.queryParamAsClass("hub.mode", String.class) | ||
.check(v -> v.equals("subscribe"), "hub.mode must be subscribe"); | ||
ctx.queryParamAsClass("hub.verify_token", String.class) | ||
.check(v -> v.equals(verifyToken), "verify_token is incorrect"); | ||
int challenge = ctx.queryParamAsClass("hub.challenge", int.class).get(); | ||
ctx.result(String.valueOf(challenge)); | ||
} | ||
|
||
static String hmac(String body, String appSecret) { | ||
Mac sha256HMAC; | ||
SecretKeySpec secretKey; | ||
try { | ||
sha256HMAC = Mac.getInstance("HmacSHA256"); | ||
secretKey = new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); | ||
sha256HMAC.init(secretKey); | ||
} catch (NoSuchAlgorithmException | InvalidKeyException e) { | ||
throw new RuntimeException(e); // Algorithms guaranteed to exist | ||
} | ||
return Hex.encodeHexString(sha256HMAC.doFinal(body.getBytes(StandardCharsets.UTF_8))); | ||
} | ||
|
||
/** | ||
* Use the appSecret to validate that the value set in X-Hub-Signature-256 is correct. Throws a | ||
* Javalin {@link io.javalin.validation.ValidationError} if the header is not valid | ||
* | ||
* <p><a | ||
* href="https://developers.facebook.com/docs/messenger-platform/reference/webhook-events">messenger | ||
* documentation on this process</a> | ||
* | ||
* @param ctx Javalin context corresponding to this post request | ||
* @param appSecret app secret corresponding to this app | ||
*/ | ||
static void postHeaderValidator(Context ctx, String appSecret) { | ||
ctx.headerAsClass("X-Hub-Signature-256", String.class) | ||
.check( | ||
h -> { | ||
String[] hashParts = h.strip().split("="); | ||
if (hashParts.length != 2) { | ||
return false; | ||
} | ||
String calculatedHmac = hmac(ctx.body(), appSecret); | ||
return hashParts[1].equals(calculatedHmac); | ||
}, | ||
"X-Hub-Signature-256 could not be validated") | ||
.getOrThrow(ignored -> new ForbiddenResponse("X-Hub-Signature-256 could not be validated")); | ||
} | ||
} |
106 changes: 106 additions & 0 deletions
106
src/main/java/com/meta/chatbridge/message/TextChunker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* | ||
* 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.chatbridge.message; | ||
|
||
import com.google.common.base.Preconditions; | ||
import com.google.common.collect.ImmutableList; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.regex.Pattern; | ||
import java.util.stream.Stream; | ||
import org.checkerframework.common.reflection.qual.NewInstance; | ||
|
||
/** | ||
* Splits texts into 'chunks' of test with less than or equal to the requested number of characters. | ||
* The text block is split first on the regex separators, in the defined order, each separator is | ||
* only used to split the chunk if the chunk exceeds the defined maximum numbers of characters per | ||
* chunk. If all separators are exhausted and a chunk still exceeds the maximum number of characters | ||
* allowed it is split without regard for any separator into chunks less than or equal to the | ||
* maximum character size. | ||
*/ | ||
public class TextChunker { | ||
|
||
private final int maxCharsPerChunk; | ||
private final List<Pattern> regex; | ||
|
||
private TextChunker(int maxCharsPerChunk, List<Pattern> regex) { | ||
Preconditions.checkArgument(maxCharsPerChunk > 0); | ||
this.maxCharsPerChunk = maxCharsPerChunk; | ||
this.regex = regex; | ||
} | ||
|
||
public static TextChunker from(int maxCharsPerChunk) { | ||
return new TextChunker(maxCharsPerChunk, Collections.emptyList()); | ||
} | ||
|
||
/** | ||
* A separator with sensible separators, the separators are applied in this order. | ||
* | ||
* <pre>{@code | ||
* from(maxCharsPerChunk) | ||
* .withSeparator("\n\n\n+") | ||
* .withSeparator("\n\n") | ||
* .withSeparator("\n") | ||
* .withSeparator("\\. +") // any period, including the following whitespaces | ||
* .withSeparator("\s\s+") // any set of two or more whitespace characters | ||
* .withSeparator(" +"); // any set of one or more whitespace spaces | ||
* }</pre> | ||
* | ||
* @return stream of text chunks less than or equal to the maxCharsPerChunk | ||
*/ | ||
public static TextChunker standard(int maxCharsPerChunk) { | ||
return from(maxCharsPerChunk) | ||
.withSeparator("\n\n\n+") | ||
.withSeparator("\n\n") | ||
.withSeparator("\n") | ||
.withSeparator("\\. +") // any period, including the following whitespaces | ||
.withSeparator("\s\s+") // any set of two or more whitespace characters | ||
.withSeparator(" +"); // any set of one or more whitespace spaces | ||
} | ||
|
||
public @NewInstance TextChunker withSeparator(String regex) { | ||
return withSeparator(Pattern.compile(regex)); | ||
} | ||
|
||
public @NewInstance TextChunker withSeparator(Pattern regex) { | ||
ImmutableList<Pattern> newRegex = | ||
ImmutableList.<Pattern>builder().addAll(this.regex).add(regex).build(); | ||
|
||
return new TextChunker(maxCharsPerChunk, newRegex); | ||
} | ||
|
||
private Stream<String> breaker(String text) { | ||
ArrayList<String> out = new ArrayList<>((text.length() / maxCharsPerChunk) + 1); | ||
while (text.length() > maxCharsPerChunk) { | ||
out.add(text.substring(0, maxCharsPerChunk)); | ||
text = text.substring(maxCharsPerChunk); | ||
} | ||
out.add(text); | ||
return out.stream(); | ||
} | ||
|
||
private Stream<String> chunker(String text, Pattern regex) { | ||
if (text.length() > maxCharsPerChunk) { | ||
return Arrays.stream(regex.split(text, 0)); | ||
} | ||
return Stream.of(text); | ||
} | ||
|
||
public Stream<String> chunks(String text) { | ||
Stream<String> stream = Stream.of(text.strip()); | ||
for (Pattern r : regex) { | ||
stream = stream.flatMap(t -> chunker(t, r)); | ||
} | ||
stream = stream.flatMap(this::breaker); | ||
|
||
return stream; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* | ||
* 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.chatbridge.message; | ||
|
||
import com.meta.chatbridge.Identifier; | ||
import java.time.Instant; | ||
|
||
public record WAMessage( | ||
Instant timestamp, | ||
Identifier instanceId, | ||
Identifier senderId, | ||
Identifier recipientId, | ||
String message, | ||
Role role) | ||
implements Message {} |
Oops, something went wrong.