matterbridge/matterhook/matterhook.go

153 lines
3.9 KiB
Go
Raw Normal View History

2015-10-23 21:31:14 +00:00
//Package matterhook provides interaction with mattermost incoming/outgoing webhooks
2015-10-23 15:09:08 +00:00
package matterhook
import (
"bytes"
"crypto/tls"
2015-10-23 15:09:08 +00:00
"encoding/json"
"fmt"
"github.com/gorilla/schema"
"io"
"io/ioutil"
"log"
"net/http"
"strconv"
)
// OMessage for mattermost incoming webhook. (send to mattermost)
type OMessage struct {
Channel string `json:"channel,omitempty"`
IconURL string `json:"icon_url,omitempty"`
IconEmoji string `json:"icon_emoji,omitempty"`
UserName string `json:"username,omitempty"`
Text string `json:"text"`
Attachments interface{} `json:"attachments,omitempty"`
Type string `json:"type,omitempty"`
2015-10-23 15:09:08 +00:00
}
// IMessage for mattermost outgoing webhook. (received from mattermost)
type IMessage struct {
Token string `schema:"token"`
TeamID string `schema:"team_id"`
TeamDomain string `schema:"team_domain"`
ChannelID string `schema:"channel_id"`
ServiceID string `schema:"service_id"`
ChannelName string `schema:"channel_name"`
Timestamp string `schema:"timestamp"`
UserID string `schema:"user_id"`
UserName string `schema:"user_name"`
Text string `schema:"text"`
TriggerWord string `schema:"trigger_word"`
}
// Client for Mattermost.
type Client struct {
2015-10-28 16:18:05 +00:00
Url string // URL for incoming webhooks on mattermost.
In chan IMessage
Out chan OMessage
httpclient *http.Client
2015-10-23 15:09:08 +00:00
Config
}
2015-10-24 16:01:15 +00:00
// Config for client.
2015-10-23 15:09:08 +00:00
type Config struct {
Port int // Port to listen on.
Token string // Only allow this token from Mattermost. (Allow everything when empty)
InsecureSkipVerify bool // disable certificate checking
2015-10-28 16:18:05 +00:00
DisableServer bool // Do not start server for outgoing webhooks from Mattermost.
2015-10-23 15:09:08 +00:00
}
// New Mattermost client.
func New(url string, config Config) *Client {
2015-10-28 16:18:05 +00:00
c := &Client{Url: url, In: make(chan IMessage), Out: make(chan OMessage), Config: config}
2015-10-23 15:09:08 +00:00
if c.Port == 0 {
c.Port = 9999
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
}
c.httpclient = &http.Client{Transport: tr}
2015-10-28 16:18:05 +00:00
if !c.DisableServer {
go c.StartServer()
}
2015-10-23 15:09:08 +00:00
return c
}
// StartServer starts a webserver listening for incoming mattermost POSTS.
func (c *Client) StartServer() {
mux := http.NewServeMux()
mux.Handle("/", c)
log.Printf("Listening on http://0.0.0.0:%v...\n", c.Port)
if err := http.ListenAndServe((":" + strconv.Itoa(c.Port)), mux); err != nil {
log.Fatal(err)
}
}
// ServeHTTP implementation.
func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr)
http.NotFound(w, r)
return
}
2015-10-23 15:09:08 +00:00
msg := IMessage{}
err := r.ParseForm()
if err != nil {
log.Println(err)
http.NotFound(w, r)
return
}
defer r.Body.Close()
decoder := schema.NewDecoder()
err = decoder.Decode(&msg, r.PostForm)
if err != nil {
log.Println(err)
http.NotFound(w, r)
return
}
if msg.Token == "" {
log.Println("no token from " + r.RemoteAddr)
http.NotFound(w, r)
return
}
2015-10-24 16:01:15 +00:00
if c.Token != "" {
if msg.Token != c.Token {
log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr)
http.NotFound(w, r)
return
}
}
2015-10-23 15:09:08 +00:00
c.In <- msg
}
// Receive returns an incoming message from mattermost outgoing webhooks URL.
func (c *Client) Receive() IMessage {
for {
select {
case msg := <-c.In:
return msg
}
}
}
// Send sends a msg to mattermost incoming webhooks URL.
func (c *Client) Send(msg OMessage) error {
buf, err := json.Marshal(msg)
if err != nil {
return err
}
2015-10-28 16:18:05 +00:00
resp, err := c.httpclient.Post(c.Url, "application/json", bytes.NewReader(buf))
2015-10-23 15:09:08 +00:00
if err != nil {
return err
}
defer resp.Body.Close()
// Read entire body to completion to re-use keep-alive connections.
io.Copy(ioutil.Discard, resp.Body)
if resp.StatusCode != 200 {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
return nil
}