blob: 7becc70cce7aadcc7ced4cf6a2b32725ca108c69 [file] [log] [blame]
// chatbot is a package for creating chatbots that interact via webhooks.
package chatbot
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/metadata"
"go.skia.org/infra/go/sklog"
)
const (
BOT_WEBHOOK_METADATA_KEY = "bot_webhooks"
)
var (
client *http.Client
botName string
)
type Sender struct {
DisplayName string `json:"displayName"`
}
type Message struct {
Name string `json:"name"`
Text string `json:"text"`
Sender Sender `json:"sender"`
}
func Init(name string) {
client = httputils.NewTimeoutClient()
botName = name
}
// Send the 'body' as a message to the given chat 'room' name.
func Send(body string, room string) error {
return SendUsingMetadataGet(body, room, metadata.ProjectGetWithDefault)
}
// GetWithDefault is a func that returns metadata.
type GetWithDefault func(key string, defaultValue string) string
// SendUsingMetadataGet is just like Send(), but the metadata retrieved is
// abstracted.
func SendUsingMetadataGet(body string, room string, metadataGet GetWithDefault) error {
// First look up the chat room webhook address as stored in project level
// metadata. The list of supported webhooks is stored at
// BOT_WEBHOOK_METADATA_KEY in the metadata, and is a multiline string of the
// form:
//
// botname_1 webhook_url_1 \n
// botname_2 webhook_url_2 \n
// botname_3 webhook_url_3 \n
//
// Note that we load the metadata every time through this func, since loading
// metadata is very fast, and we expect the message rate to be very low. This
// ensures we always have a fresh set of bots.
botWebhooks := metadataGet(BOT_WEBHOOK_METADATA_KEY, "")
if botWebhooks == "" {
return fmt.Errorf("Failed to find project metadata for %s", BOT_WEBHOOK_METADATA_KEY)
}
lines := strings.Split(botWebhooks, "\n")
url := ""
for _, line := range lines {
parts := strings.Split(line, " ")
if len(parts) == 2 && parts[0] == room {
url = parts[1]
break
}
}
if url == "" {
return fmt.Errorf("Unknown room name: %q", room)
}
sklog.Infof("Sending to: %q", url)
body = strings.TrimSpace(body)
if body == "" {
body = "*no message*"
}
// We've found the room, so compose the message.
msg := Message{
Text: body,
Sender: Sender{
DisplayName: botName,
},
}
b, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("Failed to encode message: %s", err)
}
buf := bytes.NewBuffer(b)
// Now send the message to the webhook.
resp, err := client.Post(url, "application/json", buf)
if err != nil {
return fmt.Errorf("Failed to send encoded message: %s", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("Wrong status code sending message: %d %s", resp.StatusCode, resp.Status)
}
return nil
}