~michalr/xmpp-toolbox

0a5a009cd00f5c4ac9e74a0401db3896a7af888e — Michał Rudowicz 2 years ago c16c436 + a674aa6 master
Merge branch 'master' of git.sr.ht:~michalr/xmpp-toolbox
6 files changed, 86 insertions(+), 17 deletions(-)

M .build.yml
M .gitignore
A README.md
M go.mod
M main.go
M xmpp.go
M .build.yml => .build.yml +1 -1
@@ 3,7 3,7 @@ packages:
  - golang
  - curl
secrets:
  - 601f28e4-9b07-42e9-822e-de5a46dfb0c5
  - eb5f916b-5731-47ca-9b06-526c25632d65
tasks:
  - build-x86_64: |
      cd xmpp-toolbox

M .gitignore => .gitignore +2 -1
@@ 2,4 2,5 @@ config.json
.vscode
xmpp-toolbox
go.sum
.DS_Store
\ No newline at end of file
.DS_Store
config.env
\ No newline at end of file

A README.md => README.md +54 -0
@@ 0,0 1,54 @@
XMPP Toolbox
============

[![builds.sr.ht status](https://builds.sr.ht/~michalr/xmpp-toolbox/.build.yml.svg)](https://builds.sr.ht/~michalr/xmpp-toolbox/.build.yml?)

**Disclaimer**: I am pretty new to golang, so the code is probably of low quality. Please point out problems and my mistakes on the [mailing list](https://lists.sr.ht/~michalr/xmpp-toolbox). Thanks!

Simple XMPP tool that has two purposes:

 * Receives XMPP messages and prints their content to standard output in JSON format for further processing (for example using [jq](https://stedolan.github.io/jq/)),
 * Reads data from standard input and sends them to a specified JID as chat messages.

Both of those functionalities have to be explicitly enabled on the command line:

```
Usage of ./xmpp-toolbox:
  -listen
        If specified, will connect to XMPP and print incoming messages to the console
  -recipent string
        If specified, a messages from STDIN will be sent to provided JID

```

Exiting
-------

 * Program terminates immediately when neither `-listen` nor `-recipent` is used in the command line,
 * SIGTERM causes the program to disconnect from the XMPP server and terminate,
 * In case of `-recipent` command line option being used, program terminates when EOF is encountered on the standard input.

Configuration
-------------

Program reads the server, JID and password from the following environment variables:

 * `XMPP_SERVER`
 * `XMPP_USERNAME`
 * `XMPP_PASSWORD`

There are multiple options where to go from there. My suggestion would be to create a file like the following and `source` it before executing the toolbox:

```sh
export XMPP_SERVER="server.tld"
export XMPP_USERNAME="user@server.tld"
export XMPP_PASSWORD="password"
```

Downloads
---------

Automatically built binaries:

 * [x86_64](https://nc.fl9.eu/index.php/s/K375DQxxNGEE6YG)
 * [arm5 (Raspberry Pi)](https://nc.fl9.eu/index.php/s/T5PAPzNRWa2Dk4J)

M go.mod => go.mod +5 -1
@@ 2,4 2,8 @@ module code.fl9.eu/xmpp-toolbox

go 1.13

require gosrc.io/xmpp v0.3.0
require (
	golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
	gosrc.io/xmpp v0.4.0
	nhooyr.io/websocket v1.7.4 // indirect
)

M main.go => main.go +16 -8
@@ 25,10 25,10 @@ func listenToMessages(x XmppConnection) {
	})
}

func sendMessagesFromStdin(x XmppConnection, recipentJid *xmpp.Jid) {
func sendMessagesFromStdin(s xmpp.Sender, x XmppConnection, recipentJid *string) {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		x.Send(scanner.Text(), recipentJid)
		x.Send(s, scanner.Text(), recipentJid)
	}
	x.Disconnect()
	os.Exit(0)


@@ 41,27 41,35 @@ func main() {

	var listen = flag.Bool("listen", false, "If specified, will connect to XMPP and print incoming messages to the console")
	var messageRecipent = flag.String("recipent", "", "If specified, a messages from STDIN will be sent to provided JID")
	var recipentJid *xmpp.Jid = nil
	var recipentJid *stanza.Jid = nil
	var err error
	optionChosen := false

	flag.Parse()

	if (len(*messageRecipent) > 0) {
		recipentJid, err = xmpp.NewJid(*messageRecipent)
		recipentJid, err = stanza.NewJid(*messageRecipent)
		if (err != nil) {
			log.Fatalf("Invalid JID provided as recipent: %s", err)
		}
		optionChosen = true
	}

	xmppConnection, err := CreateXMPPConnection(xmppServer, xmppUsername, xmppPassword)
	xmppSender := make(chan xmpp.Sender)
	xmppConnection, err := CreateXMPPConnection(xmppServer, xmppUsername, xmppPassword, xmppSender)
	if (err != nil) {
		log.Fatalf("Could not connect to XMPP server: %s\n", err)
		log.Fatalf("Could not create an XMPP connection: %s\n", err)
	}

	if (*listen) {
		optionChosen = true
		listenToMessages(xmppConnection)
	}

	if (!optionChosen) {
		log.Fatal("No option chosen in the command line! Exiting")
	}

	xmppError := make(chan error)
	go xmppConnection.Run(xmppError)
	go func() {


@@ 70,12 78,12 @@ func main() {
	}()

	if (recipentJid != nil) {
		go sendMessagesFromStdin(xmppConnection, recipentJid)		
		go sendMessagesFromStdin(<- xmppSender, xmppConnection, messageRecipent)		
	}

	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
	func() {
		<-c
		xmppConnection.Disconnect()
		os.Exit(0)

M xmpp.go => xmpp.go +8 -6
@@ 1,6 1,8 @@
package main

import (
	"log"
	
	"gosrc.io/xmpp"
	"gosrc.io/xmpp/stanza"
)


@@ 23,15 25,15 @@ func (x XmppConnection) Run(c chan error) {
	c <- x.cm.Run()
}

func (x XmppConnection) Send(body string, to *xmpp.Jid) {
func (x XmppConnection) Send(s xmpp.Sender, body string, to *string) {
	msg := stanza.Message{
		Attrs: stanza.Attrs{Type: stanza.MessageTypeChat, To: to.Full()},
		Attrs: stanza.Attrs{Type: stanza.MessageTypeChat, To: *to},
		Body:  body,
	}
	x.client.Send(msg)
	s.Send(msg)
}

func CreateXMPPConnection(serverAddress string, jid string, password string) (XmppConnection, error) {
func CreateXMPPConnection(serverAddress string, jid string, password string, sender chan<- xmpp.Sender) (XmppConnection, error) {
	config := xmpp.Config{
		TransportConfiguration: xmpp.TransportConfiguration{
			Address: serverAddress,


@@ 44,14 46,14 @@ func CreateXMPPConnection(serverAddress string, jid string, password string) (Xm

	router := xmpp.NewRouter()

	client, err := xmpp.NewClient(config, router)
	client, err := xmpp.NewClient(config, router, func(e error){ log.Fatalf("Connection error: %s", e) })
	if err != nil {
		return XmppConnection{cm: nil, client: nil, router: nil}, err
	}

	// If you pass the client to a connection manager, it will handle the reconnect policy
	// for you automatically.
	cm := xmpp.NewStreamManager(client, nil)
	cm := xmpp.NewStreamManager(client, func(s xmpp.Sender){sender <- s})

	retval := XmppConnection{
		cm: cm,