otiai10/amesh-bot

View on GitHub
service/slack.go

Summary

Maintainability
C
1 day
Test Coverage
package service

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "os"
    "strings"

    "github.com/slack-go/slack"
)

type (
    SlackClient struct {
        AccessToken string
    }
    SlackOAuthClient struct{}

    ISlackClient interface {
        PostMessage(ctx context.Context, msg interface{}) (*PostMessageResponse, error)
        // DeleteMessage(ctx context.Context, msg interface{}) error
        UpdateMessage(ctx context.Context, msg interface{}) error
        GetChannelInfo(context.Context, string) (slack.Channel, error)
        GetThreadHistory(ctx context.Context, channel, thread string) ([]slack.Msg, error)
    }

    SlackMsg struct {
        Channel         string        `json:"channel"`
        Text            string        `json:"text,omitempty"`
        Blocks          []slack.Block `json:"blocks,omitempty"`
        Timestamp       string        `json:"ts,omitempty"`
        ThreadTimestamp string        `json:"thread_ts,omitempty"`
        UnfurlMedia     *bool         `json:"unfurl_media,omitempty"`
        // UnfurlLinks  *bool         `json:"unfurl_links,omitempty"`
    }

    // OAuthResponse ...
    // https://api.slack.com/methods/oauth.v2.access#response
    OAuthResponse struct {
        OK         bool   `json:"ok"     firestore:"ok"`
        AppID      string `json:"app_id" firestore:"app_id"`
        AuthedUser struct {
            ID string `json:"id" firestore:"id"`
        } `json:"authed_user" firestore:"authed_user"`
        Scope       string `json:"scope"        firestore:"scope"`
        TokenType   string `json:"token_type"   firestore:"token_type"`
        AccessToken string `json:"access_token" firestore:"access_token"`
        BotUserID   string `json:"bot_user_id"  firestore:"bot_user_id"`
        Team        struct {
            ID   string `json:"id"   firestore:"id"`
            Name string `json:"name" firestore:"name"`
        } `json:"team" firestore:"team"`
        Enterprise interface{} `json:"enterprise" firestore:"-"`
    }

    PostMessageResponse struct {
        slack.SlackResponse
        slack.Msg
    }
)

func NewSlackClient(accessToken string) *SlackClient {
    return &SlackClient{
        AccessToken: accessToken,
    }
}

func (c *SlackClient) PostMessage(ctx context.Context, msg interface{}) (*PostMessageResponse, error) {
    body := bytes.NewBuffer(nil)
    if err := json.NewEncoder(body).Encode(msg); err != nil {
        return nil, err
    }

    // https://api.slack.com/methods/chat.postMessage
    req, err := http.NewRequest("POST", "https://slack.com/api/chat.postMessage", body)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AccessToken))
    req.Header.Set("Content-Type", "application/json")

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    if res.StatusCode >= 400 {
        return nil, fmt.Errorf(res.Status)
    }

    response := &PostMessageResponse{}
    if err := json.NewDecoder(res.Body).Decode(response); err != nil {
        return nil, err
    }
    if !response.Ok {
        // @see https://github.com/slack-go/slack/issues/939
        buf := bytes.NewBuffer(nil)
        json.NewEncoder(buf).Encode(response)
        return response, fmt.Errorf("%s: %s", response.Error, buf.String())
    }
    return response, nil
}

func (c *SlackClient) GetThreadHistory(ctx context.Context, channel, thread string) ([]slack.Msg, error) {
    // api := slack.New(c.AccessToken)
    // api.GetConversationRepliesContext(ctx, &slack.GetConversationRepliesParameters{})
    query := url.Values{"channel": []string{channel}, "ts": []string{thread}}
    req, err := http.NewRequest("GET", "https://slack.com/api/conversations.replies?"+query.Encode(), nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AccessToken))
    req.Header.Set("Content-Type", "application/json")
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    if res.StatusCode >= 400 {
        return nil, fmt.Errorf(res.Status)
    }
    response := struct {
        slack.SlackResponse
        Messages []slack.Msg `json:"messages"`
    }{}
    if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
        return nil, err
    }
    if !response.SlackResponse.Ok {
        // @see https://github.com/slack-go/slack/issues/939
        return nil, fmt.Errorf("%s", response.Error)
    }
    return response.Messages, nil
}

// https://api.slack.com/methods/chat.delete
func (c *SlackClient) DeleteMessage(ctx context.Context, msg interface{}) error {

    body := bytes.NewBuffer(nil)
    if err := json.NewEncoder(body).Encode(msg); err != nil {
        return err
    }

    // https://api.slack.com/methods/chat.delete
    req, err := http.NewRequest("POST", "https://slack.com/api/chat.delete", body)
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AccessToken))
    req.Header.Set("Content-Type", "application/json")

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.StatusCode >= 400 {
        return fmt.Errorf(res.Status)
    }

    response := &slack.SlackResponse{}
    if err := json.NewDecoder(res.Body).Decode(response); err != nil {
        return err
    }
    if !response.Ok {
        // @see https://github.com/slack-go/slack/issues/939
        buf := bytes.NewBuffer(nil)
        json.NewEncoder(buf).Encode(response)
        return fmt.Errorf("%s: %s", response.Error, buf.String())
    }
    return nil

}

func (c *SlackClient) UpdateMessage(ctx context.Context, msg interface{}) error {

    body := bytes.NewBuffer(nil)
    if err := json.NewEncoder(body).Encode(msg); err != nil {
        return err
    }

    // https://api.slack.com/methods/chat.update
    req, err := http.NewRequest("POST", "https://slack.com/api/chat.update", body)
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AccessToken))
    req.Header.Set("Content-Type", "application/json")

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.StatusCode >= 400 {
        return fmt.Errorf(res.Status)
    }

    response := &slack.SlackResponse{}
    if err := json.NewDecoder(res.Body).Decode(response); err != nil {
        return err
    }
    if !response.Ok {
        // @see https://github.com/slack-go/slack/issues/939
        buf := bytes.NewBuffer(nil)
        json.NewEncoder(buf).Encode(response)
        return fmt.Errorf("%s: %s", response.Error, buf.String())
    }
    return nil

}

func (c *SlackClient) GetChannelInfo(ctx context.Context, id string) (info slack.Channel, err error) {
    req, err := http.NewRequest("GET", "https://slack.com/api/conversations.info?channel="+id, nil)
    if err != nil {
        // return err
        return
    }
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AccessToken))
    req.Header.Set("Content-Type", "application/json")
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return info, err
    }
    defer res.Body.Close()

    if res.StatusCode >= 400 {
        return info, fmt.Errorf(res.Status)
    }

    response := struct {
        OK      bool
        Channel slack.Channel
    }{Channel: info}
    if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
        return info, err
    }
    if !response.OK {
        // @see https://github.com/slack-go/slack/issues/939
        errres := slack.SlackResponse{}
        return info, fmt.Errorf("%s", errres.Error)
    }

    return response.Channel, nil
}

func (o *SlackOAuthClient) ExchangeOAuthCodeWithAccessToken(ctx context.Context, code string) (*http.Response, error) {
    params := url.Values{
        "code":          {code},
        "client_id":     {os.Getenv("SLACK_APP_CLIENT_ID")},
        "client_secret": {os.Getenv("SLACK_APP_CLIENT_SECRET")},
    }
    req, err := http.NewRequest("POST", "https://slack.com/api/oauth.v2.access", strings.NewReader(params.Encode()))
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }

    return res, nil
}