← Cloudflare Workers / workers / tutorials
Создайте Slackbot
В этом руководстве вы создадите Slack ↗ бота с использованием Cloudflare Workers. Бот будет использовать GitHub webhooks для отправки сообщений в канал Slack при обновлении или создании issue, а также позволит пользователям с помощью команды искать issue GitHub прямо из Slack.
Это руководство рекомендуется тем, кто уже писал веб-приложения. Вы будете использовать TypeScript в качестве языка программирования и Hono ↗ в качестве веб-фреймворка. Если вы создавали приложение с помощью таких инструментов, как Node ↗ и Express ↗, этот проект покажется вам очень знакомым. Если вы никогда не писали веб-приложения или давно хотели создать что-то вроде Slack-бота, но вас останавливала сложность развёртывания или настройки, Workers станут для вас способом сосредоточиться на написании кода и выпуске проектов.
Если перед тем как приступить к этому руководству вы хотите посмотреть код или увидеть, как бот работает в реальном канале Slack, финальная версия кодовой базы доступна на GitHub ↗. Из GitHub можно добавить собственные ключи Slack API и развернуть приложение в своих каналах Slack для тестирования.
Прежде чем начать
Во всех руководствах предполагается, что вы уже выполнили Руководство по началу работы, который поможет вам настроить аккаунт Cloudflare Workers, C3 ↗, а также Wrangler.
Настройка Slack
Это руководство предполагает, что у вас уже есть аккаунт Slack и возможность создавать приложения Slack и управлять ими.
Настройка приложения Slack
Чтобы отправлять сообщения из своего Cloudflare Worker в канал Slack, нужно создать приложение в интерфейсе Slack. Для этого перейдите в раздел API Slack по адресу api.slack.com/apps ↗, и выберите Создать новое приложение.
Приложения Slack обладают множеством функций. Вы задействуете две из них, Incoming Webhooks и Slash Commands, чтобы создать Slack-бота на основе Worker.
Incoming Webhook
Incoming Webhooks предоставляют URL-адреса, с помощью которых можно отправлять сообщения в каналы Slack. Ваш incoming webhook будет связан с поддержкой webhook в GitHub, чтобы отправлять сообщения в канал Slack при каждом обновлении issue в выбранном репозитории. Подробнее код вы увидите по мере создания приложения. Сначала создайте webhook Slack:
- На боковой панели интерфейса Slack выберите Incoming Webhooks.
- В URL-адреса вебхуков для вашего Workspace, выберите Добавление нового Webhook в Workspace.
- На следующем экране выберите канал, в который webhook будет отправлять сообщения (можно выбрать комнату, например #general или #code, либо получать сообщения напрямую от Slack-бота при вызове webhook).
- Авторизуйте новый URL вебхука.
После авторизации URL webhook вы будете перенаправлены на Incoming Webhooks странице и увидите новый URL вебхука. Вы добавите его в код Workers позже. Далее вы добавите второй компонент своего Slack-бота: команду Slash Command.
Slash Command
Slash Command в Slack представляет собой настраиваемую команду, которую можно привязать к запросу URL. Например, если вы настроили /weather <zip>, Slack отправит запрос HTTP POST на настроенный URL, передав текст <zip> чтобы получить погоду для указанного почтового индекса. В своём приложении вы будете использовать /issue команду, чтобы искать issue в GitHub с помощью GitHub API ↗. Типизация /issue cloudflare/wrangler#1 отправит текст cloudflare/wrangler#1 в HTTP-запросе POST к вашему приложению, которое использует его для поиска соответствующий issue в GitHub ↗.
- На боковой панели Slack выберите Slash Commands.
- Создайте свою первую слэш-команду.
В этом руководстве вы будете использовать команду /issue. URL запроса должен быть /lookup путь по URL вашего приложения: например, если приложение будет размещено по адресу https://myworkerurl.com, URL запроса должен быть https://myworkerurl.com/lookup.
Настройка GitHub Webhooks
Ваше приложение на Cloudflare Workers сможет обрабатывать входящие запросы от Slack. Оно также должно уметь получать события напрямую от GitHub. Если issue в GitHub создаётся или обновляется, вы можете использовать GitHub webhooks, чтобы отправить это событие в приложение Workers и опубликовать соответствующее сообщение в Slack.
Чтобы настроить вебхук:
- В репозитории GitHub перейдите на страницу Настройки > Webhooks > Добавить webhook.
Если у вас есть репозиторий вида https://github.com/user/repo, вы можете получить доступ к Webhooks страницу напрямую по адресу https://github.com/user/repo/settings/hooks.
- Укажите в качестве Payload URL значение
/webhookпуть по URL вашего Worker.
Например, если ваш Worker будет размещён по адресу https://myworkerurl.com, URL полезной нагрузки должен быть https://myworkerurl.com/webhook.
- В Тип контента раскрывающемся списке выберите application/json.
Тип контента для вашей полезной нагрузки может быть либо полезная нагрузка в формате URL-encoded (application/x-www-form-urlencoded) или JSON (application/json). В рамках этого руководства и для упрощения разбора данных, отправляемых в приложение, выберите JSON.
- В Какие события должны запускать этот webhook?, выберите Выбрать события по отдельности.
Веб-хуки GitHub позволяют указать, какие события должны отправляться на ваш веб-хук. По умолчанию веб-хук отправляет push события из вашего репозитория. Для целей данного руководства вы выберете Выбрать события по отдельности.
- Выберите Проблемы тип события.
Для вашего вебхука можно включить множество различных типов событий. Выбор Проблемы будет отправлять на ваш webhook все события, связанные с issue, включая их создание, редактирование, удаление и другие. Если в будущем вы захотите расширить приложение Slack-бота, вы можете выбрать больше таких событий после завершения этого руководства.
- Выберите Добавить webhook.
После создания webhook он попытается отправить тестовую полезную нагрузку в ваше приложение. Поскольку приложение ещё не развёрнуто, оставьте настройки как есть. Позже вы вернётесь в свой репозиторий, чтобы создать, отредактировать и закрыть несколько issue и убедиться, что webhook работает после развёртывания приложения.
Инициализация
Чтобы инициировать проект, используйте интерфейс командной строки C3 (create-cloudflare-cli) ↗.
npm create cloudflare@latest -- slack-botЧтобы создать проект Hono, выполните следующие шаги.
- Для С чего вы хотите начать?, выберите
Framework Starter. - Для Какой фреймворк для разработки вы хотите использовать?, выберите
Hono. - Для, Хотите развернуть приложение?, выберите
No.
Перейдите в slack-bot каталог:
cd slack-botОткройте src/index.ts в редакторе, чтобы найти следующий код.
import { Hono } from "hono";
type Bindings = {
[key in keyof CloudflareBindings]: CloudflareBindings[key];
};
const app = new Hono<{ Bindings: Bindings }>();
app.get("/", (c) => {
return c.text("Hello Hono!");
});
export default app;Это минимальное приложение на основе Hono. Если GET запрос поступает по пути /, он вернёт ответ с текстом Hello Hono!. Он также возвращает сообщение 404 Not Found с кодом состояния 404, если запрашивается любой другой путь или метод.
Чтобы запустить приложение на локальном компьютере, выполните следующую команду.
npm i -- devДоступ к http://localhost:8787 в браузере после запуска сервера, и вы увидите сообщение.
Hono помогает быстро и легко создавать приложения Workers.
Build
Теперь создадим Slack-бота на Cloudflare Workers.
Разделение файлов
Приложение можно создавать в нескольких файлах, а не описывать все эндпоинты и функции в одном файле. В Hono есть возможность добавлять маршрутизацию дочерних приложений в родительское с помощью функции app.route().
Например, представьте следующее приложение Web API.
import { Hono } from "hono";
const app = new Hono();
app.get("/posts", (c) => c.text("Posts!"));
app.post("/posts", (c) => c.text("Created!", 201));
export default app;Маршруты можно добавить в /api/v1.
import { Hono } from "hono";
import api from "./api";
const app = new Hono();
app.route("/api/v1", api);
export default app;Возвращает Posts! при обращении к GET /api/v1/posts.
У Slack-бота будет два дочерних приложения, каждое из которых называется "route".
-
lookupмаршрут будет принимать запросы от Slack (отправляемые, когда пользователь использует/issueкоманду) и ищет соответствующий issue с помощью GitHub API. Это приложение будет добавлено в/lookupв основном приложении. -
webhookмаршрут будет вызываться при изменении issue в GitHub через настроенный вебхук. Это приложение будет добавлено в/webhookв основном приложении.
Создайте файлы маршрутов в каталоге с именем routes.
mkdir -p src/routes
touch src/routes/lookup.ts
touch src/routes/webhook.tsЗатем обновите основное приложение.
import { Hono } from "hono";
import lookup from "./routes/lookup";
import webhook from "./routes/webhook";
const app = new Hono();
app.route("/lookup", lookup);
app.route("/webhook", webhook);
export default app;Определение типов TypeScript
Прежде чем реализовывать сами функции, необходимо определить типы TypeScript, которые будут использоваться в этом проекте. Создайте в приложении новый файл по пути src/types.ts и напишите код. Bindings представляет собой тип, описывающий переменные окружения Cloudflare Workers. Issue представляет собой тип для GitHub issue, а User представляет собой тип для пользователя GitHub. Они понадобятся вам позже.
export type Bindings = {
SLACK_WEBHOOK_URL: string;
};
export type Issue = {
html_url: string;
title: string;
body: string;
state: string;
created_at: string;
number: number;
user: User;
};
type User = {
html_url: string;
login: string;
avatar_url: string;
};Создание маршрута поиска
Начните создавать маршрут поиска в src/routes/lookup.ts.
import { Hono } from "hono";
const app = new Hono();
export default app;Чтобы понять, как правильно спроектировать эту функцию, нужно разобраться, как слэш-команды Slack отправляют данные на URL.
Согласно документация по слэш-командам Slack ↗, Slack отправляет запрос HTTP POST на указанный вами URL с application/x-www-form-urlencoded тип содержимого. Например, если кто-то введёт /issue cloudflare/wrangler#1, вы можете ожидать данные в следующем формате:
token=gIkuvaNzQIHg97ATvDxqgjtO
&team_id=T0001
&team_domain=example
&enterprise_id=E0001
&enterprise_name=Globular%20Construct%20Inc
&channel_id=C2147483705
&channel_name=test
&user_id=U2147483697
&user_name=Steve
&command=/issue
&text=cloudflare/wrangler#1
&response_url=https://hooks.slack.com/commands/1234/5678
&trigger_id=13345224609.738474920.8088930838d88f008e0Имея это тело запроса, разберите его и получите значение text ключ. С его помощью text, например, cloudflare/wrangler#1, вы можете разобрать эту строку на известные части данных (owner, repo, а также issue_number), и используйте его для отправки запроса к API GitHub, чтобы получить данные issue.
С помощью slash-команд Slack можно отвечать на входящую slash-команду, возвращая структурированные данные. В этом случае нужно использовать ответ API GitHub, чтобы представить отформатированную версию issue GitHub, включая такие данные, как заголовок issue, автора и дату создания. Новый Block Kit ↗ фреймворк позволит вам возвращать развёрнутый ответ с сообщением, формируя текстовые и графические блоки на основе данных из API GitHub.
Разбор слэш-команд
Для начала lookup маршрут должен разбирать сообщения, поступающие из Slack. Как упоминалось ранее, API Slack отправляет HTTP POST в формате URL Encoded. Вы можете получить переменную text разобрав его с помощью c.req.json().
import { Hono } from "hono";
const app = new Hono();
app.post("/", async (c) => {
const { text } = await c.req.parseBody();
if (typeof text !== "string") {
return c.notFound();
}
});
export default app;Имея text переменную, которая содержит текст вроде cloudflare/wrangler#1, вам следует разобрать этот текст и получить из него отдельные части для использования с API GitHub: owner, repo, а также issue_number.
Для этого создайте в приложении новый файл по пути src/utils/github.ts. Этот файл будет содержать несколько «служебных» функций для работы с API GitHub. Первой из них будет парсер строк с именем parseGhIssueString:
const ghIssueRegex =
/(?<owner>[\w.-]*)\/(?<repo>[\w.-]*)\#(?<issue_number>\d*)/;
export const parseGhIssueString = (text: string) => {
const match = text.match(ghIssueRegex);
return match ? (match.groups ?? {}) : {};
};parseGhIssueString принимает text ввод, сопоставляет его с ghIssueRegex, и если совпадение найдено, возвращает groups объект из этого совпадения, используя owner, repo, а также issue_number групп захвата, определённых в регулярном выражении. Экспортируя эту функцию из src/utils/github.ts, вы можете снова использовать его в src/handlers/lookup.ts:
import { Hono } from "hono";
import { parseGhIssueString } from "../utils/github";
const app = new Hono();
app.post("/", async (c) => {
const { text } = await c.req.parseBody();
if (typeof text !== "string") {
return c.notFound();
}
const { owner, repo, issue_number } = parseGhIssueString(text);
});
export default app;Выполнение запросов к API GitHub
Имея эти данные, можно сделать первый запрос к API GitHub. Снова создайте новую функцию в src/utils/github.ts, чтобы выполнить fetch запрос к GitHub API за данными issue:
const ghIssueRegex =
/(?<owner>[\w.-]*)\/(?<repo>[\w.-]*)\#(?<issue_number>\d*)/;
export const parseGhIssueString = (text: string) => {
const match = text.match(ghIssueRegex);
return match ? (match.groups ?? {}) : {};
};
export const fetchGithubIssue = (
owner: string,
repo: string,
issue_number: string,
) => {
const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}`;
const headers = { "User-Agent": "simple-worker-slack-bot" };
return fetch(url, { headers });
};Ранее в src/handlers/lookup.ts, используйте fetchGitHubIssue чтобы выполнить запрос к API GitHub и разобрать ответ:
import { Hono } from "hono";
import { fetchGithubIssue, parseGhIssueString } from "../utils/github";
import { Issue } from "../types";
const app = new Hono();
app.post("/", async (c) => {
const { text } = await c.req.parseBody();
if (typeof text !== "string") {
return c.notFound();
}
const { owner, repo, issue_number } = parseGhIssueString(text);
const response = await fetchGithubIssue(owner, repo, issue_number);
const issue = await response.json<Issue>();
});
export default app;Формирование сообщения Slack
После получения ответа от API GitHub последним шагом будет сформировать сообщение Slack с данными issue и вернуть его пользователю. Итоговый результат будет выглядеть примерно так:
На скриншоте выше видно четыре элемента:
- Первая строка (выделена жирным) содержит ссылку на issue и показывает его заголовок
- Следующие строки (включая фрагменты кода) составляют тело issue
- В последней строке текста указаны статус issue, автор issue (со ссылкой на профиль пользователя на GitHub) и дата создания issue
- Фото профиля автора issue, справа
Упомянутый ранее Block Kit ↗ фреймворк поможет взять данные issue (в структуре, описанной в Документация REST API GitHub ↗) и отформатировать её примерно так, как показано на скриншоте выше.
Создайте еще один файл, src/utils/slack.ts, чтобы содержать функцию constructGhIssueSlackMessage, функция, которая принимает данные issue и преобразует их в набор блоков (blocks). Блоки представляют собой объекты JavaScript, которые Slack использует для форматирования сообщения:
import { Issue } from "../types";
export const constructGhIssueSlackMessage = (
issue: Issue,
issue_string: string,
prefix_text?: string,
) => {
const issue_link = `<${issue.html_url}|${issue_string}>`;
const user_link = `<${issue.user.html_url}|${issue.user.login}>`;
const date = new Date(Date.parse(issue.created_at)).toLocaleDateString();
const text_lines = [
prefix_text,
`*${issue.title} - ${issue_link}*`,
issue.body,
`*${issue.state}* - Created by ${user_link} on ${date}`,
];
};Сообщения Slack поддерживают вариант Markdown, где полужирный текст оформляется с помощью звёздочек (*bolded text*), а также ссылки в формате <https://yoururl.com|Display Text>.
Используя этот формат, создайте issue_link, который принимает html_url свойство из GitHub API issue данные (в формате https://github.com/cloudflare/wrangler-legacy/issues/1), и issue_string отправленные из слэш-команды Slack, и объединяет их в кликабельную ссылку в сообщении Slack.
user_link аналогичен и использует issue.user.html_url (в формате https://github.com/signalnerve, пользователь GitHub) и имя пользователя GitHub (issue.user.login), чтобы создать кликабельную ссылку на пользователя GitHub.
Наконец, разберите issue.created_at, строку ISO 8601, преобразуйте ее в экземпляр JavaScript Date, и преобразует её в отформатированную строку в формате MM/DD/YY.
Когда эти переменные заданы, text_lines представляет собой массив, каждый элемент которого содержит одну строку текста сообщения Slack. Первая строка представляет собой заголовок issue и ссылка на issue, а вторая это тело issue, а последней строкой является статус issue (например, open или closed), ссылка пользователя, и дата создания.
Когда текст сформирован, можно наконец собрать сообщение Slack, вернув массив блоков для Block Kit ↗. В этом случае есть только один блок: раздел ↗ блок с текстом Markdown и дополнительным изображением пользователя, создавшего issue. Верните этот единственный блок внутри массива, чтобы завершить constructGhIssueSlackMessage функция:
import { Issue } from "../types";
export const constructGhIssueSlackMessage = (
issue: Issue,
issue_string: string,
prefix_text?: string,
) => {
const issue_link = `<${issue.html_url}|${issue_string}>`;
const user_link = `<${issue.user.html_url}|${issue.user.login}>`;
const date = new Date(Date.parse(issue.created_at)).toLocaleDateString();
const text_lines = [
prefix_text,
`*${issue.title} - ${issue_link}*`,
issue.body,
`*${issue.state}* - Created by ${user_link} on ${date}`,
];
return [
{
type: "section",
text: {
type: "mrkdwn",
text: text_lines.join("\n"),
},
accessory: {
type: "image",
image_url: issue.user.avatar_url,
alt_text: issue.user.login,
},
},
];
};Завершение маршрута поиска
В src/handlers/lookup.ts, используйте constructGhIssueSlackMessage чтобы создать blocks, и вернуть их в виде нового ответа с c.json() при вызове слэш команды:
import { Hono } from "hono";
import { fetchGithubIssue, parseGhIssueString } from "../utils/github";
import { constructGhIssueSlackMessage } from "../utils/slack";
import { Issue } from "../types";
const app = new Hono();
app.post("/", async (c) => {
const { text } = await c.req.parseBody();
if (typeof text !== "string") {
return c.notFound();
}
const { owner, repo, issue_number } = parseGhIssueString(text);
const response = await fetchGithubIssue(owner, repo, issue_number);
const issue = await response.json<Issue>();
const blocks = constructGhIssueSlackMessage(issue, text);
return c.json({
blocks,
response_type: "in_channel",
});
});
export default app;Еще один дополнительный параметр, передаваемый в ответ, это response_type. По умолчанию ответы на слэш-команды являются эфемерными: их видит только пользователь, отправивший команду. Если передать response_type in_channel, как показано выше, приведёт к тому, что ответ появится у всех пользователей в канале.
Если вы хотите, чтобы сообщения оставались приватными, удалите response_type строку. Это приведёт к тому, что response_type по умолчанию принимать значение ephemeral.
Обработка ошибок
lookup маршрут почти готов, но в нем возможен ряд ошибок: например, при разборе тела запроса от Slack, при получении issue из GitHub или при формировании самого сообщения для Slack. Хотя приложения на Hono обрабатывают ошибки без дополнительных действий с вашей стороны, вы можете настроить возвращаемый ответ следующим образом.
import { Hono } from "hono";
import { fetchGithubIssue, parseGhIssueString } from "../utils/github";
import { constructGhIssueSlackMessage } from "../utils/slack";
import { Issue } from "../types";
const app = new Hono();
app.post("/", async (c) => {
const { text } = await c.req.parseBody();
if (typeof text !== "string") {
return c.notFound();
}
const { owner, repo, issue_number } = parseGhIssueString(text);
const response = await fetchGithubIssue(owner, repo, issue_number);
const issue = await response.json<Issue>();
const blocks = constructGhIssueSlackMessage(issue, text);
return c.json({
blocks,
response_type: "in_channel",
});
});
app.onError((_e, c) => {
return c.text(
"Uh-oh! We couldn't find the issue you provided. " +
"We can only find public issues in the following format: `owner/repo#issue_number`.",
);
});
export default app;Создание маршрута webhook
Вы уже прошли половину пути в реализации маршрутов для приложения Workers. При реализации следующего маршрута src/routes/webhook.ts, вы повторно используете большую часть кода, уже написанного для маршрута поиска.
В начале этого руководства вы настроили webhook GitHub для отслеживания событий, связанных с issue в вашем репозитории. Например, когда issue открывается, вызывается функция, соответствующая пути /webhook в вашем приложении Workers должно принимать данные, отправленные из GitHub, и публиковать новое сообщение в настроенном канале Slack.
В src/routes/webhook.ts, определите пустое приложение Hono. Отличие от lookup маршрут заключается в том, что Bindings передается как параметр обобщенного типа для new Hono(). Это необходимо, чтобы присвоить подходящий тип TypeScript SLACK_WEBHOOK_URL который будет использован позже.
import { Hono } from "hono";
import { Bindings } from "../types";
const app = new Hono<{ Bindings: Bindings }>();
export default app;Как и в случае с lookup маршрут, вам нужно будет разобрать входящий payload внутри request, получите из него нужные данные issue (см. документация GitHub API о IssueEvent ↗ для полной схемы полезной нагрузки), а также отправить отформатированное сообщение в Slack с указанием произошедших изменений. Итоговая версия будет выглядеть примерно так:
Сравните этот формат сообщения с форматом, который возвращается, когда пользователь использует /issue слэш-команды. Вы увидите, что между ними есть только одно фактическое различие: добавление текста действия в первой строке в формате An issue was $action:. Это действие, отправляемое как часть IssueEvent от GitHub, будут использованы при формировании хорошо знакомого набора блоков с помощью Slack Block Kit.
Разбор данных события
Чтобы начать заполнять маршрут, разберите тело запроса, отформатированное как JSON, в объект и создайте несколько вспомогательных переменных:
import { Hono } from "hono";
import { constructGhIssueSlackMessage } from "../utils/slack";
const app = new Hono();
app.post("/", async (c) => {
const { action, issue, repository } = await c.req.json();
const prefix_text = `An issue was ${action}:`;
const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`;
});
export default app;Одна IssueEvent, полезная нагрузка, отправляемая GitHub в рамках настройки вебхука, включает action (что произошло с issue: например, он был открыт, закрыт, заблокирован и так далее), issue сам по себе, и repository, среди прочего.
Используйте c.req.json() чтобы преобразовать тело запроса из JSON в обычный объект JS. Используйте деструктуризацию ES6, чтобы задать action, issue и repository как переменные, которые можно использовать в коде. prefix_text представляет собой строку, указывающую, что произошло с issue, и issue_string это привычная строка owner/repo#issue_number который вы уже видели: пока lookup маршрут напрямую использовал текст, отправленный из Slack, для заполнения issue_string, вы создадите его напрямую на основе данных, переданных в теле JSON.
Формирование и отправка сообщения Slack
Сообщения, которые ваш Slack-бот отправляет обратно в канал Slack из lookup и webhook маршруты очень похожи друг на друга. Благодаря этому вы можете повторно использовать существующий constructGhIssueSlackMessage чтобы продолжить заполнение src/handlers/webhook.ts. Импортируйте функцию из src/utils/slack.ts, и передать в неё данные issue:
import { Hono } from "hono";
import { constructGhIssueSlackMessage } from "../utils/slack";
const app = new Hono();
app.post("/", async (c) => {
const { action, issue, repository } = await c.req.json();
const prefix_text = `An issue was ${action}:`;
const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`;
const blocks = constructGhIssueSlackMessage(issue, issue_string, prefix_text);
});
export default app;Важно, что использование constructGhIssueSlackMessage в этом обработчике добавляет функции один дополнительный аргумент, prefix_text. Обновите соответствующую функцию внутри src/utils/slack.ts, добавив prefix_text в коллекцию text_lines в блоке сообщения, если оно было передано в функцию.
Добавьте служебную функцию, compact, который принимает массив и отфильтровывает все null или undefined значения из него. Эта функция будет использоваться для удаления prefix_text от text_lines если оно фактически не было передано в функцию, например при вызове из src/handlers/lookup.ts. Полная (и окончательная) версия src/utils/slack.ts выглядит следующим образом:
import { Issue } from "../types";
const compact = (array: unknown[]) => array.filter((el) => el);
export const constructGhIssueSlackMessage = (
issue: Issue,
issue_string: string,
prefix_text?: string,
) => {
const issue_link = `<${issue.html_url}|${issue_string}>`;
const user_link = `<${issue.user.html_url}|${issue.user.login}>`;
const date = new Date(Date.parse(issue.created_at)).toLocaleDateString();
const text_lines = [
prefix_text,
`*${issue.title} - ${issue_link}*`,
issue.body,
`*${issue.state}* - Created by ${user_link} on ${date}`,
];
return [
{
type: "section",
text: {
type: "mrkdwn",
text: compact(text_lines).join("\n"),
},
accessory: {
type: "image",
image_url: issue.user.avatar_url,
alt_text: issue.user.login,
},
},
];
};Ранее в src/handlers/webhook.ts, blocks которые возвращаются из constructGhIssueSlackMessage становится телом нового fetch запрос, HTTP-запрос методом POST на URL вебхука Slack. После завершения этого запроса верните ответ с кодом состояния 200, а текст тела "OK":
import { Hono } from "hono";
import { constructGhIssueSlackMessage } from "../utils/slack";
import { Bindings } from "../types";
const app = new Hono<{ Bindings: Bindings }>();
app.post("/", async (c) => {
const { action, issue, repository } = await c.req.json();
const prefix_text = `An issue was ${action}:`;
const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`;
const blocks = constructGhIssueSlackMessage(issue, issue_string, prefix_text);
const fetchResponse = await fetch(c.env.SLACK_WEBHOOK_URL, {
body: JSON.stringify({ blocks }),
method: "POST",
headers: { "Content-Type": "application/json" },
});
return c.text("OK");
});
export default app;Константа SLACK_WEBHOOK_URL представляет собой Slack Webhook URL, который вы создали ещё в начале, в разделе Incoming Webhook раздел этого руководства.
Чтобы использовать эту константу в своём коде, используйте wrangler secret команда:
npx wrangler secret put SLACK_WEBHOOK_URLEnter a secret value: https://hooks.slack.com/services/abc123Обработка ошибок
Так же, как и в lookup маршрут, webhook маршрут должен включать базовую обработку ошибок. В отличие от lookup, который отправляет ответы напрямую обратно в Slack: если с вашим webhook что-то пойдёт не так, может быть полезно сгенерировать ошибочный ответ и вернуть его в GitHub.
Для этого напишите пользовательский обработчик ошибок с помощью app.onError() и вернуть новый ответ с кодом состояния 500. Итоговая версия src/routes/webhook.ts выглядит следующим образом:
import { Hono } from "hono";
import { constructGhIssueSlackMessage } from "../utils/slack";
import { Bindings } from "../types";
const app = new Hono<{ Bindings: Bindings }>();
app.post("/", async (c) => {
const { action, issue, repository } = await c.req.json();
const prefix_text = `An issue was ${action}:`;
const issue_string = `${repository.owner.login}/${repository.name}#${issue.number}`;
const blocks = constructGhIssueSlackMessage(issue, issue_string, prefix_text);
const fetchResponse = await fetch(c.env.SLACK_WEBHOOK_URL, {
body: JSON.stringify({ blocks }),
method: "POST",
headers: { "Content-Type": "application/json" },
});
if (!fetchResponse.ok) throw new Error();
return c.text("OK");
});
app.onError((_e, c) => {
return c.json(
{
message: "Unable to handle webhook",
},
500,
);
});
export default app;Развернуть
Выполнив предыдущие шаги, вы завершили написание кода для Slack-бота. Теперь вы можете развернуть приложение.
Wrangler имеет встроенную поддержку сборки, загрузки и релиза приложения Cloudflare Workers. Для этого выполните следующую команду, которая соберёт и развернёт ваш код.
npm i -- deployТеперь при развертывании приложения Workers обновления issue должны начать появляться в вашем Slack-канале, поскольку GitHub webhook теперь может успешно обращаться к маршруту webhook вашего Workers:
Дополнительные материалы
В этом руководстве вы создали и развернули приложение Cloudflare Workers, которое реагирует на события webhook GitHub и позволяет выполнять запросы к GitHub API прямо из Slack. Чтобы посмотреть полный исходный код приложения, вы найдёте репозиторий на GitHub ↗.
Если вы хотите начать создавать собственные проекты, ознакомьтесь с существующим списком Шаблоны для быстрого старта.