go-again/webhooks/webhooks.go

162 lines
4.0 KiB
Go

package webooks
import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
)
var logger chan string
func init() {
logger = make(chan string, 10)
go func() {
for {
msg := <-logger
log.Println(msg)
}
}()
}
type Webhook struct {
ID string `json:"id,omitempty"`
Comment string `json:"comment"`
Method string `json:"method"`
URL string `json:"url"`
TZ string `json:"-"`
Auth map[string]string `json:"auth,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Form map[string]string `json:"form,omitempty"`
JSON map[string]string `json:"json,omitempty"`
Config map[string]string `json:"config,omitempty"`
}
func Log(str string, args ...interface{}) {
logger <- fmt.Sprintf(str, args...)
}
func Run(h Webhook) {
// TODO do this in main on config init
if "" == h.Method {
h.Method = "POST"
}
var body *strings.Reader
var err error
// TODO real templates
loc, err := time.LoadLocation(h.TZ)
if nil != err {
Log("Bad timezone", h.TZ)
loc, _ = time.LoadLocation("UTC")
}
t := time.Now().In(loc)
z, _ := t.Zone()
if 0 != len(h.Form) {
form := url.Values{}
for k := range h.Form {
v := h.Form[k]
// because `{{` gets urlencoded
//v = strings.Replace(v, "{{ .Name }}", d.Name, -1)
v = strings.Replace(v, "{{ .Datetime }}", t.Format("2006-01-02 3:04:05 MST"), -1)
v = strings.Replace(v, "{{ .Date }}", t.Format("2006-01-02"), -1)
v = strings.Replace(v, "{{ .Time }}", t.Format(time.Kitchen), -1)
v = strings.Replace(v, "{{ .Zone }}", z, -1)
Log("[HEADER] %s: %s", k, v)
form.Set(k, v)
}
body = strings.NewReader(form.Encode())
} else if 0 != len(h.JSON) {
bodyBuf, err := json.Marshal(h.JSON)
if nil != err {
Log("[Notify] JSON Marshal Error for '%s': %s", h.Comment, err)
return
}
// `{{` is left alone in the body
bodyStr := string(bodyBuf)
bodyStr = strings.Replace(bodyStr, "{{ .Datetime }}", t.Format("2006-01-02 3:04:05 MST"), -1)
bodyStr = strings.Replace(bodyStr, "{{ .Date }}", t.Format("2006-01-02"), -1)
bodyStr = strings.Replace(bodyStr, "{{ .Time }}", t.Format("3:04:05PM"), -1)
bodyStr = strings.Replace(bodyStr, "{{ .Zone }}", z, -1)
body = strings.NewReader(bodyStr)
//body = strings.NewReader(string(bodyBuf))
}
if nil == body {
body = strings.NewReader("")
}
client := NewHTTPClient()
fmt.Println("bd?", h.Method, h.URL, body)
req, err := http.NewRequest(h.Method, h.URL, body)
if nil != err {
Log("[Notify] HTTP Client Network Error for '%s': %s", h.Comment, err)
return
}
if 0 != len(h.Form) {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
} else if 0 != len(h.JSON) {
req.Header.Set("Content-Type", "application/json")
}
if 0 != len(h.Auth) {
user := h.Auth["user"]
if "" == user {
user = h.Auth["username"]
}
pass := h.Auth["pass"]
if "" == user {
pass = h.Auth["password"]
}
req.SetBasicAuth(user, pass)
}
req.Header.Set("User-Agent", "Watchdog/1.0")
for k := range h.Headers {
req.Header.Set(k, h.Headers[k])
}
resp, err := client.Do(req)
if nil != err {
Log("[Notify] HTTP Client Error for '%s': %s", h.Comment, err)
return
}
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
Log("[Notify] Response Error for '%s': %s", h.Comment, resp.Status)
return
}
// TODO json vs xml vs txt
var data map[string]interface{}
req.Header.Add("Accept", "application/json")
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&data)
if err != nil {
Log("[Notify] Response Body Error for '%s': %s", h.Comment, resp.Status)
return
}
// TODO some sort of way to determine if data is successful (keywords)
Log("[Notify] Success? %#v", data)
}
// The default http client uses unsafe defaults
func NewHTTPClient() *http.Client {
transport := &http.Transport{
Dial: (&net.Dialer{
Timeout: 10 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
client := &http.Client{
Timeout: time.Second * 5,
Transport: transport,
}
return client
}