From 62dfe9d3ac8dfdbfc2b9ea5ec95c802edc666ea6 Mon Sep 17 00:00:00 2001 From: Martin Dosch Date: Fri, 31 May 2019 23:45:29 +0200 Subject: [PATCH] Distributed the code on several files to get a better overview. --- feed-to-muc.go | 349 ++----------------------- getarticles.go => getArticles.go | 0 openConfig.go | 76 ++++++ processStanzas.go | 209 +++++++++++++++ removetracking.go => removeTracking.go | 0 sendPings.go | 56 ++++ 6 files changed, 360 insertions(+), 330 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 c3e2cf9..b3093ba 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,245 +103,3 @@ func main() { time.Sleep(config.RefreshTime * time.Second) } } - -// 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) { - 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.") - } - } -} - -// 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) - } - } -} - -func processStanzas(client *xmpp.Client, muc string, mucNick string, feeds []string, quiet bool, contact string) { - for { // Receive stanzas. ToDo: Receive stanzas continiously without 30s delay. - 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.") - } - - 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/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) + } + } +}