📤

Node-REDでSlackへ画像を投稿する(Slack API版)

に公開

Slackの「files.upload」APIが、2025年春に廃止されたため、Slackのファイルアップロード用APIを利用して画像を投稿する方法について説明します。

Slackの準備

Slackの準備は、以下の記事を参考にしてください。
https://zenn.dev/dsl_gunma/articles/9fc4e58783d7da

今回は、ワークスペース「Node-RED開発」のチャンネル「node-red_alert」に画像を投稿します。


SlackのファイルアップロードAPIについて

SlackのファイルアップロードAPIを利用して画像を送信するには、次の3つのステップを行います。

  1. files.getUploadURLExternal APIを呼び出してファイルのアップロードに使用するURLとファイルIDを取得する
  2. 取得したURLにPOSTリクエストを送信してファイルをアップロードする
  3. files.completeUploadExternal APIにファイルIDに加えてチャンネルIDなどを送信して、アップロード処理を完了させる

https://docs.slack.dev/messaging/working-with-files/#uploading_files


Node-REDによるフローの作成

Node-REDで、Slackへ画像を投稿するためのフローを作成します。
Node-REDのインストール方法は、以下のページを参考にしてください。
https://nodered.jp/docs/getting-started/local

ノードの設定

  1. Node-REDのワークスペースに以下のノードを追加し、図のように配置します。
    • injectノード
    • read fileノード
    • functionノード x 3
    • http requestノード x 3
    • debugノード

  1. read fileノードをダブルクリックします。ファイル名に送信したいローカルの画像の絶対パスを入力して、出力形式に「バイナリバッファ」を選択します。「完了」ボタンをクリックします。

  1. 各functionノードをダブルクリックして、次のようにコードを入力します。
  • function 1ノード

function 1ノード
// 設定項目
const token = "xoxb-xxxx-xxxx-xxxx";
const filename = "D:\logo\node-red-icon-2.png";

// 画像データ(Buffer)を一時保存し、ファイルサイズを取得
msg.imageBuffer = msg.payload;
const fileSize = msg.payload.length;

// Slackへのリクエスト設定
msg.url = "https://slack.com/api/files.getUploadURLExternal";
msg.method = "POST";
msg.headers = {
    "Authorization": "Bearer " + token,
    "Content-Type": "application/x-www-form-urlencoded"
};

// 送信データの設定
// filename: Slack上での名前、length: バイト数
msg.payload = {
    filename: filename,
    length: fileSize
};

return msg;
function 1ノードの説明

function 1ノードでは、アップロード用URLを取得します。

  • 定数 token: xoxb-から始まるSlackアプリのトークンを設定します。
  • 定数 filename: 送信する画像のパスを設定します。
  • msg.url: ファイルアップロードAPIを指定します。「files.getUploadURLExternal」はファイルをアップロードするURLを取得するAPIです。
  • msg.method: 「POST」リクエストを指定します。
  • msg.headers: 投稿する画像のパラメータを指定します。
    • Authorization: Bearer <Slackアプリのトークン>
  • msg.payload: 投稿する画像のパラメータを設定します。
    • filename: アップロードするファイルの名前
    • file: アップロードするファイルのサイズ
  • function 2ノード

function 2ノード
// Slackからのレスポンスをチェック
if (!msg.payload.ok) {
    node.error("URL取得失敗: " + msg.payload.error);
    return null;
}

// 後の完了報告のためにfile_idを保存
msg.fileId = msg.payload.file_id;

// 送信先をSlackが発行したupload_urlに変更
msg.url = msg.payload.upload_url;
msg.method = "POST";
msg.headers = {
    "Content-Type": "image/png"
};

// payloadを画像データ(バイナリ)に戻す
msg.payload = msg.imageBuffer;

return msg;
function 2ノードの説明

function 2ノードでは、取得したURLに画像をアップロードします。

  • msg.url: アップロード用URLを指定します。
  • msg.method: 「POST」リクエストを指定します。
  • msg.headers: 投稿する画像のパラメータを指定します。
    • Content-Type: 画像の種類
  • msg.payload: 投稿する画像のパラメータを設定します。
  • function 3ノード

function 3ノード
// 設定項目
const token = "xoxb-xxxx-xxxx-xxxx";
const channelId = "Cxxxxxxxxxx"; // 投稿先のチャンネルID

// Slackへのリクエスト設定
msg.url = "https://slack.com/api/files.completeUploadExternal";
msg.method = "POST";
msg.headers = {
    "Authorization": "Bearer " + token,
    "Content-Type": "application/json; charset=utf-8"
};

// 完了報告のデータをセット
msg.payload = {
    files: [
        { id: msg.fileId, title: "ロゴ画像" }
    ],
    channel_id: channelId,
    initial_comment: "ロゴ画像をアップロードしました。"
};

return msg;
function 3ノードの説明

