Skip to content

Commit 947fcf0

Browse files
remicornierejsautret
authored andcommitted
PubSub protocol support (#142)
* PubSub protocol support Added support for : - XEP-0050 (Command)) - XEP-0060 (PubSub) - XEP-0004 (Forms) Fixed the NewClient function by adding parsing of the domain from the JID if no domain is provided in transport config. Updated xmpp_jukebox example * Delete useless pubsub errors * README.md update Fixed import in echo example * Typo * Fixed raw send on client example * Fixed jukebox example and added a README.md
1 parent 6e2ba9c commit 947fcf0

29 files changed

+3375
-51
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ config := xmpp.Config{
5252
- [XEP-0355: Namespace Delegation](https://xmpp.org/extensions/xep-0355.html)
5353
- [XEP-0356: Privileged Entity](https://xmpp.org/extensions/xep-0356.html)
5454

55+
### Extensions
56+
- [XEP-0060: Publish-Subscribe](https://xmpp.org/extensions/xep-0060.html)
57+
Note : "6.5.4 Returning Some Items" requires support for [XEP-0059: Result Set Management](https://xmpp.org/extensions/xep-0059.html),
58+
and is therefore not supported yet.
59+
- [XEP-0004: Data Forms](https://xmpp.org/extensions/xep-0004.html)
60+
- [XEP-0050: Ad-Hoc Commands](https://xmpp.org/extensions/xep-0050.html)
61+
5562
## Package overview
5663

5764
### Stanza subpackage
@@ -108,15 +115,16 @@ func main() {
108115
Address: "localhost:5222",
109116
},
110117
Jid: "test@localhost",
111-
Credential: xmpp.Password("Test"),
118+
Credential: xmpp.Password("test"),
112119
StreamLogger: os.Stdout,
113120
Insecure: true,
121+
// TLSConfig: tls.Config{InsecureSkipVerify: true},
114122
}
115123

116124
router := xmpp.NewRouter()
117125
router.HandleFunc("message", handleMessage)
118126

119-
client, err := xmpp.NewClient(config, router)
127+
client, err := xmpp.NewClient(config, router, errorHandler)
120128
if err != nil {
121129
log.Fatalf("%+v", err)
122130
}
@@ -138,6 +146,11 @@ func handleMessage(s xmpp.Sender, p stanza.Packet) {
138146
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
139147
_ = s.Send(reply)
140148
}
149+
150+
func errorHandler(err error) {
151+
fmt.Println(err.Error())
152+
}
153+
141154
```
142155

143156
## Reference documentation

_examples/delegation/delegation.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
171171
return
172172
}
173173

174-
pubsub, ok := forwardedIQ.Payload.(*stanza.PubSub)
174+
pubsub, ok := forwardedIQ.Payload.(*stanza.PubSubGeneric)
175175
if !ok {
176176
// We only support pubsub delegation
177177
return
@@ -180,7 +180,7 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
180180
if pubsub.Publish.XMLName.Local == "publish" {
181181
// Prepare pubsub IQ reply
182182
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
183-
payload := stanza.PubSub{
183+
payload := stanza.PubSubGeneric{
184184
XMLName: xml.Name{
185185
Space: "http://jabber.org/protocol/pubsub",
186186
Local: "pubsub",

_examples/xmpp_chat_client/interface.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ func writeInput(g *gocui.Gui, v *gocui.View) error {
136136
input := strings.Join(v.ViewBufferLines(), "\n")
137137

138138
fmt.Fprintln(chatLogWindow, "Me : ", input)
139-
textChan <- input
139+
if viewState.input == rawInputWindow {
140+
rawTextChan <- input
141+
} else {
142+
textChan <- input
143+
}
140144

141145
v.Clear()
142146
v.EditDeleteToStartOfLine()

_examples/xmpp_echo/xmpp_echo.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func main() {
2828
router := xmpp.NewRouter()
2929
router.HandleFunc("message", handleMessage)
3030

31-
client, err := xmpp.NewClient(config, router)
31+
client, err := xmpp.NewClient(config, router, errorHandler)
3232
if err != nil {
3333
log.Fatalf("%+v", err)
3434
}
@@ -50,3 +50,7 @@ func handleMessage(s xmpp.Sender, p stanza.Packet) {
5050
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
5151
_ = s.Send(reply)
5252
}
53+
54+
func errorHandler(err error) {
55+
fmt.Println(err.Error())
56+
}

_examples/xmpp_jukebox/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Jukebox example
2+
3+
## Requirements
4+
- You need mpg123 installed on your computer because the example runs it as a command :
5+
[Official MPG123 website](https://mpg123.de/)
6+
Most linux distributions have a package for it.
7+
- You need a soundcloud ID to play a music from the website through mpg123. You currently cannot play music files with this example.
8+
Your user ID is available in your account settings on the [soundcloud website](https://soundcloud.com/)
9+
**One is provided for convenience.**
10+
- You need a running jabber server. You can run your local instance of [ejabberd](https://www.ejabberd.im/) for example.
11+
- You need a registered user on the running jabber server.
12+
13+
## Run
14+
You can edit the soundcloud ID in the example file with your own, or use the provided one :
15+
```go
16+
const scClientID = "dde6a0075614ac4f3bea423863076b22"
17+
```
18+
19+
To run the example, build it with (while in the example directory) :
20+
```
21+
go build xmpp_jukebox.go
22+
```
23+
24+
then run it with (update the command arguments accordingly):
25+
```
26+
./xmpp_jukebox -jid=MY_USERE@MY_DOMAIN/jukebox -password=MY_PASSWORD -address=MY_SERVER:MY_SERVER_PORT
27+
```
28+
Make sure to have a resource, for instance "/jukebox", on your jid.
29+
30+
Then you can send the following stanza to "MY_USERE@MY_DOMAIN/jukebox" (with the resource) to play a song (update the soundcloud URL accordingly) :
31+
```xml
32+
<iq id="1" to="MY_USERE@MY_DOMAIN/jukebox" type="set">
33+
<set xml:lang="en" xmlns="urn:xmpp:iot:control">
34+
<string name="url" value="https://soundcloud.com/UPDATE/ME"/>
35+
</set>
36+
</iq>
37+
```

_examples/xmpp_jukebox/xmpp_jukebox.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package main
44

55
import (
6+
"encoding/xml"
67
"flag"
78
"fmt"
89
"log"
@@ -19,7 +20,7 @@ import (
1920
const scClientID = "dde6a0075614ac4f3bea423863076b22"
2021

2122
func main() {
22-
jid := flag.String("jid", "", "jukebok XMPP JID, resource is optional")
23+
jid := flag.String("jid", "", "jukebok XMPP Jid, resource is optional")
2324
password := flag.String("password", "", "XMPP account password")
2425
address := flag.String("address", "", "If needed, XMPP server DNSName or IP and optional port (ie myserver:5222)")
2526
flag.Parse()
@@ -48,7 +49,7 @@ func main() {
4849
handleMessage(s, p, player)
4950
})
5051
router.NewRoute().
51-
Packet("message").
52+
Packet("iq").
5253
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
5354
handleIQ(s, p, player)
5455
})
@@ -108,19 +109,37 @@ func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
108109
}
109110

110111
func sendUserTune(s xmpp.Sender, artist string, title string) {
111-
tune := stanza.Tune{Artist: artist, Title: title}
112-
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, Id: "usertune-1", Lang: "en"})
113-
payload := stanza.PubSub{Publish: &stanza.Publish{Node: "http://jabber.org/protocol/tune", Item: stanza.Item{Tune: &tune}}}
114-
iq.Payload = &payload
115-
_ = s.Send(iq)
112+
rq, err := stanza.NewPublishItemRq("localhost",
113+
"http://jabber.org/protocol/tune",
114+
"",
115+
stanza.Item{
116+
XMLName: xml.Name{Space: "http://jabber.org/protocol/tune", Local: "tune"},
117+
Any: &stanza.Node{
118+
Nodes: []stanza.Node{
119+
{
120+
XMLName: xml.Name{Local: "artist"},
121+
Content: artist,
122+
},
123+
{
124+
XMLName: xml.Name{Local: "title"},
125+
Content: title,
126+
},
127+
},
128+
},
129+
})
130+
if err != nil {
131+
fmt.Printf("failed to build the publish request : %s", err.Error())
132+
return
133+
}
134+
_ = s.Send(rq)
116135
}
117136

118137
func playSCURL(p *mpg123.Player, rawURL string) {
119138
songID, _ := soundcloud.GetSongID(rawURL)
120139
// TODO: Maybe we need to check the track itself to get the stream URL from reply ?
121140
url := soundcloud.FormatStreamURL(songID)
122141

123-
_ = p.Play(url)
142+
_ = p.Play(strings.ReplaceAll(url, "YOUR_SOUNDCLOUD_CLIENTID", scClientID))
124143
}
125144

126145
// TODO

client.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ Setting up the client / Checking the parameters
107107
*/
108108

109109
// NewClient generates a new XMPP client, based on Config passed as parameters.
110-
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
110+
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the Jid.
111111
// Default the port to 5222.
112112
func NewClient(config Config, r *Router, errorHandler func(error)) (c *Client, err error) {
113113
if config.KeepaliveInterval == 0 {
114114
config.KeepaliveInterval = time.Second * 30
115115
}
116-
// Parse JID
117-
if config.parsedJid, err = NewJid(config.Jid); err != nil {
116+
// Parse Jid
117+
if config.parsedJid, err = stanza.NewJid(config.Jid); err != nil {
118118
err = errors.New("missing jid")
119119
return nil, NewConnError(err, true)
120120
}
@@ -142,6 +142,11 @@ func NewClient(config Config, r *Router, errorHandler func(error)) (c *Client, e
142142
}
143143
}
144144
}
145+
if config.Domain == "" {
146+
// Fallback to jid domain
147+
config.Domain = config.parsedJid.Domain
148+
}
149+
145150
c = new(Client)
146151
c.config = config
147152
c.router = r

cmd/fluuxmpp/send.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bufio"
5+
"gosrc.io/xmpp/stanza"
56
"os"
67
"strings"
78
"sync"
@@ -48,7 +49,7 @@ func sendxmpp(cmd *cobra.Command, args []string) {
4849
wg.Add(1)
4950

5051
// FIXME: Remove global variables
51-
var mucsToLeave []*xmpp.Jid
52+
var mucsToLeave []*stanza.Jid
5253

5354
cm := xmpp.NewStreamManager(client, func(c xmpp.Sender) {
5455
defer wg.Done()
@@ -57,7 +58,7 @@ func sendxmpp(cmd *cobra.Command, args []string) {
5758

5859
if isMUCRecipient {
5960
for _, muc := range receiver {
60-
jid, err := xmpp.NewJid(muc)
61+
jid, err := stanza.NewJid(muc)
6162
if err != nil {
6263
log.WithField("muc", muc).Errorf("skipping invalid muc jid: %w", err)
6364
continue

cmd/fluuxmpp/xmppmuc.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"gosrc.io/xmpp/stanza"
88
)
99

10-
func joinMUC(c xmpp.Sender, toJID *xmpp.Jid) error {
10+
func joinMUC(c xmpp.Sender, toJID *stanza.Jid) error {
1111
return c.Send(stanza.Presence{Attrs: stanza.Attrs{To: toJID.Full()},
1212
Extensions: []stanza.PresExtension{
1313
stanza.MucPresence{
@@ -16,7 +16,7 @@ func joinMUC(c xmpp.Sender, toJID *xmpp.Jid) error {
1616
})
1717
}
1818

19-
func leaveMUCs(c xmpp.Sender, mucsToLeave []*xmpp.Jid) {
19+
func leaveMUCs(c xmpp.Sender, mucsToLeave []*stanza.Jid) {
2020
for _, muc := range mucsToLeave {
2121
if err := c.Send(stanza.Presence{Attrs: stanza.Attrs{
2222
To: muc.Full(),

config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package xmpp
22

33
import (
4+
"gosrc.io/xmpp/stanza"
45
"os"
56
"time"
67
)
@@ -11,7 +12,7 @@ type Config struct {
1112
TransportConfiguration
1213

1314
Jid string
14-
parsedJid *Jid // For easier manipulation
15+
parsedJid *stanza.Jid // For easier manipulation
1516
Credential Credential
1617
StreamLogger *os.File // Used for debugging
1718
Lang string // TODO: should default to 'en'

0 commit comments

Comments
 (0)