1
0
mirror of https://github.com/Luzifer/lounge-control.git synced 2024-09-19 14:23:01 +00:00

Initial bunch of code

This commit is contained in:
Knut Ahlers 2020-05-09 23:17:15 +02:00
commit 1be09c7ccd
Signed by: luzifer
GPG Key ID: DC2729FDD34BE99E
14 changed files with 930 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

66
cmd_join.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/Luzifer/lounge-control/sioclient"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
func init() {
registerCommand("join", commandJoin)
}
func commandJoin(args []string) handlerFunc {
if len(args) == 0 {
log.Fatal("No channels given to join")
}
return addGenericHandler(func(pType string, msg *sioclient.Message) error {
if pType != "init" {
return nil
}
// After join command is finished we can execute the joins
network := initData.NetworkByNameOrUUID(cfg.Network)
if network == nil {
return errors.New("Network not found")
}
var lobby *channel
for _, c := range network.Channels {
if c.Type == "lobby" {
lobby = &c
break
}
}
if lobby == nil {
return errors.New("Unable to find lobby for network")
}
for _, ch := range args {
if !strings.HasPrefix(ch, "#") {
ch = "#" + ch
}
msg, err := sioclient.NewMessage(sioclient.MessageTypeEvent, 0, "input", map[string]interface{}{
"text": fmt.Sprintf("/join %s", ch),
"target": lobby.ID,
})
if err != nil {
return errors.Wrap(err, "Unable to compose join message")
}
if err = msg.Send(client); err != nil {
return errors.Wrap(err, "Unable to send join message")
}
}
interrupt <- os.Interrupt
return nil
})
}

44
cmd_listChannels.go Normal file
View File

@ -0,0 +1,44 @@
package main
import (
"errors"
"fmt"
"os"
"sort"
"strings"
"github.com/Luzifer/lounge-control/sioclient"
)
func init() {
registerCommand("list-channels", commandListChannels)
}
func commandListChannels(args []string) handlerFunc {
return addGenericHandler(func(pType string, msg *sioclient.Message) error {
if pType != "init" {
return nil
}
network := initData.NetworkByNameOrUUID(cfg.Network)
if network == nil {
return errors.New("Network not found")
}
var channels []string
for _, c := range network.Channels {
if c.Type == "lobby" {
continue
}
channels = append(channels, c.Name)
}
sort.Strings(channels)
fmt.Println(strings.Join(channels, "\n"))
interrupt <- os.Interrupt
return nil
})
}

66
cmd_part.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/Luzifer/lounge-control/sioclient"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
func init() {
registerCommand("part", commandPart)
}
func commandPart(args []string) handlerFunc {
if len(args) == 0 {
log.Fatal("No channels given to part")
}
return addGenericHandler(func(pType string, msg *sioclient.Message) error {
if pType != "init" {
return nil
}
// After join command is finished we can execute the joins
network := initData.NetworkByNameOrUUID(cfg.Network)
if network == nil {
return errors.New("Network not found")
}
var lobby *channel
for _, c := range network.Channels {
if c.Type == "lobby" {
lobby = &c
break
}
}
if lobby == nil {
return errors.New("Unable to find lobby for network")
}
for _, ch := range args {
if !strings.HasPrefix(ch, "#") {
ch = "#" + ch
}
msg, err := sioclient.NewMessage(sioclient.MessageTypeEvent, 0, "input", map[string]interface{}{
"text": fmt.Sprintf("/part %s", ch),
"target": lobby.ID,
})
if err != nil {
return errors.Wrap(err, "Unable to compose part message")
}
if err = msg.Send(client); err != nil {
return errors.Wrap(err, "Unable to send part message")
}
}
interrupt <- os.Interrupt
return nil
})
}

