2016-03-14 10:31:31 +00:00
|
|
|
// Can be launched with:
|
|
|
|
// ./xmpp_jukebox -jid=test@localhost/jukebox -password=test -address=localhost:5222
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-01-09 14:33:11 +00:00
|
|
|
"encoding/xml"
|
2016-03-14 10:31:31 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/processone/mpg123"
|
|
|
|
"github.com/processone/soundcloud"
|
2018-12-26 17:50:01 +00:00
|
|
|
"gosrc.io/xmpp"
|
2019-06-26 15:14:52 +00:00
|
|
|
"gosrc.io/xmpp/stanza"
|
2016-03-14 10:31:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Get the actual song Stream URL from SoundCloud website song URL and play it with mpg123 player.
|
|
|
|
const scClientID = "dde6a0075614ac4f3bea423863076b22"
|
|
|
|
|
|
|
|
func main() {
|
2020-01-09 14:33:11 +00:00
|
|
|
jid := flag.String("jid", "", "jukebok XMPP Jid, resource is optional")
|
2016-03-14 10:31:31 +00:00
|
|
|
password := flag.String("password", "", "XMPP account password")
|
|
|
|
address := flag.String("address", "", "If needed, XMPP server DNSName or IP and optional port (ie myserver:5222)")
|
|
|
|
flag.Parse()
|
|
|
|
|
2019-06-19 09:19:49 +00:00
|
|
|
// 1. Create mpg player
|
|
|
|
player, err := mpg123.NewPlayer()
|
2016-03-14 10:31:31 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-06-19 09:19:49 +00:00
|
|
|
// 2. Prepare XMPP client
|
|
|
|
config := xmpp.Config{
|
2019-10-11 04:41:15 +00:00
|
|
|
TransportConfiguration: xmpp.TransportConfiguration{
|
|
|
|
Address: *address,
|
|
|
|
},
|
2019-10-01 08:59:55 +00:00
|
|
|
Jid: *jid,
|
|
|
|
Credential: xmpp.Password(*password),
|
2019-06-29 08:45:25 +00:00
|
|
|
// StreamLogger: os.Stdout,
|
2019-06-19 09:19:49 +00:00
|
|
|
Insecure: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
router := xmpp.NewRouter()
|
|
|
|
router.NewRoute().
|
|
|
|
Packet("message").
|
2019-06-26 15:14:52 +00:00
|
|
|
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
|
2019-06-19 09:19:49 +00:00
|
|
|
handleMessage(s, p, player)
|
|
|
|
})
|
|
|
|
router.NewRoute().
|
2020-01-09 14:33:11 +00:00
|
|
|
Packet("iq").
|
2019-06-26 15:14:52 +00:00
|
|
|
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
|
2019-06-19 09:19:49 +00:00
|
|
|
handleIQ(s, p, player)
|
|
|
|
})
|
|
|
|
|
2019-12-05 17:12:00 +00:00
|
|
|
client, err := xmpp.NewClient(config, router, errorHandler)
|
2019-06-19 09:19:49 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("%+v", err)
|
2016-03-14 10:31:31 +00:00
|
|
|
}
|
2019-06-19 09:19:49 +00:00
|
|
|
|
|
|
|
cm := xmpp.NewStreamManager(client, nil)
|
|
|
|
log.Fatal(cm.Run())
|
2016-03-14 10:31:31 +00:00
|
|
|
}
|
2019-12-05 17:12:00 +00:00
|
|
|
func errorHandler(err error) {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
}
|
2016-03-14 10:31:31 +00:00
|
|
|
|
2019-06-26 15:14:52 +00:00
|
|
|
func handleMessage(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
|
|
|
msg, ok := p.(stanza.Message)
|
2019-06-19 09:19:49 +00:00
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
command := strings.Trim(msg.Body, " ")
|
2016-03-14 10:31:31 +00:00
|
|
|
if command == "stop" {
|
2019-06-19 09:19:49 +00:00
|
|
|
player.Stop()
|
2016-03-14 10:31:31 +00:00
|
|
|
} else {
|
2019-06-19 09:19:49 +00:00
|
|
|
playSCURL(player, command)
|
|
|
|
sendUserTune(s, "Radiohead", "Spectre")
|
2016-03-14 10:31:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-26 15:14:52 +00:00
|
|
|
func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
|
|
|
iq, ok := p.(stanza.IQ)
|
2019-06-19 09:19:49 +00:00
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
switch payload := iq.Payload.(type) {
|
2016-03-14 10:31:31 +00:00
|
|
|
// We support IOT Control IQ
|
2019-06-26 15:14:52 +00:00
|
|
|
case *stanza.ControlSet:
|
2016-03-14 10:31:31 +00:00
|
|
|
var url string
|
|
|
|
for _, element := range payload.Fields {
|
|
|
|
if element.XMLName.Local == "string" && element.Name == "url" {
|
|
|
|
url = strings.Trim(element.Value, " ")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-19 09:19:49 +00:00
|
|
|
playSCURL(player, url)
|
2019-06-26 15:14:52 +00:00
|
|
|
setResponse := new(stanza.ControlSetResponse)
|
2019-06-18 10:19:39 +00:00
|
|
|
// FIXME: Broken
|
2019-06-26 15:14:52 +00:00
|
|
|
reply := stanza.IQ{Attrs: stanza.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
|
2019-06-19 09:19:49 +00:00
|
|
|
_ = s.Send(reply)
|
2016-03-14 10:31:31 +00:00
|
|
|
// TODO add Soundclound artist / title retrieval
|
2019-06-19 09:19:49 +00:00
|
|
|
sendUserTune(s, "Radiohead", "Spectre")
|
2016-03-14 10:31:31 +00:00
|
|
|
default:
|
2019-06-19 09:19:49 +00:00
|
|
|
_, _ = fmt.Fprintf(os.Stdout, "Other IQ Payload: %T\n", iq.Payload)
|
2016-03-14 10:31:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-19 09:19:49 +00:00
|
|
|
func sendUserTune(s xmpp.Sender, artist string, title string) {
|
2020-01-09 14:33:11 +00:00
|
|
|
rq, err := stanza.NewPublishItemRq("localhost",
|
|
|
|
"http://jabber.org/protocol/tune",
|
|
|
|
"",
|
|
|
|
stanza.Item{
|
|
|
|
XMLName: xml.Name{Space: "http://jabber.org/protocol/tune", Local: "tune"},
|
|
|
|
Any: &stanza.Node{
|
|
|
|
Nodes: []stanza.Node{
|
|
|
|
{
|
|
|
|
XMLName: xml.Name{Local: "artist"},
|
|
|
|
Content: artist,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
XMLName: xml.Name{Local: "title"},
|
|
|
|
Content: title,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("failed to build the publish request : %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_ = s.Send(rq)
|
2016-03-14 10:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func playSCURL(p *mpg123.Player, rawURL string) {
|
|
|
|
songID, _ := soundcloud.GetSongID(rawURL)
|
|
|
|
// TODO: Maybe we need to check the track itself to get the stream URL from reply ?
|
|
|
|
url := soundcloud.FormatStreamURL(songID)
|
|
|
|
|
2020-01-09 14:33:11 +00:00
|
|
|
_ = p.Play(strings.ReplaceAll(url, "YOUR_SOUNDCLOUD_CLIENTID", scClientID))
|
2016-03-14 10:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
// - Have a player API to play, play next, or add to queue
|
|
|
|
// - Have the ability to parse custom packet to play sound
|
|
|
|
// - Use PEP to display tunes status
|
|
|
|
// - Ability to "speak" messages
|