mirror of
https://salsa.debian.org/mdosch/feed-to-muc.git
synced 2024-11-22 14:08:39 +01:00
Cherrypicked 62dfe9d3ac
This commit is contained in:
parent
d1fa786a2e
commit
6e8ac0866c
6 changed files with 363 additions and 88 deletions
110
feed-to-muc.go
110
feed-to-muc.go
|
@ -4,112 +4,43 @@ Licensed under the "MIT License" */
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chilts/sid"
|
|
||||||
"github.com/mattn/go-xmpp"
|
"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
|
// Variables defined globally as they are used by functions pingMUC
|
||||||
// and processStanzas.
|
// and processStanzas.
|
||||||
var (
|
var (
|
||||||
id string
|
ID string
|
||||||
err error
|
|
||||||
pingSent time.Time
|
pingSent time.Time
|
||||||
pingReceived bool
|
pingReceived bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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.
|
// Read path to config from command line option.
|
||||||
configFilePtr := flag.String("config", "none", "path to configuration file")
|
configFilePtr := flag.String("config", "none", "path to configuration file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *configFilePtr != "none" {
|
config := openConfig(configFilePtr)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var client *xmpp.Client
|
var client *xmpp.Client
|
||||||
|
|
||||||
|
@ -123,13 +54,13 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to server.
|
// Connect to server.
|
||||||
client, err = options.NewClient()
|
client, err := options.NewClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error: Can't connect to xmpp server: ", err)
|
log.Fatal("Error: Can't connect to xmpp server: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join the MUC
|
// Join the MUC
|
||||||
_, err := client.JoinMUCNoHistory(config.Muc, config.MucNick)
|
_, err = client.JoinMUCNoHistory(config.Muc, config.MucNick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error: Can't join MUC: ", err)
|
log.Fatal("Error: Can't join MUC: ", err)
|
||||||
}
|
}
|
||||||
|
@ -172,6 +103,7 @@ func main() {
|
||||||
time.Sleep(config.RefreshTime * time.Second)
|
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.
|
// 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) {
|
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.
|
||||||
|
|
76
openConfig.go
Normal file
76
openConfig.go
Normal file
|
@ -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
|
||||||
|
}
|
209
processStanzas.go
Normal file
209
processStanzas.go
Normal file
|
@ -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),
|
||||||
|
"<query xmlns='http://jabber.org/protocol/disco#info'/>") == true {
|
||||||
|
_, err := client.RawInformation(client.JID(), v.From, v.ID,
|
||||||
|
"result", "<query xmlns='http://jabber.org/protocol/disco#info'>"+
|
||||||
|
"<identity category='client' type='bot' name='feedbot'/>"+
|
||||||
|
"<feature var='http://jabber.org/protocol/disco#info'/></query>")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error: Failed to reply to disco#info:", err)
|
||||||
|
}
|
||||||
|
} else if strings.Contains(string(v.Query), "<ping xmlns='urn:xmpp:ping'/>") == 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",
|
||||||
|
"<error type='cancel'><service-unavailable "+
|
||||||
|
"xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error: Failed to send error IQ:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
sendPings.go
Normal file
56
sendPings.go
Normal file
|
@ -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", "<ping xmlns='urn:xmpp:ping'/>")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue