summaryrefslogtreecommitdiffstats
path: root/vendor/github.com
diff options
context:
space:
mode:
authorWim <wim@42.be>2019-02-10 17:00:11 +0100
committerWim <wim@42.be>2019-02-15 18:19:34 +0100
commit6ebd5cbbd8a941e0bc5f99f0d8e99cfd1d8ac0d7 (patch)
tree47070e58e7802afd80fce53f0048a87a014a7c0d /vendor/github.com
parent2cfd880cdb0df29771bf8f31df8d990ab897889d (diff)
downloadmatterbridge-msglm-6ebd5cbbd8a941e0bc5f99f0d8e99cfd1d8ac0d7.tar.gz
matterbridge-msglm-6ebd5cbbd8a941e0bc5f99f0d8e99cfd1d8ac0d7.tar.bz2
matterbridge-msglm-6ebd5cbbd8a941e0bc5f99f0d8e99cfd1d8ac0d7.zip
Refactor and update RocketChat bridge
* Add support for editing/deleting messages * Add support for uploading files * Add support for avatars * Use the Rocket.Chat.Go.SDK * Use the rest and streaming api
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/Jeffail/gabs/LICENSE19
-rw-r--r--vendor/github.com/Jeffail/gabs/README.md315
-rw-r--r--vendor/github.com/Jeffail/gabs/gabs.go581
-rw-r--r--vendor/github.com/Jeffail/gabs/gabs_logo.pngbin0 -> 167771 bytes
-rw-r--r--vendor/github.com/gopackage/ddp/.gitignore24
-rw-r--r--vendor/github.com/gopackage/ddp/LICENSE13
-rw-r--r--vendor/github.com/gopackage/ddp/README.md3
-rw-r--r--vendor/github.com/gopackage/ddp/ddp.go79
-rw-r--r--vendor/github.com/gopackage/ddp/ddp_client.go654
-rw-r--r--vendor/github.com/gopackage/ddp/ddp_collection.go245
-rw-r--r--vendor/github.com/gopackage/ddp/ddp_ejson.go217
-rw-r--r--vendor/github.com/gopackage/ddp/ddp_messages.go82
-rw-r--r--vendor/github.com/gopackage/ddp/ddp_stats.go321
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go39
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go133
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go75
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go7
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go21
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go29
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go10
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go263
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go96
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go10
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go21
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go240
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go54
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go53
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go41
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go103
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go64
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go176
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go98
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go67
-rw-r--r--vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go145
-rw-r--r--vendor/github.com/nelsonken/gomf/README.md37
-rw-r--r--vendor/github.com/nelsonken/gomf/form_builder.go89
-rw-r--r--vendor/github.com/nelsonken/gomf/up.php33
37 files changed, 4457 insertions, 0 deletions
diff --git a/vendor/github.com/Jeffail/gabs/LICENSE b/vendor/github.com/Jeffail/gabs/LICENSE
new file mode 100644
index 00000000..99a62c62
--- /dev/null
+++ b/vendor/github.com/Jeffail/gabs/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Ashley Jeffs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/Jeffail/gabs/README.md b/vendor/github.com/Jeffail/gabs/README.md
new file mode 100644
index 00000000..a58193fd
--- /dev/null
+++ b/vendor/github.com/Jeffail/gabs/README.md
@@ -0,0 +1,315 @@
+![Gabs](gabs_logo.png "Gabs")
+
+Gabs is a small utility for dealing with dynamic or unknown JSON structures in
+golang. It's pretty much just a helpful wrapper around the golang
+`json.Marshal/json.Unmarshal` behaviour and `map[string]interface{}` objects.
+It does nothing spectacular except for being fabulous.
+
+https://godoc.org/github.com/Jeffail/gabs
+
+## How to install:
+
+``` bash
+go get github.com/Jeffail/gabs
+```
+
+## How to use
+
+### Parsing and searching JSON
+
+``` go
+...
+
+import "github.com/Jeffail/gabs"
+
+jsonParsed, err := gabs.ParseJSON([]byte(`{
+ "outter":{
+ "inner":{
+ "value1":10,
+ "value2":22
+ },
+ "alsoInner":{
+ "value1":20
+ }
+ }
+}`))
+
+var value float64
+var ok bool
+
+value, ok = jsonParsed.Path("outter.inner.value1").Data().(float64)
+// value == 10.0, ok == true
+
+value, ok = jsonParsed.Search("outter", "inner", "value1").Data().(float64)
+// value == 10.0, ok == true
+
+value, ok = jsonParsed.Path("does.not.exist").Data().(float64)
+// value == 0.0, ok == false
+
+exists := jsonParsed.Exists("outter", "inner", "value1")
+// exists == true
+
+exists := jsonParsed.Exists("does", "not", "exist")
+// exists == false
+
+exists := jsonParsed.ExistsP("does.not.exist")
+// exists == false
+
+...
+```
+
+### Iterating objects
+
+``` go
+...
+
+jsonParsed, _ := gabs.ParseJSON([]byte(`{"object":{ "first": 1, "second": 2, "third": 3 }}`))
+
+// S is shorthand for Search
+children, _ := jsonParsed.S("object").ChildrenMap()
+for key, child := range children {
+ fmt.Printf("key: %v, value: %v\n", key, child.Data().(string))
+}
+
+...
+```
+
+### Iterating arrays
+
+``` go
+...
+
+jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ "first", "second", "third" ]}`))
+
+// S is shorthand for Search
+children, _ := jsonParsed.S("array").Children()
+for _, child := range children {
+ fmt.Println(child.Data().(string))
+}
+
+...
+```
+
+Will print:
+
+```
+first
+second
+third
+```
+
+Children() will return all children of an array in order. This also works on
+objects, however, the children will be returned in a random order.
+
+### Searching through arrays
+
+If your JSON structure contains arrays you can still search the fields of the
+objects within the array, this returns a JSON array containing the results for
+each element.
+
+``` go
+...
+
+jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ {"value":1}, {"value":2}, {"value":3} ]}`))
+fmt.Println(jsonParsed.Path("array.value").String())
+
+...
+```
+
+Will print:
+
+```
+[1,2,3]
+```
+
+### Generating JSON
+
+``` go
+...
+
+jsonObj := gabs.New()
+// or gabs.Consume(jsonObject) to work on an existing map[string]interface{}
+
+jsonObj.Set(10, "outter", "inner", "value")
+jsonObj.SetP(20, "outter.inner.value2")
+jsonObj.Set(30, "outter", "inner2", "value3")
+
+fmt.Println(jsonObj.String())
+
+...
+```
+
+Will print:
+
+```
+{"outter":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
+```
+
+To pretty-print:
+
+``` go
+...
+
+fmt.Println(jsonObj.StringIndent("", " "))
+
+...
+```
+
+Will print:
+
+```
+{
+ "outter": {
+ "inner": {
+ "value": 10,
+ "value2": 20
+ },
+ "inner2": {
+ "value3": 30
+ }
+ }
+}
+```
+
+### Generating Arrays
+
+``` go
+...
+
+jsonObj := gabs.New()
+
+jsonObj.Array("foo", "array")
+// Or .ArrayP("foo.array")
+
+jsonObj.ArrayAppend(10, "foo", "array")
+jsonObj.ArrayAppend(20, "foo", "array")
+jsonObj.ArrayAppend(30, "foo", "array")
+
+fmt.Println(jsonObj.String())
+
+...
+```
+
+Will print:
+
+```
+{"foo":{"array":[10,20,30]}}
+```
+
+Working with arrays by index:
+
+``` go
+...
+
+jsonObj := gabs.New()
+
+// Create an array with the length of 3
+jsonObj.ArrayOfSize(3, "foo")
+
+jsonObj.S("foo").SetIndex("test1", 0)
+jsonObj.S("foo").SetIndex("test2", 1)
+
+// Create an embedded array with the length of 3
+jsonObj.S("foo").ArrayOfSizeI(3, 2)
+
+jsonObj.S("foo").Index(2).SetIndex(1, 0)
+jsonObj.S("foo").Index(2).SetIndex(2, 1)
+jsonObj.S("foo").Index(2).SetIndex(3, 2)
+
+fmt.Println(jsonObj.String())
+
+...
+```
+
+Will print:
+
+```
+{"foo":["test1","test2",[1,2,3]]}
+```
+
+### Converting back to JSON
+
+This is the easiest part:
+
+``` go
+...
+
+jsonParsedObj, _ := gabs.ParseJSON([]byte(`{
+ "outter":{
+ "values":{
+ "first":10,
+ "second":11
+ }
+ },
+ "outter2":"hello world"
+}`))
+
+jsonOutput := jsonParsedObj.String()
+// Becomes `{"outter":{"values":{"first":10,"second":11}},"outter2":"hello world"}`
+
+...
+```
+
+And to serialize a specific segment is as simple as:
+
+``` go
+...
+
+jsonParsedObj := gabs.ParseJSON([]byte(`{
+ "outter":{
+ "values":{
+ "first":10,
+ "second":11
+ }
+ },
+ "outter2":"hello world"
+}`))
+
+jsonOutput := jsonParsedObj.Search("outter").String()
+// Becomes `{"values":{"first":10,"second":11}}`
+
+...
+```
+
+### Merge two containers
+
+You can merge a JSON structure into an existing one, where collisions will be
+converted into a JSON array.
+
+``` go
+jsonParsed1, _ := ParseJSON([]byte(`{"outter": {"value1": "one"}}`))
+jsonParsed2, _ := ParseJSON([]byte(`{"outter": {"inner": {"value3": "three"}}, "outter2": {"value2": "two"}}`))
+
+jsonParsed1.Merge(jsonParsed2)
+// Becomes `{"outter":{"inner":{"value3":"three"},"value1":"one"},"outter2":{"value2":"two"}}`
+```
+
+Arrays are merged:
+
+``` go
+jsonParsed1, _ := ParseJSON([]byte(`{"array": ["one"]}`))
+jsonParsed2, _ := ParseJSON([]byte(`{"array": ["two"]}`))
+
+jsonParsed1.Merge(jsonParsed2)
+// Becomes `{"array":["one", "two"]}`
+```
+
+### Parsing Numbers
+
+Gabs uses the `json` package under the bonnet, which by default will parse all
+number values into `float64`. If you need to parse `Int` values then you should
+use a `json.Decoder` (https://golang.org/pkg/encoding/json/#Decoder):
+
+``` go
+sample := []byte(`{"test":{"int":10, "float":6.66}}`)
+dec := json.NewDecoder(bytes.NewReader(sample))
+dec.UseNumber()
+
+val, err := gabs.ParseJSONDecoder(dec)
+if err != nil {
+ t.Errorf("Failed to parse: %v", err)
+ return
+}
+
+intValue, err := val.Path("test.int").Data().(json.Number).Int64()
+```
diff --git a/vendor/github.com/Jeffail/gabs/gabs.go b/vendor/github.com/Jeffail/gabs/gabs.go
new file mode 100644
index 00000000..a21a79d7
--- /dev/null
+++ b/vendor/github.com/Jeffail/gabs/gabs.go
@@ -0,0 +1,581 @@
+/*
+Copyright (c) 2014 Ashley Jeffs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+// Package gabs implements a simplified wrapper around creating and parsing JSON.
+package gabs
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "io"
+ "io/ioutil"
+ "strings"
+)
+
+//--------------------------------------------------------------------------------------------------
+
+var (
+ // ErrOutOfBounds - Index out of bounds.
+ ErrOutOfBounds = errors.New("out of bounds")
+
+ // ErrNotObjOrArray - The target is not an object or array type.
+ ErrNotObjOrArray = errors.New("not an object or array")
+
+ // ErrNotObj - The target is not an object type.
+ ErrNotObj = errors.New("not an object")
+
+ // ErrNotArray - The target is not an array type.
+ ErrNotArray = errors.New("not an array")
+
+ // ErrPathCollision - Creating a path failed because an element collided with an existing value.
+ ErrPathCollision = errors.New("encountered value collision whilst building path")
+
+ // ErrInvalidInputObj - The input value was not a map[string]interface{}.
+ ErrInvalidInputObj = errors.New("invalid input object")
+
+ // ErrInvalidInputText - The input data could not be parsed.
+ ErrInvalidInputText = errors.New("input text could not be parsed")
+
+ // ErrInvalidPath - The filepath was not valid.
+ ErrInvalidPath = errors.New("invalid file path")
+
+ // ErrInvalidBuffer - The input buffer contained an invalid JSON string
+ ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
+)
+
+//--------------------------------------------------------------------------------------------------
+
+// Container - an internal structure that holds a reference to the core interface map of the parsed
+// json. Use this container to move context.
+type Container struct {
+ object interface{}
+}
+
+// Data - Return the contained data as an interface{}.
+func (g *Container) Data() interface{} {
+ if g == nil {
+ return nil
+ }
+ return g.object
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Path - Search for a value using dot notation.
+func (g *Container) Path(path string) *Container {
+ return g.Search(strings.Split(path, ".")...)
+}
+
+// Search - Attempt to find and return an object within the JSON structure by specifying the
+// hierarchy of field names to locate the target. If the search encounters an array and has not
+// reached the end target then it will iterate each object of the array for the target and return
+// all of the results in a JSON array.
+func (g *Container) Search(hierarchy ...string) *Container {
+ var object interface{}
+
+ object = g.Data()
+ for target := 0; target < len(hierarchy); target++ {
+ if mmap, ok := object.(map[string]interface{}); ok {
+ object, ok = mmap[hierarchy[target]]
+ if !ok {
+ return nil
+ }
+ } else if marray, ok := object.([]interface{}); ok {
+ tmpArray := []interface{}{}
+ for _, val := range marray {
+ tmpGabs := &Container{val}
+ res := tmpGabs.Search(hierarchy[target:]...)
+ if res != nil {
+ tmpArray = append(tmpArray, res.Data())
+ }
+ }
+ if len(tmpArray) == 0 {
+ return nil
+ }
+ return &Container{tmpArray}
+ } else {
+ return nil
+ }
+ }
+ return &Container{object}
+}
+
+// S - Shorthand method, does the same thing as Search.
+func (g *Container) S(hierarchy ...string) *Container {
+ return g.Search(hierarchy...)
+}
+
+// Exists - Checks whether a path exists.
+func (g *Container) Exists(hierarchy ...string) bool {
+ return g.Search(hierarchy...) != nil
+}
+
+// ExistsP - Checks whether a dot notation path exists.
+func (g *Container) ExistsP(path string) bool {
+ return g.Exists(strings.Split(path, ".")...)
+}
+
+// Index - Attempt to find and return an object within a JSON array by index.
+func (g *Container) Index(index int) *Container {
+ if array, ok := g.Data().([]interface{}); ok {
+ if index >= len(array) {
+ return &Container{nil}
+ }
+ return &Container{array[index]}
+ }
+ return &Container{nil}
+}
+
+// Children - Return a slice of all the children of the array. This also works for objects, however,
+// the children returned for an object will NOT be in order and you lose the names of the returned
+// objects this way.
+func (g *Container) Children() ([]*Container, error) {
+ if array, ok := g.Data().([]interface{}); ok {
+ children := make([]*Container, len(array))
+ for i := 0; i < len(array); i++ {
+ children[i] = &Container{array[i]}
+ }
+ return children, nil
+ }
+ if mmap, ok := g.Data().(map[string]interface{}); ok {
+ children := []*Container{}
+ for _, obj := range mmap {
+ children = append(children, &Container{obj})
+ }
+ return children, nil
+ }
+ return nil, ErrNotObjOrArray
+}
+
+// ChildrenMap - Return a map of all the children of an object.
+func (g *Container) ChildrenMap() (map[string]*Container, error) {
+ if mmap, ok := g.Data().(map[string]interface{}); ok {
+ children := map[string]*Container{}
+ for name, obj := range mmap {
+ children[name] = &Container{obj}
+ }
+ return children, nil
+ }
+ return nil, ErrNotObj
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be
+// constructed, and if a collision occurs with a non object type whilst iterating the path an error
+// is returned.
+func (g *Container) Set(value interface{}, path ...string) (*Container, error) {
+ if len(path) == 0 {
+ g.object = value
+ return g, nil
+ }
+ var object interface{}
+ if g.object == nil {
+ g.object = map[string]interface{}{}
+ }
+ object = g.object
+ for target := 0; target < len(path); target++ {
+ if mmap, ok := object.(map[string]interface{}); ok {
+ if target == len(path)-1 {
+ mmap[path[target]] = value
+ } else if mmap[path[target]] == nil {
+ mmap[path[target]] = map[string]interface{}{}
+ }
+ object = mmap[path[target]]
+ } else {
+ return &Container{nil}, ErrPathCollision
+ }
+ }
+ return &Container{object}, nil
+}
+
+// SetP - Does the same as Set, but using a dot notation JSON path.
+func (g *Container) SetP(value interface{}, path string) (*Container, error) {
+ return g.Set(value, strings.Split(path, ".")...)
+}
+
+// SetIndex - Set a value of an array element based on the index.
+func (g *Container) SetIndex(value interface{}, index int) (*Container, error) {
+ if array, ok := g.Data().([]interface{}); ok {
+ if index >= len(array) {
+ return &Container{nil}, ErrOutOfBounds
+ }
+ array[index] = value
+ return &Container{array[index]}, nil
+ }
+ return &Container{nil}, ErrNotArray
+}
+
+// Object - Create a new JSON object at a path. Returns an error if the path contains a collision
+// with a non object type.
+func (g *Container) Object(path ...string) (*Container, error) {
+ return g.Set(map[string]interface{}{}, path...)
+}
+
+// ObjectP - Does the same as Object, but using a dot notation JSON path.
+func (g *Container) ObjectP(path string) (*Container, error) {
+ return g.Object(strings.Split(path, ".")...)
+}
+
+// ObjectI - Create a new JSON object at an array index. Returns an error if the object is not an
+// array or the index is out of bounds.
+func (g *Container) ObjectI(index int) (*Container, error) {
+ return g.SetIndex(map[string]interface{}{}, index)
+}
+
+// Array - Create a new JSON array at a path. Returns an error if the path contains a collision with
+// a non object type.
+func (g *Container) Array(path ...string) (*Container, error) {
+ return g.Set([]interface{}{}, path...)
+}
+
+// ArrayP - Does the same as Array, but using a dot notation JSON path.
+func (g *Container) ArrayP(path string) (*Container, error) {
+ return g.Array(strings.Split(path, ".")...)
+}
+
+// ArrayI - Create a new JSON array at an array index. Returns an error if the object is not an
+// array or the index is out of bounds.
+func (g *Container) ArrayI(index int) (*Container, error) {
+ return g.SetIndex([]interface{}{}, index)
+}
+
+// ArrayOfSize - Create a new JSON array of a particular size at a path. Returns an error if the
+// path contains a collision with a non object type.
+func (g *Container) ArrayOfSize(size int, path ...string) (*Container, error) {
+ a := make([]interface{}, size)
+ return g.Set(a, path...)
+}
+
+// ArrayOfSizeP - Does the same as ArrayOfSize, but using a dot notation JSON path.
+func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) {
+ return g.ArrayOfSize(size, strings.Split(path, ".")...)
+}
+
+// ArrayOfSizeI - Create a new JSON array of a particular size at an array index. Returns an error
+// if the object is not an array or the index is out of bounds.
+func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) {
+ a := make([]interface{}, size)
+ return g.SetIndex(a, index)
+}
+
+// Delete - Delete an element at a JSON path, an error is returned if the element does not exist.
+func (g *Container) Delete(path ...string) error {
+ var object interface{}
+
+ if g.object == nil {
+ return ErrNotObj
+ }
+ object = g.object
+ for target := 0; target < len(path); target++ {
+ if mmap, ok := object.(map[string]interface{}); ok {
+ if target == len(path)-1 {
+ if _, ok := mmap[path[target]]; ok {
+ delete(mmap, path[target])
+ } else {
+ return ErrNotObj
+ }
+ }
+ object = mmap[path[target]]
+ } else {
+ return ErrNotObj
+ }
+ }
+ return nil
+}
+
+// DeleteP - Does the same as Delete, but using a dot notation JSON path.
+func (g *Container) DeleteP(path string) error {
+ return g.Delete(strings.Split(path, ".")...)
+}
+
+// Merge - Merges two gabs-containers
+func (g *Container) Merge(toMerge *Container) error {
+ var recursiveFnc func(map[string]interface{}, []string) error
+ recursiveFnc = func(mmap map[string]interface{}, path []string) error {
+ for key, value := range mmap {
+ newPath := append(path, key)
+ if g.Exists(newPath...) {
+ target := g.Search(newPath...)
+ switch t := value.(type) {
+ case map[string]interface{}:
+ switch targetV := target.Data().(type) {
+ case map[string]interface{}:
+ if err := recursiveFnc(t, newPath); err != nil {
+ return err
+ }
+ case []interface{}:
+ g.Set(append(targetV, t), newPath...)
+ default:
+ newSlice := append([]interface{}{}, targetV)
+ g.Set(append(newSlice, t), newPath...)
+ }
+ case []interface{}:
+ for _, valueOfSlice := range t {
+ if err := g.ArrayAppend(valueOfSlice, newPath...); err != nil {
+ return err
+ }
+ }
+ default:
+ switch targetV := target.Data().(type) {
+ case []interface{}:
+ g.Set(append(targetV, t), newPath...)
+ default:
+ newSlice := append([]interface{}{}, targetV)
+ g.Set(append(newSlice, t), newPath...)
+ }
+ }
+ } else {
+ // path doesn't exist. So set the value
+ if _, err := g.Set(value, newPath...); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+ }
+ if mmap, ok := toMerge.Data().(map[string]interface{}); ok {
+ return recursiveFnc(mmap, []string{})
+ }
+ return nil
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/*
+Array modification/search - Keeping these options simple right now, no need for anything more
+complicated since you can just cast to []interface{}, modify and then reassign with Set.
+*/
+
+// ArrayAppend - Append a value onto a JSON array. If the target is not a JSON array then it will be
+// converted into one, with its contents as the first element of the array.
+func (g *Container) ArrayAppend(value interface{}, path ...string) error {
+ if array, ok := g.Search(path...).Data().([]interface{}); ok {
+ array = append(array, value)
+ _, err := g.Set(array, path...)
+ return err
+ }
+
+ newArray := []interface{}{}
+ if d := g.Search(path...).Data(); d != nil {
+ newArray = append(newArray, d)
+ }
+ newArray = append(newArray, value)
+
+ _, err := g.Set(newArray, path...)
+ return err
+}
+
+// ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path.
+func (g *Container) ArrayAppendP(value interface{}, path string) error {
+ return g.ArrayAppend(value, strings.Split(path, ".")...)
+}
+
+// ArrayRemove - Remove an element from a JSON array.
+func (g *Container) ArrayRemove(index int, path ...string) error {
+ if index < 0 {
+ return ErrOutOfBounds
+ }
+ array, ok := g.Search(path...).Data().([]interface{})
+ if !ok {
+ return ErrNotArray
+ }
+ if index < len(array) {
+ array = append(array[:index], array[index+1:]...)
+ } else {
+ return ErrOutOfBounds
+ }
+ _, err := g.Set(array, path...)
+ return err
+}
+
+// ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path.
+func (g *Container) ArrayRemoveP(index int, path string) error {
+ return g.ArrayRemove(index, strings.Split(path, ".")...)
+}
+
+// ArrayElement - Access an element from a JSON array.
+func (g *Container) ArrayElement(index int, path ...string) (*Container, error) {
+ if index < 0 {
+ return &Container{nil}, ErrOutOfBounds
+ }
+ array, ok := g.Search(path...).Data().([]interface{})
+ if !ok {
+ return &Container{nil}, ErrNotArray
+ }
+ if index < len(array) {
+ return &Container{array[index]}, nil
+ }
+ return &Container{nil}, ErrOutOfBounds
+}
+
+// ArrayElementP - Access an element from a JSON array using a dot notation JSON path.
+func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
+ return g.ArrayElement(index, strings.Split(path, ".")...)
+}
+
+// ArrayCount - Count the number of elements in a JSON array.
+func (g *Container) ArrayCount(path ...string) (int, error) {
+ if array, ok := g.Search(path...).Data().([]interface{}); ok {
+ return len(array), nil
+ }
+ return 0, ErrNotArray
+}
+
+// ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path.
+func (g *Container) ArrayCountP(path string) (int, error) {
+ return g.ArrayCount(strings.Split(path, ".")...)
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Bytes - Converts the contained object back to a JSON []byte blob.
+func (g *Container) Bytes() []byte {
+ if g.Data() != nil {
+ if bytes, err := json.Marshal(g.object); err == nil {
+ return bytes
+ }
+ }
+ return []byte("{}")
+}
+
+// BytesIndent - Converts the contained object to a JSON []byte blob formatted with prefix, indent.
+func (g *Container) BytesIndent(prefix string, indent string) []byte {
+ if g.object != nil {
+ if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil {
+ return bytes
+ }
+ }
+ return []byte("{}")
+}
+
+// String - Converts the contained object to a JSON formatted string.
+func (g *Container) String() string {
+ return string(g.Bytes())
+}
+
+// StringIndent - Converts the contained object back to a JSON formatted string with prefix, indent.
+func (g *Container) StringIndent(prefix string, indent string) string {
+ return string(g.BytesIndent(prefix, indent))
+}
+
+// EncodeOpt is a functional option for the EncodeJSON method.
+type EncodeOpt func(e *json.Encoder)
+
+// EncodeOptHTMLEscape sets the encoder to escape the JSON for html.
+func EncodeOptHTMLEscape(doEscape bool) EncodeOpt {
+ return func(e *json.Encoder) {
+ e.SetEscapeHTML(doEscape)
+ }
+}
+
+// EncodeOptIndent sets the encoder to indent the JSON output.
+func EncodeOptIndent(prefix string, indent string) EncodeOpt {
+ return func(e *json.Encoder) {
+ e.SetIndent(prefix, indent)
+ }
+}
+
+// EncodeJSON - Encodes the contained object back to a JSON formatted []byte
+// using a variant list of modifier functions for the encoder being used.
+// Functions for modifying the output are prefixed with EncodeOpt, e.g.
+// EncodeOptHTMLEscape.
+func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte {
+ var b bytes.Buffer
+ encoder := json.NewEncoder(&b)
+ encoder.SetEscapeHTML(false) // Do not escape by default.
+ for _, opt := range encodeOpts {
+ opt(encoder)
+ }
+ if err := encoder.Encode(g.object); err != nil {
+ return []byte("{}")
+ }
+ result := b.Bytes()
+ if len(result) > 0 {
+ result = result[:len(result)-1]
+ }
+ return result
+}
+
+// New - Create a new gabs JSON object.
+func New() *Container {
+ return &Container{map[string]interface{}{}}
+}
+
+// Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object.
+func Consume(root interface{}) (*Container, error) {
+ return &Container{root}, nil
+}
+
+// ParseJSON - Convert a string into a representation of the parsed JSON.
+func ParseJSON(sample []byte) (*Container, error) {
+ var gabs Container
+
+ if err := json.Unmarshal(sample, &gabs.object); err != nil {
+ return nil, err
+ }
+
+ return &gabs, nil
+}
+
+// ParseJSONDecoder - Convert a json.Decoder into a representation of the parsed JSON.
+func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
+ var gabs Container
+
+ if err := decoder.Decode(&gabs.object); err != nil {
+ return nil, err
+ }
+
+ return &gabs, nil
+}
+
+// ParseJSONFile - Read a file and convert into a representation of the parsed JSON.
+func ParseJSONFile(path string) (*Container, error) {
+ if len(path) > 0 {
+ cBytes, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ container, err := ParseJSON(cBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ return container, nil
+ }
+ return nil, ErrInvalidPath
+}
+
+// ParseJSONBuffer - Read the contents of a buffer into a representation of the parsed JSON.
+func ParseJSONBuffer(buffer io.Reader) (*Container, error) {
+ var gabs Container
+ jsonDecoder := json.NewDecoder(buffer)
+ if err := jsonDecoder.Decode(&gabs.object); err != nil {
+ return nil, err
+ }
+
+ return &gabs, nil
+}
+
+//--------------------------------------------------------------------------------------------------
diff --git a/vendor/github.com/Jeffail/gabs/gabs_logo.png b/vendor/github.com/Jeffail/gabs/gabs_logo.png
new file mode 100644
index 00000000..b6c1fad9
--- /dev/null
+++ b/vendor/github.com/Jeffail/gabs/gabs_logo.png
Binary files differ
diff --git a/vendor/github.com/gopackage/ddp/.gitignore b/vendor/github.com/gopackage/ddp/.gitignore
new file mode 100644
index 00000000..daf913b1
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/gopackage/ddp/LICENSE b/vendor/github.com/gopackage/ddp/LICENSE
new file mode 100644
index 00000000..03d77e8a
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2015, Metamech LLC.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/vendor/github.com/gopackage/ddp/README.md b/vendor/github.com/gopackage/ddp/README.md
new file mode 100644
index 00000000..fd62c718
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/README.md
@@ -0,0 +1,3 @@
+# ddp
+
+MeteorJS DDP library for Golang
diff --git a/vendor/github.com/gopackage/ddp/ddp.go b/vendor/github.com/gopackage/ddp/ddp.go
new file mode 100644
index 00000000..910adafd
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp.go
@@ -0,0 +1,79 @@
+// Package ddp implements the MeteorJS DDP protocol over websockets. Fallback
+// to longpolling is NOT supported (and is not planned on ever being supported
+// by this library). We will try to model the library after `net/http` - right
+// now the library is barebones and doesn't provide the pluggability of http.
+// However, that's the goal for the package eventually.
+package ddp
+
+import (
+ "fmt"
+ "log"
+ "sync"
+ "time"
+)
+
+// debugLog is true if we should log debugging information about the connection
+var debugLog = true
+
+// The main file contains common utility types.
+
+// -------------------------------------------------------------------
+
+// idManager provides simple incrementing IDs for ddp messages.
+type idManager struct {
+ // nextID is the next ID for API calls
+ nextID uint64
+ // idMutex is a mutex to protect ID updates
+ idMutex *sync.Mutex
+}
+
+// newidManager creates a new instance and sets up resources.
+func newidManager() *idManager {
+ return &idManager{idMutex: new(sync.Mutex)}
+}
+
+// newID issues a new ID for use in calls.
+func (id *idManager) newID() string {
+ id.idMutex.Lock()
+ next := id.nextID
+ id.nextID++
+ id.idMutex.Unlock()
+ return fmt.Sprintf("%x", next)
+}
+
+// -------------------------------------------------------------------
+
+// pingTracker tracks in-flight pings.
+type pingTracker struct {
+ handler func(error)
+ timeout time.Duration
+ timer *time.Timer
+}
+
+// -------------------------------------------------------------------
+
+// Call represents an active RPC call.
+type Call struct {
+ ID string // The uuid for this method call
+ ServiceMethod string // The name of the service and method to call.
+ Args interface{} // The argument to the function (*struct).
+ Reply interface{} // The reply from the function (*struct).
+ Error error // After completion, the error status.
+ Done chan *Call // Strobes when call is complete.
+ Owner *Client // Client that owns the method call
+}
+
+// done removes the call from any owners and strobes the done channel with itself.
+func (call *Call) done() {
+ delete(call.Owner.calls, call.ID)
+ select {
+ case call.Done <- call:
+ // ok
+ default:
+ // We don't want to block here. It is the caller's responsibility to make
+ // sure the channel has enough buffer space. See comment in Go().
+ if debugLog {
+ log.Println("rpc: discarding Call reply due to insufficient Done chan capacity")
+ }
+ }
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_client.go b/vendor/github.com/gopackage/ddp/ddp_client.go
new file mode 100644
index 00000000..8d6323b7
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_client.go
@@ -0,0 +1,654 @@
+package ddp
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "sync"
+ "time"
+
+ "golang.org/x/net/websocket"
+ "errors"
+)
+
+const (
+ DISCONNECTED = iota
+ DIALING
+ CONNECTING
+ CONNECTED
+)
+
+type ConnectionListener interface {
+ Connected()
+}
+
+type ConnectionNotifier interface {
+ AddConnectionListener(listener ConnectionListener)
+}
+
+type StatusListener interface {
+ Status(status int)
+}
+
+type StatusNotifier interface {
+ AddStatusListener(listener StatusListener)
+}
+
+// Client represents a DDP client connection. The DDP client establish a DDP
+// session and acts as a message pump for other tools.
+type Client struct {
+ // HeartbeatInterval is the time between heartbeats to send
+ HeartbeatInterval time.Duration
+ // HeartbeatTimeout is the time for a heartbeat ping to timeout
+ HeartbeatTimeout time.Duration
+ // ReconnectInterval is the time between reconnections on bad connections
+ ReconnectInterval time.Duration
+
+ // writeStats controls statistics gathering for current websocket writes.
+ writeSocketStats *WriterStats
+ // writeStats controls statistics gathering for overall client writes.
+ writeStats *WriterStats
+ // writeLog controls logging for client writes.
+ writeLog *WriterLogger
+ // readStats controls statistics gathering for current websocket reads.
+ readSocketStats *ReaderStats
+ // readStats controls statistics gathering for overall client reads.
+ readStats *ReaderStats
+ // readLog control logging for clietn reads.
+ readLog *ReaderLogger
+ // reconnects in the number of reconnections the client has made
+ reconnects int64
+ // pingsIn is the number of pings received from the server
+ pingsIn int64
+ // pingsOut is te number of pings sent by the client
+ pingsOut int64
+
+ // session contains the DDP session token (can be used for reconnects and debugging).
+ session string
+ // version contains the negotiated DDP protocol version in use.
+ version string
+ // serverID the cluster node ID for the server we connected to
+ serverID string
+ // ws is the underlying websocket being used.
+ ws *websocket.Conn
+ // encoder is a JSON encoder to send outgoing packets to the websocket.
+ encoder *json.Encoder
+ // url the URL the websocket is connected to
+ url string
+ // origin is the origin for the websocket connection
+ origin string
+ // inbox is an incoming message channel
+ inbox chan map[string]interface{}
+ // errors is an incoming errors channel
+ errors chan error
+ // pingTimer is a timer for sending regular pings to the server
+ pingTimer *time.Timer
+ // pings tracks inflight pings based on each ping ID.
+ pings map[string][]*pingTracker
+ // calls tracks method invocations that are still in flight
+ calls map[string]*Call
+ // subs tracks active subscriptions. Map contains name->args
+ subs map[string]*Call
+ // collections contains all the collections currently subscribed
+ collections map[string]Collection
+ // connectionStatus is the current connection status of the client
+ connectionStatus int
+ // reconnectTimer is the timer tracking reconnections
+ reconnectTimer *time.Timer
+ // reconnectLock protects access to reconnection
+ reconnectLock *sync.Mutex
+
+ // statusListeners will be informed when the connection status of the client changes
+ statusListeners []StatusListener
+ // connectionListeners will be informed when a connection to the server is established
+ connectionListeners []ConnectionListener
+
+ // idManager tracks IDs for ddp messages
+ idManager
+}
+
+// NewClient creates a default client (using an internal websocket) to the
+// provided URL using the origin for the connection. The client will
+// automatically connect, upgrade to a websocket, and establish a DDP
+// connection session before returning the client. The client will
+// automatically and internally handle heartbeats and reconnects.
+//
+// TBD create an option to use an external websocket (aka htt.Transport)
+// TBD create an option to substitute heartbeat and reconnect behavior (aka http.Tranport)
+// TBD create an option to hijack the connection (aka http.Hijacker)
+// TBD create profiling features (aka net/http/pprof)
+func NewClient(url, origin string) *Client {
+ c := &Client{
+ HeartbeatInterval: time.Minute, // Meteor impl default + 10 (we ping last)
+ HeartbeatTimeout: 15 * time.Second, // Meteor impl default
+ ReconnectInterval: 5 * time.Second,
+ collections: map[string]Collection{},
+ url: url,
+ origin: origin,
+ inbox: make(chan map[string]interface{}, 100),
+ errors: make(chan error, 100),
+ pings: map[string][]*pingTracker{},
+ calls: map[string]*Call{},
+ subs: map[string]*Call{},
+ connectionStatus: DISCONNECTED,
+ reconnectLock: &sync.Mutex{},
+
+ // Stats
+ writeSocketStats: NewWriterStats(nil),
+ writeStats: NewWriterStats(nil),
+ readSocketStats: NewReaderStats(nil),
+ readStats: NewReaderStats(nil),
+
+ // Loggers
+ writeLog: NewWriterTextLogger(nil),
+ readLog: NewReaderTextLogger(nil),
+
+ idManager: *newidManager(),
+ }
+ c.encoder = json.NewEncoder(c.writeStats)
+ c.SetSocketLogActive(false)
+
+ // We spin off an inbox processing goroutine
+ go c.inboxManager()
+
+ return c
+}
+
+// Session returns the negotiated session token for the connection.
+func (c *Client) Session() string {
+ return c.session
+}
+
+// Version returns the negotiated protocol version in use by the client.
+func (c *Client) Version() string {
+ return c.version
+}
+
+// AddStatusListener in order to receive status change updates.
+func (c *Client) AddStatusListener(listener StatusListener) {
+ c.statusListeners = append(c.statusListeners, listener)
+}
+
+// AddConnectionListener in order to receive connection updates.
+func (c *Client) AddConnectionListener(listener ConnectionListener) {
+ c.connectionListeners = append(c.connectionListeners, listener)
+}
+
+// status updates all status listeners with the new client status.
+func (c *Client) status(status int) {
+ if c.connectionStatus == status {
+ return
+ }
+ c.connectionStatus = status
+ for _, listener := range c.statusListeners {
+ listener.Status(status)
+ }
+}
+
+// Connect attempts to connect the client to the server.
+func (c *Client) Connect() error {
+ c.status(DIALING)
+ ws, err := websocket.Dial(c.url, "", c.origin)
+ if err != nil {
+ c.Close()
+ log.Println("Dial error", err)
+ c.reconnectLater()
+ return err
+ }
+ // Start DDP connection
+ c.start(ws, NewConnect())
+ return nil
+}
+
+// Reconnect attempts to reconnect the client to the server on the existing
+// DDP session.
+//
+// TODO needs a reconnect backoff so we don't trash a down server
+// TODO reconnect should not allow more reconnects while a reconnection is already in progress.
+func (c *Client) Reconnect() {
+ func() {
+ c.reconnectLock.Lock()
+ defer c.reconnectLock.Unlock()
+ if c.reconnectTimer != nil {
+ c.reconnectTimer.Stop()
+ c.reconnectTimer = nil
+ }
+ }()
+
+ c.Close()
+
+ c.reconnects++
+
+ // Reconnect
+ c.status(DIALING)
+ ws, err := websocket.Dial(c.url, "", c.origin)
+ if err != nil {
+ c.Close()
+ log.Println("Dial error", err)
+ c.reconnectLater()
+ return
+ }
+
+ c.start(ws, NewReconnect(c.session))
+
+ // --------------------------------------------------------------------
+ // We resume inflight or ongoing subscriptions - we don't have to wait
+ // for connection confirmation (messages can be pipelined).
+ // --------------------------------------------------------------------
+
+ // Send calls that haven't been confirmed - may not have been sent
+ // and effects should be idempotent
+ for _, call := range c.calls {
+ c.Send(NewMethod(call.ID, call.ServiceMethod, call.Args.([]interface{})))
+ }
+
+ // Resend subscriptions and patch up collections
+ for _, sub := range c.subs {
+ c.Send(NewSub(sub.ID, sub.ServiceMethod, sub.Args.([]interface{})))
+ }
+}
+
+// Subscribe subscribes to data updates.
+func (c *Client) Subscribe(subName string, done chan *Call, args ...interface{}) *Call {
+
+ if args == nil {
+ args = []interface{}{}
+ }
+ call := new(Call)
+ call.ID = c.newID()
+ call.ServiceMethod = subName
+ call.Args = args
+ call.Owner = c
+
+ if done == nil {
+ done = make(chan *Call, 10) // buffered.
+ } else {
+ // If caller passes done != nil, it must arrange that
+ // done has enough buffer for the number of simultaneous
+ // RPCs that will be using that channel. If the channel
+ // is totally unbuffered, it's best not to run at all.
+ if cap(done) == 0 {
+ log.Panic("ddp.rpc: done channel is unbuffered")
+ }
+ }
+ call.Done = done
+ c.subs[call.ID] = call
+
+ // Save this subscription to the client so we can reconnect
+ subArgs := make([]interface{}, len(args))
+ copy(subArgs, args)
+
+ c.Send(NewSub(call.ID, subName, args))
+
+ return call
+}
+
+// Sub sends a synchronous subscription request to the server.
+func (c *Client) Sub(subName string, args ...interface{}) error {
+ call := <-c.Subscribe(subName, make(chan *Call, 1), args...).Done
+ return call.Error
+}
+
+// Go invokes the function asynchronously. It returns the Call structure representing
+// the invocation. The done channel will signal when the call is complete by returning
+// the same Call object. If done is nil, Go will allocate a new channel.
+// If non-nil, done must be buffered or Go will deliberately crash.
+//
+// Go and Call are modeled after the standard `net/rpc` package versions.
+func (c *Client) Go(serviceMethod string, done chan *Call, args ...interface{}) *Call {
+
+ if args == nil {
+ args = []interface{}{}
+ }
+ call := new(Call)
+ call.ID = c.newID()
+ call.ServiceMethod = serviceMethod
+ call.Args = args
+ call.Owner = c
+ if done == nil {
+ done = make(chan *Call, 10) // buffered.
+ } else {
+ // If caller passes done != nil, it must arrange that
+ // done has enough buffer for the number of simultaneous
+ // RPCs that will be using that channel. If the channel
+ // is totally unbuffered, it's best not to run at all.
+ if cap(done) == 0 {
+ log.Panic("ddp.rpc: done channel is unbuffered")
+ }
+ }
+ call.Done = done
+ c.calls[call.ID] = call
+
+ c.Send(NewMethod(call.ID, serviceMethod, args))
+
+ return call
+}
+
+// Call invokes the named function, waits for it to complete, and returns its error status.
+func (c *Client) Call(serviceMethod string, args ...interface{}) (interface{}, error) {
+ call := <-c.Go(serviceMethod, make(chan *Call, 1), args...).Done
+ return call.Reply, call.Error
+}
+
+// Ping sends a heartbeat signal to the server. The Ping doesn't look for
+// a response but may trigger the connection to reconnect if the ping timesout.
+// This is primarily useful for reviving an unresponsive Client connection.
+func (c *Client) Ping() {
+ c.PingPong(c.newID(), c.HeartbeatTimeout, func(err error) {
+ if err != nil {
+ // Is there anything else we should or can do?
+ c.reconnectLater()
+ }
+ })
+}
+
+// PingPong sends a heartbeat signal to the server and calls the provided
+// function when a pong is received. An optional id can be sent to help
+// track the responses - or an empty string can be used. It is the
+// responsibility of the caller to respond to any errors that may occur.
+func (c *Client) PingPong(id string, timeout time.Duration, handler func(error)) {
+ err := c.Send(NewPing(id))
+ if err != nil {
+ handler(err)
+ return
+ }
+ c.pingsOut++
+ pings, ok := c.pings[id]
+ if !ok {
+ pings = make([]*pingTracker, 0, 5)
+ }
+ tracker := &pingTracker{handler: handler, timeout: timeout, timer: time.AfterFunc(timeout, func() {
+ handler(fmt.Errorf("ping timeout"))
+ })}
+ c.pings[id] = append(pings, tracker)
+}
+
+// Send transmits messages to the server. The msg parameter must be json
+// encoder compatible.
+func (c *Client) Send(msg interface{}) error {
+ return c.encoder.Encode(msg)
+}
+
+// Close implements the io.Closer interface.
+func (c *Client) Close() {
+ // Shutdown out all outstanding pings
+ if c.pingTimer != nil {
+ c.pingTimer.Stop()
+ c.pingTimer = nil
+ }
+
+ // Close websocket
+ if c.ws != nil {
+ c.ws.Close()
+ c.ws = nil
+ }
+ for _, collection := range c.collections {
+ collection.reset()
+ }
+ c.status(DISCONNECTED)
+}
+
+// ResetStats resets the statistics for the client.
+func (c *Client) ResetStats() {
+ c.readSocketStats.Reset()
+ c.readStats.Reset()
+ c.writeSocketStats.Reset()
+ c.writeStats.Reset()
+ c.reconnects = 0
+ c.pingsIn = 0
+ c.pingsOut = 0
+}
+
+// Stats returns the read and write statistics of the client.
+func (c *Client) Stats() *ClientStats {
+ return &ClientStats{
+ Reads: c.readSocketStats.Snapshot(),
+ TotalReads: c.readStats.Snapshot(),
+ Writes: c.writeSocketStats.Snapshot(),
+ TotalWrites: c.writeStats.Snapshot(),
+ Reconnects: c.reconnects,
+ PingsSent: c.pingsOut,
+ PingsRecv: c.pingsIn,
+ }
+}
+
+// SocketLogActive returns the current logging status for the socket.
+func (c *Client) SocketLogActive() bool {
+ return c.writeLog.Active
+}
+
+// SetSocketLogActive to true to enable logging of raw socket data.
+func (c *Client) SetSocketLogActive(active bool) {
+ c.writeLog.Active = active
+ c.readLog.Active = active
+}
+
+// CollectionByName retrieves a collection by it's name.
+func (c *Client) CollectionByName(name string) Collection {
+ collection, ok := c.collections[name]
+ if !ok {
+ collection = NewCollection(name)
+ c.collections[name] = collection
+ }
+ return collection
+}
+
+// CollectionStats returns a snapshot of statistics for the currently known collections.
+func (c *Client) CollectionStats() []CollectionStats {
+ stats := make([]CollectionStats, 0, len(c.collections))
+ for name, collection := range c.collections {
+ stats = append(stats, CollectionStats{Name: name, Count: len(collection.FindAll())})
+ }
+ return stats
+}
+
+// start starts a new client connection on the provided websocket
+func (c *Client) start(ws *websocket.Conn, connect *Connect) {
+
+ c.status(CONNECTING)
+
+ c.ws = ws
+ c.writeLog.SetWriter(ws)
+ c.writeSocketStats = NewWriterStats(c.writeLog)
+ c.writeStats.SetWriter(c.writeSocketStats)
+ c.readLog.SetReader(ws)
+ c.readSocketStats = NewReaderStats(c.readLog)
+ c.readStats.SetReader(c.readSocketStats)
+
+ // We spin off an inbox stuffing goroutine
+ go c.inboxWorker(c.readStats)
+
+ c.Send(connect)
+}
+
+// inboxManager pulls messages from the inbox and routes them to appropriate
+// handlers.
+func (c *Client) inboxManager() {
+ for {
+ select {
+ case msg := <-c.inbox:
+ // Message!
+ //log.Println("Got message", msg)
+ mtype, ok := msg["msg"]
+ if ok {
+ switch mtype.(string) {
+ // Connection management
+ case "connected":
+ c.status(CONNECTED)
+ for _, collection := range c.collections {
+ collection.init()
+ }
+ c.version = "1" // Currently the only version we support
+ c.session = msg["session"].(string)
+ // Start automatic heartbeats
+ c.pingTimer = time.AfterFunc(c.HeartbeatInterval, func() {
+ c.Ping()
+ c.pingTimer.Reset(c.HeartbeatInterval)
+ })
+ // Notify connection listeners
+ for _, listener := range c.connectionListeners {
+ go listener.Connected()
+ }
+ case "failed":
+ log.Fatalf("IM Failed to connect, we support version 1 but server supports %s", msg["version"])
+
+ // Heartbeats
+ case "ping":
+ // We received a ping - need to respond with a pong
+ id, ok := msg["id"]
+ if ok {
+ c.Send(NewPong(id.(string)))
+ } else {
+ c.Send(NewPong(""))
+ }
+ c.pingsIn++
+ case "pong":
+ // We received a pong - we can clear the ping tracker and call its handler
+ id, ok := msg["id"]
+ var key string
+ if ok {
+ key = id.(string)
+ }
+ pings, ok := c.pings[key]
+ if ok && len(pings) > 0 {
+ ping := pings[0]
+ pings = pings[1:]
+ if len(key) == 0 || len(pings) > 0 {
+ c.pings[key] = pings
+ }
+ ping.timer.Stop()
+ ping.handler(nil)
+ }
+
+ // Live Data
+ case "nosub":
+ log.Println("Subscription returned a nosub error", msg)
+ // Clear related subscriptions
+ sub, ok := msg["id"]
+ if ok {
+ id := sub.(string)
+ runningSub := c.subs[id]
+
+ if runningSub != nil {
+ runningSub.Error = errors.New("Subscription returned a nosub error")
+ runningSub.done()
+ delete(c.subs, id)
+ }
+ }
+ case "ready":
+ // Run 'done' callbacks on all ready subscriptions
+ subs, ok := msg["subs"]
+ if ok {
+ for _, sub := range subs.([]interface{}) {
+ call, ok := c.subs[sub.(string)]
+ if ok {
+ call.done()
+ }
+ }
+ }
+ case "added":
+ c.collectionBy(msg).added(msg)
+ case "changed":
+ c.collectionBy(msg).changed(msg)
+ case "removed":
+ c.collectionBy(msg).removed(msg)
+ case "addedBefore":
+ c.collectionBy(msg).addedBefore(msg)
+ case "movedBefore":
+ c.collectionBy(msg).movedBefore(msg)
+
+ // RPC
+ case "result":
+ id, ok := msg["id"]
+ if ok {
+ call := c.calls[id.(string)]
+ delete(c.calls, id.(string))
+ e, ok := msg["error"]
+ if ok {
+ txt, _ := json.Marshal(e)
+ call.Error = fmt.Errorf(string(txt))
+ call.Reply = e
+ } else {
+ call.Reply = msg["result"]
+ }
+ call.done()
+ }
+ case "updated":
+ // We currently don't do anything with updated status
+
+ default:
+ // Ignore?
+ log.Println("Server sent unexpected message", msg)
+ }
+ } else {
+ // Current Meteor server sends an undocumented DDP message
+ // (looks like clustering "hint"). We will register and
+ // ignore rather than log an error.
+ serverID, ok := msg["server_id"]
+ if ok {
+ switch ID := serverID.(type) {
+ case string:
+ c.serverID = ID
+ default:
+ log.Println("Server cluster node", serverID)
+ }
+ } else {
+ log.Println("Server sent message with no `msg` field", msg)
+ }
+ }
+ case err := <-c.errors:
+ log.Println("Websocket error", err)
+ }
+ }
+}
+
+func (c *Client) collectionBy(msg map[string]interface{}) Collection {
+ n, ok := msg["collection"]
+ if !ok {
+ return NewMockCollection()
+ }
+ switch name := n.(type) {
+ case string:
+ return c.CollectionByName(name)
+ default:
+ return NewMockCollection()
+ }
+}
+
+// inboxWorker pulls messages from a websocket, decodes JSON packets, and
+// stuffs them into a message channel.
+func (c *Client) inboxWorker(ws io.Reader) {
+ dec := json.NewDecoder(ws)
+ for {
+ var event interface{}
+
+ if err := dec.Decode(&event); err == io.EOF {
+ break
+ } else if err != nil {
+ c.errors <- err
+ }
+ if c.pingTimer != nil {
+ c.pingTimer.Reset(c.HeartbeatInterval)
+ }
+ if event == nil {
+ log.Println("Inbox worker found nil event. May be due to broken websocket. Reconnecting.")
+ break
+ } else {
+ c.inbox <- event.(map[string]interface{})
+ }
+ }
+
+ c.reconnectLater()
+}
+
+// reconnectLater schedules a reconnect for later. We need to make sure that we don't
+// block, and that we don't reconnect more frequently than once every c.ReconnectInterval
+func (c *Client) reconnectLater() {
+ c.Close()
+ c.reconnectLock.Lock()
+ defer c.reconnectLock.Unlock()
+ if c.reconnectTimer == nil {
+ c.reconnectTimer = time.AfterFunc(c.ReconnectInterval, c.Reconnect)
+ }
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_collection.go b/vendor/github.com/gopackage/ddp/ddp_collection.go
new file mode 100644
index 00000000..f417e68a
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_collection.go
@@ -0,0 +1,245 @@
+package ddp
+
+// ----------------------------------------------------------------------
+// Collection
+// ----------------------------------------------------------------------
+
+type Update map[string]interface{}
+type UpdateListener interface {
+ CollectionUpdate(collection, operation, id string, doc Update)
+}
+
+// Collection managed cached collection data sent from the server in a
+// livedata subscription.
+//
+// It would be great to build an entire mongo compatible local store (minimongo)
+type Collection interface {
+
+ // FindOne queries objects and returns the first match.
+ FindOne(id string) Update
+ // FindAll returns a map of all items in the cache - this is a hack
+ // until we have time to build out a real minimongo interface.
+ FindAll() map[string]Update
+ // AddUpdateListener adds a channel that receives update messages.
+ AddUpdateListener(listener UpdateListener)
+
+ // livedata updates
+ added(msg Update)
+ changed(msg Update)
+ removed(msg Update)
+ addedBefore(msg Update)
+ movedBefore(msg Update)
+ init() // init informs the collection that the connection to the server has begun/resumed
+ reset() // reset informs the collection that the connection to the server has been lost
+}
+
+// NewMockCollection creates an empty collection that does nothing.
+func NewMockCollection() Collection {
+ return &MockCache{}
+}
+
+// NewCollection creates a new collection - always KeyCache.
+func NewCollection(name string) Collection {
+ return &KeyCache{name, make(map[string]Update), nil}
+}
+
+// KeyCache caches items keyed on unique ID.
+type KeyCache struct {
+ // The name of the collection
+ Name string
+ // items contains collection items by ID
+ items map[string]Update
+ // listeners contains all the listeners that should be notified of collection updates.
+ listeners []UpdateListener
+ // TODO(badslug): do we need to protect from multiple threads
+}
+
+func (c *KeyCache) added(msg Update) {
+ id, fields := parseUpdate(msg)
+ if fields != nil {
+ c.items[id] = fields
+ c.notify("create", id, fields)
+ }
+}
+
+func (c *KeyCache) changed(msg Update) {
+ id, fields := parseUpdate(msg)
+ if fields != nil {
+ item, ok := c.items[id]
+ if ok {
+ for key, value := range fields {
+ item[key] = value
+ }
+ c.items[id] = item
+ c.notify("update", id, item)
+ }
+ }
+}
+
+func (c *KeyCache) removed(msg Update) {
+ id, _ := parseUpdate(msg)
+ if len(id) > 0 {
+ delete(c.items, id)
+ c.notify("remove", id, nil)
+ }
+}
+
+func (c *KeyCache) addedBefore(msg Update) {
+ // for keyed cache, ordered commands are a noop
+}
+
+func (c *KeyCache) movedBefore(msg Update) {
+ // for keyed cache, ordered commands are a noop
+}
+
+// init prepares the collection for data updates (called when a new connection is
+// made or a connection/session is resumed).
+func (c *KeyCache) init() {
+ // TODO start to patch up the current data with fresh server state
+}
+
+func (c *KeyCache) reset() {
+ // TODO we should mark the collection but maintain it's contents and then
+ // patch up the current contents with the new contents when we receive them.
+ //c.items = nil
+ c.notify("reset", "", nil)
+}
+
+// notify sends a Update to all UpdateListener's which should never block.
+func (c *KeyCache) notify(operation, id string, doc Update) {
+ for _, listener := range c.listeners {
+ listener.CollectionUpdate(c.Name, operation, id, doc)
+ }
+}
+
+// FindOne returns the item with matching id.
+func (c *KeyCache) FindOne(id string) Update {
+ return c.items[id]
+}
+
+// FindAll returns a dump of all items in the collection
+func (c *KeyCache) FindAll() map[string]Update {
+ return c.items
+}
+
+// AddUpdateListener adds a listener for changes on a collection.
+func (c *KeyCache) AddUpdateListener(listener UpdateListener) {
+ c.listeners = append(c.listeners, listener)
+}
+
+// OrderedCache caches items based on list order.
+// This is a placeholder, currently not implemented as the Meteor server
+// does not transmit ordered collections over DDP yet.
+type OrderedCache struct {
+ // ranks contains ordered collection items for ordered collections
+ items []interface{}
+}
+
+func (c *OrderedCache) added(msg Update) {
+ // for ordered cache, key commands are a noop
+}
+
+func (c *OrderedCache) changed(msg Update) {
+
+}
+
+func (c *OrderedCache) removed(msg Update) {
+
+}
+
+func (c *OrderedCache) addedBefore(msg Update) {
+
+}
+
+func (c *OrderedCache) movedBefore(msg Update) {
+
+}
+
+func (c *OrderedCache) init() {
+
+}
+
+func (c *OrderedCache) reset() {
+
+}
+
+// FindOne returns the item with matching id.
+func (c *OrderedCache) FindOne(id string) Update {
+ return nil
+}
+
+// FindAll returns a dump of all items in the collection
+func (c *OrderedCache) FindAll() map[string]Update {
+ return map[string]Update{}
+}
+
+// AddUpdateListener does nothing.
+func (c *OrderedCache) AddUpdateListener(ch UpdateListener) {
+}
+
+// MockCache implements the Collection interface but does nothing with the data.
+type MockCache struct {
+}
+
+func (c *MockCache) added(msg Update) {
+
+}
+
+func (c *MockCache) changed(msg Update) {
+
+}
+
+func (c *MockCache) removed(msg Update) {
+
+}
+
+func (c *MockCache) addedBefore(msg Update) {
+
+}
+
+func (c *MockCache) movedBefore(msg Update) {
+
+}
+
+func (c *MockCache) init() {
+
+}
+
+func (c *MockCache) reset() {
+
+}
+
+// FindOne returns the item with matching id.
+func (c *MockCache) FindOne(id string) Update {
+ return nil
+}
+
+// FindAll returns a dump of all items in the collection
+func (c *MockCache) FindAll() map[string]Update {
+ return map[string]Update{}
+}
+
+// AddUpdateListener does nothing.
+func (c *MockCache) AddUpdateListener(ch UpdateListener) {
+}
+
+// parseUpdate returns the ID and fields from a DDP Update document.
+func parseUpdate(up Update) (ID string, Fields Update) {
+ key, ok := up["id"]
+ if ok {
+ switch id := key.(type) {
+ case string:
+ updates, ok := up["fields"]
+ if ok {
+ switch fields := updates.(type) {
+ case map[string]interface{}:
+ return id, Update(fields)
+ default:
+ // Don't know what to do...
+ }
+ }
+ return id, nil
+ }
+ }
+ return "", nil
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_ejson.go b/vendor/github.com/gopackage/ddp/ddp_ejson.go
new file mode 100644
index 00000000..a3e1fec0
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_ejson.go
@@ -0,0 +1,217 @@
+package ddp
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "strings"
+ "time"
+)
+
+// ----------------------------------------------------------------------
+// EJSON document interface
+// ----------------------------------------------------------------------
+// https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md#appendix-ejson
+
+// Doc provides hides the complexity of ejson documents.
+type Doc struct {
+ root interface{}
+}
+
+// NewDoc creates a new document from a generic json parsed document.
+func NewDoc(in interface{}) *Doc {
+ doc := &Doc{in}
+ return doc
+}
+
+// Map locates a map[string]interface{} - json object - at a path
+// or returns nil if not found.
+func (d *Doc) Map(path string) map[string]interface{} {
+ item := d.Item(path)
+ if item != nil {
+ switch m := item.(type) {
+ case map[string]interface{}:
+ return m
+ default:
+ return nil
+ }
+ }
+ return nil
+}
+
+// Array locates an []interface{} - json array - at a path
+// or returns nil if not found.
+func (d *Doc) Array(path string) []interface{} {
+ item := d.Item(path)
+ if item != nil {
+ switch m := item.(type) {
+ case []interface{}:
+ return m
+ default:
+ return nil
+ }
+ }
+ return nil
+}
+
+// StringArray locates an []string - json array of strings - at a path
+// or returns nil if not found. The string array will contain all string values
+// in the array and skip any non-string entries.
+func (d *Doc) StringArray(path string) []string {
+ item := d.Item(path)
+ if item != nil {
+ switch m := item.(type) {
+ case []interface{}:
+ items := []string{}
+ for _, item := range m {
+ switch val := item.(type) {
+ case string:
+ items = append(items, val)
+ }
+ }
+ return items
+ case []string:
+ return m
+ default:
+ return nil
+ }
+ }
+ return nil
+}
+
+// String returns a string value located at the path or an empty string if not found.
+func (d *Doc) String(path string) string {
+ item := d.Item(path)
+ if item != nil {
+ switch m := item.(type) {
+ case string:
+ return m
+ default:
+ return ""
+ }
+ }
+ return ""
+}
+
+// Bool returns a boolean value located at the path or false if not found.
+func (d *Doc) Bool(path string) bool {
+ item := d.Item(path)
+ if item != nil {
+ switch m := item.(type) {
+ case bool:
+ return m
+ default:
+ return false
+ }
+ }
+ return false
+}
+
+// Float returns a float64 value located at the path or zero if not found.
+func (d *Doc) Float(path string) float64 {
+ item := d.Item(path)
+ if item != nil {
+ switch m := item.(type) {
+ case float64:
+ return m
+ default:
+ return 0
+ }
+ }
+ return 0
+}
+
+// Time returns a time value located at the path or nil if not found.
+func (d *Doc) Time(path string) time.Time {
+ ticks := d.Float(path + ".$date")
+ var t time.Time
+ if ticks > 0 {
+ sec := int64(ticks / 1000)
+ t = time.Unix(int64(sec), 0)
+ }
+ return t
+}
+
+// Item locates a "raw" item at the provided path, returning
+// the item found or nil if not found.
+func (d *Doc) Item(path string) interface{} {
+ item := d.root
+ steps := strings.Split(path, ".")
+ for _, step := range steps {
+ // This is an intermediate step - we must be in a map
+ switch m := item.(type) {
+ case map[string]interface{}:
+ item = m[step]
+ case Update:
+ item = m[step]
+ default:
+ return nil
+ }
+ }
+ return item
+}
+
+// Set a value for a path. Intermediate items are created as necessary.
+func (d *Doc) Set(path string, value interface{}) {
+ item := d.root
+ steps := strings.Split(path, ".")
+ last := steps[len(steps)-1]
+ steps = steps[:len(steps)-1]
+ for _, step := range steps {
+ // This is an intermediate step - we must be in a map
+ switch m := item.(type) {
+ case map[string]interface{}:
+ item = m[step]
+ if item == nil {
+ item = map[string]interface{}{}
+ m[step] = item
+ }
+ default:
+ return
+ }
+ }
+ // Item is now the last map so we just set the value
+ switch m := item.(type) {
+ case map[string]interface{}:
+ m[last] = value
+ }
+}
+
+// Accounts password login support
+type Login struct {
+ User *User `json:"user"`
+ Password *Password `json:"password"`
+}
+
+func NewEmailLogin(email, pass string) *Login {
+ return &Login{User: &User{Email: email}, Password: NewPassword(pass)}
+}
+
+func NewUsernameLogin(user, pass string) *Login {
+ return &Login{User: &User{Username: user}, Password: NewPassword(pass)}
+}
+
+type LoginResume struct {
+ Token string `json:"resume"`
+}
+
+func NewLoginResume(token string) *LoginResume {
+ return &LoginResume{Token: token}
+}
+
+type User struct {
+ Email string `json:"email,omitempty"`
+ Username string `json:"username,omitempty"`
+}
+
+type Password struct {
+ Digest string `json:"digest"`
+ Algorithm string `json:"algorithm"`
+}
+
+func NewPassword(pass string) *Password {
+ sha := sha256.New()
+ io.WriteString(sha, pass)
+ digest := sha.Sum(nil)
+ return &Password{Digest: hex.EncodeToString(digest), Algorithm: "sha-256"}
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_messages.go b/vendor/github.com/gopackage/ddp/ddp_messages.go
new file mode 100644
index 00000000..68c9eab4
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_messages.go
@@ -0,0 +1,82 @@
+package ddp
+
+// ------------------------------------------------------------
+// DDP Messages
+//
+// Go structs representing DDP raw messages ready for JSON
+// encoding.
+// ------------------------------------------------------------
+
+// Message contains the common fields that all DDP messages use.
+type Message struct {
+ Type string `json:"msg"`
+ ID string `json:"id,omitempty"`
+}
+
+// Connect represents a DDP connect message.
+type Connect struct {
+ Message
+ Version string `json:"version"`
+ Support []string `json:"support"`
+ Session string `json:"session,omitempty"`
+}
+
+// NewConnect creates a new connect message
+func NewConnect() *Connect {
+ return &Connect{Message: Message{Type: "connect"}, Version: "1", Support: []string{"1"}}
+}
+
+// NewReconnect creates a new connect message with a session ID to resume.
+func NewReconnect(session string) *Connect {
+ c := NewConnect()
+ c.Session = session
+ return c
+}
+
+// Ping represents a DDP ping message.
+type Ping Message
+
+// NewPing creates a new ping message with optional ID.
+func NewPing(id string) *Ping {
+ return &Ping{Type: "ping", ID: id}
+}
+
+// Pong represents a DDP pong message.
+type Pong Message
+
+// NewPong creates a new pong message with optional ID.
+func NewPong(id string) *Pong {
+ return &Pong{Type: "pong", ID: id}
+}
+
+// Method is used to send a remote procedure call to the server.
+type Method struct {
+ Message
+ ServiceMethod string `json:"method"`
+ Args []interface{} `json:"params"`
+}
+
+// NewMethod creates a new method invocation object.
+func NewMethod(id, serviceMethod string, args []interface{}) *Method {
+ return &Method{
+ Message: Message{Type: "method", ID: id},
+ ServiceMethod: serviceMethod,
+ Args: args,
+ }
+}
+
+// Sub is used to send a subscription request to the server.
+type Sub struct {
+ Message
+ SubName string `json:"name"`
+ Args []interface{} `json:"params"`
+}
+
+// NewSub creates a new sub object.
+func NewSub(id, subName string, args []interface{}) *Sub {
+ return &Sub{
+ Message: Message{Type: "sub", ID: id},
+ SubName: subName,
+ Args: args,
+ }
+}
diff --git a/vendor/github.com/gopackage/ddp/ddp_stats.go b/vendor/github.com/gopackage/ddp/ddp_stats.go
new file mode 100644
index 00000000..1546b547
--- /dev/null
+++ b/vendor/github.com/gopackage/ddp/ddp_stats.go
@@ -0,0 +1,321 @@
+package ddp
+
+import (
+ "encoding/hex"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "sync"
+ "time"
+)
+
+// Gather statistics about a DDP connection.
+
+// ---------------------------------------------------------
+// io utilities
+//
+// This is generic - should be moved into a stand alone lib
+// ---------------------------------------------------------
+
+// ReaderProxy provides common tooling for structs that manage an io.Reader.
+type ReaderProxy struct {
+ reader io.Reader
+}
+
+// NewReaderProxy creates a new proxy for the provided reader.
+func NewReaderProxy(reader io.Reader) *ReaderProxy {
+ return &ReaderProxy{reader}
+}
+
+// SetReader sets the reader on the proxy.
+func (r *ReaderProxy) SetReader(reader io.Reader) {
+ r.reader = reader
+}
+
+// WriterProxy provides common tooling for structs that manage an io.Writer.
+type WriterProxy struct {
+ writer io.Writer
+}
+
+// NewWriterProxy creates a new proxy for the provided writer.
+func NewWriterProxy(writer io.Writer) *WriterProxy {
+ return &WriterProxy{writer}
+}
+
+// SetWriter sets the writer on the proxy.
+func (w *WriterProxy) SetWriter(writer io.Writer) {
+ w.writer = writer
+}
+
+// Logging data types
+const (
+ DataByte = iota // data is raw []byte
+ DataText // data is string data
+)
+
+// Logger logs data from i/o sources.
+type Logger struct {
+ // Active is true if the logger should be logging reads
+ Active bool
+ // Truncate is >0 to indicate the number of characters to truncate output
+ Truncate int
+
+ logger *log.Logger
+ dtype int
+}
+
+// NewLogger creates a new i/o logger.
+func NewLogger(logger *log.Logger, active bool, dataType int, truncate int) Logger {
+ return Logger{logger: logger, Active: active, dtype: dataType, Truncate: truncate}
+}
+
+// Log logs the current i/o operation and returns the read and error for
+// easy call chaining.
+func (l *Logger) Log(p []byte, n int, err error) (int, error) {
+ if l.Active && err == nil {
+ limit := n
+ truncated := false
+ if l.Truncate > 0 && l.Truncate < limit {
+ limit = l.Truncate
+ truncated = true
+ }
+ switch l.dtype {
+ case DataText:
+ if truncated {
+ l.logger.Printf("[%d] %s...", n, string(p[:limit]))
+ } else {
+ l.logger.Printf("[%d] %s", n, string(p[:limit]))
+ }
+ case DataByte:
+ fallthrough
+ default:
+ l.logger.Println(hex.Dump(p[:limit]))
+ }
+ }
+ return n, err
+}
+
+// ReaderLogger logs data from any io.Reader.
+// ReaderLogger wraps a Reader and passes data to the actual data consumer.
+type ReaderLogger struct {
+ Logger
+ ReaderProxy
+}
+
+// NewReaderDataLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewReaderDataLogger(reader io.Reader) *ReaderLogger {
+ logger := log.New(os.Stdout, "<- ", log.LstdFlags)
+ return NewReaderLogger(reader, logger, true, DataByte, 0)
+}
+
+// NewReaderTextLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewReaderTextLogger(reader io.Reader) *ReaderLogger {
+ logger := log.New(os.Stdout, "<- ", log.LstdFlags)
+ return NewReaderLogger(reader, logger, true, DataText, 80)
+}
+
+// NewReaderLogger creates a Reader logger for the provided parameters.
+func NewReaderLogger(reader io.Reader, logger *log.Logger, active bool, dataType int, truncate int) *ReaderLogger {
+ return &ReaderLogger{ReaderProxy: *NewReaderProxy(reader), Logger: NewLogger(logger, active, dataType, truncate)}
+}
+
+// Read logs the read bytes and passes them to the wrapped reader.
+func (r *ReaderLogger) Read(p []byte) (int, error) {
+ n, err := r.reader.Read(p)
+ return r.Log(p, n, err)
+}
+
+// WriterLogger logs data from any io.Writer.
+// WriterLogger wraps a Writer and passes data to the actual data producer.
+type WriterLogger struct {
+ Logger
+ WriterProxy
+}
+
+// NewWriterDataLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewWriterDataLogger(writer io.Writer) *WriterLogger {
+ logger := log.New(os.Stdout, "-> ", log.LstdFlags)
+ return NewWriterLogger(writer, logger, true, DataByte, 0)
+}
+
+// NewWriterTextLogger creates an active binary data logger with a default
+// log.Logger and a '->' prefix.
+func NewWriterTextLogger(writer io.Writer) *WriterLogger {
+ logger := log.New(os.Stdout, "-> ", log.LstdFlags)
+ return NewWriterLogger(writer, logger, true, DataText, 80)
+}
+
+// NewWriterLogger creates a Reader logger for the provided parameters.
+func NewWriterLogger(writer io.Writer, logger *log.Logger, active bool, dataType int, truncate int) *WriterLogger {
+ return &WriterLogger{WriterProxy: *NewWriterProxy(writer), Logger: NewLogger(logger, active, dataType, truncate)}
+}
+
+// Write logs the written bytes and passes them to the wrapped writer.
+func (w *WriterLogger) Write(p []byte) (int, error) {
+ if w.writer != nil {
+ n, err := w.writer.Write(p)
+ return w.Log(p, n, err)
+ }
+ return 0, nil
+}
+
+// Stats tracks statistics for i/o operations. Stats are produced from a
+// of a running stats agent.
+type Stats struct {
+ // Bytes is the total number of bytes transferred.
+ Bytes int64
+ // Ops is the total number of i/o operations performed.
+ Ops int64
+ // Errors is the total number of i/o errors encountered.
+ Errors int64
+ // Runtime is the duration that stats have been gathered.
+ Runtime time.Duration
+}
+
+// ClientStats displays combined statistics for the Client.
+type ClientStats struct {
+ // Reads provides statistics on the raw i/o network reads for the current connection.
+ Reads *Stats
+ // Reads provides statistics on the raw i/o network reads for the all client connections.
+ TotalReads *Stats
+ // Writes provides statistics on the raw i/o network writes for the current connection.
+ Writes *Stats
+ // Writes provides statistics on the raw i/o network writes for all the client connections.
+ TotalWrites *Stats
+ // Reconnects is the number of reconnections the client has made.
+ Reconnects int64
+ // PingsSent is the number of pings sent by the client
+ PingsSent int64
+ // PingsRecv is the number of pings received by the client
+ PingsRecv int64
+}
+
+// String produces a compact string representation of the client stats.
+func (stats *ClientStats) String() string {
+ i := stats.Reads
+ ti := stats.TotalReads
+ o := stats.Writes
+ to := stats.TotalWrites
+ totalRun := (ti.Runtime * 1000000) / 1000000
+ run := (i.Runtime * 1000000) / 1000000
+ return fmt.Sprintf("bytes: %d/%d##%d/%d ops: %d/%d##%d/%d err: %d/%d##%d/%d reconnects: %d pings: %d/%d uptime: %v##%v",
+ i.Bytes, o.Bytes,
+ ti.Bytes, to.Bytes,
+ i.Ops, o.Ops,
+ ti.Ops, to.Ops,
+ i.Errors, o.Errors,
+ ti.Errors, to.Errors,
+ stats.Reconnects,
+ stats.PingsRecv, stats.PingsSent,
+ run, totalRun)
+}
+
+// CollectionStats combines statistics about a collection.
+type CollectionStats struct {
+ Name string // Name of the collection
+ Count int // Count is the total number of documents in the collection
+}
+
+// String produces a compact string representation of the collection stat.
+func (s *CollectionStats) String() string {
+ return fmt.Sprintf("%s[%d]", s.Name, s.Count)
+}
+
+// StatsTracker provides the basic tooling for tracking i/o stats.
+type StatsTracker struct {
+ bytes int64
+ ops int64
+ errors int64
+ start time.Time
+ lock *sync.Mutex
+}
+
+// NewStatsTracker create a new stats tracker with start time set to now.
+func NewStatsTracker() *StatsTracker {
+ return &StatsTracker{start: time.Now(), lock: new(sync.Mutex)}
+}
+
+// Op records an i/o operation. The parameters are passed through to
+// allow easy chaining.
+func (t *StatsTracker) Op(n int, err error) (int, error) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+ t.ops++
+ if err == nil {
+ t.bytes += int64(n)
+ } else {
+ if err == io.EOF {
+ // I don't think we should log EOF stats as an error
+ } else {
+ t.errors++
+ }
+ }
+
+ return n, err
+}
+
+// Snapshot takes a snapshot of the current reader statistics.
+func (t *StatsTracker) Snapshot() *Stats {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+ return t.snap()
+}
+
+// Reset sets all of the stats to initial values.
+func (t *StatsTracker) Reset() *Stats {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ stats := t.snap()
+ t.bytes = 0
+ t.ops = 0
+ t.errors = 0
+ t.start = time.Now()
+
+ return stats
+}
+
+func (t *StatsTracker) snap() *Stats {
+ return &Stats{Bytes: t.bytes, Ops: t.ops, Errors: t.errors, Runtime: time.Since(t.start)}
+}
+
+// ReaderStats tracks statistics on any io.Reader.
+// ReaderStats wraps a Reader and passes data to the actual data consumer.
+type ReaderStats struct {
+ StatsTracker
+ ReaderProxy
+}
+
+// NewReaderStats creates a ReaderStats object for the provided reader.
+func NewReaderStats(reader io.Reader) *ReaderStats {
+ return &ReaderStats{ReaderProxy: *NewReaderProxy(reader), StatsTracker: *NewStatsTracker()}
+}
+
+// Read passes through a read collecting statistics and logging activity.
+func (r *ReaderStats) Read(p []byte) (int, error) {
+ return r.Op(r.reader.Read(p))
+}
+
+// WriterStats tracks statistics on any io.Writer.
+// WriterStats wraps a Writer and passes data to the actual data producer.
+type WriterStats struct {
+ StatsTracker
+ WriterProxy
+}
+
+// NewWriterStats creates a WriterStats object for the provided writer.
+func NewWriterStats(writer io.Writer) *WriterStats {
+ return &WriterStats{WriterProxy: *NewWriterProxy(writer), StatsTracker: *NewStatsTracker()}
+}
+
+// Write passes through a write collecting statistics.
+func (w *WriterStats) Write(p []byte) (int, error) {
+ if w.writer != nil {
+ return w.Op(w.writer.Write(p))
+ }
+ return 0, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
new file mode 100644
index 00000000..c6579ece
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
@@ -0,0 +1,39 @@
+package models
+
+import "time"
+
+type Channel struct {
+ ID string `json:"_id"`
+ Name string `json:"name"`
+ Fname string `json:"fname,omitempty"`
+ Type string `json:"t"`
+ Msgs int `json:"msgs"`
+
+ ReadOnly bool `json:"ro,omitempty"`
+ SysMes bool `json:"sysMes,omitempty"`
+ Default bool `json:"default"`
+ Broadcast bool `json:"broadcast,omitempty"`
+
+ Timestamp *time.Time `json:"ts,omitempty"`
+ UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
+
+ User *User `json:"u,omitempty"`
+ LastMessage *Message `json:"lastMessage,omitempty"`
+
+ // Lm interface{} `json:"lm"`
+ // CustomFields struct {
+ // } `json:"customFields,omitempty"`
+}
+
+type ChannelSubscription struct {
+ ID string `json:"_id"`
+ Alert bool `json:"alert"`
+ Name string `json:"name"`
+ DisplayName string `json:"fname"`
+ Open bool `json:"open"`
+ RoomId string `json:"rid"`
+ Type string `json:"c"`
+ User User `json:"u"`
+ Roles []string `json:"roles"`
+ Unread float64 `json:"unread"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
new file mode 100644
index 00000000..fb99e7c2
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
@@ -0,0 +1,133 @@
+package models
+
+import "time"
+
+type Info struct {
+ Version string `json:"version"`
+
+ Build struct {
+ NodeVersion string `json:"nodeVersion"`
+ Arch string `json:"arch"`
+ Platform string `json:"platform"`
+ Cpus int `json:"cpus"`
+ } `json:"build"`
+
+ Commit struct {
+ Hash string `json:"hash"`
+ Date string `json:"date"`
+ Author string `json:"author"`
+ Subject string `json:"subject"`
+ Tag string `json:"tag"`
+ Branch string `json:"branch"`
+ } `json:"commit"`
+}
+
+type Pagination struct {
+ Count int `json:"count"`
+ Offset int `json:"offset"`
+ Total int `json:"total"`
+}
+
+type Directory struct {
+ Result []struct {
+ ID string `json:"_id"`
+ CreatedAt time.Time `json:"createdAt"`
+ Emails []struct {
+ Address string `json:"address"`
+ Verified bool `json:"verified"`
+ } `json:"emails"`
+ Name string `json:"name"`
+ Username string `json:"username"`
+ } `json:"result"`
+
+ Pagination
+}
+
+type Spotlight struct {
+ Users []User `json:"users"`
+ Rooms []Channel `json:"rooms"`
+}
+
+type Statistics struct {
+ ID string `json:"_id"`
+ UniqueID string `json:"uniqueId"`
+ Version string `json:"version"`
+
+ ActiveUsers int `json:"activeUsers"`
+ NonActiveUsers int `json:"nonActiveUsers"`
+ OnlineUsers int `json:"onlineUsers"`
+ AwayUsers int `json:"awayUsers"`
+ OfflineUsers int `json:"offlineUsers"`
+ TotalUsers int `json:"totalUsers"`
+
+ TotalRooms int `json:"totalRooms"`
+ TotalChannels int `json:"totalChannels"`
+ TotalPrivateGroups int `json:"totalPrivateGroups"`
+ TotalDirect int `json:"totalDirect"`
+ TotlalLivechat int `json:"totlalLivechat"`
+ TotalMessages int `json:"totalMessages"`
+ TotalChannelMessages int `json:"totalChannelMessages"`
+ TotalPrivateGroupMessages int `json:"totalPrivateGroupMessages"`
+ TotalDirectMessages int `json:"totalDirectMessages"`
+ TotalLivechatMessages int `json:"totalLivechatMessages"`
+
+ InstalledAt time.Time `json:"installedAt"`
+ LastLogin time.Time `json:"lastLogin"`
+ LastMessageSentAt time.Time `json:"lastMessageSentAt"`
+ LastSeenSubscription time.Time `json:"lastSeenSubscription"`
+
+ Os struct {
+ Type string `json:"type"`
+ Platform string `json:"platform"`
+ Arch string `json:"arch"`
+ Release string `json:"release"`
+ Uptime int `json:"uptime"`
+ Loadavg []float64 `json:"loadavg"`
+ Totalmem int64 `json:"totalmem"`
+ Freemem int `json:"freemem"`
+ Cpus []struct {
+ Model string `json:"model"`
+ Speed int `json:"speed"`
+ Times struct {
+ User int `json:"user"`
+ Nice int `json:"nice"`
+ Sys int `json:"sys"`
+ Idle int `json:"idle"`
+ Irq int `json:"irq"`
+ } `json:"times"`
+ } `json:"cpus"`
+ } `json:"os"`
+
+ Process struct {
+ NodeVersion string `json:"nodeVersion"`
+ Pid int `json:"pid"`
+ Uptime float64 `json:"uptime"`
+ } `json:"process"`
+
+ Deploy struct {
+ Method string `json:"method"`
+ Platform string `json:"platform"`
+ } `json:"deploy"`
+
+ Migration struct {
+ ID string `json:"_id"`
+ Version int `json:"version"`
+ Locked bool `json:"locked"`
+ LockedAt time.Time `json:"lockedAt"`
+ BuildAt time.Time `json:"buildAt"`
+ } `json:"migration"`
+
+ InstanceCount int `json:"instanceCount"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"_updatedAt"`
+}
+
+type StatisticsInfo struct {
+ Statistics Statistics `json:"statistics"`
+}
+
+type StatisticsList struct {
+ Statistics []Statistics `json:"statistics"`
+
+ Pagination
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
new file mode 100644
index 00000000..8be3e3b6
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
@@ -0,0 +1,75 @@
+package models
+
+import "time"
+
+type Message struct {
+ ID string `json:"_id"`
+ RoomID string `json:"rid"`
+ Msg string `json:"msg"`
+ EditedBy string `json:"editedBy,omitempty"`
+
+ Groupable bool `json:"groupable,omitempty"`
+
+ EditedAt *time.Time `json:"editedAt,omitempty"`
+ Timestamp *time.Time `json:"ts,omitempty"`
+ UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
+
+ Mentions []User `json:"mentions,omitempty"`
+ User *User `json:"u,omitempty"`
+ PostMessage
+
+ // Bot interface{} `json:"bot"`
+ // CustomFields interface{} `json:"customFields"`
+ // Channels []interface{} `json:"channels"`
+ // SandstormSessionID interface{} `json:"sandstormSessionId"`
+}
+
+// PostMessage Payload for postmessage rest API
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
+type PostMessage struct {
+ RoomID string `json:"roomId,omitempty"`
+ Channel string `json:"channel,omitempty"`
+ Text string `json:"text,omitempty"`
+ ParseUrls bool `json:"parseUrls,omitempty"`
+ Alias string `json:"alias,omitempty"`
+ Emoji string `json:"emoji,omitempty"`
+ Avatar string `json:"avatar,omitempty"`
+ Attachments []Attachment `json:"attachments,omitempty"`
+}
+
+// Attachment Payload for postmessage rest API
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
+type Attachment struct {
+ Color string `json:"color,omitempty"`
+ Text string `json:"text,omitempty"`
+ Timestamp string `json:"ts,omitempty"`
+ ThumbURL string `json:"thumb_url,omitempty"`
+ MessageLink string `json:"message_link,omitempty"`
+ Collapsed bool `json:"collapsed"`
+
+ AuthorName string `json:"author_name,omitempty"`
+ AuthorLink string `json:"author_link,omitempty"`
+ AuthorIcon string `json:"author_icon,omitempty"`
+
+ Title string `json:"title,omitempty"`
+ TitleLink string `json:"title_link,omitempty"`
+ TitleLinkDownload string `json:"title_link_download,omitempty"`
+
+ ImageURL string `json:"image_url,omitempty"`
+
+ AudioURL string `json:"audio_url,omitempty"`
+ VideoURL string `json:"video_url,omitempty"`
+
+ Fields []AttachmentField `json:"fields,omitempty"`
+}
+
+// AttachmentField Payload for postmessage rest API
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
+type AttachmentField struct {
+ Short bool `json:"short"`
+ Title string `json:"title"`
+ Value string `json:"value"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
new file mode 100644
index 00000000..052bad8a
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
@@ -0,0 +1,7 @@
+package models
+
+type Permission struct {
+ ID string `json:"_id"`
+ UpdatedAt string `json:"_updatedAt.$date"`
+ Roles []string `json:"roles"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
new file mode 100644
index 00000000..aeacb385
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
@@ -0,0 +1,21 @@
+package models
+
+type Setting struct {
+ ID string `json:"_id"`
+ Blocked bool `json:"blocked"`
+ Group string `json:"group"`
+ Hidden bool `json:"hidden"`
+ Public bool `json:"public"`
+ Type string `json:"type"`
+ PackageValue string `json:"packageValue"`
+ Sorter int `json:"sorter"`
+ Value string `json:"value"`
+ ValueBool bool `json:"valueBool"`
+ ValueInt float64 `json:"valueInt"`
+ ValueSource string `json:"valueSource"`
+ ValueAsset Asset `json:"asset"`
+}
+
+type Asset struct {
+ DefaultUrl string `json:"defaultUrl"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
new file mode 100644
index 00000000..ee56bdc3
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
@@ -0,0 +1,29 @@
+package models
+
+type User struct {
+ ID string `json:"_id"`
+ Name string `json:"name"`
+ UserName string `json:"username"`
+ Status string `json:"status"`
+ Token string `json:"token"`
+ TokenExpires int64 `json:"tokenExpires"`
+}
+
+type CreateUserRequest struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Password string `json:"password"`
+ Username string `json:"username"`
+ CustomFields map[string]string `json:"customFields,omitempty"`
+}
+
+type UpdateUserRequest struct {
+ UserID string `json:"userId"`
+ Data struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Password string `json:"password"`
+ Username string `json:"username"`
+ CustomFields map[string]string `json:"customFields,omitempty"`
+ } `json:"data"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
new file mode 100644
index 00000000..296e26fb
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
@@ -0,0 +1,10 @@
+package models
+
+type UserCredentials struct {
+ ID string `json:"id"`
+ Token string `json:"token"`
+
+ Email string `json:"email"`
+ Name string `json:"name"`
+ Password string `json:"pass"`
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
new file mode 100644
index 00000000..5779cb38
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
@@ -0,0 +1,263 @@
+package realtime
+
+import (
+ "github.com/Jeffail/gabs"
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+func (c *Client) GetChannelId(name string) (string, error) {
+ rawResponse, err := c.ddp.Call("getRoomIdByNameOrId", name)
+ if err != nil {
+ return "", err
+ }
+
+ //log.Println(rawResponse)
+
+ return rawResponse.(string), nil
+}
+
+// GetChannelsIn returns list of channels
+// Optionally includes date to get all since last check or 0 to get all
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-rooms/
+func (c *Client) GetChannelsIn() ([]models.Channel, error) {
+ rawResponse, err := c.ddp.Call("rooms/get", map[string]int{
+ "$date": 0,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
+
+ chans, err := document.Children()
+
+ var channels []models.Channel
+
+ for _, i := range chans {
+ channels = append(channels, models.Channel{
+ ID: stringOrZero(i.Path("_id").Data()),
+ //Default: stringOrZero(i.Path("default").Data()),
+ Name: stringOrZero(i.Path("name").Data()),
+ Type: stringOrZero(i.Path("t").Data()),
+ })
+ }
+
+ return channels, nil
+}
+
+// GetChannelSubscriptions gets users channel subscriptions
+// Optionally includes date to get all since last check or 0 to get all
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-subscriptions
+func (c *Client) GetChannelSubscriptions() ([]models.ChannelSubscription, error) {
+ rawResponse, err := c.ddp.Call("subscriptions/get", map[string]int{
+ "$date": 0,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
+
+ channelSubs, err := document.Children()
+
+ var channelSubscriptions []models.ChannelSubscription
+
+ for _, sub := range channelSubs {
+ channelSubscription := models.ChannelSubscription{
+ ID: stringOrZero(sub.Path("_id").Data()),
+ Alert: sub.Path("alert").Data().(bool),
+ Name: stringOrZero(sub.Path("name").Data()),
+ DisplayName: stringOrZero(sub.Path("fname").Data()),
+ Open: sub.Path("open").Data().(bool),
+ Type: stringOrZero(sub.Path("t").Data()),
+ User: models.User{
+ ID: stringOrZero(sub.Path("u._id").Data()),
+ UserName: stringOrZero(sub.Path("u.username").Data()),
+ },
+ Unread: sub.Path("unread").Data().(float64),
+ }
+
+ if sub.Path("roles").Data() != nil {
+ var roles []string
+ for _, role := range sub.Path("roles").Data().([]interface{}) {
+ roles = append(roles, role.(string))
+ }
+
+ channelSubscription.Roles = roles
+ }
+
+ channelSubscriptions = append(channelSubscriptions, channelSubscription)
+ }
+
+ return channelSubscriptions, nil
+}
+
+// GetChannelRoles returns room roles
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-room-roles
+func (c *Client) GetChannelRoles(roomId string) error {
+ _, err := c.ddp.Call("getRoomRoles", roomId)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// CreateChannel creates a channel
+// Takes name and users array
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-channels
+func (c *Client) CreateChannel(name string, users []string) error {
+ _, err := c.ddp.Call("createChannel", name, users)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// CreateGroup creates a private group
+// Takes group name and array of users
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-private-groups
+func (c *Client) CreateGroup(name string, users []string) error {
+ _, err := c.ddp.Call("createPrivateGroup", name, users)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// JoinChannel joins a channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/joining-channels
+func (c *Client) JoinChannel(roomId string) error {
+ _, err := c.ddp.Call("joinRoom", roomId)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// LeaveChannel leaves a channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/leaving-rooms
+func (c *Client) LeaveChannel(roomId string) error {
+ _, err := c.ddp.Call("leaveRoom", roomId)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ArchiveChannel archives the channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/archive-rooms
+func (c *Client) ArchiveChannel(roomId string) error {
+ _, err := c.ddp.Call("archiveRoom", roomId)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// UnArchiveChannel unarchives the channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unarchive-rooms
+func (c *Client) UnArchiveChannel(roomId string) error {
+ _, err := c.ddp.Call("unarchiveRoom", roomId)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// DeleteChannel deletes the channel
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-rooms
+func (c *Client) DeleteChannel(roomId string) error {
+ _, err := c.ddp.Call("eraseRoom", roomId)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SetChannelTopic sets channel topic
+// takes roomId and topic
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelTopic(roomId string, topic string) error {
+ _, err := c.ddp.Call("saveRoomSettings", roomId, "roomTopic", topic)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SetChannelType sets the channel type
+// takes roomId and roomType
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelType(roomId string, roomType string) error {
+ _, err := c.ddp.Call("saveRoomSettings", roomId, "roomType", roomType)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SetChannelJoinCode sets channel join code
+// takes roomId and joinCode
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelJoinCode(roomId string, joinCode string) error {
+ _, err := c.ddp.Call("saveRoomSettings", roomId, "joinCode", joinCode)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SetChannelReadOnly sets channel as read only
+// takes roomId and boolean
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelReadOnly(roomId string, readOnly bool) error {
+ _, err := c.ddp.Call("saveRoomSettings", roomId, "readOnly", readOnly)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SetChannelDescription sets channels description
+// takes roomId and description
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
+func (c *Client) SetChannelDescription(roomId string, description string) error {
+ _, err := c.ddp.Call("saveRoomSettings", roomId, "roomDescription", description)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
new file mode 100644
index 00000000..1dde80bf
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
@@ -0,0 +1,96 @@
+// Provides access to Rocket.Chat's realtime API via ddp
+package realtime
+
+import (
+ "fmt"
+ "math/rand"
+ "net/url"
+ "strconv"
+ "time"
+
+ "github.com/gopackage/ddp"
+)
+
+type Client struct {
+ ddp *ddp.Client
+}
+
+// Creates a new instance and connects to the websocket.
+func NewClient(serverURL *url.URL, debug bool) (*Client, error) {
+ rand.Seed(time.Now().UTC().UnixNano())
+
+ wsURL := "ws"
+ port := 80
+
+ if serverURL.Scheme == "https" {
+ wsURL = "wss"
+ port = 443
+ }
+
+ if len(serverURL.Port()) > 0 {
+ port, _ = strconv.Atoi(serverURL.Port())
+ }
+
+ wsURL = fmt.Sprintf("%s://%v:%v%s/websocket", wsURL, serverURL.Hostname(), port, serverURL.Path)
+
+ // log.Println("About to connect to:", wsURL, port, serverURL.Scheme)
+
+ c := new(Client)
+ c.ddp = ddp.NewClient(wsURL, serverURL.String())
+
+ if debug {
+ c.ddp.SetSocketLogActive(true)
+ }
+
+ if err := c.ddp.Connect(); err != nil {
+ return nil, err
+ }
+
+ return c, nil
+}
+
+type statusListener struct {
+ listener func(int)
+}
+
+func (s statusListener) Status(status int) {
+ s.listener(status)
+}
+
+func (c *Client) AddStatusListener(listener func(int)) {
+ c.ddp.AddStatusListener(statusListener{listener: listener})
+}
+
+func (c *Client) Reconnect() {
+ c.ddp.Reconnect()
+}
+
+// ConnectionAway sets connection status to away
+func (c *Client) ConnectionAway() error {
+ _, err := c.ddp.Call("UserPresence:away")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ConnectionOnline sets connection status to online
+func (c *Client) ConnectionOnline() error {
+ _, err := c.ddp.Call("UserPresence:online")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Close closes the ddp session
+func (c *Client) Close() {
+ c.ddp.Close()
+}
+
+// Some of the rocketchat objects need unique IDs specified by the client
+func (c *Client) newRandomId() string {
+ return fmt.Sprintf("%f", rand.Float64())
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
new file mode 100644
index 00000000..90f2c6ee
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
@@ -0,0 +1,10 @@
+package realtime
+
+func (c *Client) getCustomEmoji() error {
+ _, err := c.ddp.Call("listEmojiCustom")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
new file mode 100644
index 00000000..f3c945cf
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
@@ -0,0 +1,21 @@
+package realtime
+
+import "fmt"
+
+func (c *Client) StartTyping(roomId string, username string) error {
+ _, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, true)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (c *Client) StopTyping(roomId string, username string) error {
+ _, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, false)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
new file mode 100644
index 00000000..9c0c9bb4
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
@@ -0,0 +1,240 @@
+package realtime
+
+import (
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/Jeffail/gabs"
+ "github.com/gopackage/ddp"
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+const (
+ // RocketChat doesn't send the `added` event for new messages by default, only `changed`.
+ send_added_event = true
+ default_buffer_size = 100
+)
+
+// LoadHistory loads history
+// Takes roomId
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/load-history
+func (c *Client) LoadHistory(roomId string) error {
+ _, err := c.ddp.Call("loadHistory", roomId)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SendMessage sends message to channel
+// takes channel and message
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/send-message
+func (c *Client) SendMessage(m *models.Message) (*models.Message, error) {
+ m.ID = c.newRandomId()
+
+ rawResponse, err := c.ddp.Call("sendMessage", m)
+ if err != nil {
+ return nil, err
+ }
+
+ return getMessageFromData(rawResponse.(map[string]interface{})), nil
+}
+
+// EditMessage edits a message
+// takes message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/update-message
+func (c *Client) EditMessage(message *models.Message) error {
+ _, err := c.ddp.Call("updateMessage", message)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// DeleteMessage deletes a message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-message
+func (c *Client) DeleteMessage(message *models.Message) error {
+ _, err := c.ddp.Call("deleteMessage", map[string]string{
+ "_id": message.ID,
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ReactToMessage adds a reaction to a message
+// takes a message and emoji
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/set-reaction
+func (c *Client) ReactToMessage(message *models.Message, reaction string) error {
+ _, err := c.ddp.Call("setReaction", reaction, message.ID)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// StarMessage stars message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
+func (c *Client) StarMessage(message *models.Message) error {
+ _, err := c.ddp.Call("starMessage", map[string]interface{}{
+ "_id": message.ID,
+ "rid": message.RoomID,
+ "starred": true,
+ })
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// UnStarMessage unstars message
+// takes message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
+func (c *Client) UnStarMessage(message *models.Message) error {
+ _, err := c.ddp.Call("starMessage", map[string]interface{}{
+ "_id": message.ID,
+ "rid": message.RoomID,
+ "starred": false,
+ })
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// PinMessage pins a message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/pin-message
+func (c *Client) PinMessage(message *models.Message) error {
+ _, err := c.ddp.Call("pinMessage", message)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// UnPinMessage unpins message
+// takes a message object
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unpin-messages
+func (c *Client) UnPinMessage(message *models.Message) error {
+ _, err := c.ddp.Call("unpinMessage", message)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SubscribeToMessageStream Subscribes to the message updates of a channel
+// Returns a buffered channel
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
+func (c *Client) SubscribeToMessageStream(channel *models.Channel, msgChannel chan models.Message) error {
+
+ if err := c.ddp.Sub("stream-room-messages", channel.ID, send_added_event); err != nil {
+ return err
+ }
+
+ //msgChannel := make(chan models.Message, default_buffer_size)
+ c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(messageExtractor{msgChannel, "update"})
+
+ return nil
+}
+
+func getMessagesFromUpdateEvent(update ddp.Update) []models.Message {
+ document, _ := gabs.Consume(update["args"])
+ args, err := document.Children()
+
+ if err != nil {
+ // log.Printf("Event arguments are in an unexpected format: %v", err)
+ return make([]models.Message, 0)
+ }
+
+ messages := make([]models.Message, len(args))
+
+ for i, arg := range args {
+ messages[i] = *getMessageFromDocument(arg)
+ }
+
+ return messages
+}
+
+func getMessageFromData(data interface{}) *models.Message {
+ // TODO: We should know what this will look like, we shouldn't need to use gabs
+ document, _ := gabs.Consume(data)
+ return getMessageFromDocument(document)
+}
+
+func getMessageFromDocument(arg *gabs.Container) *models.Message {
+ var ts *time.Time
+ date := stringOrZero(arg.Path("ts.$date").Data())
+ if len(date) > 0 {
+ if ti, err := strconv.ParseFloat(date, 64); err == nil {
+ t := time.Unix(int64(ti)/1e3, int64(ti)%1e3)
+ ts = &t
+ }
+ }
+ return &models.Message{
+ ID: stringOrZero(arg.Path("_id").Data()),
+ RoomID: stringOrZero(arg.Path("rid").Data()),
+ Msg: stringOrZero(arg.Path("msg").Data()),
+ Timestamp: ts,
+ User: &models.User{
+ ID: stringOrZero(arg.Path("u._id").Data()),
+ UserName: stringOrZero(arg.Path("u.username").Data()),
+ },
+ }
+}
+
+func stringOrZero(i interface{}) string {
+ if i == nil {
+ return ""
+ }
+
+ switch i.(type) {
+ case string:
+ return i.(string)
+ case float64:
+ return fmt.Sprintf("%f", i.(float64))
+ default:
+ return ""
+ }
+}
+
+type messageExtractor struct {
+ messageChannel chan models.Message
+ operation string
+}
+
+func (u messageExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
+ if operation == u.operation {
+ msgs := getMessagesFromUpdateEvent(doc)
+ for _, m := range msgs {
+ u.messageChannel <- m
+ }
+ }
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
new file mode 100644
index 00000000..fc5df3da
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
@@ -0,0 +1,54 @@
+package realtime
+
+import (
+ "github.com/Jeffail/gabs"
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+// GetPermissions gets permissions
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-permissions
+func (c *Client) GetPermissions() ([]models.Permission, error) {
+ rawResponse, err := c.ddp.Call("permissions/get")
+ if err != nil {
+ return nil, err
+ }
+
+ document, _ := gabs.Consume(rawResponse)
+
+ perms, _ := document.Children()
+
+ var permissions []models.Permission
+
+ for _, permission := range perms {
+ var roles []string
+ for _, role := range permission.Path("roles").Data().([]interface{}) {
+ roles = append(roles, role.(string))
+ }
+
+ permissions = append(permissions, models.Permission{
+ ID: stringOrZero(permission.Path("_id").Data()),
+ Roles: roles,
+ })
+ }
+
+ return permissions, nil
+}
+
+// GetUserRoles gets current users roles
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-user-roles
+func (c *Client) GetUserRoles() error {
+ rawResponse, err := c.ddp.Call("getUserRoles")
+ if err != nil {
+ return err
+ }
+
+ document, _ := gabs.Consume(rawResponse)
+
+ _, err = document.Children()
+ // TODO: Figure out if this function is even useful if so return it
+ //log.Println(roles)
+
+ return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
new file mode 100644
index 00000000..c37eedbd
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
@@ -0,0 +1,53 @@
+package realtime
+
+import (
+ "github.com/Jeffail/gabs"
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+// GetPublicSettings gets public settings
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-public-settings
+func (c *Client) GetPublicSettings() ([]models.Setting, error) {
+ rawResponse, err := c.ddp.Call("public-settings/get")
+ if err != nil {
+ return nil, err
+ }
+
+ document, _ := gabs.Consume(rawResponse)
+
+ sett, _ := document.Children()
+
+ var settings []models.Setting
+
+ for _, rawSetting := range sett {
+ setting := models.Setting{
+ ID: stringOrZero(rawSetting.Path("_id").Data()),
+ Type: stringOrZero(rawSetting.Path("type").Data()),
+ }
+
+ switch setting.Type {
+ case "boolean":
+ setting.ValueBool = rawSetting.Path("value").Data().(bool)
+ case "string":
+ setting.Value = stringOrZero(rawSetting.Path("value").Data())
+ case "code":
+ setting.Value = stringOrZero(rawSetting.Path("value").Data())
+ case "color":
+ setting.Value = stringOrZero(rawSetting.Path("value").Data())
+ case "int":
+ setting.ValueInt = rawSetting.Path("value").Data().(float64)
+ case "asset":
+ setting.ValueAsset = models.Asset{
+ DefaultUrl: stringOrZero(rawSetting.Path("value").Data().(map[string]interface{})["defaultUrl"]),
+ }
+
+ default:
+ // log.Println(setting.Type, rawSetting.Path("value").Data())
+ }
+
+ settings = append(settings, setting)
+ }
+
+ return settings, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
new file mode 100644
index 00000000..5013e53d
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
@@ -0,0 +1,41 @@
+package realtime
+
+import (
+ "fmt"
+
+ "github.com/gopackage/ddp"
+)
+
+// Subscribes to stream-notify-logged
+// Returns a buffered channel
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
+func (c *Client) Sub(name string, args ...interface{}) (chan string, error) {
+
+ if args == nil {
+ //log.Println("no args passed")
+ if err := c.ddp.Sub(name); err != nil {
+ return nil, err
+ }
+ } else {
+ if err := c.ddp.Sub(name, args[0], false); err != nil {
+ return nil, err
+ }
+ }
+
+ msgChannel := make(chan string, default_buffer_size)
+ c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(genericExtractor{msgChannel, "update"})
+
+ return msgChannel, nil
+}
+
+type genericExtractor struct {
+ messageChannel chan string
+ operation string
+}
+
+func (u genericExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
+ if operation == u.operation {
+ u.messageChannel <- fmt.Sprintf("%s -> update", collection)
+ }
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
new file mode 100644
index 00000000..09a4f1f4
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
@@ -0,0 +1,103 @@
+package realtime
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "strconv"
+
+ "github.com/Jeffail/gabs"
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type ddpLoginRequest struct {
+ User ddpUser `json:"user"`
+ Password ddpPassword `json:"password"`
+}
+
+type ddpTokenLoginRequest struct {
+ Token string `json:"resume"`
+}
+
+type ddpUser struct {
+ Email string `json:"email"`
+}
+
+type ddpPassword struct {
+ Digest string `json:"digest"`
+ Algorithm string `json:"algorithm"`
+}
+
+// RegisterUser a new user on the server. This function does not need a logged in user. The registered user gets logged in
+// to set its username.
+func (c *Client) RegisterUser(credentials *models.UserCredentials) (*models.User, error) {
+
+ if _, err := c.ddp.Call("registerUser", credentials); err != nil {
+ return nil, err
+ }
+
+ user, err := c.Login(credentials)
+ if err != nil {
+ return nil, err
+ }
+
+ if _, err := c.ddp.Call("setUsername", credentials.Name); err != nil {
+ return nil, err
+ }
+
+ return user, nil
+}
+
+// Login a user.
+// token shouldn't be nil, otherwise the password and the email are not allowed to be nil.
+//
+// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/login/
+func (c *Client) Login(credentials *models.UserCredentials) (*models.User, error) {
+ var request interface{}
+ if credentials.Token != "" {
+ request = ddpTokenLoginRequest{
+ Token: credentials.Token,
+ }
+ } else {
+ digest := sha256.Sum256([]byte(credentials.Password))
+ request = ddpLoginRequest{
+ User: ddpUser{Email: credentials.Email},
+ Password: ddpPassword{
+ Digest: hex.EncodeToString(digest[:]),
+ Algorithm: "sha-256",
+ },
+ }
+ }
+
+ rawResponse, err := c.ddp.Call("login", request)
+ if err != nil {
+ return nil, err
+ }
+
+ user := getUserFromData(rawResponse.(map[string]interface{}))
+ if credentials.Token == "" {
+ credentials.ID, credentials.Token = user.ID, user.Token
+ }
+
+ return user, nil
+}
+
+func getUserFromData(data interface{}) *models.User {
+ document, _ := gabs.Consume(data)
+
+ expires, _ := strconv.ParseFloat(stringOrZero(document.Path("tokenExpires.$date").Data()), 64)
+ return &models.User{
+ ID: stringOrZero(document.Path("id").Data()),
+ Token: stringOrZero(document.Path("token").Data()),
+ TokenExpires: int64(expires),
+ }
+}
+
+// SetPresence set user presence
+func (c *Client) SetPresence(status string) error {
+ _, err := c.ddp.Call("UserPresence:setDefaultStatus", status)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
new file mode 100644
index 00000000..71377500
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
@@ -0,0 +1,64 @@
+package rest
+
+import (
+ "bytes"
+ "fmt"
+ "net/url"
+
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type ChannelsResponse struct {
+ Status
+ models.Pagination
+ Channels []models.Channel `json:"channels"`
+}
+
+type ChannelResponse struct {
+ Status
+ Channel models.Channel `json:"channel"`
+}
+
+// GetPublicChannels returns all channels that can be seen by the logged in user.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/list
+func (c *Client) GetPublicChannels() (*ChannelsResponse, error) {
+ response := new(ChannelsResponse)
+ if err := c.Get("channels.list", nil, response); err != nil {
+ return nil, err
+ }
+
+ return response, nil
+}
+
+// GetJoinedChannels returns all channels that the user has joined.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/list-joined
+func (c *Client) GetJoinedChannels(params url.Values) (*ChannelsResponse, error) {
+ response := new(ChannelsResponse)
+ if err := c.Get("channels.list.joined", params, response); err != nil {
+ return nil, err
+ }
+
+ return response, nil
+}
+
+// LeaveChannel leaves a channel. The id of the channel has to be not nil.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/leave
+func (c *Client) LeaveChannel(channel *models.Channel) error {
+ var body = fmt.Sprintf(`{ "roomId": "%s"}`, channel.ID)
+ return c.Post("channels.leave", bytes.NewBufferString(body), new(ChannelResponse))
+}
+
+// GetChannelInfo get information about a channel. That might be useful to update the usernames.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/info
+func (c *Client) GetChannelInfo(channel *models.Channel) (*models.Channel, error) {
+ response := new(ChannelResponse)
+ if err := c.Get("channels.info", url.Values{"roomId": []string{channel.ID}}, response); err != nil {
+ return nil, err
+ }
+
+ return &response.Channel, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
new file mode 100644
index 00000000..0e37123e
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
@@ -0,0 +1,176 @@
+//Package rest provides a RocketChat rest client.
+package rest
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/url"
+)
+
+var (
+ ResponseErr = fmt.Errorf("got false response")
+)
+
+type Response interface {
+ OK() error
+}
+
+type Client struct {
+ Protocol string
+ Host string
+ Path string
+ Port string
+ Version string
+
+ // Use this switch to see all network communication.
+ Debug bool
+
+ auth *authInfo
+}
+
+type Status struct {
+ Success bool `json:"success"`
+ Error string `json:"error"`
+
+ Status string `json:"status"`
+ Message string `json:"message"`
+}
+
+type authInfo struct {
+ token string
+ id string
+}
+
+func (s Status) OK() error {
+ if s.Success {
+ return nil
+ }
+
+ if len(s.Error) > 0 {
+ return fmt.Errorf(s.Error)
+ }
+
+ if s.Status == "success" {
+ return nil
+ }
+
+ if len(s.Message) > 0 {
+ return fmt.Errorf("status: %s, message: %s", s.Status, s.Message)
+ }
+ return ResponseErr
+}
+
+// StatusResponse The base for the most of the json responses
+type StatusResponse struct {
+ Status
+ Channel string `json:"channel"`
+}
+
+func NewClient(serverUrl *url.URL, debug bool) *Client {
+ protocol := "http"
+ port := "80"
+
+ if serverUrl.Scheme == "https" {
+ protocol = "https"
+ port = "443"
+ }
+
+ if len(serverUrl.Port()) > 0 {
+ port = serverUrl.Port()
+ }
+
+ return &Client{Host: serverUrl.Hostname(), Path: serverUrl.Path, Port: port, Protocol: protocol, Version: "v1", Debug: debug}
+}
+
+func (c *Client) getUrl() string {
+ if len(c.Version) == 0 {
+ c.Version = "v1"
+ }
+ return fmt.Sprintf("%v://%v:%v%s/api/%s", c.Protocol, c.Host, c.Port, c.Path, c.Version)
+}
+
+// Get call Get
+func (c *Client) Get(api string, params url.Values, response Response) error {
+ return c.doRequest(http.MethodGet, api, params, nil, response)
+}
+
+// Post call as JSON
+func (c *Client) Post(api string, body io.Reader, response Response) error {
+ return c.doRequest(http.MethodPost, api, nil, body, response)
+}
+
+// PostForm call as Form Data
+func (c *Client) PostForm(api string, params url.Values, response Response) error {
+ return c.doRequest(http.MethodPost, api, params, nil, response)
+}
+
+func (c *Client) doRequest(method, api string, params url.Values, body io.Reader, response Response) error {
+ contentType := "application/x-www-form-urlencoded"
+ if method == http.MethodPost {
+ if body != nil {
+ contentType = "application/json"
+ } else if len(params) > 0 {
+ body = bytes.NewBufferString(params.Encode())
+ }
+ }
+
+ request, err := http.NewRequest(method, c.getUrl()+"/"+api, body)
+ if err != nil {
+ return err
+ }
+
+ if method == http.MethodGet {
+ if len(params) > 0 {
+ request.URL.RawQuery = params.Encode()
+ }
+ } else {
+ request.Header.Set("Content-Type", contentType)
+ }
+
+ if c.auth != nil {
+ request.Header.Set("X-Auth-Token", c.auth.token)
+ request.Header.Set("X-User-Id", c.auth.id)
+ }
+
+ if c.Debug {
+ log.Println(request)
+ }
+
+ resp, err := http.DefaultClient.Do(request)
+
+ if err != nil {
+ return err
+ }
+
+ defer resp.Body.Close()
+ bodyBytes, err := ioutil.ReadAll(resp.Body)
+
+ if c.Debug {
+ log.Println(string(bodyBytes))
+ }
+
+ var parse bool
+ if err == nil {
+ if e := json.Unmarshal(bodyBytes, response); e == nil {
+ parse = true
+ }
+ }
+ if resp.StatusCode != http.StatusOK {
+ if parse {
+ return response.OK()
+ }
+ return errors.New("Request error: " + resp.Status)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ return response.OK()
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
new file mode 100644
index 00000000..dd831c85
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
@@ -0,0 +1,98 @@
+package rest
+
+import (
+ "net/url"
+
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type InfoResponse struct {
+ Status
+ Info models.Info `json:"info"`
+}
+
+// GetServerInfo a simple method, requires no authentication,
+// that returns information about the server including version information.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/info
+func (c *Client) GetServerInfo() (*models.Info, error) {
+ response := new(InfoResponse)
+ if err := c.Get("info", nil, response); err != nil {
+ return nil, err
+ }
+
+ return &response.Info, nil
+}
+
+type DirectoryResponse struct {
+ Status
+ models.Directory
+}
+
+// GetDirectory a method, that searches by users or channels on all users and channels available on server.
+// It supports the Offset, Count, and Sort Query Parameters along with Query and Fields Query Parameters.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/directory
+func (c *Client) GetDirectory(params url.Values) (*models.Directory, error) {
+ response := new(DirectoryResponse)
+ if err := c.Get("directory", params, response); err != nil {
+ return nil, err
+ }
+
+ return &response.Directory, nil
+}
+
+type SpotlightResponse struct {
+ Status
+ models.Spotlight
+}
+
+// GetSpotlight searches for users or rooms that are visible to the user.
+// WARNING: It will only return rooms that user didn’t join yet.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/spotlight
+func (c *Client) GetSpotlight(params url.Values) (*models.Spotlight, error) {
+ response := new(SpotlightResponse)
+ if err := c.Get("spotlight", params, response); err != nil {
+ return nil, err
+ }
+
+ return &response.Spotlight, nil
+}
+
+type StatisticsResponse struct {
+ Status
+ models.StatisticsInfo
+}
+
+// GetStatistics
+// Statistics about the Rocket.Chat server.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/statistics
+func (c *Client) GetStatistics() (*models.StatisticsInfo, error) {
+ response := new(StatisticsResponse)
+ if err := c.Get("statistics", nil, response); err != nil {
+ return nil, err
+ }
+
+ return &response.StatisticsInfo, nil
+}
+
+type StatisticsListResponse struct {
+ Status
+ models.StatisticsList
+}
+
+// GetStatisticsList
+// Selectable statistics about the Rocket.Chat server.
+// It supports the Offset, Count and Sort Query Parameters along with just the Fields and Query Parameters.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/statistics.list
+func (c *Client) GetStatisticsList(params url.Values) (*models.StatisticsList, error) {
+ response := new(StatisticsListResponse)
+ if err := c.Get("statistics.list", params, response); err != nil {
+ return nil, err
+ }
+
+ return &response.StatisticsList, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
new file mode 100644
index 00000000..b3ad5846
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
@@ -0,0 +1,67 @@
+package rest
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "html"
+ "net/url"
+ "strconv"
+
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type MessagesResponse struct {
+ Status
+ Messages []models.Message `json:"messages"`
+}
+
+type MessageResponse struct {
+ Status
+ Message models.Message `json:"message"`
+}
+
+// Sends a message to a channel. The name of the channel has to be not nil.
+// The message will be html escaped.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
+func (c *Client) Send(channel *models.Channel, msg string) error {
+ body := fmt.Sprintf(`{ "channel": "%s", "text": "%s"}`, channel.Name, html.EscapeString(msg))
+ return c.Post("chat.postMessage", bytes.NewBufferString(body), new(MessageResponse))
+}
+
+// PostMessage send a message to a channel. The channel or roomId has to be not nil.
+// The message will be json encode.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
+func (c *Client) PostMessage(msg *models.PostMessage) (*MessageResponse, error) {
+ body, err := json.Marshal(msg)
+ if err != nil {
+ return nil, err
+ }
+
+ response := new(MessageResponse)
+ err = c.Post("chat.postMessage", bytes.NewBuffer(body), response)
+ return response, err
+}
+
+// Get messages from a channel. The channel id has to be not nil. Optionally a
+// count can be specified to limit the size of the returned messages.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/channels/history
+func (c *Client) GetMessages(channel *models.Channel, page *models.Pagination) ([]models.Message, error) {
+ params := url.Values{
+ "roomId": []string{channel.ID},
+ }
+
+ if page != nil {
+ params.Add("count", strconv.Itoa(page.Count))
+ }
+
+ response := new(MessagesResponse)
+ if err := c.Get("channels.history", params, response); err != nil {
+ return nil, err
+ }
+
+ return response.Messages, nil
+}
diff --git a/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
new file mode 100644
index 00000000..dcf783a0
--- /dev/null
+++ b/vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
@@ -0,0 +1,145 @@
+package rest
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "time"
+
+ "github.com/matterbridge/Rocket.Chat.Go.SDK/models"
+)
+
+type logoutResponse struct {
+ Status
+ Data struct {
+ Message string `json:"message"`
+ } `json:"data"`
+}
+
+type logonResponse struct {
+ Status
+ Data struct {
+ Token string `json:"authToken"`
+ UserID string `json:"userID"`
+ } `json:"data"`
+}
+
+type CreateUserResponse struct {
+ Status
+ User struct {
+ ID string `json:"_id"`
+ CreatedAt time.Time `json:"createdAt"`
+ Services struct {
+ Password struct {
+ Bcrypt string `json:"bcrypt"`
+ } `json:"password"`
+ } `json:"services"`
+ Username string `json:"username"`
+ Emails []struct {
+ Address string `json:"address"`
+ Verified bool `json:"verified"`
+ } `json:"emails"`
+ Type string `json:"type"`
+ Status string `json:"status"`
+ Active bool `json:"active"`
+ Roles []string `json:"roles"`
+ UpdatedAt time.Time `json:"_updatedAt"`
+ Name string `json:"name"`
+ CustomFields map[string]string `json:"customFields"`
+ } `json:"user"`
+}
+
+// Login a user. The Email and the Password are mandatory. The auth token of the user is stored in the Client instance.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/authentication/login
+func (c *Client) Login(credentials *models.UserCredentials) error {
+ if c.auth != nil {
+ return nil
+ }
+
+ if credentials.ID != "" && credentials.Token != "" {
+ c.auth = &authInfo{id: credentials.ID, token: credentials.Token}
+ return nil
+ }
+
+ response := new(logonResponse)
+ data := url.Values{"user": {credentials.Email}, "password": {credentials.Password}}
+ if err := c.PostForm("login", data, response); err != nil {
+ return err
+ }
+
+ c.auth = &authInfo{id: response.Data.UserID, token: response.Data.Token}
+ credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
+ return nil
+}
+
+// CreateToken creates an access token for a user
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/createtoken/
+func (c *Client) CreateToken(userID, username string) (*models.UserCredentials, error) {
+ response := new(logonResponse)
+ data := url.Values{"userId": {userID}, "username": {username}}
+ if err := c.PostForm("users.createToken", data, response); err != nil {
+ return nil, err
+ }
+ credentials := &models.UserCredentials{}
+ credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
+ return credentials, nil
+}
+
+// Logout a user. The function returns the response message of the server.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/authentication/logout
+func (c *Client) Logout() (string, error) {
+
+ if c.auth == nil {
+ return "Was not logged in", nil
+ }
+
+ response := new(logoutResponse)
+ if err := c.Get("logout", nil, response); err != nil {
+ return "", err
+ }
+
+ return response.Data.Message, nil
+}
+
+// CreateUser being logged in with a user that has permission to do so.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/create
+func (c *Client) CreateUser(req *models.CreateUserRequest) (*CreateUserResponse, error) {
+ body, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+
+ response := new(CreateUserResponse)
+ err = c.Post("users.create", bytes.NewBuffer(body), response)
+ return response, err
+}
+
+// UpdateUser updates a user's data being logged in with a user that has permission to do so.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/update/
+func (c *Client) UpdateUser(req *models.UpdateUserRequest) (*CreateUserResponse, error) {
+ body, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+
+ response := new(CreateUserResponse)
+ err = c.Post("users.update", bytes.NewBuffer(body), response)
+ return response, err
+}
+
+// SetUserAvatar updates a user's avatar being logged in with a user that has permission to do so.
+// Currently only passing an URL is possible.
+//
+// https://rocket.chat/docs/developer-guides/rest-api/users/setavatar/
+func (c *Client) SetUserAvatar(userID, username, avatarURL string) (*Status, error) {
+ body := fmt.Sprintf(`{ "userId": "%s","username": "%s","avatarUrl":"%s"}`, userID, username, avatarURL)
+ response := new(Status)
+ err := c.Post("users.setAvatar", bytes.NewBufferString(body), response)
+ return response, err
+}
diff --git a/vendor/github.com/nelsonken/gomf/README.md b/vendor/github.com/nelsonken/gomf/README.md
new file mode 100644
index 00000000..237e9370
--- /dev/null
+++ b/vendor/github.com/nelsonken/gomf/README.md
@@ -0,0 +1,37 @@
+# golang 可多文件上传的request builder 库
+
+## 测试方法
+
+1. start php upload server: php -S 127.0.0.1:8080 ./
+2. run go test -v
+
+## 使用方法
+
+```go
+ fb := gomf.New()
+ fb.WriteField("name", "accountName")
+ fb.WriteField("password", "pwd")
+ fb.WriteFile("picture", "icon.png", "image/jpeg", []byte(strings.Repeat("0", 100)))
+
+ log.Println(fb.GetBuffer().String())
+
+ req, err := fb.GetHTTPRequest(context.Background(), "http://127.0.0.1:8080/up.php")
+ if err != nil {
+ log.Fatal(err)
+ }
+ res, err := http.DefaultClient.Do(req)
+
+ log.Println(res.StatusCode)
+ log.Println(res.Status)
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ b, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Println(string(b))
+```
diff --git a/vendor/github.com/nelsonken/gomf/form_builder.go b/vendor/github.com/nelsonken/gomf/form_builder.go
new file mode 100644
index 00000000..9b0d2294
--- /dev/null
+++ b/vendor/github.com/nelsonken/gomf/form_builder.go
@@ -0,0 +1,89 @@
+package gomf
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "mime/multipart"
+ "net/http"
+ "net/textproto"
+)
+
+type FormBuilder struct {
+ w *multipart.Writer
+ b *bytes.Buffer
+}
+
+func New() *FormBuilder {
+ buf := new(bytes.Buffer)
+ writer := multipart.NewWriter(buf)
+ return &FormBuilder{
+ w: writer,
+ b: buf,
+ }
+}
+
+func (ufw *FormBuilder) WriteField(name, value string) error {
+ w, err := ufw.w.CreateFormField(name)
+ if err != nil {
+ return err
+ }
+
+ _, err = w.Write([]byte(value))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// WriteFile if contentType is empty-string, will auto convert to application/octet-stream
+func (ufw *FormBuilder) WriteFile(fieldName, fileName, contentType string, content []byte) error {
+ if contentType == "" {
+ contentType = "application/octet-stream"
+ }
+
+ wx, err := ufw.w.CreatePart(textproto.MIMEHeader{
+ "Content-Type": []string{
+ contentType,
+ },
+ "Content-Disposition": []string{
+ fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fileName),
+ },
+ })
+ if err != nil {
+ return err
+ }
+
+ _, err = wx.Write(content)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (fb *FormBuilder) Close() error {
+ return fb.w.Close()
+}
+
+func (fb *FormBuilder) GetBuffer() *bytes.Buffer {
+ return fb.b
+}
+
+func (fb *FormBuilder) GetHTTPRequest(ctx context.Context, reqURL string) (*http.Request, error) {
+ err := fb.Close()
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", reqURL, fb.b)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Content-Type", fb.w.FormDataContentType())
+ req = req.WithContext(ctx)
+
+ return req, nil
+}
diff --git a/vendor/github.com/nelsonken/gomf/up.php b/vendor/github.com/nelsonken/gomf/up.php
new file mode 100644
index 00000000..e56e5bbb
--- /dev/null
+++ b/vendor/github.com/nelsonken/gomf/up.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ *
+ * PHP UPLOAD DEMO for gomf test
+ * USAGE:
+ * php -S 127.0.0.1:8080 -t ./
+ *
+ */
+
+print_r($_FILES);
+
+if ($_FILES["picture"]["error"] > 0) {
+ echo "Return Code: " . $_FILES["picture"]["error"] . "\n";
+} else {
+ echo "Upload: " . $_FILES["picture"]["name"] . "\n";
+ echo "Type: " . $_FILES["picture"]["type"] . "\n";
+ echo "Size: " . ($_FILES["picture"]["size"] / 1024) . " Kb\n";
+ echo "Temp file: " . $_FILES["picture"]["tmp_name"] . "\n>";
+
+ if (file_exists($_FILES["picture"]["name"]))
+ {
+ echo $_FILES["picture"]["name"] . " already exists. \n";
+ }
+ else
+ {
+ move_uploaded_file($_FILES["picture"]["tmp_name"], $_FILES["picture"]["name"]);
+ echo "Stored in: " . $_FILES["picture"]["name"] . "\n";
+ }
+}
+
+
+
+