From 07c69c14bb568f5ee3f09000301bcb0c415698e9 Mon Sep 17 00:00:00 2001 From: Bernhard Tittelbach Date: Fri, 23 Aug 2013 04:21:21 +0000 Subject: [PATCH] r3 xmpp bot --- r3-webstatus-spaceapi/main.go | 35 ++- r3-webstatus-spaceapi/make_deploy.zsh | 4 + r3-webstatus-spaceapi/r3xmppbot/r3xmppbot.go | 367 ++++++++++++++++++++++++++ 3 files changed, 402 insertions(+), 4 deletions(-) create mode 100644 r3-webstatus-spaceapi/make_deploy.zsh create mode 100644 r3-webstatus-spaceapi/r3xmppbot/r3xmppbot.go diff --git a/r3-webstatus-spaceapi/main.go b/r3-webstatus-spaceapi/main.go index 322b647..77dd32a 100644 --- a/r3-webstatus-spaceapi/main.go +++ b/r3-webstatus-spaceapi/main.go @@ -2,6 +2,7 @@ package main import ( "./spaceapi" + "./r3xmppbot" "bufio" "fmt" "net" @@ -15,6 +16,8 @@ import ( type SpaceState struct { present bool buttonpress_until int64 + door_locked bool + door_shut bool } var ( @@ -26,6 +29,7 @@ var ( re_querystresc_ *regexp.Regexp = regexp.MustCompile("[^\x30-\x39\x41-\x7E]") spaceapidata spaceapi.SpaceInfo = spaceapi.NewSpaceInfo("realraum", "http://realraum.at", "http://realraum.at/logo-red_250x250.png", "http://realraum.at/logo-re_open_100x100.png", "http://realraum.at/logo-re_empty_100x100.png",47.065779129, 15.442322614).AddSpaceAddress("Jakoministr. 16 ground level left, 8010 Graz, Austria") statusstate *SpaceState = new(SpaceState) + xmpp_presence_events_chan_ chan interface{} ) //------- @@ -75,18 +79,24 @@ func parseSocketInputLine(line string) { if match_presence != nil { statusstate.present = (match_presence[1] == "yes") + statusstate.door_locked = (match_presence[2] == "closed") //spaceapidata.MergeInSensor(spaceapi.MakeDoorLockSensor("Torwaechter", "Front Door", match_presence[2] == "closed")) - spaceapidata.MergeInSensor(spaceapi.MakeDoorLockSensor("TorwaechterLock", "Türschloß", match_presence[2] == "closed")) + spaceapidata.MergeInSensor(spaceapi.MakeDoorLockSensor("TorwaechterLock", "Türschloß", statusstate.door_locked)) publishStateToWeb() + xmpp_presence_events_chan_ <- r3xmppbot.PresenceEvent{statusstate.present, statusstate.door_locked, statusstate.door_shut} } else if match_status != nil { - spaceapidata.MergeInSensor(spaceapi.MakeDoorLockSensor("TorwaechterLock", "Türschloß", match_status[1] == "closed")) - spaceapidata.MergeInSensor(spaceapi.MakeDoorLockSensor("TorwaechterAjarSensor", "Türkontakt", match_status[3] == "shut")) + statusstate.door_locked = (match_status[1] == "closed") + statusstate.door_shut = (match_status[3] == "shut") + spaceapidata.MergeInSensor(spaceapi.MakeDoorLockSensor("TorwaechterLock", "Türschloß", statusstate.door_locked)) + spaceapidata.MergeInSensor(spaceapi.MakeDoorLockSensor("TorwaechterAjarSensor", "Türkontakt", statusstate.door_shut)) //spaceapidata.MergeInSensor(spaceapi.MakeDoorAjarSensor("Torwaechter", "Front Door", match_presence[3] == "shut")) publishStateToWeb() + xmpp_presence_events_chan_ <- r3xmppbot.PresenceEvent{statusstate.present, statusstate.door_locked, statusstate.door_shut} } else if match_button != nil { statusstate.buttonpress_until = time.Now().Unix() + 3600 spaceapidata.AddSpaceEvent("PanicButton", "check-in", "The button has been pressed") publishStateToWeb() + xmpp_presence_events_chan_ <- "The button has been pressed ! Propably someone is bored and need company ! ;-)" } else if match_temp != nil { newtemp, err := strconv.ParseFloat((match_temp[1]), 32) if err == nil { @@ -127,7 +137,24 @@ func main() { spaceapidata.AddSpaceFeed("blog", "https://plus.google.com/113737596421797426873") spaceapidata.AddSpaceFeed("wiki", "http://realraum.at/wiki") spaceapidata.AddSpaceContactInfo("+43780700888524", "irc://irc.oftc.net/#realraum", "realraum@realraum.at", "realraum@realraum.at", "realraum@realraum.at", "vorstand@realraum.at") - eventqueue := make(chan string) + + var err error + var bot *r3xmppbot.XmppBot + bot, xmpp_presence_events_chan_, err = r3xmppbot.NewStartedBot("realrauminfo@realraum.at/Tuer", "d7ynC6Dg", "r3alraumOLGAXMPPInfos", true) + if err != nil { + fmt.Println(err) + return + } + defer bot.StopBot() + + //~ presence_events <- PresenceEvent{true, true, true} + //~ presence_events <- PresenceEvent{true, true, false} + //~ presence_events <- PresenceEvent{true, false, false} + + + eventqueue := make(chan string) + defer close(eventqueue) + ticker := time.NewTicker(time.Duration(7) * time.Minute) go readFromUSocket("/var/run/tuer/presence.socket", eventqueue) for { diff --git a/r3-webstatus-spaceapi/make_deploy.zsh b/r3-webstatus-spaceapi/make_deploy.zsh new file mode 100644 index 0000000..9b710fc --- /dev/null +++ b/r3-webstatus-spaceapi/make_deploy.zsh @@ -0,0 +1,4 @@ +#!/bin/zsh +go-linux-386 build -ldflags "-s -d" +strip r3-webstatus-spaceapi +rsync -v r3-webstatus-spaceapi wuzzler.realraum.at:/flash/tuer/ \ No newline at end of file diff --git a/r3-webstatus-spaceapi/r3xmppbot/r3xmppbot.go b/r3-webstatus-spaceapi/r3xmppbot/r3xmppbot.go new file mode 100644 index 0000000..465ee95 --- /dev/null +++ b/r3-webstatus-spaceapi/r3xmppbot/r3xmppbot.go @@ -0,0 +1,367 @@ +package r3xmppbot + +import ( + xmpp "code.google.com/p/goexmpp" + "fmt" + "log" + "crypto/tls" + "os" + "time" + "encoding/json" + "path" +) + +//~ type StdLogger struct { +//~ } + +//~ func (s *StdLogger) Log(v ...interface{}) { + //~ log.Println(v...) +//~ } + +//~ func (s *StdLogger) Logf(fmt string, v ...interface{}) { + //~ log.Printf(fmt, v...) +//~ } + +func (botdata *XmppBot) makeXMPPMessage(to, message string, subject interface{}) *xmpp.Message { + xmppmsgheader := xmpp.Header{To: to, + From: botdata.my_jid_, + Id: <-xmpp.Id, + Type: "chat", + Lang: "", + Innerxml: "", + Error: nil, + Nested: make([]interface{},0)} + msgsubject := xmpp.Generic{} + if subject != nil { + msgsubject.Chardata = subject.(string) + } + return &xmpp.Message{Header: xmppmsgheader , Subject: &msgsubject, Body: &xmpp.Generic{Chardata:message}, Thread: &xmpp.Generic{}} +} + +func (botdata *XmppBot) makeXMPPPresence(to, ptype string) *xmpp.Presence { + xmppmsgheader := xmpp.Header{To: to, + From: botdata.my_jid_, + Id: <-xmpp.Id, + Type: ptype, + Lang: "", + Innerxml: "", + Error: nil, + Nested: make([]interface{},0)} + return &xmpp.Presence{Header: xmppmsgheader} +} + +type R3JIDDesire int + +const ( + R3NoChange R3JIDDesire = -1 + R3NoInfo R3JIDDesire = iota // ignore first value by assigning to blank identifier + R3NoOfflineInfo + R3AllInfo + R3DebugInfo +) + +type JidData struct { + Online bool + Wants R3JIDDesire +} + +type JabberEvent struct { + JID string + Online bool + Wants R3JIDDesire +} + +type PresenceEvent struct { + Present bool + DoorLock bool + DoorAjar bool +} + +type RealraumXmppNotifierConfig map[string]JidData + +type XmppBot struct { + jid_lastauthtime_ map[string]int64 + realraum_jids_ RealraumXmppNotifierConfig + password_ string + auth_cmd_ string + my_jid_ string + auth_timeout_ int64 + config_file_ string + my_login_password_ string + xmppclient_ *xmpp.Client + presence_events_ *chan interface{} +} + + +func (data RealraumXmppNotifierConfig) saveTo(filepath string) () { + fh, err := os.Create(filepath) + if err != nil { + log.Println(err) + return + } + defer fh.Close() + enc := json.NewEncoder(fh) + if err = enc.Encode(&data); err != nil { + log.Println(err) + return + } +} + +func (data RealraumXmppNotifierConfig) loadFrom(filepath string) () { + fh, err := os.Open(filepath) + if err != nil { + log.Println(err) + return + } + defer fh.Close() + dec := json.NewDecoder(fh) + if err = dec.Decode(&data); err != nil { + log.Println(err) + return + } + for to, jiddata := range data { + jiddata.Online = false + data[to]=jiddata + } +} + + +func init() { + //~ logger := &StdLogger{} + //~ xmpp.Debug = logger + //~ xmpp.Info = logger + //~ xmpp.Warn = logger +} + +func (botdata *XmppBot) handleEventsforXMPP(xmppout chan <- xmpp.Stanza, presence_events <- chan interface{}, jabber_events <- chan JabberEvent) { + var msg, presence_str, lock_str, ajar_str string + last_presence := false + var debug_msg bool + + for { + debug_msg = false + select { + case pe := <-presence_events: + switch pec := pe.(type) { + case xmpp.Stanza: + xmppout <- pec + continue + case string: + msg = pec + case PresenceEvent: + if pec.Present { + presence_str = "Somebody is present !" + } else { + presence_str = "Everybody left." + } + if pec.DoorLock { + lock_str = "locked" + } else { + lock_str = "unlocked" + } + if pec.DoorAjar { + ajar_str = "ajar" + } else { + ajar_str = "shut" + } + msg = fmt.Sprintf("%s (Door is %s and %s)", presence_str, lock_str, ajar_str) + if last_presence == pec.Present { + debug_msg = true + } else { + last_presence = pec.Present + } + default: + break + } + + for to, jiddata := range botdata.realraum_jids_ { + if debug_msg && jiddata.Wants < R3DebugInfo { + continue + } + if (jiddata.Wants == R3NoOfflineInfo && jiddata.Online) || jiddata.Wants > R3NoOfflineInfo { + xmppout <- botdata.makeXMPPMessage(to, msg, nil) + } //else { + //~ fmt.Println("Not sending to ", to, jiddata) + //~ } + } + + case je := <-jabber_events: + jid_data, jid_in_map := botdata.realraum_jids_[je.JID] + if jid_in_map { + jid_data.Online = je.Online + if je.Wants > R3NoChange { + jid_data.Wants = je.Wants + } + botdata.realraum_jids_[je.JID] = jid_data + botdata.realraum_jids_.saveTo(botdata.config_file_) + } else if je.Wants > R3NoChange { + botdata.realraum_jids_[je.JID] = JidData{je.Online, je.Wants} + botdata.realraum_jids_.saveTo(botdata.config_file_) + } + } + } +} + +func removeJIDResource(jid string) string { + var jidjid xmpp.JID + jidjid.Set(jid) + jidjid.Resource = "" + return jidjid.String() +} + +func (botdata *XmppBot) isAuthenticated(jid string) bool { + authtime, in_map := botdata.jid_lastauthtime_[jid] + //~ log.Println("isAuthenticated", in_map, authtime, time.Now().Unix(), auth_timeout_, time.Now().Unix() - authtime > auth_timeout_) + return in_map && time.Now().Unix() - authtime < botdata.auth_timeout_ +} + +const help_text_ string = "\nauth \n .... enables you to use the other commands\non\n .... you will be notified of r3 status changes\noff\n .... you will no longer recieve notifications\non_while_offline\n .... you will be notified of r3 status changes even if you are offline" + +//~ var re_msg_auth_ *regexp.Regexp = regexp.MustCompile("auth\s+(\S+)") + +func (botdata *XmppBot) handleIncomingMessageDialog(inmsg xmpp.Message, xmppout chan<- xmpp.Stanza, jabber_events chan JabberEvent) { + if inmsg.Body == nil || inmsg.GetHeader() == nil { + return + } + bodytext :=inmsg.Body.Chardata + //~ log.Println("Message Body:", bodytext) + if botdata.isAuthenticated(inmsg.GetHeader().From) { + switch bodytext { + case "on": + jabber_events <- JabberEvent{removeJIDResource(inmsg.GetHeader().From), true, R3NoOfflineInfo} + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, "Receive r3 information while online" , "Your New Status") + case "off": + jabber_events <- JabberEvent{removeJIDResource(inmsg.GetHeader().From), true, R3NoInfo} + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, "Do not receive r3 information" , "Your New Status") + case "on_while_offline": + jabber_events <- JabberEvent{removeJIDResource(inmsg.GetHeader().From), true, R3AllInfo} + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, "Receive r3 information even while offline" , "Your New Status") + case "debug": + jabber_events <- JabberEvent{removeJIDResource(inmsg.GetHeader().From), true, R3DebugInfo} + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, "Debug mode enabled" , "Your New Status") + case "bye", "Bye", "quit", "logout": + botdata.jid_lastauthtime_[inmsg.GetHeader().From] = 0 + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, "Bye Bye !" ,nil) + default: + //~ auth_match = re_msg_auth_.FindStringSubmatch(inmsg.Body.Chardata) + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, help_text_, "Available Commands") + } + } else { + switch bodytext { + case "Hilfe","hilfe","help","Help","?","hallo","Hallo","Yes","yes","ja","ja bitte","bitte","sowieso": + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, help_text_, "Available Commands") + case botdata.auth_cmd_: + botdata.jid_lastauthtime_[inmsg.GetHeader().From] = time.Now().Unix() + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, "You are now authorized to use commands" , "Authorized") + default: + //~ auth_match = re_msg_auth_.FindStringSubmatch(inmsg.Body.Chardata) + xmppout <- botdata.makeXMPPMessage(inmsg.GetHeader().From, "A nice day to you too !\nDo you need \"help\" ?", nil) + } + } +} + +func (botdata *XmppBot) handleIncomingXMPPStanzas(xmppin <- chan xmpp.Stanza, xmppout chan<- xmpp.Stanza, jabber_events chan JabberEvent) { + var incoming_stanza interface{} + for incoming_stanza = range xmppin { + switch stanza := incoming_stanza.(type) { + case *xmpp.Message: + botdata.handleIncomingMessageDialog(*stanza, xmppout, jabber_events) + case *xmpp.Presence: + if stanza.GetHeader() == nil { + continue + } + if stanza.GetHeader().Type == "subscribe" { + xmppout <- botdata.makeXMPPPresence(stanza.GetHeader().From, "subscribed") + } + jabber_events <- JabberEvent{stanza.GetHeader().From, stanza.GetHeader().Type != "unavailable", R3NoChange} + case *xmpp.Iq: + if stanza.GetHeader() == nil { + continue + } + } + } +} + +var default_state_save_dir_ string = "/flash/var/lib/r3netstatus/" + +func NewStartedBot(loginjid, loginpwd, password string, insecuretls bool) (*XmppBot, chan interface{}, error) { + var err error + botdata := new(XmppBot) + + botdata.realraum_jids_ = make(map[string]JidData, 1) + botdata.jid_lastauthtime_ = make(map[string]int64,1) + botdata.auth_cmd_ = "auth " + password + botdata.my_jid_ = loginjid + botdata.my_login_password_ = loginpwd + botdata.auth_timeout_ = 1200 + + botdata.config_file_ = path.Join(default_state_save_dir_, "r3xmpp."+removeJIDResource(loginjid)+".json") + + //~ log.Println(botdata.config_file_) + + //~ logger := &StdLogger{} + //~ xmpp.Debug = logger + //~ xmpp.Info = logger + //~ xmpp.Warn = logger + + xmpp.TlsConfig = tls.Config{InsecureSkipVerify: insecuretls} + botdata.realraum_jids_.loadFrom(botdata.config_file_) + + client_jid := new(xmpp.JID) + client_jid.Set(botdata.my_jid_) + botdata.xmppclient_, err = xmpp.NewClient(client_jid, botdata.my_login_password_, nil) + if err != nil { + log.Println("Error connecting to xmpp server", err) + return nil, nil, err + } + + err = botdata.xmppclient_.StartSession(false, &xmpp.Presence{}) + if err != nil { + log.Println("'Error StartSession:", err) + return nil, nil, err + } + + presence_events := make(chan interface{},1) + jabber_events := make(chan JabberEvent,1) + + go botdata.handleEventsforXMPP(botdata.xmppclient_.Out, presence_events, jabber_events) + go botdata.handleIncomingXMPPStanzas(botdata.xmppclient_.In, botdata.xmppclient_.Out, jabber_events) + + botdata.presence_events_ = &presence_events + + return botdata, presence_events, nil +} + +func (botdata *XmppBot) StopBot() { + if botdata.xmppclient_ != nil { + close(botdata.xmppclient_.Out) + } + if botdata.presence_events_ != nil { + *botdata.presence_events_ <- false + close(*botdata.presence_events_) + } +} + +//~ func main() { + //~ bot, presence_events, err := NewStartedBot("realrauminfo@realraum.at/Tuer", "d7ynC6Dg", "r3alraumOLGAXMPPInfos", true) + + //~ if err != nil { + //~ log.Println(err) + //~ os.Exit(1) + //~ } + + //~ presence_events <- PresenceEvent{true, true, true} + //~ presence_events <- PresenceEvent{true, true, false} + //~ presence_events <- PresenceEvent{true, false, false} + + //~ p := make([]byte, 1024) + //~ for + //~ { + //~ nr, _ := os.Stdin.Read(p) + //~ if nr == 0 { + //~ break + //~ } + //~ } + + //~ bot.StopBot() +//~ } -- 1.7.10.4