エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Discord Webhook で Elixir からファイルを POST する

人と直接会う機会がめっきり減り、代わってテキスト、音声、動画を問わずチャットを利用する日々が続いています。

そんなチャット環境を少しでも便利にしようと、Discord の Webhook を Elixir から利用する方法を調べていました。

Discord の Webhook の情報

Webhook の情報自体はポータルにまとまっています。

discord.com

メッセージを投下するだけであれば、Webhook の URL を取得して HTTP の POST メソッドをリクエストすればよいので簡単です。

テキストを POST する

テキストのみの場合、JSON エンコードした body を POST するだけです。 本文は content 、ユーザ名は usernameJSON のキーにします。 content は必須です。 そのほかのフィールドは Discord のドキュメントを参照してください。

ここでは HTTP クライアントに HTTPoison を、JSONエンコードには Jason を利用します。

hex.pm

hex.pm

また Webhook の URL は次のように環境変数 WEBHOOK_URL に設定してあることにします。

$ export WEBHOOK_URL=https://discord.com/api/webhooks/****/*******

あとは Webhook の URL 、JSON エンコードした body 、ヘッダにコンテントタイプを指定して POST すれば OK です。

  def post_text do
    HTTPoison.post(
      System.get_env("WEBHOOK_URL"),
      Jason.encode!(%{content: "Hello"}),
      "Content-Type": "application/json"
    )
  end

ファイルを POST する

Discord のドキュメントに次のように注意書きがされています。

This endpoint supports both JSON and form data bodies. It does require multipart/form-data requests instead of the normal JSON request type when uploading files. Make sure you set your Content-Type to multipart/form-data if you're doing that.

ファイルをアップロードするときは JSON でなくて multipart/form-data で POST する必要があるとのこと。

multipart については HTTPoison のドキュメント で触れられているので、それを参考に引数を変更します。

  def post_file do
    HTTPoison.post(
      System.get_env("WEBHOOK_URL"),
      {
        :multipart,
        [
          {"content", "Hello"}, # フィールド content の値
          {
            :file,
            "image.jpg", # 読み込むファイルのファイル名
            {
              "form-data",
              [{"filename", "image.jpg"}], # 送信するファイル名
            },
            [{"Content-Type", "image/jpg"}] # 送信するファイルのコンテントタイプ
          }
        ]
      },
      "Content-Type": "multipart/form-data"
    )
  end

バイナリを POST する

上の例では既存のファイルを POST しましたが、プログラム内で生成したファイルデータを POST したいときなどは次のように変更します。

multipart に :file を指定した場合に自動的にファイルからデータを取得するようになっているようで、こちらの方がより一般的な設定のようです。

  def post_binary do
    HTTPoison.post(
      System.get_env("WEBHOOK_URL"),
      {
        :multipart,
        [
          {"content", "Hello"}, # フィールド content の値
          {
            "file", # フィールド file
            File.read!("image.jpg"), # 送信するバイナリそのもの
            {
              "form-data",
              [{"filename", "image.jpg"}],
            },
            [{"Content-Type", "image/jpg"}]
          }
        ]
      },
      "Content-Type": "multipart/form-data"
    )
  end

multipart については、HTTPoison が利用しているパッケージ hackney のドキュメントに利用できる形式が説明されています。 が、ちょっとわかりにくいかも。

hex.pm

いつか読むはずっと読まない:Stormbringer! Stormbringer!

Mournblade! Mournblade!

Stormbringer! Stormbringer!