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
This commit is contained in:
Wim
2019-02-10 17:00:11 +01:00
parent 2cfd880cdb
commit 6ebd5cbbd8
53 changed files with 6203 additions and 45 deletions

View File

@@ -0,0 +1,39 @@
package models
import "time"
type Channel struct {
ID string `json:"_id"`
Name string `json:"name"`
Fname string `json:"fname,omitempty"`
Type string `json:"t"`
Msgs int `json:"msgs"`
ReadOnly bool `json:"ro,omitempty"`
SysMes bool `json:"sysMes,omitempty"`
Default bool `json:"default"`
Broadcast bool `json:"broadcast,omitempty"`
Timestamp *time.Time `json:"ts,omitempty"`
UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
User *User `json:"u,omitempty"`
LastMessage *Message `json:"lastMessage,omitempty"`
// Lm interface{} `json:"lm"`
// CustomFields struct {
// } `json:"customFields,omitempty"`
}
type ChannelSubscription struct {
ID string `json:"_id"`
Alert bool `json:"alert"`
Name string `json:"name"`
DisplayName string `json:"fname"`
Open bool `json:"open"`
RoomId string `json:"rid"`
Type string `json:"c"`
User User `json:"u"`
Roles []string `json:"roles"`
Unread float64 `json:"unread"`
}

View File

@@ -0,0 +1,133 @@
package models
import "time"
type Info struct {
Version string `json:"version"`
Build struct {
NodeVersion string `json:"nodeVersion"`
Arch string `json:"arch"`
Platform string `json:"platform"`
Cpus int `json:"cpus"`
} `json:"build"`
Commit struct {
Hash string `json:"hash"`
Date string `json:"date"`
Author string `json:"author"`
Subject string `json:"subject"`
Tag string `json:"tag"`
Branch string `json:"branch"`
} `json:"commit"`
}
type Pagination struct {
Count int `json:"count"`
Offset int `json:"offset"`
Total int `json:"total"`
}
type Directory struct {
Result []struct {
ID string `json:"_id"`
CreatedAt time.Time `json:"createdAt"`
Emails []struct {
Address string `json:"address"`
Verified bool `json:"verified"`
} `json:"emails"`
Name string `json:"name"`
Username string `json:"username"`
} `json:"result"`
Pagination
}
type Spotlight struct {
Users []User `json:"users"`
Rooms []Channel `json:"rooms"`
}
type Statistics struct {
ID string `json:"_id"`
UniqueID string `json:"uniqueId"`
Version string `json:"version"`
ActiveUsers int `json:"activeUsers"`
NonActiveUsers int `json:"nonActiveUsers"`
OnlineUsers int `json:"onlineUsers"`
AwayUsers int `json:"awayUsers"`
OfflineUsers int `json:"offlineUsers"`
TotalUsers int `json:"totalUsers"`
TotalRooms int `json:"totalRooms"`
TotalChannels int `json:"totalChannels"`
TotalPrivateGroups int `json:"totalPrivateGroups"`
TotalDirect int `json:"totalDirect"`
TotlalLivechat int `json:"totlalLivechat"`
TotalMessages int `json:"totalMessages"`
TotalChannelMessages int `json:"totalChannelMessages"`
TotalPrivateGroupMessages int `json:"totalPrivateGroupMessages"`
TotalDirectMessages int `json:"totalDirectMessages"`
TotalLivechatMessages int `json:"totalLivechatMessages"`
InstalledAt time.Time `json:"installedAt"`
LastLogin time.Time `json:"lastLogin"`
LastMessageSentAt time.Time `json:"lastMessageSentAt"`
LastSeenSubscription time.Time `json:"lastSeenSubscription"`
Os struct {
Type string `json:"type"`
Platform string `json:"platform"`
Arch string `json:"arch"`
Release string `json:"release"`
Uptime int `json:"uptime"`
Loadavg []float64 `json:"loadavg"`
Totalmem int64 `json:"totalmem"`
Freemem int `json:"freemem"`
Cpus []struct {
Model string `json:"model"`
Speed int `json:"speed"`
Times struct {
User int `json:"user"`
Nice int `json:"nice"`
Sys int `json:"sys"`
Idle int `json:"idle"`
Irq int `json:"irq"`
} `json:"times"`
} `json:"cpus"`
} `json:"os"`
Process struct {
NodeVersion string `json:"nodeVersion"`
Pid int `json:"pid"`
Uptime float64 `json:"uptime"`
} `json:"process"`
Deploy struct {
Method string `json:"method"`
Platform string `json:"platform"`
} `json:"deploy"`
Migration struct {
ID string `json:"_id"`
Version int `json:"version"`
Locked bool `json:"locked"`
LockedAt time.Time `json:"lockedAt"`
BuildAt time.Time `json:"buildAt"`
} `json:"migration"`
InstanceCount int `json:"instanceCount"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"_updatedAt"`
}
type StatisticsInfo struct {
Statistics Statistics `json:"statistics"`
}
type StatisticsList struct {
Statistics []Statistics `json:"statistics"`
Pagination
}

