GoでQiitaAPIを呼んでSlackに通知してみた話

## はじめに
本記事は[コネヒト Advent Calendar 2019](https://qiita.com/advent-calendar/2019/connehito)の8日目の記事です。

こんにちわ！バリ寒かなりましたね！
布団にくるまって出社したいフロントエンジニアの黒田です。

今回は最近業務の都合上、Goを触ることになったのでその学習の一環としてツールを作成してみた話です。
というのも、まだGo言語を触り出して1,2ヶ月ほどで、A Tour of Goをや入門書を読んだ上でそろそろ実際に動くもので価値を提供できそうなものを作りたいなといったことが主な動機です。
というわけで、ちょうど社内slackにQiitaの人気記事が毎日流れてくるチャンネルがあり、レベル感もちょうど良さそうなので同じような機能をGoで作ってみました。

## 要件
要件としては、

- SlackAPIからその日の人気記事を取得する
 - ここでいう人気記事は前日作成されたストック10以上の記事とします
- 上記で取得された記事の情報を最大5件SlackにPOSTする

の大きく2つです。

## 環境構築
ということで、まずはローカルでGoの開発環境を構築していきますが、
検索すればその手のHowToはたくさん出てくるので詳細は割愛します。

```sh
brew install go
```

## パッケージ
基本的に標準パッケージのみを使用していますが、記事コンテンツを先頭100文字でトリミングする際にマルチバイトでカウントするために`utf8string`をインポートしています。

```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"golang.org/x/exp/utf8string"
)
```

## 構造体
次は記事の構造体を定義します。
表示したい内容は`タイトル``リンク``コンテンツ(先頭100文字)`で、以下のように定義しました。

```go
type Article struct {
	Title string `json:"title"`
	Body string `json:"body"`
	Url string `json:"url"`
}
```

## QiitaAPIを叩いて記事を取得する
さて、実際にQiitaAPIを叩く処理をみていきましょう。

```go
func fetchTrendArticles() []Article {
	year, month, day := time.Now().AddDate(0, 0, -1).Date()
	yesterday := fmt.Sprintf("%d-%d-%d", year, int(month), day)

	endpoint, err := url.Parse("https://qiita.com/api/v2/items?page=1&per_page=5&query=stocks:>10+created:>="+yesterday)
	if err != nil {
		panic(err)
	}

	c, err := json.Marshal(Article{})
	if err != nil {
		panic(err)
	}

	var res = &http.Response{}
	res, err = http.DefaultClient.Do(&http.Request{
		URL:endpoint,
		Method: "GET",
		Header: http.Header{
			"Content-Type":  {"application/json"},
		},
	})
	defer res.Body.Close()
	if err != nil {
		panic(err)
	}

	c, err = ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	var article []Article
	if err := json.Unmarshal(c, &article); err != nil {
		panic(err)
	}

	return article
}
```
注意点としては、month型をintにキャストする点とjsonの扱いでよく出てくるMarshal/Unmarshalを覚えることです。
また、コード上では表現していませんが、QiitaAPIにアクセスする際にアクセストークンを利用するとより良いと思われます。

## SlackにPOSTする
そして最後に上記で取得した記事をSlackにPOSTする処理になります。

```go
func sendSlack(article Article) error {
	slackUrl := os.Getenv("SLACK_URL")
	shortedBody := utf8string.NewString(article.Body).Slice(0, 100)

	jsonStr := `{"text":"` + article.Title + `\n` + article.Url + `\n` + shortedBody + `\n"}`
	req, err := http.NewRequest(
		"POST",
		slackUrl,
		bytes.NewBuffer([]byte(jsonStr)),
	)

	var res = &http.Response{}
	res, err = http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	return nil
}
```

記事コンテンツの先頭100文字取得に`[:100]`を使いたいところですが、マルチバイトに対応していないようなので、実験的パッケージの`utf8string`を使用しています。

## 動作確認
<img width="686" alt="スクリーンショット 2019-12-08 11.25.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143841/1530b74c-a0aa-0c41-8b5c-10b9c871b63f.png">
見栄えはさておき通知が来ました！

## 終わりに
機能としてはAPIを叩いてSlackに流す簡単な機能でしたが、実際に社内で利用されているものだったので要件もほぼ明確で、モチベーションも高く保てたと思います。
ただ、実際問題正常動作を前提としてエラーハンドリングを`panic`で握り潰していることからわかるように、まだまだ私自身Goの入り口に立ったばかりですが、今後プロダクトコードや社内ツールなどでもっと触れる機会が多くなってくるのでいい経験になりました。
特に私のようなGo初級者でも隙間時間で楽しく書けるGoはおすすめのプログラミング言語です。

## PR
最後に、コネヒトでは「技術は手段」と考えている文化が根付いていることもあって、サーバサイド・フロントエンドなどの職域を問わず幅広くWebエンジニアを積極採用中です！
サーバーサイドが得意な方は[こちら](https://www.wantedly.com/projects/351988)から!
フロントエンドが得意な方は[こちら](https://www.wantedly.com/projects/388158)から！
皆様のご応募をお待ちしております！！！