42
commands.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"sort"
"sync"
log "github.com/sirupsen/logrus"
"github.com/Luzifer/lounge-control/sioclient"
)
type commandFunc func(args []string) handlerFunc
type handlerFunc func(msg *sioclient.Message) error
type typedHandlerFunc func(mType string, msg *sioclient.Message) error
var (
commands = map[string]commandFunc{}
commandsMutex = new(sync.RWMutex)
)
func availableCommands() (cmds []string) {
commandsMutex.RLock()
defer commandsMutex.RUnlock()
for k := range commands {
cmds = append(cmds, k)
}
sort.Strings(cmds)
return cmds
}
func registerCommand(cmd string, cf commandFunc) {
commandsMutex.Lock()
defer commandsMutex.Unlock()
if _, ok := commands[cmd]; ok {
log.Fatalf("Duplicate registration of command %q", cmd)
}
commands[cmd] = cf
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module github.com/Luzifer/lounge-control
go 1.14
replace github.com/Luzifer/lounge-control/sioclient => ./sioclient
require (
github.com/Luzifer/lounge-control/sioclient v0.0.0-00010101000000-000000000000
github.com/Luzifer/rconfig/v2 v2.2.1
github.com/gorilla/websocket v1.4.2 // indirect
github.com/pkg/errors v0.9.1
github.com/sacOO7/go-logger v0.0.0-20180719173527-9ac9add5a50d // indirect
github.com/sacOO7/gowebsocket v0.0.0-20180719182212-1436bb906a4e
github.com/sirupsen/logrus v1.6.0
)

27
go.sum Normal file
View File

@ -0,0 +1,27 @@
github.com/Luzifer/rconfig v1.2.0 h1:waD1sqasGVSQSrExpLrQ9Q1JmMaltrS391VdOjWXP/I=
github.com/Luzifer/rconfig/v2 v2.2.1 h1:zcDdLQlnlzwcBJ8E0WFzOkQE1pCMn3EbX0dFYkeTczg=
github.com/Luzifer/rconfig/v2 v2.2.1/go.mod h1:OKIX0/JRZrPJ/ZXXWklQEFXA6tBfWaljZbW37w+sqBw=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sacOO7/go-logger v0.0.0-20180719173527-9ac9add5a50d h1:5T+fbRuQbpi+WZtB2yfuu59r00F6T2HV/zGYrwX8nvE=
github.com/sacOO7/go-logger v0.0.0-20180719173527-9ac9add5a50d/go.mod h1:L5EJe2k8GwpBoGXDRLAEs58R239jpZuE7NNEtW+T7oo=
github.com/sacOO7/gowebsocket v0.0.0-20180719182212-1436bb906a4e h1:yG1sLAkFltiFiwIpKdiA2CVIPxRL4P9OywNnfq45ivg=
github.com/sacOO7/gowebsocket v0.0.0-20180719182212-1436bb906a4e/go.mod h1:4a2a9BlxB807BaME8FJzQRLrZwYKj0cWjon25PlIssM=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19 h1:WB265cn5OpO+hK3pikC9hpP1zI/KTwmyMFKloW9eOVc=
gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

122
handler.go Normal file
View File

@ -0,0 +1,122 @@
package main
import (
"encoding/json"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/Luzifer/lounge-control/sioclient"
)
func addGenericHandler(f typedHandlerFunc) handlerFunc {
return func(msg *sioclient.Message) error {
if msg.Type != sioclient.MessageTypeEvent {
// We don't care about anything but events
return nil
}
pType, err := msg.PayloadType()
if err != nil {
log.Printf("Event message had no payload type: %#v - %s", msg, err)
return nil
}
switch pType {
case "auth:failed":
log.Fatal("Login failed")
case "auth:start":
msg, err := sioclient.NewMessage(sioclient.MessageTypeEvent, 0, "auth:perform", map[string]string{"user": cfg.Username, "password": cfg.Password})
if err != nil {
return errors.Wrap(err, "Unable to create auth:peform")
}
if err := msg.Send(client); err != nil {
return errors.Wrap(err, "Unable to create payload")
}
case "init":
if err := json.Unmarshal(msg.Payload[1], &initData); err != nil {
return errors.Wrap(err, "Unable to parse init payload")
}
}
return f(pType, msg)
}
}
// DEPRECATED: Only storing code for now
func handleMessage(msg *sioclient.Message) error {
if msg.Type != sioclient.MessageTypeEvent {
// We don't care about anything but events
return nil
}
pType, err := msg.PayloadType()
if err != nil {
log.Printf("Event message had no payload type: %#v - %s", msg, err)
return nil
}
switch pType {
case "auth:failed":
log.Fatal("Login failed")
case "auth:start":
msg, err := sioclient.NewMessage(sioclient.MessageTypeEvent, 0, "auth:perform", map[string]string{"user": cfg.Username, "password": cfg.Password})
if err != nil {
return errors.Wrap(err, "Unable to create auth:peform")
}
if err := msg.Send(client); err != nil {
return errors.Wrap(err, "Unable to create payload")
}
case "auth:success":
log.Info("Logged in successfully")
case "init":
if err := json.Unmarshal(msg.Payload[1], &initData); err != nil {
return errors.Wrap(err, "Unable to parse init payload")
}
case "msg":
// Message in channel
var payload chatMessage
if err := json.Unmarshal(msg.Payload[1], &payload); err != nil {
return errors.Wrap(err, "Unable to parse init payload")
}
switch payload.Msg.Type {
case "join", "part":
// Don't care
case "message", "notice":
//log.Infof("Msg: %#v", payload)
case "unhandled":
log.Infof("CMD: %s", msg.Payload[1])
default:
log.Infof("Unhandled message %q: %#v", payload.Msg.Type, payload)
}
case "commands", "configuration", "names", "open", "push:issubscribed", "users":
// Drop irrelevantt messages
default:
if len(msg.Payload) == 2 {
log.Warnf("Recieved unhandled message %q: %s", pType, msg.Payload[1])
return nil
}
log.Warnf("Recieved unhandled message %q: %#v", pType, msg)
}
return nil
}

87
main.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"fmt"
"os"
"os/signal"
"strings"
log "github.com/sirupsen/logrus"
"github.com/Luzifer/lounge-control/sioclient"
"github.com/Luzifer/rconfig/v2"
)
var (
cfg = struct {
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
Network string `flag:"network,n" description:"Name or UUID of the network to act on"`
Password string `flag:"password,p" description:"Password for the given username" validate:"nonzero"`
SocketURL string `flag:"socket-url" description:"URL to TheLounge websocket (i.e. 'wss://example.com/socket.io/')" validate:"nonzero"`
Username string `flag:"username,u" description:"Username to log into the socket" validate:"nonzero"`
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
}{}
client *sioclient.Client
initData initMessage
interrupt = make(chan os.Signal, 1)
version = "dev"
)
func init() {
rconfig.AutoEnv(true)
if err := rconfig.ParseAndValidate(&cfg); err != nil {
log.Fatalf("Unable to parse commandline options: %s", err)
}
if cfg.VersionAndExit {
fmt.Printf("lounge-control %s\n", version)
os.Exit(0)
}
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
log.WithError(err).Fatal("Unable to parse log level")
} else {
log.SetLevel(l)
}
}
func main() {
signal.Notify(interrupt, os.Interrupt)
args := rconfig.Args()[1:]
if len(args) == 0 {
log.Fatalf("No command given. Available commands: %s", strings.Join(availableCommands(), ", "))
}
commandsMutex.RLock()
cf, ok := commands[args[0]]
commandsMutex.RUnlock()
if !ok {
log.Fatalf("Unknown command %q. Available commands: %s", args[0], strings.Join(availableCommands(), ", "))
}
var err error
client, err = sioclient.New(sioclient.Config{
MessageHandler: cf(args[1:]),
URL: cfg.SocketURL,
})
if err != nil {
log.WithError(err).Fatal("Unable to connect to server")
}
defer client.Close()
for {
select {
case <-interrupt:
return
case err := <-client.EIO.Errors():
log.WithError(err).Error("Error in in command / socket")
return
}
}
}

