// Copyright (c) 2016 Tristan Colgate-McFarlane // // This file is part of hugot. // // hugot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // hugot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with hugot. If not, see . package hugot import ( "errors" "fmt" "net/http" "net/url" "regexp" "sync" "github.com/golang/glog" "context" ) func init() { DefaultMux = NewMux("hugot", "") http.Handle("/hugot", DefaultMux) http.Handle("/hugot/", DefaultMux) } // Mux is a Handler that multiplexes messages to a set of Command, Hears, and // Raw handlers. type Mux struct { name string desc string burl *url.URL *sync.RWMutex hndlrs []Handler // All the handlers rhndlrs []RawHandler // Raw handlers bghndlrs []BackgroundHandler // Long running background handlers whhndlrs map[string]WebHookHandler // WebHooks hears map[*regexp.Regexp][]HearsHandler // Hearing handlers cmds *CommandSet // Command handlers httpm *http.ServeMux // http Mux } // DefaultMux is a default Mux instance, http Handlers will be added to // http.DefaultServeMux var DefaultMux *Mux // NewMux creates a new Mux. func NewMux(name, desc string) *Mux { mx := &Mux{ name: name, desc: desc, RWMutex: &sync.RWMutex{}, rhndlrs: []RawHandler{}, bghndlrs: []BackgroundHandler{}, whhndlrs: map[string]WebHookHandler{}, hears: map[*regexp.Regexp][]HearsHandler{}, cmds: NewCommandSet(), httpm: http.NewServeMux(), burl: &url.URL{Path: "/" + name}, } mx.HandleCommand(&muxHelp{mx}) return mx } // Describe implements the Describe method of Handler for // the Mux func (mx *Mux) Describe() (string, string) { return mx.name, mx.desc } // URL returns the base URL for the default Mux func URL() *url.URL { return DefaultMux.URL() } // URL returns the base URL for this Mux func (mx *Mux) URL() *url.URL { mx.RLock() defer mx.RUnlock() return mx.url() } func (mx *Mux) url() *url.URL { return mx.burl } // SetURL sets the base URL for web hooks. func SetURL(b *url.URL) { if b.Path != "" { panic(errors.New("Can't set URL with path at the moment, sorry")) } DefaultMux.SetURL(b) } // SetURL sets the base URL for this mux's web hooks. func (mx *Mux) SetURL(b *url.URL) { mx.Lock() defer mx.Unlock() mx.burl = b for _, h := range mx.whhndlrs { n, _ := h.Describe() p := fmt.Sprintf("%s/%s/%s/", b.Path, mx.name, n) nu := *b nu.Path = p h.SetURL(&nu) } } // StartBackground starts any registered background handlers. func (mx *Mux) StartBackground(ctx context.Context, w ResponseWriter) { mx.Lock() defer mx.Unlock() for _, h := range mx.bghndlrs { go runBackgroundHandler(ctx, h, w.Copy()) } } // SetAdapter sets the adapter on all the webhook of this mux. func (mx *Mux) SetAdapter(a Adapter) { mx.Lock() defer mx.Unlock() for _, wh := range mx.whhndlrs { wh.SetAdapter(a) } } // ProcessMessage implements the Handler interface. Message will first be passed to // any registered RawHandlers. If the message has been deemed, by the Adapter // to have been sent directly to the bot, any comand handlers will be processed. // Then, if appropriate, the message will be matched against any Hears patterns // and all matching Heard functions will then be called. // Any unrecognized errors from the Command handlers will be passed back to the // user that sent us the message. func (mx *Mux) ProcessMessage(ctx context.Context, w ResponseWriter, m *Message) error { mx.RLock() defer mx.RUnlock() var err error // We run all raw message handlers for _, rh := range mx.rhndlrs { mc := *m go rh.ProcessMessage(ctx, w, &mc) } if m.ToBot { err = mx.cmds.NextCommand(ctx, w, m) } if err == ErrSkipHears { return nil } for _, hhs := range mx.hears { for _, hh := range hhs { mc := *m if runHearsHandler(ctx, hh, w, &mc) { err = nil } } } if err != nil { fmt.Fprintf(w, "error, %s", err.Error()) } return nil } // Handle adds the provided handler to the DefaultMux func Handle(h Handler) error { return DefaultMux.Handle(h) } // Handle adds a generic handler that supports one or more of the handler // types. WARNING: This may be removed in the future. Prefer to // the specific Add*Handler methods. func (mx *Mux) Handle(h Handler) error { var used bool if h, ok := h.(RawHandler); ok { mx.HandleRaw(h) used = true } if h, ok := h.(BackgroundHandler); ok { mx.HandleBackground(h) used = true } if h, ok := h.(CommandHandler); ok { mx.HandleCommand(h) used = true } if h, ok := h.(HearsHandler); ok { mx.HandleHears(h) used = true } if h, ok := h.(WebHookHandler); ok { mx.HandleHTTP(h) used = true } mx.Lock() defer mx.Unlock() if !used { return fmt.Errorf("Don't know how to use %T as a handler", h) } mx.hndlrs = append(mx.hndlrs, h) return nil } // HandleRaw adds the provided handler to the DefaultMux func HandleRaw(h RawHandler) error { return DefaultMux.HandleRaw(h) } // HandleRaw adds the provided handler to the Mux. All // messages sent to the mux will be forwarded to this handler. func (mx *Mux) HandleRaw(h RawHandler) error { mx.Lock() defer mx.Unlock() if h, ok := h.(RawHandler); ok { mx.rhndlrs = append(mx.rhndlrs, h) } return nil } // HandleBackground adds the provided handler to the DefaultMux func HandleBackground(h BackgroundHandler) error { return DefaultMux.HandleBackground(h) } // HandleBackground adds the provided handler to the Mux. It // will be started with the Mux is started. func (mx *Mux) HandleBackground(h BackgroundHandler) error { mx.Lock() defer mx.Unlock() //name, _ := h.Describe() mx.bghndlrs = append(mx.bghndlrs, h) return nil } // HandleHears adds the provided handler to the DefaultMux func HandleHears(h HearsHandler) error { return DefaultMux.HandleHears(h) } // HandleHears adds the provided handler to the mux. All // messages matching the Hears patterns will be forwarded to // the handler. func (mx *Mux) HandleHears(h HearsHandler) error { mx.Lock() defer mx.Unlock() r := h.Hears() mx.hears[r] = append(mx.hears[r], h) return nil } // HandleCommand adds the provided handler to the DefaultMux func HandleCommand(h CommandHandler) { DefaultMux.HandleCommand(h) } // HandleCommand adds the provided handler to the mux. func (mx *Mux) HandleCommand(h CommandHandler) { mx.Lock() defer mx.Unlock() mx.cmds.AddCommandHandler(h) } type webHookBridge struct { nh http.Handler } func (whb *webHookBridge) ServeHTTP(w http.ResponseWriter, r *http.Request) { if glog.V(2) { glog.Infof("webHookBridge ServeHTTP %v %s\n", *whb, *r) } whb.nh.ServeHTTP(w, r) } // HandleHTTP adds the provided handler to the DefaultMux func HandleHTTP(h WebHookHandler) { DefaultMux.HandleHTTP(h) } // HandleHTTP registers h as a WebHook handler. The name // of the Mux, and the name of the handler are used to // construct a unique URL that can be used to send web // requests to this handler func (mx *Mux) HandleHTTP(h WebHookHandler) { mx.Lock() defer mx.Unlock() n, _ := h.Describe() p := fmt.Sprintf("/%s/%s", mx.name, n) mx.httpm.Handle(p, h) mx.httpm.Handle(p+"/", h) if glog.V(2) { glog.Infof("registering %v at %s, on %v\n", h, p, mx.httpm) } mx.whhndlrs[n] = h mu := mx.url() nu := *mu nu.Path = fmt.Sprintf("/%s/%s/", mx.name, n) h.SetURL(&nu) } // ServeHTTP iplements http.ServeHTTP for a Mux to allow it to // act as a web server. func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if glog.V(2) { glog.Infof("Mux ServeHTTP %s\n", *r) } mx.httpm.ServeHTTP(w, r) }