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 }