Skip to content

Commit

Permalink
Merge branch 'slack-go:master' into richtext-list
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-pieper-personio authored Nov 8, 2023
2 parents 5bc77ac + e715221 commit 8809a3e
Show file tree
Hide file tree
Showing 36 changed files with 1,331 additions and 194 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/stale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '0 0 * * 1' # every Monday 0:00 UTC

workflow_dispatch:

permissions:
issues: write
pull-requests: write

jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
with:
any-of-labels: 'feedback given'
days-before-stale: 45
days-before-pr-close: 10
stale-issue-message: 'This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
close-issue-message: 'This issue was closed because it has been stalled for 10 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
operations-per-run: 120
12 changes: 9 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:
strategy:
matrix:
go:
- '1.17'
- '1.18'
- '1.19'
- '1.20'
name: test go-${{ matrix.go }}
steps:
- uses: actions/checkout@v3
Expand All @@ -29,8 +29,14 @@ jobs:
runs-on: ubuntu-22.04
name: lint
steps:
- uses: actions/setup-go@v4
with:
go-version: '1.20'
cache: false
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: golangci-lint
uses: golangci/golangci-lint-action@537aa1903e5d359d0b27dbc19ddd22c5087f3fbc # v3.2.0
uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # v3.4.0
with:
version: v1.48.0
version: v1.52.2
1 change: 1 addition & 0 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
MBTInput MessageBlockType = "input"
MBTHeader MessageBlockType = "header"
MBTRichText MessageBlockType = "rich_text"
MBTVideo MessageBlockType = "video"
)

// Block defines an interface all block types should implement
Expand Down
2 changes: 2 additions & 0 deletions block_conv.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ func (b *Blocks) UnmarshalJSON(data []byte) error {
block = &RichTextBlock{}
case "section":
block = &SectionBlock{}
case "video":
block = &VideoBlock{}
default:
block = &UnknownBlock{}
}
Expand Down
2 changes: 1 addition & 1 deletion block_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ type ConfirmationBlockObject struct {
Title *TextBlockObject `json:"title"`
Text *TextBlockObject `json:"text"`
Confirm *TextBlockObject `json:"confirm"`
Deny *TextBlockObject `json:"deny"`
Deny *TextBlockObject `json:"deny,omitempty"`
Style Style `json:"style,omitempty"`
}

Expand Down
65 changes: 65 additions & 0 deletions block_video.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package slack

// VideoBlock defines data required to display a video as a block element
//
// More Information: https://api.slack.com/reference/block-kit/blocks#video
type VideoBlock struct {
Type MessageBlockType `json:"type"`
VideoURL string `json:"video_url"`
ThumbnailURL string `json:"thumbnail_url"`
AltText string `json:"alt_text"`
Title *TextBlockObject `json:"title"`
BlockID string `json:"block_id,omitempty"`
TitleURL string `json:"title_url,omitempty"`
AuthorName string `json:"author_name,omitempty"`
ProviderName string `json:"provider_name,omitempty"`
ProviderIconURL string `json:"provider_icon_url,omitempty"`
Description *TextBlockObject `json:"description,omitempty"`
}

// BlockType returns the type of the block
func (s VideoBlock) BlockType() MessageBlockType {
return s.Type
}

// NewVideoBlock returns an instance of a new Video Block type
func NewVideoBlock(videoURL, thumbnailURL, altText, blockID string, title *TextBlockObject) *VideoBlock {
return &VideoBlock{
Type: MBTVideo,
VideoURL: videoURL,
ThumbnailURL: thumbnailURL,
AltText: altText,
BlockID: blockID,
Title: title,
}
}

// WithAuthorName sets the author name for the VideoBlock
func (s *VideoBlock) WithAuthorName(authorName string) *VideoBlock {
s.AuthorName = authorName
return s
}

// WithTitleURL sets the title URL for the VideoBlock
func (s *VideoBlock) WithTitleURL(titleURL string) *VideoBlock {
s.TitleURL = titleURL
return s
}

// WithDescription sets the description for the VideoBlock
func (s *VideoBlock) WithDescription(description *TextBlockObject) *VideoBlock {
s.Description = description
return s
}

// WithProviderIconURL sets the provider icon URL for the VideoBlock
func (s *VideoBlock) WithProviderIconURL(providerIconURL string) *VideoBlock {
s.ProviderIconURL = providerIconURL
return s
}

// WithProviderName sets the provider name for the VideoBlock
func (s *VideoBlock) WithProviderName(providerName string) *VideoBlock {
s.ProviderName = providerName
return s
}
23 changes: 23 additions & 0 deletions block_video_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package slack

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewVideoBlock(t *testing.T) {

videoTitle := NewTextBlockObject("plain_text", "VideoTitle", false, false)
videoBlock := NewVideoBlock(
"https://example.com/example.mp4",
"https://example.com/thumbnail.png",
"alternative text", "blockID", videoTitle)

assert.Equal(t, string(videoBlock.Type), "video")
assert.Equal(t, videoBlock.Title.Type, "plain_text")
assert.Equal(t, videoBlock.BlockID, "blockID")
assert.Contains(t, videoBlock.Title.Text, "VideoTitle")
assert.Contains(t, videoBlock.VideoURL, "example.mp4")

}
23 changes: 19 additions & 4 deletions chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"

"github.com/slack-go/slack/slackutilsx"
Expand All @@ -29,9 +30,9 @@ const (

type chatResponseFull struct {
Channel string `json:"channel"`
Timestamp string `json:"ts"` //Regular message timestamp
MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
ScheduledMessageID string `json:"scheduled_message_id,omitempty"` //Scheduled message id
Timestamp string `json:"ts"` // Regular message timestamp
MessageTimeStamp string `json:"message_ts"` // Ephemeral message timestamp
ScheduledMessageID string `json:"scheduled_message_id,omitempty"` // Scheduled message id
Text string `json:"text"`
SlackResponse
}
Expand Down Expand Up @@ -224,7 +225,7 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt
return "", "", "", err
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
api.Debugf("Sending request: %s", string(reqBody))
api.Debugf("Sending request: %s", redactToken(reqBody))
}

