はじめに
こんばんは。
Go勉強中の僕です。
最近例のブログSlackの通知ロジックをPythonからGoに書き換えたのですが、Slack内にいるGoが得意な方からさらに楽なロジックを教わったので、やってみました。
やってみた
前回までは、SlackのChannels.hisotyAPIから指定の日付の範囲で絞って、発言を取得していました。
その発言からRSSアプリの発言のみを後に抜き出していました。
旧コード
package notification import ( "bytes" "encoding/json" "io/ioutil" "net/http" "net/url" "strconv" "time" ) type ChannelHistoriesData struct { Ok bool `json:"ok"` Latest string `json:"latest"` Oldest string `json:"oldest"` Messages []struct { Type string `json:"type"` User string `json:"user,omitempty"` Text string `json:"text"` ClientMsgID string `json:"client_msg_id,omitempty"` ThreadTs string `json:"thread_ts,omitempty"` Ts string `json:"ts"` Username string `json:"username,omitempty"` Icons struct { Image36 string `json:"image_36"` Image48 string `json:"image_48"` Image72 string `json:"image_72"` } `json:"icons,omitempty"` } `json:"messages"` HasMore bool `json:"has_more"` } /** * ChannelHistoryAPIを実行する */ func SendChannelHistoryAPI(configData ConfigData, latest time.Time, oldest time.Time) *ChannelHistoriesData { // パラメータをURLエンコードしたものを取得 params := url.Values{} params.Set("token", configData.Slack.RequestToken) params.Add("channel", configData.Slack.GetChannelID) params.Add("latest", strconv.FormatInt(latest.Unix(), 10)) params.Add("oldest", strconv.FormatInt(oldest.Unix(), 10)) params.Add("count", "700") urlParams := params.Encode() // channel history apiを実行する response, httpRequestError := http.Get(configData.Slack.GetAPIURL + "?" + urlParams) if httpRequestError != nil { panic("sendChannelHistoryAPIのリクエストに失敗しました。") } defer response.Body.Close() // responseBodyを最終的に配列へ変換する byteData, _ := ioutil.ReadAll(response.Body) data := new(ChannelHistoriesData) jsonDecodeError := json.Unmarshal(byteData, data) if jsonDecodeError != nil { panic("JSONのデコードに失敗しました。") } return data }
発言をどかっと取得するので、Lambdaから外部へリクエストするのは1回で済んでいたのですが、あくまで「発言を取得する」だけなので、これくらいの件数を取得すれば指定の日付範囲の発言全部取れるな。というどんぶり勘定でした。
今回変更したのは、RSSアプリで登録したFeedURLにリクエストを飛ばして、最新のPostの公開日から今週ブログを書いたかどうかを判断するようにしました。
新コード
package main import ( time "time" config "./config" gofeed "github.com/mmcdole/gofeed" ) func main() { const targetHour int = 15 const tomlFile string = "config.toml" tomlFilePath, _ := filepath.Abs(tomlFile) configData := config.GetConfigData(tomlFilePath) feedList := FindLatestFeedList(configData) targetUserIDList := getTargetUserIDList(feedList, targetHour) notification.SendMessage(configData, targetUserIDList) } /** * 各ユーザーのブログの最新公開日を取得する */ func FindLatestFeedList(configData config.ConfigData) map[string]time.Time { // 日本時間に合わせる locale, _ := time.LoadLocation("Asia/Tokyo") results := map[string]time.Time{} parser := gofeed.NewParser() for i := 0; i < len(configData.MemberData); i++ { // 最新フィードの公開日を取得する results[configData.MemberData[i].UserID] = getLatestFeedPubDate(configData.MemberData[i].FeedURL, parser, locale) } return results } /** * 最新フィードの公開日を取得する */ func getLatestFeedPubDate(feedURL string, parser *gofeed.Parser, locale *time.Location) time.Time { // フィードを取得 feed, err := parser.ParseURL(feedURL) if err != nil { panic("フィードが取得できませんでした。失敗したフィードURL => " + feedURL) } // 最新日を取得 published := feed.Items[0].Published latest, err := time.ParseInLocation(time.RFC3339, published, locale) if err != nil { // 取得できない = フォーマットを変えれば取得できる可能性がある latest2, err := time.ParseInLocation(time.RFC1123Z, published, locale) if err != nil { // それでも取得できない場合は、フィードで取得した生データをもらう latest = *feed.Items[0].PublishedParsed } else { latest = latest2 } } return latest } /** * 通知対象のUserIDのリストを取得する */ func getTargetUserIDList(feedList map[string]time.Time, targetHour int) []string { nowDate := date.GetNowDate(targetHour) thisMonday := date.GetThisMonday(nowDate) targetUserIDList := []string{} for userID, latestPublishDate := range feedList { if thisMonday.After(latestPublishDate) { // 今週の月曜日がAfterになる = 今週ブログを書いていない targetUserIDList = append(targetUserIDList, userID) } } return targetUserIDList }
登録されているFeedURL全てに対してリクエストを送ってるので、前回よりLambdaの実行時間が増えているのですが、とてもシンプルに仕上がりました。
迷ったこと
1. feedのPublishedがXMLによって変換がうまくいかない場合があった
PublishedはPubDateを保持しているようなんですが、JSTに所定のフォーマットで変換しようとすると、特定のフォーマットに限ってうまくいかない場合がありました。
一定のフォーマットで変換してしまうと、変換できないPublishedは 0001-01-01 00:00:00 +0000 UTC
とかになってしまってたので、再度変換してあげる必要がありました。
// 最新日を取得 published := feed.Items[0].Published latest, err := time.ParseInLocation(time.RFC3339, published, locale) if err != nil { // 取得できない = フォーマットを変えれば取得できる可能性がある latest2, err := time.ParseInLocation(time.RFC1123Z, published, locale) if err != nil { // それでも取得できない場合は、フィードで取得した生データをもらう latest = *feed.Items[0].PublishedParsed } else { latest = latest2 } }
2. lambdaで設定ファイルを読み込ませるには設定ファイルもzipでまとめて上げる必要があった
これは当たり前だよ!
って話なんですが、GOOS=linux GOARCH=amd64 go build -o blog_reminder
って感じでバイナリ作って、zipで固めた後lambdaにあげてたんですが、何回やってもTomlファイルロードエラーになって???ってなってました。
ローカルでは問題なかったのになんで?ってなったんですが、そもそもtomlファイルはzipにまとめてないんだから、そりゃ読み込めないよって話でした。
3. map型のループ
keyとvalueが欲しかったので、forでどうやるんだろうと調べてたんですが、range
で出来ました。
for userID, latestPublishDate := range feedList { if thisMonday.After(latestPublishDate) { // 今週の月曜日がAfterになる = 今週ブログを書いていない targetUserIDList = append(targetUserIDList, userID) } }
終わりに
いや、まじで、すぐに相談できる方がメンバーにいてくれるのってめっちゃ心強いですね。
現状、追加ブログを書いてないユーザーを見分けるロジックが入ってないので、このまま頑張ってGoで実装してみます!!