Skip to content

Commit

Permalink
Events API support (slack-go#293)
Browse files Browse the repository at this point in the history
* Add ParseEvent method to client

* Add app_mention event

* Refactor ParseEvent

* parse outer and inner events; package in one

* refactor parsing

* Catch unmapped inner events

* return errors

* update comment

* return errors; update test

* return proper error

* add events api example

* s/ACollectionOfAtoms/nlopes/g

* attempt to fix test

* remove events.go

* create slackevents packages; support more events; add test

* add TokenComparator support

* update example

* update parsers_test.go

* update example again

* Rename function

* fix example

* Update readme

* Update README.md

additional changes to readme.
  • Loading branch information
ACollectionOfAtoms authored and james-lawrence committed May 6, 2018
1 parent 6609ec0 commit 41bf61c
Show file tree
Hide file tree
Showing 9 changed files with 720 additions and 3 deletions.
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -7,7 +7,10 @@ This library supports most if not all of the `api.slack.com` REST
calls, as well as the Real-Time Messaging protocol over websocket, in
a fully managed way.



## Change log
Support for the EventsAPI has recently been added. It is still in its early stages but nearly all events have been added and tested (except for those events in [Developer Preview](https://api.slack.com/slack-apps-preview) mode). API stability for events is not promised at this time.

### v0.2.0 - Feb 10, 2018

Expand Down Expand Up @@ -77,6 +80,11 @@ func main() {
See https://github.com/nlopes/slack/blob/master/examples/websocket/websocket.go


## Minimal EventsAPI usage:

See https://github.com/nlopes/slack/blob/master/examples/eventsapi/events.go


## Contributing

You are more than welcome to contribute to this project. Fork and
Expand Down
46 changes: 46 additions & 0 deletions examples/eventsapi/events.go
@@ -0,0 +1,46 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"net/http"

"github.com/nlopes/slack"
"github.com/nlopes/slack/slackevents"
)

// You more than likely want your "Bot User OAuth Access Token" which starts with "xoxb-"
var api = slack.New("TOKEN")

func main() {
http.HandleFunc("/events-endpoint", func(w http.ResponseWriter, r *http.Request) {
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
body := buf.String()
eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{"TOKEN"}))
if e != nil {
w.WriteHeader(http.StatusInternalServerError)
}

if eventsAPIEvent.Type == slackevents.URLVerification {
var r *slackevents.ChallengeResponse
err := json.Unmarshal([]byte(body), &r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "text")
w.Write([]byte(r.Challenge))
}
if eventsAPIEvent.Type == slackevents.CallbackEvent {
postParams := slack.PostMessageParameters{}
innerEvent := eventsAPIEvent.InnerEvent
switch ev := innerEvent.Data.(type) {
case *slackevents.AppMentionEvent:
api.PostMessage(ev.Channel, "Yes, hello.", postParams)
}
}
})
fmt.Println("[INFO] Server listening")
http.ListenAndServe(":3000", nil)
}
97 changes: 97 additions & 0 deletions slackevents/inner_events.go
@@ -0,0 +1,97 @@
// inner_events.go provides EventsAPI particular inner events

package slackevents

import "encoding/json"

// EventsAPIInnerEvent the inner event of a EventsAPI event_callback Event.
type EventsAPIInnerEvent struct {
Type string `json:"type"`
Data interface{}
}

// AppMentionEvent is an (inner) EventsAPI subscribable event.
type AppMentionEvent struct {
Type string `json:"type"`
User string `json:"user"`
Text string `json:"text"`
TimeStamp string `json:"ts"`
Channel string `json:"channel"`
EventTimeStamp json.Number `json:"event_ts"`
}

// AppUninstalledEvent Your Slack app was uninstalled.
type AppUninstalledEvent struct {
Type string `json:"type"`
}

// GridMigrationFinishedEvent An enterprise grid migration has finished on this workspace.
type GridMigrationFinishedEvent struct {
Type string `json:"type"`
EnterpriseID string `json:"enterprise_id"`
}

// GridMigrationStartedEvent An enterprise grid migration has started on this workspace.
type GridMigrationStartedEvent struct {
Type string `json:"type"`
EnterpriseID string `json:"enterprise_id"`
}

// LinkSharedEvent A message was posted containing one or more links relevant to your application
type LinkSharedEvent struct {
Type string `json:"type"`
User string `json:"user"`
TimeStamp string `json:"ts"`
Channel string `json:"channel"`
MessageTimeStamp json.Number `json:"message_ts"`
Links []sharedLinks `json:"links"`
}

type sharedLinks struct {
Domain string `json:"domain"`
URL string `json:"url"`
}

// MessageEvent occurs when a variety of types of messages has been posted.
// Parse ChannelType to see which
// if ChannelType = "group", this is a private channel message
// if ChannelType = "channel", this message was sent to a channel
// if ChannelType = "im", this is a private message
// if ChannelType = "mim", A message was posted in a multiparty direct message channel
// TODO: Improve this so that it is not required to manually parse ChannelType
type MessageEvent struct {
Type string `json:"type"`
User string `json:"user"`
Text string `json:"text"`
TimeStamp string `json:"ts"`
Channel string `json:"channel"`
ChannelType string `json:"channel_type"`
EventTimeStamp json.Number `json:"event_ts"`
}

const (
// AppMention is an Events API subscribable event
AppMention = "app_mention"
// AppUninstalled Your Slack app was uninstalled.
AppUninstalled = "app_uninstalled"
// GridMigrationFinished An enterprise grid migration has finished on this workspace.
GridMigrationFinished = "grid_migration_finished"
// GridMigrationStarted An enterprise grid migration has started on this workspace.
GridMigrationStarted = "grid_migration_started"
// LinkShared A message was posted containing one or more links relevant to your application
LinkShared = "link_shared"
// Message A message was posted to a channel, private channel (group), im, or mim
Message = "message"
)

// EventsAPIInnerEventMapping maps INNER Event API events to their corresponding struct
// implementations. The structs should be instances of the unmarshalling
// target for the matching event type.
var EventsAPIInnerEventMapping = map[string]interface{}{
AppMention: AppMentionEvent{},
AppUninstalled: AppUninstalledEvent{},
GridMigrationFinished: GridMigrationFinishedEvent{},
GridMigrationStarted: GridMigrationStartedEvent{},
LinkShared: LinkSharedEvent{},
Message: MessageEvent{},
}
117 changes: 117 additions & 0 deletions slackevents/inner_events_test.go
@@ -0,0 +1,117 @@
package slackevents

import (
"encoding/json"
"testing"
)

func TestAppMention(t *testing.T) {
rawE := []byte(`
{
"type": "app_mention",
"user": "U061F7AUR",
"text": "<@U0LAN0Z89> is it everything a river should be?",
"ts": "1515449522.000016",
"channel": "C0LAN2Q65",
"event_ts": "1515449522000016"
}
`)
err := json.Unmarshal(rawE, &AppMentionEvent{})
if err != nil {
t.Error(err)
}
}

func TestAppUninstalled(t *testing.T) {
rawE := []byte(`
{
"type": "app_uninstalled"
}
`)
err := json.Unmarshal(rawE, &AppUninstalledEvent{})
if err != nil {
t.Error(err)
}
}

func TestGridMigrationFinishedEvent(t *testing.T) {
rawE := []byte(`
{
"type": "grid_migration_finished",
"enterprise_id": "EXXXXXXXX"
}
`)
err := json.Unmarshal(rawE, &GridMigrationFinishedEvent{})
if err != nil {
t.Error(err)
}
}

func TestGridMigrationStartedEvent(t *testing.T) {
rawE := []byte(`
{
"token": "XXYYZZ",
"team_id": "TXXXXXXXX",
"api_app_id": "AXXXXXXXXX",
"event": {
"type": "grid_migration_started",
"enterprise_id": "EXXXXXXXX"
},
"type": "event_callback",
"event_id": "EvXXXXXXXX",
"event_time": 1234567890
}
`)
err := json.Unmarshal(rawE, &GridMigrationStartedEvent{})
if err != nil {
t.Error(err)
}
}

func TestLinkSharedEvent(t *testing.T) {
rawE := []byte(`
{
"type": "link_shared",
"channel": "Cxxxxxx",
"user": "Uxxxxxxx",
"message_ts": "123456789.9875",
"links":
[
{
"domain": "example.com",
"url": "https://example.com/12345"
},
{
"domain": "example.com",
"url": "https://example.com/67890"
},
{
"domain": "another-example.com",
"url": "https://yet.another-example.com/v/abcde"
}
]
}
`)
err := json.Unmarshal(rawE, &LinkSharedEvent{})
if err != nil {
t.Error(err)
}
}

func TestMessageEvent(t *testing.T) {
rawE := []byte(`
{
"type": "message",
"channel": "G024BE91L",
"user": "U2147483697",
"text": "Live long and prospect.",
"ts": "1355517523.000005",
"event_ts": "1355517523.000005",
"channel_type": "channel"
}
`)
err := json.Unmarshal(rawE, &MessageEvent{})
if err != nil {
t.Error(err)
}
}
66 changes: 66 additions & 0 deletions slackevents/outer_events.go
@@ -0,0 +1,66 @@
// outer_events.go provides EventsAPI particular outer events

package slackevents

import (
"encoding/json"
)

// EventsAPIEvent is the base EventsAPIEvent
type EventsAPIEvent struct {
Token string `json:"token"`
Type string `json:"type"`
Data interface{}
InnerEvent EventsAPIInnerEvent
}

// EventsAPIURLVerificationEvent recieved when configuring a EventsAPI driven app
type EventsAPIURLVerificationEvent struct {
Token string `json:"token"`
Challenge string `json:"challenge"`
Type string `json:"type"`
}

// ChallengeResponse is a response to a EventsAPIEvent URLVerification challenge
type ChallengeResponse struct {
Challenge string
}

// EventsAPICallbackEvent is the main (outer) EventsAPI event.
type EventsAPICallbackEvent struct {
Type string `json:"type"`
Token string `json:"token"`
TeamID string `json:"team_id"`
APIAppID string `json:"api_app_id"`
InnerEvent *json.RawMessage `json:"event"`
AuthedUsers []string `json:"authed_users"`
EventID string `json:"event_id"`
EventTime int `json:"event_time"`
}

// EventsAPIAppRateLimited indicates your app's event subscriptions are being rate limited
type EventsAPIAppRateLimited struct {
Type string `json:"type"`
Token string `json:"token"`
TeamID string `json:"team_id"`
MinuteRateLimited int `json:"minute_rate_limited"`
APIAppID string `json:"api_app_id"`
}

const (
// CallbackEvent is the "outer" event of an EventsAPI event.
CallbackEvent = "event_callback"
// URLVerification is an event used when configuring your EventsAPI app
URLVerification = "url_verification"
// AppRateLimited indicates your app's event subscriptions are being rate limited
AppRateLimited = "app_rate_limited"
)

// EventsAPIEventMap maps OUTTER Event API events to their corresponding struct
// implementations. The structs should be instances of the unmarshalling
// target for the matching event type.
var EventsAPIEventMap = map[string]interface{}{
CallbackEvent: EventsAPICallbackEvent{},
URLVerification: EventsAPIURLVerificationEvent{},
AppRateLimited: EventsAPIAppRateLimited{},
}

0 comments on commit 41bf61c

Please sign in to comment.