function 3ノードでは、アップロード用URLを取得します。

  • 定数 token: function 1ノードと同じトークンをを設定します。
  • 定数 channelId: 投稿先のチャンネルIDを設定します。
  • msg.url: アップロードを完了するAPIを指定します。「files.completeUploadExternal」はfiles.getUploadURLExternalで開始したアップロードを完了するAPIです。
  • msg.method: 「POST」リクエストを指定します。
  • msg.headers: 投稿する画像のパラメータを指定します。
    • Authorization: Bearer <Slackアプリのトークン>
  • msg.payload: 投稿する画像のパラメータを設定します。
    • files: ファイルIDとそれに対応するタイトル
    • channel_id: 投稿先のチャンネルID
    • initial_comment: ファイルを紹介するメッセージ テキスト
  1. 各http requestをダブルクリックして、次のように設定します。
  • http request 1ノード
    メソッド:前段のfunctionノードで設定している「msg.method」の定義を使用する
    出力形式:結果をJSONオブジェクトで取得する

  • http request 2ノード
    メソッド:前段のfunctionノードで設定している「msg.method」の定義を使用する
    出力形式:結果が「ok」という文字列で返ってくるので、UTF8文字列で取得する

  • http request 3ノード
    メソッド:前段のfunctionノードで設定している「msg.method」の定義を使用する
    出力形式:結果をJSONオブジェクトで取得する

  1. 「デプロイ」ボタンをクリックします。

  1. 完成したフローです。

作成したフローのJSONを以下に貼っておきます。
Node-REDのメニューから「読み出し」->「クリップボード」で配置することができます。
各functionノードの定数tokenや定数channelIdは再設定してください。

[{"id":"2f606c6d28d294c5","type":"tab","label":"フロー 1","disabled":false,"info":"","env":[]},{"id":"23e61e6e5d131c36","type":"inject","z":"2f606c6d28d294c5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":60,"wires":[["596c473eeed00a51"]]},{"id":"596c473eeed00a51","type":"file in","z":"2f606c6d28d294c5","name":"","filename":"D:\\logo\\node-red-icon-2.png","filenameType":"str","format":"","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":380,"y":60,"wires":[["abf20a9642a0401b"]]},{"id":"abf20a9642a0401b","type":"function","z":"2f606c6d28d294c5","name":"function 1","func":"// 設定項目\nconst token = \"xoxb-xxxx-xxxx-xxxx\";\nconst filename = \"D:\\logo\\node-red-icon-2.png\";\n\n// 画像データ(Buffer)を一時保存し、ファイルサイズを取得\nmsg.imageBuffer = msg.payload;\nconst fileSize = msg.payload.length;\n\n// Slackへのリクエスト設定\nmsg.url = \"https://slack.com/api/files.getUploadURLExternal\";\nmsg.method = \"POST\";\nmsg.headers = {\n    \"Authorization\": \"Bearer \" + token,\n    \"Content-Type\": \"application/x-www-form-urlencoded\"\n};\n\n// 送信データの設定\n// filename: Slack上での名前、length: バイト数\nmsg.payload = {\n    filename: filename,\n    length: fileSize\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":120,"wires":[["4a874d8cb5465605"]]},{"id":"4a874d8cb5465605","type":"http request","z":"2f606c6d28d294c5","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":120,"wires":[["7ec210b40906aa94"]]},{"id":"7ec210b40906aa94","type":"function","z":"2f606c6d28d294c5","name":"function 2","func":"// Slackからのレスポンスをチェック\nif (!msg.payload.ok) {\n    node.error(\"URL取得失敗: \" + msg.payload.error);\n    return null;\n}\n\n// 後の完了報告のためにfile_idを保存\nmsg.fileId = msg.payload.file_id;\n\n// 送信先をSlackが発行したupload_urlに変更\nmsg.url = msg.payload.upload_url;\nmsg.method = \"POST\";\nmsg.headers = {\n    \"Content-Type\": \"image/png\"\n};\n\n// payloadを画像データ(バイナリ)に戻す\nmsg.payload = msg.imageBuffer;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":180,"wires":[["3881ff9fe9655264"]]},{"id":"3881ff9fe9655264","type":"http request","z":"2f606c6d28d294c5","name":"","method":"use","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":180,"wires":[["e7a1b21bcebaa077"]]},{"id":"e7a1b21bcebaa077","type":"function","z":"2f606c6d28d294c5","name":"function 3","func":"// 設定項目\nconst token = \"xoxb-xxxx-xxxx-xxxx\";\nconst channelId = \"Cxxxxxxxxxx\"; // 投稿先のチャンネルID\n\n// Slackへのリクエスト設定\nmsg.url = \"https://slack.com/api/files.completeUploadExternal\";\nmsg.method = \"POST\";\nmsg.headers = {\n    \"Authorization\": \"Bearer \" + token,\n    \"Content-Type\": \"application/json; charset=utf-8\"\n};\n\n// 完了報告のデータをセット\nmsg.payload = {\n    files: [\n        { id: msg.fileId, title: \"ロゴ画像\" }\n    ],\n    channel_id: channelId,\n    initial_comment: \"ロゴ画像をアップロードしました。\"\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":240,"wires":[["a043c954ed1ae6e7"]]},{"id":"a043c954ed1ae6e7","type":"http request","z":"2f606c6d28d294c5","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":240,"wires":[["33c0e56032660f8c"]]},{"id":"33c0e56032660f8c","type":"debug","z":"2f606c6d28d294c5","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":660,"y":240,"wires":[]}]

動作確認

injectノードの左側のボタンをクリックすると、Slackのチャンネル「node-red_alert」へNode-REDのロゴ画像が投稿されます。

Discussion