Refactor and update RocketChat bridge (#707)
* 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
This commit is contained in:
		
							
								
								
									
										69
									
								
								bridge/rocketchat/handlers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								bridge/rocketchat/handlers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										198
									
								
								bridge/rocketchat/helpers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								bridge/rocketchat/helpers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text += fi.URL | ||||
| 				} | ||||
| 			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 { | ||||
| 			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) | ||||
| 	smsg := &models.Message{ | ||||
| 		RoomID: channel.ID, | ||||
| 		Msg:    msg.Text, | ||||
| 		PostMessage: models.PostMessage{ | ||||
| 			Avatar: msg.Avatar, | ||||
| 			Alias:  msg.Username, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	rmsg, err := b.c.SendMessage(smsg) | ||||
| 	if rmsg == nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	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} | ||||
| 	} | ||||
| 	return rmsg.ID, err | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								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-20190212232658-7aa251978a18 | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								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= | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										19
									
								
								vendor/github.com/Jeffail/gabs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/Jeffail/gabs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										315
									
								
								vendor/github.com/Jeffail/gabs/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								vendor/github.com/Jeffail/gabs/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,315 @@ | ||||
|  | ||||
|  | ||||
| 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() | ||||
| ``` | ||||
							
								
								
									
										581
									
								
								vendor/github.com/Jeffail/gabs/gabs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										581
									
								
								vendor/github.com/Jeffail/gabs/gabs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
|  | ||||
| //-------------------------------------------------------------------------------------------------- | ||||
							
								
								
									
										
											BIN
										
									
								
								vendor/github.com/Jeffail/gabs/gabs_logo.png
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vendor/github.com/Jeffail/gabs/gabs_logo.png
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 164 KiB | 
							
								
								
									
										24
									
								
								vendor/github.com/gopackage/ddp/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/gopackage/ddp/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										13
									
								
								vendor/github.com/gopackage/ddp/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/gopackage/ddp/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										3
									
								
								vendor/github.com/gopackage/ddp/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/gopackage/ddp/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # ddp | ||||
|  | ||||
| MeteorJS DDP library for Golang | ||||
							
								
								
									
										79
									
								
								vendor/github.com/gopackage/ddp/ddp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								vendor/github.com/gopackage/ddp/ddp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										654
									
								
								vendor/github.com/gopackage/ddp/ddp_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										654
									
								
								vendor/github.com/gopackage/ddp/ddp_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										245
									
								
								vendor/github.com/gopackage/ddp/ddp_collection.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								vendor/github.com/gopackage/ddp/ddp_collection.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										217
									
								
								vendor/github.com/gopackage/ddp/ddp_ejson.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								vendor/github.com/gopackage/ddp/ddp_ejson.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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"} | ||||
| } | ||||
							
								
								
									
										82
									
								
								vendor/github.com/gopackage/ddp/ddp_messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/gopackage/ddp/ddp_messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										321
									
								
								vendor/github.com/gopackage/ddp/ddp_stats.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								vendor/github.com/gopackage/ddp/ddp_stats.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										39
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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"` | ||||
| } | ||||
							
								
								
									
										133
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										75
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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"` | ||||
| } | ||||
							
								
								
									
										7
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| package models | ||||
|  | ||||
| type Permission struct { | ||||
| 	ID        string   `json:"_id"` | ||||
| 	UpdatedAt string   `json:"_updatedAt.$date"` | ||||
| 	Roles     []string `json:"roles"` | ||||
| } | ||||
							
								
								
									
										21
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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"` | ||||
| } | ||||
							
								
								
									
										29
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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"` | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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"` | ||||
| } | ||||
							
								
								
									
										263
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										96
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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()) | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| package realtime | ||||
|  | ||||
| func (c *Client) getCustomEmoji() error { | ||||
| 	_, err := c.ddp.Call("listEmojiCustom") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										21
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										240
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										54
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										53
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										41
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										103
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										64
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										176
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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() | ||||
| } | ||||
							
								
								
									
										98
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										67
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										145
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										37
									
								
								vendor/github.com/nelsonken/gomf/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/nelsonken/gomf/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| ``` | ||||
							
								
								
									
										89
									
								
								vendor/github.com/nelsonken/gomf/form_builder.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vendor/github.com/nelsonken/gomf/form_builder.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										33
									
								
								vendor/github.com/nelsonken/gomf/up.php
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/nelsonken/gomf/up.php
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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"; | ||||
|       } | ||||
| } | ||||
|  | ||||
|  | ||||
|    | ||||
|  | ||||
							
								
								
									
										3
									
								
								vendor/golang.org/x/net/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/golang.org/x/net/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										3
									
								
								vendor/golang.org/x/net/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/golang.org/x/net/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										27
									
								
								vendor/golang.org/x/net/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/golang.org/x/net/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										22
									
								
								vendor/golang.org/x/net/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/golang.org/x/net/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										106
									
								
								vendor/golang.org/x/net/websocket/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								vendor/golang.org/x/net/websocket/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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} | ||||
| } | ||||
							
								
								
									
										24
									
								
								vendor/golang.org/x/net/websocket/dial.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/golang.org/x/net/websocket/dial.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										583
									
								
								vendor/golang.org/x/net/websocket/hybi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										583
									
								
								vendor/golang.org/x/net/websocket/hybi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										113
									
								
								vendor/golang.org/x/net/websocket/server.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								vendor/golang.org/x/net/websocket/server.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										448
									
								
								vendor/golang.org/x/net/websocket/websocket.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										448
									
								
								vendor/golang.org/x/net/websocket/websocket.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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} | ||||
							
								
								
									
										12
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Wim
					Wim