冬やすみ
冬やすみの間、やりたいこと、やっといたほうがいいやつをやってました。 ひとつは xremap の設定ともうひとつは CO2-mini から CO2 を見える ようにした。
今回は、 mackerel で見えるようになった CO2 の値を Slack へ定期的に投げるようにします。今回も Rust を利用しています。
準備
準備として、 mackerel 1 と Slack 2 両アプリケーションの投稿 API 用 Token をそれぞれ用意します。 各公式ページにあるように生成、取得するだけでよいです。
mackerel 側は ホストメトリック API を利用します。
Slack 側は chat.postMessage API を利用します。
各 API に対して取得した API Token を用いて curl
で確認しておきます。
実装
今回は対話式の bot ではないので、 RTM を利用せずに、HTTP クライアントだけで構成しています3。 Rust の HTTP クライアントとして hyper4 を利用します。 TLS は hyper_tls を利用しています。
実装とは言っても対象の mackerel の APIを叩き値を取得して、 その値を元に Slack へポストするだけです。
mackerel での値取得時に気をつける点としては、ホストメトリック API では host名
ではなく、
host id
がパラメーターとなっていますので注意が必要です。
まずレスポンスを入れる構造体を定義します。
#[derive(Deserialize)]
pub struct Metric {
pub time: i64,
pub value: i16, // 今回は co2 の値なので i16 としている
}
#[derive(Deserialize)]
pub struct ResponseMetrics {
pub metrics: Vec<Metric>,
}
つぎに以下のようにしてリクエストを組みたてて、値を取得しています。
let https = HttpsConnector::new();
let req = hyper::Request::builder()
.method(hyper::Method::GET)
.uri(url)
.header("X-Api-Key", api_key)
.body(hyper::Body::empty())?;
// https として request する
let client = hyper::Client::builder().build::<_, hyper::Body>(https);
let res = client.request(req).await?;
let body = hyper::body::aggregate(res).await?;
let json: ResponseMetrics = serde_json::from_reader(body.reader())?;
let metrics = json.metrics;
値を取得したら、今度は同じように Slack の方も構造体を定義します。
// リクエスト用構造体
#[derive(Serialize)]
pub struct SlackMessage {
pub channel: String,
pub sub_type: String,
pub text: String,
pub username: String,
pub as_user: bool,
}
// レスポンス用構造体
#[derive(Deserialize)]
pub struct PostMessage {
#[allow(unused)]
channel: String,
}
リクエストを組みたてて、POSTします。 見てわかると思いますが、ほとんど mackerel と変わらないです。
// リクエスト body を json に変換
let json = serde_json::to_string(&SlackMessage {
channel: "channel".to_string(),
sub_type: "bot_message".to_string(),
text: "message".to_string(),
username: "botname".to_string(),
as_user: true,
})?;
let https = hyper_tls::HttpsConnector::new();
let req = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", get_env("SLACK_API_KEY")))
.body(hyper::Body::from(json))?;
let client = hyper::Client::builder().build::<_, hyper::Body>(https);
let res = client.request(req).await?;
let body = hyper::body::aggregate(res).await?;
let _json: PostMessage = serde_json::from_reader(body.reader())?;
ポストするメッセージを作る際に2つのことをしています。
まずは time
の UNIX EPOCH TIME からローカルの時間を表示するようにしています。
それと CO2 の値に依って絵文字を追加するかどうかを入れています。 -1
とか予定していない値が入ってきた場合は panic!
するようにしています。
// chrono を利用して unix time からローカルの文字列へ変換
let t = chrono::Local.timestamp(time);
let v = match value {
0..=700 => ":large_green_circle:",
701..=1000 => ":large_yellow_circle:",
1001.. => ":red_circle:", // なんで slack は :large_red_circle: を用意していないんだろうか
_ => panic!("unexpected number!!"),
};
こうやってポストされたメッセージは以下のようになります。
絵文字つきでポストされましたね。
まとめ
Rust で bot を作ってみました。 と言ってもただの HTTP クライアント な bot なだけですけど。 一旦 Slack でも見えるようになったので今度は Nature Remo と連携して気温や湿度での自動化ができたらいいな。
-
https://mackerel.io/ja/api-docs/ ↩
-
https://slack.com/intl/ja-jp/help/articles/215770388-API-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E7%94%9F%E6%88%90%E3%81%A8%E5%86%8D%E7%94%9F%E6%88%90 ↩
-
公式の README にあるようにこの場合は reqwest を利用するほうがよかったかもしれない。TLS は直接 hyper が対応していなかったりしてすこし面倒です。 ↩