View File

@@ -0,0 +1,75 @@
package models
import "time"
type Message struct {
ID string `json:"_id"`
RoomID string `json:"rid"`
Msg string `json:"msg"`
EditedBy string `json:"editedBy,omitempty"`
Groupable bool `json:"groupable,omitempty"`
EditedAt *time.Time `json:"editedAt,omitempty"`
Timestamp *time.Time `json:"ts,omitempty"`
UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
Mentions []User `json:"mentions,omitempty"`
User *User `json:"u,omitempty"`
PostMessage
// Bot interface{} `json:"bot"`
// CustomFields interface{} `json:"customFields"`
// Channels []interface{} `json:"channels"`
// SandstormSessionID interface{} `json:"sandstormSessionId"`
}
// PostMessage Payload for postmessage rest API
//
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
type PostMessage struct {
RoomID string `json:"roomId,omitempty"`
Channel string `json:"channel,omitempty"`
Text string `json:"text,omitempty"`
ParseUrls bool `json:"parseUrls,omitempty"`
Alias string `json:"alias,omitempty"`
Emoji string `json:"emoji,omitempty"`
Avatar string `json:"avatar,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
}
// Attachment Payload for postmessage rest API
//
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
type Attachment struct {
Color string `json:"color,omitempty"`
Text string `json:"text,omitempty"`
Timestamp string `json:"ts,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
MessageLink string `json:"message_link,omitempty"`
Collapsed bool `json:"collapsed"`
AuthorName string `json:"author_name,omitempty"`
AuthorLink string `json:"author_link,omitempty"`
AuthorIcon string `json:"author_icon,omitempty"`
Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
TitleLinkDownload string `json:"title_link_download,omitempty"`
ImageURL string `json:"image_url,omitempty"`
AudioURL string `json:"audio_url,omitempty"`
VideoURL string `json:"video_url,omitempty"`
Fields []AttachmentField `json:"fields,omitempty"`
}
// AttachmentField Payload for postmessage rest API
//
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
type AttachmentField struct {
Short bool `json:"short"`
Title string `json:"title"`
Value string `json:"value"`
}

View File

@@ -0,0 +1,7 @@
package models
type Permission struct {
ID string `json:"_id"`
UpdatedAt string `json:"_updatedAt.$date"`
Roles []string `json:"roles"`
}

View File

@@ -0,0 +1,21 @@
package models
type Setting struct {
ID string `json:"_id"`
Blocked bool `json:"blocked"`
Group string `json:"group"`
Hidden bool `json:"hidden"`
Public bool `json:"public"`
Type string `json:"type"`
PackageValue string `json:"packageValue"`
Sorter int `json:"sorter"`
Value string `json:"value"`
ValueBool bool `json:"valueBool"`
ValueInt float64 `json:"valueInt"`
ValueSource string `json:"valueSource"`
ValueAsset Asset `json:"asset"`
}
type Asset struct {
DefaultUrl string `json:"defaultUrl"`
}

View File

@@ -0,0 +1,29 @@
package models
type User struct {
ID string `json:"_id"`
Name string `json:"name"`
UserName string `json:"username"`
Status string `json:"status"`
Token string `json:"token"`
TokenExpires int64 `json:"tokenExpires"`
}
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
Username string `json:"username"`
CustomFields map[string]string `json:"customFields,omitempty"`
}
type UpdateUserRequest struct {
UserID string `json:"userId"`
Data struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
Username string `json:"username"`
CustomFields map[string]string `json:"customFields,omitempty"`
} `json:"data"`
}

View File

@@ -0,0 +1,10 @@
package models
type UserCredentials struct {
ID string `json:"id"`
Token string `json:"token"`
Email string `json:"email"`
Name string `json:"name"`
Password string `json:"pass"`
}

View File

