@trigger.dev/slack: Added joinConversation task, and automatically try and join a public conversation if the bot is not in it when posting a message
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/slack": patch
|
||||
---
|
||||
|
||||
Added joinConversation task, and automatically try and join a public conversation if the bot is not in it when posting a message
|
||||
@@ -28,10 +28,10 @@ export class IntegrationConnectionCreatedService {
|
||||
|
||||
const missingConnection = await tx.missingConnection.findUnique({
|
||||
where: {
|
||||
integrationId_connectionType_externalAccountId: {
|
||||
integrationId_connectionType_accountIdentifier: {
|
||||
integrationId: connection.integrationId,
|
||||
connectionType: connection.connectionType,
|
||||
externalAccountId: connection.externalAccount
|
||||
accountIdentifier: connection.externalAccount
|
||||
? connection.externalAccount.id
|
||||
: "DEVELOPER",
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@ export const slack: Integration = {
|
||||
authenticationMethods: {
|
||||
oauth2Bot: {
|
||||
name: "OAuth2 (Bot)",
|
||||
description: "Authenticate as a bot",
|
||||
description: "Authenticate as a bot. This is the recommended method.",
|
||||
type: "oauth2",
|
||||
client: {
|
||||
id: {
|
||||
@@ -111,6 +111,7 @@ export const slack: Integration = {
|
||||
{
|
||||
name: "channels:join",
|
||||
description: "Join public channels in a workspace",
|
||||
defaultChecked: true,
|
||||
},
|
||||
{
|
||||
name: "channels:manage",
|
||||
@@ -141,17 +142,20 @@ export const slack: Integration = {
|
||||
{
|
||||
name: "chat:write",
|
||||
description: "Post messages in approved channels & conversations",
|
||||
defaultChecked: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "chat:write.customize",
|
||||
description:
|
||||
"Send messages as @your_slack_app with a customized username and avatar",
|
||||
defaultChecked: true,
|
||||
},
|
||||
{
|
||||
name: "chat:write.public",
|
||||
description:
|
||||
"Send messages to channels @your_slack_app isn't a member of",
|
||||
defaultChecked: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -591,10 +595,12 @@ export const slack: Integration = {
|
||||
{
|
||||
name: "chat:write:bot",
|
||||
description: "Send messages as your slack app",
|
||||
defaultChecked: true,
|
||||
},
|
||||
{
|
||||
name: "chat:write:user",
|
||||
description: "Send messages on a user’s behalf",
|
||||
defaultChecked: true,
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -195,16 +195,17 @@ export class StartRunService {
|
||||
missingConnections: {
|
||||
connectOrCreate: missingConnections.map((connection) => ({
|
||||
where: {
|
||||
integrationId_connectionType_externalAccountId: {
|
||||
integrationId_connectionType_accountIdentifier: {
|
||||
integrationId: connection.integration.id,
|
||||
connectionType: connection.connectionType,
|
||||
externalAccountId: connection.externalAccountId ?? "DEVELOPER",
|
||||
accountIdentifier: connection.externalAccountId ?? "DEVELOPER",
|
||||
},
|
||||
},
|
||||
create: {
|
||||
integrationId: connection.integration.id,
|
||||
connectionType: connection.connectionType,
|
||||
externalAccountId: connection.externalAccountId ?? "DEVELOPER",
|
||||
accountIdentifier: connection.externalAccountId ?? "DEVELOPER",
|
||||
externalAccountId: connection.externalAccountId,
|
||||
resolved: false,
|
||||
},
|
||||
})),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { client } from "@/trigger";
|
||||
import { Slack } from "@trigger.dev/slack";
|
||||
import { Job, cronTrigger } from "@trigger.dev/sdk";
|
||||
import { Job, cronTrigger, eventTrigger } from "@trigger.dev/sdk";
|
||||
|
||||
const db = {
|
||||
getKpiSummary: async (date: Date) => {
|
||||
@@ -11,7 +11,7 @@ const db = {
|
||||
},
|
||||
};
|
||||
|
||||
export const slack = new Slack({ id: "slack" });
|
||||
export const slack = new Slack({ id: "slack-6" });
|
||||
|
||||
new Job(client, {
|
||||
id: "slack-kpi-summary",
|
||||
@@ -33,3 +33,39 @@ new Job(client, {
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "slack-auto-join",
|
||||
name: "Slack Auto Join",
|
||||
version: "0.1.1",
|
||||
integrations: {
|
||||
slack,
|
||||
},
|
||||
trigger: eventTrigger({
|
||||
name: "slack.auto_join",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const response = await io.slack.postMessage("Slack 📝", {
|
||||
channel: "C05G130TH4G",
|
||||
text: "Welcome to the team, Eric!",
|
||||
blocks: [
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Welcome to the team, Eric!`,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `I'm here to help you get started with Trigger!`,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { client } from "@/trigger";
|
||||
import "@/jobs/github";
|
||||
import "@/jobs/openai";
|
||||
import "@/jobs/resend";
|
||||
import "@/jobs/general";
|
||||
// import "@/jobs/github";
|
||||
// import "@/jobs/openai";
|
||||
// import "@/jobs/resend";
|
||||
// import "@/jobs/general";
|
||||
import "@/jobs/slack";
|
||||
import "@/jobs/logging";
|
||||
import { createPagesRoute } from "@trigger.dev/nextjs";
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { WebClient } from "@slack/web-api";
|
||||
import type { IntegrationClient, TriggerIntegration } from "@trigger.dev/sdk";
|
||||
import { clientFactory } from "./client";
|
||||
import { postMessage } from "./tasks";
|
||||
import { joinConversation, postMessage } from "./tasks";
|
||||
|
||||
const tasks = {
|
||||
postMessage,
|
||||
joinConversation,
|
||||
};
|
||||
|
||||
export type SlackIntegrationOptions = {
|
||||
|
||||
@@ -1,19 +1,85 @@
|
||||
import type {
|
||||
Block,
|
||||
KnownBlock,
|
||||
MessageAttachment,
|
||||
MessageMetadata,
|
||||
WebAPIPlatformError,
|
||||
} from "@slack/web-api";
|
||||
import { clientFactory } from "./client";
|
||||
import type { AuthenticatedTask } from "@trigger.dev/sdk";
|
||||
|
||||
type SlackClientType = ReturnType<typeof clientFactory>;
|
||||
|
||||
export type ChatPostMessageArguments = {
|
||||
channel: string;
|
||||
text?: string;
|
||||
as_user?: boolean;
|
||||
attachments?: MessageAttachment[];
|
||||
blocks?: (KnownBlock | Block)[];
|
||||
icon_emoji?: string;
|
||||
icon_url?: string;
|
||||
metadata?: MessageMetadata;
|
||||
link_names?: boolean;
|
||||
mrkdwn?: boolean;
|
||||
parse?: "full" | "none";
|
||||
reply_broadcast?: boolean;
|
||||
thread_ts?: string;
|
||||
unfurl_links?: boolean;
|
||||
unfurl_media?: boolean;
|
||||
username?: string;
|
||||
};
|
||||
|
||||
function isPlatformError(error: unknown): error is WebAPIPlatformError {
|
||||
return (
|
||||
typeof error === "object" &&
|
||||
error !== null &&
|
||||
"code" in error &&
|
||||
typeof error.code === "string" &&
|
||||
error.code === "slack_webapi_platform_error"
|
||||
);
|
||||
}
|
||||
|
||||
export const postMessage: AuthenticatedTask<
|
||||
ReturnType<typeof clientFactory>,
|
||||
{ text: string; channel: string },
|
||||
ChatPostMessageArguments,
|
||||
Awaited<ReturnType<SlackClientType["chat"]["postMessage"]>>
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.chat.postMessage({
|
||||
text: params.text,
|
||||
channel: params.channel,
|
||||
link_names: true,
|
||||
});
|
||||
run: async (params, client, task, io, auth) => {
|
||||
try {
|
||||
const response = await client.chat.postMessage(params);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (isPlatformError(error)) {
|
||||
if (error.data.error === "not_in_channel") {
|
||||
// @ts-ignore
|
||||
const joinResponse = await io.runTask<ConversationsJoinResponse>(
|
||||
`Join ${params.channel}`,
|
||||
joinConversation.init(params),
|
||||
// @ts-ignore
|
||||
async (t, io) => {
|
||||
const subResponse = await joinConversation.run(
|
||||
{ channel: params.channel },
|
||||
client,
|
||||
t,
|
||||
io,
|
||||
auth
|
||||
);
|
||||
|
||||
return subResponse;
|
||||
}
|
||||
);
|
||||
|
||||
if (joinResponse.ok) {
|
||||
const response = await client.chat.postMessage(params);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
@@ -25,9 +91,35 @@ export const postMessage: AuthenticatedTask<
|
||||
label: "Channel ID",
|
||||
text: params.channel,
|
||||
},
|
||||
...(params.text ? [{ label: "Message", text: params.text }] : []),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
type ConversationsJoinResponse = Awaited<
|
||||
ReturnType<SlackClientType["conversations"]["join"]>
|
||||
>;
|
||||
|
||||
export const joinConversation: AuthenticatedTask<
|
||||
ReturnType<typeof clientFactory>,
|
||||
{ channel: string },
|
||||
ConversationsJoinResponse
|
||||
> = {
|
||||
run: async (params, client, task, io, auth) => {
|
||||
const response = await client.conversations.join(params);
|
||||
|
||||
return response;
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Join Channel",
|
||||
params,
|
||||
icon: "slack",
|
||||
properties: [
|
||||
{
|
||||
label: "Message",
|
||||
text: params.text,
|
||||
label: "Channel ID",
|
||||
text: params.channel,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[integrationId,connectionType,accountIdentifier]` on the table `MissingConnection` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "MissingConnection" ADD COLUMN "accountIdentifier" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "MissingConnection_integrationId_connectionType_accountIdent_key" ON "MissingConnection"("integrationId", "connectionType", "accountIdentifier");
|
||||
@@ -122,7 +122,7 @@ model IntegrationDefinition {
|
||||
name String
|
||||
instructions String?
|
||||
description String?
|
||||
packageName String @default("")
|
||||
packageName String @default("")
|
||||
|
||||
authMethods IntegrationAuthMethod[]
|
||||
Integration Integration[]
|
||||
@@ -137,7 +137,7 @@ model Integration {
|
||||
description String?
|
||||
|
||||
setupStatus IntegrationSetupStatus @default(COMPLETE)
|
||||
authSource IntegrationAuthSource @default(HOSTED)
|
||||
authSource IntegrationAuthSource @default(HOSTED)
|
||||
|
||||
definition IntegrationDefinition @relation(fields: [definitionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
definitionId String
|
||||
@@ -987,10 +987,13 @@ model MissingConnection {
|
||||
externalAccount ExternalAccount? @relation(fields: [externalAccountId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
externalAccountId String?
|
||||
|
||||
accountIdentifier String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([integrationId, connectionType, externalAccountId])
|
||||
@@unique([integrationId, connectionType, accountIdentifier])
|
||||
}
|
||||
|
||||
model ApiIntegrationVote {
|
||||
|
||||
Reference in New Issue
Block a user