346 lines
8.0 KiB
Go
346 lines
8.0 KiB
Go
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
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)
|
|
}
|