From 6e8ac0866cea8d22b829e73ab017063ab8075143 Mon Sep 17 00:00:00 2001 From: Martin Dosch Date: Fri, 31 May 2019 23:45:29 +0200 Subject: [PATCH] Cherrypicked 62dfe9d3ac8dfdbfc2b9ea5ec95c802edc666ea6 --- feed-to-muc.go | 110 +++---------- getarticles.go => getArticles.go | 0 openConfig.go | 76 +++++++++ processStanzas.go | 209 +++++++++++++++++++++++++ removetracking.go => removeTracking.go | 0 sendPings.go | 56 +++++++ 6 files changed, 363 insertions(+), 88 deletions(-) rename getarticles.go => getArticles.go (100%) create mode 100644 openConfig.go create mode 100644 processStanzas.go rename removetracking.go => removeTracking.go (100%) create mode 100644 sendPings.go diff --git a/feed-to-muc.go b/feed-to-muc.go index ad35701..4d7476c 100644 --- a/feed-to-muc.go +++ b/feed-to-muc.go @@ -4,112 +4,43 @@ Licensed under the "MIT License" */ package main import ( - "encoding/json" "flag" "log" - "os" - "os/user" - "strings" "time" - "github.com/chilts/sid" "github.com/mattn/go-xmpp" ) +type configuration struct { + ServerAddress string + BotJid string + Password string + Muc string + MucNick string + MaxArticles int + RefreshTime time.Duration + NoExcerpt bool + Quiet bool + Contact string + Filter []string + Feeds []string +} + // Variables defined globally as they are used by functions pingMUC // and processStanzas. var ( - id string - err error + ID string pingSent time.Time pingReceived bool ) func main() { - var configPath, configFile string - - type configuration struct { - ServerAddress string - BotJid string - Password string - Muc string - MucNick string - MaxArticles int - RefreshTime time.Duration - NoExcerpt bool - Quiet bool - Contact string - Filter []string - Feeds []string - } - // Read path to config from command line option. configFilePtr := flag.String("config", "none", "path to configuration file") flag.Parse() - if *configFilePtr != "none" { - configFile = *configFilePtr - } else { - // Get systems user config path. - osConfigDir := os.Getenv("$XDG_CONFIG_HOME") - if osConfigDir != "" { - // Create configPath if not yet existing. - configPath = osConfigDir + "/.config/feed-to-muc/" - if _, err := os.Stat(configPath); os.IsNotExist(err) { - err = os.MkdirAll(configPath, 0700) - if err != nil { - log.Fatal("Error: Can't create config path: ", err) - } - } - - } else { // Get the current user. - curUser, err := user.Current() - if err != nil { - log.Fatal("Error: Can't get current user: ", err) - return - } - // Get home directory. - home := curUser.HomeDir - - if home == "" { - log.Fatal("Error: No home directory available.") - return - } - - // Create configPath if not yet existing. - configPath = home + "/.config/feed-to-muc/" - if _, err := os.Stat(configPath + "config.json"); os.IsNotExist(err) { - err = os.MkdirAll(configPath, 0700) - if err != nil { - log.Fatal("Error: Can't create config path: ", err) - } - } - - } - configFile = configPath + "config.json" - } - - // Check that config file is existing. - if _, err := os.Stat(configFile); os.IsNotExist(err) { - log.Fatal("Error: Can't find config file: ", err) - } - - // Read configuration file into variable config. - file, _ := os.Open(configFile) - defer file.Close() - decoder := json.NewDecoder(file) - config := configuration{} - if err := decoder.Decode(&config); err != nil { - log.Fatal("Error: Can't decode config: ", err) - } - - if _, err := os.Stat(configFile); os.IsNotExist(err) { - err = os.MkdirAll(configPath, 0700) - if err != nil { - log.Fatal("Error: Can't create config path: ", err) - } - } + config := openConfig(configFilePtr) var client *xmpp.Client @@ -123,13 +54,13 @@ func main() { } // Connect to server. - client, err = options.NewClient() + client, err := options.NewClient() if err != nil { log.Fatal("Error: Can't connect to xmpp server: ", err) } // Join the MUC - _, err := client.JoinMUCNoHistory(config.Muc, config.MucNick) + _, err = client.JoinMUCNoHistory(config.Muc, config.MucNick) if err != nil { log.Fatal("Error: Can't join MUC: ", err) } @@ -172,6 +103,7 @@ func main() { time.Sleep(config.RefreshTime * time.Second) } } +<<<<<<< HEAD // Send a ping every 30 seconds after last successful ping to check if the MUC is still available. func pingMUC(client *xmpp.Client, botJid string, muc string, mucNick string) { @@ -414,3 +346,5 @@ func processStanzas(client *xmpp.Client, muc string, mucNick string, feeds []str } } } +======= +>>>>>>> 62dfe9d... Distributed the code on several files to get a better overview. diff --git a/getarticles.go b/getArticles.go similarity index 100% rename from getarticles.go rename to getArticles.go diff --git a/openConfig.go b/openConfig.go new file mode 100644 index 0000000..1f08864 --- /dev/null +++ b/openConfig.go @@ -0,0 +1,76 @@ +package main + +import ( + "encoding/json" + "log" + "os" + "os/user" +) + +func openConfig(configFilePtr *string) configuration { + var ( + configFile string + configPath string + ) + if *configFilePtr != "none" { + configFile = *configFilePtr + } else { + // Get systems user config path. + osConfigDir := os.Getenv("$XDG_CONFIG_HOME") + if osConfigDir != "" { + // Create configPath if not yet existing. + configPath = osConfigDir + "/.config/feed-to-muc/" + if _, err := os.Stat(configPath); os.IsNotExist(err) { + err = os.MkdirAll(configPath, 0700) + if err != nil { + log.Fatal("Error: Can't create config path: ", err) + } + } + + } else { // Get the current user. + curUser, err := user.Current() + if err != nil { + log.Fatal("Error: Can't get current user: ", err) + } + // Get home directory. + home := curUser.HomeDir + + if home == "" { + log.Fatal("Error: No home directory available.") + } + + // Create configPath if not yet existing. + configPath = home + "/.config/feed-to-muc/" + if _, err := os.Stat(configPath + "config.json"); os.IsNotExist(err) { + err = os.MkdirAll(configPath, 0700) + if err != nil { + log.Fatal("Error: Can't create config path: ", err) + } + } + + } + configFile = configPath + "config.json" + } + + // Check that config file is existing. + if _, err := os.Stat(configFile); os.IsNotExist(err) { + log.Fatal("Error: Can't find config file: ", err) + } + + // Read configuration file into variable config. + file, _ := os.Open(configFile) + defer file.Close() + decoder := json.NewDecoder(file) + config := configuration{} + if err := decoder.Decode(&config); err != nil { + log.Fatal("Error: Can't decode config: ", err) + } + + if _, err := os.Stat(configFile); os.IsNotExist(err) { + err = os.MkdirAll(configPath, 0700) + if err != nil { + log.Fatal("Error: Can't create config path: ", err) + } + } + return config +} diff --git a/processStanzas.go b/processStanzas.go new file mode 100644 index 0000000..4affea6 --- /dev/null +++ b/processStanzas.go @@ -0,0 +1,209 @@ +package main + +import ( + "log" + "strings" + + "github.com/mattn/go-xmpp" +) + +func processStanzas(client *xmpp.Client, muc string, mucNick string, feeds []string, quiet bool, contact string) { + for { + // Receive stanzas. + stanza, err := client.Recv() + if err != nil { + log.Fatal("Error: Failed receiving Stanzas:", err) + } + + // Check stanzas, maybe we want to reply. + switch v := stanza.(type) { + // Reply to requests for source and feeds. + case xmpp.Chat: + var command string + + // Check for room mention of the bots nick if the the message type is groupchat. + if v.Type == "groupchat" { + // Leave if option quiet is set. + if quiet == true { + break + } + // Get first word of the message and transform it to lower case. + mention := strings.ToLower(strings.Split(v.Text, " ")[0]) + + // If it is not the bots nick remove one trailing character as + // a lot of clients append `:` or `,` to mentions. + if mention != strings.ToLower(mucNick) { + mentionLength := len(mention) + // Leave if mentionLength is <= 0 + if mentionLength <= 0 { + break + } + mention = mention[:mentionLength-1] + // Leave if the message is not addressed to the bot. + if mention != strings.ToLower(mucNick) { + break + } + } + // As the first word is the mention of the bots nickname, the command is + // the second word in a groupchat message. + command = strings.ToLower(strings.Split(v.Text, " ")[1]) + // If the message type is chat (e.g. private message), the command is the + // first word. + } else if v.Type == "chat" { + command = strings.ToLower(strings.Split(v.Text, " ")[0]) + } else { + break + } + + // Check for the command. + switch command { + + // Reply with a short summary of available commands for `help`. + case "help": + + reply := "The following commands are available:\n" + + "\"contact\": Show contact for this bot.\n" + + "\"feeds\": List feeds I'm following.\n" + + "\"ping\": Sends back a pong.\n" + + "\"source\": Show source code URL." + + if v.Type == "groupchat" { + _, err = client.Send(xmpp.Chat{Remote: muc, + Type: "groupchat", Text: strings.Split(v.Remote, "/")[1] + ": " + reply}) + if err != nil { + log.Fatal("Error: Failed sending message to MUC:", err) + } + } else if v.Type == "chat" { + _, err = client.Send(xmpp.Chat{Remote: v.Remote, + Type: "chat", Text: reply}) + if err != nil { + log.Fatal("Error: Failed sending message to ", v.Remote, ": ", err) + } + } + + // Reply with repo address for `source`. + case "source": + + reply := "My source can be found at " + + "https://salsa.debian.org/mdosch-guest/feed-to-muc" + + if v.Type == "groupchat" { + _, err = client.Send(xmpp.Chat{Remote: muc, + Type: "groupchat", Text: strings.Split(v.Remote, "/")[1] + ": " + reply}) + if err != nil { + log.Fatal("Error: Failed sending message to MUC:", err) + } + } else if v.Type == "chat" { + _, err = client.Send(xmpp.Chat{Remote: v.Remote, + Type: "chat", Text: reply}) + if err != nil { + log.Fatal("Error: Failed sending message to ", v.Remote, ": ", err) + } + } + // Reply with the list of monitored feeds for `feeds`. + case "feeds": + var feedList string + for _, feed := range feeds { + // Add next feed element and a newline. + feedList = feedList + feed + "\n" + } + + reply := "Feeds I'm following:\n" + feedList + + if v.Type == "groupchat" { + _, err = client.Send(xmpp.Chat{Remote: muc, + Type: "groupchat", Text: strings.Split(v.Remote, "/")[1] + ": " + reply}) + if err != nil { + log.Fatal("Error: Failed sending message to MUC:", err) + } + } else if v.Type == "chat" { + _, err = client.Send(xmpp.Chat{Remote: v.Remote, + Type: "chat", Text: reply}) + if err != nil { + log.Fatal("Error: Failed sending message to ", v.Remote, ": ", err) + } + } + + case "ping": + + reply := "pong" + + if v.Type == "groupchat" { + _, err = client.Send(xmpp.Chat{Remote: muc, + Type: "groupchat", Text: strings.Split(v.Remote, "/")[1] + ": " + reply}) + if err != nil { + log.Fatal("Error: Failed sending message to MUC:", err) + } + } else if v.Type == "chat" { + _, err = client.Send(xmpp.Chat{Remote: v.Remote, + Type: "chat", Text: reply}) + if err != nil { + log.Fatal("Error: Failed sending message to ", v.Remote, ": ", err) + } + } + + case "contact": + + if contact == "" { + contact = "Sorry, no contact information provided." + } + + if v.Type == "groupchat" { + _, err = client.Send(xmpp.Chat{Remote: muc, + Type: "groupchat", Text: strings.Split(v.Remote, "/")[1] + ": " + contact}) + if err != nil { + log.Fatal("Error: Failed sending message to MUC:", err) + } + } else if v.Type == "chat" { + _, err = client.Send(xmpp.Chat{Remote: v.Remote, + Type: "chat", Text: contact}) + if err != nil { + log.Fatal("Error: Failed sending message to ", v.Remote, ": ", err) + } + } + } + + // Reply to pings and disco queries. + case xmpp.IQ: + + if (v.Type == "error") && (v.ID == ID) { + log.Fatal("MUC not available. processStanza") + } + + if (v.Type == "result") && (v.ID == ID) { + pingReceived = true + } + + if v.Type == "get" { + // Reply to disco#info requests according to https://xmpp.org/extensions/xep-0030.html. + if strings.Contains(string(v.Query), + "") == true { + _, err := client.RawInformation(client.JID(), v.From, v.ID, + "result", ""+ + ""+ + "") + if err != nil { + log.Fatal("Error: Failed to reply to disco#info:", err) + } + } else if strings.Contains(string(v.Query), "") == true { + // Reply to pings. + _, err := client.RawInformation(client.JID(), v.From, v.ID, "result", "") + if err != nil { + log.Fatal("Error: Failed to reply to ping:", err) + } + } else { + // Send error replies for all other IQs. + _, err := client.RawInformation(client.JID(), v.From, v.ID, "error", + "") + if err != nil { + log.Fatal("Error: Failed to send error IQ:", err) + } + } + } + + default: + break + } + } +} diff --git a/removetracking.go b/removeTracking.go similarity index 100% rename from removetracking.go rename to removeTracking.go diff --git a/sendPings.go b/sendPings.go new file mode 100644 index 0000000..e2077b9 --- /dev/null +++ b/sendPings.go @@ -0,0 +1,56 @@ +package main + +import ( + "log" + "strings" + "time" + + "github.com/chilts/sid" + "github.com/mattn/go-xmpp" +) + +// Send a ping every 30 seconds after last successful ping to check if the MUC is still available. +func pingMUC(client *xmpp.Client, botJid string, muc string, mucNick string) { + + var err error + + for { + time.Sleep(30 * time.Second) + + // Send ping to own MUC participant to check we are still joined. + ID, err = client.RawInformation(botJid, muc+"/"+mucNick, sid.Id(), + "get", "") + if err != nil { + log.Fatal("Error: Can't send MUC self ping: ", err) + } + + pingSent = time.Now() + pingReceived = false + + // Check for result IQ as long as there was no reply yet. + for (time.Since(pingSent).Seconds() < 10.0) && (pingReceived == false) { + time.Sleep(1 * time.Second) + if pingReceived == true { + break + } + + } + // Quit if no ping reply was received. + if pingReceived == false { + log.Fatal("MUC not available. pingMUC") + } + } +} + +// Send a ping to the server every 30 seconds to check if the connection is still alive. +func pingServer(client *xmpp.Client, server string, botJid string) { + for { + time.Sleep(30 * time.Second) + + // Send ping to server to check if connection still exists. + err := client.PingC2S(botJid, strings.Split(server, ":")[0]) + if err != nil { + log.Fatal("Error: Client2Server ping failed:", err) + } + } +}