73
messages.go Normal file
View File

@ -0,0 +1,73 @@
package main
import "time"
type chatMessageContent struct {
Command string `json:"command"`
From struct {
Mode string `json:"mode"`
Nick string `json:"nick"`
} `json:"from"`
Highlight bool `json:"highlight"`
ID int `json:"id"`
Params []string `json:"params"`
Previews []interface{} `json:"previews"`
Self bool `json:"self"`
Text string `json:"text"`
Time time.Time `json:"time"`
Type string `json:"type"`
Users []string `json:"users"`
}
type chatMessage struct {
Chan int `json:"chan"`
Msg chatMessageContent `json:"msg"`
Unread int `json:"unread"`
}
type channel struct {
Name string `json:"name"`
Type string `json:"type"`
ID int `json:"id"`
Messages []chatMessageContent `json:"messages"`
TotalMessages int `json:"totalMessages"`
Key string `json:"key"`
Topic string `json:"topic"`
State int `json:"state"`
FirstUnread int `json:"firstUnread"`
Unread int `json:"unread"`
Highlight int `json:"highlight"`
Users []string `json:"users"`
}
type network struct {
UUID string `json:"uuid"`
Name string `json:"name"`
Nick string `json:"nick"`
Channels []channel `json:"channels"`
ServerOptions struct {
CHANTYPES []string `json:"CHANTYPES"`
PREFIX []string `json:"PREFIX"`
NETWORK string `json:"NETWORK"`
} `json:"serverOptions"`
Status struct {
Connected bool `json:"connected"`
Secure bool `json:"secure"`
} `json:"status"`
}
type initMessage struct {
Active int `json:"active"`
Networks []network `json:"networks"`
Token string `json:"token"`
}
func (i initMessage) NetworkByNameOrUUID(id string) *network {
for _, n := range i.Networks {
if n.Name == id || n.UUID == id {
return &n
}
}
return nil
}

