Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
10
Help us understand the problem. What is going on with this article?
@sksksks

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

More than 1 year has passed since last update.

この記事は

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と連携してみたり

日々精進します

10
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
sksksks
gopher

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
10
Help us understand the problem. What is going on with this article?