Slack にファイルを送る GitHub Action を作成した
作成したもの
channel_id とファイルパスを渡すと
Slack チャンネルに対象のファイルをアップロードする action を生成しました。
作成背景
files.upload API が deprecated になってしまいました。
その代わりに、
を使うこととなりました。
非推奨になった背景については以下の記事が参考になりました。
この影響で、愛用していた https://github.com/adrey/slack-file-upload-action が、
中で呼んでいる API が files.upload
であったために、新規アプリでの利用ができなくなってしまいました。
ファイルアップの流れ
-
files.getUploadURLExternal
でファイルアップロードに利用する Upload 用の URL とファイルIDを取得し - Upload 用 URL にファイルを渡してアップロード
-
files.completeUploadExternal
で、1 で取得したファイルIDとチャンネルIDを渡してアップロードを完了
という流れです。
Create Action
慣れているため、TS で action を作成することにしました。
また、公式が出している Creating a JavaScript action が参考になりました。
Project の雛形作成は、以下の記事が参考になりました。
引数を受け取れるようにする
一旦はシンプルな実装を目的にしているため、最小限の引数のみで構成します。
-
token
Slack App の Token -
path
送りたいファイルのパス -
channel_id
送付されるチャンネルのID
name: 'yanskun/slack-file-upload-action'
description: "GitHub Action for uploading a file to Slack"
author: "yanskun"
branding:
icon: file
color: orange
inputs:
token:
description: Slack Bot Token
required: true
path:
description: file path
required: true
channel_id:
description: Slack Channel ID
required: true
runs:
using: "node20"
main: "dist/index.js"
取得方法は、上記の記事でも紹介されていますが、
import { getInput, setFailed } from "@actions/core";
const path = getInput("path");
if (!path) {
setFailed("No filename provided!");
}
const channelId = getInput("channel_id");
if (!channelId) {
setFailed("No channel ID provided!");
}
取得できなかったら、エラーを返すようにしています。
Slack Client を用意する
Slack API を呼び出すために、Slack SDK を install します
npm i @slack/web-api
そして Slack Client を呼び出します。
import { getInput, setFailed } from "@actions/core";
import { WebClient } from "@slack/web-api";
const token = getInput("token");
if (!token) {
setFailed("No token provided!");
}
export const slackClient = new WebClient(token);
Slack API を実行する
import { slackClient } from "./slackClient";
// 略
return slackClient.files.getUploadURLExternal({
length: fileSize,
filename,
});
こんな感じで呼び出すことができます。
file を用意する
ここは ts-node の File system を使って実装をします。
npm install @types/node
import * as fs from "fs";
import FormData from "form-data";
const filename = path.split("/").at(-1) ?? path;
const stats = fs.statSync(path);
const fileSize = stats.size;
const formData = new FormData();
formData.append("file", fs.createReadStream(path), filename);
fileSize
は files.getUploadURLExternal
で、
formData
は、 files.getUploadURLExternal
の Response である upload_url
を使った、POST Reqest に渡します。
また、upload_url
を使う箇所は Slack API とは違うため、
axios を使って POST API を実行します。
import axios from "axios";
await axios.post(postFileResult.upload_url, formData, {
headers: {
...formData.getHeaders(),
},
});
完成
あとは、これらを組み合わせてあげることで完成します。
import { getInput, setFailed } from "@actions/core";
import { slackClient } from "./slackClient";
import * as fs from "fs";
import axios from "axios";
import FormData from "form-data";
const path = getInput("path");
if (!path) {
setFailed("No filename provided!");
}
const channelId = getInput("channel_id");
if (!channelId) {
setFailed("No channel ID provided!");
}
const filename = path.split("/").at(-1) ?? path;
function postFile() {
let fileSize = 0;
try {
const stats = fs.statSync(path);
fileSize = stats.size;
console.log("File size: ", fileSize);
console.log("Filename: ", filename);
return slackClient.files.getUploadURLExternal({
length: fileSize,
filename,
});
} catch {
setFailed("Error getting file stats");
}
}
async function uploadFile(fileId: string) {
try {
slackClient.files.completeUploadExternal({
files: [
{
id: fileId ?? "",
title: filename,
},
],
channel_id: channelId,
});
} catch {
setFailed("Error uploading file");
}
}
async function main() {
const postFileResult = await postFile();
if (!postFileResult?.ok) {
setFailed("Error posting file");
}
const formData = new FormData();
formData.append("file", fs.createReadStream(path), filename);
if (!postFileResult?.upload_url) {
setFailed("Error getting upload URL");
} else {
try {
await axios.post(postFileResult.upload_url, formData, {
headers: {
...formData.getHeaders(),
},
});
console.log("File uploaded successfully");
} catch (error) {
console.error("Error uploading file:", error);
setFailed("Error uploading file");
}
}
if (!postFileResult?.file_id) {
setFailed("Error getting file ID");
} else {
uploadFile(postFileResult.file_id);
}
}
main().catch((error) => {
console.error("Error in main function:", error);
setFailed("Error in main function");
});
かなり汚いですが、こんな感じで仕上がりました。
最後に build を実行して Release して終わりです。
出来上がったものが以下です。
使い方
例えば、Nuxt などの Project でコーポレートサイトを構築している場合、
ドキュメントなどの更新に関しては、非技術者にも関心のあることだと思います。
その場合に、
CI で Release をトリガーに、Latest との差分を取得して、Slack に通知することで、
いつ変更があったか?などを通知することができます。
主に Repository 内にあるドキュメントを共有したい際に活用できるのではないかなと思っています。
もっと面白い使い方があれば、ぜひ教えてください!
Discussion