From 6ebd5cbbd8a941e0bc5f99f0d8e99cfd1d8ac0d7 Mon Sep 17 00:00:00 2001
From: Wim <wim@42.be>
Date: Sun, 10 Feb 2019 17:00:11 +0100
Subject: Refactor and update RocketChat bridge

* Add support for editing/deleting messages
* Add support for uploading files
* Add support for avatars
* Use the Rocket.Chat.Go.SDK
* Use the rest and streaming api
---
 bridge/rocketchat/handlers.go                      |  69 +++
 bridge/rocketchat/helpers.go                       | 198 +++++++
 bridge/rocketchat/rocketchat.go                    | 150 +++--
 go.mod                                             |   4 +
 go.sum                                             |   8 +
 matterbridge.toml.sample                           |  17 +
 vendor/github.com/Jeffail/gabs/LICENSE             |  19 +
 vendor/github.com/Jeffail/gabs/README.md           | 315 ++++++++++
 vendor/github.com/Jeffail/gabs/gabs.go             | 581 ++++++++++++++++++
 vendor/github.com/Jeffail/gabs/gabs_logo.png       | Bin 0 -> 167771 bytes
 vendor/github.com/gopackage/ddp/.gitignore         |  24 +
 vendor/github.com/gopackage/ddp/LICENSE            |  13 +
 vendor/github.com/gopackage/ddp/README.md          |   3 +
 vendor/github.com/gopackage/ddp/ddp.go             |  79 +++
 vendor/github.com/gopackage/ddp/ddp_client.go      | 654 +++++++++++++++++++++
 vendor/github.com/gopackage/ddp/ddp_collection.go  | 245 ++++++++
 vendor/github.com/gopackage/ddp/ddp_ejson.go       | 217 +++++++
 vendor/github.com/gopackage/ddp/ddp_messages.go    |  82 +++
 vendor/github.com/gopackage/ddp/ddp_stats.go       | 321 ++++++++++
 .../Rocket.Chat.Go.SDK/models/channel.go           |  39 ++
 .../matterbridge/Rocket.Chat.Go.SDK/models/info.go | 133 +++++
 .../Rocket.Chat.Go.SDK/models/message.go           |  75 +++
 .../Rocket.Chat.Go.SDK/models/permission.go        |   7 +
 .../Rocket.Chat.Go.SDK/models/setting.go           |  21 +
 .../matterbridge/Rocket.Chat.Go.SDK/models/user.go |  29 +
 .../Rocket.Chat.Go.SDK/models/userCredentials.go   |  10 +
 .../Rocket.Chat.Go.SDK/realtime/channels.go        | 263 +++++++++
 .../Rocket.Chat.Go.SDK/realtime/client.go          |  96 +++
 .../Rocket.Chat.Go.SDK/realtime/emoji.go           |  10 +
 .../Rocket.Chat.Go.SDK/realtime/events.go          |  21 +
 .../Rocket.Chat.Go.SDK/realtime/messages.go        | 240 ++++++++
 .../Rocket.Chat.Go.SDK/realtime/permissions.go     |  54 ++
 .../Rocket.Chat.Go.SDK/realtime/settings.go        |  53 ++
 .../Rocket.Chat.Go.SDK/realtime/subscriptions.go   |  41 ++
 .../Rocket.Chat.Go.SDK/realtime/users.go           | 103 ++++
 .../Rocket.Chat.Go.SDK/rest/channels.go            |  64 ++
 .../matterbridge/Rocket.Chat.Go.SDK/rest/client.go | 176 ++++++
 .../Rocket.Chat.Go.SDK/rest/information.go         |  98 +++
 .../Rocket.Chat.Go.SDK/rest/messages.go            |  67 +++
 .../matterbridge/Rocket.Chat.Go.SDK/rest/users.go  | 145 +++++
 vendor/github.com/nelsonken/gomf/README.md         |  37 ++
 vendor/github.com/nelsonken/gomf/form_builder.go   |  89 +++
 vendor/github.com/nelsonken/gomf/up.php            |  33 ++
 vendor/golang.org/x/net/AUTHORS                    |   3 +
 vendor/golang.org/x/net/CONTRIBUTORS               |   3 +
 vendor/golang.org/x/net/LICENSE                    |  27 +
 vendor/golang.org/x/net/PATENTS                    |  22 +
 vendor/golang.org/x/net/websocket/client.go        | 106 ++++
 vendor/golang.org/x/net/websocket/dial.go          |  24 +
 vendor/golang.org/x/net/websocket/hybi.go          | 583 ++++++++++++++++++
 vendor/golang.org/x/net/websocket/server.go        | 113 ++++
 vendor/golang.org/x/net/websocket/websocket.go     | 448 ++++++++++++++
 vendor/modules.txt                                 |  12 +
 53 files changed, 6201 insertions(+), 43 deletions(-)
 create mode 100644 bridge/rocketchat/handlers.go
 create mode 100644 bridge/rocketchat/helpers.go
 create mode 100644 vendor/github.com/Jeffail/gabs/LICENSE
 create mode 100644 vendor/github.com/Jeffail/gabs/README.md
 create mode 100644 vendor/github.com/Jeffail/gabs/gabs.go
 create mode 100644 vendor/github.com/Jeffail/gabs/gabs_logo.png
 create mode 100644 vendor/github.com/gopackage/ddp/.gitignore
 create mode 100644 vendor/github.com/gopackage/ddp/LICENSE
 create mode 100644 vendor/github.com/gopackage/ddp/README.md
 create mode 100644 vendor/github.com/gopackage/ddp/ddp.go
 create mode 100644 vendor/github.com/gopackage/ddp/ddp_client.go
 create mode 100644 vendor/github.com/gopackage/ddp/ddp_collection.go
 create mode 100644 vendor/github.com/gopackage/ddp/ddp_ejson.go
 create mode 100644 vendor/github.com/gopackage/ddp/ddp_messages.go
 create mode 100644 vendor/github.com/gopackage/ddp/ddp_stats.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
 create mode 100644 vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
 create mode 100644 vendor/github.com/nelsonken/gomf/README.md
 create mode 100644 vendor/github.com/nelsonken/gomf/form_builder.go
 create mode 100644 vendor/github.com/nelsonken/gomf/up.php
 create mode 100644 vendor/golang.org/x/net/AUTHORS
 create mode 100644 vendor/golang.org/x/net/CONTRIBUTORS
 create mode 100644 vendor/golang.org/x/net/LICENSE
 create mode 100644 vendor/golang.org/x/net/PATENTS
 create mode 100644 vendor/golang.org/x/net/websocket/client.go
 create mode 100644 vendor/golang.org/x/net/websocket/dial.go
 create mode 100644 vendor/golang.org/x/net/websocket/hybi.go
 create mode 100644 vendor/golang.org/x/net/websocket/server.go
 create mode 100644 vendor/golang.org/x/net/websocket/websocket.go

