Skip to content

Commit

Permalink
feat: implement Slack component (#120)
Browse files Browse the repository at this point in the history
Because

- We'd like to introduce a new component connector to Slack.

This commit

- Implements first stage of Slack component, including
  - Read task to retrieve the messages from Slack.
  - Write task to send the message to Slack.
  • Loading branch information
chuang8511 authored May 14, 2024
1 parent 4c4189a commit 1ecff8a
Show file tree
Hide file tree
Showing 13 changed files with 1,071 additions and 4 deletions.
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ require (
github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.24.0
golang.org/x/image v0.15.0
golang.org/x/text v0.14.0
golang.org/x/text v0.15.0
google.golang.org/api v0.149.0
google.golang.org/grpc v1.61.1
google.golang.org/protobuf v1.33.0
Expand Down Expand Up @@ -73,6 +73,7 @@ require (
github.com/google/uuid v1.4.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 // indirect
Expand All @@ -95,6 +96,7 @@ require (
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/slack-go/slack v0.12.5 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/temoto/robotstxt v1.1.1 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
Expand All @@ -105,12 +107,12 @@ require (
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/tools v0.9.1 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.8 // indirect
Expand Down
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA=
github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
Expand Down Expand Up @@ -141,6 +142,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand All @@ -155,6 +157,9 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
Expand Down Expand Up @@ -247,12 +252,15 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8=
github.com/slack-go/slack v0.12.5 h1:ddZ6uz6XVaB+3MTDhoW04gG+Vc/M/X1ctC+wssy2cqs=
github.com/slack-go/slack v0.12.5/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand Down Expand Up @@ -299,6 +307,8 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
Expand Down Expand Up @@ -338,6 +348,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
Expand Down Expand Up @@ -365,6 +377,8 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand All @@ -383,6 +397,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
2 changes: 2 additions & 0 deletions pkg/connector/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/instill-ai/component/pkg/connector/pinecone/v0"
"github.com/instill-ai/component/pkg/connector/redis/v0"
"github.com/instill-ai/component/pkg/connector/restapi/v0"
"github.com/instill-ai/component/pkg/connector/slack/v0"
"github.com/instill-ai/component/pkg/connector/stabilityai/v0"
"github.com/instill-ai/component/pkg/connector/website/v0"

Expand Down Expand Up @@ -96,6 +97,7 @@ func Init(
conStore.Import(redis.Init(baseConn))
conStore.Import(restapi.Init(baseConn))
conStore.Import(website.Init(baseConn))
conStore.Import(slack.Init(baseConn))

})

Expand Down
58 changes: 58 additions & 0 deletions pkg/connector/slack/v0/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: "Slack"
lang: "en-US"
draft: false
description: "Learn about how to set up a VDP Slack connector https://github.com/instill-ai/instill-core"
---

The Slack component is a application connector that allows users to get and send message on Slack.
It can carry out the following tasks:

- [Read Message](#read-message)
- [Send Message](#send-message)

## Release Stage

`Alpha`

## Configuration

The component configuration is defined and maintained [here](https://github.com/instill-ai/component/blob/main/pkg/connector/slack/v0/config/definition.json).

## Connection

| Field | Field ID | Type | Note |
| :--- | :--- | :--- | :--- |
| token | `token` | string | Fill your token |

## Supported Tasks

### Read Message

Get the latest message since specific date

| Input | ID | Type | Description |
| :--- | :--- | :--- | :--- |
| Task ID (required) | `task` | string | `TASK_READ_MESSAGE` |
| Channel Name (required) | `channel_name` | string | A channel name display in Slack |
| Start to read date | `start_to_read_date` | string | earliest date in all read messages |
| Public channel | `is_public_channel` | boolean | Whether all members can read the channel |

| Output | ID | Type | Description |
| :--- | :--- | :--- | :--- |
| Conversations | `conversations` | array[object] | An array of conversations with thread messages |

### Send Message

send message to a specific channel

| Input | ID | Type | Description |
| :--- | :--- | :--- | :--- |
| Task ID (required) | `task` | string | `TASK_WRITE_MESSAGE` |
| Channel Name (required) | `channel_name` | string | A channel name display in Slack |
| Message (required) | `message` | string | message to be sent to the target channel |
| Public channel | `is_public_channel` | boolean | Whether all members can read the channel |

| Output | ID | Type | Description |
| :--- | :--- | :--- | :--- |
| Result | `result` | string | result for sending message |
194 changes: 194 additions & 0 deletions pkg/connector/slack/v0/apiFunctions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package slack

import (
"fmt"
"strconv"
"time"

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

func loopChannelListAPI(e *execution, isPublic bool, channelName string) (string, error) {
var apiParams slack.GetConversationsParameters
setChannelType(&apiParams, isPublic)

var targetChannelID string
for {

slackChannels, nextCur, err := e.client.GetConversations(&apiParams)
if err != nil {
return "", err
}

targetChannelID := getChannelID(channelName, slackChannels)

if targetChannelID != "" {
break
}

if targetChannelID == "" && nextCur == "" {
err := fmt.Errorf("there is no match name in slack channel [%v]", channelName)
return "", err
}

apiParams.Cursor = nextCur

}

return targetChannelID, nil
}

// Todo: make it multiple options
func setChannelType(params *slack.GetConversationsParameters, isPublicChannel bool) {
if !isPublicChannel {
params.Types = append(params.Types, "private_channel")
} else {
params.Types = append(params.Types, "public_channel")
}
}

func getChannelID(channelName string, channels []slack.Channel) (channelID string) {
for _, slackChannel := range channels {
if channelName == slackChannel.Name {
return slackChannel.ID
}
}
return ""
}

func getConversationHistory(e *execution, channelID string, nextCur string) (*slack.GetConversationHistoryResponse, error) {
apiHistoryParams := slack.GetConversationHistoryParameters{
ChannelID: channelID,
Cursor: nextCur,
}

historiesResp, err := e.client.GetConversationHistory(&apiHistoryParams)
if err != nil {
return nil, err
}
if !historiesResp.Ok {
err := fmt.Errorf("slack api error: %v", historiesResp.Error)
return nil, err
}

return historiesResp, nil
}

func getConversationReply(e *execution, channelID string, ts string) ([]slack.Message, error) {
apiParams := slack.GetConversationRepliesParameters{
ChannelID: channelID,
Timestamp: ts,
}
msgs, _, nextCur, err := e.client.GetConversationReplies(&apiParams)

if err != nil {
return nil, err
}

if nextCur == "" {
return msgs, nil
}

allMsgs := msgs

for nextCur != "" {
apiParams.Cursor = nextCur
msgs, _, nextCur, err = e.client.GetConversationReplies(&apiParams)
if err != nil {
return nil, err
}
allMsgs = append(allMsgs, msgs...)
}

return allMsgs, nil
}

func setAPIRespToReadTaskResp(apiResp []slack.Message, readTaskResp *ReadTaskResp, startReadDateString string) error {

for _, msg := range apiResp {
formatedDateString, err := transformTSToDate(msg.Timestamp, time.DateOnly)
if err != nil {
return err
}

startReadDate, err := time.Parse("2006-01-02", startReadDateString)
if err != nil {
return err
}

formatedDate, err := time.Parse("2006-01-02", formatedDateString)
if err != nil {
return err
}

if startReadDate.After(formatedDate) {
continue
}

conversation := Conversation{
UserID: msg.User,
Message: msg.Text,
StartDate: formatedDateString,
LastDate: formatedDateString,
ReplyCount: msg.ReplyCount,
TS: msg.Timestamp,
}
conversation.ThreadReplyMessage = []ThreadReplyMessage{}
readTaskResp.Conversations = append(readTaskResp.Conversations, conversation)
}
return nil
}

func setRepliedToConversation(resp *ReadTaskResp, replies []slack.Message, idx int) error {
c := resp.Conversations[idx]
lastDay, err := time.Parse("2006-01-02", c.LastDate)
if err != nil {
return err
}
for _, msg := range replies {

if c.TS == msg.Timestamp {
continue
}

formatedDateTime, err := transformTSToDate(msg.Timestamp, time.RFC3339)
if err != nil {
return err
}
reply := ThreadReplyMessage{
UserID: msg.User,
DateTime: formatedDateTime,
Message: msg.Text,
}

foramtedDate, err := transformTSToDate(msg.Timestamp, time.DateOnly)
if err != nil {
return err
}

replyDate, err := time.Parse("2006-01-02", foramtedDate)
if err != nil {
return err
}

if replyDate.After(lastDay) {
replyDateString := replyDate.Format("2006-01-02")
resp.Conversations[idx].LastDate = replyDateString
}
resp.Conversations[idx].ThreadReplyMessage = append(resp.Conversations[idx].ThreadReplyMessage, reply)
}
return nil
}

func transformTSToDate(ts string, format string) (string, error) {

tsFloat, err := strconv.ParseFloat(ts, 64)
if err != nil {
return "", err
}

timestamp := time.Unix(int64(tsFloat), int64((tsFloat-float64(int64(tsFloat)))*1e9))

formatedTS := timestamp.Format(format)
return formatedTS, nil
}
Loading

0 comments on commit 1ecff8a

Please sign in to comment.