This repository has been archived by the owner on Jan 9, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement Slack component (#120)
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
1 parent
4c4189a
commit 1ecff8a
Showing
13 changed files
with
1,071 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.