194
sioclient/eio.go Normal file
View File

@ -0,0 +1,194 @@
package sioclient
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)
type EIOMessageType int
const (
EIOMessageTypeOpen EIOMessageType = iota
EIOMessageTypeClose
EIOMessageTypePing
EIOMessageTypePong
EIOMessageTypeMessage
EIOMessageTypeUpgrade
EIOMessageTypeNoop
)
var ErrNotConnected = errors.New("Websocket is not connected")
type EIOClientConfig struct {
MessageHandlerBinary func([]byte) error
MessageHandlerText func([]byte) error
URL string
}
type eioSessionStart struct {
SID string `json:"sid"`
Upgrades []string `json:"upgrades"`
PingTimeout int64 `json:"pingTimeout"`
PingInterval int64 `json:"pingInterval"`
}
type EIOClient struct {
cfg EIOClientConfig
dialer *websocket.Dialer
errC chan error
isConnected bool
writeMutex *sync.Mutex
ws *websocket.Conn
}
func NewEIOClient(config EIOClientConfig) (*EIOClient, error) {
var (
client = new(EIOClient)
err error
)
socketURL, err := url.Parse(config.URL)
if err != nil {
return nil, errors.Wrap(err, "Unable to parse URL")
}
qVars := socketURL.Query()
qVars.Set("EIO", "3")
qVars.Set("transport", "websocket")
socketURL.RawQuery = qVars.Encode()
client.cfg = config
client.errC = make(chan error, 10)
client.writeMutex = new(sync.Mutex)
client.ws, _, err = client.dialer.Dial(socketURL.String(), http.Header{})
if err != nil {
return nil, errors.Wrap(err, "Unable to dial to given URL")
}
// Mark connection as established
client.isConnected = true
defaultCloseHandler := client.ws.CloseHandler()
client.ws.SetCloseHandler(func(code int, text string) error {
client.isConnected = false
return defaultCloseHandler(code, text)
})
go func() {
for client.isConnected {
messageType, message, err := client.ws.ReadMessage()
if err != nil {
client.errC <- err
continue
}
if err = client.handleMessage(messageType, message); err != nil {
client.errC <- err
continue
}
}
}()
return client, nil
}
func (e EIOClient) Close() error { return e.ws.Close() }
func (e EIOClient) Errors() <-chan error { return e.errC }
func (e EIOClient) SendTextMessage(t EIOMessageType, data string) error {
if !e.isConnected {
return ErrNotConnected
}
e.writeMutex.Lock()
defer e.writeMutex.Unlock()
return errors.Wrap(
e.ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%d%s", t, data))),
"Unable to transmit message",
)
}
func (e EIOClient) handleMessage(messageType int, message []byte) error {
if len(message) < 1 {
return errors.New("Empty message received")
}
var mType EIOMessageType
switch messageType {
case websocket.TextMessage:
v, err := strconv.Atoi(string(message[0]))
if err != nil {
return errors.Wrap(err, "Unable to parse message type")
}
mType = EIOMessageType(v)
case websocket.BinaryMessage:
mType = EIOMessageType(message[0])
}
switch mType {
case EIOMessageTypeOpen:
var handshake eioSessionStart
if err := json.Unmarshal(message[1:], &handshake); err != nil {
return errors.Wrap(err, "Unable to unmarshal handshake")
}
// Start pinger
go func() {
for t := time.NewTicker(time.Duration(handshake.PingInterval) * time.Millisecond); e.isConnected; <-t.C {
e.SendTextMessage(EIOMessageTypePing, "")
}
}()
case EIOMessageTypeClose:
e.ws.Close()
case EIOMessageTypePing:
e.SendTextMessage(EIOMessageTypePong, "")
case EIOMessageTypePong:
// Ignore
case EIOMessageTypeMessage:
var hdl func([]byte) error
switch messageType {
case websocket.TextMessage:
hdl = e.cfg.MessageHandlerText
case websocket.BinaryMessage:
hdl = e.cfg.MessageHandlerBinary
}
if err := hdl(message[1:]); err != nil {
return errors.Wrap(err, "Failed to handle message")
}
case EIOMessageTypeUpgrade:
// Ignore?
case EIOMessageTypeNoop:
// Noop!
default:
return errors.Errorf("Received unknown EIO message type %d", mType)
}
return nil
}

