O365ライブラリでGraph APIのカレンダー絡みのエラーを回避する

# はじめに
2025年6月上旬から、Graph APIでOutlookカレンダーの全ての予定表をGETしようとすると500 Internal Server Errorを吐く事態となった。
[Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)で試してみると、以下のような反応が返ってくる。
```
GET https://graph.microsoft.com/v1.0/me/calendars

// レスポンス
{
    "error": {
        "code": "ErrorInternalServerError",
        "message": "An internal server error occurred. The operation failed."
    }
}
```
::: note alert
Internal Server Error - 500
:::
# 解決策
X(Twitter)で検索をかけてみると、上記の問題を指摘し、Microsoftに修正を要求しているMark Messinger氏の投稿があった。
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hey, <a href="https://twitter.com/Microsoft365?ref_src=twsrc%5Etfw">@Microsoft365</a>, your Graph API is busted. Your Graph Explorer confirms with certain accounts (e.g., Family 365), GET All my calendars throws Internal Server Error - 500. Business 365 account I&#39;ve tested is not impacted. Your API, your test tool, your GET query. Please fix.</p>&mdash; Mark Messinger (@MarkMessinger) <a href="https://twitter.com/MarkMessinger/status/1929443393479827503?ref_src=twsrc%5Etfw">June 2, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

Messinger氏のスレッドの下の方を見てみると、どうやら`conference`フィールドを除いてGraph APIを呼び出すとOutlookカレンダーのすべての予定表が取得できるとのこと。
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Developers of applications using the Microsoft Office 365 Graph API have found that making a call that does not include the &quot;conference&quot; fields will allow users to successfully sync their Outlook calendars.<br><br>Fix your API, Microsoft.</p>&mdash; Mark Messinger (@MarkMessinger) <a href="https://twitter.com/MarkMessinger/status/1929895173737021578?ref_src=twsrc%5Etfw">June 3, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

そこで、以下のようなクエリを足したうえでGraph APIを叩いてみるとMessinger氏の言う通り、Outlook予定表のデータを取得することができた。（Graph Explorerで確認済）
```
GET https://graph.microsoft.com/v1.0/me/calendars?$select=id,name,color,isDefaultCalendar
```
::: note info
OK - 200
:::

# では、Pythonでどう動かすか？
私は普段、PythonとO365ライブラリを使って、Graph APIから情報を取得したり、操作したりしている。確かに、クエリを足せばGraph APIからカレンダーを取得できることは分かったが、O365の`get_calendar()`メソッドには残念ながら`$select`パラメータを指定できるような引数は存在しない。どうにかしてO365でも`$select`パラメータを指定してGETできるようにしたい。

### O365ライブラリの`calendar.py`を直接編集する
Windowsならだいたい`"C:\Users\hoge\AppData\Local\Programs\Python\Python3xx\Lib\site-packages\O365"`とかにO365ライブラリのソースコードがある。
O365ライブラリのソースコードを探ってみると、APIを叩くURLを生成しているのは、`connection.py`の`get()`メソッドであることが分かった。
```connection.py
    def get(self, url: str, params: Optional[dict] = None, **kwargs) -> Response:
        """Shorthand for self.oauth_request(url, 'get')

        :param str url: url to send get oauth request to
        :param dict params: request parameter to get the service data
        :param kwargs: extra params to send to request api
        :return: Response of the request
        :rtype: requests.Response
        """
        return self.oauth_request(url, "get", params=params, **kwargs)
```
この`params`に`$select`パラメータの内容を渡せばいいことが分かる。
```calendar.py
    def get_calendar(self, calendar_id=None, calendar_name=None, query=None):
    
        """
        省略
        """
        
        if calendar_id:
            # get calendar by it's id
            url = self.build_url(
                self._endpoints.get('get_calendar').format(id=calendar_id))
            params = {"$select": "id,name,color,isDefaultCalendar"} # 書き換え箇所
            # params = None
        else:
            # get calendar by name
            url = self.build_url(self._endpoints.get('root_calendars'))
            params = {
                '$filter': "{} eq '{}'".format(self._cc('name'), calendar_name),
                '$top': 1,
                "$select": "id,name,color,isDefaultCalendar"} # 書き換え箇所
            """ 
            params = {
                '$filter': "{} eq '{}'".format(self._cc('name'), calendar_name),
                '$top': 1}
            """
        
        response = self.con.get(url, params=params)
        """
        省略
        """

        # Everything received from cloud must be passed as self._cloud_data_key
        return self.calendar_constructor(parent=self,
                                         **{self._cloud_data_key: data})
```
あとは、`calendar.py`の`get_calendar`メソッドを書き換えて、`con.get()`に`$select`の内容を含んだ`params`を読ませるようにする。

これで無事、Python+O365でもOutlookカレンダーが取得できるようになった。
というか、Microsoftは早くこのバグを直してくれヽ(`Д´)ﾉ
# 参考
[python-o365 - github](https://github.com/O365/python-o365/tree/master)
[Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)