|
| 1 | +The following example combines WhatsApp and Slack to create a workflow that allows you to receive WhatsApp messages in Slack, and use a modal to compose a reply. |
| 2 | + |
| 3 | +<CodeGroup> |
| 4 | + |
| 5 | +```tsx whatsappToSlack.tsx |
| 6 | +/** @jsxImportSource jsx-slack */ |
| 7 | +import { Trigger } from "@trigger.dev/sdk"; |
| 8 | +import { |
| 9 | + events, |
| 10 | + sendText, |
| 11 | + getMediaUrl, |
| 12 | + MessageEventMessage, |
| 13 | +} from "@trigger.dev/whatsapp"; |
| 14 | +import JSXSlack, { |
| 15 | + Actions, |
| 16 | + Blocks, |
| 17 | + Button, |
| 18 | + Section, |
| 19 | + Header, |
| 20 | + Context, |
| 21 | + Image, |
| 22 | + Modal, |
| 23 | + Input, |
| 24 | + Textarea, |
| 25 | +} from "jsx-slack"; |
| 26 | +import * as slack from "@trigger.dev/slack"; |
| 27 | + |
| 28 | +const dateFormatter = new Intl.DateTimeFormat("en-US", { |
| 29 | + timeStyle: "short", |
| 30 | + dateStyle: "short", |
| 31 | +}); |
| 32 | + |
| 33 | +// this trigger listens for WhatsApp messages and sends them to Slack |
| 34 | +new Trigger({ |
| 35 | + id: "whatsapp-to-slack", |
| 36 | + name: "WhatsApp: load messages", |
| 37 | + on: events.messageEvent({ |
| 38 | + accountId: "<account id>", |
| 39 | + }), |
| 40 | + run: async (event, ctx) => { |
| 41 | + //this generates Slack blocks from the WhatsApp message |
| 42 | + const messageBody = await createMessageBody(event.message); |
| 43 | + |
| 44 | + await slack.postMessage("jsx-test", { |
| 45 | + channelName: "whatsapp-support", |
| 46 | + //text appears in Slack notifications on mobile/desktop |
| 47 | + text: "How is your progress today?", |
| 48 | + //import and use JSXSlack to make creating rich messages much easier |
| 49 | + blocks: JSXSlack( |
| 50 | + <Blocks> |
| 51 | + <Header>From: {event.message.from}</Header> |
| 52 | + <Context>At: {dateFormatter.format(event.message.timestamp)}</Context> |
| 53 | + {messageBody} |
| 54 | + <Actions blockId="launch-modal"> |
| 55 | + <Button value="reply" actionId="reply"> |
| 56 | + Reply |
| 57 | + </Button> |
| 58 | + </Actions> |
| 59 | + </Blocks> |
| 60 | + ), |
| 61 | + //pass the WhatsApp message to the next trigger |
| 62 | + metadata: { |
| 63 | + whatsAppMessage: event.message, |
| 64 | + }, |
| 65 | + }); |
| 66 | + }, |
| 67 | +}).listen(); |
| 68 | +``` |
| 69 | + |
| 70 | +```tsx replyButtonInteraction.tsx |
| 71 | +//this trigger creates a Slack modal when a user presses the Reply button |
| 72 | +new Trigger({ |
| 73 | + id: "whatsapp-to-slack-modal", |
| 74 | + name: "WhatsApp: show message composer", |
| 75 | + on: slack.events.blockActionInteraction({ |
| 76 | + blockId: "launch-modal", |
| 77 | + }), |
| 78 | + run: async (event, ctx) => { |
| 79 | + if (!event.trigger_id) { |
| 80 | + return; |
| 81 | + } |
| 82 | + |
| 83 | + //get the action (pressing the reply button) and the original WhatsApp message |
| 84 | + const action = event.actions[0]; |
| 85 | + const whatsAppMessage = |
| 86 | + event.message?.metadata?.event_payload.whatsAppMessage; |
| 87 | + |
| 88 | + //generate Slack blocks from the WhatsApp message |
| 89 | + const messageBody = await createMessageBody(whatsAppMessage); |
| 90 | + |
| 91 | + if (action.action_id === "reply" && action.type === "button") { |
| 92 | + //show a reply modal, with the original message and an input field for the reply |
| 93 | + await slack.openView( |
| 94 | + "Opening view", |
| 95 | + event.trigger_id, |
| 96 | + JSXSlack( |
| 97 | + <Modal title="Your reply" close="Cancel" callbackId="submit-message"> |
| 98 | + <Header>Original message</Header> |
| 99 | + {messageBody} |
| 100 | + <Header>Your reply</Header> |
| 101 | + <Textarea |
| 102 | + name="message" |
| 103 | + label="Message" |
| 104 | + placeholder="Your message" |
| 105 | + maxLength={500} |
| 106 | + id="messageField" |
| 107 | + /> |
| 108 | + <Input type="submit" value="submit" /> |
| 109 | + </Modal> |
| 110 | + ), |
| 111 | + { |
| 112 | + onSubmit: "close", |
| 113 | + //pass the original WhatsApp message to the next trigger, and the original Slack message so we can thread replies |
| 114 | + metadata: { |
| 115 | + whatsAppMessage, |
| 116 | + thread_ts: event.message?.ts, |
| 117 | + }, |
| 118 | + } |
| 119 | + ); |
| 120 | + } |
| 121 | + }, |
| 122 | +}).listen(); |
| 123 | +``` |
| 124 | + |
| 125 | +```tsx replyModalSubmission.tsx |
| 126 | +//this trigger sends the reply from Slack to WhatsApp |
| 127 | +new Trigger({ |
| 128 | + id: "whatsapp-composed-slack-message", |
| 129 | + name: "WhatsApp: send message from Slack", |
| 130 | + on: slack.events.viewSubmissionInteraction({ |
| 131 | + callbackId: "submit-message", |
| 132 | + }), |
| 133 | + run: async (event, ctx) => { |
| 134 | + await ctx.logger.info("Modal submission", { event }); |
| 135 | + |
| 136 | + //the message from the input field in Slack |
| 137 | + const usersResponse = event.view.state?.values.messageField.message |
| 138 | + .value as string; |
| 139 | + |
| 140 | + //get the data from the previous messages/panels |
| 141 | + const privateMetadata = |
| 142 | + event.view.private_metadata && JSON.parse(event.view.private_metadata); |
| 143 | + await ctx.logger.info("Private metadata", privateMetadata); |
| 144 | + const whatsAppMessage = privateMetadata?.whatsAppMessage; |
| 145 | + |
| 146 | + if (!whatsAppMessage || !usersResponse) { |
| 147 | + await ctx.logger.error("No message or original message", { event }); |
| 148 | + return; |
| 149 | + } |
| 150 | + |
| 151 | + //send WhatsApp message |
| 152 | + await sendText("send-whatsapp", { |
| 153 | + fromId: "102119172798864", |
| 154 | + to: whatsAppMessage.from, |
| 155 | + text: usersResponse, |
| 156 | + }); |
| 157 | + |
| 158 | + //send message in the Slack thread |
| 159 | + await slack.postMessage("slack-reply", { |
| 160 | + channelName: "whatsapp-support", |
| 161 | + text: `Replied with: ${usersResponse}`, |
| 162 | + blocks: JSXSlack( |
| 163 | + <Blocks> |
| 164 | + <Header>Reply from @{event.user.username}</Header> |
| 165 | + <Context> |
| 166 | + At: {dateFormatter.format(new Date())} To: {whatsAppMessage.from} |
| 167 | + </Context> |
| 168 | + <Section>{usersResponse}</Section> |
| 169 | + </Blocks> |
| 170 | + ), |
| 171 | + thread_ts: privateMetadata?.thread_ts, |
| 172 | + }); |
| 173 | + |
| 174 | + return event; |
| 175 | + }, |
| 176 | +}).listen(); |
| 177 | + |
| 178 | +//creates different Slack blocks depending on the type of WhatsApp message (e.g. text, image, etc.) |
| 179 | +async function createMessageBody(message: MessageEventMessage) { |
| 180 | + switch (message.type) { |
| 181 | + case "text": { |
| 182 | + return <Section>{message.text.body}</Section>; |
| 183 | + } |
| 184 | + case "image": { |
| 185 | + const mediaUrl = await getMediaUrl(`getImageUrl`, message.image); |
| 186 | + return ( |
| 187 | + <Image |
| 188 | + src={mediaUrl} |
| 189 | + alt={message.image.caption ?? ""} |
| 190 | + title={message.image.caption ?? ""} |
| 191 | + /> |
| 192 | + ); |
| 193 | + } |
| 194 | + case "video": { |
| 195 | + const mediaUrl = await getMediaUrl(`getVideoUrl`, message.video); |
| 196 | + return <Section>{mediaUrl}</Section>; |
| 197 | + } |
| 198 | + default: |
| 199 | + return <Section>Unsupported message type: {message.type}</Section>; |
| 200 | + } |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +</CodeGroup> |
0 commit comments