attachments付きのメッセージをJavaで投稿する

# はじめに
今回Javaで作っているプログラムでSlackに投稿したいことがありました。
[GASでなら以前やった](http://qiita.com/KanaSakaguchi/items/f0b1bb1cf73f0ec5ec71)のでそこからコピペ等すれば簡単ですが、Javaでの投稿は未経験でした。
Javaでのやり方はネットを探すも見つけられなかったので記事にします。
(海外のサイトやGitHubなどを漁ればあるだろうとは思いますが。)
試行錯誤している最中のコードも載せていますが、"失敗談はどうでもいいから結果が知りたい"という人は最後の方まで飛ばしてください。

ちなみに、どうにか送れるようになるまで私は1日ほどかかりました。

# 失敗その1 － チャンネルがない
次のようなメソッドを実行してみました。
(JSONのインデントや改行は必須ではないと思います。)

```java:試作コード1
private static void testSlack(){
    String json = "{\n" +
            "    \"channel\":\"bot−test\"," +
            "    \"username\":\"テスト\",\n" +
            "    \"text\": \"テスト\",\n" +
            "    \"attachments\": [\n" +
            "        {\n" +
            "            \"color\": \"#89ceeb\",\n" +
            "            \"text\": \"テスト\"\n" +
            "        }\n" +
            "    ]\n" +
            "}";
    try {
        URL url = new URL("https://slack.com/api/chat.postMessage?token=トークン");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-type", "application/json");
        OutputStream stream = connection.getOutputStream();
        PrintStream ps = new PrintStream(stream);
        ps.print(json);
        ps.close();
        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String str = "";
        String body;
        while ((body = reader.readLine()) != null) {
            str += body + "\n";
        }
        System.out.println(str);
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (ProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
```

結果は次の通り。
(見やすいよう整形しています。)

```json:結果1
{
  "ok": false,
  "error": "channel_not_found",
  "warning": "missing_charset"
}
```
なぜかチャンネルがないと言われてしまいました。
実際に存在するチャンネル名なのでそのはずがありません。
もしかしたら多分名前はNGでチャンネルIDの方を渡さないといけないのかもしれません。
GASの時はそんなことなかったのですが・・・。

charsetがないというのは警告なのでとりあえずスルーします。

# 失敗その2 － テキストがない
Slack APIは項目をパラメータとして渡せれば良いので、チャンネルはURLのパラメータにしてみました。

```java:試作コード2
private void testSlack(){
    String json = "{\n" +
            "    \"username\":\"テスト\",\n" +
            "    \"text\": \"テスト\",\n" +
            "    \"attachments\": [\n" +
            "        {\n" +
            "            \"color\": \"#89ceeb\",\n" +
            "            \"text\": \"テスト\"\n" +
            "        }\n" +
            "    ]\n" +
            "}";
    try {
        URL url = new URL("https://slack.com/api/chat.postMessage?token=トークン&channel=%23bot-test");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-type", "application/json");
        OutputStream stream = connection.getOutputStream();
        PrintStream ps = new PrintStream(stream);
        ps.print(json);
        ps.close();
        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String str = "";
        String body;
        while ((body = reader.readLine()) != null) {
            str += body + "\n";
        }
        System.out.println(str);
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (ProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
```

今度はテキストがないと言われました。

```json:結果2
{
  "ok":false,
  "error":"no_text",
  "warning":"missing_charset"
}
```

そこでテキストもパラメータにと思いましたが、日本語はエンコードしないといけないので保留。
そもそもJSONのPOSTがうまくいっていないような気がします。

# そもそもJavaでJSONを送信するには？
JavaでJSONを送信する方法について調べてみました。

- [【Java】【Android】【PHP】【JSON】JSON を Java から送信して、PHP で受信する - ヽ｜∵｜ゝ(Fantom) の 開発blog？](http://fantom1x.blog130.fc2.com/blog-entry-142.html)
    - 相違点：connectしている・Gson使ってる・PrintWriterで書き出している
- [AndroidのHttpURLConnectionのPOSTでハマる - 初老のボケ防止日記](http://osa030.hatenablog.com/entry/2015/05/22/181155)
    - 相違点：Android用のサンプル・charsetを指定している・バイト配列にしてDataOutputStreamで書き出している

どちらも試してみたのですが、どちらもテキストがないと・・・。
(Gsonは試してません。)

# cURL経由で送信する
JavaからcURLのコマンドを実行してみることにしました。

```bash:コマンド
curl -XPOST --data-urlencode 'payload={"attachments":[{"color":"#89ceeb","text":"テスト"}],"text":"テスト","username":"テスト"}' https://slack.com/api/chat.postMessage?token=トークン&channel=%23bot-test
```

Javaのコードはこんな感じです。

```java:試作コード5
private void testSlack() {
    String json = "{\n" +
            "\"username\":\"テスト\"," +
            "\"text\": \"テスト\"," +
            "\"attachments\": [" +
            "{" +
            "\"color\": \"#89ceeb\"," +
            "\"text\": \"テスト\"" +
            "}" +
            "]" +
            "}";
    String command = "curl -XPOST --data-urlencode 'payload=" + json + "' https://slack.com/api/chat.postMessage?token=トークン&channel=%23bot-test";
    try {
        Process p = Runtime.getRuntime().exec(command);
        p.waitFor();
        InputStream input = p.getInputStream();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
            String lines = "";
            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
                lines += line + "\n";
            }
            System.out.println(lines);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
```

その結果は次の通り。

```json:結果5
{
  "ok": false,
  "error": "invalid_arg_name"
}
```

今度は引数の名前がおかしいみたいです。
ヘッダーをきちんと指定しても同様でした。
このコマンドをターミナルで実行するとまたチャンネルがないと言われてしまうという・・・。

# どうにかcURL経由で送れるようになった
Slack APIはJSONで指定することは必須ではなく、URLのパラメータで指定することも可能です。
attachmentsをパラメータにできたらなぁと思い、中身をURLエンコードした次のようなコードを実行してみました。
ちなみにエンコード後の文字列は[こちら]()のサイトを利用して用意しました。

```java:試作コード7
private void testSlask(){
    String command = "curl -XPOST  https://slack.com/api/chat.postMessage?token=トークン&channel=%23bot-test&text=test&attachments=%5B%7B%22color%22%3A%22%2389ceeb%22%2C%22text%22%3A%22%E3%83%86%E3%82%B9%E3%83%88%22%7D%5D";
    try {
        Process p = Runtime.getRuntime().exec(command);
        p.waitFor();
        InputStream input = p.getInputStream();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
            String lines = "";
            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
                lines += line + "\n";
            }
            System.out.println(lines);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
```

結果は次の通りです。

```json:結果7
{
  "channel": "チャンネルID",
  "ok": true,
  "message": {
    "attachments": [{
      "color": "89ceeb",
      "text": "テスト",
      "id": 1,
      "fallback": "NO FALLBACK DEFINED"
    }],
    "subtype": "bot_message",
    "text": "test",
    "type": "message",
    "bot_id": "BOT ID",
    "username": "Slack API Tester",
    "ts": "1463901412.000002"
  },
  "ts": "1463901412.000002"
}
```

ついに送れるようになりました。 :tada: 

# cURLを使わずにJavaで投稿する
今度はエンコードをJavaで行い投稿も最初のHttpURLConnectionでの方法にしました。

```java:試作コード8
private void testSlack() {
    String attachments = "[" +
            "{" +
            "\"color\": \"#89ceeb\"," +
            "\"text\": \"テスト\"" +
            "}" +
            "]";
    try {
        attachments= URLEncoder.encode(attachments,"UTF-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    try {
        URL url = new URL("https://slack.com/api/chat.postMessage?token=トークン&channel=%23bot-test&text=test&attachments=" + attachments);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String str = "";
        String body;
        while ((body = reader.readLine()) != null) {
            str += body + "\n";
        }
        System.out.println(str);
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (ProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
```

# あとがき
ずっとSlackへの投稿はJSONをPOSTすることしか考えていなかったので、トータル半日以上その線を探り続けていました。
諦めかけて他のことしてた時間を入れると1日くらいになると思います。
最後は、JSONを外部ファイル化するとか、GASのプロジェクトにPOSTしてそっちで投稿するとか、とにかく諦めて未完で結論書いて記事投稿しようとか考えたりもしました。
チャンネル・ユーザー名・テキストはパラメータ指定で行けることを知っているのでattachmentsを諦めようと思ったのですが、諦める必要はありませんでした。

URLアクセスで完結するAPIはいいですね。