if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil {
Expand All @@ -234,6 +235,20 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt
return response.Channel, response.getMessageTimestamp(), response.Text, response.Err()
}

func redactToken(b []byte) []byte {
// See https://api.slack.com/authentication/token-types
// and https://api.slack.com/authentication/rotation
re, err := regexp.Compile(`(token=x[a-z.]+)-[0-9A-Za-z-]+`)
if err != nil {
// The regular expression above should never result in errors,
// but just in case, do no harm.
return b
}
// Keep "token=" and the first element of the token, which identifies its type
// (this could be useful for debugging, e.g. when using a wrong token).
return re.ReplaceAll(b, []byte("$1-REDACTED"))
}

// UnsafeApplyMsgOptions utility function for debugging/testing chat requests.
// NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this function
// will be supported by the library.
Expand Down
51 changes: 50 additions & 1 deletion chat_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package slack

import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"reflect"
"regexp"
"testing"
)

Expand Down Expand Up @@ -39,7 +42,6 @@ func TestGetPermalink(t *testing.T) {
timeStamp := "p135854651500008"

http.HandleFunc("/chat.getPermalink", func(rw http.ResponseWriter, r *http.Request) {

if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want {
t.Errorf("request uses unexpected content type: got %s, want %s", got, want)
}
Expand Down Expand Up @@ -296,3 +298,50 @@ func TestPostMessageWhenMsgOptionDeleteOriginalApplied(t *testing.T) {

_, _, _ = api.PostMessage("CXXX", MsgOptionDeleteOriginal(responseURL))
}

func TestSendMessageContextRedactsTokenInDebugLog(t *testing.T) {
tests := []struct {
name string
token string
want string
}{
{
name: "regular token",
token: "xtest-token-1234-abcd",
want: "xtest-REDACTED",
},
{
name: "refresh token",
token: "xoxe.xtest-token-1234-abcd",
want: "xoxe.xtest-REDACTED",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
once.Do(startServer)
buf := bytes.NewBufferString("")

opts := []Option{
OptionAPIURL("http://" + serverAddr + "/"),
OptionLog(log.New(buf, "", log.Lshortfile)),
OptionDebug(true),
}
api := New(tt.token, opts...)
// Why send the token in the message text too? To test that we're not
// redacting substrings in the request which look like a token but aren't.
api.SendMessage("CXXX", MsgOptionText(token, false))
s := buf.String()

re := regexp.MustCompile(`token=[\w.-]*`)
want := "token=" + tt.want
if got := re.FindString(s); got != want {
t.Errorf("Logged token in SendMessageContext(): got %q, want %q", got, want)
}
re = regexp.MustCompile(`text=[\w.-]*`)
want = "text=" + token
if got := re.FindString(s); got != want {
t.Errorf("Logged text in SendMessageContext(): got %q, want %q", got, want)
}
})
}
}
47 changes: 47 additions & 0 deletions conversation.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,53 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
return response.Channel, response.Err()
}

// InviteSharedEmailsToConversation invites users to a shared channels by email
func (api *Client) InviteSharedEmailsToConversation(channelID string, emails ...string) (string, bool, error) {
return api.inviteSharedToConversationHelper(context.Background(), channelID, emails, nil)
}

// InviteSharedEmailsToConversationContext invites users to a shared channels by email using context
func (api *Client) InviteSharedEmailsToConversationContext(ctx context.Context, channelID string, emails ...string) (string, bool, error) {
return api.inviteSharedToConversationHelper(ctx, channelID, emails, nil)
}

// InviteSharedUserIDsToConversation invites users to a shared channels by user id
func (api *Client) InviteSharedUserIDsToConversation(channelID string, userIDs ...string) (string, bool, error) {
return api.inviteSharedToConversationHelper(context.Background(), channelID, nil, userIDs)
}

// InviteSharedUserIDsToConversationContext invites users to a shared channels by user id with context
func (api *Client) InviteSharedUserIDsToConversationContext(ctx context.Context, channelID string, userIDs ...string) (string, bool, error) {
return api.inviteSharedToConversationHelper(ctx, channelID, nil, userIDs)
}

// inviteSharedToConversationHelper invites emails or userIDs to a channel with a custom context.
// This is a helper function for InviteSharedEmailsToConversation and InviteSharedUserIDsToConversation.
// It accepts either emails or userIDs, but not both.
func (api *Client) inviteSharedToConversationHelper(ctx context.Context, channelID string, emails []string, userIDs []string) (string, bool, error) {
values := url.Values{
"token": {api.token},
"channel": {channelID},
}
if len(emails) > 0 {
values.Add("emails", strings.Join(emails, ","))
} else if len(userIDs) > 0 {
values.Add("user_ids", strings.Join(userIDs, ","))
}
response := struct {
SlackResponse
InviteID string `json:"invite_id"`
IsLegacySharedChannel bool `json:"is_legacy_shared_channel"`
}{}

err := api.postMethod(ctx, "conversations.inviteShared", values, &response)
if err != nil {
return "", false, err
}

return response.InviteID, response.IsLegacySharedChannel, response.Err()
}

// KickUserFromConversation removes a user from a conversation
func (api *Client) KickUserFromConversation(channelID string, user string) error {
return api.KickUserFromConversationContext(context.Background(), channelID, user)
Expand Down
Loading

0 comments on commit 8809a3e

Please sign in to comment.