Skip to content

Plugins #3 Slack API

Stephen Brennan edited this page Mar 21, 2017 · 6 revisions

At this point, you should have learned the basic types of handlers you can create as a plugin. However, handlers may not be that interesting if you can't get information from Slack, or post something to Slack. So here, we will give an overview of how to use the Slack APIs. First we will discuss the conveniences that Slacksoc affords us, and then introduce you to the true source of power, the underlying Slack API.

A big theme in this document is the concept of "blocking" - that simply means that the program is waiting for some result (like a Slack API response). Blocking is bad for a bot, because when a plugin blocks waiting for something, it delays the bot from handling other events and executing other plugins. So don't block the main thread!

Replying and Reacting

As we've already seen, Slacksoc provides some utilities to make replying and reacting to messages easy. They are:

You can use these without any worries about blocking the bot.

IDs and Mentions

When constructing mentions, you should be aware of a couple things. First, the Slack API refers to users, channels, and most other things by an ID. The ID is an alphanumeric string that may look something like this: C09B4TCCQ. The API communicates only in terms of IDs, and messages themselves contain IDs rather than actual usernames.

So when you @mention someone, your message actually looks like this: <@U09B4TCCQ>: yo!. When you write a channel name, your message looks more like this: check out <#C09B4TCCQ>. If you want your bot to be able to construct and/or recognize these things, you may want some help.

First of all, given an ID, you can immediately tell if it corresponds to a Channel, User, DM, Group, or File by its first letter. To make this readable in code, use the functions Is(DM|Channel|User|Group|DM|File).

Next, if you want to create a mention, use these handy bot methods, none of which block:

  • bot.Mention, bot.MentionI, bot.MentionN: these take a User, ID, or Name, and return a string containing an @mention
  • bot.SayChannelI, bot.SayChannelN: these take a channel ID or name and return a string containing a linked #channel mention.
  • bot.SpecialMention: takes a string (either "channel", "here", "group", or "everyone") and creates a special @mention string out of it.

To parse a user mention into the ID it contains, use this function rather than trying to do it yourself:

  • ParseUserMention - note that it is part of the library, not a method of the bot

See more documentation on message formatting here.

Users and Channels

Before turning to the API, some of the answers you're looking for (about users and channels, at least) may be available from the bot already. When Slacksoc connects to the bot, it receives a ton of info about the users and channels on the team. It saves that info, and keeps it up to date as it changes on the team. You can ask the bot for this information using the following functions, none of which block:

In particular, the User objects returned have lots of juicy details:

User struct

type User struct {
    ID                string      `json:"id"`
    Name              string      `json:"name"`
    Deleted           bool        `json:"deleted"`
    Color             string      `json:"color"`
    RealName          string      `json:"real_name"`
    TZ                string      `json:"tz,omitempty"`
    TZLabel           string      `json:"tz_label"`
    TZOffset          int         `json:"tz_offset"`
    Profile           UserProfile `json:"profile"`
    IsBot             bool        `json:"is_bot"`
    IsAdmin           bool        `json:"is_admin"`
    IsOwner           bool        `json:"is_owner"`
    IsPrimaryOwner    bool        `json:"is_primary_owner"`
    IsRestricted      bool        `json:"is_restricted"`
    IsUltraRestricted bool        `json:"is_ultra_restricted"`
    Has2FA            bool        `json:"has_2fa"`
    HasFiles          bool        `json:"has_files"`
    Presence          string      `json:"presence"`
}

UserProfile struct, included in Profile field of User

type UserProfile struct {
    FirstName          string `json:"first_name"`
    LastName           string `json:"last_name"`
    RealName           string `json:"real_name"`
    RealNameNormalized string `json:"real_name_normalized"`
    Email              string `json:"email"`
    Skype              string `json:"skype"`
    Phone              string `json:"phone"`
    Image24            string `json:"image_24"`
    Image32            string `json:"image_32"`
    Image48            string `json:"image_48"`
    Image72            string `json:"image_72"`
    Image192           string `json:"image_192"`
    ImageOriginal      string `json:"image_original"`
    Title              string `json:"title"`
    BotID              string `json:"bot_id,omitempty"`
    ApiAppID           string `json:"api_app_id,omitempty"`
}

Channels do not have nearly as much interesting information associated with them. However, you can get info (like lists of users in the channel) from the API. Details on that are coming right up.

The API

So you want to do something that hasn't been discussed yet? Well, for everything else, there's MasterCard.

Just kidding. For everything else, there's the API object.

The Bot contains another struct member named API. This is an authenticated Slack Web API client, which can be used to do all sorts of goodies - just check out the methods listed on the GoDoc!

However, every method of API blocks, and so you need to take care to avoid blocking the main thread!

The nice and easy way to do this is to make your whole handler run in a goroutine. See the concurrency section for information about how to do this - it's not hard! The TL;DR is:

func (l *lov) Foobar(bot *lib.Bot, evt *slack.MessageEvent) error {
	go func() {
		// do everything in here
	}
	return nil
}

The RTM connection

The Bot contains a struct member named RTM. This object, maintained by the Slack library, doesn't have a ton that is useful to plugin developers, but it exists. The RTM connection is how messages are posted (don't use the API object for that, unless you have a good reason). Interestingly, there is actually a separate goroutine already handling the connection, and sending a message simply hands it off to that thread. This is why bot.Reply does not block!