Skip to content

Commit 80b52b0

Browse files
authored
Expose private File Upload V2 methods to support multiple file uploads in a single message (#1376)
Currently, `UploadFileV2Context` works by sequentially calling three private methods in the following flow: 1. getUploadURLExternal (retrieves the upload URL) 2. uploadToURL (uploads the file to that URL) 3. completeUploadExternal (finalizes the upload) Because these methods are private, developers can only upload one file per message via `UploadFileV2Context`. However, the Slack API itself supports uploading multiple files in a single message by running these steps for each file and then calling completeUploadURLExternal for all file IDs together. To unlock this functionality while still using slack-go, I’ve made the following changes in this PR: - Made `getUploadURLExternal` and `completeUploadExternal` public - This allows developers to orchestrate multiple file uploads (Steps 1–3 repeated for each file) and finalize them all in a single message. - `uploadToURL` remains private for now. This method is simply a regular POST request handler, not part of Slack’s official API. Since making getUploadURLExternal and completeUploadExternal public already addresses the main use case of uploading multiple files, this might be sufficient. - However, if you feel uploadToURL should also be made public for consistency, please let me know and I can include that change in this PR.
2 parents 11d6a3b + cf4c69c commit 80b52b0

File tree

2 files changed

+319
-45
lines changed

2 files changed

+319
-45
lines changed

files.go

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,14 @@ type UploadFileV2Parameters struct {
173173
SnippetText string
174174
}
175175

176-
type getUploadURLExternalParameters struct {
177-
altText string
178-
fileSize int
179-
fileName string
180-
snippetText string
176+
type GetUploadURLExternalParameters struct {
177+
AltText string
178+
FileSize int
179+
FileName string
180+
SnippetText string
181181
}
182182

183-
type getUploadURLExternalResponse struct {
183+
type GetUploadURLExternalResponse struct {
184184
UploadURL string `json:"upload_url"`
185185
FileID string `json:"file_id"`
186186
SlackResponse
@@ -199,14 +199,14 @@ type FileSummary struct {
199199
Title string `json:"title"`
200200
}
201201

202-
type completeUploadExternalParameters struct {
203-
title string
204-
channel string
205-
initialComment string
206-
threadTimestamp string
202+
type CompleteUploadExternalParameters struct {
203+
Files []FileSummary
204+
Channel string
205+
InitialComment string
206+
ThreadTimestamp string
207207
}
208208

209-
type completeUploadExternalResponse struct {
209+
type CompleteUploadExternalResponse struct {
210210
SlackResponse
211211
Files []FileSummary `json:"files"`
212212
}
@@ -506,20 +506,28 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string)
506506
return &response.File, response.Comments, &response.Paging, nil
507507
}
508508

509-
// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file.
510-
func (api *Client) getUploadURLExternal(ctx context.Context, params getUploadURLExternalParameters) (*getUploadURLExternalResponse, error) {
509+
// GetUploadURLExternalContext gets a URL and fileID from slack which can later be used to upload a file.
510+
// Slack API docs: https://api.slack.com/methods/files.getUploadURLExternal
511+
func (api *Client) GetUploadURLExternalContext(ctx context.Context, params GetUploadURLExternalParameters) (*GetUploadURLExternalResponse, error) {
512+
if params.FileName == "" {
513+
return nil, fmt.Errorf("FileName cannot be empty")
514+
}
515+
if params.FileSize == 0 {
516+
return nil, fmt.Errorf("FileSize cannot be 0")
517+
}
518+
511519
values := url.Values{
512520
"token": {api.token},
513-
"filename": {params.fileName},
514-
"length": {strconv.Itoa(params.fileSize)},
521+
"filename": {params.FileName},
522+
"length": {strconv.Itoa(params.FileSize)},
515523
}
516-
if params.altText != "" {
517-
values.Add("initial_comment", params.altText)
524+
if params.AltText != "" {
525+
values.Add("initial_comment", params.AltText)
518526
}
519-
if params.snippetText != "" {
520-
values.Add("thread_ts", params.snippetText)
527+
if params.SnippetText != "" {
528+
values.Add("thread_ts", params.SnippetText)
521529
}
522-
response := &getUploadURLExternalResponse{}
530+
response := &GetUploadURLExternalResponse{}
523531
err := api.postMethod(ctx, "files.getUploadURLExternal", values, response)
524532
if err != nil {
525533
return nil, err
@@ -542,28 +550,28 @@ func (api *Client) uploadToURL(ctx context.Context, params uploadToURLParameters
542550
return err
543551
}
544552

545-
// completeUploadExternal once files are uploaded, this completes the upload and shares it to the specified channel
546-
func (api *Client) completeUploadExternal(ctx context.Context, fileID string, params completeUploadExternalParameters) (file *completeUploadExternalResponse, err error) {
547-
request := []FileSummary{{ID: fileID, Title: params.title}}
548-
requestBytes, err := json.Marshal(request)
553+
// CompleteUploadExternalContext once files are uploaded, this completes the upload and shares it to the specified channel
554+
// Slack API docs: https://api.slack.com/methods/files.completeUploadExternal
555+
func (api *Client) CompleteUploadExternalContext(ctx context.Context, params CompleteUploadExternalParameters) (file *CompleteUploadExternalResponse, err error) {
556+
filesBytes, err := json.Marshal(params.Files)
549557
if err != nil {
550558
return nil, err
551559
}
552560
values := url.Values{
553561
"token": {api.token},
554-
"files": {string(requestBytes)},
562+
"files": {string(filesBytes)},
555563
}
556564

557-
if params.channel != "" {
558-
values.Add("channel_id", params.channel)
565+
if params.Channel != "" {
566+
values.Add("channel_id", params.Channel)
559567
}
560-
if params.initialComment != "" {
561-
values.Add("initial_comment", params.initialComment)
568+
if params.InitialComment != "" {
569+
values.Add("initial_comment", params.InitialComment)
562570
}
563-
if params.threadTimestamp != "" {
564-
values.Add("thread_ts", params.threadTimestamp)
571+
if params.ThreadTimestamp != "" {
572+
values.Add("thread_ts", params.ThreadTimestamp)
565573
}
566-
response := &completeUploadExternalResponse{}
574+
response := &CompleteUploadExternalResponse{}
567575
err = api.postMethod(ctx, "files.completeUploadExternal", values, response)
568576
if err != nil {
569577
return nil, err
@@ -594,11 +602,11 @@ func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2P
594602
return nil, fmt.Errorf("file.upload.v2: file size cannot be 0")
595603
}
596604

597-
u, err := api.getUploadURLExternal(ctx, getUploadURLExternalParameters{
598-
altText: params.AltTxt,
599-
fileName: params.Filename,
600-
fileSize: params.FileSize,
601-
snippetText: params.SnippetText,
605+
u, err := api.GetUploadURLExternalContext(ctx, GetUploadURLExternalParameters{
606+
AltText: params.AltTxt,
607+
FileName: params.Filename,
608+
FileSize: params.FileSize,
609+
SnippetText: params.SnippetText,
602610
})
603611
if err != nil {
604612
return nil, err
@@ -615,11 +623,14 @@ func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2P
615623
return nil, err
616624
}
617625

618-
c, err := api.completeUploadExternal(ctx, u.FileID, completeUploadExternalParameters{
619-
title: params.Title,
620-
channel: params.Channel,
621-
initialComment: params.InitialComment,
622-
threadTimestamp: params.ThreadTimestamp,
626+
c, err := api.CompleteUploadExternalContext(ctx, CompleteUploadExternalParameters{
627+
Files: []FileSummary{{
628+
ID: u.FileID,
629+
Title: params.Title,
630+
}},
631+
Channel: params.Channel,
632+
InitialComment: params.InitialComment,
633+
ThreadTimestamp: params.ThreadTimestamp,
623634
})
624635
if err != nil {
625636
return nil, err

0 commit comments

Comments
 (0)