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
91 changes: 44 additions & 47 deletions slack/actions/dms/send.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AppContext } from "../../mod.ts";
import { SlackMessage, SlackResponse } from "../../client.ts";

export interface SendDmProps {
/**
Expand All @@ -17,22 +18,6 @@ export interface SendDmProps {
blocks?: unknown[];
}

export interface SendDmResponse {
success: boolean;
message: string;
channelId?: string;
ts?: string;
messageData?: {
ok: boolean;
channel: string;
ts: string;
warning?: string;
response_metadata?: {
warnings?: string[];
};
};
}

/**
* @name DMS_SEND
* @title Send DM
Expand All @@ -43,45 +28,57 @@ export default async function sendDm(
props: SendDmProps,
_req: Request,
ctx: AppContext,
): Promise<SendDmResponse> {
): Promise<
SlackResponse<{
channel: string;
ts: string;
message: SlackMessage;
warning?: string;
}>
> {
try {
// Send message directly to the user ID (Slack automatically opens DM channel)
const messageResponse = await ctx.slack.postMessage(
props.userId,
props.text,
{
blocks: props.blocks,
},
);
// First, open/get the DM channel with the user
const channelResponse = await ctx.slack.openDmChannel(props.userId);

if (!channelResponse.ok) {
return {
ok: false,
error: channelResponse.error || "Failed to open DM channel",
data: {
channel: "",
ts: "",
message: {} as SlackMessage,
},
};
}

if (!messageResponse.ok) {
const channelId = channelResponse.data.channel?.id;
if (!channelId) {
return {
success: false,
message: `Failed to send DM: ${
messageResponse.error || "Unknown error"
}`,
ok: false,
error: "No channel ID returned from conversations.open",
data: {
channel: "",
ts: "",
message: {} as SlackMessage,
},
};
}

return {
success: true,
message: "DM sent successfully",
channelId: messageResponse.data.channel,
ts: messageResponse.data.ts,
messageData: {
ok: messageResponse.ok,
channel: messageResponse.data.channel,
ts: messageResponse.data.ts,
warning: messageResponse.data.warning,
response_metadata: messageResponse.response_metadata,
},
};
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
// Now send the message to the DM channel
return await ctx.slack.postMessage(channelId, props.text, {
blocks: props.blocks,
});
} catch (error) {
console.error("Error sending DM:", error);
return {
success: false,
message: `Error sending DM: ${message}`,
ok: false,
error: error instanceof Error ? error.message : "Unknown error",
data: {
channel: "",
ts: "",
message: {} as SlackMessage,
},
};
}
}
150 changes: 19 additions & 131 deletions slack/actions/files/upload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AppContext } from "../../mod.ts";
import { SlackFile } from "../../client.ts";
import { SlackResponse } from "../../client.ts";

export interface UploadFileProps {
/**
Expand Down Expand Up @@ -27,162 +27,50 @@ export interface UploadFileProps {
*/
initial_comment?: string;

/**
* @description File type (optional, usually auto-detected) - only used with legacy API
*/
filetype?: string;

/**
* @description Thread timestamp to upload file to a specific thread
*/
thread_ts?: string;

/**
* @description Use legacy files.upload API instead of the new V2 API (not recommended)
* @default false
*/
useLegacyApi?: boolean;
}

export interface UploadFileResponse {
ok: boolean;
files?: Array<{
id: string;
title?: string;
name?: string;
mimetype?: string;
filetype?: string;
permalink?: string;
url_private?: string;
}>;
// For compatibility, expose a safe subset of SlackFile
file?: Pick<
SlackFile,
| "id"
| "name"
| "title"
| "mimetype"
| "filetype"
| "permalink"
| "url_private"
>;
error?: string;
warning?: string;
response_metadata?: {
warnings?: string[];
};
}

/**
* @name FILES_UPLOAD
* @title Upload File
* @description Uploads a file to Slack using the new V2 API by default, with fallback to legacy API
* @description Uploads a file to Slack using the new V2 API
* @action upload-file
*/
export default async function uploadFile(
props: UploadFileProps,
_req: Request,
ctx: AppContext,
): Promise<UploadFileResponse> {
): Promise<
SlackResponse<{
files: Array<{
id: string;
title?: string;
name?: string;
mimetype?: string;
filetype?: string;
permalink?: string;
url_private?: string;
}>;
}>
> {
try {
// Use V2 API by default unless explicitly requested to use legacy
if (!props.useLegacyApi) {
const response = await ctx.slack.uploadFileV2({
channels: props.channels,
file: props.file,
filename: props.filename,
title: props.title,
thread_ts: props.thread_ts,
initial_comment: props.initial_comment,
});

if (!response.ok) {
return {
ok: false,
files: [],
error: response.error || "Failed to upload file with V2 API",
};
}

const first = response.data?.files?.[0];
return {
ok: response.ok,
files: response.data?.files?.map((f) => ({
id: f.id,
name: f.name ?? props.filename,
title: f.title ?? props.title ?? props.filename,
mimetype: f.mimetype ?? "",
filetype: f.filetype ?? "",
permalink: f.permalink ?? "",
url_private: f.url_private ?? "",
})) ?? [],
// For backwards compatibility, expose only safe subset of SlackFile
file: first
? {
id: first.id,
name: first.name ?? props.filename,
title: first.title ?? props.title ?? props.filename,
mimetype: first.mimetype ?? "",
filetype: first.filetype ?? "",
permalink: first.permalink ?? "",
url_private: first.url_private ?? "",
}
: undefined,
response_metadata: response.response_metadata,
};
}

// Legacy API fallback
if (
typeof props.file !== "string" &&
!(typeof File !== "undefined" && props.file instanceof File)
) {
return {
ok: false,
error:
"Legacy API only supports string (base64) or File objects. Use V2 API for other file types.",
};
}

const response = await ctx.slack.uploadFile({
return await ctx.slack.uploadFileV2({
channels: props.channels,
file: props.file as string | File,
file: props.file,
filename: props.filename,
title: props.title,
initial_comment: props.initial_comment,
filetype: props.filetype,
thread_ts: props.thread_ts,
initial_comment: props.initial_comment,
});

if (!response.ok) {
return {
ok: false,
error: response.error || "Failed to upload file with legacy API",
};
}

return {
ok: response.ok,
file: response.data.file,
files: response.data.file
? [{
id: response.data.file.id,
name: response.data.file.name,
title: response.data.file.title,
mimetype: response.data.file.mimetype,
filetype: response.data.file.filetype,
permalink: response.data.file.permalink,
url_private: response.data.file.url_private,
}]
: [],
warning: response.data.warning,
response_metadata: response.response_metadata,
};
} catch (error) {
console.error("Error uploading file:", error);
return {
ok: false,
error: error instanceof Error ? error.message : "Unknown error",
data: { files: [] },
};
}
}
Loading
Loading