@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({
|
const missingConnection = await tx.missingConnection.findUnique({
|
||||||
where: {
|
where: {
|
||||||
integrationId_connectionType_externalAccountId: {
|
integrationId_connectionType_accountIdentifier: {
|
||||||
integrationId: connection.integrationId,
|
integrationId: connection.integrationId,
|
||||||
connectionType: connection.connectionType,
|
connectionType: connection.connectionType,
|
||||||
externalAccountId: connection.externalAccount
|
accountIdentifier: connection.externalAccount
|
||||||
? connection.externalAccount.id
|
? connection.externalAccount.id
|
||||||
: "DEVELOPER",
|
: "DEVELOPER",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const slack: Integration = {
|
|||||||
authenticationMethods: {
|
authenticationMethods: {
|
||||||
oauth2Bot: {
|
oauth2Bot: {
|
||||||
name: "OAuth2 (Bot)",
|
name: "OAuth2 (Bot)",
|
||||||
description: "Authenticate as a bot",
|
description: "Authenticate as a bot. This is the recommended method.",
|
||||||
type: "oauth2",
|
type: "oauth2",
|
||||||
client: {
|
client: {
|
||||||
id: {
|
id: {
|
||||||
@@ -111,6 +111,7 @@ export const slack: Integration = {
|
|||||||
{
|
{
|
||||||
name: "channels:join",
|
name: "channels:join",
|
||||||
description: "Join public channels in a workspace",
|
description: "Join public channels in a workspace",
|
||||||
|
defaultChecked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "channels:manage",
|
name: "channels:manage",
|
||||||
@@ -141,17 +142,20 @@ export const slack: Integration = {
|
|||||||
{
|
{
|
||||||
name: "chat:write",
|
name: "chat:write",
|
||||||
description: "Post messages in approved channels & conversations",
|
description: "Post messages in approved channels & conversations",
|
||||||
|
defaultChecked: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "chat:write.customize",
|
name: "chat:write.customize",
|
||||||
description:
|
description:
|
||||||
"Send messages as @your_slack_app with a customized username and avatar",
|
"Send messages as @your_slack_app with a customized username and avatar",
|
||||||
|
defaultChecked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "chat:write.public",
|
name: "chat:write.public",
|
||||||
description:
|
description:
|
||||||
"Send messages to channels @your_slack_app isn't a member of",
|
"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",
|
name: "chat:write:bot",
|
||||||
description: "Send messages as your slack app",
|
description: "Send messages as your slack app",
|
||||||
|
defaultChecked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "chat:write:user",
|
name: "chat:write:user",
|
||||||
description: "Send messages on a user’s behalf",
|
description: "Send messages on a user’s behalf",
|
||||||
|
defaultChecked: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -195,16 +195,17 @@ export class StartRunService {
|
|||||||
missingConnections: {
|
missingConnections: {
|
||||||
connectOrCreate: missingConnections.map((connection) => ({
|
connectOrCreate: missingConnections.map((connection) => ({
|
||||||
where: {
|
where: {
|
||||||
integrationId_connectionType_externalAccountId: {
|
integrationId_connectionType_accountIdentifier: {
|
||||||
integrationId: connection.integration.id,
|
integrationId: connection.integration.id,
|
||||||
connectionType: connection.connectionType,
|
connectionType: connection.connectionType,
|
||||||
externalAccountId: connection.externalAccountId ?? "DEVELOPER",
|
accountIdentifier: connection.externalAccountId ?? "DEVELOPER",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
integrationId: connection.integration.id,
|
integrationId: connection.integration.id,
|
||||||
connectionType: connection.connectionType,
|
connectionType: connection.connectionType,
|
||||||
externalAccountId: connection.externalAccountId ?? "DEVELOPER",
|
accountIdentifier: connection.externalAccountId ?? "DEVELOPER",
|
||||||
|
externalAccountId: connection.externalAccountId,
|
||||||
resolved: false,
|
resolved: false,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { client } from "@/trigger";
|
import { client } from "@/trigger";
|
||||||
import { Slack } from "@trigger.dev/slack";
|
import { Slack } from "@trigger.dev/slack";
|
||||||
import { Job, cronTrigger } from "@trigger.dev/sdk";
|
import { Job, cronTrigger, eventTrigger } from "@trigger.dev/sdk";
|
||||||
|
|
||||||
const db = {
|
const db = {
|
||||||
getKpiSummary: async (date: Date) => {
|
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, {
|
new Job(client, {
|
||||||
id: "slack-kpi-summary",
|
id: "slack-kpi-summary",
|
||||||
@@ -33,3 +33,39 @@ new Job(client, {
|
|||||||
return response;
|
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 { client } from "@/trigger";
|
||||||
import "@/jobs/github";
|
// import "@/jobs/github";
|
||||||
import "@/jobs/openai";
|
// import "@/jobs/openai";
|
||||||
import "@/jobs/resend";
|
// import "@/jobs/resend";
|
||||||
import "@/jobs/general";
|
// import "@/jobs/general";
|
||||||
import "@/jobs/slack";
|
import "@/jobs/slack";
|
||||||
import "@/jobs/logging";
|
import "@/jobs/logging";
|
||||||
import { createPagesRoute } from "@trigger.dev/nextjs";
|
import { createPagesRoute } from "@trigger.dev/nextjs";
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { WebClient } from "@slack/web-api";
|
import { WebClient } from "@slack/web-api";
|
||||||
import type { IntegrationClient, TriggerIntegration } from "@trigger.dev/sdk";
|
import type { IntegrationClient, TriggerIntegration } from "@trigger.dev/sdk";
|
||||||
import { clientFactory } from "./client";
|
import { clientFactory } from "./client";
|
||||||
import { postMessage } from "./tasks";
|
import { joinConversation, postMessage } from "./tasks";
|
||||||
|
|
||||||
const tasks = {
|
const tasks = {
|
||||||
postMessage,
|
postMessage,
|
||||||
|
joinConversation,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SlackIntegrationOptions = {
|
export type SlackIntegrationOptions = {
|
||||||
|
|||||||
@@ -1,19 +1,85 @@
|
|||||||
|
import type {
|
||||||
|
Block,
|
||||||
|
KnownBlock,
|
||||||
|
MessageAttachment,
|
||||||
|
MessageMetadata,
|
||||||
|
WebAPIPlatformError,
|
||||||
|
} from "@slack/web-api";
|
||||||
import { clientFactory } from "./client";
|
import { clientFactory } from "./client";
|
||||||
import type { AuthenticatedTask } from "@trigger.dev/sdk";
|
import type { AuthenticatedTask } from "@trigger.dev/sdk";
|
||||||
|
|
||||||
type SlackClientType = ReturnType<typeof clientFactory>;
|
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<
|
export const postMessage: AuthenticatedTask<
|
||||||
ReturnType<typeof clientFactory>,
|
ReturnType<typeof clientFactory>,
|
||||||
{ text: string; channel: string },
|
ChatPostMessageArguments,
|
||||||
Awaited<ReturnType<SlackClientType["chat"]["postMessage"]>>
|
Awaited<ReturnType<SlackClientType["chat"]["postMessage"]>>
|
||||||
> = {
|
> = {
|
||||||
run: async (params, client) => {
|
run: async (params, client, task, io, auth) => {
|
||||||
return client.chat.postMessage({
|
try {
|
||||||
text: params.text,
|
const response = await client.chat.postMessage(params);
|
||||||
channel: params.channel,
|
|
||||||
link_names: true,
|
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) => {
|
init: (params) => {
|
||||||
return {
|
return {
|
||||||
@@ -25,9 +91,35 @@ export const postMessage: AuthenticatedTask<
|
|||||||
label: "Channel ID",
|
label: "Channel ID",
|
||||||
text: params.channel,
|
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",
|
label: "Channel ID",
|
||||||
text: params.text,
|
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
|
name String
|
||||||
instructions String?
|
instructions String?
|
||||||
description String?
|
description String?
|
||||||
packageName String @default("")
|
packageName String @default("")
|
||||||
|
|
||||||
authMethods IntegrationAuthMethod[]
|
authMethods IntegrationAuthMethod[]
|
||||||
Integration Integration[]
|
Integration Integration[]
|
||||||
@@ -137,7 +137,7 @@ model Integration {
|
|||||||
description String?
|
description String?
|
||||||
|
|
||||||
setupStatus IntegrationSetupStatus @default(COMPLETE)
|
setupStatus IntegrationSetupStatus @default(COMPLETE)
|
||||||
authSource IntegrationAuthSource @default(HOSTED)
|
authSource IntegrationAuthSource @default(HOSTED)
|
||||||
|
|
||||||
definition IntegrationDefinition @relation(fields: [definitionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
definition IntegrationDefinition @relation(fields: [definitionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
definitionId String
|
definitionId String
|
||||||
@@ -987,10 +987,13 @@ model MissingConnection {
|
|||||||
externalAccount ExternalAccount? @relation(fields: [externalAccountId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
externalAccount ExternalAccount? @relation(fields: [externalAccountId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
externalAccountId String?
|
externalAccountId String?
|
||||||
|
|
||||||
|
accountIdentifier String?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@unique([integrationId, connectionType, externalAccountId])
|
@@unique([integrationId, connectionType, externalAccountId])
|
||||||
|
@@unique([integrationId, connectionType, accountIdentifier])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ApiIntegrationVote {
|
model ApiIntegrationVote {
|
||||||
|
|||||||
Reference in New Issue
Block a user