💭

オリジナルのスマートスピーカーを作ってみる 8 Googleカレンダー連携

2024/04/23に公開

この記事は?

この記事は オリジナルのスマートスピーカーを作ってみる シリーズの続き「その8」です。

朝、AIに今日の予定を言ってほしい

朝、AIに今日の予定を言ってほしいですよね。
ということで Google カレンダー連携します。
Google カレンダー API の概要はこちら。API の利用料金は無料のようですね。
.NET プラットフォーム向けの Google Calendar API クライアント ライブラリがあり、今回はそれを利用しました。
以下、関連の公式ドキュメントをまとめておきます。

C# による Google Calendar API の呼び出し例の記事があり、こちらも参考にさせていただきました。
https://qiita.com/GodPhwng/items/4310586e1ec3bda4d91f

任意日付の予定を取得するコード例

以下に Google Calendar API により任意日付の予定を取得するプログラムをご紹介します。
これで無事、毎朝、起こしてくれて予定も確認してくれるアシスタントを作ることができました。
次回は LLM の AI を組み込んで何か言わせる機能を作ろうと思います。

開発環境

アプリケーションプラットフォーム .NET 8
言語 C# 12
IDE Visual Studio Community 2022
GCCalendarEventFetcher.cs
using CommableAssistant.Library;
using CommableAssistant.WorkerService.Models;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Services;
using Newtonsoft.Json;

internal static class GCCalendarEventFetcher
{
    /// <summary>
    /// 引数の date の日付の予定を取得する
    /// 
    /// 例: 05/05 の予定を取得する場合
    /// 
    /// 検索条件は以下のように設定しています
    /// request.TimeMinDateTimeOffset = 05/05 00:00:00
    /// request.TimeMaxDateTimeOffset = 05/06 00:00:00
    /// 
    /// どのような予定が取得されるか?
    /// 以下 〇取得される ×されない
    /// × 05/04 23:00 ~ 05/04 23:59
    /// × 05/04 23:00 ~ 05/05 00:00
    /// 〇 05/04 23:00 ~ 05/05 00:01
    /// 〇 05/04 23:30 ~ 05/05 00:30
    /// 〇 05/05 00:00 ~ 05/05 01:00
    /// 〇 05/05 12:00 ~ 05/05 13:00
    /// 〇 05/05 23:00 ~ 05/05 23:59
    /// 〇 05/05 23:00 ~ 05/06 00:00
    /// 〇 05/05 23:30 ~ 05/06 00:30
    /// × 05/06 00:00 ~ 05/06 01:00
    /// × 05/06 00:30 ~ 05/06 01:30
    /// </summary>
    public static IEnumerable<GCCalendarEventInfo>Fetch(string calendarID, DateTimeOffset date)
    {
        //Google Cloud コンソールで作成したサービスアカウントのキーファイルを読み込む
        var publicKeyFile = @"./xgca.json";
        var jsonText = TextFile.Read(publicKeyFile);

        if (string.IsNullOrWhiteSpace(jsonText))
            throw new NullReferenceException("jsonText is null or empty.");

        var keySettings = JsonConvert.DeserializeObject<GCServiceAccountKeyFile>(jsonText);

        if (keySettings == null)
            throw new NullReferenceException("keySettings is null.");

        var credential = new ServiceAccountCredential(
        new ServiceAccountCredential.Initializer(keySettings.client_email)
        {
            Scopes = [CalendarService.Scope.Calendar]
        }.FromPrivateKey(keySettings.private_key));

        var service = new CalendarService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = Product.Name,
        });

        var request = new EventsResource.ListRequest(service, calendarID);
        var today_0000 = new DateTimeOffset(date.Year, date.Month, date.Day, 0, 0, 0, TimeSpan.FromHours(9));
        var tomorrow_0000 = today_0000.AddDays(1);
        request.TimeMinDateTimeOffset = today_0000;
        request.TimeMaxDateTimeOffset = tomorrow_0000;
        request.TimeZone = "Asia/Tokyo";

        var events = request.Execute();

        if (events == null
            || events.Items == null
            || !events.Items.Any())
            return Enumerable.Empty<GCCalendarEventInfo>();

        var eventInfoList = new List<GCCalendarEventInfo>();

        foreach (var eventItem in events.Items)
        {
            var item = new GCCalendarEventInfo(eventItem);
            eventInfoList.Add(item);
        }

        var sortedEventItems = eventInfoList
            .OrderBy(x => x.Start.DateTimeDateTimeOffset)
            .OrderBy(x => x.Kind);
        return sortedEventItems;
    }
}
GCServiceAccountKeyFile.cs
internal class GCServiceAccountKeyFile
{
    public string? client_email { get; set; }
    public string? private_key { get; set; }
}
GCCalendarEventInfo.cs
using Google.Apis.Calendar.v3.Data;
using Newtonsoft.Json;

internal class GCCalendarEventInfo
{
    public string Summary { get; private set; } = "";

    [JsonIgnore]
    public EventDateTime Start { get; private set; }
    public string StartDisplay { get; private set; } = "";

    [JsonIgnore]
    public EventDateTime End { get; private set; }
    public string EndDisplay { get; private set; } = "";

    [JsonIgnore]
    public GCCalendarEventKind Kind { get; private set; } = GCCalendarEventKind.Time;
    public string KindDisplay { get; private set; } = "";

    #pragma warning disable CS8618
    public GCCalendarEventInfo(Event? eventItem)
    {
        if (eventItem == null) throw new ArgumentNullException(nameof(eventItem));

        SetStart(eventItem.Start);
        SetEnd(eventItem.End);
        Summary = eventItem.Summary;
    }
    #pragma warning restore CS8618

    private void SetKind(GCCalendarEventKind kind)
    {
        Kind = kind;
        KindDisplay = kind.Description();
    }
    private void SetStart(EventDateTime? start)
    {
        if (start == null) throw new ArgumentNullException(nameof(start));

        Start = start;

        if (start.DateTimeDateTimeOffset == null)
        {
            StartDisplay = start.Date;
            SetKind(GCCalendarEventKind.AllDay);
            return;
        }

        StartDisplay = start.DateTimeDateTimeOffset?.ToString() ?? "";
        SetKind(GCCalendarEventKind.Time);
    }
    private void SetEnd(EventDateTime end)
    {
        if (end == null) throw new ArgumentNullException(nameof(end));

        End = end;

        if (end.DateTimeDateTimeOffset == null)
        {
            EndDisplay = end.Date;
            return;
        }

        EndDisplay = end.DateTimeDateTimeOffset?.ToString() ?? "";
    }
}
GCCalendarEventKind.cs
using System.ComponentModel;

internal enum GCCalendarEventKind
{
    [Description("時間指定イベント")]
    Time,
    [Description("終日イベント")]
    AllDay
}

Discussion