はじめに
こんばんは。
絶賛Go勉強中の僕です。
前回・前々回と基礎的なところを勉強してみたので、今回は現状Pythonで実装しているブログSlackの通知ロジックをGoに書き直してみようと思いました。
その中でいろいろと学んだことをさらに記録しておこうと思います。
コード
設定ファイルどうする?
pythonで実装していたときは、YAMLファイルで用意してたのですが、Goはどうなのかなといろいろ調べてると、TOMLで用意するという記事がみかけたので、それに習ってみました。
インストール
$ go get github.com/BurntSushi/toml
config.toml
[Slack] RequestToken = "XXXXXX" GetAPIURL = "https://slack.com/api/channels.history" GetChannelID = "XXXXXX" SendAPIURL = "https://hooks.slack.com/XXXXXXX" SendChannelName = "XXXXXX" [[MemberData]] BlogTitle = "もがき系プログラマの日常" UserID = "XXXXX" [[MemberData]] BlogTitle = "XXXXXXXXXXXXXXX" UserID = "XXXXX"
main.go
package main import ( "fmt" "github.com/BurntSushi/toml" ) type ConfigData struct { Slack Slack MemberData []MemberData } type Slack struct { RequestToken string GetAPIURL string GetChannelID string SendAPIURL string SendChannelName string } type MemberData struct { BlogTitle string UserID string } func main() { var configData ConfigData _, err := toml.DecodeFile("config.toml", &configData) if err != nil { panic("tomlファイルを読み込めません") } fmt.Println(configData.Slack.GetAPIURL) fmt.Println(configData.Slack.RequestToken) for i := 0; i < len(configData.MemberData); i++ { fmt.Println(configData.MemberData[i].BlogTitle) } }g
実行
$ go run main.go https://slack.com/api/channels.history XXXXXX もがき系プログラマの日常 XXXXXXXXXXXXXXX
日付の計算とかどうする?
月曜日基準で考えて、水曜・金曜・日曜にリマインド通知を行っているので、それぞれの曜日で月曜までの時間の差を求めるみたいなことをしていました。
その求めた時間を、slackの発言履歴の時間の範囲指定で使うためです。
Pythonでは以下みたいなコードで対象の日付を取得してました。
def execute(): nowDate = GetNowDate() thisMonday = getThisMonday(nowDate.replace(hour=0)) #開始日は0時から取得したいので、 def getWeekDayNumber(): u""" 曜日の番号を返す @return int """ return datetime.date.today().weekday() def GetNowDate(): u""" 現在の日付を取得 15時に通知するためhourは決め打ち @return string """ return datetime.datetime.now().replace(hour=15, minute=0, second=0, microsecond=0) def getThisMonday(nowDate): u""" 今週の月曜の日付を取得 @return string """ weekday = getWeekDayNumber() return nowDate + datetime.timedelta(days=-weekday)
Goではいろいろ調べて以下みたいな感じにしました。
func main() { nowDate := GetNowDate() thisMonday := GetThisMonday(nowDate) fmt.Println("現在の日付 => " + nowDate.String()) fmt.Println("月曜の日付 => " + thisMonday.String()) } /** * 曜日の番号を返す * Goの場合 * 0 => 日曜 * 6 => 土曜 * * になるので、Pythonにあわすため、以下にする * 0 => 月曜 * 6 => 日曜 */ func GetWeekDayNumber() int { weekday := int(time.Now().Weekday()) - 1 if weekday == -1 { weekday = 6 } return weekday } /** * 現在の日付を取得する */ func GetNowDate() time.Time { t := time.Now() return time.Date(t.Year(), t.Month(), t.Day(), 15, 00, 00, 0, time.Local) } /** * 今週の月曜の日付を取得する */ func GetThisMonday(nowDate time.Time) time.Time { weekday := GetWeekDayNumber() nowDate = time.Date(nowDate.Year(), nowDate.Month(), nowDate.Day(), 00, 00, 00, 0, time.Local) return nowDate.Add(time.Duration(-24*weekday) * time.Hour) }
$ go run main.go 現在の日付 => 2018-10-14 15:00:00 +0900 JST 月曜の日付 => 2018-10-08 00:00:00 +0900 JST
URLエンコードどうする?
net/url
パッケージの Encode関数で再現できました。
func sendMessageAPI(configData ConfigData, latest time.Time, oldest time.Time) { 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") fmt.Println(params.Encode()) }
$ go run main.go channel=XXXXXX&count=700&latest=1539496800&oldest=1538924400&token=XXXXXX
また、ここを調べてるところで、
int64 => stringへのキャスト方法は、いつものstrconv.Atoi()ではなく、strconv.FormatInt()である
ということも学びました。第2引数の10は進数とのことです。
jsonのデータをデコードしたい
slackのchannel.historyAPIを実行し、結果のjsonを配列にしてpythonでは処理してました。
goではどうするのかなと調べてると、構造体とかに保存する必要があるみたいです。。
うぇぇぇ。。と思ってたら、jsonのデータをもとに構造体の定義を生成するサイトが合ったので、ソッコーで使いました。
パッケージは encoding/json
を使うようです。
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) { // パラメータを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", "200") 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 }
配列内(Slice内)の重複データを削除したい
現状の通知プログラムでは、通知アプリが発言したデータを配列に入れていたので、同じことをしようと思ったのですが、phpでいうarray_unique()みたいなのは調べるとGoではないようだったので、参考サイト様をみつけたので、使わせていただきました。
https://ema-hiro.hatenablog.com/entry/20170712/1499785693
/** * Slice内に存在する重複した値を削る */ func uniqueSlice(data []string) []string { m := make(map[string]bool) results := []string{} for _, ele := range data { if !m[ele] { m[ele] = true results = append(results, ele) } } return results }
一旦trueを入れておいて、次のループでtrueが入ってたら、if文に入らず結果uniqueになるということです。
配列を文字列で結合したい
strings
パッケージの、Joinで出来ました。
/** * Slackへ送信する用のメッセージを作成する */ func MakeNormalSendText(configData ConfigData, messageData []string) string { targetUsers := getTargetUsers(configData, messageData) textData := "<!channel>\nまだブログを書けていないユーザーがいます!\n今週中に書けるようみんなで煽りましょう!\n書けていないユーザー\n================\n" + strings.Join(targetUsers, "\n") return textData }
終わりに
とりあえず移植は成功しました。
ただ、こんな夜中にテストで通知を飛ばしてしまって、すぐ消しましたが、ご迷惑をおかけしました。。。
あーまにあった。。。