-
Notifications
You must be signed in to change notification settings - Fork 125
Feat: Implement whatsapp with vonage dart #246
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
Merged
loks0n
merged 6 commits into
appwrite:main
from
tejas-raskar:feat-implement-whatsapp-with-vonage-dart
Oct 31, 2023
Merged
Changes from 3 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
0a0a197
Added main.dart
tejas-raskar 29e486d
Updated Readme
tejas-raskar 793106b
made requested changes
tejas-raskar e9626be
removed empty lines
tejas-raskar 370ce34
used JSON for error handling
tejas-raskar 35c0268
updated comments
tejas-raskar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,27 @@ | ||
| # See https://www.dartlang.org/guides/libraries/private-files | ||
|
|
||
| # Files and directories created by pub | ||
| .dart_tool/ | ||
| .packages | ||
| build/ | ||
| # If you're building an application, you may want to check-in your pubspec.lock | ||
| pubspec.lock | ||
|
|
||
| # Directory created by dartdoc | ||
| # If you don't generate documentation locally you can remove this line. | ||
| doc/api/ | ||
|
|
||
| # dotenv environment variables file | ||
| .env* | ||
|
|
||
| # Avoid committing generated Javascript files: | ||
| *.dart.js | ||
| *.info.json # Produced by the --dump-info flag. | ||
| *.js # When generated by dart2js. Don't specify *.js if your | ||
| # project includes source files written in JavaScript. | ||
| *.js_ | ||
| *.js.deps | ||
| *.js.map | ||
|
|
||
| .flutter-plugins | ||
| .flutter-plugins-dependencies |
This file contains hidden or 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,104 @@ | ||
| # 💬 Dart WhatsApp Bot with Vonage Function | ||
|
|
||
| Simple bot to answer WhatsApp messages. | ||
|
|
||
| ## 🧰 Usage | ||
|
|
||
| ### GET / | ||
|
|
||
| HTML form for interacting with the function. | ||
|
|
||
| ### POST / | ||
|
|
||
| Receives a message, validates its signature, and sends a response back to the sender. | ||
|
|
||
| **Parameters** | ||
|
|
||
| | Name | Description | Location | Type | Sample Value | | ||
| | ------------- | ---------------------------------- | -------- | ------------------- | -------------------- | | ||
| | Content-Type | Content type of the request | Header | `application/json ` | N/A | | ||
| | Authorization | Webhook signature for verification | Header | String | `Bearer <signature>` | | ||
| | from | Sender's identifier. | Body | String | `12345` | | ||
| | text | Text content of the message. | Body | String | `Hello!` | | ||
|
|
||
| > All parameters are coming from Vonage webhook. Exact documentation can be found in [Vonage API Docs](https://developer.vonage.com/en/api/messages-olympus#inbound-message). | ||
|
|
||
| **Response** | ||
|
|
||
| Sample `200` Response: | ||
|
|
||
| ```json | ||
| { | ||
| "ok": true | ||
| } | ||
| ``` | ||
|
|
||
| Sample `400` Response: | ||
|
|
||
| ```json | ||
| { | ||
| "ok": false, | ||
| "error": "Missing required parameter: from" | ||
| } | ||
| ``` | ||
|
|
||
| Sample `401` Response: | ||
|
|
||
| ```json | ||
| { | ||
| "ok": false, | ||
| "error": "Payload hash mismatch." | ||
| } | ||
| ``` | ||
|
|
||
| ## ⚙️ Configuration | ||
|
|
||
| | Setting | Value | | ||
| | ----------------- | ------------- | | ||
| | Runtime | Dart (3.1) | | ||
| | Entrypoint | `lib/main.dart` | | ||
| | Build Commands | `pub get` | | ||
| | Permissions | `any` | | ||
| | Timeout (Seconds) | 15 | | ||
|
|
||
| ## 🔒 Environment Variables | ||
|
|
||
| ### VONAGE_API_KEY | ||
|
|
||
| API Key to use the Vonage API. | ||
|
|
||
| | Question | Answer | | ||
| | ------------- | ------------------------------------------------------------------------------------------------------------------------ | | ||
| | Required | Yes | | ||
| | Sample Value | `62...97` | | ||
| | Documentation | [Vonage: Q&A](https://api.support.vonage.com/hc/en-us/articles/204014493-How-do-I-find-my-Voice-API-key-and-API-secret-) | | ||
|
|
||
| ### VONAGE_API_SECRET | ||
|
|
||
| Secret to use the Vonage API. | ||
|
|
||
| | Question | Answer | | ||
| | ------------- | ------------------------------------------------------------------------------------------------------------------------ | | ||
| | Required | Yes | | ||
| | Sample Value | `Zjc...5PH` | | ||
| | Documentation | [Vonage: Q&A](https://api.support.vonage.com/hc/en-us/articles/204014493-How-do-I-find-my-Voice-API-key-and-API-secret-) | | ||
|
|
||
| ### VONAGE_API_SIGNATURE_SECRET | ||
|
|
||
| Secret to verify the webhooks sent by Vonage. | ||
|
|
||
| | Question | Answer | | ||
| | ------------- | -------------------------------------------------------------------------------------------------------------- | | ||
| | Required | Yes | | ||
| | Sample Value | `NXOi3...IBHDa` | | ||
| | Documentation | [Vonage: Webhooks](https://developer.vonage.com/en/getting-started/concepts/webhooks#decoding-signed-webhooks) | | ||
|
|
||
| ### VONAGE_WHATSAPP_NUMBER | ||
|
|
||
| Vonage WhatsApp number to send messages from. | ||
|
|
||
| | Question | Answer | | ||
| | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | | ||
| | Required | Yes | | ||
| | Sample Value | `+14000000102` | | ||
| | Documentation | [Vonage: Q&A](https://api.support.vonage.com/hc/en-us/articles/4431993282580-Where-do-I-find-my-WhatsApp-Number-Certificate-) | |
This file contains hidden or 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 @@ | ||
| include: package:lints/recommended.yaml |
This file contains hidden or 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,65 @@ | ||
| import 'dart:convert'; | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:crypto/crypto.dart'; | ||
| import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; | ||
| import 'package:whatsapp_with_vonage/utils.dart'; | ||
| import 'package:http/http.dart' as http; | ||
|
|
||
| Future<dynamic> main(final context) async { | ||
| throwIfMissing(Platform.environment, [ | ||
| 'VONAGE_API_KEY', | ||
| 'VONAGE_API_SECRET', | ||
| 'VONAGE_API_SIGNATURE_SECRET', | ||
| 'VONAGE_WHATSAPP_NUMBER' | ||
| ]); | ||
|
|
||
| if (context.req.method == 'GET') { | ||
| return context.res.send(getStaticFile('index.html'), 200, | ||
| {'Content-Type': 'text/html; character=utf-8'}); | ||
tejas-raskar marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| final token = context.req.headers['authorization'].split(' ')[1]; | ||
|
|
||
| final apiSecretKey = Platform.environment['VONAGE_API_SIGNATURE_SECRET']; | ||
tejas-raskar marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (token == null) { | ||
| return context.res.send('Unauthorized', 401); | ||
tejas-raskar marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| if (context.req.body['from'] == null || context.req.body['text'] == null) { | ||
| return context.res.send("Missing required fields.", 400); | ||
| } | ||
|
|
||
| final jwt = JWT.verify(token, SecretKey(apiSecretKey!)); | ||
|
|
||
| if (jwt.payload['payload_hash'] == null) { | ||
| return context.res.send('Missing payload hash', 400); | ||
| } | ||
|
|
||
| final payloadHash = sha256.convert(utf8.encode(context.req.bodyRaw)).toString(); | ||
|
|
||
| if (jwt.payload['payload_hash'] != payloadHash) { | ||
| return context.res.send('Payload Hash Mismatch', 401); | ||
| } | ||
|
|
||
| final basicAuthToken = base64Encode(utf8.encode( | ||
| '${Platform.environment['VONAGE_API_KEY']}:${Platform.environment['VONAGE_API_SECRET']}')); | ||
|
|
||
| final response = await http.post( | ||
| Uri.parse('https://messages-sandbox.nexmo.com/v1/messages'), | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'Authorization': 'Basic $basicAuthToken', | ||
| }, | ||
| body: jsonEncode({ | ||
| 'from': Platform.environment['VONAGE_WHATSAPP_NUMBER'], | ||
| 'to': context.req.body['from'], | ||
| 'message_type': 'text', | ||
| 'text': 'Hi there! You sent me: ${context.req.body['text']}', | ||
| 'channel': 'whatsapp', | ||
| }), | ||
| ); | ||
|
|
||
| return context.res.send("Success", 200); | ||
| } | ||
This file contains hidden or 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,33 @@ | ||
| import 'dart:io'; | ||
| import 'package:path/path.dart' as p; | ||
|
|
||
| final staticFolder = p.join(p.dirname(Platform.script.toFilePath()), '../static'); | ||
|
|
||
| /// Throws an error if any of the keys are missing from the object | ||
| /// Parameters: | ||
| /// obj - The object to check | ||
| /// keys - The list of keys to check for | ||
| /// throws Exception | ||
tejas-raskar marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
tejas-raskar marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| void throwIfMissing(Map<String, String> obj, List<String> keys) { | ||
| final missing = <String>[]; | ||
| for (final key in keys) { | ||
| if (!obj.containsKey(key) || obj[key] == null) { | ||
| missing.add(key); | ||
| } | ||
| } | ||
|
|
||
| if (missing.isNotEmpty) { | ||
| throw StateError('Missing environment variables: ${missing.join(', ')}'); | ||
| } | ||
| } | ||
|
|
||
| /// Returns the contents of a file in the static folder | ||
| /// Parameters: | ||
| /// fileName - The name of the file to read | ||
| /// returns Contents of static/{fileName} | ||
tejas-raskar marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
tejas-raskar marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| String getStaticFile(String fileName) { | ||
| final file = File(p.join(staticFolder, fileName)); | ||
| return file.readAsStringSync(); | ||
| } | ||
This file contains hidden or 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,16 @@ | ||
| name: whatsapp_with_vonage | ||
| version: 1.0.0 | ||
|
|
||
| environment: | ||
| sdk: ^2.17.0 | ||
|
|
||
| dependencies: | ||
| crypto: ^3.0.2 | ||
| http: ^0.13.5 | ||
| path: ^1.8.3 | ||
| convert: ^3.1.0 | ||
| dart_jsonwebtoken: ^2.12.0 | ||
|
|
||
|
|
||
| dev_dependencies: | ||
| lints: ^2.0.0 |
This file contains hidden or 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,34 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>WhatsApp Bot with Vonage</title> | ||
|
|
||
| <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" /> | ||
| <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" /> | ||
| </head> | ||
| <body> | ||
| <main class="main-content"> | ||
| <div class="top-cover u-padding-block-end-56"> | ||
| <div class="container"> | ||
| <div | ||
| class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16" | ||
| > | ||
| <h1 class="heading-level-1">WhatsApp Bot with Vonage</h1> | ||
| <code class="u-un-break-text"></code> | ||
| </div> | ||
| <p | ||
| class="body-text-1 u-normal u-margin-block-start-8" | ||
| style="max-width: 50rem" | ||
| > | ||
| This function listens to incoming webhooks from Vonage regarding | ||
| WhatsApp messages, and responds to them. To use the function, send | ||
| message to the WhatsApp user provided by Vonage. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </main> | ||
| </body> | ||
| </html> |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.