Skip to content

Commit

Permalink
Merge pull request #1 from zerobotlabs/master
Browse files Browse the repository at this point in the history
Relax Original to Relax Reflektive
  • Loading branch information
ericktai authored Jun 8, 2018
2 parents 748a241 + 650939c commit 6661961
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sudo: required
language: go
go:
- 1.6
- 1.7
services:
- redis-server
script: make test
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ This is a string value contains the type of event can hold the following values:
Type | What it does
-------------------|---------------
`disable_bot` | This event is sent when authentication with a team fails (either due to a wrong token or an expired token). `message_new` | This is event is sent When a new message is received by Relax. *Note*: Only events for messages intended to Relax (so an @-mention to the bot or a direct message) are sent.
`message_changed` | This event is sent when a message has been edited.
`message_edited` | This event is sent when a message has been edited.
`message_deleted` | This event is sent when a message has been deleted.
`reaction_added` | This event is sent when a reaction has been added to a message.
`reaction_removed` | This event is sent when a reaction has been removed from a message.
Expand Down Expand Up @@ -156,7 +156,7 @@ event. This can mean different things in different contexts:
Type | What "text" field means
-------------------|---------------
`message_new` | The message text
`message_changed` | The *new* text of the message
`message_edited` | The *new* text of the message
`message_deleted` | The original message text
`reaction_added` | Reaction added to a message. This contains the text representation of a reaction, for e.g. `:simple_smiley:`
`reaction_removed` | Reaction removed from a message. This contain the text representation of a reaction, for e.g. `:simple_smiley:`
Expand All @@ -179,7 +179,7 @@ Type | What "timestamp" field means
-------------------|---------------
`disable_bot` | Empty string
`message_new` | Timestamp at which the message was created. In this case, `timestamp` and `event_timestamp` will be the same
`message_changed` | Timestamp of the message that has been changed. Upon receiving a "message_changed" event, you can use "channel_uid" and "timestamp" to identify the message who's text has been changed and modify its text accordingly
`message_edited` | Timestamp of the message that has been changed. Upon receiving a "message_changed" event, you can use "channel_uid" and "timestamp" to identify the message who's text has been changed and modify its text accordingly
`message_deleted` | Timestamp of the message that has been deleted. Upon receiving a "message_changed" event, you can use "channel_uid" and "timestamp" to identify the message that has been deleted and delete that message accordingly
`reaction_added` | Timestamp of the message for which a reaction has been added. Upon receiving a "reaction_added" event, you can use "channel_uid" and "timestamp" to identify the message for which a reaction has been added and change the metadata for that message accordingly
`reaction_removed` | Timestamp of the message for which a reaction has been removed. Upon receiving a "reaction_removed" event, you can use "channel_uid" and "timestamp" to identify the message for which a reaction has been removed and change the metadata for that message accordingly
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.0
0.6.0
37 changes: 25 additions & 12 deletions slack/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,26 @@ func (c *Client) IncrementHeartBeatsMissed() {
// Login calls the "rtm.start" Slack API and gets a bunch of information such as
// the websocket URL to connect to, users and channel information for the team and so on
func (c *Client) Login() error {
contents, err := c.callSlack("rtm.start", map[string][]string{}, 200)
contents, resp, err := c.callSlack("rtm.start", map[string][]string{}, 200)
var metadata Metadata

if err != nil {
log.WithFields(log.Fields{
"team": c.TeamId,
"error": err,
}).Error("calling rtm.start")
// Check for Slack Rate Limiting
for resp != nil && resp.StatusCode == 429 {
retryAfter := resp.Header.Get("Retry-After")
log.WithFields(log.Fields{
"team": c.TeamId,
"retry-after": retryAfter,
}).Info("rate-limit hit, retrying")

retryAfterSeconds, err := strconv.Atoi(retryAfter)
if err != nil {
retryAfterSeconds = 5
}
// Sleep for the required amount of time and try again
time.Sleep(time.Duration(retryAfterSeconds) * time.Second)
contents, resp, err = c.callSlack("rtm.start", map[string][]string{}, 200)
}

return err
} else {
Expand Down Expand Up @@ -265,7 +277,7 @@ func (c *Client) Stop() error {
}

// callSlack is a utility method that makes HTTP API calls to Slack
func (c *Client) callSlack(method string, params url.Values, expectedStatusCode int) (string, error) {
func (c *Client) callSlack(method string, params url.Values, expectedStatusCode int) (string, *http.Response, error) {
params.Set("token", c.Token)
method = "/api/" + method

Expand Down Expand Up @@ -296,6 +308,7 @@ func (c *Client) sendEvent(responseType string, msg *Message, text string, times
RelaxBotUid: c.data.Self.Id,
Timestamp: timestamp,
EventTimestamp: eventTimestamp,
Attachments: msg.Attachments,
Namespace: c.Namespace,
Provider: "slack",
}
Expand Down Expand Up @@ -679,10 +692,10 @@ func (c *Client) handleMessage(msg *Message) {

// callAPI is a utility method that is invoked by callSlack and is used to make
// HTTP calls to REST API endpoints
func (c *Client) callAPI(h string, method string, params url.Values, expectedStatusCode int) (string, error) {
func (c *Client) callAPI(h string, method string, params url.Values, expectedStatusCode int) (string, *http.Response, error) {
u, err := url.ParseRequestURI(h)
if err != nil {
return "", err
return "", nil, err
}

u.Path = method
Expand All @@ -696,18 +709,18 @@ func (c *Client) callAPI(h string, method string, params url.Values, expectedSta
resp, err := client.Do(r)

if err != nil {
return "", err
return "", resp, err
} else {
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)

if err != nil {
return "", err
return "", resp, err
} else {
if resp.StatusCode != expectedStatusCode {
return "", fmt.Errorf("Expected Status Code: %d, Got: %d\n", expectedStatusCode, resp.StatusCode)
return "", resp, fmt.Errorf("Expected Status Code: %d, Got: %d\n", expectedStatusCode, resp.StatusCode)
} else {
return string(contents), err
return string(contents), resp, err
}
}
}
Expand Down
157 changes: 157 additions & 0 deletions slack/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,163 @@ var _ = Describe("Client", func() {
})
})

Context("message comes in from Slack (and has attachments)", func() {
BeforeEach(func() {
wsServer = newWSServer(`
{
"type": "message",
"channel": "C2147483705",
"user": "U2147483697",
"text": "Hey <@UBOTUID> Hello world",
"ts": "1355517523.000009",
"attachments": [
{
"fallback": "Required plain-text summary of the attachment.",
"color": "#36a64f",
"pretext": "Optional text that appears above the attachment block",
"author_name": "Bobby Tables",
"author_link": "http://flickr.com/bobby/",
"author_icon": "http://flickr.com/icons/bobby.jpg",
"title": "Slack API Documentation",
"title_link": "https://api.slack.com/",
"text": "Optional text that appears within the attachment",
"fields": [
{
"title": "Priority",
"value": "High",
"short": false
}
],
"image_url": "http://my-website.com/path/to/image.jpg",
"thumb_url": "http://example.com/path/to/thumb.png",
"footer": "Slack API",
"footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
"ts": 123456789,
"attachment_type": "default",
"actions": [
{
"name": "chess",
"text": "Chess",
"type": "button",
"value": "chess"
},
{
"name": "maze",
"text": "Falken's Maze",
"type": "button",
"value": "maze"
},
{
"name": "war",
"text": "Thermonuclear War",
"style": "danger",
"type": "button",
"value": "war",
"confirm": {
"title": "Are you sure?",
"text": "Wouldn't you prefer a good game of chess?",
"ok_text": "Yes",
"dismiss_text": "No"
}
}
]
}
]
}
`)

client.data = &Metadata{
Ok: true,
Url: makeWsProto(wsServer.URL),
Users: map[string]User{},
Channels: map[string]Channel{},
Self: User{Id: "UBOTUID"},
}
client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
client.data.Users["U2147483697"] = User{Id: "U2147483697"}
client.TeamId = "TDEADBEEF"

client.Start()
})

AfterEach(func() {
wsServer.Close()
})

It("should send an event to Redis with the attachments", func() {
var event Event

resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
result := resultevent.Val()

Expect(len(result)).To(Equal(2))
err := json.Unmarshal([]byte(result[1]), &event)

Expect(err).To(BeNil())
Expect(event.Type).To(Equal("message_new"))
Expect(event.ChannelUid).To(Equal("C2147483705"))
Expect(event.UserUid).To(Equal("U2147483697"))
Expect(event.Text).To(Equal("Hey <@UBOTUID> Hello world"))
Expect(event.Timestamp).To(Equal("1355517523.000009"))
Expect(event.Im).To(BeFalse())
Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
Expect(event.TeamUid).To(Equal("TDEADBEEF"))
Expect(event.Provider).To(Equal("slack"))
Expect(event.EventTimestamp).To(Equal("1355517523.000009"))
Expect(len(event.Attachments)).To(Equal(1))

attachment := event.Attachments[0]

Expect(attachment.Fallback).To(Equal("Required plain-text summary of the attachment."))
Expect(attachment.Color).To(Equal("#36a64f"))
Expect(attachment.Pretext).To(Equal("Optional text that appears above the attachment block"))
Expect(attachment.AuthorName).To(Equal("Bobby Tables"))
Expect(attachment.AuthorLink).To(Equal("http://flickr.com/bobby/"))
Expect(attachment.AuthorIcon).To(Equal("http://flickr.com/icons/bobby.jpg"))
Expect(attachment.Text).To(Equal("Optional text that appears within the attachment"))
Expect(attachment.ImageUrl).To(Equal("http://my-website.com/path/to/image.jpg"))
Expect(attachment.ThumbUrl).To(Equal("http://example.com/path/to/thumb.png"))
Expect(attachment.Footer).To(Equal("Slack API"))
Expect(attachment.FooterIcon).To(Equal("https://platform.slack-edge.com/img/default_application_icon.png"))
Expect(attachment.Ts).To(Equal(int64(123456789)))
Expect(attachment.AttachmentType).To(Equal("default"))
Expect(len(attachment.Fields)).To(Equal(1))

field := attachment.Fields[0]
Expect(field.Title).To(Equal("Priority"))
Expect(field.Value).To(Equal("High"))
Expect(field.Short).To(Equal(false))

Expect(len(attachment.Actions)).To(Equal(3))
action1 := attachment.Actions[0]
Expect(action1.Name).To(Equal("chess"))
Expect(action1.Text).To(Equal("Chess"))
Expect(action1.Type).To(Equal("button"))
Expect(action1.Value).To(Equal("chess"))

action2 := attachment.Actions[1]
Expect(action2.Name).To(Equal("maze"))
Expect(action2.Text).To(Equal("Falken's Maze"))
Expect(action2.Type).To(Equal("button"))
Expect(action2.Value).To(Equal("maze"))

action3 := attachment.Actions[2]
Expect(action3.Name).To(Equal("war"))
Expect(action3.Text).To(Equal("Thermonuclear War"))
Expect(action3.Style).To(Equal("danger"))
Expect(action3.Type).To(Equal("button"))
Expect(action3.Value).To(Equal("war"))
Expect(action3.Confirm.Title).To(Equal("Are you sure?"))
Expect(action3.Confirm.Text).To(Equal("Wouldn't you prefer a good game of chess?"))
Expect(action3.Confirm.OkText).To(Equal("Yes"))
Expect(action3.Confirm.DismissText).To(Equal("No"))

val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
Expect(val).ToNot(BeNil())
Expect(val.Val()).To(Equal("ok"))
})
})

Context("message comes in from Slack and is from the bot itself", func() {
BeforeEach(func() {
wsServer = newWSServer(`
Expand Down
83 changes: 64 additions & 19 deletions slack/datatypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,60 @@ type Im struct {
CreatorId string `json:"user"`
}

type Field struct {
Title string `json:"title"`
Value string `json:"value"`
Short bool `json:"short"`
}

type ConfirmAction struct {
Title string `json:"title"`
Text string `json:"text"`
OkText string `json:"ok_text"`
DismissText string `json:"dismiss_text"`
}

type Action struct {
Name string `json:"name"`
Text string `json:"text"`
Type string `json:"type"`
Style string `json:"style"`
Value string `json:"value"`
Confirm ConfirmAction `json:"confirm"`
}

type Attachment struct {
Fallback string `json:"fallback"`
Color string `json:"color"`
Pretext string `json:"pretext"`
AuthorName string `json:"author_name"`
AuthorLink string `json:"author_link"`
AuthorIcon string `json:"author_icon"`
Title string `json:"title"`
TitleLink string `json:"title_link"`
Text string `json:"text"`
Fields []Field `json:"fields"`
ImageUrl string `json:"image_url"`
ThumbUrl string `json:"thumb_url"`
Footer string `json:"footer"`
FooterIcon string `json:"footer_icon"`
Ts int64 `json:"ts"`
AttachmentType string `json:"attachment_type"`
CallbackId string `json:"callback_id"`
Actions []Action `json:"actions"`
}

// Message represents a message on Slack
type Message struct {
Id string `json:"id"`
Type string `json:"type"`
Subtype string `json:"subtype"`
Text string `json:"text"`
Timestamp string `json:"ts"`
DeletedTimestamp string `json:"deleted_ts"`
Reaction string `json:"reaction"`
Hidden bool `json:"hidden"`
Id string `json:"id"`
Type string `json:"type"`
Subtype string `json:"subtype"`
Text string `json:"text"`
Timestamp string `json:"ts"`
DeletedTimestamp string `json:"deleted_ts"`
Reaction string `json:"reaction"`
Hidden bool `json:"hidden"`
Attachments []Attachment `json:"attachments"`
// For some events, such as message_changed, message_deleted, etc.
// the Timestamp field contains the timestamp of the original message
// so to make sure only one instance of the event is sent to REDIS_QUEUE_WEB
Expand Down Expand Up @@ -172,15 +216,16 @@ type User struct {
// for e.g. when a message is received, an emoji reaction is added, etc.
// an event is sent back to the user.
type Event struct {
Type string `json:"type"`
UserUid string `json:"user_uid"`
ChannelUid string `json:"channel_uid"`
TeamUid string `json:"team_uid"`
Im bool `json:"im"`
Text string `json:"text"`
RelaxBotUid string `json:"relax_bot_uid"`
Timestamp string `json:"timestamp"`
Provider string `json:"provider"`
EventTimestamp string `json:"event_timestamp"`
Namespace string `json:"namespace"`
Type string `json:"type"`
UserUid string `json:"user_uid"`
ChannelUid string `json:"channel_uid"`
TeamUid string `json:"team_uid"`
Im bool `json:"im"`
Text string `json:"text"`
RelaxBotUid string `json:"relax_bot_uid"`
Timestamp string `json:"timestamp"`
Provider string `json:"provider"`
EventTimestamp string `json:"event_timestamp"`
Namespace string `json:"namespace"`
Attachments []Attachment `json:"attachments"`
}

0 comments on commit 6661961

Please sign in to comment.