やりたいこと
MATLABで長時間かかる処理を投げるときに、進歩をSlackで通知できるようにしたい!!
->webwriteでできた!
ついでに(途中)結果のプロット画像を送ってもらえれば、もっとべんりかも!!
->webwriteでうまくできない!!
前準備(slack api)
ここにアクセスして、tokenを取得します。
https://api.slack.com/apps
Scopesは、chat:writeとfiles:writeを使います。
これとか参考になると思います。
https://qiita.com/ykhirao/items/3b19ee6a1458cfb4ba21
文字を送るだけならwebwriteで簡単にできる
文字だけの通知をSlackに送信したいなら、webwrite関数を使用することで簡単に送信することができます。サンプルコードを載せておきます。
使用するAPIのリファレンスもおいておきます。
https://api.slack.com/methods/chat.postMessage
url = "https://slack.com/api/chat.postMessage";
token = ""; %自分で発行したAPIトークン
text = "test"; %通知の本文
channel = "#チャンネル名"; %送信先のチャンネル
responseData = webwrite(url, "token",token ,"text",text,"channel",channel);
plotを画像にしたい
saveas関数を使用してプロットを画像に保存できます。imgpathには相対絶対パスで画像の保存場所と名前を指定できます。
imgpath="適当な画像名";
saveas(gcf,imgpath);
画像を送りたい
files.uploadのリファレンス曰く、webwriteの引数にfileかcontentsで画像を渡すとよさそうです。が、webwriteでいい感じに渡す方法が分かりませんでした。
そこで、マルチパート フォーム メッセージの送信を参考にしてコードを作成しました。
import matlab.net.http.*
import matlab.net.http.field.*
import matlab.net.http.io.*
upload_url = matlab.net.URI('https://slack.com/api/files.upload');
token = ""; %自分で発行したAPIトークン
text = "test"; %通知の本文
channel = "#チャンネル名"; %送信先のチャンネル
imgpath = ""; %送信したい画像のパス(保存に使用したものと同じものを使用できる)
provider = MultipartFormProvider('token', token, 'channels', channel, 'filename', imgpath, 'initial_comment', text, "file", FileProvider(imgpath));
req = RequestMessage('POST',[], provider);
responseData = req.send(upload_url);
自分の環境で発生したエラー
先のコードだと、たまにdatefieldが未来になってしまうエラーが発生しました。
雑な対処として、datefieldをすごい過去にしました。多分こんなことしなくてもいいです。
dateField = matlab.net.http.field.DateField(datetime('yesterday')); %昨日
req = RequestMessage('POST',[dateField], provider);
いいかんじにまとめたやつ
自分で使う用にいい感じの関数を作成したので、おまけに載せておきます。
自分のトークンとチャンネル名、画像のパスを指定すれば使えます。
画像名に日時を入れて重複避けにしています。
パスの記述がWindowsなので、困る人は書き換えるとよいです。
SlackSend(text):textの文字列をSlackに投稿できます。
SlackSend(text,gcf,title):titleを画像名に含めて現在のプロット画像を保存し、textの文字列を本文として画像Slackに投稿できます。
function SlackSend(text,myGCF,title)
import matlab.net.http.*
import matlab.net.http.field.*
import matlab.net.http.io.*
url = matlab.net.URI("https://slack.com/api/chat.postMessage");
token = "";
channel = "";
image_dir=".";
dateField = matlab.net.http.field.DateField(datetime('yesterday'));
switch nargin
case 1
provider = MultipartFormProvider( "token",token ,"text",text,"channel",channel);
req = RequestMessage('POST',[dateField], provider);
responseData = req.send(url);
case 3
%保存するよ
imgname = append(title,"_", char(datetime('now','Format','yyyyMMddHHmmss')), '.png');
imgpath= append(image_dir , '\', imgname);
saveas(myGCF,imgpath);
% file.upload APIのURL
upload_url = matlab.net.URI('https://slack.com/api/files.upload');
% Slackに送信
provider = MultipartFormProvider('token', token, 'channels', channel, 'filename', imgname, 'initial_comment', text, 'title', title, "file", FileProvider(imgpath));
req = RequestMessage('POST',[], provider);
responseData = req.send(upload_url);
otherwise
disp("SlackSend:の数が合わないよ");
end
end
追記 API廃止への対応(2025/07/16)
files.uploadが廃止されるため、上記の方法は使えなくなるようです。
推奨されるAPIに書き直したバージョンを置いておきます。(こちらの版は、ChatGPTを活用して作成されたものです。)
新しいやり方では、チャンネルを指定する際にチャンネルIDが必要らしいです。
チャンネルIDは、Slack内のチャンネル詳細からコピーすることができるので、channel = "C-----";の所を自分のチャンネルIDに書き換えて利用してください。
function SlackSend(text, myGCF, title, path)
% SlackSend ── Slack にテキストのみ/画像付きで通知
%
% SlackSend(text)
% SlackSend(text, figH, title, subfolder)
%
% 2025‑11‑12 以降の新 API フローに対応
arguments
text {mustBeTextScalar}
myGCF matlab.ui.Figure = matlab.ui.Figure.empty
title string = "Figure"
path string = "."
end
import matlab.net.*
import matlab.net.http.*
import matlab.net.http.field.*
import matlab.net.http.io.*
token = strtrim(fileread("apikey.txt")); %tokenは自分のものをapikey.txtに記入しておいてください。
channel = "C-----"; %channelIDは、チャンネル詳細にコピーできる場所があります
hdrTok = HeaderField("Authorization","Bearer " + token);
%% ── テキストのみ ──────────────────────────
if isempty(myGCF)
urlMsg = URI("https://slack.com/api/chat.postMessage");
prov = FormProvider("channel",channel,"text",text);
resp = RequestMessage('POST',[hdrTok],prov).send(urlMsg);
assert(resp.StatusCode==StatusCode.OK && resp.Body.Data.ok,...
"chat.postMessage 失敗: %s",resp.Body.Data.error);
return
end
%% ── 画像付き ────────────────────────────
% 1) 図を PNG 保存
ts = datetime('now','Format','yyyyMMddHHmmss');
baseTitle = title;
stem = baseTitle + "_" + char(ts);
mkdir(path); % 存在チェック
pngFile = fullfile(path, stem + ".png");
saveas(myGCF, pngFile);
figFile = fullfile(path, stem + ".fig");
saveas(myGCF, figFile,"fig");
epsFile = fullfile(path, stem + ".eps");
saveas(myGCF, epsFile,"epsc");
pdfFile = fullfile(path, stem + ".pdf");
print(myGCF,'-vector',pdfFile,'-dpdf') % PDF
% 2) アップロード URL と file_id を取得
fileInfo = dir(pngFile);
urlGet = URI("https://slack.com/api/files.getUploadURLExternal");
provGet = FormProvider("filename",stem+".png", ...
"length",num2str(fileInfo.bytes));
respGet = RequestMessage('POST',[hdrTok],provGet).send(urlGet);
dataGet = respGet.Body.Data;
if ~dataGet.ok
error("getUploadURLExternal 失敗: %s", dataGet.error);
end
uploadURL = URI(dataGet.upload_url);
fileID = dataGet.file_id;
% 3) PUT/POST で実データをアップロード
respUp = RequestMessage('POST',[],FileProvider(pngFile)).send(uploadURL);
assert(respUp.StatusCode==StatusCode.OK, ...
"実ファイルのアップロードに失敗 (%d)",respUp.StatusCode);
% --- Step 4: completeUploadExternal(修正版) -----------------
% --- Step 4: completeUploadExternal (urlencoded 版・初期コメント無し) ----
urlComp = URI("https://slack.com/api/files.completeUploadExternal");
filesJson = sprintf('[{"id":"%s","title":"%s"}]', fileID, stem+".png");
provComp = FormProvider( ...
"files", filesJson, ... % ← JSON 文字列をそのまま
"channel_id", channel ); % ← Cxxxxxxxx
hdrs = HeaderField("Authorization","Bearer " + token);
respComp = RequestMessage('POST', hdrs, provComp).send(urlComp);
if ~respComp.Body.Data.ok
assert(respComp.Body.Data.ok, ...
"completeUploadExternal 失敗: %s", string(respComp.Body.Data.error));
end
end
%% ── バリデータ ──────────────────────────────
function mustBeTextScalar(v)
if ~(ischar(v) || (isstring(v)&&isscalar(v)))
error("text は 1×1 の非空文字列で指定してください");
end
end