-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Slack bot: subscribe slash command (#96)
* feat: handle slack command * refactor: process591QueryUrl * chore: add inngest dev script * feat: subscription models and slash command * style: linting
- Loading branch information
Showing
11 changed files
with
267 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import url from "url"; | ||
|
||
export function extractSearchParams(urlString: string) { | ||
const parsedUrl = url.parse(urlString); | ||
return parsedUrl.search || ""; | ||
} | ||
|
||
export function appendRentalAPIParams(urlString: string) { | ||
const searchParams = new URLSearchParams(urlString); | ||
|
||
searchParams.set("is_format_data", "1"); | ||
searchParams.set("is_new_list", "1"); | ||
searchParams.set("type", "1"); | ||
|
||
return searchParams.toString(); | ||
} | ||
|
||
export function process591QueryUrl(urlString: string) { | ||
const search = extractSearchParams(urlString); | ||
|
||
return appendRentalAPIParams(search); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,15 @@ | ||
import { config } from "@/lib/config"; | ||
import { dispatchAll, notifyLineNotification, notifySlackNotification } from "@/lib/notification"; | ||
import { slashCommand } from "@/lib/slackApp/commands"; | ||
import { fetchNewHousesFn, fetchNewHousesEvent } from "@/lib/tasks"; | ||
|
||
export const fns = [dispatchAll, notifyLineNotification, notifySlackNotification, fetchNewHousesFn, fetchNewHousesEvent]; | ||
export const fns = [ | ||
dispatchAll, | ||
notifyLineNotification, | ||
notifySlackNotification, | ||
config.cronEnabled ? fetchNewHousesFn : null, | ||
fetchNewHousesEvent, | ||
|
||
// slack commands | ||
slashCommand, | ||
].filter(Boolean); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { SlackAppInstallation } from "@prisma/client"; | ||
import { IncomingWebhook } from "@slack/webhook"; | ||
|
||
import { process591QueryUrl } from "@/lib/591House/utils"; | ||
import { config } from "@/lib/config"; | ||
import { inngest } from "@/lib/inngest/client"; | ||
import { prisma } from "@/lib/prisma"; | ||
|
||
type CommandHandler = (args: { | ||
webhook: IncomingWebhook; | ||
args: string; | ||
installation: SlackAppInstallation; | ||
}) => Promise<void>; | ||
|
||
const commandHandlers: Record<string, CommandHandler> = { | ||
subscribe: async ({ webhook, args, installation }) => { | ||
const firstNonWhitespace = /^[^\s]+/g; | ||
const match = args.match(firstNonWhitespace); | ||
|
||
if (!match) { | ||
throw new Error("No subscribe argument provided"); | ||
} | ||
|
||
const arg = match[0]; | ||
|
||
const installationToSubscriptions = await prisma.slackInstallationToSubscription.findMany({ | ||
where: { | ||
channelId: installation.incomingWebhookChannelId!, | ||
}, | ||
include: { | ||
subscription: true, | ||
}, | ||
}); | ||
|
||
switch (arg) { | ||
case "list": { | ||
const subscriptionUrls = installationToSubscriptions.map( | ||
(record) => record.subscription.query | ||
); | ||
|
||
if (subscriptionUrls.length === 0) { | ||
await webhook.send({ | ||
text: `You are not subscribed to any queries`, | ||
}); | ||
} else { | ||
// TODO: print subscription id to let user unsubscribe | ||
// Prepend 591 url with https://rent.591.com.tw/ | ||
await webhook.send({ | ||
text: `You are subscribed to the following queries: ${subscriptionUrls.join(", ")}`, | ||
}); | ||
} | ||
|
||
break; | ||
} | ||
default: { | ||
const query = process591QueryUrl(arg); | ||
|
||
// TODO: Check if query is valid 591 url | ||
const record = installationToSubscriptions.find( | ||
(record) => record.subscription.query === query | ||
); | ||
|
||
if (!record) { | ||
const subscription = await prisma.houseSubscription.create({ | ||
data: { | ||
query, | ||
SlackInstallationToSubscription: { | ||
create: { | ||
channelId: installation.incomingWebhookChannelId!, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
await webhook.send({ | ||
text: `You are now subscribed to ${subscription.query}`, | ||
}); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
}, | ||
unsubscribe: async ({ webhook }) => {}, | ||
help: async ({ webhook }) => {}, | ||
}; | ||
|
||
export const availableCommands = Object.keys(commandHandlers); | ||
|
||
export const slashCommand = inngest.createFunction( | ||
"Subscribe command", | ||
"slackCommands/slashHandler", | ||
async ({ event }) => { | ||
const { channelId, userId, command, args } = event.data; | ||
|
||
const installation = await prisma.slackAppInstallation.findFirst({ | ||
where: { | ||
incomingWebhookChannelId: channelId, | ||
userId, | ||
}, | ||
}); | ||
|
||
if (!installation || !installation.incomingWebhookUrl) { | ||
throw new Error("No installation found"); | ||
} | ||
|
||
const webhook = new IncomingWebhook(installation.incomingWebhookUrl!); | ||
|
||
const handler = commandHandlers[command]; | ||
|
||
if (!handler) { | ||
// TODO: Send help message | ||
throw new Error("No handler found"); | ||
} | ||
|
||
try { | ||
await handler({ webhook, args, installation }); | ||
} catch (error) { | ||
console.error(error); | ||
|
||
if (config.slackDevMode) { | ||
await webhook.send({ | ||
text: `Error: ${error}`, | ||
}); | ||
} | ||
} | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,53 @@ | ||
import { config } from "@/lib/config"; | ||
import { inngest } from "@/lib/inngest/client"; | ||
import { setupSlackApp } from "@/lib/slackApp"; | ||
import { availableCommands } from "@/lib/slackApp/commands"; | ||
|
||
export { appRunner } from "@/lib/slackApp"; | ||
|
||
setupSlackApp((app) => { | ||
app.command(config.slackSlashCommand || "/zuzugo", async ({ ack, command, say, client }) => { | ||
console.log(command.text); | ||
|
||
await client.chat.postMessage({ | ||
blocks: [ | ||
{ | ||
type: "section", | ||
text: { | ||
type: "mrkdwn", | ||
text: "*Hello*, _World!_", | ||
}, | ||
}, | ||
], | ||
channel: command.channel_id, | ||
const slashCommand = config.slackSlashCommand || "/zuzugo"; | ||
|
||
app.command(slashCommand, async ({ ack, command, client }) => { | ||
if (config.slackDevMode) { | ||
console.log("Slash command", command); | ||
} | ||
|
||
// TODO: Add show help command | ||
const handleInvalidCommand = async () => { | ||
await client.chat.postEphemeral({ | ||
channel: command.channel_id, | ||
text: "Invalid command", | ||
user: command.user_id, | ||
}); | ||
|
||
ack(); | ||
}; | ||
|
||
const { text } = command; | ||
const regex = /(?<cmd>\w+)(?:\s+(?<args>.*))?/; | ||
const match = text.match(regex); | ||
|
||
if (!match) { | ||
return handleInvalidCommand(); | ||
} | ||
|
||
const { cmd, args } = match.groups as { cmd: string; args: string }; | ||
if (!availableCommands.includes(cmd)) { | ||
return handleInvalidCommand(); | ||
} | ||
|
||
await inngest.send("slackCommands/slashHandler", { | ||
data: { | ||
channelId: command.channel_id, | ||
command: cmd, | ||
args, | ||
userId: command.user_id, | ||
}, | ||
}); | ||
|
||
ack(); | ||
await ack({ | ||
response_type: "in_channel", | ||
}); | ||
}); | ||
}); |
22 changes: 22 additions & 0 deletions
22
prisma/migrations/20230303144617_add_subscription_slack_relations/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
-- CreateTable | ||
CREATE TABLE "SlackInstallationToSubscription" ( | ||
"id" TEXT NOT NULL, | ||
"channel_id" TEXT NOT NULL, | ||
"houseSubscriptionId" TEXT NOT NULL, | ||
|
||
CONSTRAINT "SlackInstallationToSubscription_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateTable | ||
CREATE TABLE "HouseSubscription" ( | ||
"id" TEXT NOT NULL, | ||
"query" TEXT NOT NULL, | ||
|
||
CONSTRAINT "HouseSubscription_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "SlackInstallationToSubscription_channel_id_houseSubscriptio_key" ON "SlackInstallationToSubscription"("channel_id", "houseSubscriptionId"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "SlackInstallationToSubscription" ADD CONSTRAINT "SlackInstallationToSubscription_houseSubscriptionId_fkey" FOREIGN KEY ("houseSubscriptionId") REFERENCES "HouseSubscription"("id") ON DELETE RESTRICT ON UPDATE CASCADE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
463ff76
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
zuzugo – ./
zuzugo-yukaihuangtw.vercel.app
zuzugo.vercel.app
zuzugo-git-main-yukaihuangtw.vercel.app