-
Couldn't load subscription status.
- Fork 121
Feat 3959 write sendmessage function using java #146
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
Open
zvikfir
wants to merge
2
commits into
open-runtimes:main
Choose a base branch
from
zvikfir:feat-3959-write-sendmessage-function-using-java
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
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,174 @@ | ||
| import com.google.gson.Gson; | ||
|
|
||
| import java.io.IOException; | ||
| import java.net.URI; | ||
| import java.net.URLEncoder; | ||
| import java.net.http.HttpClient; | ||
| import java.net.http.HttpRequest; | ||
| import java.net.http.HttpResponse; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.Base64; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.function.BiConsumer; | ||
| import io.github.redouane59.twitter.TwitterClient; | ||
| import io.github.redouane59.twitter.signature.TwitterCredentials; | ||
|
|
||
| private static class MessageSendingFailedException extends RuntimeException { | ||
| public MessageSendingFailedException(String message) { | ||
| super(message); | ||
| } | ||
|
|
||
| public MessageSendingFailedException(String message, Throwable cause) { | ||
| super(message, cause); | ||
| } | ||
| } | ||
|
|
||
| public RuntimeResponse main(RuntimeRequest req, RuntimeResponse res) { | ||
| Map<String, BiConsumer<Map<String, Object>, Map<String, String>>> messageDispatcher = Map.of( | ||
| "Discord", this::sendDiscordMessage, | ||
| "Email", this::sendEmailMessage, | ||
| "SMS", this::sendSMSMessage, | ||
| "Twitter", this::sendTwitterMessage | ||
| ); | ||
|
|
||
| String payloadString = req.getPayload(); | ||
| Gson gson = new Gson(); | ||
| Map payload = gson.fromJson(payloadString, Map.class); | ||
|
|
||
| try { | ||
| Optional.ofNullable(messageDispatcher.get((String) payload.get("type"))) | ||
| .orElseThrow(() -> new IllegalArgumentException(String.format("No such type: %s", payload.get("type")))) | ||
| .accept(payload, req.getVariables()); | ||
| return res.json(Map.of("success", true)); | ||
| } catch (IllegalArgumentException exception) { | ||
| return res.json(Map.of( | ||
| "success", false, | ||
| "message", exception.getMessage() | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| private void sendDiscordMessage(Map<String, Object> messageBody, Map<String, String> variables) { | ||
| String discordWebhookURL = variables.get("DISCORD_WEBHOOK_URL"); | ||
|
|
||
| Gson gson = new Gson(); | ||
| String message = getMessageField(messageBody,"message"); | ||
| String body = gson.toJson(Map.of("content", message)); | ||
|
|
||
| HttpClient client = HttpClient.newHttpClient(); | ||
| HttpRequest request = HttpRequest.newBuilder() | ||
| .uri(URI.create(discordWebhookURL)) | ||
| .header("Content-Type", "application/json") | ||
| .POST(HttpRequest.BodyPublishers.ofString(body)) | ||
| .build(); | ||
|
|
||
| try { | ||
| HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | ||
| if (response.statusCode() > 299) { | ||
| throw new MessageSendingFailedException("Request failed: " + response.body()); | ||
| } | ||
| } catch (IOException | InterruptedException e) { | ||
| throw new MessageSendingFailedException(e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| private void sendEmailMessage(Map<String, Object> messageBody, Map<String, String> variables) { | ||
| String mailgunDomain = variables.get("MAILGUN_DOMAIN"); | ||
| String mailgunApiKey = variables.get("MAILGUN_API_KEY"); | ||
|
|
||
| Gson gson = new Gson(); | ||
|
|
||
| String receiver = getMessageField(messageBody, "receiver"); // "[email protected]", | ||
| String message = getMessageField(messageBody, "message"); // "Programming is fun!", | ||
| String subject = getMessageField(messageBody, "subject"); // "Programming is funny!" | ||
|
|
||
| String body = gson.toJson(Map.of( | ||
| "from", "<[email protected]>", | ||
| "to", receiver, | ||
| "subject", subject, | ||
| "text",message) | ||
| ); | ||
|
|
||
| HttpClient client = HttpClient.newHttpClient(); | ||
| String uri = String.format("https://api.mailgun.net/v3/%s/messages", mailgunDomain); | ||
|
|
||
| HttpRequest request = HttpRequest.newBuilder() | ||
| .uri(URI.create(uri)) | ||
| .header("Content-Type", "application/json") | ||
| .header("Authorization", String.format("APIKEY %s", mailgunApiKey)) | ||
| .POST(HttpRequest.BodyPublishers.ofString(body)) | ||
| .build(); | ||
|
|
||
| try { | ||
| HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | ||
| if (response.statusCode() > 299) { | ||
| throw new MessageSendingFailedException("Request failed: " + response.body()); | ||
| } | ||
| } catch (IOException | InterruptedException e) { | ||
| throw new MessageSendingFailedException(e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| private void sendSMSMessage(Map<String, Object> messageBody, Map<String, String> variables) { | ||
| String twilioAccountSid = variables.get("TWILIO_ACCOUNT_SID"); | ||
| String twilioAuthToken = variables.get("TWILIO_AUTH_TOKEN"); | ||
| String twilioSender = variables.get("TWILIO_SENDER"); | ||
|
|
||
| Gson gson = new Gson(); | ||
|
|
||
| String receiver = getMessageField(messageBody, "receiver"); // "[email protected]", | ||
| String message = getMessageField(messageBody, "message"); // "Programming is fun!", | ||
|
|
||
| String body = gson.toJson(Map.of( | ||
| "From", twilioSender, | ||
| "To", receiver, | ||
| "Body", message) | ||
| ); | ||
|
|
||
| HttpClient client = HttpClient.newHttpClient(); | ||
| String uri = String.format("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", twilioAccountSid); | ||
| String authorizationHeader = "BASIC " + Base64.getEncoder().encodeToString(String.format("%s:%s", twilioAccountSid, twilioAuthToken).getBytes()); | ||
|
|
||
| HttpRequest request = HttpRequest.newBuilder() | ||
| .uri(URI.create(uri)) | ||
| .header("Content-Type", "application/json") | ||
| .header("Authorization", authorizationHeader) | ||
| .POST(HttpRequest.BodyPublishers.ofString(body)) | ||
| .build(); | ||
|
|
||
| try { | ||
| HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | ||
| if (response.statusCode() > 299) { | ||
| throw new MessageSendingFailedException("Request failed: " + response.body()); | ||
| } | ||
| } catch (IOException | InterruptedException e) { | ||
| throw new MessageSendingFailedException(e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| private void sendTwitterMessage(Map<String, Object> messageBody, Map<String, String> variables) { | ||
| String twitterApiKey = variables.get("TWITTER_API_KEY"); | ||
| String twitterApiKeySecret = variables.get("TWITTER_API_KEY_SECRET"); | ||
| String twitterAccessToken = variables.get("TWITTER_ACCESS_TOKEN"); | ||
| String twitterAccessTokenSecret = variables.get("TWITTER_ACCESS_TOKEN_SECRET"); | ||
|
|
||
| String message = getMessageField(messageBody, "message"); // "Programming is fun!", | ||
|
|
||
| try { | ||
| TwitterClient twitterClient = new TwitterClient(TwitterCredentials.builder() | ||
| .apiKey(twitterApiKey) | ||
| .apiSecretKey(twitterApiKeySecret) | ||
| .accessToken(twitterAccessToken) | ||
| .accessTokenSecret(twitterAccessTokenSecret) | ||
| .build()); | ||
|
|
||
| twitterClient.postTweet(message); | ||
| } catch(Exception e) { | ||
| throw new MessageSendingFailedException("There seems to have been a problem either with posting the tweet or with using Twitter's API.", e); | ||
| } | ||
| } | ||
|
|
||
| private String getMessageField(Map<String, Object> messageBody, String field) { | ||
| return Optional.ofNullable((String)messageBody.get(field)).orElseThrow(() -> new IllegalArgumentException(String.format("%s field not specified", field))); | ||
| } | ||
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,122 @@ | ||
| # SendMessage() | ||
|
|
||
| A Java Cloud Function for sending a message using a specific channel to a receiver | ||
|
|
||
| Supported channels are `SMS`, `Email` ,`Disocrd` and `Twitter`. | ||
|
|
||
| _SMS Example payload_ | ||
|
|
||
| ```json | ||
| { "type": "SMS", "receiver": "+123456789", "message": "Programming is fun!" } | ||
| ``` | ||
|
|
||
| _Email Example payload_ | ||
|
|
||
| ```json | ||
| { | ||
| "type": "Email", | ||
| "receiver": "[email protected]", | ||
| "message": "Programming is fun!", | ||
| "subject": "Programming is funny!" | ||
| } | ||
| ``` | ||
|
|
||
| _Discord Example payload_ | ||
|
|
||
| ```json | ||
| { | ||
| "type": "Discord", | ||
| "message": "Hi" | ||
| } | ||
| ``` | ||
|
|
||
| _Twitter Example payload_ | ||
|
|
||
| ```json | ||
| { | ||
| "type": "Twitter", | ||
| "message": "Programming is fun!" | ||
| } | ||
| ``` | ||
|
|
||
| _Successful function response:_ | ||
|
|
||
| ```json | ||
| { | ||
| "success": true | ||
| } | ||
| ``` | ||
|
|
||
| _Error function response:_ | ||
|
|
||
| ```json | ||
| { | ||
| "success": false, | ||
| "message": "Failed to send message,check webhook URL" | ||
| } | ||
| ``` | ||
|
|
||
| ## 📝 Variables | ||
|
|
||
| List of variables used by this cloud function: | ||
|
|
||
| Mailgun | ||
|
|
||
| - **MAILGUN_API_KEY** - API Key for Mailgun | ||
| - **MAILGUN_DOMAIN** - Domain Name from Mailgun | ||
|
|
||
| Discord | ||
|
|
||
| - **DISCORD_WEBHOOK_URL** - Webhook URL for Discord | ||
|
|
||
| Twilio | ||
|
|
||
| - **TWILIO_ACCOUNT_SID** - Acount SID from Twilio | ||
| - **TWILIO_AUTH_TOKEN** - Auth Token from Twilio | ||
| - **TWILIO_SENDER** - Sender Phone Number from Twilio | ||
|
|
||
|
|
||
| - **TWITTER_API_KEY** - API Key for Twitter | ||
| - **TWITTER_API_KEY_SECRET** - API Key Secret for Twitter | ||
| - **TWITTER_ACCESS_TOKEN** - Access Token from Twitter | ||
| - **TWITTER_ACCESS_TOKEN_SECRET** - Access Token Secret from Twitter | ||
|
|
||
| ## 🚀 Deployment | ||
|
|
||
| 1. Clone this repository, and enter this function folder: | ||
|
|
||
| ``` | ||
| $ git clone https://github.com/open-runtimes/examples.git && cd examples | ||
| $ cd java/sendMessage | ||
| ``` | ||
|
|
||
| 2. Enter this function folder and build the code: | ||
|
|
||
| ```bash | ||
| docker run -e INTERNAL_RUNTIME_ENTRYPOINT=Index.java --rm --interactive --tty --volume $PWD:/usr/code openruntimes/java:v2-18.0 sh /usr/local/src/build.sh | ||
| ``` | ||
|
|
||
| As a result, a `code.tar.gz` file will be generated. | ||
|
|
||
| 3. Start the Open Runtime: | ||
|
|
||
| ```bash | ||
| docker run -p 3000:3000 -e INTERNAL_RUNTIME_KEY=secret-key --rm --interactive --tty --volume $PWD/code.tar.gz:/tmp/code.tar.gz:ro openruntimes/java:v2-18.0 sh /usr/local/src/start.sh | ||
| ``` | ||
|
|
||
| Your function is now listening on port `3000`, and you can execute it by sending `POST` request with appropriate authorization headers. To learn more about runtime, you can visit Python runtime [README](https://github.com/open-runtimes/open-runtimes/tree/main/runtimes/python-3.10/example). | ||
|
|
||
| 4. Curl Command ( Email ) | ||
|
|
||
| ```bash | ||
| curl -X POST http://localhost:3000/ -d '{"variables": {"MAILGUN_API_KEY":"YOUR_MAILGUN_API_KEY","MAILGUN_DOMAIN":"YOUR_MAILGUN_DOMAIN"},"payload": "{\"type\": \"Email\",\"receiver\": \"[email protected]\",\"message\": \"Programming is fun!\",\"subject\": \"Programming is funny!\"}"}' -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" | ||
| ``` | ||
|
|
||
| ```bash | ||
| curl -X POST http://localhost:3000/ -d '{"variables": {"DISCORD_WEBHOOK_URL":"YOUR_DISCORD_WEBHOOK_URL"},"payload": "{\"type\": \"Discord\", \"message\": \"Programming is fun!\"}"}' -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" | ||
| ``` | ||
|
|
||
| ## 📝 Notes | ||
|
|
||
| - This function is designed for use with Appwrite Cloud Functions. You can learn more about it in [Appwrite docs](https://appwrite.io/docs/functions). |
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,7 @@ | ||
| dependencies { | ||
| implementation "io.github.redouane59.twitter:twittered:1.27" | ||
| implementation 'org.slf4j:slf4j-api:1.7.30' | ||
| implementation 'org.slf4j:slf4j-simple:1.7.30' | ||
| } | ||
|
|
||
| tasks.withType(Jar) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't catch an exception if the API call fails due to a bad API key. Instead, the output is:
{ "stdout": "", "stderr": "io.openruntimes.java.Wrapper$MessageSendingFailedException: Request failed: Forbidden\n\tat io.openruntimes.java.Wrapper.sendEmailMessage(Wrapper.java:109)\n\tat io.openruntimes.java.Wrapper.main(Wrapper.java:45)\n\tat io.openruntimes.java.Server.execute(Server.java:43)\n\tat org.rapidoid.http.handler.optimized.DelegatingParamsAwareReqRespHandler.handleReq(DelegatingParamsAwareReqRespHandler.java:49)\n\tat org.rapidoid.http.handler.AbstractHttpHandlerDecorator.handleReqAndPostProcess(AbstractHttpHandlerDecorator.java:48)\n\tat org.rapidoid.http.handler.HttpManagedHandlerDecorator$3.invokeNext(HttpManagedHandlerDecorator.java:161)\n\tat org.rapidoid.http.handler.HttpManagedHandlerDecorator$3.invoke(HttpManagedHandlerDecorator.java:121)\n\tat org.rapidoid.http.handler.HttpAuthWrapper.wrap(HttpAuthWrapper.java:70)\n\tat org.rapidoid.http.handler.HttpManagedHandlerDecorator.wrap(HttpManagedHandlerDecorator.java:185)\n\tat org.rapidoid.http.handler.HttpManagedHandlerDecorator.handleWithWrappers(HttpManagedHandlerDecorator.java:100)\n\tat org.rapidoid.http.handler.HttpManagedHandlerDecorator.access$200(HttpManagedHandlerDecorator.java:39)\n\tat org.rapidoid.http.handler.HttpManagedHandlerDecorator$2.run(HttpManagedHandlerDecorator.java:83)\n\tat org.rapidoid.job.PredefinedContextJobWrapper.run(PredefinedContextJobWrapper.java:56)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\n" }