8
sioclient/go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/Luzifer/lounge-control/sioclient
go 1.14
require (
github.com/gorilla/websocket v1.4.2
github.com/pkg/errors v0.9.1
)

4
sioclient/go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

181
sioclient/sio.go Normal file
View File

@ -0,0 +1,181 @@
package sioclient
import (
"bytes"
"encoding/json"
"strconv"
"github.com/pkg/errors"
)
type MessageType int
const (
MessageTypeConnect MessageType = iota
MessageTypeDisconnect
MessageTypeEvent
MessageTypeAck
MessageTypeError
MessageTypeBinaryEvent
MessageTypeBinaryAck
)
type Config struct {
MessageHandler func(*Message) error
URL string
}
type Client struct {
EIO *EIOClient
cfg Config
}
func New(c Config) (*Client, error) {
var (
client = new(Client)
err error
)
client.cfg = c
if client.EIO, err = NewEIOClient(EIOClientConfig{
MessageHandlerText: client.handleTextMessage,
URL: c.URL,
}); err != nil {
return nil, errors.Wrap(err, "Unable to create EIO client")
}
return client, nil
}
func (c Client) Close() error {
return c.EIO.Close()
}
func (c Client) handleTextMessage(msg []byte) error {
m, err := c.parseProto(msg)
if err != nil {
return errors.Wrap(err, "Unable to parse message")
}
return c.cfg.MessageHandler(m)
}
type Message struct {
Type MessageType
Namespace string
ID int
Payload []json.RawMessage
}
func NewMessage(sType MessageType, id int, payloadType string, data interface{}) (*Message, error) {
out := &Message{
Type: sType,
Namespace: "/",
ID: id,
Payload: make([]json.RawMessage, 2),
}
var err error
if out.Payload[0], err = json.Marshal(payloadType); err != nil {
return nil, errors.Wrap(err, "Unable to marshal payloadType")
}
if out.Payload[1], err = json.Marshal(data); err != nil {
return nil, errors.Wrap(err, "Unable to marshal data")
}
return out, nil
}
func (m Message) Encode() (string, error) {
data, err := json.Marshal(m.Payload)
if err != nil {
return "", errors.Wrap(err, "Unable to marshal payload")
}
var msg = new(bytes.Buffer)
msg.WriteString(strconv.Itoa(int(m.Type)))
if m.Namespace != "" && m.Namespace != "/" {
msg.WriteString(m.Namespace + ",")
}
if m.ID > 0 {
msg.WriteString(strconv.Itoa(m.ID))
}
msg.Write(data)
return msg.String(), nil
}
func (m Message) PayloadType() (string, error) {
if len(m.Payload) == 0 {
return "", errors.New("No payload type available")
}
var t string
err := json.Unmarshal(m.Payload[0], &t)
return t, errors.Wrap(err, "Unable to unmarshal payload type")
}
func (m Message) Send(c *Client) error {
raw, err := m.Encode()
if err != nil {
return errors.Wrap(err, "Unable to encode message")
}
return c.EIO.SendTextMessage(EIOMessageTypeMessage, raw)
}
func (m Message) UnmarshalPayload(out interface{}) error { return json.Unmarshal(m.Payload[1], out) }
func (c Client) parseProto(msg []byte) (*Message, error) {
var (
err error
outMsg = new(Message)
ptr int
)
if len(msg) < 1 {
return nil, errors.New("Message was empty")
}
// Get message type
mType, err := strconv.Atoi(string(msg[ptr]))
if err != nil {
return nil, errors.Wrap(err, "Unable to parse message type")
}
outMsg.Type = MessageType(mType)
ptr++
// Message contains only message type
if len(msg[ptr:]) == 0 {
return outMsg, nil
}
// Binary
if outMsg.Type == MessageTypeBinaryEvent || outMsg.Type == MessageTypeBinaryAck {
return nil, errors.New("Binary is not supported")
}
// Check for namespace
if msg[ptr] == '/' {
return nil, errors.New("Namespaces is not supported")
}
// Read message ID if any
if outMsg.ID, err = strconv.Atoi(string(msg[ptr])); err == nil {
ptr++
}
// If there is no more data we have an empty message
if len(msg[ptr:]) == 0 || outMsg.Type == MessageTypeConnect {
return outMsg, nil
}
return outMsg, errors.Wrap(json.Unmarshal(msg[ptr:], &outMsg.Payload), "Unable to unmarshal message")
}