Quantcast
Viewing latest article 14
Browse Latest Browse All 24

我が家の救世主、Google Calendar連携Slack botをつくました

この記事は

Slack Advent Calendar 2018 - Qiita の14日目の記事です。

私は個人で使用しているSlackのワークスペースに、
Google カレンダーと Slack を連携させる – Slack 
これを使っているんですが、なんせ通知が来ても彼氏がシカトして予定を忘れやがる。

この窮地を救うため、聞けば返してくれるGoogle Calendar連携botくんを作成しました.

botの概要

予定が丸見えです。

実装内容

Slack API | Slack と Calendar API | Google Developers を使用します

main.go

func main() {
    token := os.Getenv("SLACKBOT")
    api := slack.New(token)
    client, err := gcalendar.Authorize()
    if err != nil {
        logger.Errorf("can not google calendar API authorized")
    }

    // WebSocketでSlack RTM APIに接続する
    rtm := api.NewRTM()
    // goroutineで並列化する
    go rtm.ManageConnection()

    // イベントを取得する
    for msg := range rtm.IncomingEvents {
        // 型swtichで型を比較する
        switch ev := msg.Data.(type) {
        case *slack.MessageEvent:
            switch ev.Msg.Text {
            case "今週の予定は?":
                schedule, err := schedule.New(client, "week")
                if err != nil {
                    logger.Errorf("get event error: %v", err)
                }
                rtm.SendMessage(rtm.NewOutgoingMessage(schedule, ev.Channel))
            case "今日の予定は?":
                schedule, err := schedule.New(client, "day")
                if err != nil {
                    logger.Errorf("get event error: %v", err)
                }
                rtm.SendMessage(rtm.NewOutgoingMessage(schedule, ev.Channel))
            }
        case *slack.InvalidAuthEvent:
            log.Print("Invalid credentials")
        }
    }
}

schedule.go

func New(c *http.Client, duration string) (string, error) {
    srv, err := calendar.New(c)
    if err != nil {
        return "", err
    }

    now := time.Now()
    var end time.Time
    switch duration {
    case "day":
        end = now.AddDate(0, 0, 1)
    case "week":
        end = now.AddDate(0, 0, 7)
    }
    if end.IsZero() {
        end = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
    }

    events, err := srv.Events.List("5t9khdis149i45g9a43dvg7vn8@group.calendar.google.com").
        ShowDeleted(false).SingleEvents(true).TimeMin(now.Format(time.RFC3339)).
        MaxResults(10).TimeMax(end.Format(time.RFC3339)).OrderBy("startTime").Do()
    if err != nil {
        log.Fatalf("Unable to retrieve next ten of the user's events: %v", err)
        return "", err
    }

    var messages []string
    if len(events.Items) == 0 {
        fmt.Println("No upcoming events found.")
    } else {
        for _, item := range events.Items {
            date := item.Start.DateTime
            if date == "" {
                date = item.Start.Date
            }
            messages = append(messages, fmt.Sprintf("%v: %v\n", date, item.Summary))
        }
    }

    return strings.Join(messages, ""), nil
}

gcalendar.go

func Authorize() (*http.Client, error) {
    // If modifying these scopes, delete your previously saved token.json.
    b, err := ioutil.ReadFile("credentials.json")
    if err != nil {
        log.Fatalf("Unable to read client secret file: %v", err)
        return nil, err
    }
    config, err := google.ConfigFromJSON(b, calendar.CalendarReadonlyScope)
    if err != nil {
        log.Fatalf("Unable to parse client secret file to config: %v", err)
    }
    // The file token.json stores the user's access and refresh tokens, and is
    // created automatically when the authorization flow completes for the first
    // time.
    tokFile := "token.json"
    tok, err := tokenFromFile(tokFile)
    if err != nil {
        tok = tokenFromWeb(config)
        saveToken(tokFile, tok)
    }
    return config.Client(context.Background(), tok), nil
}

func tokenFromFile(path string) (*oauth2.Token, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, errors.New("cannot open file")
    }
    defer f.Close()

    tok := &oauth2.Token{}
    err = json.NewDecoder(f).Decode(tok)
    return tok, err
}

// Request a token from the web, then returns the retrieved token.
func tokenFromWeb(config *oauth2.Config) *oauth2.Token {
    authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
    fmt.Printf("Go to the following link in your browser then type the "+
        "authorization code: \n%v\n", authURL)

    var authCode string
    if _, err := fmt.Scan(&authCode); err != nil {
        log.Fatalf("Unable to read authorization code: %v", err)
    }

    tok, err := config.Exchange(context.TODO(), authCode)
    if err != nil {
        log.Fatalf("Unable to retrieve token from web: %v", err)
    }
    return tok
}

// Saves a token to a file path.
func saveToken(path string, token *oauth2.Token) {
    fmt.Printf("Saving credential file to: %s\n", path)
    f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
    if err != nil {
        log.Fatalf("Unable to cache oauth token: %v", err)
    }
    defer f.Close()
    json.NewEncoder(f).Encode(token)
}

あとは go run main.goするなり、buildして実行するなりで動きます。
SlackのAPI登録、GoogleCalendarAPI登録に関しては他の記事を参考になさってください。

まとめ

現状、作ったbotアプリによる生活の向上は見えていません。ただの気休めでした。

ちなみに、冒頭で紹介した現状を招いている素晴らしいアプリは、Slackで見るとこんな感じに表示してくれます。

Image may be NSFW.
Clik here to view.
gcal_view

リマインダーも設定できるし、見た目も綺麗だし最高です。

これからの課題

今回急いで作ったので、Slackの用意したアプリに近づけるため、これからも進化させてゆきたいと思います。

  • 時間をもっとみやすく
  • せっかくのbotなのでもっと愛嬌をもたせたい
  • 時間の設定までしてあげたり
  • Alexaと連携してみたり

日々精進します


Viewing latest article 14
Browse Latest Browse All 24

Trending Articles