9
9

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-13

この記事は

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

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

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

botの概要

  • "今日の予定は?"って聞くと一覧で出してくれる スクリーンショット 2018-12-11 13.33.48.png
  • "今週の予定は?"って聞くと一覧で出してくれる スクリーンショット 2018-12-11 13.37.00.png

予定が丸見えです。

実装内容

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で見るとこんな感じに表示してくれます。

gcal_view

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

これからの課題

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

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

日々精進します

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9