冬やすみの間、やりたいこと、やっといたほうがいいやつをやってました。 ひとつは xremap の設定ともうひとつは CO2-mini から CO2 を見える ようにした。

今回は、 mackerel で見えるようになった CO2 の値を Slack へ定期的に投げるようにします。今回も Rust を利用しています。

準備

準備として、 mackerel 1Slack 2 両アプリケーションの投稿 API 用 Token をそれぞれ用意します。 各公式ページにあるように生成、取得するだけでよいです。

mackerel 側は ホストメトリック API を利用します。 Slack 側は chat.postMessage API を利用します。 各 API に対して取得した API Token を用いて curl で確認しておきます。

実装

今回は対話式の bot ではないので、 RTM を利用せずに、HTTP クライアントだけで構成しています3RustHTTP クライアントとして hyper4 を利用します。 TLShyper_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つのことをしています。 まずは timeUNIX 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!!"),
};

こうやってポストされたメッセージは以下のようになります。

絵文字つきでポストされましたね。

まとめ

Rustbot を作ってみました。 と言ってもただの HTTP クライアントbot なだけですけど。 一旦 Slack でも見えるようになったので今度は Nature Remo と連携して気温や湿度での自動化ができたらいいな。


  1. https://mackerel.io/ja/api-docs/ 

  2. 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 

  3. slack-rs という便利ライブラリがあるのですが、ちょっと触ってみたこれは必要なんだっけ?となってやめました。 

  4. 公式の README にあるようにこの場合は reqwest を利用するほうがよかったかもしれない。TLS は直接 hyper が対応していなかったりしてすこし面倒です。