Creating a Go module

We’re going to create a CLI tool for sending a message to a channel in Slack using the command line. This post is similar to my earlier post: Creating an Elixir Module. We’ll be using the chat.postMessage Slack API endpoint. Also, make sure you have a Slack API token.

Our CLI syntax will be:

$ ./slack -message 'hello world!' -channel @slackbot

First, make sure you have your $GOPATH set properly.

export GOPATH="/path/to/go/"

Next, set your Slack token as an environment variable:

export SLACK_TOKEN=<token>

Typically, developers will use one folder for all of their Go code, and create new folders within for each new project. My structure looks like this:

    go
    โ”œโ”€โ”€ bin
    โ”œโ”€โ”€ pkg
    โ””โ”€โ”€ src

Make a folder called slack within src, then inside that folder create the following files and folders as well:

    slack/
    โ”œโ”€โ”€ api
    โ”‚ย ย  โ””โ”€โ”€ slack.go
    โ””โ”€โ”€ main.go

Using the builtin Go command line parser flag, we will write our main.go file to parse the message and channel from the command line.

slack/main.go


package main

import (
    "slack/api"
    "flag"
)

func main() {
    msg := flag.String("message", "", "The `message` to send the `channel`")
    channel := flag.String("channel", "", "The `channel` to send the `message`")
    flag.Parse()
    api.SendMsg(*msg, *channel)
}

This sets us up with a CLI parser which will take our provided arguments and pass them to the SendMsg function, which we will define now.

Make sure you have set the environment variable SLACK_TOKEN with your token.

slack/api/slack.go


package api

import (
    "fmt"
    "net/http"
    "net/url"
    "os"
)

const slackUrl string = "https://slack.com/api/"
const chatPostMsg string = "chat.postMessage"

func SendMsg(msg, channel string) {
    token := getToken()
    // keys and values for query string parameters used in the API call
    paramMap := map[string]string{
        "token": token,
        "as_user": "true",
        "text": msg,
        "channel": channel,
    }
    url := buildUrl(slackUrl+chatPostMsg, paramMap)
    resp, err := http.Post(url, "application/json", nil)
    // handle and error calling `http.Post`
    if err != nil {
        fmt.Println(err)
        fmt.Println("failed to post")
    }
    // handle non-200 status codes
    if resp.StatusCode == 200 {
        fmt.Println("Sent message!")
        fmt.Println(fmt.Sprintf("%s <- %s", channel, msg))
    } else {
        fmt.Println("Failed to send message.")
        fmt.Println(resp)
    }
}

// Build the URL using the parameters map and builtin URL library
func buildUrl(urlString string, args map[string] string) string {
    u, err := url.Parse(urlString)
    if err != nil {
        fmt.Println("Error parsing URL string")
    }
    v := url.Values{}
    for key, value := range args {
        v.Set(key, value)
    }
    u.RawQuery = v.Encode()
    return u.String()
}

// check that token is set in environment
func getToken() string {
    token := os.Getenv("SLACK_TOKEN")
    if token == "" {
        fmt.Println("Environment variable `SLACK_TOKEN` not set")
        fmt.Println("Set with:")
        fmt.Println("\texport SLACK_TOKEN=<token>")
        panic("Exiting")
    }
    return token
}

With these two files written, we can go the to slack folder and test the program:

$ go run main.go -channel @slackbot -message 'hello!'

If all goes well, you will see the output:

Sent message!
@slackbot <- hello!

and the message will show up from you to slackbot in the Slack app.

To build the program as a standalone, distributable binary run:

$ go build -o slack

This creates a binary file called slack in the folder, which can be run with:

$ ./slack -message 'hello world!' -channel @slackbot

That’s it!

Thanks to Hans Li for the help with testing!