もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

write-blog-every-weekの通知ロジックを再度修正

はじめに

こんばんは。

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で実装してみます!!