@@ -0,0 +1,263 @@
package realtime
import (
"github.com/Jeffail/gabs"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
func (c *Client) GetChannelId(name string) (string, error) {
rawResponse, err := c.ddp.Call("getRoomIdByNameOrId", name)
if err != nil {
return "", err
}
//log.Println(rawResponse)
return rawResponse.(string), nil
}
// GetChannelsIn returns list of channels
// Optionally includes date to get all since last check or 0 to get all
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-rooms/
func (c *Client) GetChannelsIn() ([]models.Channel, error) {
rawResponse, err := c.ddp.Call("rooms/get", map[string]int{
"$date": 0,
})
if err != nil {
return nil, err
}
document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
chans, err := document.Children()
var channels []models.Channel
for _, i := range chans {
channels = append(channels, models.Channel{
ID: stringOrZero(i.Path("_id").Data()),
//Default: stringOrZero(i.Path("default").Data()),
Name: stringOrZero(i.Path("name").Data()),
Type: stringOrZero(i.Path("t").Data()),
})
}
return channels, nil
}
// GetChannelSubscriptions gets users channel subscriptions
// Optionally includes date to get all since last check or 0 to get all
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-subscriptions
func (c *Client) GetChannelSubscriptions() ([]models.ChannelSubscription, error) {
rawResponse, err := c.ddp.Call("subscriptions/get", map[string]int{
"$date": 0,
})
if err != nil {
return nil, err
}
document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
channelSubs, err := document.Children()
var channelSubscriptions []models.ChannelSubscription
for _, sub := range channelSubs {
channelSubscription := models.ChannelSubscription{
ID: stringOrZero(sub.Path("_id").Data()),
Alert: sub.Path("alert").Data().(bool),
Name: stringOrZero(sub.Path("name").Data()),
DisplayName: stringOrZero(sub.Path("fname").Data()),
Open: sub.Path("open").Data().(bool),
Type: stringOrZero(sub.Path("t").Data()),
User: models.User{
ID: stringOrZero(sub.Path("u._id").Data()),
UserName: stringOrZero(sub.Path("u.username").Data()),
},
Unread: sub.Path("unread").Data().(float64),
}
if sub.Path("roles").Data() != nil {
var roles []string
for _, role := range sub.Path("roles").Data().([]interface{}) {
roles = append(roles, role.(string))
}
channelSubscription.Roles = roles
}
channelSubscriptions = append(channelSubscriptions, channelSubscription)
}
return channelSubscriptions, nil
}
// GetChannelRoles returns room roles
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-room-roles
func (c *Client) GetChannelRoles(roomId string) error {
_, err := c.ddp.Call("getRoomRoles", roomId)
if err != nil {
return err
}
return nil
}
// CreateChannel creates a channel
// Takes name and users array
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-channels
func (c *Client) CreateChannel(name string, users []string) error {
_, err := c.ddp.Call("createChannel", name, users)
if err != nil {
return err
}
return nil
}
// CreateGroup creates a private group
// Takes group name and array of users
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-private-groups
func (c *Client) CreateGroup(name string, users []string) error {
_, err := c.ddp.Call("createPrivateGroup", name, users)
if err != nil {
return err
}
return nil
}
// JoinChannel joins a channel
// Takes roomId
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/joining-channels
func (c *Client) JoinChannel(roomId string) error {
_, err := c.ddp.Call("joinRoom", roomId)
if err != nil {
return err
}
return nil
}
// LeaveChannel leaves a channel
// Takes roomId
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/leaving-rooms
func (c *Client) LeaveChannel(roomId string) error {
_, err := c.ddp.Call("leaveRoom", roomId)
if err != nil {
return err
}
return nil
}
// ArchiveChannel archives the channel
// Takes roomId
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/archive-rooms
func (c *Client) ArchiveChannel(roomId string) error {
_, err := c.ddp.Call("archiveRoom", roomId)
if err != nil {
return err
}
return nil
}
// UnArchiveChannel unarchives the channel
// Takes roomId
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unarchive-rooms
func (c *Client) UnArchiveChannel(roomId string) error {
_, err := c.ddp.Call("unarchiveRoom", roomId)
if err != nil {
return err
}
return nil
}
// DeleteChannel deletes the channel
// Takes roomId
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-rooms
func (c *Client) DeleteChannel(roomId string) error {
_, err := c.ddp.Call("eraseRoom", roomId)
if err != nil {
return err
}
return nil
}
// SetChannelTopic sets channel topic
// takes roomId and topic
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
func (c *Client) SetChannelTopic(roomId string, topic string) error {
_, err := c.ddp.Call("saveRoomSettings", roomId, "roomTopic", topic)
if err != nil {
return err
}
return nil
}
// SetChannelType sets the channel type
// takes roomId and roomType
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
func (c *Client) SetChannelType(roomId string, roomType string) error {
_, err := c.ddp.Call("saveRoomSettings", roomId, "roomType", roomType)
if err != nil {
return err
}
return nil
}
// SetChannelJoinCode sets channel join code
// takes roomId and joinCode
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
func (c *Client) SetChannelJoinCode(roomId string, joinCode string) error {
_, err := c.ddp.Call("saveRoomSettings", roomId, "joinCode", joinCode)
if err != nil {
return err
}
return nil
}
// SetChannelReadOnly sets channel as read only
// takes roomId and boolean
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
func (c *Client) SetChannelReadOnly(roomId string, readOnly bool) error {
_, err := c.ddp.Call("saveRoomSettings", roomId, "readOnly", readOnly)
if err != nil {
return err
}
return nil
}
// SetChannelDescription sets channels description
// takes roomId and description
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
func (c *Client) SetChannelDescription(roomId string, description string) error {
_, err := c.ddp.Call("saveRoomSettings", roomId, "roomDescription", description)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,96 @@
// Provides access to Rocket.Chat's realtime API via ddp
package realtime
import (
"fmt"
"math/rand"
"net/url"
"strconv"
"time"
"github.com/gopackage/ddp"
)
type Client struct {
ddp *ddp.Client
}
// Creates a new instance and connects to the websocket.
func NewClient(serverURL *url.URL, debug bool) (*Client, error) {
rand.Seed(time.Now().UTC().UnixNano())
wsURL := "ws"
port := 80
if serverURL.Scheme == "https" {
wsURL = "wss"
port = 443
}
if len(serverURL.Port()) > 0 {
port, _ = strconv.Atoi(serverURL.Port())
}
wsURL = fmt.Sprintf("%s://%v:%v%s/websocket", wsURL, serverURL.Hostname(), port, serverURL.Path)
// log.Println("About to connect to:", wsURL, port, serverURL.Scheme)
c := new(Client)
c.ddp = ddp.NewClient(wsURL, serverURL.String())
if debug {
c.ddp.SetSocketLogActive(true)
}
if err := c.ddp.Connect(); err != nil {
return nil, err
}
return c, nil
}
type statusListener struct {
listener func(int)
}
func (s statusListener) Status(status int) {
s.listener(status)
}
func (c *Client) AddStatusListener(listener func(int)) {
c.ddp.AddStatusListener(statusListener{listener: listener})
}
func (c *Client) Reconnect() {
c.ddp.Reconnect()
}
// ConnectionAway sets connection status to away
func (c *Client) ConnectionAway() error {
_, err := c.ddp.Call("UserPresence:away")
if err != nil {
return err
}
return nil
}
// ConnectionOnline sets connection status to online
func (c *Client) ConnectionOnline() error {
_, err := c.ddp.Call("UserPresence:online")
if err != nil {
return err
}
return nil
}
// Close closes the ddp session
func (c *Client) Close() {
c.ddp.Close()
}
// Some of the rocketchat objects need unique IDs specified by the client
func (c *Client) newRandomId() string {
return fmt.Sprintf("%f", rand.Float64())
}

View File

@@ -0,0 +1,10 @@
package realtime
func (c *Client) getCustomEmoji() error {
_, err := c.ddp.Call("listEmojiCustom")
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,21 @@
package realtime
import "fmt"
func (c *Client) StartTyping(roomId string, username string) error {
_, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, true)
if err != nil {
return err
}
return nil
}
func (c *Client) StopTyping(roomId string, username string) error {
_, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, false)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,240 @@
package realtime
import (
"fmt"
"strconv"
"time"
"github.com/Jeffail/gabs"
"github.com/gopackage/ddp"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
const (
// RocketChat doesn't send the `added` event for new messages by default, only `changed`.
send_added_event = true
default_buffer_size = 100
)
// LoadHistory loads history
// Takes roomId
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/load-history
func (c *Client) LoadHistory(roomId string) error {
_, err := c.ddp.Call("loadHistory", roomId)
if err != nil {
return err
}
return nil
}
// SendMessage sends message to channel
// takes channel and message
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/send-message
func (c *Client) SendMessage(m *models.Message) (*models.Message, error) {
m.ID = c.newRandomId()
rawResponse, err := c.ddp.Call("sendMessage", m)
if err != nil {
return nil, err
}
return getMessageFromData(rawResponse.(map[string]interface{})), nil
}
// EditMessage edits a message
// takes message object
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/update-message
func (c *Client) EditMessage(message *models.Message) error {
_, err := c.ddp.Call("updateMessage", message)
if err != nil {
return err
}
return nil
}
// DeleteMessage deletes a message
// takes a message object
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-message
func (c *Client) DeleteMessage(message *models.Message) error {
_, err := c.ddp.Call("deleteMessage", map[string]string{
"_id": message.ID,
})
if err != nil {
return err
}
return nil
}
// ReactToMessage adds a reaction to a message
// takes a message and emoji
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/set-reaction
func (c *Client) ReactToMessage(message *models.Message, reaction string) error {
_, err := c.ddp.Call("setReaction", reaction, message.ID)
if err != nil {
return err
}
return nil
}
// StarMessage stars message
// takes a message object
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
func (c *Client) StarMessage(message *models.Message) error {
_, err := c.ddp.Call("starMessage", map[string]interface{}{
"_id": message.ID,
"rid": message.RoomID,
"starred": true,
})
if err != nil {
return err
}
return nil
}
// UnStarMessage unstars message
// takes message object
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
func (c *Client) UnStarMessage(message *models.Message) error {
_, err := c.ddp.Call("starMessage", map[string]interface{}{
"_id": message.ID,
"rid": message.RoomID,
"starred": false,
})
if err != nil {
return err
}
return nil
}
// PinMessage pins a message
// takes a message object
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/pin-message
func (c *Client) PinMessage(message *models.Message) error {
_, err := c.ddp.Call("pinMessage", message)
if err != nil {
return err
}
return nil
}
// UnPinMessage unpins message
// takes a message object
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unpin-messages
func (c *Client) UnPinMessage(message *models.Message) error {
_, err := c.ddp.Call("unpinMessage", message)
if err != nil {
return err
}
return nil
}
// SubscribeToMessageStream Subscribes to the message updates of a channel
// Returns a buffered channel
//
// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
func (c *Client) SubscribeToMessageStream(channel *models.Channel, msgChannel chan models.Message) error {
if err := c.ddp.Sub("stream-room-messages", channel.ID, send_added_event); err != nil {
return err
}
//msgChannel := make(chan models.Message, default_buffer_size)
c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(messageExtractor{msgChannel, "update"})
return nil
}
func getMessagesFromUpdateEvent(update ddp.Update) []models.Message {
document, _ := gabs.Consume(update["args"])
args, err := document.Children()
if err != nil {
// log.Printf("Event arguments are in an unexpected format: %v", err)
return make([]models.Message, 0)
}
messages := make([]models.Message, len(args))
for i, arg := range args {
messages[i] = *getMessageFromDocument(arg)
}
return messages
}
func getMessageFromData(data interface{}) *models.Message {
// TODO: We should know what this will look like, we shouldn't need to use gabs
document, _ := gabs.Consume(data)
return getMessageFromDocument(document)
}
func getMessageFromDocument(arg *gabs.Container) *models.Message {
var ts *time.Time
date := stringOrZero(arg.Path("ts.$date").Data())
if len(date) > 0 {
if ti, err := strconv.ParseFloat(date, 64); err == nil {
t := time.Unix(int64(ti)/1e3, int64(ti)%1e3)
ts = &t
}
}
return &models.Message{
ID: stringOrZero(arg.Path("_id").Data()),
RoomID: stringOrZero(arg.Path("rid").Data()),
Msg: stringOrZero(arg.Path("msg").Data()),
Timestamp: ts,
User: &models.User{
ID: stringOrZero(arg.Path("u._id").Data()),
UserName: stringOrZero(arg.Path("u.username").Data()),
},
}
}
func stringOrZero(i interface{}) string {
if i == nil {
return ""
}
switch i.(type) {
case string:
return i.(string)
case float64:
return fmt.Sprintf("%f", i.(float64))
default:
return ""
}
}
type messageExtractor struct {
messageChannel chan models.Message
operation string
}
func (u messageExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
if operation == u.operation {
msgs := getMessagesFromUpdateEvent(doc)
for _, m := range msgs {
u.messageChannel <- m
}
}
}

View File

@@ -0,0 +1,54 @@
package realtime
import (
"github.com/Jeffail/gabs"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
// GetPermissions gets permissions
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-permissions
func (c *Client) GetPermissions() ([]models.Permission, error) {
rawResponse, err := c.ddp.Call("permissions/get")
if err != nil {
return nil, err
}
document, _ := gabs.Consume(rawResponse)
perms, _ := document.Children()
var permissions []models.Permission
for _, permission := range perms {
var roles []string
for _, role := range permission.Path("roles").Data().([]interface{}) {
roles = append(roles, role.(string))
}
permissions = append(permissions, models.Permission{
ID: stringOrZero(permission.Path("_id").Data()),
Roles: roles,
})
}
return permissions, nil
}
// GetUserRoles gets current users roles
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-user-roles
func (c *Client) GetUserRoles() error {
rawResponse, err := c.ddp.Call("getUserRoles")
if err != nil {
return err
}
document, _ := gabs.Consume(rawResponse)
_, err = document.Children()
// TODO: Figure out if this function is even useful if so return it
//log.Println(roles)
return nil
}

View File

@@ -0,0 +1,53 @@
package realtime
import (
"github.com/Jeffail/gabs"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
// GetPublicSettings gets public settings
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-public-settings
func (c *Client) GetPublicSettings() ([]models.Setting, error) {
rawResponse, err := c.ddp.Call("public-settings/get")
if err != nil {
return nil, err
}
document, _ := gabs.Consume(rawResponse)
sett, _ := document.Children()
var settings []models.Setting
for _, rawSetting := range sett {
setting := models.Setting{
ID: stringOrZero(rawSetting.Path("_id").Data()),
Type: stringOrZero(rawSetting.Path("type").Data()),
}
switch setting.Type {
case "boolean":
setting.ValueBool = rawSetting.Path("value").Data().(bool)
case "string":
setting.Value = stringOrZero(rawSetting.Path("value").Data())
case "code":
setting.Value = stringOrZero(rawSetting.Path("value").Data())
case "color":
setting.Value = stringOrZero(rawSetting.Path("value").Data())
case "int":
setting.ValueInt = rawSetting.Path("value").Data().(float64)
case "asset":
setting.ValueAsset = models.Asset{
DefaultUrl: stringOrZero(rawSetting.Path("value").Data().(map[string]interface{})["defaultUrl"]),
}
default:
// log.Println(setting.Type, rawSetting.Path("value").Data())
}
settings = append(settings, setting)
}
return settings, nil
}

View File

@@ -0,0 +1,41 @@
package realtime
import (
"fmt"
"github.com/gopackage/ddp"
)
// Subscribes to stream-notify-logged
// Returns a buffered channel
//
// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
func (c *Client) Sub(name string, args ...interface{}) (chan string, error) {
if args == nil {
//log.Println("no args passed")
if err := c.ddp.Sub(name); err != nil {
return nil, err
}
} else {
if err := c.ddp.Sub(name, args[0], false); err != nil {
return nil, err
}
}
msgChannel := make(chan string, default_buffer_size)
c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(genericExtractor{msgChannel, "update"})
return msgChannel, nil
}
type genericExtractor struct {
messageChannel chan string
operation string
}
func (u genericExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
if operation == u.operation {
u.messageChannel <- fmt.Sprintf("%s -> update", collection)
}
}

View File

@@ -0,0 +1,103 @@
package realtime
import (
"crypto/sha256"
"encoding/hex"
"strconv"
"github.com/Jeffail/gabs"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
type ddpLoginRequest struct {
User ddpUser `json:"user"`
Password ddpPassword `json:"password"`
}
type ddpTokenLoginRequest struct {
Token string `json:"resume"`
}
type ddpUser struct {
Email string `json:"email"`
}
type ddpPassword struct {
Digest string `json:"digest"`
Algorithm string `json:"algorithm"`
}
// RegisterUser a new user on the server. This function does not need a logged in user. The registered user gets logged in
// to set its username.
func (c *Client) RegisterUser(credentials *models.UserCredentials) (*models.User, error) {
if _, err := c.ddp.Call("registerUser", credentials); err != nil {
return nil, err
}
user, err := c.Login(credentials)
if err != nil {
return nil, err
}
if _, err := c.ddp.Call("setUsername", credentials.Name); err != nil {
return nil, err
}
return user, nil
}
// Login a user.
// token shouldn't be nil, otherwise the password and the email are not allowed to be nil.
//
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/login/
func (c *Client) Login(credentials *models.UserCredentials) (*models.User, error) {
var request interface{}
if credentials.Token != "" {
request = ddpTokenLoginRequest{
Token: credentials.Token,
}
} else {
digest := sha256.Sum256([]byte(credentials.Password))
request = ddpLoginRequest{
User: ddpUser{Email: credentials.Email},
Password: ddpPassword{
Digest: hex.EncodeToString(digest[:]),
Algorithm: "sha-256",
},
}
}
rawResponse, err := c.ddp.Call("login", request)
if err != nil {
return nil, err
}
user := getUserFromData(rawResponse.(map[string]interface{}))
if credentials.Token == "" {
credentials.ID, credentials.Token = user.ID, user.Token
}
return user, nil
}
func getUserFromData(data interface{}) *models.User {
document, _ := gabs.Consume(data)
expires, _ := strconv.ParseFloat(stringOrZero(document.Path("tokenExpires.$date").Data()), 64)
return &models.User{
ID: stringOrZero(document.Path("id").Data()),
Token: stringOrZero(document.Path("token").Data()),
TokenExpires: int64(expires),
}
}
// SetPresence set user presence
func (c *Client) SetPresence(status string) error {
_, err := c.ddp.Call("UserPresence:setDefaultStatus", status)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,64 @@
package rest
import (
"bytes"
"fmt"
"net/url"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
type ChannelsResponse struct {
Status
models.Pagination
Channels []models.Channel `json:"channels"`
}
type ChannelResponse struct {
Status
Channel models.Channel `json:"channel"`
}
// GetPublicChannels returns all channels that can be seen by the logged in user.
//
// https://rocket.chat/docs/developer-guides/rest-api/channels/list
func (c *Client) GetPublicChannels() (*ChannelsResponse, error) {
response := new(ChannelsResponse)
if err := c.Get("channels.list", nil, response); err != nil {
return nil, err
}
return response, nil
}
// GetJoinedChannels returns all channels that the user has joined.
//
// https://rocket.chat/docs/developer-guides/rest-api/channels/list-joined
func (c *Client) GetJoinedChannels(params url.Values) (*ChannelsResponse, error) {
response := new(ChannelsResponse)
if err := c.Get("channels.list.joined", params, response); err != nil {
return nil, err
}
return response, nil
}
// LeaveChannel leaves a channel. The id of the channel has to be not nil.
//
// https://rocket.chat/docs/developer-guides/rest-api/channels/leave
func (c *Client) LeaveChannel(channel *models.Channel) error {
var body = fmt.Sprintf(`{ "roomId": "%s"}`, channel.ID)
return c.Post("channels.leave", bytes.NewBufferString(body), new(ChannelResponse))
}
// GetChannelInfo get information about a channel. That might be useful to update the usernames.
//
// https://rocket.chat/docs/developer-guides/rest-api/channels/info
func (c *Client) GetChannelInfo(channel *models.Channel) (*models.Channel, error) {
response := new(ChannelResponse)
if err := c.Get("channels.info", url.Values{"roomId": []string{channel.ID}}, response); err != nil {
return nil, err
}
return &response.Channel, nil
}

View File

@@ -0,0 +1,176 @@
//Package rest provides a RocketChat rest client.
package rest
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
)
var (
ResponseErr = fmt.Errorf("got false response")
)
type Response interface {
OK() error
}
type Client struct {
Protocol string
Host string
Path string
Port string
Version string
// Use this switch to see all network communication.
Debug bool
auth *authInfo
}
type Status struct {
Success bool `json:"success"`
Error string `json:"error"`
Status string `json:"status"`
Message string `json:"message"`
}
type authInfo struct {
token string
id string
}
func (s Status) OK() error {
if s.Success {
return nil
}
if len(s.Error) > 0 {
return fmt.Errorf(s.Error)
}
if s.Status == "success" {
return nil
}
if len(s.Message) > 0 {
return fmt.Errorf("status: %s, message: %s", s.Status, s.Message)
}
return ResponseErr
}
// StatusResponse The base for the most of the json responses
type StatusResponse struct {
Status
Channel string `json:"channel"`
}
func NewClient(serverUrl *url.URL, debug bool) *Client {
protocol := "http"
port := "80"
if serverUrl.Scheme == "https" {
protocol = "https"
port = "443"
}
if len(serverUrl.Port()) > 0 {
port = serverUrl.Port()
}
return &Client{Host: serverUrl.Hostname(), Path: serverUrl.Path, Port: port, Protocol: protocol, Version: "v1", Debug: debug}
}
func (c *Client) getUrl() string {
if len(c.Version) == 0 {
c.Version = "v1"
}
return fmt.Sprintf("%v://%v:%v%s/api/%s", c.Protocol, c.Host, c.Port, c.Path, c.Version)
}
// Get call Get
func (c *Client) Get(api string, params url.Values, response Response) error {
return c.doRequest(http.MethodGet, api, params, nil, response)
}
// Post call as JSON
func (c *Client) Post(api string, body io.Reader, response Response) error {
return c.doRequest(http.MethodPost, api, nil, body, response)
}
// PostForm call as Form Data
func (c *Client) PostForm(api string, params url.Values, response Response) error {
return c.doRequest(http.MethodPost, api, params, nil, response)
}
func (c *Client) doRequest(method, api string, params url.Values, body io.Reader, response Response) error {
contentType := "application/x-www-form-urlencoded"
if method == http.MethodPost {
if body != nil {
contentType = "application/json"
} else if len(params) > 0 {
body = bytes.NewBufferString(params.Encode())
}
}
request, err := http.NewRequest(method, c.getUrl()+"/"+api, body)
if err != nil {
return err
}
if method == http.MethodGet {
if len(params) > 0 {
request.URL.RawQuery = params.Encode()
}
} else {
request.Header.Set("Content-Type", contentType)
}
if c.auth != nil {
request.Header.Set("X-Auth-Token", c.auth.token)
request.Header.Set("X-User-Id", c.auth.id)
}
if c.Debug {
log.Println(request)
}
resp, err := http.DefaultClient.Do(request)
if err != nil {
return err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if c.Debug {
log.Println(string(bodyBytes))
}
var parse bool
if err == nil {
if e := json.Unmarshal(bodyBytes, response); e == nil {
parse = true
}
}
if resp.StatusCode != http.StatusOK {
if parse {
return response.OK()
}
return errors.New("Request error: " + resp.Status)
}
if err != nil {
return err
}
return response.OK()
}

View File

@@ -0,0 +1,98 @@
package rest
import (
"net/url"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
type InfoResponse struct {
Status
Info models.Info `json:"info"`
}
// GetServerInfo a simple method, requires no authentication,
// that returns information about the server including version information.
//
// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/info
func (c *Client) GetServerInfo() (*models.Info, error) {
response := new(InfoResponse)
if err := c.Get("info", nil, response); err != nil {
return nil, err
}
return &response.Info, nil
}
type DirectoryResponse struct {
Status
models.Directory
}
// GetDirectory a method, that searches by users or channels on all users and channels available on server.
// It supports the Offset, Count, and Sort Query Parameters along with Query and Fields Query Parameters.
//
// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/directory
func (c *Client) GetDirectory(params url.Values) (*models.Directory, error) {
response := new(DirectoryResponse)
if err := c.Get("directory", params, response); err != nil {
return nil, err
}
return &response.Directory, nil
}
type SpotlightResponse struct {
Status
models.Spotlight
}
// GetSpotlight searches for users or rooms that are visible to the user.
// WARNING: It will only return rooms that user didnt 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
}

View File

@@ -0,0 +1,67 @@
package rest
import (
"bytes"
"encoding/json"
"fmt"
"html"
"net/url"
"strconv"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
type MessagesResponse struct {
Status
Messages []models.Message `json:"messages"`
}
type MessageResponse struct {
Status
Message models.Message `json:"message"`
}
// Sends a message to a channel. The name of the channel has to be not nil.
// The message will be html escaped.
//
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
func (c *Client) Send(channel *models.Channel, msg string) error {
body := fmt.Sprintf(`{ "channel": "%s", "text": "%s"}`, channel.Name, html.EscapeString(msg))
return c.Post("chat.postMessage", bytes.NewBufferString(body), new(MessageResponse))
}
// PostMessage send a message to a channel. The channel or roomId has to be not nil.
// The message will be json encode.
//
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
func (c *Client) PostMessage(msg *models.PostMessage) (*MessageResponse, error) {
body, err := json.Marshal(msg)
if err != nil {
return nil, err
}
response := new(MessageResponse)
err = c.Post("chat.postMessage", bytes.NewBuffer(body), response)
return response, err
}
// Get messages from a channel. The channel id has to be not nil. Optionally a
// count can be specified to limit the size of the returned messages.
//
// https://rocket.chat/docs/developer-guides/rest-api/channels/history
func (c *Client) GetMessages(channel *models.Channel, page *models.Pagination) ([]models.Message, error) {
params := url.Values{
"roomId": []string{channel.ID},
}
if page != nil {
params.Add("count", strconv.Itoa(page.Count))
}
response := new(MessagesResponse)
if err := c.Get("channels.history", params, response); err != nil {
return nil, err
}
return response.Messages, nil
}

View File

@@ -0,0 +1,145 @@
package rest
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
)
type logoutResponse struct {
Status
Data struct {
Message string `json:"message"`
} `json:"data"`
}
type logonResponse struct {
Status
Data struct {
Token string `json:"authToken"`
UserID string `json:"userID"`
} `json:"data"`
}
type CreateUserResponse struct {
Status
User struct {
ID string `json:"_id"`
CreatedAt time.Time `json:"createdAt"`
Services struct {
Password struct {
Bcrypt string `json:"bcrypt"`
} `json:"password"`
} `json:"services"`
Username string `json:"username"`
Emails []struct {
Address string `json:"address"`
Verified bool `json:"verified"`
} `json:"emails"`
Type string `json:"type"`
Status string `json:"status"`
Active bool `json:"active"`
Roles []string `json:"roles"`
UpdatedAt time.Time `json:"_updatedAt"`
Name string `json:"name"`
CustomFields map[string]string `json:"customFields"`
} `json:"user"`
}
// Login a user. The Email and the Password are mandatory. The auth token of the user is stored in the Client instance.
//
// https://rocket.chat/docs/developer-guides/rest-api/authentication/login
func (c *Client) Login(credentials *models.UserCredentials) error {
if c.auth != nil {
return nil
}
if credentials.ID != "" && credentials.Token != "" {
c.auth = &authInfo{id: credentials.ID, token: credentials.Token}
return nil
}
response := new(logonResponse)
data := url.Values{"user": {credentials.Email}, "password": {credentials.Password}}
if err := c.PostForm("login", data, response); err != nil {
return err
}
c.auth = &authInfo{id: response.Data.UserID, token: response.Data.Token}
credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
return nil
}
// CreateToken creates an access token for a user
//
// https://rocket.chat/docs/developer-guides/rest-api/users/createtoken/
func (c *Client) CreateToken(userID, username string) (*models.UserCredentials, error) {
response := new(logonResponse)
data := url.Values{"userId": {userID}, "username": {username}}
if err := c.PostForm("users.createToken", data, response); err != nil {
return nil, err
}
credentials := &models.UserCredentials{}
credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
return credentials, nil
}
// Logout a user. The function returns the response message of the server.
//
// https://rocket.chat/docs/developer-guides/rest-api/authentication/logout
func (c *Client) Logout() (string, error) {
if c.auth == nil {
return "Was not logged in", nil
}
response := new(logoutResponse)
if err := c.Get("logout", nil, response); err != nil {
return "", err
}
return response.Data.Message, nil
}
// CreateUser being logged in with a user that has permission to do so.
//
// https://rocket.chat/docs/developer-guides/rest-api/users/create
func (c *Client) CreateUser(req *models.CreateUserRequest) (*CreateUserResponse, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
response := new(CreateUserResponse)
err = c.Post("users.create", bytes.NewBuffer(body), response)
return response, err
}
// UpdateUser updates a user's data being logged in with a user that has permission to do so.
//
// https://rocket.chat/docs/developer-guides/rest-api/users/update/
func (c *Client) UpdateUser(req *models.UpdateUserRequest) (*CreateUserResponse, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
response := new(CreateUserResponse)
err = c.Post("users.update", bytes.NewBuffer(body), response)
return response, err
}
// SetUserAvatar updates a user's avatar being logged in with a user that has permission to do so.
// Currently only passing an URL is possible.
//
// https://rocket.chat/docs/developer-guides/rest-api/users/setavatar/
func (c *Client) SetUserAvatar(userID, username, avatarURL string) (*Status, error) {
body := fmt.Sprintf(`{ "userId": "%s","username": "%s","avatarUrl":"%s"}`, userID, username, avatarURL)
response := new(Status)
err := c.Post("users.setAvatar", bytes.NewBufferString(body), response)
return response, err
}