Bird 最近推出了 Programmable Conversations。它让公司可以将 WhatsApp、Messenger 和 SMS 等通信平台整合到他们的系统中——使用单个 API。
我想试一试,所以我构建了一个 WhatsApp 机器人待办事项列表,因为谁不需要一个自动化的待办事项列表来帮助组织他们的一天呢?听起来可能很复杂,但实际上很简单,我想告诉你们所有关于它的事情。
现在,我在 MessageBird 工作,所以我可以直接开始构建。如果你尝试这样做,你需要 请求早期访问权限。但一旦你设置了 WhatsApp 通道,你就可以登录到 MessageBird 网站的仪表板并开始。
我做的第一件事就是阅读文档。我了解到,为了从机器人接收消息,我需要使用一个 webhook。这意味着我的机器人需要可以从互联网访问。当构建像这样的 API 时,遵循API 版本控制最佳实践对于维护性很重要。因为我只是刚开始编码,所以我决定使用ngrok。它创建了一个从公共互联网到您本地主机端口 5007 的隧道。动手吧!
ngrok http 5007 -region eu -subdomain todobot
接下来,我需要调用 Programmable Conversations API 来创建 webhook。这是一个发送到 https://conversations.messagebird.com/v1/webhooks
的 POST 请求,看起来像这样:
func main() {
wh := struct {
Events []string `json:"events"`
ChannelID string `json:"channelId"`
URL string `json:"url"`
} {
URL: "https://todobot.eu.ngrok.io/create-hook",
Events: []string{"message.created"},
ChannelID: "23a780701b8849f7b974d8620a89a279",
}
var b bytes.Buffer
err := json.NewEncoder(&b).Encode(&wh)
if err != nil {
panic(err)
}
req, err := http.NewRequest("POST", "https://conversations.messagebird.com/v1/webhooks", &b)
req.Header.Set("Authorization", "AccessKey todo-your-access-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode >= http.StatusBadRequest {
panic(fmt.Errorf("Bad response code from api when trying to create webhook: %s. Body: %s", resp.Status, string(body)))
} else {
log.Println("All good. response body: ", string(body))
}
}
太好了。现在,Conversations API 将对这个网址发送一个 POST 请求:
https://todobot.eu.ngrok.io/create-hook
无论何时在之前设置的 WhatsApp 通道上创建新消息。
这就是 webhook 负载的样子:
{
"conversation":{
"id":"55c66895c22a40e39a8e6bd321ec192e",
"contactId":"db4dd5087fb343738e968a323f640576",
"status":"active",
"createdDatetime":"2018-08-17T10:14:14Z",
"updatedDatetime":"2018-08-17T14:30:31.915292912Z",
"lastReceivedDatetime":"2018-08-17T14:30:31.898389294Z"
},
"message":{
"id":"ddb150149e2c4036a48f581544e22cfe",
"conversationId":"55c66895c22a40e39a8e6bd321ec192e",
"channelId":"23a780701b8849f7b974d8620a89a279",
"status":"received",
"type":"text",
"direction":"received",
"content":{
"text":"add buy milk"
},
"createdDatetime":"2018-08-17T14:30:31.898389294Z",
"updatedDatetime":"2018-08-17T14:30:31.915292912Z"
},
"type":"message.created"
}
我们想回复那些消息。让我们从回声开始,你说呢?
type whPayload struct {
Conversation conversation `json:"conversation"`
Message message `json:"message"`
Type string `json:"type"`
}
type message struct {
ID string `json:"id"`
Direction string `json:"direction"`
Type string `json:"type"`
Content content `json:"content"`
}
type content struct {
Text string `json:"text"`
}
type conversation struct {
ID string `json:"id"`
}
func main() {
http.HandleFunc("/create-hook", createHookHandler)
log.Fatal(http.ListenAndServe(*httpListenAddress, nil))
}
func createHookHandler(w http.ResponseWriter, r *http.Request) {
whp := &whPayload{}
err := json.NewDecoder(r.Body).Decode(whp)
if err != nil {
log.Println("Err: got weird body on the webhook")
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Internal Server Error")
return
}
if whp.Message.Direction != "received" {
fmt.Fprintf(w, "ok")
return
}
err = respond(whp.Conversation.ID, whp.Message.Content.Text)
if err != nil {
log.Println("Err: ", err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Internal Server Error")return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "ok")
}
现在,到了有趣的部分。做一个 POST 请求到:
https://conversations.messagebird.com/v1/conversations/<conversationID>/messages
来回答请求。
func respond(conversationID, responseBody string) error {
u := fmt.Sprintf("https://conversations.messagebird.com/v1/conversations/%s/messages", conversationID)msg := message{
Content: content{
Text: responseBody,
},
Type: "text",
}
var b bytes.Buffer
err := json.NewEncoder(&b).Encode(&msg)
if err != nil {
return fmt.Errorf("Error encoding buffer: %v", err)
}
req, err := http.NewRequest("POST", u.String(), &b)
req.Header.Set("Authorization", "AccessKey todo-your-access-key")
req.Header.Set("Content-Type", "application/json")client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("Bad response code from api when trying to create message: %s. Body: %s", resp.Status, string(body))
}
log.Println("All good. Response body: ", string(body))
return nil
}
就这样。你只需要这些就可以创建一个像五岁小孩一样行事的机器人。
现在,让我们朝着构建整个待办事项列表迈进。首先,稍微修改 createHookHandler 函数,以便它调用新的 handleMessage 函数而不是 respond。
func createHookHandler(w http.ResponseWriter, r *http.Request) {
...
err = handleMessage(whp)
...
}
handle 将简单地解析消息,执行一些操作,然后选择回复。我们来看一下“add”命令:
func handleMessage(whp *whPayload) error {
list := manager.fetch(whp.Conversation.ID)
text := whp.Message.Content.Text
text = regexp.MustCompile(" +").ReplaceAllString(text, " ")
parts := strings.Split(text, " ")
command := strings.ToLower(parts[0])
responseBody := "I don't understand. Type 'help' to get help."
switch command {
...
case "add":
if len(parts) < 2 {
return respond(whp.Conversation.ID, "err... the 'add' command needs a second param: the todo item you want to save. Something like 'add buy milk'.")
}
item := strings.Join(parts[1:], " ")list.add(item)
responseBody = "added."
...
return respond(whp.Conversation.ID, responseBody)
}
在这里,我们设置:list := manager.fetch(whp.Conversation.ID)。基本上,“manager” 是一个并发安全的映射,将会话ID映射到待办事项列表。
待办事项列表是一个并发安全的字符串片段。全部在内存中!
另一件重要的事情!你可以归档会话。在某些应用中,比如 CRM,跟踪某些互动非常重要——例如跟踪客户支持员工的有效性。Conversations API 允许你归档一个会话以“关闭”主题。如果用户/客户发送另一条消息,Conversations API 将自动打开一个新主题。
此外,发送 PATCH 请求到 https://conversations.messagebird.com/v1/conversations/{id}
时,在主体中具有正确的状态允许你用那个id归档会话。我们用“bye”命令来解决这个问题:
case "bye":
archiveConversation(whp.Conversation.ID)
manager.close(whp.Conversation.ID)
responseBody = "bye!"
archiveConversation 将进行 PATCH 请求,而 manager.close(whp.Conversation.ID) 将删除待办事项列表会话。
不过,Programmable Conversations 是一个多渠道解决方案。如果你想为不同的平台(如 WeChat)重用机器人的代码怎么办?这种多渠道的方法是将询问引导至低成本渠道策略的一部分。你该怎么做呢?
只需创建一个新的 webhook 来定位该通道即可!一个将请求发送到我们用于 WhatsApp 的同一 https://todobot.eu.ngrok.io/create-hook
URL 的 webhook!
这将有效,因为处理程序代码始终使用来自 webhook 负载的 conversationID 来回答消息,而不是硬编码的 channelID。MessageBird 的 Conversations API 将自动确定用于发送消息的会话的渠道。
你想建立自己的机器人吗?查看 Github 上的完整代码:Wabot on Github,通过访问 WhatsApp 页面并单击联系销售按钮填写表格来请求早期访问 WhatsApp。祝你制作机器人愉快!