[Go] Search channel ID/user ID from channel name/user name with slack api

Background

  1. slack api parameters require channel id and user id
  2. You can get it manually, but it is troublesome because you need to open the browser.
    Reference: How to check Slack channel ID --Qiita
  3. Then, let's create a process to search for ID by channel name/user name.

Search for ID by channel name

I will omit the method of creating OAuth Tokens for slack because it will come out if you look for it. The required permissions are as follows.

Authority
channels:read

In order to search the ID from the channel name in slack, you need to get the channel list in conversations.list. And since this conversations.list has an upper limit on the number of channels that can be acquired at one time, if there are many channels, you have to specify cursor and hit it several times.

This time, there is a convenient library called slack-go/slack, so I will use it. I think that you can go by hitting api directly with GET because you just make a request internally and hit GET.

When I implement it, it looks like this.

slackclient/client.go


package slackclient

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"github.com/slack-go/slack"
)

type SlackClient interface {
	SearchChannel(ctx context.Context, channelName string) (*slack.Channel, error)
}

type slackClient struct {
	Api *slack.Client
}

func NewClient(token string, options ...slack.Option) SlackClient {
	api := slack.New(token, options...)
	return &slackClient{api}
}

func (s slackClient) SearchChannel(ctx context.Context, channelName string) (*slack.Channel, error) {
	var cursor string
	for {
		requestParam := &slack.GetConversationsParameters{
			// public,Both private are targeted, but if you want to use either one, remove the unnecessary one and it's ok
			// conversations.DM is also supported as a list, but this time it is not applicable
			Types:           []string{"public_channel", "private_channel"},
			// Must be an integer no larger than 1000.
			// https://api.slack.com/methods/conversations.list#arg_limit
			Limit:           1000,
			//Exclude archived channels
			//Set to false if you also want to search archived channels
			ExcludeArchived: "true",
		}
		if cursor != "" {
			requestParam.Cursor = cursor
		}
		var channels []slack.Channel
		var err error
		channels, cursor, err = s.Api.GetConversationsContext(ctx, requestParam)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		for _, channel := range channels {
			if channel.Name == channelName {
				return &channel, nil
			}
		}
		if cursor == "" {
			break
		}
	}
	return nil, errors.New(fmt.Sprintf("channel not found. channelName=%s", channelName))
}

It's not that simple because you can't get all the channels with one api call, but once you implement it, you can reuse it, so Yoshi. Immediately write a test and check the operation.

slackclient/client_test.go


package slackclient_test

import (
	"context"
	"github.com/stretchr/testify/assert"
	"os"
	"testing"
	"trial-go/syusya/slackclient"
)

func TestSlackClient_SearchChannel(t *testing.T) {
	token := os.Getenv("SLACK_TOKEN")
	if !assert.NotZero(t, token) {
		t.FailNow()
	}

	t.Run("Search for existing channel", func(t *testing.T) {
		ctx := context.Background()
		client := slackclient.NewClient(token)

		channelName := "harada-debug"
		channel, err := client.SearchChannel(ctx, channelName)
		if !assert.NoError(t ,err) {
			t.FailNow()
		}
		assert.Equal(t, channelName, channel.Name)
	})

	t.Run("Search for non-existent channel", func(t *testing.T) {
		ctx := context.Background()
		client := slackclient.NewClient(token)

		channelName := "not exists channel"
		_, err := client.SearchChannel(ctx, channelName)
		assert.Error(t ,err)
	})
}
=== RUN   TestSlackClient_SearchChannel
--- PASS: TestSlackClient_SearchChannel (1.52s)
=== RUN   TestSlackClient_SearchChannel/Search_for_existing_channel
channel_id=<Channel ID>, channel_name=harada-debug
    --- PASS: TestSlackClient_SearchChannel/Search_for_existing_channel (0.87s)
=== RUN   TestSlackClient_SearchChannel/Search_for_non-existent_channel
error: channel not found. channelName=not exists channel
    --- PASS: TestSlackClient_SearchChannel/Search_for_non-existent_channel (0.65s)
PASS

It's working well. I'm a little worried, for example, if the number of channels is 10,000, the api call will fly 10 times in a row, but I wonder if it will hit the api limit. If you get stuck in the api limit, you may have to wait.

Search for ID by user name

Unlike the channel list, the user list is simple because there is no cursor operation. However, please note that the display name specified by mention as the search target is user.Profile.DisplayName instead of user.Name.

Authority
-

slackclient/client.go


package slackclient

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"github.com/slack-go/slack"
)

type SlackClient interface {
	SearchChannel(ctx context.Context, channelName string) (*slack.Channel, error)
	SearchUser(ctx context.Context, userName string) (*slack.User, error)
}

func (s slackClient) SearchUser(ctx context.Context, userName string) (*slack.User, error) {
	users, err := s.Api.GetUsersContext(ctx)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	for _, user := range users {
		if user.IsAppUser || user.IsBot || user.Deleted {
			continue
		}
		if user.Profile.DisplayName == userName {
			return &user, nil
		}
	}
	return nil, errors.New(fmt.Sprintf("user not found. userName=%s", userName))
}

I will try it in a test.

slackclient/client_test.go


func TestSlackClient_SearchUser(t *testing.T) {
	token := os.Getenv("SLACK_TOKEN")
	if !assert.NotZero(t, token) {
		t.FailNow()
	}

	t.Run("Search for existing user", func(t *testing.T) {
		ctx := context.Background()
		client := slackclient.NewClient(token)

		userName := "tharada"
		user, err := client.SearchUser(ctx, userName)
		if !assert.NoError(t, err) {
			t.FailNow()
		}
		assert.Equal(t, "tharada", user.Profile.DisplayName)
		fmt.Printf("user_id=%s, user_name=%s\n", user.ID, user.Profile.DisplayName)
	})

	t.Run("Search for non-existent user", func(t *testing.T) {
		ctx := context.Background()
		client := slackclient.NewClient(token)

		userName := "not exists user"
		_, err := client.SearchUser(ctx, userName)
		assert.Error(t, err)
	})
}
=== RUN   TestSlackClient_SearchUser
--- PASS: TestSlackClient_SearchUser (2.45s)
=== RUN   TestSlackClient_SearchUser/Search_for_existing_user
user_id=<user id>, user_name=tharada
    --- PASS: TestSlackClient_SearchUser/Search_for_existing_user (1.22s)
=== RUN   TestSlackClient_SearchUser/Search_for_non-existent_user
    --- PASS: TestSlackClient_SearchUser/Search_for_non-existent_user (1.23s)

I was able to get the user information from the user name safely.

Recommended Posts

[Go] Search channel ID/user ID from channel name/user name with slack api
Bit full search with Go