diff --git a/chats/chats.go b/chats/chats.go new file mode 100644 index 0000000..56961af --- /dev/null +++ b/chats/chats.go @@ -0,0 +1,25 @@ +package chats + +import ( + "suah.dev/mcchunkie/plugins" +) + +// ChatStore matches MCStore. This allows the main store to be used by +// plugins. +type ChatStore interface { + Set(key, values string) + Get(key string) (string, error) +} + +// Chat represents a mode of communication like Matrix, IRC or SMS. +type Chat interface { + // Connect connects + Connect(s plugins.PluginStore) error +} + +// Chats is a collection of our chat methods. An instance of this is iterated +// over for each message the bot responds to. +type Chats []Chat + +// ChatMethods defines the "enabled" chat methogs. +var ChatMethods = Chats{} diff --git a/chats/irc.go b/chats/irc.go new file mode 100644 index 0000000..76cf7b2 --- /dev/null +++ b/chats/irc.go @@ -0,0 +1,102 @@ +package chats + +import ( + "crypto/tls" + "fmt" + "log" + "strings" + + "gopkg.in/irc.v3" + "suah.dev/mcchunkie/plugins" +) + +// IRCConnect connects to our irc server +func IRCConnect(store ChatStore, plugins *plugins.Plugins) error { + var ircServer, _ = store.Get("irc_server") + var ircPort, _ = store.Get("irc_port") + var ircNick, _ = store.Get("irc_nick") + var ircPass, _ = store.Get("irc_pass") + var ircRooms, _ = store.Get("irc_rooms") + //var toRE = regexp.MustCompile(`^:(\w+)\s`) + + if ircServer != "" { + + log.Printf("IRC: connecting to %q\n", ircServer) + + dialStr := fmt.Sprintf("%s:%s", ircServer, ircPort) + conn, err := tls.Dial("tcp", dialStr, &tls.Config{ + ServerName: ircServer, + }) + if err != nil { + return err + } + + config := irc.ClientConfig{ + Nick: ircNick, + Pass: ircPass, + User: ircNick, + Name: "McChunkie", + Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) { + switch m.Command { + case "001": + for _, r := range strings.Split(ircRooms, ",") { + log.Printf("IRC: joining %q\n", r) + c.Write(fmt.Sprintf("JOIN %s", r)) + } + case "PING": + server := m.Trailing() + log.Printf("IRC: pong %q\n", server) + c.Write(fmt.Sprintf("PONG %s", server)) + case "INVITE": + room := m.Trailing() + log.Printf("IRC: joining %q\n", room) + c.Write(fmt.Sprintf("JOIN %s", room)) + case "PRIVMSG": + msg := m.Trailing() + from := m.Prefix.Name + to := m.Params[0] + + if from == c.CurrentNick() { + // Ignore messages from ourselves + return + } + + resp := "" + for _, p := range *plugins { + if p.Match(c.CurrentNick(), msg) { + p.SetStore(store) + + resp = p.Process(from, msg) + } + } + + if !c.FromChannel(m) { + // in a private chat + to = from + + } + + if resp != "" { + log.Printf("IRC: sending: %q to %q\n", resp, to) + c.WriteMessage(&irc.Message{ + Command: "PRIVMSG", + Params: []string{ + to, + resp, + }, + }) + } + default: + log.Printf("IRC: unhandled - %q", m.String()) + } + }), + } + + client := irc.NewClient(conn, config) + err = client.Run() + if err != nil { + return err + } + } + return nil +} diff --git a/chats/matrix.go b/chats/matrix.go new file mode 100644 index 0000000..aa31c5f --- /dev/null +++ b/chats/matrix.go @@ -0,0 +1,7 @@ +package chats + +/* +func matrixConnect() { + +} +*/ diff --git a/chats/sms.go b/chats/sms.go new file mode 100644 index 0000000..19172ec --- /dev/null +++ b/chats/sms.go @@ -0,0 +1,94 @@ +package chats + +import ( + "fmt" + "log" + "net/http" + "strings" + + "golang.org/x/crypto/bcrypt" + "suah.dev/mcchunkie/plugins" +) + +func smsCanSend(number string, numbers []string) bool { + for _, s := range numbers { + if number == s { + return true + } + } + return false +} + +// SMSListen listens for our incoming sms +func SMSListen(store ChatStore, plugins *plugins.Plugins) { + var smsPort, _ = store.Get("sms_listen") + var smsAllowed, _ = store.Get("sms_users") + var smsUsers = strings.Split(smsAllowed, ",") + + if smsPort != "" { + var htpass, _ = store.Get("sms_htpass") + + log.Printf("SMS: listening on %q\n", smsPort) + + http.HandleFunc("/_sms", func(w http.ResponseWriter, r *http.Request) { + var msg, from string + user, pass, ok := r.BasicAuth() + err := bcrypt.CompareHashAndPassword([]byte(htpass), []byte(pass)) + if !(ok && err == nil && user == "sms") { + log.Printf("SMS: failed auth %q %q\n", user, pass) + w.Header().Set("WWW-Authenticate", `Basic realm="sms notify"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + err = r.ParseForm() + if err != nil { + http.Error(w, "invalid request", http.StatusBadRequest) + return + } + + log.Println(r.Method) + + switch r.Method { + case http.MethodPost: + msg = r.Form.Get("Body") + from = r.Form.Get("From") + default: + http.Error( + w, + fmt.Sprintf("method %q not implemented", r.Method), + http.StatusMethodNotAllowed, + ) + return + } + + if smsCanSend(from, smsUsers) { + msg = strings.TrimSuffix(msg, "\n") + + if msg == "" { + fmt.Fprintf(w, "empty message") + return + } + + for _, p := range *plugins { + if p.Match(from, msg) { + log.Printf("%s: responding to '%s'", p.Name(), from) + p.SetStore(store) + + resp := p.Process(from, msg) + fmt.Fprint(w, resp) + } + } + } else { + log.Printf("number not allowed (%q)", from) + http.Error( + w, + fmt.Sprintf("number not allowed (%q)", from), + http.StatusMethodNotAllowed, + ) + return + } + }) + log.Fatal(http.ListenAndServe(smsPort, nil)) + } +} diff --git a/main.go b/main.go index 14450bf..24a79c9 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "time" "github.com/matrix-org/gomatrix" + "suah.dev/mcchunkie/chats" "suah.dev/mcchunkie/plugins" "suah.dev/protect" ) @@ -198,12 +199,21 @@ func main() { }() go func() { - smsListen(store, &plugins.Plugs) + chats.SMSListen(store, &plugins.Plugs) + }() + + go func() { + log.Println("MATRIX: syncing..") + if err := matrixCLI.Sync(); err != nil { + fmt.Println("Sync() returned ", err) + } + + time.Sleep(1 * time.Second) }() go func() { for { - err := ircConnect(store, &plugins.Plugs) + err := chats.IRCConnect(store, &plugins.Plugs) if err != nil { log.Println(err) } @@ -350,11 +360,6 @@ func main() { } for { - log.Println("syncing..") - if err := matrixCLI.Sync(); err != nil { - fmt.Println("Sync() returned ", err) - } - - time.Sleep(1 * time.Second) + time.Sleep(time.Second * 60) } }