3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MATLABからslack apiを使ってプロット画像を投稿する方法

Last updated at Posted at 2023-08-24

やりたいこと

MATLABで長時間かかる処理を投げるときに、進歩をSlackで通知できるようにしたい!!
->webwriteでできた!
ついでに(途中)結果のプロット画像を送ってもらえれば、もっとべんりかも!!
->webwriteでうまくできない!!

前準備(slack api)

ここにアクセスして、tokenを取得します。
https://api.slack.com/apps

Scopesは、chat:writefiles: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の引数にfilecontentsで画像を渡すとよさそうです。が、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


3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?