diff --git a/bridge/rocketchat/handlers.go b/bridge/rocketchat/handlers.go
new file mode 100644
index 00000000..b44ea46f
--- /dev/null
+++ b/bridge/rocketchat/handlers.go
@@ -0,0 +1,69 @@
+package brocketchat
+
+import (
+	"github.com/42wim/matterbridge/bridge/config"
+)
+
+func (b *Brocketchat) handleRocket() {
+	messages := make(chan *config.Message)
+	if b.GetString("WebhookBindAddress") != "" {
+		b.Log.Debugf("Choosing webhooks based receiving")
+		go b.handleRocketHook(messages)
+	} else {
+		b.Log.Debugf("Choosing login/password based receiving")
+		go b.handleRocketClient(messages)
+	}
+	for message := range messages {
+		message.Account = b.Account
+		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
+		b.Log.Debugf("<= Message is %#v", message)
+		b.Remote <- *message
+	}
+}
+
+func (b *Brocketchat) handleRocketHook(messages chan *config.Message) {
+	for {
+		message := b.rh.Receive()
+		b.Log.Debugf("Receiving from rockethook %#v", message)
+		// do not loop
+		if message.UserName == b.GetString("Nick") {
+			continue
+		}
+		messages <- &config.Message{
+			UserID:   message.UserID,
+			Username: message.UserName,
+			Text:     message.Text,
+			Channel:  message.ChannelName,
+		}
+	}
+}
+
+func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
+	for message := range b.messageChan {
+		b.Log.Debugf("message %#v", message)
+		m := message
+		if b.skipMessage(&m) {
+			b.Log.Debugf("Skipped message: %#v", message)
+			continue
+		}
+
+		rmsg := &config.Message{Text: message.Msg,
+			Username: message.User.UserName,
+			Channel:  b.getChannelName(message.RoomID),
+			Account:  b.Account,
+			UserID:   message.User.ID,
+			ID:       message.ID,
+		}
+		messages <- rmsg
+	}
+}
+
+func (b *Brocketchat) handleUploadFile(msg *config.Message) error {
+	for _, f := range msg.Extra["file"] {
+		fi := f.(config.FileInfo)
+		if err := b.uploadFile(&fi, b.getChannelID(msg.Channel)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/bridge/rocketchat/helpers.go b/bridge/rocketchat/helpers.go
new file mode 100644
index 00000000..c6bd2dd8
--- /dev/null
+++ b/bridge/rocketchat/helpers.go
@@ -0,0 +1,198 @@
+package brocketchat
+
+import (
+	"context"
+	"io/ioutil"
+	"mime"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/42wim/matterbridge/bridge/config"
+	"github.com/42wim/matterbridge/bridge/helper"
+	"github.com/42wim/matterbridge/hook/rockethook"
+	"github.com/42wim/matterbridge/matterhook"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/realtime"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/rest"
+	"github.com/nelsonken/gomf"
+)
+
+func (b *Brocketchat) doConnectWebhookBind() error {
+	switch {
+	case b.GetString("WebhookURL") != "":
+		b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
+		b.mh = matterhook.New(b.GetString("WebhookURL"),
+			matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
+				DisableServer: true})
+		b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
+	case b.GetString("Login") != "":
+		b.Log.Info("Connecting using login/password (sending)")
+		err := b.apiLogin()
+		if err != nil {
+			return err
+		}
+	default:
+		b.Log.Info("Connecting using webhookbindaddress (receiving)")
+		b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
+	}
+	return nil
+}
+
+func (b *Brocketchat) doConnectWebhookURL() error {
+	b.Log.Info("Connecting using webhookurl (sending)")
+	b.mh = matterhook.New(b.GetString("WebhookURL"),
+		matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
+			DisableServer: true})
+	if b.GetString("Login") != "" {
+		b.Log.Info("Connecting using login/password (receiving)")
+		err := b.apiLogin()
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (b *Brocketchat) apiLogin() error {
+	b.Log.Debugf("handling apiLogin()")
+	credentials := &models.UserCredentials{Email: b.GetString("login"), Password: b.GetString("password")}
+	myURL, err := url.Parse(b.GetString("server"))
+	if err != nil {
+		return err
+	}
+	client, err := realtime.NewClient(myURL, b.GetBool("debug"))
+	b.c = client
+	if err != nil {
+		return err
+	}
+	restclient := rest.NewClient(myURL, b.GetBool("debug"))
+	user, err := b.c.Login(credentials)
+	if err != nil {
+		return err
+	}
+	b.user = user
+	b.r = restclient
+	err = b.r.Login(credentials)
+	if err != nil {
+		return err
+	}
+	b.Log.Info("Connection succeeded")
+	return nil
+}
+
+func (b *Brocketchat) getChannelName(id string) string {
+	b.RLock()
+	defer b.RUnlock()
+	if name, ok := b.channelMap[id]; ok {
+		return name
+	}
+	return ""
+}
+
+func (b *Brocketchat) getChannelID(name string) string {
+	b.RLock()
+	defer b.RUnlock()
+	for k, v := range b.channelMap {
+		if v == name {
+			return k
+		}
+	}
+	return ""
+}
+
+func (b *Brocketchat) skipMessage(message *models.Message) bool {
+	return message.User.ID == b.user.ID
+}
+
+func (b *Brocketchat) uploadFile(fi *config.FileInfo, channel string) error {
+	fb := gomf.New()
+	if err := fb.WriteField("description", fi.Comment); err != nil {
+		return err
+	}
+	sp := strings.Split(fi.Name, ".")
+	mtype := mime.TypeByExtension("." + sp[len(sp)-1])
+	if !strings.Contains(mtype, "image") && !strings.Contains(mtype, "video") {
+		return nil
+	}
+	if err := fb.WriteFile("file", fi.Name, mtype, *fi.Data); err != nil {
+		return err
+	}
+	req, err := fb.GetHTTPRequest(context.TODO(), b.GetString("server")+"/api/v1/rooms.upload/"+channel)
+	if err != nil {
+		return err
+	}
+	req.Header.Add("X-Auth-Token", b.user.Token)
+	req.Header.Add("X-User-Id", b.user.ID)
+	client := &http.Client{
+		Timeout: time.Second * 5,
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 200 {
+		b.Log.Errorf("failed: %#v", string(body))
+	}
+	return nil
+}
+
+// sendWebhook uses the configured WebhookURL to send the message
+func (b *Brocketchat) sendWebhook(msg *config.Message) error {
+	// skip events
+	if msg.Event != "" {
+		return nil
+	}
+
+	if b.GetBool("PrefixMessagesWithNick") {
+		msg.Text = msg.Username + msg.Text
+	}
+	if msg.Extra != nil {
+		// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
+		for _, rmsg := range helper.HandleExtra(msg, b.General) {
+			rmsg := rmsg // scopelint
+			iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
+			matterMessage := matterhook.OMessage{
+				IconURL:  iconURL,
+				Channel:  rmsg.Channel,
+				UserName: rmsg.Username,
+				Text:     rmsg.Text,
+				Props:    make(map[string]interface{}),
+			}
+			if err := b.mh.Send(matterMessage); err != nil {
+				b.Log.Errorf("sendWebhook failed: %s ", err)
+			}
+		}
+
+		// webhook doesn't support file uploads, so we add the url manually
+		if len(msg.Extra["file"]) > 0 {
+			for _, f := range msg.Extra["file"] {
+				fi := f.(config.FileInfo)
+				if fi.URL != "" {
+					msg.Text += fi.URL
+				}
+			}
+		}
+	}
+	iconURL := config.GetIconURL(msg, b.GetString("iconurl"))
+	matterMessage := matterhook.OMessage{
+		IconURL:  iconURL,
+		Channel:  msg.Channel,
+		UserName: msg.Username,
+		Text:     msg.Text,
+	}
+	if msg.Avatar != "" {
+		matterMessage.IconURL = msg.Avatar
+	}
+	err := b.mh.Send(matterMessage)
+	if err != nil {
+		b.Log.Info(err)
+		return err
+	}
+	return nil
+}
diff --git a/bridge/rocketchat/rocketchat.go b/bridge/rocketchat/rocketchat.go
index 1dbc7be0..82b6627d 100644
--- a/bridge/rocketchat/rocketchat.go
+++ b/bridge/rocketchat/rocketchat.go
@@ -1,21 +1,37 @@
 package brocketchat
 
 import (
+	"errors"
+	"sync"
+
 	"github.com/42wim/matterbridge/bridge"
 	"github.com/42wim/matterbridge/bridge/config"
 	"github.com/42wim/matterbridge/bridge/helper"
 	"github.com/42wim/matterbridge/hook/rockethook"
 	"github.com/42wim/matterbridge/matterhook"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/realtime"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/rest"
 )
 
 type Brocketchat struct {
 	mh *matterhook.Client
 	rh *rockethook.Client
+	c  *realtime.Client
+	r  *rest.Client
 	*bridge.Config
+	messageChan chan models.Message
+	channelMap  map[string]string
+	user        *models.User
+	sync.RWMutex
 }
 
 func New(cfg *bridge.Config) bridge.Bridger {
-	return &Brocketchat{Config: cfg}
+	b := &Brocketchat{Config: cfg}
+	b.messageChan = make(chan models.Message)
+	b.channelMap = make(map[string]string)
+	b.Log.Debugf("enabling rocketchat")
+	return b
 }
 
 func (b *Brocketchat) Command(cmd string) string {
@@ -23,70 +39,118 @@ func (b *Brocketchat) Command(cmd string) string {
 }
 
 func (b *Brocketchat) Connect() error {
-	b.Log.Info("Connecting webhooks")
-	b.mh = matterhook.New(b.GetString("WebhookURL"),
-		matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
-			DisableServer: true})
-	b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
-	go b.handleRocketHook()
+	if b.GetString("WebhookBindAddress") != "" {
+		if err := b.doConnectWebhookBind(); err != nil {
+			return err
+		}
+		go b.handleRocket()
+		return nil
+	}
+	switch {
+	case b.GetString("WebhookURL") != "":
+		if err := b.doConnectWebhookURL(); err != nil {
+			return err
+		}
+		go b.handleRocket()
+		return nil
+	case b.GetString("Login") != "":
+		b.Log.Info("Connecting using login/password (sending and receiving)")
+		err := b.apiLogin()
+		if err != nil {
+			return err
+		}
+		go b.handleRocket()
+	}
+	if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" &&
+		b.GetString("Login") == "" {
+		return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Login/Password/Server configured")
+	}
 	return nil
 }
 
 func (b *Brocketchat) Disconnect() error {
 	return nil
-
 }
 
 func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
+	if b.c == nil {
+		return nil
+	}
+	id, err := b.c.GetChannelId(channel.Name)
+	if err != nil {
+		return err
+	}
+	b.Lock()
+	b.channelMap[id] = channel.Name
+	b.Unlock()
+	mychannel := &models.Channel{ID: id, Name: channel.Name}
+	if err := b.c.JoinChannel(id); err != nil {
+		return err
+	}
+	if err := b.c.SubscribeToMessageStream(mychannel, b.messageChan); err != nil {
+		return err
+	}
 	return nil
 }
 
 func (b *Brocketchat) Send(msg config.Message) (string, error) {
-	// ignore delete messages
+	channel := &models.Channel{ID: b.getChannelID(msg.Channel), Name: msg.Channel}
+
+	// Delete message
 	if msg.Event == config.EventMsgDelete {
-		return "", nil
+		if msg.ID == "" {
+			return "", nil
+		}
+		return msg.ID, b.c.DeleteMessage(&models.Message{ID: msg.ID})
 	}
-	b.Log.Debugf("=> Receiving %#v", msg)
+
+	// Use webhook to send the message
+	if b.GetString("WebhookURL") != "" {
+		return "", b.sendWebhook(&msg)
+	}
+
+	// Prepend nick if configured
+	if b.GetBool("PrefixMessagesWithNick") {
+		msg.Text = msg.Username + msg.Text
+	}
+
+	// Edit message if we have an ID
+	if msg.ID != "" {
+		return msg.ID, b.c.EditMessage(&models.Message{ID: msg.ID, Msg: msg.Text, RoomID: b.getChannelID(msg.Channel)})
+	}
+
+	// Upload a file if it exists
 	if msg.Extra != nil {
 		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
-			rmsg := rmsg // scopelint
-			iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
-			matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text}
-			b.mh.Send(matterMessage)
+			smsg := &models.Message{
+				RoomID: b.getChannelID(rmsg.Channel),
+				Msg:    rmsg.Username + rmsg.Text,
+				PostMessage: models.PostMessage{
+					Avatar: rmsg.Avatar,
+					Alias:  rmsg.Username,
+				},
+			}
+			if _, err := b.c.SendMessage(smsg); err != nil {
+				b.Log.Errorf("SendMessage failed: %s", err)
+			}
 		}
 		if len(msg.Extra["file"]) > 0 {
-			for _, f := range msg.Extra["file"] {
-				fi := f.(config.FileInfo)
-				if fi.URL != "" {
-					msg.Text += fi.URL
-				}
-			}
+			return "", b.handleUploadFile(&msg)
 		}
 	}
 
-	iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
-	matterMessage := matterhook.OMessage{IconURL: iconURL}
-	matterMessage.Channel = msg.Channel
-	matterMessage.UserName = msg.Username
-	matterMessage.Type = ""
-	matterMessage.Text = msg.Text
-	err := b.mh.Send(matterMessage)
-	if err != nil {
-		b.Log.Info(err)
-		return "", err
+	smsg := &models.Message{
+		RoomID: channel.ID,
+		Msg:    msg.Text,
+		PostMessage: models.PostMessage{
+			Avatar: msg.Avatar,
+			Alias:  msg.Username,
+		},
 	}
-	return "", nil
-}
 
-func (b *Brocketchat) handleRocketHook() {
-	for {
-		message := b.rh.Receive()
-		b.Log.Debugf("Receiving from rockethook %#v", message)
-		// do not loop
-		if message.UserName == b.GetString("Nick") {
-			continue
-		}
-		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.UserName, b.Account)
-		b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID}
+	rmsg, err := b.c.SendMessage(smsg)
+	if rmsg == nil {
+		return "", err
 	}
+	return rmsg.ID, err
 }
diff --git a/go.mod b/go.mod
index 1f8cb17f..1320b25a 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/42wim/matterbridge
 require (
 	github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
 	github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 // indirect
+	github.com/Jeffail/gabs v1.1.1 // indirect
 	github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
 	github.com/bwmarrin/discordgo v0.19.0
 	github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
@@ -10,6 +11,7 @@ require (
 	github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
 	github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc // indirect
 	github.com/google/gops v0.3.5
+	github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
 	github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
 	github.com/gorilla/schema v1.0.2
 	github.com/gorilla/websocket v1.4.0
@@ -23,6 +25,7 @@ require (
 	github.com/lrstanley/girc v0.0.0-20190102153329-c1e59a02f488
 	github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
 	github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
+	github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
 	github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
 	github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
 	github.com/matterbridge/gozulipbot v0.0.0-20180507190239-b6bb12d33544
@@ -31,6 +34,7 @@ require (
 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
 	github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
 	github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
+	github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
 	github.com/nicksnyder/go-i18n v1.4.0 // indirect
 	github.com/nlopes/slack v0.5.0
 	github.com/onsi/ginkgo v1.6.0 // indirect
diff --git a/go.sum b/go.sum
index c5aca265..e10bbb3a 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557 h1:IZtuWGfzQnKnCSu
 github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557/go.mod h1:jL0YSXMs/txjtGJ4PWrmETOk6KUHMDPMshgQZlTeB3Y=
 github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 h1:v/zr4ns/4sSahF9KBm4Uc933bLsEEv7LuT63CJ019yo=
 github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E=
+github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
 github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 h1:xZBoq249G9MSt+XuY7sVQzcfONJ6IQuwpCK+KAaOpnY=
 github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
 github.com/alexcesaro/log v0.0.0-20150915221235-61e686294e58 h1:MkpmYfld/S8kXqTYI68DfL8/hHXjHogL120Dy00TIxc=
@@ -27,6 +29,8 @@ github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc h1:wdhDSKrkYy24mcf
 github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/gops v0.3.5 h1:SIWvPLiYvy5vMwjxB3rVFTE4QBhUFj2KKWr3Xm7CKhw=
 github.com/google/gops v0.3.5/go.mod h1:pMQgrscwEK/aUSW1IFSaBPbJX82FPHWaSoJw1axQfD0=
+github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo=
+github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA=
 github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
 github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/schema v1.0.2 h1:sAgNfOcNYvdDSrzGHVy9nzCQahG+qmsg+nE8dK85QRA=
@@ -66,6 +70,8 @@ github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408
 github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d h1:F+Sr+C0ojSlYQ37BLylQtSFmyQULe3jbAygcyXQ9mVs=
+github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A=
 github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 h1:KzDEcy8eDbTx881giW8a6llsAck3e2bJvMyKvh1IK+k=
 github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
 github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea h1:kaADGqpK4gGO2BpzEyJrBxq2Jc57Rsar4i2EUxcACUc=
@@ -88,6 +94,8 @@ github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 h1:oKIteT
 github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
 github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff h1:HLGD5/9UxxfEuO9DtP8gnTmNtMxbPyhYltfxsITel8g=
 github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff/go.mod h1:B8jLfIIPn2sKyWr0D7cL2v7tnrDD5z291s2Zypdu89E=
+github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9 h1:mp6tU1r0xLostUGLkTspf/9/AiHuVD7ptyXhySkDEsE=
+github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9/go.mod h1:A5SRAcpTemjGgIuBq6Kic2yHcoeUFWUinOAlMP/i9xo=
 github.com/nicksnyder/go-i18n v1.4.0 h1:AgLl+Yq7kg5OYlzCgu9cKTZOyI4tD/NgukKqLqC8E+I=
 github.com/nicksnyder/go-i18n v1.4.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
 github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0=
diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample
index 42d20af7..77f5b51e 100644
--- a/matterbridge.toml.sample
+++ b/matterbridge.toml.sample
@@ -893,6 +893,21 @@ ShowTopicChange=false
 #REQUIRED
 
 [rocketchat.rockme]
+#The rocketchat hostname. (prefix it with http or https)
+#REQUIRED (when not using webhooks)
+Server="https://yourrocketchatserver.domain.com:443"
+
+#login/pass of your bot. 
+#Use a dedicated user for this and not your own! 
+#REQUIRED (when not using webhooks)
+Login="yourlogin"
+Password="yourpass"
+
+#### Settings for webhook matterbridge.
+#USE DEDICATED BOT USER WHEN POSSIBLE! This allows you to use advanced features like message editing/deleting and uploads
+#You don't need to configure this, if you have configured the settings 
+#above.
+
 #Url is your incoming webhook url as specified in rocketchat
 #Read #https://rocket.chat/docs/administrator-guides/integrations/#how-to-create-a-new-incoming-webhook
 #See administration - integrations - new integration - incoming webhook
@@ -917,6 +932,8 @@ NoTLS=false
 #OPTIONAL (default false)
 SkipTLSVerify=true
 
+#### End settings for webhook matterbridge.
+
 ## RELOADABLE SETTINGS
 ## Settings below can be reloaded by editing the file
 
diff --git a/vendor/github.com/Jeffail/gabs/LICENSE b/vendor/github.com/Jeffail/gabs/LICENSE
new file mode 100644
index 00000000..99a62c62
--- /dev/null
+++ b/vendor/github.com/Jeffail/gabs/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Ashley Jeffs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/Jeffail/gabs/README.md b/vendor/github.com/Jeffail/gabs/README.md
new file mode 100644
index 00000000..a58193fd
--- /dev/null
+++ b/vendor/github.com/Jeffail/gabs/README.md
@@ -0,0 +1,315 @@
+![Gabs](gabs_logo.png "Gabs")
+
+Gabs is a small utility for dealing with dynamic or unknown JSON structures in
+golang. It's pretty much just a helpful wrapper around the golang
+`json.Marshal/json.Unmarshal` behaviour and `map[string]interface{}` objects.
+It does nothing spectacular except for being fabulous.
+
+https://godoc.org/github.com/Jeffail/gabs
+
+## How to install:
+
+``` bash
+go get github.com/Jeffail/gabs
+```
+
+## How to use
+
+### Parsing and searching JSON
+
+``` go
+...
+
+import "github.com/Jeffail/gabs"
+
+jsonParsed, err := gabs.ParseJSON([]byte(`{
+	"outter":{
+		"inner":{
+			"value1":10,
+			"value2":22
+		},
+		"alsoInner":{
+			"value1":20
+		}
+	}
+}`))
+
+var value float64
+var ok bool
+
+value, ok = jsonParsed.Path("outter.inner.value1").Data().(float64)
+// value == 10.0, ok == true
+
+value, ok = jsonParsed.Search("outter", "inner", "value1").Data().(float64)
+// value == 10.0, ok == true
+
+value, ok = jsonParsed.Path("does.not.exist").Data().(float64)
+// value == 0.0, ok == false
+
+exists := jsonParsed.Exists("outter", "inner", "value1")
+// exists == true
+
+exists := jsonParsed.Exists("does", "not", "exist")
+// exists == false
+
+exists := jsonParsed.ExistsP("does.not.exist")
+// exists == false
+
+...
+```
+
+### Iterating objects
+
+``` go
+...
+
+jsonParsed, _ := gabs.ParseJSON([]byte(`{"object":{ "first": 1, "second": 2, "third": 3 }}`))
+
+// S is shorthand for Search
+children, _ := jsonParsed.S("object").ChildrenMap()
+for key, child := range children {
+	fmt.Printf("key: %v, value: %v\n", key, child.Data().(string))
+}
+
+...
+```
+
+### Iterating arrays
+
+``` go
+...
+
+jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ "first", "second", "third" ]}`))
+
+// S is shorthand for Search
+children, _ := jsonParsed.S("array").Children()
+for _, child := range children {
+	fmt.Println(child.Data().(string))
+}
+
+...
+```
+
+Will print:
+
+```
+first
+second
+third
+```
+
+Children() will return all children of an array in order. This also works on
+objects, however, the children will be returned in a random order.
+
+### Searching through arrays
+
+If your JSON structure contains arrays you can still search the fields of the
+objects within the array, this returns a JSON array containing the results for
+each element.
+
+``` go
+...
+
+jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ {"value":1}, {"value":2}, {"value":3} ]}`))
+fmt.Println(jsonParsed.Path("array.value").String())
+
+...
+```
+
+Will print:
+
+```
+[1,2,3]
+```
+
+### Generating JSON
+
+``` go
+...
+
+jsonObj := gabs.New()
+// or gabs.Consume(jsonObject) to work on an existing map[string]interface{}
+
+jsonObj.Set(10, "outter", "inner", "value")
+jsonObj.SetP(20, "outter.inner.value2")
+jsonObj.Set(30, "outter", "inner2", "value3")
+
+fmt.Println(jsonObj.String())
+
+...
+```
+
+Will print:
+
+```
+{"outter":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
+```
+
+To pretty-print:
+
+``` go
+...
+
+fmt.Println(jsonObj.StringIndent("", "  "))
+
+...
+```
+
+Will print:
+
+```
+{
+  "outter": {
+    "inner": {
+      "value": 10,
+      "value2": 20
+    },
+    "inner2": {
+      "value3": 30
+    }
+  }
+}
+```
+
+### Generating Arrays
+
+``` go
+...
+
+jsonObj := gabs.New()
+
+jsonObj.Array("foo", "array")
+// Or .ArrayP("foo.array")
+
+jsonObj.ArrayAppend(10, "foo", "array")
+jsonObj.ArrayAppend(20, "foo", "array")
+jsonObj.ArrayAppend(30, "foo", "array")
+
+fmt.Println(jsonObj.String())
+
+...
+```
+
+Will print:
+
+```
+{"foo":{"array":[10,20,30]}}
+```
+
+Working with arrays by index:
+
+``` go
+...
+
+jsonObj := gabs.New()
+
+// Create an array with the length of 3
+jsonObj.ArrayOfSize(3, "foo")
+
+jsonObj.S("foo").SetIndex("test1", 0)
+jsonObj.S("foo").SetIndex("test2", 1)
+
+// Create an embedded array with the length of 3
+jsonObj.S("foo").ArrayOfSizeI(3, 2)
+
+jsonObj.S("foo").Index(2).SetIndex(1, 0)
+jsonObj.S("foo").Index(2).SetIndex(2, 1)
+jsonObj.S("foo").Index(2).SetIndex(3, 2)
+
+fmt.Println(jsonObj.String())
+
+...
+```
+
+Will print:
+
+```
+{"foo":["test1","test2",[1,2,3]]}
+```
+
+### Converting back to JSON
+
+This is the easiest part:
+
+``` go
+...
+
+jsonParsedObj, _ := gabs.ParseJSON([]byte(`{
+	"outter":{
+		"values":{
+			"first":10,
+			"second":11
+		}
+	},
+	"outter2":"hello world"
+}`))
+
+jsonOutput := jsonParsedObj.String()
+// Becomes `{"outter":{"values":{"first":10,"second":11}},"outter2":"hello world"}`
+
+...
+```
+
+And to serialize a specific segment is as simple as:
+
+``` go
+...
+
+jsonParsedObj := gabs.ParseJSON([]byte(`{
+	"outter":{
+		"values":{
+			"first":10,
+			"second":11
+		}
+	},
+	"outter2":"hello world"
+}`))
+
+jsonOutput := jsonParsedObj.Search("outter").String()
+// Becomes `{"values":{"first":10,"second":11}}`
+
+...
+```
+
+### Merge two containers
+
+You can merge a JSON structure into an existing one, where collisions will be
+converted into a JSON array.
+
+``` go
+jsonParsed1, _ := ParseJSON([]byte(`{"outter": {"value1": "one"}}`))
+jsonParsed2, _ := ParseJSON([]byte(`{"outter": {"inner": {"value3": "three"}}, "outter2": {"value2": "two"}}`))
+
+jsonParsed1.Merge(jsonParsed2)
+// Becomes `{"outter":{"inner":{"value3":"three"},"value1":"one"},"outter2":{"value2":"two"}}`
+```
+
+Arrays are merged:
+
+``` go
+jsonParsed1, _ := ParseJSON([]byte(`{"array": ["one"]}`))
+jsonParsed2, _ := ParseJSON([]byte(`{"array": ["two"]}`))
+
+jsonParsed1.Merge(jsonParsed2)
+// Becomes `{"array":["one", "two"]}`
+```
+
+### Parsing Numbers
+
+Gabs uses the `json` package under the bonnet, which by default will parse all
+number values into `float64`. If you need to parse `Int` values then you should
+use a `json.Decoder` (https://golang.org/pkg/encoding/json/#Decoder):
+
+``` go
+sample := []byte(`{"test":{"int":10, "float":6.66}}`)
+dec := json.NewDecoder(bytes.NewReader(sample))
+dec.UseNumber()
+
+val, err := gabs.ParseJSONDecoder(dec)
+if err != nil {
+    t.Errorf("Failed to parse: %v", err)
+    return
+}
+
+intValue, err := val.Path("test.int").Data().(json.Number).Int64()
+```
diff --git a/vendor/github.com/Jeffail/gabs/gabs.go b/vendor/github.com/Jeffail/gabs/gabs.go
new file mode 100644
index 00000000..a21a79d7
--- /dev/null
+++ b/vendor/github.com/Jeffail/gabs/gabs.go
@@ -0,0 +1,581 @@
+/*
+Copyright (c) 2014 Ashley Jeffs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+// Package gabs implements a simplified wrapper around creating and parsing JSON.
+package gabs
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"io"
+	"io/ioutil"
+	"strings"
+)
+
+//--------------------------------------------------------------------------------------------------
+
+var (
+	// ErrOutOfBounds - Index out of bounds.
+	ErrOutOfBounds = errors.New("out of bounds")
+
+	// ErrNotObjOrArray - The target is not an object or array type.
+	ErrNotObjOrArray = errors.New("not an object or array")
+
+	// ErrNotObj - The target is not an object type.
+	ErrNotObj = errors.New("not an object")
+
+	// ErrNotArray - The target is not an array type.
+	ErrNotArray = errors.New("not an array")
+
+	// ErrPathCollision - Creating a path failed because an element collided with an existing value.
+	ErrPathCollision = errors.New("encountered value collision whilst building path")
+
+	// ErrInvalidInputObj - The input value was not a map[string]interface{}.
+	ErrInvalidInputObj = errors.New("invalid input object")
+
+	// ErrInvalidInputText - The input data could not be parsed.
+	ErrInvalidInputText = errors.New("input text could not be parsed")
+
+	// ErrInvalidPath - The filepath was not valid.
+	ErrInvalidPath = errors.New("invalid file path")
+
+	// ErrInvalidBuffer - The input buffer contained an invalid JSON string
+	ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
+)
+
+//--------------------------------------------------------------------------------------------------
+
+// Container - an internal structure that holds a reference to the core interface map of the parsed
+// json. Use this container to move context.
+type Container struct {
+	object interface{}
+}
+
+// Data - Return the contained data as an interface{}.
+func (g *Container) Data() interface{} {
+	if g == nil {
+		return nil
+	}
+	return g.object
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Path - Search for a value using dot notation.
+func (g *Container) Path(path string) *Container {
+	return g.Search(strings.Split(path, ".")...)
+}
+
+// Search - Attempt to find and return an object within the JSON structure by specifying the
+// hierarchy of field names to locate the target. If the search encounters an array and has not
+// reached the end target then it will iterate each object of the array for the target and return
+// all of the results in a JSON array.
+func (g *Container) Search(hierarchy ...string) *Container {
+	var object interface{}
+
+	object = g.Data()
+	for target := 0; target < len(hierarchy); target++ {
+		if mmap, ok := object.(map[string]interface{}); ok {
+			object, ok = mmap[hierarchy[target]]
+			if !ok {
+				return nil
+			}
+		} else if marray, ok := object.([]interface{}); ok {
+			tmpArray := []interface{}{}
+			for _, val := range marray {
+				tmpGabs := &Container{val}
+				res := tmpGabs.Search(hierarchy[target:]...)
+				if res != nil {
+					tmpArray = append(tmpArray, res.Data())
+				}
+			}
+			if len(tmpArray) == 0 {
+				return nil
+			}
+			return &Container{tmpArray}
+		} else {
+			return nil
+		}
+	}
+	return &Container{object}
+}
+
+// S - Shorthand method, does the same thing as Search.
+func (g *Container) S(hierarchy ...string) *Container {
+	return g.Search(hierarchy...)
+}
+
+// Exists - Checks whether a path exists.
+func (g *Container) Exists(hierarchy ...string) bool {
+	return g.Search(hierarchy...) != nil
+}
+
+// ExistsP - Checks whether a dot notation path exists.
+func (g *Container) ExistsP(path string) bool {
+	return g.Exists(strings.Split(path, ".")...)
+}
+
+// Index - Attempt to find and return an object within a JSON array by index.
+func (g *Container) Index(index int) *Container {
+	if array, ok := g.Data().([]interface{}); ok {
+		if index >= len(array) {
+			return &Container{nil}
+		}
+		return &Container{array[index]}
+	}
+	return &Container{nil}
+}
+
+// Children - Return a slice of all the children of the array. This also works for objects, however,
+// the children returned for an object will NOT be in order and you lose the names of the returned
+// objects this way.
+func (g *Container) Children() ([]*Container, error) {
+	if array, ok := g.Data().([]interface{}); ok {
+		children := make([]*Container, len(array))
+		for i := 0; i < len(array); i++ {
+			children[i] = &Container{array[i]}
+		}
+		return children, nil
+	}
+	if mmap, ok := g.Data().(map[string]interface{}); ok {
+		children := []*Container{}
+		for _, obj := range mmap {
+			children = append(children, &Container{obj})
+		}
+		return children, nil
+	}
+	return nil, ErrNotObjOrArray
+}
+
+// ChildrenMap - Return a map of all the children of an object.
+func (g *Container) ChildrenMap() (map[string]*Container, error) {
+	if mmap, ok := g.Data().(map[string]interface{}); ok {
+		children := map[string]*Container{}
+		for name, obj := range mmap {
+			children[name] = &Container{obj}
+		}
+		return children, nil
+	}
+	return nil, ErrNotObj
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be
+// constructed, and if a collision occurs with a non object type whilst iterating the path an error
+// is returned.
+func (g *Container) Set(value interface{}, path ...string) (*Container, error) {
+	if len(path) == 0 {
+		g.object = value
+		return g, nil
+	}
+	var object interface{}
+	if g.object == nil {
+		g.object = map[string]interface{}{}
+	}
+	object = g.object
+	for target := 0; target < len(path); target++ {
+		if mmap, ok := object.(map[string]interface{}); ok {
+			if target == len(path)-1 {
+				mmap[path[target]] = value
+			} else if mmap[path[target]] == nil {
+				mmap[path[target]] = map[string]interface{}{}
+			}
+			object = mmap[path[target]]
+		} else {
+			return &Container{nil}, ErrPathCollision
+		}
+	}
+	return &Container{object}, nil
+}
+
+// SetP - Does the same as Set, but using a dot notation JSON path.
+func (g *Container) SetP(value interface{}, path string) (*Container, error) {
+	return g.Set(value, strings.Split(path, ".")...)
+}
+
+// SetIndex - Set a value of an array element based on the index.
+func (g *Container) SetIndex(value interface{}, index int) (*Container, error) {
+	if array, ok := g.Data().([]interface{}); ok {
+		if index >= len(array) {
+			return &Container{nil}, ErrOutOfBounds
+		}
+		array[index] = value
+		return &Container{array[index]}, nil
+	}
+	return &Container{nil}, ErrNotArray
+}
+
+// Object - Create a new JSON object at a path. Returns an error if the path contains a collision
+// with a non object type.
+func (g *Container) Object(path ...string) (*Container, error) {
+	return g.Set(map[string]interface{}{}, path...)
+}
+
+// ObjectP - Does the same as Object, but using a dot notation JSON path.
+func (g *Container) ObjectP(path string) (*Container, error) {
+	return g.Object(strings.Split(path, ".")...)
+}
+
+// ObjectI - Create a new JSON object at an array index. Returns an error if the object is not an
+// array or the index is out of bounds.
+func (g *Container) ObjectI(index int) (*Container, error) {
+	return g.SetIndex(map[string]interface{}{}, index)
+}
+
+// Array - Create a new JSON array at a path. Returns an error if the path contains a collision with
+// a non object type.
+func (g *Container) Array(path ...string) (*Container, error) {
+	return g.Set([]interface{}{}, path...)
+}
+
+// ArrayP - Does the same as Array, but using a dot notation JSON path.
+func (g *Container) ArrayP(path string) (*Container, error) {
+	return g.Array(strings.Split(path, ".")...)
+}
+
+// ArrayI - Create a new JSON array at an array index. Returns an error if the object is not an
+// array or the index is out of bounds.
+func (g *Container) ArrayI(index int) (*Container, error) {
+	return g.SetIndex([]interface{}{}, index)
+}
+
+// ArrayOfSize - Create a new JSON array of a particular size at a path. Returns an error if the
+// path contains a collision with a non object type.
+func (g *Container) ArrayOfSize(size int, path ...string) (*Container, error) {
+	a := make([]interface{}, size)
+	return g.Set(a, path...)
+}
+
+// ArrayOfSizeP - Does the same as ArrayOfSize, but using a dot notation JSON path.
+func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) {
+	return g.ArrayOfSize(size, strings.Split(path, ".")...)
+}
+
+// ArrayOfSizeI - Create a new JSON array of a particular size at an array index. Returns an error
+// if the object is not an array or the index is out of bounds.
+func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) {
+	a := make([]interface{}, size)
+	return g.SetIndex(a, index)
+}
+
+// Delete - Delete an element at a JSON path, an error is returned if the element does not exist.
+func (g *Container) Delete(path ...string) error {
+	var object interface{}
+
+	if g.object == nil {
+		return ErrNotObj
+	}
+	object = g.object
+	for target := 0; target < len(path); target++ {
+		if mmap, ok := object.(map[string]interface{}); ok {
+			if target == len(path)-1 {
+				if _, ok := mmap[path[target]]; ok {
+					delete(mmap, path[target])
+				} else {
+					return ErrNotObj
+				}
+			}
+			object = mmap[path[target]]
+		} else {
+			return ErrNotObj
+		}
+	}
+	return nil
+}
+
+// DeleteP - Does the same as Delete, but using a dot notation JSON path.
+func (g *Container) DeleteP(path string) error {
+	return g.Delete(strings.Split(path, ".")...)
+}
+
+// Merge - Merges two gabs-containers
+func (g *Container) Merge(toMerge *Container) error {
+	var recursiveFnc func(map[string]interface{}, []string) error
+	recursiveFnc = func(mmap map[string]interface{}, path []string) error {
+		for key, value := range mmap {
+			newPath := append(path, key)
+			if g.Exists(newPath...) {
+				target := g.Search(newPath...)
+				switch t := value.(type) {
+				case map[string]interface{}:
+					switch targetV := target.Data().(type) {
+					case map[string]interface{}:
+						if err := recursiveFnc(t, newPath); err != nil {
+							return err
+						}
+					case []interface{}:
+						g.Set(append(targetV, t), newPath...)
+					default:
+						newSlice := append([]interface{}{}, targetV)
+						g.Set(append(newSlice, t), newPath...)
+					}
+				case []interface{}:
+					for _, valueOfSlice := range t {
+						if err := g.ArrayAppend(valueOfSlice, newPath...); err != nil {
+							return err
+						}
+					}
+				default:
+					switch targetV := target.Data().(type) {
+					case []interface{}:
+						g.Set(append(targetV, t), newPath...)
+					default:
+						newSlice := append([]interface{}{}, targetV)
+						g.Set(append(newSlice, t), newPath...)
+					}
+				}
+			} else {
+				// path doesn't exist. So set the value
+				if _, err := g.Set(value, newPath...); err != nil {
+					return err
+				}
+			}
+		}
+		return nil
+	}
+	if mmap, ok := toMerge.Data().(map[string]interface{}); ok {
+		return recursiveFnc(mmap, []string{})
+	}
+	return nil
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/*
+Array modification/search - Keeping these options simple right now, no need for anything more
+complicated since you can just cast to []interface{}, modify and then reassign with Set.
+*/
+
+// ArrayAppend - Append a value onto a JSON array. If the target is not a JSON array then it will be
+// converted into one, with its contents as the first element of the array.
+func (g *Container) ArrayAppend(value interface{}, path ...string) error {
+	if array, ok := g.Search(path...).Data().([]interface{}); ok {
+		array = append(array, value)
+		_, err := g.Set(array, path...)
+		return err
+	}
+
+	newArray := []interface{}{}
+	if d := g.Search(path...).Data(); d != nil {
+		newArray = append(newArray, d)
+	}
+	newArray = append(newArray, value)
+
+	_, err := g.Set(newArray, path...)
+	return err
+}
+
+// ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path.
+func (g *Container) ArrayAppendP(value interface{}, path string) error {
+	return g.ArrayAppend(value, strings.Split(path, ".")...)
+}
+
+// ArrayRemove - Remove an element from a JSON array.
+func (g *Container) ArrayRemove(index int, path ...string) error {
+	if index < 0 {
+		return ErrOutOfBounds
+	}
+	array, ok := g.Search(path...).Data().([]interface{})
+	if !ok {
+		return ErrNotArray
+	}
+	if index < len(array) {
+		array = append(array[:index], array[index+1:]...)
+	} else {
+		return ErrOutOfBounds
+	}
+	_, err := g.Set(array, path...)
+	return err
+}
+
+// ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path.
+func (g *Container) ArrayRemoveP(index int, path string) error {
+	return g.ArrayRemove(index, strings.Split(path, ".")...)
+}
+
+// ArrayElement - Access an element from a JSON array.
+func (g *Container) ArrayElement(index int, path ...string) (*Container, error) {
+	if index < 0 {
+		return &Container{nil}, ErrOutOfBounds
+	}
+	array, ok := g.Search(path...).Data().([]interface{})
+	if !ok {
+		return &Container{nil}, ErrNotArray
+	}
+	if index < len(array) {
+		return &Container{array[index]}, nil
+	}
+	return &Container{nil}, ErrOutOfBounds
+}
+
+// ArrayElementP - Access an element from a JSON array using a dot notation JSON path.
+func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
+	return g.ArrayElement(index, strings.Split(path, ".")...)
+}
+
+// ArrayCount - Count the number of elements in a JSON array.
+func (g *Container) ArrayCount(path ...string) (int, error) {
+	if array, ok := g.Search(path...).Data().([]interface{}); ok {
+		return len(array), nil
+	}
+	return 0, ErrNotArray
+}
+
+// ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path.
+func (g *Container) ArrayCountP(path string) (int, error) {
+	return g.ArrayCount(strings.Split(path, ".")...)
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Bytes - Converts the contained object back to a JSON []byte blob.
+func (g *Container) Bytes() []byte {
+	if g.Data() != nil {
+		if bytes, err := json.Marshal(g.object); err == nil {
+			return bytes
+		}
+	}
+	return []byte("{}")
+}
+
+// BytesIndent - Converts the contained object to a JSON []byte blob formatted with prefix, indent.
+func (g *Container) BytesIndent(prefix string, indent string) []byte {
+	if g.object != nil {
+		if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil {
+			return bytes
+		}
+	}
+	return []byte("{}")
+}
+
+// String - Converts the contained object to a JSON formatted string.
+func (g *Container) String() string {
+	return string(g.Bytes())
+}
+
+// StringIndent - Converts the contained object back to a JSON formatted string with prefix, indent.
+func (g *Container) StringIndent(prefix string, indent string) string {
+	return string(g.BytesIndent(prefix, indent))
+}
+
+// EncodeOpt is a functional option for the EncodeJSON method.
+type EncodeOpt func(e *json.Encoder)
+
+// EncodeOptHTMLEscape sets the encoder to escape the JSON for html.
+func EncodeOptHTMLEscape(doEscape bool) EncodeOpt {
+	return func(e *json.Encoder) {
+		e.SetEscapeHTML(doEscape)
+	}
+}
+
+// EncodeOptIndent sets the encoder to indent the JSON output.
+func EncodeOptIndent(prefix string, indent string) EncodeOpt {
+	return func(e *json.Encoder) {
+		e.SetIndent(prefix, indent)
+	}
+}
+
+// EncodeJSON - Encodes the contained object back to a JSON formatted []byte
+// using a variant list of modifier functions for the encoder being used.
+// Functions for modifying the output are prefixed with EncodeOpt, e.g.
+// EncodeOptHTMLEscape.
+func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte {
+	var b bytes.Buffer
+	encoder := json.NewEncoder(&b)
+	encoder.SetEscapeHTML(false) // Do not escape by default.
+	for _, opt := range encodeOpts {
+		opt(encoder)
+	}
+	if err := encoder.Encode(g.object); err != nil {
+		return []byte("{}")
+	}
+	result := b.Bytes()
+	if len(result) > 0 {
+		result = result[:len(result)-1]
+	}
+	return result
+}
+
+// New - Create a new gabs JSON object.
+func New() *Container {
+	return &Container{map[string]interface{}{}}
+}
+
+// Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object.
+func Consume(root interface{}) (*Container, error) {
+	return &Container{root}, nil
+}
+
+// ParseJSON - Convert a string into a representation of the parsed JSON.
+func ParseJSON(sample []byte) (*Container, error) {
+	var gabs Container
+
+	if err := json.Unmarshal(sample, &gabs.object); err != nil {
+		return nil, err
+	}
+
+	return &gabs, nil
+}
+
+// ParseJSONDecoder - Convert a json.Decoder into a representation of the parsed JSON.
+func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
+	var gabs Container
+
+	if err := decoder.Decode(&gabs.object); err != nil {
+		return nil, err
+	}
+
+	return &gabs, nil
+}
+
+// ParseJSONFile - Read a file and convert into a representation of the parsed JSON.
+func ParseJSONFile(path string) (*Container, error) {
+	if len(path) > 0 {
+		cBytes, err := ioutil.ReadFile(path)
+		if err != nil {
+			return nil, err
+		}
+
+		container, err := ParseJSON(cBytes)
+		if err != nil {
+			return nil, err
+		}
+
+		return container, nil
+	}
+	return nil, ErrInvalidPath
+}
+
+// ParseJSONBuffer - Read the contents of a buffer into a representation of the parsed JSON.
+func ParseJSONBuffer(buffer io.Reader) (*Container, error) {
+	var gabs Container
+	jsonDecoder := json.NewDecoder(buffer)
+	if err := jsonDecoder.Decode(&gabs.object); err != nil {
+		return nil, err
+	}
+
+	return &gabs, nil
+}
+
+//--------------------------------------------------------------------------------------------------
diff --git a/vendor/github.com/Jeffail/gabs/gabs_logo.png b/vendor/github.com/Jeffail/gabs/gabs_logo.png
new file mode 100644
index 00000000..b6c1fad9
Binary files /dev/null and b/vendor/github.com/Jeffail/gabs/gabs_logo.png differ
diff --git a/vendor/github.com/gopackage/ddp/.gitignore b/vendor/github.com/gopackage/ddp/.gitignore
new file mode 100644
index 00000000..daf913b1
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/gopackage/ddp/LICENSE b/vendor/github.com/gopackage/ddp/LICENSE
new file mode 100644
index 00000000..03d77e8a
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2015, Metamech LLC.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/vendor/github.com/gopackage/ddp/README.md b/vendor/github.com/gopackage/ddp/README.md
new file mode 100644
index 00000000..fd62c718
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/README.md
@@ -0,0 +1,3 @@
+# ddp
+
+MeteorJS DDP library for Golang
diff --git a/vendor/github.com/gopackage/ddp/ddp.go b/vendor/github.com/gopackage/ddp/ddp.go
new file mode 100644
index 00000000..910adafd
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp.go
@@ -0,0 +1,79 @@
+// Package ddp implements the MeteorJS DDP protocol over websockets. Fallback
+// to longpolling is NOT supported (and is not planned on ever being supported
+// by this library). We will try to model the library after `net/http` - right
+// now the library is barebones and doesn't provide the pluggability of http.
+// However, that's the goal for the package eventually.
+package ddp
+
+import (
+	"fmt"
+	"log"
+	"sync"
+	"time"
+)
+
+// debugLog is true if we should log debugging information about the connection
+var debugLog = true
+
+// The main file contains common utility types.
+
+// -------------------------------------------------------------------
+
+// idManager provides simple incrementing IDs for ddp messages.
+type idManager struct {
+	// nextID is the next ID for API calls
+	nextID uint64
+	// idMutex is a mutex to protect ID updates
+	idMutex *sync.Mutex
+}
+
+// newidManager creates a new instance and sets up resources.
+func newidManager() *idManager {
+	return &idManager{idMutex: new(sync.Mutex)}
+}
+
+// newID issues a new ID for use in calls.
+func (id *idManager) newID() string {
+	id.idMutex.Lock()
+	next := id.nextID
+	id.nextID++
+	id.idMutex.Unlock()
+	return fmt.Sprintf("%x", next)
+}
+
+// -------------------------------------------------------------------
+
+// pingTracker tracks in-flight pings.
+type pingTracker struct {
+	handler func(error)
+	timeout time.Duration
+	timer   *time.Timer
+}
+
+// -------------------------------------------------------------------
+
+// Call represents an active RPC call.
+type Call struct {
+	ID            string      // The uuid for this method call
+	ServiceMethod string      // The name of the service and method to call.
+	Args          interface{} // The argument to the function (*struct).
+	Reply         interface{} // The reply from the function (*struct).
+	Error         error       // After completion, the error status.
+	Done          chan *Call  // Strobes when call is complete.
+	Owner         *Client     // Client that owns the method call
+}
+
+// done removes the call from any owners and strobes the done channel with itself.
+func (call *Call) done() {
+	delete(call.Owner.calls, call.ID)
+	select {
+	case call.Done <- call:
+		// ok
+	default:
+		// We don't want to block here.  It is the caller's responsibility to make
+		// sure the channel has enough buffer space. See comment in Go().
+		if debugLog {
+			log.Println("rpc: discarding Call reply due to insufficient Done chan capacity")
+		}
+	}
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_client.go b/vendor/github.com/gopackage/ddp/ddp_client.go
new file mode 100644
index 00000000..8d6323b7
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_client.go
@@ -0,0 +1,654 @@
+package ddp
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"sync"
+	"time"
+
+	"golang.org/x/net/websocket"
+	"errors"
+)
+
+const (
+	DISCONNECTED = iota
+	DIALING
+	CONNECTING
+	CONNECTED
+)
+
+type ConnectionListener interface {
+	Connected()
+}
+
+type ConnectionNotifier interface {
+	AddConnectionListener(listener ConnectionListener)
+}
+
+type StatusListener interface {
+	Status(status int)
+}
+
+type StatusNotifier interface {
+	AddStatusListener(listener StatusListener)
+}
+
+// Client represents a DDP client connection. The DDP client establish a DDP
+// session and acts as a message pump for other tools.
+type Client struct {
+	// HeartbeatInterval is the time between heartbeats to send
+	HeartbeatInterval time.Duration
+	// HeartbeatTimeout is the time for a heartbeat ping to timeout
+	HeartbeatTimeout time.Duration
+	// ReconnectInterval is the time between reconnections on bad connections
+	ReconnectInterval time.Duration
+
+	// writeStats controls statistics gathering for current websocket writes.
+	writeSocketStats *WriterStats
+	// writeStats controls statistics gathering for overall client writes.
+	writeStats *WriterStats
+	// writeLog controls logging for client writes.
+	writeLog *WriterLogger
+	// readStats controls statistics gathering for current websocket reads.
+	readSocketStats *ReaderStats
+	// readStats controls statistics gathering for overall client reads.
+	readStats *ReaderStats
+	// readLog control logging for clietn reads.
+	readLog *ReaderLogger
+	// reconnects in the number of reconnections the client has made
+	reconnects int64
+	// pingsIn is the number of pings received from the server
+	pingsIn int64
+	// pingsOut is te number of pings sent by the client
+	pingsOut int64
+
+	// session contains the DDP session token (can be used for reconnects and debugging).
+	session string
+	// version contains the negotiated DDP protocol version in use.
+	version string
+	// serverID the cluster node ID for the server we connected to
+	serverID string
+	// ws is the underlying websocket being used.
+	ws *websocket.Conn
+	// encoder is a JSON encoder to send outgoing packets to the websocket.
+	encoder *json.Encoder
+	// url the URL the websocket is connected to
+	url string
+	// origin is the origin for the websocket connection
+	origin string
+	// inbox is an incoming message channel
+	inbox chan map[string]interface{}
+	// errors is an incoming errors channel
+	errors chan error
+	// pingTimer is a timer for sending regular pings to the server
+	pingTimer *time.Timer
+	// pings tracks inflight pings based on each ping ID.
+	pings map[string][]*pingTracker
+	// calls tracks method invocations that are still in flight
+	calls map[string]*Call
+	// subs tracks active subscriptions. Map contains name->args
+	subs map[string]*Call
+	// collections contains all the collections currently subscribed
+	collections map[string]Collection
+	// connectionStatus is the current connection status of the client
+	connectionStatus int
+	// reconnectTimer is the timer tracking reconnections
+	reconnectTimer *time.Timer
+	// reconnectLock protects access to reconnection
+	reconnectLock *sync.Mutex
+
+	// statusListeners will be informed when the connection status of the client changes
+	statusListeners []StatusListener
+	// connectionListeners will be informed when a connection to the server is established
+	connectionListeners []ConnectionListener
+
+	// idManager tracks IDs for ddp messages
+	idManager
+}
+
+// NewClient creates a default client (using an internal websocket) to the
+// provided URL using the origin for the connection. The client will
+// automatically connect, upgrade to a websocket, and establish a DDP
+// connection session before returning the client. The client will
+// automatically and internally handle heartbeats and reconnects.
+//
+// TBD create an option to use an external websocket (aka htt.Transport)
+// TBD create an option to substitute heartbeat and reconnect behavior (aka http.Tranport)
+// TBD create an option to hijack the connection (aka http.Hijacker)
+// TBD create profiling features (aka net/http/pprof)
+func NewClient(url, origin string) *Client {
+	c := &Client{
+		HeartbeatInterval: time.Minute,      // Meteor impl default + 10 (we ping last)
+		HeartbeatTimeout:  15 * time.Second, // Meteor impl default
+		ReconnectInterval: 5 * time.Second,
+		collections:       map[string]Collection{},
+		url:               url,
+		origin:            origin,
+		inbox:             make(chan map[string]interface{}, 100),
+		errors:            make(chan error, 100),
+		pings:             map[string][]*pingTracker{},
+		calls:             map[string]*Call{},
+		subs:              map[string]*Call{},
+		connectionStatus:  DISCONNECTED,
+		reconnectLock:     &sync.Mutex{},
+
+		// Stats
+		writeSocketStats: NewWriterStats(nil),
+		writeStats:       NewWriterStats(nil),
+		readSocketStats:  NewReaderStats(nil),
+		readStats:        NewReaderStats(nil),
+
+		// Loggers
+		writeLog: NewWriterTextLogger(nil),
+		readLog:  NewReaderTextLogger(nil),
+
+		idManager: *newidManager(),
+	}
+	c.encoder = json.NewEncoder(c.writeStats)
+	c.SetSocketLogActive(false)
+
+	// We spin off an inbox processing goroutine
+	go c.inboxManager()
+
+	return c
+}
+
+// Session returns the negotiated session token for the connection.
+func (c *Client) Session() string {
+	return c.session
+}
+
+// Version returns the negotiated protocol version in use by the client.
+func (c *Client) Version() string {
+	return c.version
+}
+
+// AddStatusListener in order to receive status change updates.
+func (c *Client) AddStatusListener(listener StatusListener) {
+	c.statusListeners = append(c.statusListeners, listener)
+}
+
+// AddConnectionListener in order to receive connection updates.
+func (c *Client) AddConnectionListener(listener ConnectionListener) {
+	c.connectionListeners = append(c.connectionListeners, listener)
+}
+
+// status updates all status listeners with the new client status.
+func (c *Client) status(status int) {
+	if c.connectionStatus == status {
+		return
+	}
+	c.connectionStatus = status
+	for _, listener := range c.statusListeners {
+		listener.Status(status)
+	}
+}
+
+// Connect attempts to connect the client to the server.
+func (c *Client) Connect() error {
+	c.status(DIALING)
+	ws, err := websocket.Dial(c.url, "", c.origin)
+	if err != nil {
+		c.Close()
+		log.Println("Dial error", err)
+		c.reconnectLater()
+		return err
+	}
+	// Start DDP connection
+	c.start(ws, NewConnect())
+	return nil
+}
+
+// Reconnect attempts to reconnect the client to the server on the existing
+// DDP session.
+//
+// TODO needs a reconnect backoff so we don't trash a down server
+// TODO reconnect should not allow more reconnects while a reconnection is already in progress.
+func (c *Client) Reconnect() {
+	func() {
+		c.reconnectLock.Lock()
+		defer c.reconnectLock.Unlock()
+		if c.reconnectTimer != nil {
+			c.reconnectTimer.Stop()
+			c.reconnectTimer = nil
+		}
+	}()
+
+	c.Close()
+
+	c.reconnects++
+
+	// Reconnect
+	c.status(DIALING)
+	ws, err := websocket.Dial(c.url, "", c.origin)
+	if err != nil {
+		c.Close()
+		log.Println("Dial error", err)
+		c.reconnectLater()
+		return
+	}
+
+	c.start(ws, NewReconnect(c.session))
+
+	// --------------------------------------------------------------------
+	// We resume inflight or ongoing subscriptions - we don't have to wait
+	// for connection confirmation (messages can be pipelined).
+	// --------------------------------------------------------------------
+
+	// Send calls that haven't been confirmed - may not have been sent
+	// and effects should be idempotent
+	for _, call := range c.calls {
+		c.Send(NewMethod(call.ID, call.ServiceMethod, call.Args.([]interface{})))
+	}
+
+	// Resend subscriptions and patch up collections
+	for _, sub := range c.subs {
+		c.Send(NewSub(sub.ID, sub.ServiceMethod, sub.Args.([]interface{})))
+	}
+}
+
+// Subscribe subscribes to data updates.
+func (c *Client) Subscribe(subName string, done chan *Call, args ...interface{}) *Call {
+
+	if args == nil {
+		args = []interface{}{}
+	}
+	call := new(Call)
+	call.ID = c.newID()
+	call.ServiceMethod = subName
+	call.Args = args
+	call.Owner = c
+
+	if done == nil {
+		done = make(chan *Call, 10) // buffered.
+	} else {
+		// If caller passes done != nil, it must arrange that
+		// done has enough buffer for the number of simultaneous
+		// RPCs that will be using that channel.  If the channel
+		// is totally unbuffered, it's best not to run at all.
+		if cap(done) == 0 {
+			log.Panic("ddp.rpc: done channel is unbuffered")
+		}
+	}
+	call.Done = done
+	c.subs[call.ID] = call
+
+	// Save this subscription to the client so we can reconnect
+	subArgs := make([]interface{}, len(args))
+	copy(subArgs, args)
+
+	c.Send(NewSub(call.ID, subName, args))
+
+	return call
+}
+
+// Sub sends a synchronous subscription request to the server.
+func (c *Client) Sub(subName string, args ...interface{}) error {
+	call := <-c.Subscribe(subName, make(chan *Call, 1), args...).Done
+	return call.Error
+}
+
+// Go invokes the function asynchronously.  It returns the Call structure representing
+// the invocation.  The done channel will signal when the call is complete by returning
+// the same Call object.  If done is nil, Go will allocate a new channel.
+// If non-nil, done must be buffered or Go will deliberately crash.
+//
+// Go and Call are modeled after the standard `net/rpc` package versions.
+func (c *Client) Go(serviceMethod string, done chan *Call, args ...interface{}) *Call {
+
+	if args == nil {
+		args = []interface{}{}
+	}
+	call := new(Call)
+	call.ID = c.newID()
+	call.ServiceMethod = serviceMethod
+	call.Args = args
+	call.Owner = c
+	if done == nil {
+		done = make(chan *Call, 10) // buffered.
+	} else {
+		// If caller passes done != nil, it must arrange that
+		// done has enough buffer for the number of simultaneous
+		// RPCs that will be using that channel.  If the channel
+		// is totally unbuffered, it's best not to run at all.
+		if cap(done) == 0 {
+			log.Panic("ddp.rpc: done channel is unbuffered")
+		}
+	}
+	call.Done = done
+	c.calls[call.ID] = call
+
+	c.Send(NewMethod(call.ID, serviceMethod, args))
+
+	return call
+}
+
+// Call invokes the named function, waits for it to complete, and returns its error status.
+func (c *Client) Call(serviceMethod string, args ...interface{}) (interface{}, error) {
+	call := <-c.Go(serviceMethod, make(chan *Call, 1), args...).Done
+	return call.Reply, call.Error
+}
+
+// Ping sends a heartbeat signal to the server. The Ping doesn't look for
+// a response but may trigger the connection to reconnect if the ping timesout.
+// This is primarily useful for reviving an unresponsive Client connection.
+func (c *Client) Ping() {
+	c.PingPong(c.newID(), c.HeartbeatTimeout, func(err error) {
+		if err != nil {
+			// Is there anything else we should or can do?
+			c.reconnectLater()
+		}
+	})
+}
+
+// PingPong sends a heartbeat signal to the server and calls the provided
+// function when a pong is received. An optional id can be sent to help
+// track the responses - or an empty string can be used. It is the
+// responsibility of the caller to respond to any errors that may occur.
+func (c *Client) PingPong(id string, timeout time.Duration, handler func(error)) {
+	err := c.Send(NewPing(id))
+	if err != nil {
+		handler(err)
+		return
+	}
+	c.pingsOut++
+	pings, ok := c.pings[id]
+	if !ok {
+		pings = make([]*pingTracker, 0, 5)
+	}
+	tracker := &pingTracker{handler: handler, timeout: timeout, timer: time.AfterFunc(timeout, func() {
+		handler(fmt.Errorf("ping timeout"))
+	})}
+	c.pings[id] = append(pings, tracker)
+}
+
+// Send transmits messages to the server. The msg parameter must be json
+// encoder compatible.
+func (c *Client) Send(msg interface{}) error {
+	return c.encoder.Encode(msg)
+}
+
+// Close implements the io.Closer interface.
+func (c *Client) Close() {
+	// Shutdown out all outstanding pings
+	if c.pingTimer != nil {
+		c.pingTimer.Stop()
+		c.pingTimer = nil
+	}
+
+	// Close websocket
+	if c.ws != nil {
+		c.ws.Close()
+		c.ws = nil
+	}
+	for _, collection := range c.collections {
+		collection.reset()
+	}
+	c.status(DISCONNECTED)
+}
+
+// ResetStats resets the statistics for the client.
+func (c *Client) ResetStats() {
+	c.readSocketStats.Reset()
+	c.readStats.Reset()
+	c.writeSocketStats.Reset()
+	c.writeStats.Reset()
+	c.reconnects = 0
+	c.pingsIn = 0
+	c.pingsOut = 0
+}
+
+// Stats returns the read and write statistics of the client.
+func (c *Client) Stats() *ClientStats {
+	return &ClientStats{
+		Reads:       c.readSocketStats.Snapshot(),
+		TotalReads:  c.readStats.Snapshot(),
+		Writes:      c.writeSocketStats.Snapshot(),
+		TotalWrites: c.writeStats.Snapshot(),
+		Reconnects:  c.reconnects,
+		PingsSent:   c.pingsOut,
+		PingsRecv:   c.pingsIn,
+	}
+}
+
+// SocketLogActive returns the current logging status for the socket.
+func (c *Client) SocketLogActive() bool {
+	return c.writeLog.Active
+}
+
+// SetSocketLogActive to true to enable logging of raw socket data.
+func (c *Client) SetSocketLogActive(active bool) {
+	c.writeLog.Active = active
+	c.readLog.Active = active
+}
+
+// CollectionByName retrieves a collection by it's name.
+func (c *Client) CollectionByName(name string) Collection {
+	collection, ok := c.collections[name]
+	if !ok {
+		collection = NewCollection(name)
+		c.collections[name] = collection
+	}
+	return collection
+}
+
+// CollectionStats returns a snapshot of statistics for the currently known collections.
+func (c *Client) CollectionStats() []CollectionStats {
+	stats := make([]CollectionStats, 0, len(c.collections))
+	for name, collection := range c.collections {
+		stats = append(stats, CollectionStats{Name: name, Count: len(collection.FindAll())})
+	}
+	return stats
+}
+
+// start starts a new client connection on the provided websocket
+func (c *Client) start(ws *websocket.Conn, connect *Connect) {
+
+	c.status(CONNECTING)
+
+	c.ws = ws
+	c.writeLog.SetWriter(ws)
+	c.writeSocketStats = NewWriterStats(c.writeLog)
+	c.writeStats.SetWriter(c.writeSocketStats)
+	c.readLog.SetReader(ws)
+	c.readSocketStats = NewReaderStats(c.readLog)
+	c.readStats.SetReader(c.readSocketStats)
+
+	// We spin off an inbox stuffing goroutine
+	go c.inboxWorker(c.readStats)
+
+	c.Send(connect)
+}
+
+// inboxManager pulls messages from the inbox and routes them to appropriate
+// handlers.
+func (c *Client) inboxManager() {
+	for {
+		select {
+		case msg := <-c.inbox:
+			// Message!
+			//log.Println("Got message", msg)
+			mtype, ok := msg["msg"]
+			if ok {
+				switch mtype.(string) {
+				// Connection management
+				case "connected":
+					c.status(CONNECTED)
+					for _, collection := range c.collections {
+						collection.init()
+					}
+					c.version = "1" // Currently the only version we support
+					c.session = msg["session"].(string)
+					// Start automatic heartbeats
+					c.pingTimer = time.AfterFunc(c.HeartbeatInterval, func() {
+						c.Ping()
+						c.pingTimer.Reset(c.HeartbeatInterval)
+					})
+					// Notify connection listeners
+					for _, listener := range c.connectionListeners {
+						go listener.Connected()
+					}
+				case "failed":
+					log.Fatalf("IM Failed to connect, we support version 1 but server supports %s", msg["version"])
+
+				// Heartbeats
+				case "ping":
+					// We received a ping - need to respond with a pong
+					id, ok := msg["id"]
+					if ok {
+						c.Send(NewPong(id.(string)))
+					} else {
+						c.Send(NewPong(""))
+					}
+					c.pingsIn++
+				case "pong":
+					// We received a pong - we can clear the ping tracker and call its handler
+					id, ok := msg["id"]
+					var key string
+					if ok {
+						key = id.(string)
+					}
+					pings, ok := c.pings[key]
+					if ok && len(pings) > 0 {
+						ping := pings[0]
+						pings = pings[1:]
+						if len(key) == 0 || len(pings) > 0 {
+							c.pings[key] = pings
+						}
+						ping.timer.Stop()
+						ping.handler(nil)
+					}
+
+				// Live Data
+				case "nosub":
+					log.Println("Subscription returned a nosub error", msg)
+					// Clear related subscriptions
+					sub, ok := msg["id"]
+					if ok {
+						id := sub.(string)
+						runningSub := c.subs[id]
+
+						if runningSub != nil {
+							runningSub.Error = errors.New("Subscription returned a nosub error")
+							runningSub.done()
+							delete(c.subs, id)
+						}
+					}
+				case "ready":
+					// Run 'done' callbacks on all ready subscriptions
+					subs, ok := msg["subs"]
+					if ok {
+						for _, sub := range subs.([]interface{}) {
+							call, ok := c.subs[sub.(string)]
+							if ok {
+								call.done()
+							}
+						}
+					}
+				case "added":
+					c.collectionBy(msg).added(msg)
+				case "changed":
+					c.collectionBy(msg).changed(msg)
+				case "removed":
+					c.collectionBy(msg).removed(msg)
+				case "addedBefore":
+					c.collectionBy(msg).addedBefore(msg)
+				case "movedBefore":
+					c.collectionBy(msg).movedBefore(msg)
+
+				// RPC
+				case "result":
+					id, ok := msg["id"]
+					if ok {
+						call := c.calls[id.(string)]
+						delete(c.calls, id.(string))
+						e, ok := msg["error"]
+						if ok {
+							txt, _ := json.Marshal(e)
+							call.Error = fmt.Errorf(string(txt))
+							call.Reply = e
+						} else {
+							call.Reply = msg["result"]
+						}
+						call.done()
+					}
+				case "updated":
+					// We currently don't do anything with updated status
+
+				default:
+					// Ignore?
+					log.Println("Server sent unexpected message", msg)
+				}
+			} else {
+				// Current Meteor server sends an undocumented DDP message
+				// (looks like clustering "hint"). We will register and
+				// ignore rather than log an error.
+				serverID, ok := msg["server_id"]
+				if ok {
+					switch ID := serverID.(type) {
+					case string:
+						c.serverID = ID
+					default:
+						log.Println("Server cluster node", serverID)
+					}
+				} else {
+					log.Println("Server sent message with no `msg` field", msg)
+				}
+			}
+		case err := <-c.errors:
+			log.Println("Websocket error", err)
+		}
+	}
+}
+
+func (c *Client) collectionBy(msg map[string]interface{}) Collection {
+	n, ok := msg["collection"]
+	if !ok {
+		return NewMockCollection()
+	}
+	switch name := n.(type) {
+	case string:
+		return c.CollectionByName(name)
+	default:
+		return NewMockCollection()
+	}
+}
+
+// inboxWorker pulls messages from a websocket, decodes JSON packets, and
+// stuffs them into a message channel.
+func (c *Client) inboxWorker(ws io.Reader) {
+	dec := json.NewDecoder(ws)
+	for {
+		var event interface{}
+
+		if err := dec.Decode(&event); err == io.EOF {
+			break
+		} else if err != nil {
+			c.errors <- err
+		}
+		if c.pingTimer != nil {
+			c.pingTimer.Reset(c.HeartbeatInterval)
+		}
+		if event == nil {
+			log.Println("Inbox worker found nil event. May be due to broken websocket. Reconnecting.")
+			break
+		} else {
+			c.inbox <- event.(map[string]interface{})
+		}
+	}
+
+	c.reconnectLater()
+}
+
+// reconnectLater schedules a reconnect for later. We need to make sure that we don't
+// block, and that we don't reconnect more frequently than once every c.ReconnectInterval
+func (c *Client) reconnectLater() {
+	c.Close()
+	c.reconnectLock.Lock()
+	defer c.reconnectLock.Unlock()
+	if c.reconnectTimer == nil {
+		c.reconnectTimer = time.AfterFunc(c.ReconnectInterval, c.Reconnect)
+	}
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_collection.go b/vendor/github.com/gopackage/ddp/ddp_collection.go
new file mode 100644
index 00000000..f417e68a
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_collection.go
@@ -0,0 +1,245 @@
+package ddp
+
+// ----------------------------------------------------------------------
+// Collection
+// ----------------------------------------------------------------------
+
+type Update map[string]interface{}
+type UpdateListener interface {
+	CollectionUpdate(collection, operation, id string, doc Update)
+}
+
+// Collection managed cached collection data sent from the server in a
+// livedata subscription.
+//
+// It would be great to build an entire mongo compatible local store (minimongo)
+type Collection interface {
+
+	// FindOne queries objects and returns the first match.
+	FindOne(id string) Update
+	// FindAll returns a map of all items in the cache - this is a hack
+	// until we have time to build out a real minimongo interface.
+	FindAll() map[string]Update
+	// AddUpdateListener adds a channel that receives update messages.
+	AddUpdateListener(listener UpdateListener)
+
+	// livedata updates
+	added(msg Update)
+	changed(msg Update)
+	removed(msg Update)
+	addedBefore(msg Update)
+	movedBefore(msg Update)
+	init()  // init informs the collection that the connection to the server has begun/resumed
+	reset() // reset informs the collection that the connection to the server has been lost
+}
+
+// NewMockCollection creates an empty collection that does nothing.
+func NewMockCollection() Collection {
+	return &MockCache{}
+}
+
+// NewCollection creates a new collection - always KeyCache.
+func NewCollection(name string) Collection {
+	return &KeyCache{name, make(map[string]Update), nil}
+}
+
+// KeyCache caches items keyed on unique ID.
+type KeyCache struct {
+	// The name of the collection
+	Name string
+	// items contains collection items by ID
+	items map[string]Update
+	// listeners contains all the listeners that should be notified of collection updates.
+	listeners []UpdateListener
+	// TODO(badslug): do we need to protect from multiple threads
+}
+
+func (c *KeyCache) added(msg Update) {
+	id, fields := parseUpdate(msg)
+	if fields != nil {
+		c.items[id] = fields
+		c.notify("create", id, fields)
+	}
+}
+
+func (c *KeyCache) changed(msg Update) {
+	id, fields := parseUpdate(msg)
+	if fields != nil {
+		item, ok := c.items[id]
+		if ok {
+			for key, value := range fields {
+				item[key] = value
+			}
+			c.items[id] = item
+			c.notify("update", id, item)
+		}
+	}
+}
+
+func (c *KeyCache) removed(msg Update) {
+	id, _ := parseUpdate(msg)
+	if len(id) > 0 {
+		delete(c.items, id)
+		c.notify("remove", id, nil)
+	}
+}
+
+func (c *KeyCache) addedBefore(msg Update) {
+	// for keyed cache, ordered commands are a noop
+}
+
+func (c *KeyCache) movedBefore(msg Update) {
+	// for keyed cache, ordered commands are a noop
+}
+
+// init prepares the collection for data updates (called when a new connection is
+// made or a connection/session is resumed).
+func (c *KeyCache) init() {
+	// TODO start to patch up the current data with fresh server state
+}
+
+func (c *KeyCache) reset() {
+	// TODO we should mark the collection but maintain it's contents and then
+	// patch up the current contents with the new contents when we receive them.
+	//c.items = nil
+	c.notify("reset", "", nil)
+}
+
+// notify sends a Update to all UpdateListener's which should never block.
+func (c *KeyCache) notify(operation, id string, doc Update) {
+	for _, listener := range c.listeners {
+		listener.CollectionUpdate(c.Name, operation, id, doc)
+	}
+}
+
+// FindOne returns the item with matching id.
+func (c *KeyCache) FindOne(id string) Update {
+	return c.items[id]
+}
+
+// FindAll returns a dump of all items in the collection
+func (c *KeyCache) FindAll() map[string]Update {
+	return c.items
+}
+
+// AddUpdateListener adds a listener for changes on a collection.
+func (c *KeyCache) AddUpdateListener(listener UpdateListener) {
+	c.listeners = append(c.listeners, listener)
+}
+
+// OrderedCache caches items based on list order.
+// This is a placeholder, currently not implemented as the Meteor server
+// does not transmit ordered collections over DDP yet.
+type OrderedCache struct {
+	// ranks contains ordered collection items for ordered collections
+	items []interface{}
+}
+
+func (c *OrderedCache) added(msg Update) {
+	// for ordered cache, key commands are a noop
+}
+
+func (c *OrderedCache) changed(msg Update) {
+
+}
+
+func (c *OrderedCache) removed(msg Update) {
+
+}
+
+func (c *OrderedCache) addedBefore(msg Update) {
+
+}
+
+func (c *OrderedCache) movedBefore(msg Update) {
+
+}
+
+func (c *OrderedCache) init() {
+
+}
+
+func (c *OrderedCache) reset() {
+
+}
+
+// FindOne returns the item with matching id.
+func (c *OrderedCache) FindOne(id string) Update {
+	return nil
+}
+
+// FindAll returns a dump of all items in the collection
+func (c *OrderedCache) FindAll() map[string]Update {
+	return map[string]Update{}
+}
+
+// AddUpdateListener does nothing.
+func (c *OrderedCache) AddUpdateListener(ch UpdateListener) {
+}
+
+// MockCache implements the Collection interface but does nothing with the data.
+type MockCache struct {
+}
+
+func (c *MockCache) added(msg Update) {
+
+}
+
+func (c *MockCache) changed(msg Update) {
+
+}
+
+func (c *MockCache) removed(msg Update) {
+
+}
+
+func (c *MockCache) addedBefore(msg Update) {
+
+}
+
+func (c *MockCache) movedBefore(msg Update) {
+
+}
+
+func (c *MockCache) init() {
+
+}
+
+func (c *MockCache) reset() {
+
+}
+
+// FindOne returns the item with matching id.
+func (c *MockCache) FindOne(id string) Update {
+	return nil
+}
+
+// FindAll returns a dump of all items in the collection
+func (c *MockCache) FindAll() map[string]Update {
+	return map[string]Update{}
+}
+
+// AddUpdateListener does nothing.
+func (c *MockCache) AddUpdateListener(ch UpdateListener) {
+}
+
+// parseUpdate returns the ID and fields from a DDP Update document.
+func parseUpdate(up Update) (ID string, Fields Update) {
+	key, ok := up["id"]
+	if ok {
+		switch id := key.(type) {
+		case string:
+			updates, ok := up["fields"]
+			if ok {
+				switch fields := updates.(type) {
+				case map[string]interface{}:
+					return id, Update(fields)
+				default:
+					// Don't know what to do...
+				}
+			}
+			return id, nil
+		}
+	}
+	return "", nil
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_ejson.go b/vendor/github.com/gopackage/ddp/ddp_ejson.go
new file mode 100644
index 00000000..a3e1fec0
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_ejson.go
@@ -0,0 +1,217 @@
+package ddp
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"io"
+	"strings"
+	"time"
+)
+
+// ----------------------------------------------------------------------
+// EJSON document interface
+// ----------------------------------------------------------------------
+// https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md#appendix-ejson
+
+// Doc provides hides the complexity of ejson documents.
+type Doc struct {
+	root interface{}
+}
+
+// NewDoc creates a new document from a generic json parsed document.
+func NewDoc(in interface{}) *Doc {
+	doc := &Doc{in}
+	return doc
+}
+
+// Map locates a map[string]interface{} - json object - at a path
+// or returns nil if not found.
+func (d *Doc) Map(path string) map[string]interface{} {
+	item := d.Item(path)
+	if item != nil {
+		switch m := item.(type) {
+		case map[string]interface{}:
+			return m
+		default:
+			return nil
+		}
+	}
+	return nil
+}
+
+// Array locates an []interface{} - json array - at a path
+// or returns nil if not found.
+func (d *Doc) Array(path string) []interface{} {
+	item := d.Item(path)
+	if item != nil {
+		switch m := item.(type) {
+		case []interface{}:
+			return m
+		default:
+			return nil
+		}
+	}
+	return nil
+}
+
+// StringArray locates an []string - json array of strings - at a path
+// or returns nil if not found. The string array will contain all string values
+// in the array and skip any non-string entries.
+func (d *Doc) StringArray(path string) []string {
+	item := d.Item(path)
+	if item != nil {
+		switch m := item.(type) {
+		case []interface{}:
+			items := []string{}
+			for _, item := range m {
+				switch val := item.(type) {
+				case string:
+					items = append(items, val)
+				}
+			}
+			return items
+		case []string:
+			return m
+		default:
+			return nil
+		}
+	}
+	return nil
+}
+
+// String returns a string value located at the path or an empty string if not found.
+func (d *Doc) String(path string) string {
+	item := d.Item(path)
+	if item != nil {
+		switch m := item.(type) {
+		case string:
+			return m
+		default:
+			return ""
+		}
+	}
+	return ""
+}
+
+// Bool returns a boolean value located at the path or false if not found.
+func (d *Doc) Bool(path string) bool {
+	item := d.Item(path)
+	if item != nil {
+		switch m := item.(type) {
+		case bool:
+			return m
+		default:
+			return false
+		}
+	}
+	return false
+}
+
+// Float returns a float64 value located at the path or zero if not found.
+func (d *Doc) Float(path string) float64 {
+	item := d.Item(path)
+	if item != nil {
+		switch m := item.(type) {
+		case float64:
+			return m
+		default:
+			return 0
+		}
+	}
+	return 0
+}
+
+// Time returns a time value located at the path or nil if not found.
+func (d *Doc) Time(path string) time.Time {
+	ticks := d.Float(path + ".$date")
+	var t time.Time
+	if ticks > 0 {
+		sec := int64(ticks / 1000)
+		t = time.Unix(int64(sec), 0)
+	}
+	return t
+}
+
+// Item locates a "raw" item at the provided path, returning
+// the item found or nil if not found.
+func (d *Doc) Item(path string) interface{} {
+	item := d.root
+	steps := strings.Split(path, ".")
+	for _, step := range steps {
+		// This is an intermediate step - we must be in a map
+		switch m := item.(type) {
+		case map[string]interface{}:
+			item = m[step]
+		case Update:
+			item = m[step]
+		default:
+			return nil
+		}
+	}
+	return item
+}
+
+// Set a value for a path. Intermediate items are created as necessary.
+func (d *Doc) Set(path string, value interface{}) {
+	item := d.root
+	steps := strings.Split(path, ".")
+	last := steps[len(steps)-1]
+	steps = steps[:len(steps)-1]
+	for _, step := range steps {
+		// This is an intermediate step - we must be in a map
+		switch m := item.(type) {
+		case map[string]interface{}:
+			item = m[step]
+			if item == nil {
+				item = map[string]interface{}{}
+				m[step] = item
+			}
+		default:
+			return
+		}
+	}
+	// Item is now the last map so we just set the value
+	switch m := item.(type) {
+	case map[string]interface{}:
+		m[last] = value
+	}
+}
+
+// Accounts password login support
+type Login struct {
+	User     *User     `json:"user"`
+	Password *Password `json:"password"`
+}
+
+func NewEmailLogin(email, pass string) *Login {
+	return &Login{User: &User{Email: email}, Password: NewPassword(pass)}
+}
+
+func NewUsernameLogin(user, pass string) *Login {
+	return &Login{User: &User{Username: user}, Password: NewPassword(pass)}
+}
+
+type LoginResume struct {
+	Token string `json:"resume"`
+}
+
+func NewLoginResume(token string) *LoginResume {
+	return &LoginResume{Token: token}
+}
+
+type User struct {
+	Email    string `json:"email,omitempty"`
+	Username string `json:"username,omitempty"`
+}
+
+type Password struct {
+	Digest    string `json:"digest"`
+	Algorithm string `json:"algorithm"`
+}
+
+func NewPassword(pass string) *Password {
+	sha := sha256.New()
+	io.WriteString(sha, pass)
+	digest := sha.Sum(nil)
+	return &Password{Digest: hex.EncodeToString(digest), Algorithm: "sha-256"}
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_messages.go b/vendor/github.com/gopackage/ddp/ddp_messages.go
new file mode 100644
index 00000000..68c9eab4
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_messages.go
@@ -0,0 +1,82 @@
+package ddp
+
+// ------------------------------------------------------------
+// DDP Messages
+//
+// Go structs representing DDP raw messages ready for JSON
+// encoding.
+// ------------------------------------------------------------
+
+// Message contains the common fields that all DDP messages use.
+type Message struct {
+	Type string `json:"msg"`
+	ID   string `json:"id,omitempty"`
+}
+
+// Connect represents a DDP connect message.
+type Connect struct {
+	Message
+	Version string   `json:"version"`
+	Support []string `json:"support"`
+	Session string   `json:"session,omitempty"`
+}
+
+// NewConnect creates a new connect message
+func NewConnect() *Connect {
+	return &Connect{Message: Message{Type: "connect"}, Version: "1", Support: []string{"1"}}
+}
+
+// NewReconnect creates a new connect message with a session ID to resume.
+func NewReconnect(session string) *Connect {
+	c := NewConnect()
+	c.Session = session
+	return c
+}
+
+// Ping represents a DDP ping message.
+type Ping Message
+
+// NewPing creates a new ping message with optional ID.
+func NewPing(id string) *Ping {
+	return &Ping{Type: "ping", ID: id}
+}
+
+// Pong represents a DDP pong message.
+type Pong Message
+
+// NewPong creates a new pong message with optional ID.
+func NewPong(id string) *Pong {
+	return &Pong{Type: "pong", ID: id}
+}
+
+// Method is used to send a remote procedure call to the server.
+type Method struct {
+	Message
+	ServiceMethod string        `json:"method"`
+	Args          []interface{} `json:"params"`
+}
+
+// NewMethod creates a new method invocation object.
+func NewMethod(id, serviceMethod string, args []interface{}) *Method {
+	return &Method{
+		Message:       Message{Type: "method", ID: id},
+		ServiceMethod: serviceMethod,
+		Args:          args,
+	}
+}
+
+// Sub is used to send a subscription request to the server.
+type Sub struct {
+	Message
+	SubName string        `json:"name"`
+	Args    []interface{} `json:"params"`
+}
+
+// NewSub creates a new sub object.
+func NewSub(id, subName string, args []interface{}) *Sub {
+	return &Sub{
+		Message: Message{Type: "sub", ID: id},
+		SubName: subName,
+		Args:    args,
+	}
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_stats.go b/vendor/github.com/gopackage/ddp/ddp_stats.go
new file mode 100644
index 00000000..1546b547
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_stats.go
@@ -0,0 +1,321 @@
+package ddp
+
+import (
+	"encoding/hex"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"sync"
+	"time"
+)
+
+// Gather statistics about a DDP connection.
+
+// ---------------------------------------------------------
+// io utilities
+//
+// This is generic - should be moved into a stand alone lib
+// ---------------------------------------------------------
+
+// ReaderProxy provides common tooling for structs that manage an io.Reader.
+type ReaderProxy struct {
+	reader io.Reader
+}
+
+// NewReaderProxy creates a new proxy for the provided reader.
+func NewReaderProxy(reader io.Reader) *ReaderProxy {
+	return &ReaderProxy{reader}
+}
+
+// SetReader sets the reader on the proxy.
+func (r *ReaderProxy) SetReader(reader io.Reader) {
+	r.reader = reader
+}
+
+// WriterProxy provides common tooling for structs that manage an io.Writer.
+type WriterProxy struct {
+	writer io.Writer
+}
+
+// NewWriterProxy creates a new proxy for the provided writer.
+func NewWriterProxy(writer io.Writer) *WriterProxy {
+	return &WriterProxy{writer}
+}
+
+// SetWriter sets the writer on the proxy.
+func (w *WriterProxy) SetWriter(writer io.Writer) {
+	w.writer = writer
+}
+
+// Logging data types
+const (
+	DataByte = iota // data is raw []byte
+	DataText        // data is string data
+)
+
+// Logger logs data from i/o sources.
+type Logger struct {
+	// Active is true if the logger should be logging reads
+	Active bool
+	// Truncate is >0 to indicate the number of characters to truncate output
+	Truncate int
+
+	logger *log.Logger
+	dtype  int
+}
+
+// NewLogger creates a new i/o logger.
+func NewLogger(logger *log.Logger, active bool, dataType int, truncate int) Logger {
+	return Logger{logger: logger, Active: active, dtype: dataType, Truncate: truncate}
+}
+
+// Log logs the current i/o operation and returns the read and error for
+// easy call chaining.
+func (l *Logger) Log(p []byte, n int, err error) (int, error) {
+	if l.Active && err == nil {
+		limit := n
+		truncated := false
+		if l.Truncate > 0 && l.Truncate < limit {
+			limit = l.Truncate
+			truncated = true
+		}
+		switch l.dtype {
+		case DataText:
+			if truncated {
+				l.logger.Printf("[%d] %s...", n, string(p[:limit]))
+			} else {
+				l.logger.Printf("[%d] %s", n, string(p[:limit]))
+			}
+		case DataByte:
+			fallthrough
+		default:
+			l.logger.Println(hex.Dump(p[:limit]))
+		}
+	}
+	return n, err
+}
+
+// ReaderLogger logs data from any io.Reader.
+// ReaderLogger wraps a Reader and passes data to the actual data consumer.
+type ReaderLogger struct {
+	Logger
+	ReaderProxy
+}
+
+// NewReaderDataLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewReaderDataLogger(reader io.Reader) *ReaderLogger {
+	logger := log.New(os.Stdout, "<- ", log.LstdFlags)
+	return NewReaderLogger(reader, logger, true, DataByte, 0)
+}
+
+// NewReaderTextLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewReaderTextLogger(reader io.Reader) *ReaderLogger {
+	logger := log.New(os.Stdout, "<- ", log.LstdFlags)
+	return NewReaderLogger(reader, logger, true, DataText, 80)
+}
+
+// NewReaderLogger creates a Reader logger for the provided parameters.
+func NewReaderLogger(reader io.Reader, logger *log.Logger, active bool, dataType int, truncate int) *ReaderLogger {
+	return &ReaderLogger{ReaderProxy: *NewReaderProxy(reader), Logger: NewLogger(logger, active, dataType, truncate)}
+}
+
+// Read logs the read bytes and passes them to the wrapped reader.
+func (r *ReaderLogger) Read(p []byte) (int, error) {
+	n, err := r.reader.Read(p)
+	return r.Log(p, n, err)
+}
+
+// WriterLogger logs data from any io.Writer.
+// WriterLogger wraps a Writer and passes data to the actual data producer.
+type WriterLogger struct {
+	Logger
+	WriterProxy
+}
+
+// NewWriterDataLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewWriterDataLogger(writer io.Writer) *WriterLogger {
+	logger := log.New(os.Stdout, "-> ", log.LstdFlags)
+	return NewWriterLogger(writer, logger, true, DataByte, 0)
+}
+
+// NewWriterTextLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewWriterTextLogger(writer io.Writer) *WriterLogger {
+	logger := log.New(os.Stdout, "-> ", log.LstdFlags)
+	return NewWriterLogger(writer, logger, true, DataText, 80)
+}
+
+// NewWriterLogger creates a Reader logger for the provided parameters.
+func NewWriterLogger(writer io.Writer, logger *log.Logger, active bool, dataType int, truncate int) *WriterLogger {
+	return &WriterLogger{WriterProxy: *NewWriterProxy(writer), Logger: NewLogger(logger, active, dataType, truncate)}
+}
+
+// Write logs the written bytes and passes them to the wrapped writer.
+func (w *WriterLogger) Write(p []byte) (int, error) {
+	if w.writer != nil {
+		n, err := w.writer.Write(p)
+		return w.Log(p, n, err)
+	}
+	return 0, nil
+}
+
+// Stats tracks statistics for i/o operations. Stats are produced from a
+// of a running stats agent.
+type Stats struct {
+	// Bytes is the total number of bytes transferred.
+	Bytes int64
+	// Ops is the total number of i/o operations performed.
+	Ops int64
+	// Errors is the total number of i/o errors encountered.
+	Errors int64
+	// Runtime is the duration that stats have been gathered.
+	Runtime time.Duration
+}
+
+// ClientStats displays combined statistics for the Client.
+type ClientStats struct {
+	// Reads provides statistics on the raw i/o network reads for the current connection.
+	Reads *Stats
+	// Reads provides statistics on the raw i/o network reads for the all client connections.
+	TotalReads *Stats
+	// Writes provides statistics on the raw i/o network writes for the current connection.
+	Writes *Stats
+	// Writes provides statistics on the raw i/o network writes for all the client connections.
+	TotalWrites *Stats
+	// Reconnects is the number of reconnections the client has made.
+	Reconnects int64
+	// PingsSent is the number of pings sent by the client
+	PingsSent int64
+	// PingsRecv is the number of pings received by the client
+	PingsRecv int64
+}
+
+// String produces a compact string representation of the client stats.
+func (stats *ClientStats) String() string {
+	i := stats.Reads
+	ti := stats.TotalReads
+	o := stats.Writes
+	to := stats.TotalWrites
+	totalRun := (ti.Runtime * 1000000) / 1000000
+	run := (i.Runtime * 1000000) / 1000000
+	return fmt.Sprintf("bytes: %d/%d##%d/%d ops: %d/%d##%d/%d err: %d/%d##%d/%d reconnects: %d pings: %d/%d uptime: %v##%v",
+		i.Bytes, o.Bytes,
+		ti.Bytes, to.Bytes,
+		i.Ops, o.Ops,
+		ti.Ops, to.Ops,
+		i.Errors, o.Errors,
+		ti.Errors, to.Errors,
+		stats.Reconnects,
+		stats.PingsRecv, stats.PingsSent,
+		run, totalRun)
+}
+
+// CollectionStats combines statistics about a collection.
+type CollectionStats struct {
+	Name  string // Name of the collection
+	Count int    // Count is the total number of documents in the collection
+}
+
+// String produces a compact string representation of the collection stat.
+func (s *CollectionStats) String() string {
+	return fmt.Sprintf("%s[%d]", s.Name, s.Count)
+}
+
+// StatsTracker provides the basic tooling for tracking i/o stats.
+type StatsTracker struct {
+	bytes  int64
+	ops    int64
+	errors int64
+	start  time.Time
+	lock   *sync.Mutex
+}
+
+// NewStatsTracker create a new stats tracker with start time set to now.
+func NewStatsTracker() *StatsTracker {
+	return &StatsTracker{start: time.Now(), lock: new(sync.Mutex)}
+}
+
+// Op records an i/o operation. The parameters are passed through to
+// allow easy chaining.
+func (t *StatsTracker) Op(n int, err error) (int, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ops++
+	if err == nil {
+		t.bytes += int64(n)
+	} else {
+		if err == io.EOF {
+			// I don't think we should log EOF stats as an error
+		} else {
+			t.errors++
+		}
+	}
+
+	return n, err
+}
+
+// Snapshot takes a snapshot of the current reader statistics.
+func (t *StatsTracker) Snapshot() *Stats {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	return t.snap()
+}
+
+// Reset sets all of the stats to initial values.
+func (t *StatsTracker) Reset() *Stats {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	stats := t.snap()
+	t.bytes = 0
+	t.ops = 0
+	t.errors = 0
+	t.start = time.Now()
+
+	return stats
+}
+
+func (t *StatsTracker) snap() *Stats {
+	return &Stats{Bytes: t.bytes, Ops: t.ops, Errors: t.errors, Runtime: time.Since(t.start)}
+}
+
+// ReaderStats tracks statistics on any io.Reader.
+// ReaderStats wraps a Reader and passes data to the actual data consumer.
+type ReaderStats struct {
+	StatsTracker
+	ReaderProxy
+}
+
+// NewReaderStats creates a ReaderStats object for the provided reader.
+func NewReaderStats(reader io.Reader) *ReaderStats {
+	return &ReaderStats{ReaderProxy: *NewReaderProxy(reader), StatsTracker: *NewStatsTracker()}
+}
+
+// Read passes through a read collecting statistics and logging activity.
+func (r *ReaderStats) Read(p []byte) (int, error) {
+	return r.Op(r.reader.Read(p))
+}
+
+// WriterStats tracks statistics on any io.Writer.
+// WriterStats wraps a Writer and passes data to the actual data producer.
+type WriterStats struct {
+	StatsTracker
+	WriterProxy
+}
+
+// NewWriterStats creates a WriterStats object for the provided writer.
+func NewWriterStats(writer io.Writer) *WriterStats {
+	return &WriterStats{WriterProxy: *NewWriterProxy(writer), StatsTracker: *NewStatsTracker()}
+}
+
+// Write passes through a write collecting statistics.
+func (w *WriterStats) Write(p []byte) (int, error) {
+	if w.writer != nil {
+		return w.Op(w.writer.Write(p))
+	}
+	return 0, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
new file mode 100644
index 00000000..c6579ece
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
@@ -0,0 +1,39 @@
+package models
+
+import "time"
+
+type Channel struct {
+	ID    string `json:"_id"`
+	Name  string `json:"name"`
+	Fname string `json:"fname,omitempty"`
+	Type  string `json:"t"`
+	Msgs  int    `json:"msgs"`
+
+	ReadOnly  bool `json:"ro,omitempty"`
+	SysMes    bool `json:"sysMes,omitempty"`
+	Default   bool `json:"default"`
+	Broadcast bool `json:"broadcast,omitempty"`
+
+	Timestamp *time.Time `json:"ts,omitempty"`
+	UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
+
+	User        *User    `json:"u,omitempty"`
+	LastMessage *Message `json:"lastMessage,omitempty"`
+
+	// Lm          interface{} `json:"lm"`
+	// CustomFields struct {
+	// } `json:"customFields,omitempty"`
+}
+
+type ChannelSubscription struct {
+	ID          string   `json:"_id"`
+	Alert       bool     `json:"alert"`
+	Name        string   `json:"name"`
+	DisplayName string   `json:"fname"`
+	Open        bool     `json:"open"`
+	RoomId      string   `json:"rid"`
+	Type        string   `json:"c"`
+	User        User     `json:"u"`
+	Roles       []string `json:"roles"`
+	Unread      float64  `json:"unread"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
new file mode 100644
index 00000000..fb99e7c2
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
@@ -0,0 +1,133 @@
+package models
+
+import "time"
+
+type Info struct {
+	Version string `json:"version"`
+
+	Build struct {
+		NodeVersion string `json:"nodeVersion"`
+		Arch        string `json:"arch"`
+		Platform    string `json:"platform"`
+		Cpus        int    `json:"cpus"`
+	} `json:"build"`
+
+	Commit struct {
+		Hash    string `json:"hash"`
+		Date    string `json:"date"`
+		Author  string `json:"author"`
+		Subject string `json:"subject"`
+		Tag     string `json:"tag"`
+		Branch  string `json:"branch"`
+	} `json:"commit"`
+}
+
+type Pagination struct {
+	Count  int `json:"count"`
+	Offset int `json:"offset"`
+	Total  int `json:"total"`
+}
+
+type Directory struct {
+	Result []struct {
+		ID        string    `json:"_id"`
+		CreatedAt time.Time `json:"createdAt"`
+		Emails    []struct {
+			Address  string `json:"address"`
+			Verified bool   `json:"verified"`
+		} `json:"emails"`
+		Name     string `json:"name"`
+		Username string `json:"username"`
+	} `json:"result"`
+
+	Pagination
+}
+
+type Spotlight struct {
+	Users []User    `json:"users"`
+	Rooms []Channel `json:"rooms"`
+}
+
+type Statistics struct {
+	ID       string `json:"_id"`
+	UniqueID string `json:"uniqueId"`
+	Version  string `json:"version"`
+
+	ActiveUsers    int `json:"activeUsers"`
+	NonActiveUsers int `json:"nonActiveUsers"`
+	OnlineUsers    int `json:"onlineUsers"`
+	AwayUsers      int `json:"awayUsers"`
+	OfflineUsers   int `json:"offlineUsers"`
+	TotalUsers     int `json:"totalUsers"`
+
+	TotalRooms                int `json:"totalRooms"`
+	TotalChannels             int `json:"totalChannels"`
+	TotalPrivateGroups        int `json:"totalPrivateGroups"`
+	TotalDirect               int `json:"totalDirect"`
+	TotlalLivechat            int `json:"totlalLivechat"`
+	TotalMessages             int `json:"totalMessages"`
+	TotalChannelMessages      int `json:"totalChannelMessages"`
+	TotalPrivateGroupMessages int `json:"totalPrivateGroupMessages"`
+	TotalDirectMessages       int `json:"totalDirectMessages"`
+	TotalLivechatMessages     int `json:"totalLivechatMessages"`
+
+	InstalledAt          time.Time `json:"installedAt"`
+	LastLogin            time.Time `json:"lastLogin"`
+	LastMessageSentAt    time.Time `json:"lastMessageSentAt"`
+	LastSeenSubscription time.Time `json:"lastSeenSubscription"`
+
+	Os struct {
+		Type     string    `json:"type"`
+		Platform string    `json:"platform"`
+		Arch     string    `json:"arch"`
+		Release  string    `json:"release"`
+		Uptime   int       `json:"uptime"`
+		Loadavg  []float64 `json:"loadavg"`
+		Totalmem int64     `json:"totalmem"`
+		Freemem  int       `json:"freemem"`
+		Cpus     []struct {
+			Model string `json:"model"`
+			Speed int    `json:"speed"`
+			Times struct {
+				User int `json:"user"`
+				Nice int `json:"nice"`
+				Sys  int `json:"sys"`
+				Idle int `json:"idle"`
+				Irq  int `json:"irq"`
+			} `json:"times"`
+		} `json:"cpus"`
+	} `json:"os"`
+
+	Process struct {
+		NodeVersion string  `json:"nodeVersion"`
+		Pid         int     `json:"pid"`
+		Uptime      float64 `json:"uptime"`
+	} `json:"process"`
+
+	Deploy struct {
+		Method   string `json:"method"`
+		Platform string `json:"platform"`
+	} `json:"deploy"`
+
+	Migration struct {
+		ID       string    `json:"_id"`
+		Version  int       `json:"version"`
+		Locked   bool      `json:"locked"`
+		LockedAt time.Time `json:"lockedAt"`
+		BuildAt  time.Time `json:"buildAt"`
+	} `json:"migration"`
+
+	InstanceCount int       `json:"instanceCount"`
+	CreatedAt     time.Time `json:"createdAt"`
+	UpdatedAt     time.Time `json:"_updatedAt"`
+}
+
+type StatisticsInfo struct {
+	Statistics Statistics `json:"statistics"`
+}
+
+type StatisticsList struct {
+	Statistics []Statistics `json:"statistics"`
+
+	Pagination
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
new file mode 100644
index 00000000..8be3e3b6
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
@@ -0,0 +1,75 @@
+package models
+
+import "time"
+
+type Message struct {
+	ID       string `json:"_id"`
+	RoomID   string `json:"rid"`
+	Msg      string `json:"msg"`
+	EditedBy string `json:"editedBy,omitempty"`
+
+	Groupable bool `json:"groupable,omitempty"`
+
+	EditedAt  *time.Time `json:"editedAt,omitempty"`
+	Timestamp *time.Time `json:"ts,omitempty"`
+	UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
+
+	Mentions []User `json:"mentions,omitempty"`
+	User     *User  `json:"u,omitempty"`
+	PostMessage
+
+	// Bot         interface{}  `json:"bot"`
+	// CustomFields interface{} `json:"customFields"`
+	// Channels           []interface{} `json:"channels"`
+	// SandstormSessionID interface{} `json:"sandstormSessionId"`
+}
+
+// PostMessage Payload for postmessage rest API
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
+type PostMessage struct {
+	RoomID      string       `json:"roomId,omitempty"`
+	Channel     string       `json:"channel,omitempty"`
+	Text        string       `json:"text,omitempty"`
+	ParseUrls   bool         `json:"parseUrls,omitempty"`
+	Alias       string       `json:"alias,omitempty"`
+	Emoji       string       `json:"emoji,omitempty"`
+	Avatar      string       `json:"avatar,omitempty"`
+	Attachments []Attachment `json:"attachments,omitempty"`
+}
+
+// Attachment Payload for postmessage rest API
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
+type Attachment struct {
+	Color       string `json:"color,omitempty"`
+	Text        string `json:"text,omitempty"`
+	Timestamp   string `json:"ts,omitempty"`
+	ThumbURL    string `json:"thumb_url,omitempty"`
+	MessageLink string `json:"message_link,omitempty"`
+	Collapsed   bool   `json:"collapsed"`
+
+	AuthorName string `json:"author_name,omitempty"`
+	AuthorLink string `json:"author_link,omitempty"`
+	AuthorIcon string `json:"author_icon,omitempty"`
+
+	Title             string `json:"title,omitempty"`
+	TitleLink         string `json:"title_link,omitempty"`
+	TitleLinkDownload string `json:"title_link_download,omitempty"`
+
+	ImageURL string `json:"image_url,omitempty"`
+
+	AudioURL string `json:"audio_url,omitempty"`
+	VideoURL string `json:"video_url,omitempty"`
+
+	Fields []AttachmentField `json:"fields,omitempty"`
+}
+
+// AttachmentField Payload for postmessage rest API
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
+type AttachmentField struct {
+	Short bool   `json:"short"`
+	Title string `json:"title"`
+	Value string `json:"value"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
new file mode 100644
index 00000000..052bad8a
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
@@ -0,0 +1,7 @@
+package models
+
+type Permission struct {
+	ID        string   `json:"_id"`
+	UpdatedAt string   `json:"_updatedAt.$date"`
+	Roles     []string `json:"roles"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
new file mode 100644
index 00000000..aeacb385
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
@@ -0,0 +1,21 @@
+package models
+
+type Setting struct {
+	ID           string  `json:"_id"`
+	Blocked      bool    `json:"blocked"`
+	Group        string  `json:"group"`
+	Hidden       bool    `json:"hidden"`
+	Public       bool    `json:"public"`
+	Type         string  `json:"type"`
+	PackageValue string  `json:"packageValue"`
+	Sorter       int     `json:"sorter"`
+	Value        string  `json:"value"`
+	ValueBool    bool    `json:"valueBool"`
+	ValueInt     float64 `json:"valueInt"`
+	ValueSource  string  `json:"valueSource"`
+	ValueAsset   Asset   `json:"asset"`
+}
+
+type Asset struct {
+	DefaultUrl string `json:"defaultUrl"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
new file mode 100644
index 00000000..ee56bdc3
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
@@ -0,0 +1,29 @@
+package models
+
+type User struct {
+	ID           string `json:"_id"`
+	Name         string `json:"name"`
+	UserName     string `json:"username"`
+	Status       string `json:"status"`
+	Token        string `json:"token"`
+	TokenExpires int64  `json:"tokenExpires"`
+}
+
+type CreateUserRequest struct {
+	Name         string            `json:"name"`
+	Email        string            `json:"email"`
+	Password     string            `json:"password"`
+	Username     string            `json:"username"`
+	CustomFields map[string]string `json:"customFields,omitempty"`
+}
+
+type UpdateUserRequest struct {
+	UserID string `json:"userId"`
+	Data   struct {
+		Name         string            `json:"name"`
+		Email        string            `json:"email"`
+		Password     string            `json:"password"`
+		Username     string            `json:"username"`
+		CustomFields map[string]string `json:"customFields,omitempty"`
+	} `json:"data"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
new file mode 100644
index 00000000..296e26fb
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
@@ -0,0 +1,10 @@
+package models
+
+type UserCredentials struct {
+	ID    string `json:"id"`
+	Token string `json:"token"`
+
+	Email    string `json:"email"`
+	Name     string `json:"name"`
+	Password string `json:"pass"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
new file mode 100644
index 00000000..5779cb38
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
@@ -0,0 +1,263 @@
+package realtime
+
+import (
+	"github.com/Jeffail/gabs"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+func (c *Client) GetChannelId(name string) (string, error) {
+	rawResponse, err := c.ddp.Call("getRoomIdByNameOrId", name)
+	if err != nil {
+		return "", err
+	}
+
+	//log.Println(rawResponse)
+
+	return rawResponse.(string), nil
+}
+
+// GetChannelsIn returns list of channels
+// Optionally includes date to get all since last check or 0 to get all
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-rooms/
+func (c *Client) GetChannelsIn() ([]models.Channel, error) {
+	rawResponse, err := c.ddp.Call("rooms/get", map[string]int{
+		"$date": 0,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
+
+	chans, err := document.Children()
+
+	var channels []models.Channel
+
+	for _, i := range chans {
+		channels = append(channels, models.Channel{
+			ID: stringOrZero(i.Path("_id").Data()),
+			//Default: stringOrZero(i.Path("default").Data()),
+			Name: stringOrZero(i.Path("name").Data()),
+			Type: stringOrZero(i.Path("t").Data()),
+		})
+	}
+
+	return channels, nil
+}
+
+// GetChannelSubscriptions gets users channel subscriptions
+// Optionally includes date to get all since last check or 0 to get all
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-subscriptions
+func (c *Client) GetChannelSubscriptions() ([]models.ChannelSubscription, error) {
+	rawResponse, err := c.ddp.Call("subscriptions/get", map[string]int{
+		"$date": 0,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
+
+	channelSubs, err := document.Children()
+
+	var channelSubscriptions []models.ChannelSubscription
+
+	for _, sub := range channelSubs {
+		channelSubscription := models.ChannelSubscription{
+			ID:          stringOrZero(sub.Path("_id").Data()),
+			Alert:       sub.Path("alert").Data().(bool),
+			Name:        stringOrZero(sub.Path("name").Data()),
+			DisplayName: stringOrZero(sub.Path("fname").Data()),
+			Open:        sub.Path("open").Data().(bool),
+			Type:        stringOrZero(sub.Path("t").Data()),
+			User: models.User{
+				ID:       stringOrZero(sub.Path("u._id").Data()),
+				UserName: stringOrZero(sub.Path("u.username").Data()),
+			},
+			Unread: sub.Path("unread").Data().(float64),
+		}
+
+		if sub.Path("roles").Data() != nil {
+			var roles []string
+			for _, role := range sub.Path("roles").Data().([]interface{}) {
+				roles = append(roles, role.(string))
+			}
+
+			channelSubscription.Roles = roles
+		}
+
+		channelSubscriptions = append(channelSubscriptions, channelSubscription)
+	}
+
+	return channelSubscriptions, nil
+}
+
+// GetChannelRoles returns room roles
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-room-roles
+func (c *Client) GetChannelRoles(roomId string) error {
+	_, err := c.ddp.Call("getRoomRoles", roomId)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// CreateChannel creates a channel
+// Takes name and users array
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-channels
+func (c *Client) CreateChannel(name string, users []string) error {
+	_, err := c.ddp.Call("createChannel", name, users)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// CreateGroup creates a private group
+// Takes group name and array of users
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-private-groups
+func (c *Client) CreateGroup(name string, users []string) error {
+	_, err := c.ddp.Call("createPrivateGroup", name, users)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// JoinChannel joins a channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/joining-channels
+func (c *Client) JoinChannel(roomId string) error {
+	_, err := c.ddp.Call("joinRoom", roomId)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// LeaveChannel leaves a channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/leaving-rooms
+func (c *Client) LeaveChannel(roomId string) error {
+	_, err := c.ddp.Call("leaveRoom", roomId)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ArchiveChannel archives the channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/archive-rooms
+func (c *Client) ArchiveChannel(roomId string) error {
+	_, err := c.ddp.Call("archiveRoom", roomId)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UnArchiveChannel unarchives the channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unarchive-rooms
+func (c *Client) UnArchiveChannel(roomId string) error {
+	_, err := c.ddp.Call("unarchiveRoom", roomId)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteChannel deletes the channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-rooms
+func (c *Client) DeleteChannel(roomId string) error {
+	_, err := c.ddp.Call("eraseRoom", roomId)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SetChannelTopic sets channel topic
+// takes roomId and topic
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelTopic(roomId string, topic string) error {
+	_, err := c.ddp.Call("saveRoomSettings", roomId, "roomTopic", topic)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SetChannelType sets the channel type
+// takes roomId and roomType
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelType(roomId string, roomType string) error {
+	_, err := c.ddp.Call("saveRoomSettings", roomId, "roomType", roomType)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SetChannelJoinCode sets channel join code
+// takes roomId and joinCode
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelJoinCode(roomId string, joinCode string) error {
+	_, err := c.ddp.Call("saveRoomSettings", roomId, "joinCode", joinCode)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SetChannelReadOnly sets channel as read only
+// takes roomId and boolean
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelReadOnly(roomId string, readOnly bool) error {
+	_, err := c.ddp.Call("saveRoomSettings", roomId, "readOnly", readOnly)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SetChannelDescription sets channels description
+// takes roomId and description
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelDescription(roomId string, description string) error {
+	_, err := c.ddp.Call("saveRoomSettings", roomId, "roomDescription", description)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
new file mode 100644
index 00000000..1dde80bf
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
@@ -0,0 +1,96 @@
+// Provides access to Rocket.Chat's realtime API via ddp
+package realtime
+
+import (
+	"fmt"
+	"math/rand"
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/gopackage/ddp"
+)
+
+type Client struct {
+	ddp *ddp.Client
+}
+
+// Creates a new instance and connects to the websocket.
+func NewClient(serverURL *url.URL, debug bool) (*Client, error) {
+	rand.Seed(time.Now().UTC().UnixNano())
+
+	wsURL := "ws"
+	port := 80
+
+	if serverURL.Scheme == "https" {
+		wsURL = "wss"
+		port = 443
+	}
+
+	if len(serverURL.Port()) > 0 {
+		port, _ = strconv.Atoi(serverURL.Port())
+	}
+
+	wsURL = fmt.Sprintf("%s://%v:%v%s/websocket", wsURL, serverURL.Hostname(), port, serverURL.Path)
+
+	//	log.Println("About to connect to:", wsURL, port, serverURL.Scheme)
+
+	c := new(Client)
+	c.ddp = ddp.NewClient(wsURL, serverURL.String())
+
+	if debug {
+		c.ddp.SetSocketLogActive(true)
+	}
+
+	if err := c.ddp.Connect(); err != nil {
+		return nil, err
+	}
+
+	return c, nil
+}
+
+type statusListener struct {
+	listener func(int)
+}
+
+func (s statusListener) Status(status int) {
+	s.listener(status)
+}
+
+func (c *Client) AddStatusListener(listener func(int)) {
+	c.ddp.AddStatusListener(statusListener{listener: listener})
+}
+
+func (c *Client) Reconnect() {
+	c.ddp.Reconnect()
+}
+
+// ConnectionAway sets connection status to away
+func (c *Client) ConnectionAway() error {
+	_, err := c.ddp.Call("UserPresence:away")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ConnectionOnline sets connection status to online
+func (c *Client) ConnectionOnline() error {
+	_, err := c.ddp.Call("UserPresence:online")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Close closes the ddp session
+func (c *Client) Close() {
+	c.ddp.Close()
+}
+
+// Some of the rocketchat objects need unique IDs specified by the client
+func (c *Client) newRandomId() string {
+	return fmt.Sprintf("%f", rand.Float64())
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
new file mode 100644
index 00000000..90f2c6ee
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
@@ -0,0 +1,10 @@
+package realtime
+
+func (c *Client) getCustomEmoji() error {
+	_, err := c.ddp.Call("listEmojiCustom")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
new file mode 100644
index 00000000..f3c945cf
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
@@ -0,0 +1,21 @@
+package realtime
+
+import "fmt"
+
+func (c *Client) StartTyping(roomId string, username string) error {
+	_, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, true)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *Client) StopTyping(roomId string, username string) error {
+	_, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, false)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
new file mode 100644
index 00000000..9c0c9bb4
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
@@ -0,0 +1,240 @@
+package realtime
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+
+	"github.com/Jeffail/gabs"
+	"github.com/gopackage/ddp"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+const (
+	// RocketChat doesn't send the `added` event for new messages by default, only `changed`.
+	send_added_event    = true
+	default_buffer_size = 100
+)
+
+// LoadHistory loads history
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/load-history
+func (c *Client) LoadHistory(roomId string) error {
+	_, err := c.ddp.Call("loadHistory", roomId)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SendMessage sends message to channel
+// takes channel and message
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/send-message
+func (c *Client) SendMessage(m *models.Message) (*models.Message, error) {
+	m.ID = c.newRandomId()
+
+	rawResponse, err := c.ddp.Call("sendMessage", m)
+	if err != nil {
+		return nil, err
+	}
+
+	return getMessageFromData(rawResponse.(map[string]interface{})), nil
+}
+
+// EditMessage edits a message
+// takes message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/update-message
+func (c *Client) EditMessage(message *models.Message) error {
+	_, err := c.ddp.Call("updateMessage", message)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteMessage deletes a message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-message
+func (c *Client) DeleteMessage(message *models.Message) error {
+	_, err := c.ddp.Call("deleteMessage", map[string]string{
+		"_id": message.ID,
+	})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ReactToMessage adds a reaction to a message
+// takes a message and emoji
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/set-reaction
+func (c *Client) ReactToMessage(message *models.Message, reaction string) error {
+	_, err := c.ddp.Call("setReaction", reaction, message.ID)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// StarMessage stars message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
+func (c *Client) StarMessage(message *models.Message) error {
+	_, err := c.ddp.Call("starMessage", map[string]interface{}{
+		"_id":     message.ID,
+		"rid":     message.RoomID,
+		"starred": true,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UnStarMessage unstars message
+// takes message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
+func (c *Client) UnStarMessage(message *models.Message) error {
+	_, err := c.ddp.Call("starMessage", map[string]interface{}{
+		"_id":     message.ID,
+		"rid":     message.RoomID,
+		"starred": false,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// PinMessage pins a message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/pin-message
+func (c *Client) PinMessage(message *models.Message) error {
+	_, err := c.ddp.Call("pinMessage", message)
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UnPinMessage unpins message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unpin-messages
+func (c *Client) UnPinMessage(message *models.Message) error {
+	_, err := c.ddp.Call("unpinMessage", message)
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SubscribeToMessageStream Subscribes to the message updates of a channel
+// Returns a buffered channel
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
+func (c *Client) SubscribeToMessageStream(channel *models.Channel, msgChannel chan models.Message) error {
+
+	if err := c.ddp.Sub("stream-room-messages", channel.ID, send_added_event); err != nil {
+		return err
+	}
+
+	//msgChannel := make(chan models.Message, default_buffer_size)
+	c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(messageExtractor{msgChannel, "update"})
+
+	return nil
+}
+
+func getMessagesFromUpdateEvent(update ddp.Update) []models.Message {
+	document, _ := gabs.Consume(update["args"])
+	args, err := document.Children()
+
+	if err != nil {
+		//	log.Printf("Event arguments are in an unexpected format: %v", err)
+		return make([]models.Message, 0)
+	}
+
+	messages := make([]models.Message, len(args))
+
+	for i, arg := range args {
+		messages[i] = *getMessageFromDocument(arg)
+	}
+
+	return messages
+}
+
+func getMessageFromData(data interface{}) *models.Message {
+	// TODO: We should know what this will look like, we shouldn't need to use gabs
+	document, _ := gabs.Consume(data)
+	return getMessageFromDocument(document)
+}
+
+func getMessageFromDocument(arg *gabs.Container) *models.Message {
+	var ts *time.Time
+	date := stringOrZero(arg.Path("ts.$date").Data())
+	if len(date) > 0 {
+		if ti, err := strconv.ParseFloat(date, 64); err == nil {
+			t := time.Unix(int64(ti)/1e3, int64(ti)%1e3)
+			ts = &t
+		}
+	}
+	return &models.Message{
+		ID:        stringOrZero(arg.Path("_id").Data()),
+		RoomID:    stringOrZero(arg.Path("rid").Data()),
+		Msg:       stringOrZero(arg.Path("msg").Data()),
+		Timestamp: ts,
+		User: &models.User{
+			ID:       stringOrZero(arg.Path("u._id").Data()),
+			UserName: stringOrZero(arg.Path("u.username").Data()),
+		},
+	}
+}
+
+func stringOrZero(i interface{}) string {
+	if i == nil {
+		return ""
+	}
+
+	switch i.(type) {
+	case string:
+		return i.(string)
+	case float64:
+		return fmt.Sprintf("%f", i.(float64))
+	default:
+		return ""
+	}
+}
+
+type messageExtractor struct {
+	messageChannel chan models.Message
+	operation      string
+}
+
+func (u messageExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
+	if operation == u.operation {
+		msgs := getMessagesFromUpdateEvent(doc)
+		for _, m := range msgs {
+			u.messageChannel <- m
+		}
+	}
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
new file mode 100644
index 00000000..fc5df3da
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
@@ -0,0 +1,54 @@
+package realtime
+
+import (
+	"github.com/Jeffail/gabs"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+// GetPermissions gets permissions
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-permissions
+func (c *Client) GetPermissions() ([]models.Permission, error) {
+	rawResponse, err := c.ddp.Call("permissions/get")
+	if err != nil {
+		return nil, err
+	}
+
+	document, _ := gabs.Consume(rawResponse)
+
+	perms, _ := document.Children()
+
+	var permissions []models.Permission
+
+	for _, permission := range perms {
+		var roles []string
+		for _, role := range permission.Path("roles").Data().([]interface{}) {
+			roles = append(roles, role.(string))
+		}
+
+		permissions = append(permissions, models.Permission{
+			ID:    stringOrZero(permission.Path("_id").Data()),
+			Roles: roles,
+		})
+	}
+
+	return permissions, nil
+}
+
+// GetUserRoles gets current users roles
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-user-roles
+func (c *Client) GetUserRoles() error {
+	rawResponse, err := c.ddp.Call("getUserRoles")
+	if err != nil {
+		return err
+	}
+
+	document, _ := gabs.Consume(rawResponse)
+
+	_, err = document.Children()
+	// TODO: Figure out if this function is even useful if so return it
+	//log.Println(roles)
+
+	return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
new file mode 100644
index 00000000..c37eedbd
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
@@ -0,0 +1,53 @@
+package realtime
+
+import (
+	"github.com/Jeffail/gabs"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+// GetPublicSettings gets public settings
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-public-settings
+func (c *Client) GetPublicSettings() ([]models.Setting, error) {
+	rawResponse, err := c.ddp.Call("public-settings/get")
+	if err != nil {
+		return nil, err
+	}
+
+	document, _ := gabs.Consume(rawResponse)
+
+	sett, _ := document.Children()
+
+	var settings []models.Setting
+
+	for _, rawSetting := range sett {
+		setting := models.Setting{
+			ID:   stringOrZero(rawSetting.Path("_id").Data()),
+			Type: stringOrZero(rawSetting.Path("type").Data()),
+		}
+
+		switch setting.Type {
+		case "boolean":
+			setting.ValueBool = rawSetting.Path("value").Data().(bool)
+		case "string":
+			setting.Value = stringOrZero(rawSetting.Path("value").Data())
+		case "code":
+			setting.Value = stringOrZero(rawSetting.Path("value").Data())
+		case "color":
+			setting.Value = stringOrZero(rawSetting.Path("value").Data())
+		case "int":
+			setting.ValueInt = rawSetting.Path("value").Data().(float64)
+		case "asset":
+			setting.ValueAsset = models.Asset{
+				DefaultUrl: stringOrZero(rawSetting.Path("value").Data().(map[string]interface{})["defaultUrl"]),
+			}
+
+		default:
+			//	log.Println(setting.Type, rawSetting.Path("value").Data())
+		}
+
+		settings = append(settings, setting)
+	}
+
+	return settings, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
new file mode 100644
index 00000000..5013e53d
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
@@ -0,0 +1,41 @@
+package realtime
+
+import (
+	"fmt"
+
+	"github.com/gopackage/ddp"
+)
+
+// Subscribes to stream-notify-logged
+// Returns a buffered channel
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
+func (c *Client) Sub(name string, args ...interface{}) (chan string, error) {
+
+	if args == nil {
+		//log.Println("no args passed")
+		if err := c.ddp.Sub(name); err != nil {
+			return nil, err
+		}
+	} else {
+		if err := c.ddp.Sub(name, args[0], false); err != nil {
+			return nil, err
+		}
+	}
+
+	msgChannel := make(chan string, default_buffer_size)
+	c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(genericExtractor{msgChannel, "update"})
+
+	return msgChannel, nil
+}
+
+type genericExtractor struct {
+	messageChannel chan string
+	operation      string
+}
+
+func (u genericExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
+	if operation == u.operation {
+		u.messageChannel <- fmt.Sprintf("%s -> update", collection)
+	}
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
new file mode 100644
index 00000000..09a4f1f4
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
@@ -0,0 +1,103 @@
+package realtime
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"strconv"
+
+	"github.com/Jeffail/gabs"
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type ddpLoginRequest struct {
+	User     ddpUser     `json:"user"`
+	Password ddpPassword `json:"password"`
+}
+
+type ddpTokenLoginRequest struct {
+	Token string `json:"resume"`
+}
+
+type ddpUser struct {
+	Email string `json:"email"`
+}
+
+type ddpPassword struct {
+	Digest    string `json:"digest"`
+	Algorithm string `json:"algorithm"`
+}
+
+// RegisterUser a new user on the server. This function does not need a logged in user. The registered user gets logged in
+// to set its username.
+func (c *Client) RegisterUser(credentials *models.UserCredentials) (*models.User, error) {
+
+	if _, err := c.ddp.Call("registerUser", credentials); err != nil {
+		return nil, err
+	}
+
+	user, err := c.Login(credentials)
+	if err != nil {
+		return nil, err
+	}
+
+	if _, err := c.ddp.Call("setUsername", credentials.Name); err != nil {
+		return nil, err
+	}
+
+	return user, nil
+}
+
+// Login a user.
+// token shouldn't be nil, otherwise the password and the email are not allowed to be nil.
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/login/
+func (c *Client) Login(credentials *models.UserCredentials) (*models.User, error) {
+	var request interface{}
+	if credentials.Token != "" {
+		request = ddpTokenLoginRequest{
+			Token: credentials.Token,
+		}
+	} else {
+		digest := sha256.Sum256([]byte(credentials.Password))
+		request = ddpLoginRequest{
+			User: ddpUser{Email: credentials.Email},
+			Password: ddpPassword{
+				Digest:    hex.EncodeToString(digest[:]),
+				Algorithm: "sha-256",
+			},
+		}
+	}
+
+	rawResponse, err := c.ddp.Call("login", request)
+	if err != nil {
+		return nil, err
+	}
+
+	user := getUserFromData(rawResponse.(map[string]interface{}))
+	if credentials.Token == "" {
+		credentials.ID, credentials.Token = user.ID, user.Token
+	}
+
+	return user, nil
+}
+
+func getUserFromData(data interface{}) *models.User {
+	document, _ := gabs.Consume(data)
+
+	expires, _ := strconv.ParseFloat(stringOrZero(document.Path("tokenExpires.$date").Data()), 64)
+	return &models.User{
+		ID:           stringOrZero(document.Path("id").Data()),
+		Token:        stringOrZero(document.Path("token").Data()),
+		TokenExpires: int64(expires),
+	}
+}
+
+// SetPresence set user presence
+func (c *Client) SetPresence(status string) error {
+	_, err := c.ddp.Call("UserPresence:setDefaultStatus", status)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
new file mode 100644
index 00000000..71377500
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
@@ -0,0 +1,64 @@
+package rest
+
+import (
+	"bytes"
+	"fmt"
+	"net/url"
+
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type ChannelsResponse struct {
+	Status
+	models.Pagination
+	Channels []models.Channel `json:"channels"`
+}
+
+type ChannelResponse struct {
+	Status
+	Channel models.Channel `json:"channel"`
+}
+
+// GetPublicChannels returns all channels that can be seen by the logged in user.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/list
+func (c *Client) GetPublicChannels() (*ChannelsResponse, error) {
+	response := new(ChannelsResponse)
+	if err := c.Get("channels.list", nil, response); err != nil {
+		return nil, err
+	}
+
+	return response, nil
+}
+
+// GetJoinedChannels returns all channels that the user has joined.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/list-joined
+func (c *Client) GetJoinedChannels(params url.Values) (*ChannelsResponse, error) {
+	response := new(ChannelsResponse)
+	if err := c.Get("channels.list.joined", params, response); err != nil {
+		return nil, err
+	}
+
+	return response, nil
+}
+
+// LeaveChannel leaves a channel. The id of the channel has to be not nil.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/leave
+func (c *Client) LeaveChannel(channel *models.Channel) error {
+	var body = fmt.Sprintf(`{ "roomId": "%s"}`, channel.ID)
+	return c.Post("channels.leave", bytes.NewBufferString(body), new(ChannelResponse))
+}
+
+// GetChannelInfo get information about a channel. That might be useful to update the usernames.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/info
+func (c *Client) GetChannelInfo(channel *models.Channel) (*models.Channel, error) {
+	response := new(ChannelResponse)
+	if err := c.Get("channels.info", url.Values{"roomId": []string{channel.ID}}, response); err != nil {
+		return nil, err
+	}
+
+	return &response.Channel, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
new file mode 100644
index 00000000..0e37123e
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
@@ -0,0 +1,176 @@
+//Package rest provides a RocketChat rest client.
+package rest
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/url"
+)
+
+var (
+	ResponseErr = fmt.Errorf("got false response")
+)
+
+type Response interface {
+	OK() error
+}
+
+type Client struct {
+	Protocol string
+	Host     string
+	Path     string
+	Port     string
+	Version  string
+
+	// Use this switch to see all network communication.
+	Debug bool
+
+	auth *authInfo
+}
+
+type Status struct {
+	Success bool   `json:"success"`
+	Error   string `json:"error"`
+
+	Status  string `json:"status"`
+	Message string `json:"message"`
+}
+
+type authInfo struct {
+	token string
+	id    string
+}
+
+func (s Status) OK() error {
+	if s.Success {
+		return nil
+	}
+
+	if len(s.Error) > 0 {
+		return fmt.Errorf(s.Error)
+	}
+
+	if s.Status == "success" {
+		return nil
+	}
+
+	if len(s.Message) > 0 {
+		return fmt.Errorf("status: %s, message: %s", s.Status, s.Message)
+	}
+	return ResponseErr
+}
+
+// StatusResponse The base for the most of the json responses
+type StatusResponse struct {
+	Status
+	Channel string `json:"channel"`
+}
+
+func NewClient(serverUrl *url.URL, debug bool) *Client {
+	protocol := "http"
+	port := "80"
+
+	if serverUrl.Scheme == "https" {
+		protocol = "https"
+		port = "443"
+	}
+
+	if len(serverUrl.Port()) > 0 {
+		port = serverUrl.Port()
+	}
+
+	return &Client{Host: serverUrl.Hostname(), Path: serverUrl.Path, Port: port, Protocol: protocol, Version: "v1", Debug: debug}
+}
+
+func (c *Client) getUrl() string {
+	if len(c.Version) == 0 {
+		c.Version = "v1"
+	}
+	return fmt.Sprintf("%v://%v:%v%s/api/%s", c.Protocol, c.Host, c.Port, c.Path, c.Version)
+}
+
+// Get call Get
+func (c *Client) Get(api string, params url.Values, response Response) error {
+	return c.doRequest(http.MethodGet, api, params, nil, response)
+}
+
+// Post call as JSON
+func (c *Client) Post(api string, body io.Reader, response Response) error {
+	return c.doRequest(http.MethodPost, api, nil, body, response)
+}
+
+// PostForm call as Form Data
+func (c *Client) PostForm(api string, params url.Values, response Response) error {
+	return c.doRequest(http.MethodPost, api, params, nil, response)
+}
+
+func (c *Client) doRequest(method, api string, params url.Values, body io.Reader, response Response) error {
+	contentType := "application/x-www-form-urlencoded"
+	if method == http.MethodPost {
+		if body != nil {
+			contentType = "application/json"
+		} else if len(params) > 0 {
+			body = bytes.NewBufferString(params.Encode())
+		}
+	}
+
+	request, err := http.NewRequest(method, c.getUrl()+"/"+api, body)
+	if err != nil {
+		return err
+	}
+
+	if method == http.MethodGet {
+		if len(params) > 0 {
+			request.URL.RawQuery = params.Encode()
+		}
+	} else {
+		request.Header.Set("Content-Type", contentType)
+	}
+
+	if c.auth != nil {
+		request.Header.Set("X-Auth-Token", c.auth.token)
+		request.Header.Set("X-User-Id", c.auth.id)
+	}
+
+	if c.Debug {
+		log.Println(request)
+	}
+
+	resp, err := http.DefaultClient.Do(request)
+
+	if err != nil {
+		return err
+	}
+
+	defer resp.Body.Close()
+	bodyBytes, err := ioutil.ReadAll(resp.Body)
+
+	if c.Debug {
+		log.Println(string(bodyBytes))
+	}
+
+	var parse bool
+	if err == nil {
+		if e := json.Unmarshal(bodyBytes, response); e == nil {
+			parse = true
+		}
+	}
+	if resp.StatusCode != http.StatusOK {
+		if parse {
+			return response.OK()
+		}
+		return errors.New("Request error: " + resp.Status)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	return response.OK()
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
new file mode 100644
index 00000000..dd831c85
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
@@ -0,0 +1,98 @@
+package rest
+
+import (
+	"net/url"
+
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type InfoResponse struct {
+	Status
+	Info models.Info `json:"info"`
+}
+
+// GetServerInfo a simple method, requires no authentication,
+// that returns information about the server including version information.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/info
+func (c *Client) GetServerInfo() (*models.Info, error) {
+	response := new(InfoResponse)
+	if err := c.Get("info", nil, response); err != nil {
+		return nil, err
+	}
+
+	return &response.Info, nil
+}
+
+type DirectoryResponse struct {
+	Status
+	models.Directory
+}
+
+// GetDirectory a method, that searches by users or channels on all users and channels available on server.
+// It supports the Offset, Count, and Sort Query Parameters along with Query and Fields Query Parameters.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/directory
+func (c *Client) GetDirectory(params url.Values) (*models.Directory, error) {
+	response := new(DirectoryResponse)
+	if err := c.Get("directory", params, response); err != nil {
+		return nil, err
+	}
+
+	return &response.Directory, nil
+}
+
+type SpotlightResponse struct {
+	Status
+	models.Spotlight
+}
+
+// GetSpotlight searches for users or rooms that are visible to the user.
+// WARNING: It will only return rooms that user didn’t join yet.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/spotlight
+func (c *Client) GetSpotlight(params url.Values) (*models.Spotlight, error) {
+	response := new(SpotlightResponse)
+	if err := c.Get("spotlight", params, response); err != nil {
+		return nil, err
+	}
+
+	return &response.Spotlight, nil
+}
+
+type StatisticsResponse struct {
+	Status
+	models.StatisticsInfo
+}
+
+// GetStatistics
+// Statistics about the Rocket.Chat server.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/statistics
+func (c *Client) GetStatistics() (*models.StatisticsInfo, error) {
+	response := new(StatisticsResponse)
+	if err := c.Get("statistics", nil, response); err != nil {
+		return nil, err
+	}
+
+	return &response.StatisticsInfo, nil
+}
+
+type StatisticsListResponse struct {
+	Status
+	models.StatisticsList
+}
+
+// GetStatisticsList
+// Selectable statistics about the Rocket.Chat server.
+// It supports the Offset, Count and Sort Query Parameters along with just the Fields and Query Parameters.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/statistics.list
+func (c *Client) GetStatisticsList(params url.Values) (*models.StatisticsList, error) {
+	response := new(StatisticsListResponse)
+	if err := c.Get("statistics.list", params, response); err != nil {
+		return nil, err
+	}
+
+	return &response.StatisticsList, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
new file mode 100644
index 00000000..b3ad5846
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
@@ -0,0 +1,67 @@
+package rest
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"html"
+	"net/url"
+	"strconv"
+
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type MessagesResponse struct {
+	Status
+	Messages []models.Message `json:"messages"`
+}
+
+type MessageResponse struct {
+	Status
+	Message models.Message `json:"message"`
+}
+
+// Sends a message to a channel. The name of the channel has to be not nil.
+// The message will be html escaped.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
+func (c *Client) Send(channel *models.Channel, msg string) error {
+	body := fmt.Sprintf(`{ "channel": "%s", "text": "%s"}`, channel.Name, html.EscapeString(msg))
+	return c.Post("chat.postMessage", bytes.NewBufferString(body), new(MessageResponse))
+}
+
+// PostMessage send a message to a channel. The channel or roomId has to be not nil.
+// The message will be json encode.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
+func (c *Client) PostMessage(msg *models.PostMessage) (*MessageResponse, error) {
+	body, err := json.Marshal(msg)
+	if err != nil {
+		return nil, err
+	}
+
+	response := new(MessageResponse)
+	err = c.Post("chat.postMessage", bytes.NewBuffer(body), response)
+	return response, err
+}
+
+// Get messages from a channel. The channel id has to be not nil. Optionally a
+// count can be specified to limit the size of the returned messages.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/history
+func (c *Client) GetMessages(channel *models.Channel, page *models.Pagination) ([]models.Message, error) {
+	params := url.Values{
+		"roomId": []string{channel.ID},
+	}
+
+	if page != nil {
+		params.Add("count", strconv.Itoa(page.Count))
+	}
+
+	response := new(MessagesResponse)
+	if err := c.Get("channels.history", params, response); err != nil {
+		return nil, err
+	}
+
+	return response.Messages, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
new file mode 100644
index 00000000..dcf783a0
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
@@ -0,0 +1,145 @@
+package rest
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/url"
+	"time"
+
+	"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type logoutResponse struct {
+	Status
+	Data struct {
+		Message string `json:"message"`
+	} `json:"data"`
+}
+
+type logonResponse struct {
+	Status
+	Data struct {
+		Token  string `json:"authToken"`
+		UserID string `json:"userID"`
+	} `json:"data"`
+}
+
+type CreateUserResponse struct {
+	Status
+	User struct {
+		ID        string    `json:"_id"`
+		CreatedAt time.Time `json:"createdAt"`
+		Services  struct {
+			Password struct {
+				Bcrypt string `json:"bcrypt"`
+			} `json:"password"`
+		} `json:"services"`
+		Username string `json:"username"`
+		Emails   []struct {
+			Address  string `json:"address"`
+			Verified bool   `json:"verified"`
+		} `json:"emails"`
+		Type         string            `json:"type"`
+		Status       string            `json:"status"`
+		Active       bool              `json:"active"`
+		Roles        []string          `json:"roles"`
+		UpdatedAt    time.Time         `json:"_updatedAt"`
+		Name         string            `json:"name"`
+		CustomFields map[string]string `json:"customFields"`
+	} `json:"user"`
+}
+
+// Login a user. The Email and the Password are mandatory. The auth token of the user is stored in the Client instance.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/authentication/login
+func (c *Client) Login(credentials *models.UserCredentials) error {
+	if c.auth != nil {
+		return nil
+	}
+
+	if credentials.ID != "" && credentials.Token != "" {
+		c.auth = &authInfo{id: credentials.ID, token: credentials.Token}
+		return nil
+	}
+
+	response := new(logonResponse)
+	data := url.Values{"user": {credentials.Email}, "password": {credentials.Password}}
+	if err := c.PostForm("login", data, response); err != nil {
+		return err
+	}
+
+	c.auth = &authInfo{id: response.Data.UserID, token: response.Data.Token}
+	credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
+	return nil
+}
+
+// CreateToken creates an access token for a user
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/createtoken/
+func (c *Client) CreateToken(userID, username string) (*models.UserCredentials, error) {
+	response := new(logonResponse)
+	data := url.Values{"userId": {userID}, "username": {username}}
+	if err := c.PostForm("users.createToken", data, response); err != nil {
+		return nil, err
+	}
+	credentials := &models.UserCredentials{}
+	credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
+	return credentials, nil
+}
+
+// Logout a user. The function returns the response message of the server.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/authentication/logout
+func (c *Client) Logout() (string, error) {
+
+	if c.auth == nil {
+		return "Was not logged in", nil
+	}
+
+	response := new(logoutResponse)
+	if err := c.Get("logout", nil, response); err != nil {
+		return "", err
+	}
+
+	return response.Data.Message, nil
+}
+
+// CreateUser being logged in with a user that has permission to do so.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/create
+func (c *Client) CreateUser(req *models.CreateUserRequest) (*CreateUserResponse, error) {
+	body, err := json.Marshal(req)
+	if err != nil {
+		return nil, err
+	}
+
+	response := new(CreateUserResponse)
+	err = c.Post("users.create", bytes.NewBuffer(body), response)
+	return response, err
+}
+
+// UpdateUser updates a user's data being logged in with a user that has permission to do so.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/update/
+func (c *Client) UpdateUser(req *models.UpdateUserRequest) (*CreateUserResponse, error) {
+	body, err := json.Marshal(req)
+	if err != nil {
+		return nil, err
+	}
+
+	response := new(CreateUserResponse)
+	err = c.Post("users.update", bytes.NewBuffer(body), response)
+	return response, err
+}
+
+// SetUserAvatar updates a user's avatar being logged in with a user that has permission to do so.
+// Currently only passing an URL is possible.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/setavatar/
+func (c *Client) SetUserAvatar(userID, username, avatarURL string) (*Status, error) {
+	body := fmt.Sprintf(`{ "userId": "%s","username": "%s","avatarUrl":"%s"}`, userID, username, avatarURL)
+	response := new(Status)
+	err := c.Post("users.setAvatar", bytes.NewBufferString(body), response)
+	return response, err
+}
diff --git a/vendor/github.com/nelsonken/gomf/README.md b/vendor/github.com/nelsonken/gomf/README.md
new file mode 100644
index 00000000..237e9370
--- /dev/null
+++ b/vendor/github.com/nelsonken/gomf/README.md
@@ -0,0 +1,37 @@
+# golang 可多文件上传的request builder 库
+
+## 测试方法
+
+1. start php upload server: php -S 127.0.0.1:8080 ./
+2. run go test -v 
+
+## 使用方法
+
+```go
+	fb := gomf.New()
+	fb.WriteField("name", "accountName")
+	fb.WriteField("password", "pwd")
+	fb.WriteFile("picture", "icon.png", "image/jpeg", []byte(strings.Repeat("0", 100)))
+
+	log.Println(fb.GetBuffer().String())
+
+	req, err := fb.GetHTTPRequest(context.Background(), "http://127.0.0.1:8080/up.php")
+	if err != nil {
+		log.Fatal(err)
+	}
+	res, err := http.DefaultClient.Do(req)
+
+	log.Println(res.StatusCode)
+	log.Println(res.Status)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	b, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	log.Println(string(b))
+```
diff --git a/vendor/github.com/nelsonken/gomf/form_builder.go b/vendor/github.com/nelsonken/gomf/form_builder.go
new file mode 100644
index 00000000..9b0d2294
--- /dev/null
+++ b/vendor/github.com/nelsonken/gomf/form_builder.go
@@ -0,0 +1,89 @@
+package gomf
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"mime/multipart"
+	"net/http"
+	"net/textproto"
+)
+
+type FormBuilder struct {
+	w *multipart.Writer
+	b *bytes.Buffer
+}
+
+func New() *FormBuilder {
+	buf := new(bytes.Buffer)
+	writer := multipart.NewWriter(buf)
+	return &FormBuilder{
+		w: writer,
+		b: buf,
+	}
+}
+
+func (ufw *FormBuilder) WriteField(name, value string) error {
+	w, err := ufw.w.CreateFormField(name)
+	if err != nil {
+		return err
+	}
+
+	_, err = w.Write([]byte(value))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// WriteFile if contentType is empty-string, will auto convert to application/octet-stream
+func (ufw *FormBuilder) WriteFile(fieldName, fileName, contentType string, content []byte) error {
+	if contentType == "" {
+		contentType = "application/octet-stream"
+	}
+
+	wx, err := ufw.w.CreatePart(textproto.MIMEHeader{
+		"Content-Type": []string{
+			contentType,
+		},
+		"Content-Disposition": []string{
+			fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fileName),
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	_, err = wx.Write(content)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (fb *FormBuilder) Close() error {
+	return fb.w.Close()
+}
+
+func (fb *FormBuilder) GetBuffer() *bytes.Buffer {
+	return fb.b
+}
+
+func (fb *FormBuilder) GetHTTPRequest(ctx context.Context, reqURL string) (*http.Request, error) {
+	err := fb.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("POST", reqURL, fb.b)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("Content-Type", fb.w.FormDataContentType())
+	req = req.WithContext(ctx)
+
+	return req, nil
+}
diff --git a/vendor/github.com/nelsonken/gomf/up.php b/vendor/github.com/nelsonken/gomf/up.php
new file mode 100644
index 00000000..e56e5bbb
--- /dev/null
+++ b/vendor/github.com/nelsonken/gomf/up.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ *
+ * PHP UPLOAD DEMO for gomf test
+ * USAGE: 
+ *      php -S 127.0.0.1:8080 -t ./
+ *
+ */
+
+print_r($_FILES);
+  
+if ($_FILES["picture"]["error"] > 0) {
+    echo "Return Code: " . $_FILES["picture"]["error"] . "\n";
+} else {
+    echo "Upload: " . $_FILES["picture"]["name"] . "\n";
+    echo "Type: " . $_FILES["picture"]["type"] . "\n";
+    echo "Size: " . ($_FILES["picture"]["size"] / 1024) . " Kb\n";
+    echo "Temp file: " . $_FILES["picture"]["tmp_name"] . "\n>";
+
+    if (file_exists($_FILES["picture"]["name"]))
+      {
+      echo $_FILES["picture"]["name"] . " already exists. \n";
+      }
+    else
+      {
+      move_uploaded_file($_FILES["picture"]["tmp_name"], $_FILES["picture"]["name"]);
+      echo "Stored in: " . $_FILES["picture"]["name"] . "\n";
+      }
+}
+
+
+  
+
diff --git a/vendor/golang.org/x/net/AUTHORS b/vendor/golang.org/x/net/AUTHORS
new file mode 100644
index 00000000..15167cd7
--- /dev/null
+++ b/vendor/golang.org/x/net/AUTHORS
@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.
diff --git a/vendor/golang.org/x/net/CONTRIBUTORS b/vendor/golang.org/x/net/CONTRIBUTORS
new file mode 100644
index 00000000..1c4577e9
--- /dev/null
+++ b/vendor/golang.org/x/net/CONTRIBUTORS
@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.
diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE
new file mode 100644
index 00000000..6a66aea5
--- /dev/null
+++ b/vendor/golang.org/x/net/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS
new file mode 100644
index 00000000..73309904
--- /dev/null
+++ b/vendor/golang.org/x/net/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go.  This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation.  If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/net/websocket/client.go b/vendor/golang.org/x/net/websocket/client.go
new file mode 100644
index 00000000..69a4ac7e
--- /dev/null
+++ b/vendor/golang.org/x/net/websocket/client.go
@@ -0,0 +1,106 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"bufio"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+)
+
+// DialError is an error that occurs while dialling a websocket server.
+type DialError struct {
+	*Config
+	Err error
+}
+
+func (e *DialError) Error() string {
+	return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
+}
+
+// NewConfig creates a new WebSocket config for client connection.
+func NewConfig(server, origin string) (config *Config, err error) {
+	config = new(Config)
+	config.Version = ProtocolVersionHybi13
+	config.Location, err = url.ParseRequestURI(server)
+	if err != nil {
+		return
+	}
+	config.Origin, err = url.ParseRequestURI(origin)
+	if err != nil {
+		return
+	}
+	config.Header = http.Header(make(map[string][]string))
+	return
+}
+
+// NewClient creates a new WebSocket client connection over rwc.
+func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
+	br := bufio.NewReader(rwc)
+	bw := bufio.NewWriter(rwc)
+	err = hybiClientHandshake(config, br, bw)
+	if err != nil {
+		return
+	}
+	buf := bufio.NewReadWriter(br, bw)
+	ws = newHybiClientConn(config, buf, rwc)
+	return
+}
+
+// Dial opens a new client connection to a WebSocket.
+func Dial(url_, protocol, origin string) (ws *Conn, err error) {
+	config, err := NewConfig(url_, origin)
+	if err != nil {
+		return nil, err
+	}
+	if protocol != "" {
+		config.Protocol = []string{protocol}
+	}
+	return DialConfig(config)
+}
+
+var portMap = map[string]string{
+	"ws":  "80",
+	"wss": "443",
+}
+
+func parseAuthority(location *url.URL) string {
+	if _, ok := portMap[location.Scheme]; ok {
+		if _, _, err := net.SplitHostPort(location.Host); err != nil {
+			return net.JoinHostPort(location.Host, portMap[location.Scheme])
+		}
+	}
+	return location.Host
+}
+
+// DialConfig opens a new client connection to a WebSocket with a config.
+func DialConfig(config *Config) (ws *Conn, err error) {
+	var client net.Conn
+	if config.Location == nil {
+		return nil, &DialError{config, ErrBadWebSocketLocation}
+	}
+	if config.Origin == nil {
+		return nil, &DialError{config, ErrBadWebSocketOrigin}
+	}
+	dialer := config.Dialer
+	if dialer == nil {
+		dialer = &net.Dialer{}
+	}
+	client, err = dialWithDialer(dialer, config)
+	if err != nil {
+		goto Error
+	}
+	ws, err = NewClient(config, client)
+	if err != nil {
+		client.Close()
+		goto Error
+	}
+	return
+
+Error:
+	return nil, &DialError{config, err}
+}
diff --git a/vendor/golang.org/x/net/websocket/dial.go b/vendor/golang.org/x/net/websocket/dial.go
new file mode 100644
index 00000000..2dab943a
--- /dev/null
+++ b/vendor/golang.org/x/net/websocket/dial.go
@@ -0,0 +1,24 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"crypto/tls"
+	"net"
+)
+
+func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
+	switch config.Location.Scheme {
+	case "ws":
+		conn, err = dialer.Dial("tcp", parseAuthority(config.Location))
+
+	case "wss":
+		conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)
+
+	default:
+		err = ErrBadScheme
+	}
+	return
+}
diff --git a/vendor/golang.org/x/net/websocket/hybi.go b/vendor/golang.org/x/net/websocket/hybi.go
new file mode 100644
index 00000000..8cffdd16
--- /dev/null
+++ b/vendor/golang.org/x/net/websocket/hybi.go
@@ -0,0 +1,583 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+// This file implements a protocol of hybi draft.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
+
+import (
+	"bufio"
+	"bytes"
+	"crypto/rand"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+const (
+	websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+	closeStatusNormal            = 1000
+	closeStatusGoingAway         = 1001
+	closeStatusProtocolError     = 1002
+	closeStatusUnsupportedData   = 1003
+	closeStatusFrameTooLarge     = 1004
+	closeStatusNoStatusRcvd      = 1005
+	closeStatusAbnormalClosure   = 1006
+	closeStatusBadMessageData    = 1007
+	closeStatusPolicyViolation   = 1008
+	closeStatusTooBigData        = 1009
+	closeStatusExtensionMismatch = 1010
+
+	maxControlFramePayloadLength = 125
+)
+
+var (
+	ErrBadMaskingKey         = &ProtocolError{"bad masking key"}
+	ErrBadPongMessage        = &ProtocolError{"bad pong message"}
+	ErrBadClosingStatus      = &ProtocolError{"bad closing status"}
+	ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
+	ErrNotImplemented        = &ProtocolError{"not implemented"}
+
+	handshakeHeader = map[string]bool{
+		"Host":                   true,
+		"Upgrade":                true,
+		"Connection":             true,
+		"Sec-Websocket-Key":      true,
+		"Sec-Websocket-Origin":   true,
+		"Sec-Websocket-Version":  true,
+		"Sec-Websocket-Protocol": true,
+		"Sec-Websocket-Accept":   true,
+	}
+)
+
+// A hybiFrameHeader is a frame header as defined in hybi draft.
+type hybiFrameHeader struct {
+	Fin        bool
+	Rsv        [3]bool
+	OpCode     byte
+	Length     int64
+	MaskingKey []byte
+
+	data *bytes.Buffer
+}
+
+// A hybiFrameReader is a reader for hybi frame.
+type hybiFrameReader struct {
+	reader io.Reader
+
+	header hybiFrameHeader
+	pos    int64
+	length int
+}
+
+func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
+	n, err = frame.reader.Read(msg)
+	if frame.header.MaskingKey != nil {
+		for i := 0; i < n; i++ {
+			msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
+			frame.pos++
+		}
+	}
+	return n, err
+}
+
+func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
+
+func (frame *hybiFrameReader) HeaderReader() io.Reader {
+	if frame.header.data == nil {
+		return nil
+	}
+	if frame.header.data.Len() == 0 {
+		return nil
+	}
+	return frame.header.data
+}
+
+func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
+
+func (frame *hybiFrameReader) Len() (n int) { return frame.length }
+
+// A hybiFrameReaderFactory creates new frame reader based on its frame type.
+type hybiFrameReaderFactory struct {
+	*bufio.Reader
+}
+
+// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
+// See Section 5.2 Base Framing protocol for detail.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
+func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
+	hybiFrame := new(hybiFrameReader)
+	frame = hybiFrame
+	var header []byte
+	var b byte
+	// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
+	for i := 0; i < 3; i++ {
+		j := uint(6 - i)
+		hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
+	}
+	hybiFrame.header.OpCode = header[0] & 0x0f
+
+	// Second byte. Mask/Payload len(7bits)
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	mask := (b & 0x80) != 0
+	b &= 0x7f
+	lengthFields := 0
+	switch {
+	case b <= 125: // Payload length 7bits.
+		hybiFrame.header.Length = int64(b)
+	case b == 126: // Payload length 7+16bits
+		lengthFields = 2
+	case b == 127: // Payload length 7+64bits
+		lengthFields = 8
+	}
+	for i := 0; i < lengthFields; i++ {
+		b, err = buf.ReadByte()
+		if err != nil {
+			return
+		}
+		if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
+			b &= 0x7f
+		}
+		header = append(header, b)
+		hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
+	}
+	if mask {
+		// Masking key. 4 bytes.
+		for i := 0; i < 4; i++ {
+			b, err = buf.ReadByte()
+			if err != nil {
+				return
+			}
+			header = append(header, b)
+			hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
+		}
+	}
+	hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
+	hybiFrame.header.data = bytes.NewBuffer(header)
+	hybiFrame.length = len(header) + int(hybiFrame.header.Length)
+	return
+}
+
+// A HybiFrameWriter is a writer for hybi frame.
+type hybiFrameWriter struct {
+	writer *bufio.Writer
+
+	header *hybiFrameHeader
+}
+
+func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
+	var header []byte
+	var b byte
+	if frame.header.Fin {
+		b |= 0x80
+	}
+	for i := 0; i < 3; i++ {
+		if frame.header.Rsv[i] {
+			j := uint(6 - i)
+			b |= 1 << j
+		}
+	}
+	b |= frame.header.OpCode
+	header = append(header, b)
+	if frame.header.MaskingKey != nil {
+		b = 0x80
+	} else {
+		b = 0
+	}
+	lengthFields := 0
+	length := len(msg)
+	switch {
+	case length <= 125:
+		b |= byte(length)
+	case length < 65536:
+		b |= 126
+		lengthFields = 2
+	default:
+		b |= 127
+		lengthFields = 8
+	}
+	header = append(header, b)
+	for i := 0; i < lengthFields; i++ {
+		j := uint((lengthFields - i - 1) * 8)
+		b = byte((length >> j) & 0xff)
+		header = append(header, b)
+	}
+	if frame.header.MaskingKey != nil {
+		if len(frame.header.MaskingKey) != 4 {
+			return 0, ErrBadMaskingKey
+		}
+		header = append(header, frame.header.MaskingKey...)
+		frame.writer.Write(header)
+		data := make([]byte, length)
+		for i := range data {
+			data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
+		}
+		frame.writer.Write(data)
+		err = frame.writer.Flush()
+		return length, err
+	}
+	frame.writer.Write(header)
+	frame.writer.Write(msg)
+	err = frame.writer.Flush()
+	return length, err
+}
+
+func (frame *hybiFrameWriter) Close() error { return nil }
+
+type hybiFrameWriterFactory struct {
+	*bufio.Writer
+	needMaskingKey bool
+}
+
+func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
+	if buf.needMaskingKey {
+		frameHeader.MaskingKey, err = generateMaskingKey()
+		if err != nil {
+			return nil, err
+		}
+	}
+	return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
+}
+
+type hybiFrameHandler struct {
+	conn        *Conn
+	payloadType byte
+}
+
+func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
+	if handler.conn.IsServerConn() {
+		// The client MUST mask all frames sent to the server.
+		if frame.(*hybiFrameReader).header.MaskingKey == nil {
+			handler.WriteClose(closeStatusProtocolError)
+			return nil, io.EOF
+		}
+	} else {
+		// The server MUST NOT mask all frames.
+		if frame.(*hybiFrameReader).header.MaskingKey != nil {
+			handler.WriteClose(closeStatusProtocolError)
+			return nil, io.EOF
+		}
+	}
+	if header := frame.HeaderReader(); header != nil {
+		io.Copy(ioutil.Discard, header)
+	}
+	switch frame.PayloadType() {
+	case ContinuationFrame:
+		frame.(*hybiFrameReader).header.OpCode = handler.payloadType
+	case TextFrame, BinaryFrame:
+		handler.payloadType = frame.PayloadType()
+	case CloseFrame:
+		return nil, io.EOF
+	case PingFrame, PongFrame:
+		b := make([]byte, maxControlFramePayloadLength)
+		n, err := io.ReadFull(frame, b)
+		if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
+			return nil, err
+		}
+		io.Copy(ioutil.Discard, frame)
+		if frame.PayloadType() == PingFrame {
+			if _, err := handler.WritePong(b[:n]); err != nil {
+				return nil, err
+			}
+		}
+		return nil, nil
+	}
+	return frame, nil
+}
+
+func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
+	if err != nil {
+		return err
+	}
+	msg := make([]byte, 2)
+	binary.BigEndian.PutUint16(msg, uint16(status))
+	_, err = w.Write(msg)
+	w.Close()
+	return err
+}
+
+func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
+	if err != nil {
+		return 0, err
+	}
+	n, err = w.Write(msg)
+	w.Close()
+	return n, err
+}
+
+// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
+func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	if buf == nil {
+		br := bufio.NewReader(rwc)
+		bw := bufio.NewWriter(rwc)
+		buf = bufio.NewReadWriter(br, bw)
+	}
+	ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
+		frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
+		frameWriterFactory: hybiFrameWriterFactory{
+			buf.Writer, request == nil},
+		PayloadType:        TextFrame,
+		defaultCloseStatus: closeStatusNormal}
+	ws.frameHandler = &hybiFrameHandler{conn: ws}
+	return ws
+}
+
+// generateMaskingKey generates a masking key for a frame.
+func generateMaskingKey() (maskingKey []byte, err error) {
+	maskingKey = make([]byte, 4)
+	if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
+		return
+	}
+	return
+}
+
+// generateNonce generates a nonce consisting of a randomly selected 16-byte
+// value that has been base64-encoded.
+func generateNonce() (nonce []byte) {
+	key := make([]byte, 16)
+	if _, err := io.ReadFull(rand.Reader, key); err != nil {
+		panic(err)
+	}
+	nonce = make([]byte, 24)
+	base64.StdEncoding.Encode(nonce, key)
+	return
+}
+
+// removeZone removes IPv6 zone identifer from host.
+// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
+func removeZone(host string) string {
+	if !strings.HasPrefix(host, "[") {
+		return host
+	}
+	i := strings.LastIndex(host, "]")
+	if i < 0 {
+		return host
+	}
+	j := strings.LastIndex(host[:i], "%")
+	if j < 0 {
+		return host
+	}
+	return host[:j] + host[i:]
+}
+
+// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
+// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
+func getNonceAccept(nonce []byte) (expected []byte, err error) {
+	h := sha1.New()
+	if _, err = h.Write(nonce); err != nil {
+		return
+	}
+	if _, err = h.Write([]byte(websocketGUID)); err != nil {
+		return
+	}
+	expected = make([]byte, 28)
+	base64.StdEncoding.Encode(expected, h.Sum(nil))
+	return
+}
+
+// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
+func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
+	bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
+
+	// According to RFC 6874, an HTTP client, proxy, or other
+	// intermediary must remove any IPv6 zone identifier attached
+	// to an outgoing URI.
+	bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
+	bw.WriteString("Upgrade: websocket\r\n")
+	bw.WriteString("Connection: Upgrade\r\n")
+	nonce := generateNonce()
+	if config.handshakeData != nil {
+		nonce = []byte(config.handshakeData["key"])
+	}
+	bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
+	bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
+
+	if config.Version != ProtocolVersionHybi13 {
+		return ErrBadProtocolVersion
+	}
+
+	bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
+	if len(config.Protocol) > 0 {
+		bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
+	}
+	// TODO(ukai): send Sec-WebSocket-Extensions.
+	err = config.Header.WriteSubset(bw, handshakeHeader)
+	if err != nil {
+		return err
+	}
+
+	bw.WriteString("\r\n")
+	if err = bw.Flush(); err != nil {
+		return err
+	}
+
+	resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 101 {
+		return ErrBadStatus
+	}
+	if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
+		strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
+		return ErrBadUpgrade
+	}
+	expectedAccept, err := getNonceAccept(nonce)
+	if err != nil {
+		return err
+	}
+	if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
+		return ErrChallengeResponse
+	}
+	if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
+		return ErrUnsupportedExtensions
+	}
+	offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
+	if offeredProtocol != "" {
+		protocolMatched := false
+		for i := 0; i < len(config.Protocol); i++ {
+			if config.Protocol[i] == offeredProtocol {
+				protocolMatched = true
+				break
+			}
+		}
+		if !protocolMatched {
+			return ErrBadWebSocketProtocol
+		}
+		config.Protocol = []string{offeredProtocol}
+	}
+
+	return nil
+}
+
+// newHybiClientConn creates a client WebSocket connection after handshake.
+func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
+	return newHybiConn(config, buf, rwc, nil)
+}
+
+// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
+type hybiServerHandshaker struct {
+	*Config
+	accept []byte
+}
+
+func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
+	c.Version = ProtocolVersionHybi13
+	if req.Method != "GET" {
+		return http.StatusMethodNotAllowed, ErrBadRequestMethod
+	}
+	// HTTP version can be safely ignored.
+
+	if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
+		!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+
+	key := req.Header.Get("Sec-Websocket-Key")
+	if key == "" {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+	version := req.Header.Get("Sec-Websocket-Version")
+	switch version {
+	case "13":
+		c.Version = ProtocolVersionHybi13
+	default:
+		return http.StatusBadRequest, ErrBadWebSocketVersion
+	}
+	var scheme string
+	if req.TLS != nil {
+		scheme = "wss"
+	} else {
+		scheme = "ws"
+	}
+	c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+	protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
+	if protocol != "" {
+		protocols := strings.Split(protocol, ",")
+		for i := 0; i < len(protocols); i++ {
+			c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
+		}
+	}
+	c.accept, err = getNonceAccept([]byte(key))
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	return http.StatusSwitchingProtocols, nil
+}
+
+// Origin parses the Origin header in req.
+// If the Origin header is not set, it returns nil and nil.
+func Origin(config *Config, req *http.Request) (*url.URL, error) {
+	var origin string
+	switch config.Version {
+	case ProtocolVersionHybi13:
+		origin = req.Header.Get("Origin")
+	}
+	if origin == "" {
+		return nil, nil
+	}
+	return url.ParseRequestURI(origin)
+}
+
+func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
+	if len(c.Protocol) > 0 {
+		if len(c.Protocol) != 1 {
+			// You need choose a Protocol in Handshake func in Server.
+			return ErrBadWebSocketProtocol
+		}
+	}
+	buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
+	buf.WriteString("Upgrade: websocket\r\n")
+	buf.WriteString("Connection: Upgrade\r\n")
+	buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
+	if len(c.Protocol) > 0 {
+		buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
+	}
+	// TODO(ukai): send Sec-WebSocket-Extensions.
+	if c.Header != nil {
+		err := c.Header.WriteSubset(buf, handshakeHeader)
+		if err != nil {
+			return err
+		}
+	}
+	buf.WriteString("\r\n")
+	return buf.Flush()
+}
+
+func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHybiServerConn(c.Config, buf, rwc, request)
+}
+
+// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
+func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHybiConn(config, buf, rwc, request)
+}
diff --git a/vendor/golang.org/x/net/websocket/server.go b/vendor/golang.org/x/net/websocket/server.go
new file mode 100644
index 00000000..0895dea1
--- /dev/null
+++ b/vendor/golang.org/x/net/websocket/server.go
@@ -0,0 +1,113 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"net/http"
+)
+
+func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) {
+	var hs serverHandshaker = &hybiServerHandshaker{Config: config}
+	code, err := hs.ReadHandshake(buf.Reader, req)
+	if err == ErrBadWebSocketVersion {
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
+		buf.WriteString("\r\n")
+		buf.WriteString(err.Error())
+		buf.Flush()
+		return
+	}
+	if err != nil {
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		buf.WriteString("\r\n")
+		buf.WriteString(err.Error())
+		buf.Flush()
+		return
+	}
+	if handshake != nil {
+		err = handshake(config, req)
+		if err != nil {
+			code = http.StatusForbidden
+			fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+			buf.WriteString("\r\n")
+			buf.Flush()
+			return
+		}
+	}
+	err = hs.AcceptHandshake(buf.Writer)
+	if err != nil {
+		code = http.StatusBadRequest
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		buf.WriteString("\r\n")
+		buf.Flush()
+		return
+	}
+	conn = hs.NewServerConn(buf, rwc, req)
+	return
+}
+
+// Server represents a server of a WebSocket.
+type Server struct {
+	// Config is a WebSocket configuration for new WebSocket connection.
+	Config
+
+	// Handshake is an optional function in WebSocket handshake.
+	// For example, you can check, or don't check Origin header.
+	// Another example, you can select config.Protocol.
+	Handshake func(*Config, *http.Request) error
+
+	// Handler handles a WebSocket connection.
+	Handler
+}
+
+// ServeHTTP implements the http.Handler interface for a WebSocket
+func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	s.serveWebSocket(w, req)
+}
+
+func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
+	rwc, buf, err := w.(http.Hijacker).Hijack()
+	if err != nil {
+		panic("Hijack failed: " + err.Error())
+	}
+	// The server should abort the WebSocket connection if it finds
+	// the client did not send a handshake that matches with protocol
+	// specification.
+	defer rwc.Close()
+	conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
+	if err != nil {
+		return
+	}
+	if conn == nil {
+		panic("unexpected nil conn")
+	}
+	s.Handler(conn)
+}
+
+// Handler is a simple interface to a WebSocket browser client.
+// It checks if Origin header is valid URL by default.
+// You might want to verify websocket.Conn.Config().Origin in the func.
+// If you use Server instead of Handler, you could call websocket.Origin and
+// check the origin in your Handshake func. So, if you want to accept
+// non-browser clients, which do not send an Origin header, set a
+// Server.Handshake that does not check the origin.
+type Handler func(*Conn)
+
+func checkOrigin(config *Config, req *http.Request) (err error) {
+	config.Origin, err = Origin(config, req)
+	if err == nil && config.Origin == nil {
+		return fmt.Errorf("null origin")
+	}
+	return err
+}
+
+// ServeHTTP implements the http.Handler interface for a WebSocket
+func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	s := Server{Handler: h, Handshake: checkOrigin}
+	s.serveWebSocket(w, req)
+}
diff --git a/vendor/golang.org/x/net/websocket/websocket.go b/vendor/golang.org/x/net/websocket/websocket.go
new file mode 100644
index 00000000..e242c89a
--- /dev/null
+++ b/vendor/golang.org/x/net/websocket/websocket.go
@@ -0,0 +1,448 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package websocket implements a client and server for the WebSocket protocol
+// as specified in RFC 6455.
+//
+// This package currently lacks some features found in an alternative
+// and more actively maintained WebSocket package:
+//
+//     https://godoc.org/github.com/gorilla/websocket
+//
+package websocket // import "golang.org/x/net/websocket"
+
+import (
+	"bufio"
+	"crypto/tls"
+	"encoding/json"
+	"errors"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"sync"
+	"time"
+)
+
+const (
+	ProtocolVersionHybi13    = 13
+	ProtocolVersionHybi      = ProtocolVersionHybi13
+	SupportedProtocolVersion = "13"
+
+	ContinuationFrame = 0
+	TextFrame         = 1
+	BinaryFrame       = 2
+	CloseFrame        = 8
+	PingFrame         = 9
+	PongFrame         = 10
+	UnknownFrame      = 255
+
+	DefaultMaxPayloadBytes = 32 << 20 // 32MB
+)
+
+// ProtocolError represents WebSocket protocol errors.
+type ProtocolError struct {
+	ErrorString string
+}
+
+func (err *ProtocolError) Error() string { return err.ErrorString }
+
+var (
+	ErrBadProtocolVersion   = &ProtocolError{"bad protocol version"}
+	ErrBadScheme            = &ProtocolError{"bad scheme"}
+	ErrBadStatus            = &ProtocolError{"bad status"}
+	ErrBadUpgrade           = &ProtocolError{"missing or bad upgrade"}
+	ErrBadWebSocketOrigin   = &ProtocolError{"missing or bad WebSocket-Origin"}
+	ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
+	ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
+	ErrBadWebSocketVersion  = &ProtocolError{"missing or bad WebSocket Version"}
+	ErrChallengeResponse    = &ProtocolError{"mismatch challenge/response"}
+	ErrBadFrame             = &ProtocolError{"bad frame"}
+	ErrBadFrameBoundary     = &ProtocolError{"not on frame boundary"}
+	ErrNotWebSocket         = &ProtocolError{"not websocket protocol"}
+	ErrBadRequestMethod     = &ProtocolError{"bad method"}
+	ErrNotSupported         = &ProtocolError{"not supported"}
+)
+
+// ErrFrameTooLarge is returned by Codec's Receive method if payload size
+// exceeds limit set by Conn.MaxPayloadBytes
+var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit")
+
+// Addr is an implementation of net.Addr for WebSocket.
+type Addr struct {
+	*url.URL
+}
+
+// Network returns the network type for a WebSocket, "websocket".
+func (addr *Addr) Network() string { return "websocket" }
+
+// Config is a WebSocket configuration
+type Config struct {
+	// A WebSocket server address.
+	Location *url.URL
+
+	// A Websocket client origin.
+	Origin *url.URL
+
+	// WebSocket subprotocols.
+	Protocol []string
+
+	// WebSocket protocol version.
+	Version int
+
+	// TLS config for secure WebSocket (wss).
+	TlsConfig *tls.Config
+
+	// Additional header fields to be sent in WebSocket opening handshake.
+	Header http.Header
+
+	// Dialer used when opening websocket connections.
+	Dialer *net.Dialer
+
+	handshakeData map[string]string
+}
+
+// serverHandshaker is an interface to handle WebSocket server side handshake.
+type serverHandshaker interface {
+	// ReadHandshake reads handshake request message from client.
+	// Returns http response code and error if any.
+	ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
+
+	// AcceptHandshake accepts the client handshake request and sends
+	// handshake response back to client.
+	AcceptHandshake(buf *bufio.Writer) (err error)
+
+	// NewServerConn creates a new WebSocket connection.
+	NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
+}
+
+// frameReader is an interface to read a WebSocket frame.
+type frameReader interface {
+	// Reader is to read payload of the frame.
+	io.Reader
+
+	// PayloadType returns payload type.
+	PayloadType() byte
+
+	// HeaderReader returns a reader to read header of the frame.
+	HeaderReader() io.Reader
+
+	// TrailerReader returns a reader to read trailer of the frame.
+	// If it returns nil, there is no trailer in the frame.
+	TrailerReader() io.Reader
+
+	// Len returns total length of the frame, including header and trailer.
+	Len() int
+}
+
+// frameReaderFactory is an interface to creates new frame reader.
+type frameReaderFactory interface {
+	NewFrameReader() (r frameReader, err error)
+}
+
+// frameWriter is an interface to write a WebSocket frame.
+type frameWriter interface {
+	// Writer is to write payload of the frame.
+	io.WriteCloser
+}
+
+// frameWriterFactory is an interface to create new frame writer.
+type frameWriterFactory interface {
+	NewFrameWriter(payloadType byte) (w frameWriter, err error)
+}
+
+type frameHandler interface {
+	HandleFrame(frame frameReader) (r frameReader, err error)
+	WriteClose(status int) (err error)
+}
+
+// Conn represents a WebSocket connection.
+//
+// Multiple goroutines may invoke methods on a Conn simultaneously.
+type Conn struct {
+	config  *Config
+	request *http.Request
+
+	buf *bufio.ReadWriter
+	rwc io.ReadWriteCloser
+
+	rio sync.Mutex
+	frameReaderFactory
+	frameReader
+
+	wio sync.Mutex
+	frameWriterFactory
+
+	frameHandler
+	PayloadType        byte
+	defaultCloseStatus int
+
+	// MaxPayloadBytes limits the size of frame payload received over Conn
+	// by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used.
+	MaxPayloadBytes int
+}
+
+// Read implements the io.Reader interface:
+// it reads data of a frame from the WebSocket connection.
+// if msg is not large enough for the frame data, it fills the msg and next Read
+// will read the rest of the frame data.
+// it reads Text frame or Binary frame.
+func (ws *Conn) Read(msg []byte) (n int, err error) {
+	ws.rio.Lock()
+	defer ws.rio.Unlock()
+again:
+	if ws.frameReader == nil {
+		frame, err := ws.frameReaderFactory.NewFrameReader()
+		if err != nil {
+			return 0, err
+		}
+		ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
+		if err != nil {
+			return 0, err
+		}
+		if ws.frameReader == nil {
+			goto again
+		}
+	}
+	n, err = ws.frameReader.Read(msg)
+	if err == io.EOF {
+		if trailer := ws.frameReader.TrailerReader(); trailer != nil {
+			io.Copy(ioutil.Discard, trailer)
+		}
+		ws.frameReader = nil
+		goto again
+	}
+	return n, err
+}
+
+// Write implements the io.Writer interface:
+// it writes data as a frame to the WebSocket connection.
+func (ws *Conn) Write(msg []byte) (n int, err error) {
+	ws.wio.Lock()
+	defer ws.wio.Unlock()
+	w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
+	if err != nil {
+		return 0, err
+	}
+	n, err = w.Write(msg)
+	w.Close()
+	return n, err
+}
+
+// Close implements the io.Closer interface.
+func (ws *Conn) Close() error {
+	err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
+	err1 := ws.rwc.Close()
+	if err != nil {
+		return err
+	}
+	return err1
+}
+
+func (ws *Conn) IsClientConn() bool { return ws.request == nil }
+func (ws *Conn) IsServerConn() bool { return ws.request != nil }
+
+// LocalAddr returns the WebSocket Origin for the connection for client, or
+// the WebSocket location for server.
+func (ws *Conn) LocalAddr() net.Addr {
+	if ws.IsClientConn() {
+		return &Addr{ws.config.Origin}
+	}
+	return &Addr{ws.config.Location}
+}
+
+// RemoteAddr returns the WebSocket location for the connection for client, or
+// the Websocket Origin for server.
+func (ws *Conn) RemoteAddr() net.Addr {
+	if ws.IsClientConn() {
+		return &Addr{ws.config.Location}
+	}
+	return &Addr{ws.config.Origin}
+}
+
+var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
+
+// SetDeadline sets the connection's network read & write deadlines.
+func (ws *Conn) SetDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// SetReadDeadline sets the connection's network read deadline.
+func (ws *Conn) SetReadDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetReadDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// SetWriteDeadline sets the connection's network write deadline.
+func (ws *Conn) SetWriteDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetWriteDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// Config returns the WebSocket config.
+func (ws *Conn) Config() *Config { return ws.config }
+
+// Request returns the http request upgraded to the WebSocket.
+// It is nil for client side.
+func (ws *Conn) Request() *http.Request { return ws.request }
+
+// Codec represents a symmetric pair of functions that implement a codec.
+type Codec struct {
+	Marshal   func(v interface{}) (data []byte, payloadType byte, err error)
+	Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
+}
+
+// Send sends v marshaled by cd.Marshal as single frame to ws.
+func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
+	data, payloadType, err := cd.Marshal(v)
+	if err != nil {
+		return err
+	}
+	ws.wio.Lock()
+	defer ws.wio.Unlock()
+	w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
+	if err != nil {
+		return err
+	}
+	_, err = w.Write(data)
+	w.Close()
+	return err
+}
+
+// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores
+// in v. The whole frame payload is read to an in-memory buffer; max size of
+// payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds
+// limit, ErrFrameTooLarge is returned; in this case frame is not read off wire
+// completely. The next call to Receive would read and discard leftover data of
+// previous oversized frame before processing next frame.
+func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
+	ws.rio.Lock()
+	defer ws.rio.Unlock()
+	if ws.frameReader != nil {
+		_, err = io.Copy(ioutil.Discard, ws.frameReader)
+		if err != nil {
+			return err
+		}
+		ws.frameReader = nil
+	}
+again:
+	frame, err := ws.frameReaderFactory.NewFrameReader()
+	if err != nil {
+		return err
+	}
+	frame, err = ws.frameHandler.HandleFrame(frame)
+	if err != nil {
+		return err
+	}
+	if frame == nil {
+		goto again
+	}
+	maxPayloadBytes := ws.MaxPayloadBytes
+	if maxPayloadBytes == 0 {
+		maxPayloadBytes = DefaultMaxPayloadBytes
+	}
+	if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) {
+		// payload size exceeds limit, no need to call Unmarshal
+		//
+		// set frameReader to current oversized frame so that
+		// the next call to this function can drain leftover
+		// data before processing the next frame
+		ws.frameReader = frame
+		return ErrFrameTooLarge
+	}
+	payloadType := frame.PayloadType()
+	data, err := ioutil.ReadAll(frame)
+	if err != nil {
+		return err
+	}
+	return cd.Unmarshal(data, payloadType, v)
+}
+
+func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
+	switch data := v.(type) {
+	case string:
+		return []byte(data), TextFrame, nil
+	case []byte:
+		return data, BinaryFrame, nil
+	}
+	return nil, UnknownFrame, ErrNotSupported
+}
+
+func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
+	switch data := v.(type) {
+	case *string:
+		*data = string(msg)
+		return nil
+	case *[]byte:
+		*data = msg
+		return nil
+	}
+	return ErrNotSupported
+}
+
+/*
+Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
+To send/receive text frame, use string type.
+To send/receive binary frame, use []byte type.
+
+Trivial usage:
+
+	import "websocket"
+
+	// receive text frame
+	var message string
+	websocket.Message.Receive(ws, &message)
+
+	// send text frame
+	message = "hello"
+	websocket.Message.Send(ws, message)
+
+	// receive binary frame
+	var data []byte
+	websocket.Message.Receive(ws, &data)
+
+	// send binary frame
+	data = []byte{0, 1, 2}
+	websocket.Message.Send(ws, data)
+
+*/
+var Message = Codec{marshal, unmarshal}
+
+func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
+	msg, err = json.Marshal(v)
+	return msg, TextFrame, err
+}
+
+func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
+	return json.Unmarshal(msg, v)
+}
+
+/*
+JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
+
+Trivial usage:
+
+	import "websocket"
+
+	type T struct {
+		Msg string
+		Count int
+	}
+
+	// receive JSON type T
+	var data T
+	websocket.JSON.Receive(ws, &data)
+
+	// send JSON type T
+	websocket.JSON.Send(ws, data)
+*/
+var JSON = Codec{jsonMarshal, jsonUnmarshal}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 3baabde5..a2e4a3da 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,5 +1,7 @@
 # github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
 github.com/42wim/go-gitter
+# github.com/Jeffail/gabs v1.1.1
+github.com/Jeffail/gabs
 # github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
 github.com/Philipp15b/go-steam
 github.com/Philipp15b/go-steam/protocol/steamlang
@@ -30,6 +32,8 @@ github.com/golang/protobuf/protoc-gen-go/descriptor
 github.com/google/gops/agent
 github.com/google/gops/internal
 github.com/google/gops/signal
+# github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4
+github.com/gopackage/ddp
 # github.com/gorilla/schema v1.0.2
 github.com/gorilla/schema
 # github.com/gorilla/websocket v1.4.0
@@ -66,6 +70,10 @@ github.com/labstack/gommon/random
 github.com/lrstanley/girc
 # github.com/magiconair/properties v1.8.0
 github.com/magiconair/properties
+# github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
+github.com/matterbridge/Rocket.Chat.Go.SDK/models
+github.com/matterbridge/Rocket.Chat.Go.SDK/realtime
+github.com/matterbridge/Rocket.Chat.Go.SDK/rest
 # github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
 github.com/matterbridge/go-xmpp
 # github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
@@ -91,6 +99,8 @@ github.com/mitchellh/mapstructure
 github.com/mreiferson/go-httpclient
 # github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff
 github.com/mrexodia/wray
+# github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
+github.com/nelsonken/gomf
 # github.com/nicksnyder/go-i18n v1.4.0
 github.com/nicksnyder/go-i18n/i18n
 github.com/nicksnyder/go-i18n/i18n/bundle
@@ -184,6 +194,8 @@ golang.org/x/crypto/curve25519
 golang.org/x/crypto/ed25519
 golang.org/x/crypto/internal/chacha20
 golang.org/x/crypto/ed25519/internal/edwards25519
+# golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37
+golang.org/x/net/websocket
 # golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc
 golang.org/x/sys/unix
 golang.org/x/sys/windows
-- 
cgit v1.2.3