Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/content/packages/web-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,9 @@ of binary data follow soon!
The `channel_id` and `initial_comment` aren't required, but the file either won't be shared to a channel or it won't be
posted with a message if these aren't included.

A posted message can be formatted with [Block Kit](https://docs.slack.dev/block-kit) using the `blocks` argument instead
of the `initial_comment` text.

Comment on lines +504 to +506

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice ๐Ÿ’ฏ

In a successful response, the `result.files` contains an array of [shared files](https://api.slack.com/types/file).
These files are "private" and available to just the `token` holder if no `channel_id` is included in the request, and
are marked "public" when shared to a provided `channel_id`.
Expand Down
52 changes: 52 additions & 0 deletions packages/web-api/src/WebClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,58 @@ describe('WebClient', () => {
uploader.done();
});

it('uploads content', async () => {
const scope = nock('https://slack.com')
.post('/api/files.getUploadURLExternal', { filename: 'content.txt', length: 42 })
.reply(200, {
ok: true,
file_id: 'F0101010101',
upload_url: 'https://files.slack.com/upload/v1/abcdefghijklmnopqrstuvwxyz',
})
.post('/api/files.completeUploadExternal', {
blocks: '[{"type":"section","text":{"type":"plain_text","text":"Hello"}}]',
channel_id: 'C010101010',
files: '[{"id":"F0101010101","title":"content.txt"}]',
})
.reply(200, {
ok: true,
files: [
{
id: 'F0101010101',
name: 'content.txt',
permalink: 'https://my-workspace.slack.com/files/U0123456789/F0101010101/content.txt',
},
],
});
const uploader = nock('https://files.slack.com').post('/upload/v1/abcdefghijklmnopqrstuvwxyz').reply(200);
const client = new WebClient(token);
const response = await client.filesUploadV2({
blocks: [{ type: 'section', text: { type: 'plain_text', text: 'Hello' } }],
channel_id: 'C010101010',
content: 'Words from another variable might go here!',
filename: 'content.txt',
});
const expected = {
ok: true,
files: [
{
ok: true,
files: [
{
id: 'F0101010101',
name: 'content.txt',
permalink: 'https://my-workspace.slack.com/files/U0123456789/F0101010101/content.txt',
},
],
response_metadata: {},
},
],
};
assert.deepEqual(response, expected);
scope.done();
uploader.done();
});

it('uploads multiple files', async () => {
const scope = nock('https://slack.com')
.post('/api/files.getUploadURLExternal', { filename: 'test-png.png', length: 55292 })
Expand Down
17 changes: 10 additions & 7 deletions packages/web-api/src/file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export async function getFileUploadJob(
const fileUploadJob: Record<string, unknown> = {
// supplied by user
alt_text: options.alt_text,
blocks: options.blocks,
channel_id: options.channels ?? options.channel_id,
filename: options.filename ?? fileName,
initial_comment: options.initial_comment,
Expand Down Expand Up @@ -96,8 +97,8 @@ export async function getMultipleFileUploadJobs(
// ensure no omitted properties included in files_upload entry
// these properties are valid only at the top-level, not
// inside file_uploads.
const { channel_id, channels, initial_comment, thread_ts } = upload as FileUploadV2;
if (channel_id || channels || initial_comment || thread_ts) {
const { blocks, channel_id, channels, initial_comment, thread_ts } = upload as FileUploadV2;
if (blocks || channel_id || channels || initial_comment || thread_ts) {
throw errorWithCode(
new Error(buildInvalidFilesUploadParamError()),
ErrorCode.FileUploadInvalidArgumentsError,
Expand All @@ -107,6 +108,7 @@ export async function getMultipleFileUploadJobs(
// supplied at the top level.
const uploadJobArgs: Record<string, unknown> = {
...upload,
blocks: options.blocks,
channels: options.channels,
channel_id: options.channel_id,
initial_comment: options.initial_comment,
Expand Down Expand Up @@ -220,7 +222,7 @@ export async function getFileDataAsStream(readable: Readable): Promise<Buffer> {

/**
* Filters through all fileUploads and groups them into jobs for completion
* based on combination of channel_id, thread_ts, initial_comment.
* based on combination of channel_id, thread_ts, initial_comment, blocks.
* {@link https://api.slack.com/methods/files.completeUploadExternal files.completeUploadExternal} allows for multiple
* files to be uploaded with a message (`initial_comment`), and as a threaded message (`thread_ts`)
* In order to be grouped together, file uploads must have like properties.
Expand All @@ -232,13 +234,14 @@ export function getAllFileUploadsToComplete(
): Record<string, FilesCompleteUploadExternalArguments> {
const toComplete: Record<string, FilesCompleteUploadExternalArguments> = {};
for (const upload of fileUploads) {
const { channel_id, thread_ts, initial_comment, file_id, title } = upload;
const { blocks, channel_id, thread_ts, initial_comment, file_id, title } = upload;
if (file_id) {
const compareString = `:::${channel_id}:::${thread_ts}:::${initial_comment}`;
const compareString = `:::${channel_id}:::${thread_ts}:::${initial_comment}:::${JSON.stringify(blocks)}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch ๐Ÿš€

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! ๐Ÿ˜Œ

I'm sometimes thankful to use manual testing during development, but I also thinking improving tests according to an above comment could show this change too ๐Ÿš€

if (!Object.prototype.hasOwnProperty.call(toComplete, compareString)) {
toComplete[compareString] = {
files: [{ id: file_id, title }],
channel_id,
blocks,
initial_comment,
};
if (thread_ts && channel_id) {
Expand Down Expand Up @@ -425,7 +428,7 @@ export function buildMultipleChannelsErrorMsg(): string {

export function buildInvalidFilesUploadParamError(): string {
return (
'You may supply file_uploads only for a single channel, comment, thread respectively. ' +
'Therefore, please supply any channel_id, initial_comment, thread_ts in the top-layer.'
'You may supply file_uploads only for a single channel, message, or thread respectively. ' +
'Therefore, please supply any channel_id, initial_comment or blocks, or thread_ts in the top-layer.'
);
}
23 changes: 21 additions & 2 deletions packages/web-api/src/types/request/files.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Stream } from 'node:stream';
import type { Block, KnownBlock } from '@slack/types';
import type { ExcludeFromUnion } from '../helpers';
import type { FilesGetUploadURLExternalResponse } from '../response/index';
import type {
Expand Down Expand Up @@ -64,10 +65,19 @@ type FileDestinationArgumentChannels = FileChannelDestinationArgumentChannels |
// https://api.slack.com/methods/files.completeUploadExternal
export type FilesCompleteUploadExternalArguments = FileDestinationArgument &
TokenOverridable & {
/** @description Array of file IDs and their corresponding (optional) titles. */
/**
* @description Array of file IDs and their corresponding (optional) titles.
* @example [{"id":"F044GKUHN9Z", "title":"slack-test"}]
**/
files: [FileUploadComplete, ...FileUploadComplete[]];
/** @description The message text introducing the file in the specified channel. */
initial_comment?: string;
/**
* @description An array of structured rich text blocks. If the `initial_comment` field is provided, the `blocks` field is ignored.
* @example [{"type": "section", "text": {"type": "plain_text", "text": "Hello world"}}]
* @see {@link https://api.slack.com/reference/block-kit/blocks}
*/
blocks?: (KnownBlock | Block)[];
};

// https://api.slack.com/methods/files.delete
Expand Down Expand Up @@ -148,6 +158,12 @@ export type FilesUploadArguments = FileUpload & TokenOverridable;
export type FileUploadV2 = FileUpload & {
/** @description Description of image for screen-reader. */
alt_text?: string;
/**
* @description An array of structured rich text blocks. If the `initial_comment` field is provided, the `blocks` field is ignored.
* @example [{"type": "section", "text": {"type": "plain_text", "text": "Hello world"}}]
* @see {@link https://api.slack.com/reference/block-kit/blocks}
*/
blocks?: (KnownBlock | Block)[];
/** @description Channel ID where the file will be shared. If not specified the file will be private. */
channel_id?: string;
/** @deprecated use channel_id instead */
Expand All @@ -157,7 +173,10 @@ export type FileUploadV2 = FileUpload & {
};

export interface FilesUploadV2ArgumentsMultipleFiles {
file_uploads: ExcludeFromUnion<FileUploadV2, 'channel_id' | 'channels' | 'initial_comment' | 'thread_ts'>[];
file_uploads: ExcludeFromUnion<
FileUploadV2,
'blocks' | 'channel_id' | 'channels' | 'initial_comment' | 'thread_ts'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alphabetical order ๐Ÿ˜ฎ ๐ŸŒŸ

>[];
}

// https://tools.slack.dev/node-slack-sdk/web-api#upload-a-file
Expand Down