【自分向けメモ・コード集・随時更新】 Microsoft Graph APIをPythonから使いたい！ 

# 概要
本記事は、自分用にMicrosoft Graph API (以下 Graph API) をPythonから使う際に調べたことや、うまくいったコードを備忘録としてまとめるものである。[2024/10/01に廃止予定のTeamsのIncomming webhook](https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/)の代わりとしても参考になるかも？

# そもそも
筆者が「Application permission無しで、あるPythonプログラムを1日1回だけバックエンドで起動して、SharePointのListsにItemを書き込む仕組みを作りたい」と思ったところから色々と調べている。WebAppのSSOとかはよくわからない。

# 遊び場
はっきり言ってGraph APIは使うのにそこそこの鍛錬が必要である。しかし、会社や学校で使おうとすると「Microsoft 365管理者からどのAPI Permissionが必要か聞かれたけど知らん...」という初心者の嘆きが聞こえてくる。このための修行の場として、Microsoftは企業向けの高機能な Microsoft E5 ライセンスの管理者権限を開発目的で使える [Microsoft Developer Program](https://developer.microsoft.com/en-us/microsoft-365/dev-program)という、遊び場を用意してくれている。もちろん、Graph APIも使い放題。さぁ、今すぐ登録しよう。

# Microsoft Graph APIとは
Microsoft Graph APIは、Microsoft 365の各種サービス（Outlook、OneDrive、Teams、Azure Active Directoryなど）にアクセスするための統一されたAPIです。これにより、単一のエンドポイントを通じて複数のMicrosoftサービスのデータにアクセスしたり、操作したりすることができます。

# 参考サイト
- 公式 (相変わらず~~わかりづらい~~すばらしい）
    - [Microsoft Graph API を使用する](https://learn.microsoft.com/ja-jp/graph/use-the-api)

# Graph APIを使うための準備
1. [Microsoft Entra admin center](https://entra.microsoft.com/)で、アプリを登録する。
1. Pythonに、認証をサポートする[Microsoft Authentication Library (MSAL) for Python
](https://github.com/AzureAD/microsoft-authentication-library-for-python)とGraph APIを使いやすくする[Microsoft Graph SDK for Python
](https://github.com/microsoftgraph/msgraph-sdk-python)ライブラリーをインストールする（正直msgraph-sdk-pythonを使うよりもrequestsで直接エンドポイントを叩いた方がわかりやすいような気もする）。

```bash
pip install msal
pip install msgraph-sdk
```

# Graph APIを安全に使うための仕組み（認証）
## 概要
ぶっちゃけ、Graph APIを使うときに一番難儀するのは認証だと思う。しかしそれも致し方ない。Graph APIは非常に強力なREST-APIであり、厳密な認証機構がなければサービスは破綻してしまう。すなわち、Graph API側としては、アクセスを要求してきたヒト/プログラムがきちんと許可を得たものであるかチェックする必要がある。この認証は、Graph APIの使用前にMicrosoft ID プラットフォームによって行われる。これによって通行手形となるAccess_Token (時間制限付き) を得て、それを携えてGraph APIコマンドを叩く。


## Microsoft Graph APiの2つのアクセス許可
Graph APIは以下2つのアクセス許可がある。

- **委任されたアクセス許可 (Delegated permissions)**
    - Microsoft365 ユーザーが、そのユーザーの権限内でGraph APIを使うことができる。例えば、Aユーザーが Sharepoint-AAA のみにアクセス可能な場合、適切な権限があればSharepoint-AAA のリストやファイルの読み書きが可能だが、Sharepoint-BBBにはアクセスできない
    - _基本的に「ユーザーがその都度アクセスして、インタラクティブに使う」（例：WebApp）などを想定しているため、1日1回指定された時間に動かすdaemon/backend的なプログラムの動作には不向き_（[OAuth2系の認証で手に入れることができるrefresh_tokenを使用すると90日間はそれを用いてAccess_tokenを再取得できるが、それ以上は人による再ログインが必要](https://jpazureid.github.io/blog/azure-active-directory/token-lifetime-2023/)）
- **アプリケーションの許可 (Application permissions)**
    - Microsoft 365の"ユーザー"ではなく、"アプリケーション (プログラム)"に許可を与える
    - _パスワード代わりの "Client_secrets" の有効期限の間であれば、ユーザーログインの必要は一切ないためDaemon/Backendで動かすアプリに最適_(ユーザーに紐づかないので、自分の情報を取得、は当然できない)
    - そのためほぼRoot/Administrator並の権限を持つ (社内の見てはいけないSharePointやTeamsの会話が見える）
    - 故に、管理者ではない限り使用が許可されることはない (と思われる)

## Microsoft ID プラットフォームの認証方法
上記のGraph APIのアクセス許可に対応したMicrosoft ID プラットフォームの認証機構は以下の通り。詳しくは[Microsoft ID プラットフォームのアプリの種類と認証フローを参照](https://learn.microsoft.com/ja-jp/entra/identity-platform/authentication-flows-app-scenarios)。基本的には、OAuth 2.0で規定されている認証フローに対応 (参考：[OAuth 2.0 全フローの図解と動画](https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f#%E8%AA%8D%E5%8F%AF%E3%82%B3%E3%83%BC%E3%83%89%E3%83%95%E3%83%AD%E3%83%BC--authlete))。


| 認証フロー名称 | 対応するGraph APIアクセス許可 | 説明 |
|:-:|:-:|:-:|
| [承認コード (OAuth2.0)](https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth2-auth-code-flow)  | 委任されたアクセス許可 (Delegated permissions)  | いわゆるOAuth2.0での認証。Single Page AppなどのWebAppで使用。**Refresh_tokenが手に入る。**|
| [暗黙的な承認コード (OAuth2.0の暗黙的な許可)](https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth2-implicit-grant-flow)  | 委任されたアクセス許可 (Delegated permissions)  | OAuth2.0だが、ブラウザの中のCookieに登録されているTokenを用いて自動的にログインするもの。初めてのWebAppの場合やCookieが削除される場合は動かないので、Microsoftとしては認可コードに移行して欲しい様子. **Refresh_tokenが手に入る。** |
| [OAuth2.0 デバイスコード](https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth2-device-code)  | 委任されたアクセス許可 (Delegated permissions)  | 「このページにアクセスして、このデバイスIDを入力してくれ」という形の認証。スマート TV、IoT デバイス、プリンター、ブラウザを持たないクラウドインスタンス、などの入力制限のあるデバイスにサインイン可能。**Refresh_tokenが手に入らない。** |
| [OAuth2.0 On-Behalf-Of (OBO) フロー](https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth2-on-behalf-of-flow)  |  委任されたアクセス許可 (Delegated permissions) | On-Behalf-Of (OBO) フローは、OAuth 2.0認可フローの一種で、特定のユーザーに代わってアクセスを取得するために使用される。具体的には、フロントエンドのアプリケーションがバックエンドサービスに対してユーザーの代わりにAPIを呼び出す場合に利用される。  |
|  [OAuth2.0 ソース所有者のパスワード資格情報](https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth-ropc) |  委任されたアクセス許可 (Delegated permissions) | いわゆる普通のIDとパスワードのみを利用した認証。Microsoftとしてはセキュリティリスクが高いため非推奨。MFA導入の場合は利用不可。IDとパスワードがあればユーザーが手を動かさなくても認証ができる。|
| 統合 Windows 認証 | ND  | Windows民のみ許された伝家の宝刀  |
| [OAuth2.0 クライアントの資格情報](https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth2-client-creds-grant-flow)  | アプリケーションの許可 (Application permissions)  | ユーザーのログインは必要ない。|



# 各認証のPythonコード例
以下に、各認証法別のPythonコードを記述します。また、以下のコードは[microsoft-authentication-library-for-pythonのsample](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample)を少し改変し、Access_tokenを得ることを目的としている。

tenant_id、client_id、client_secretなどの秘密情報は、以下のコード例のようなハードライティングはしない。パーミッションを適切に管理するため、環境変数や別ファイルに書くことを強く推奨する。

## 承認コード (OAuth2.0)
参考：https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/sample/interactive_sample.py

```python
import logging
import msal

tenant_id="<Your_MS365_Tenant_ID>"  # 例) "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id="<Your_App_Client_ID>"  # 例) "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
scope=["<Your_GraphAPI_API_list>"] # 例) ["User.Read"]
username="<Your_MS365_UserName>" # 例) hogehoge@foo.com

# Optional logging
# logging.basicConfig(level=logging.DEBUG)  # Enable DEBUG log for entire script
# logging.getLogger("msal").setLevel(logging.INFO)  # Optionally disable MSAL DEBUG logs

authority=f"https://login.microsoftonline.com/{tenant_id}"


# If for whatever reason you plan to recreate same ClientApplication periodically,
# you shall create one global token cache and reuse it by each ClientApplication
global_token_cache = msal.TokenCache()  # The TokenCache() is in-memory.
    # See more options in https://msal-python.readthedocs.io/en/latest/#tokencache

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.PublicClientApplication(
    client_id,
    authority=authority,  # For Entra ID or External ID
    # oidc_authority=os.getenv('OIDC_AUTHORITY'),  # For External ID with custom domain
    #enable_broker_on_windows=True,  # Opted in. You will be guided to meet the prerequisites, if your app hasn't already
        # See also: https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-wam#wam-value-proposition
    token_cache=global_token_cache,  # Let this app (re)use an existing token cache.
        # If absent, ClientApplication will create its own empty token cache
    )
scopes = scope


def acquire_and_use_token():
    # The pattern to acquire a token looks like this.
    result = None

    # Firstly, check the cache to see if this end user has signed in before
    accounts = global_app.get_accounts(username=username)
    if accounts:
        logging.info("Account(s) exists in cache, probably with token too. Let's try.")
        print("Account(s) already signed in:")
        for a in accounts:
            print(a["username"])
        chosen = accounts[0]  # Assuming the end user chose this one to proceed
        print("Proceed with account: %s" % chosen["username"])
        # Now let's try to find a token in cache for this account
        result = global_app.acquire_token_silent(scopes, account=chosen)

    if not result:
        logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
        print("A local browser window will be open for you to sign in. CTRL+C to cancel.")
        result = global_app.acquire_token_interactive(  # Only works if your app is registered with redirect_uri as http://localhost
            scopes,
            #parent_window_handle=...,  # If broker is enabled, you will be guided to provide a window handle
            login_hint=username,  # Optional.
                # If you know the username ahead of time, this parameter can pre-fill
                # the username (or email address) field of the sign-in page for the user,
                # Often, apps use this parameter during reauthentication,
                # after already extracting the username from an earlier sign-in
                # by using the preferred_username claim from returned id_token_claims.

            #prompt=msal.Prompt.SELECT_ACCOUNT,  # Or simply "select_account". Optional. It forces to show account selector page
            #prompt=msal.Prompt.CREATE,  # Or simply "create". Optional. It brings user to a self-service sign-up flow.
                # Prerequisite: https://docs.microsoft.com/en-us/azure/active-directory/external-identities/self-service-sign-up-user-flow
            )

    if "access_token" in result:
        print("Token was obtained from:", result["token_source"])  # Since MSAL 1.25
        return result
    else:
        print("Token acquisition failed", result)  # Examine result["error_description"] etc. to diagnose error

##############


result = acquire_and_use_token()
access_token=result["access_token"]
token_expire=result["expires_in"]

print(access_token)
print(token_expire)
```


## デバイスコード (OAuth2.0)
参考：https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/sample/device_flow_sample.py

```python
import logging
import msal

tenant_id="<Your_MS365_Tenant_ID>"  # 例) "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id="<Your_App_Client_ID>"  # 例) "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
scope=["<Your_GraphAPI_API_list>"] # 例) ["User.Read"]

authority=f"https://login.microsoftonline.com/{tenant_id}"

# Optional logging


# Optional logging
# logging.basicConfig(level=logging.DEBUG)  # Enable DEBUG log for entire script
# logging.getLogger("msal").setLevel(logging.INFO)  # Optionally disable MSAL DEBUG logs

# If for whatever reason you plan to recreate same ClientApplication periodically,
# you shall create one global token cache and reuse it by each ClientApplication
global_token_cache = msal.TokenCache()  # The TokenCache() is in-memory.
    # See more options in https://msal-python.readthedocs.io/en/latest/#tokencache

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.PublicClientApplication(
    client_id,
    authority=authority,  # For Entra ID or External ID
    # oidc_authority=os.getenv('OIDC_AUTHORITY'),  # For External ID with custom domain
    token_cache=global_token_cache,  # Let this app (re)use an existing token cache.
        # If absent, ClientApplication will create its own empty token cache
    )
scopes = scope


def acquire_and_use_token():
    # The pattern to acquire a token looks like this.
    result = None

    # Note: If your device-flow app does not have any interactive ability, you can
    #   completely skip the following cache part. But here we demonstrate it anyway.
    # We now check the cache to see if we have some end users signed in before.
    accounts = global_app.get_accounts()
    # print(accounts[0])
    if accounts:
        logging.info("Account(s) exists in cache, probably with token too. Let's try.")
        print("Pick the account you want to use to proceed:")
        for a in accounts:
            print(a["username"])
        # Assuming the end user chose this one
        chosen = accounts[0]
        print(chosen)
        # Now let's try to find a token in cache for this account
        result = global_app.acquire_token_silent(scopes, account=chosen)

    if not result:
        logging.info("No suitable token exists in cache. Let's get a new one from AAD.")

        flow = global_app.initiate_device_flow(scopes=scopes)
        if "user_code" not in flow:
            raise ValueError(
                "Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))

        print(flow["message"])
        sys.stdout.flush()  # Some terminal needs this to ensure the message is shown

        # Ideally you should wait here, in order to save some unnecessary polling
        # input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")

        result = global_app.acquire_token_by_device_flow(flow)  # By default it will block
            # You can follow this instruction to shorten the block time
            #    https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.acquire_token_by_device_flow
            # or you may even turn off the blocking behavior,
            # and then keep calling acquire_token_by_device_flow(flow) in your own customized loop.

    if "access_token" in result:
        print("Token was obtained from:", result["token_source"])  # Since MSAL 1.25
        return result
    else:
        print("Token acquisition failed", result)  # Examine result["error_description"] etc. to diagnose error

##############


result = acquire_and_use_token()
access_token=result["access_token"]
token_expire=result["expires_in"]

print(access_token)
print(token_expire)
```

## OAuth2.0 On-Behalf-Of (OBO) フロー
Now writing...

## OAuth2.0 ソース所有者のパスワード資格情報
参照: https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/sample/username_password_sample.py
```python
import logging
import msal


tenant_id="<Your_MS365_Tenant_ID>"  # 例) "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id="<Your_App_Client_ID>"  # 例) "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
scope=["<Your_GraphAPI_API_list>"] # 例) ["User.Read"]

username="<Your_MS365_UserName>" # 例) hogehoge@foo.com
password="<Your_MS365_PassWord>"


authority=f"https://login.microsoftonline.com/{tenant_id}"

# Optional logging
# logging.basicConfig(level=logging.DEBUG)  # Enable DEBUG log for entire script
# logging.getLogger("msal").setLevel(logging.INFO)  # Optionally disable MSAL DEBUG logs

# if not username:
#     sys.exit("Please provide a username in the environment variable USERNAME.")
# password = getpass.getpass("Password for {}: ".format(username))
# password

# If for whatever reason you plan to recreate same ClientApplication periodically,
# you shall create one global token cache and reuse it by each ClientApplication
global_token_cache = msal.TokenCache()  # The TokenCache() is in-memory.
    # See more options in https://msal-python.readthedocs.io/en/latest/#tokencache

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.ClientApplication(
    client_id,
    authority=authority,  # For Entra ID or External ID
    # oidc_authority=os.getenv('OIDC_AUTHORITY'),  # For External ID with custom domain
    # client_credential=os.getenv('CLIENT_SECRET') or None,  # Treat empty string as None
    client_credential=None,  # Treat empty string as None
    token_cache=global_token_cache,  # Let this app (re)use an existing token cache.
        # If absent, ClientApplication will create its own empty token cache
    )
scopes = scope


def acquire_and_use_token():
    # The pattern to acquire a token looks like this.
    result = None

    # Firstly, check the cache to see if this end user has signed in before
    accounts = global_app.get_accounts(username=username)
    if accounts:
        logging.info("Account(s) exists in cache, probably with token too. Let's try.")
        result = global_app.acquire_token_silent(scopes, account=accounts[0])

    if not result:
        logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
        # See this page for constraints of Username Password Flow.
        # https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki/Username-Password-Authentication
        result = global_app.acquire_token_by_username_password(
            username, password, scopes=scopes)

    if "access_token" in result:
        print("Token was obtained from:", result["token_source"])  # Since MSAL 1.25
        return result
    else:
        print("Token acquisition failed", result)  # Examine result["error_description"] etc. to diagnose error
        if 65001 in result.get("error_codes", []):  # Not mean to be coded programatically, but...
            raise RuntimeError(
                "Microsoft Entra ID requires user consent for U/P flow to succeed. "
                "Run acquire_token_interactive() instead.")

##############

result = acquire_and_use_token()
access_token=result["access_token"]
token_expire=result["expires_in"]

print(access_token)
print(token_expire)
```

## OAuth2.0 クライアントの資格情報
参照: https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/sample/confidential_client_sample.py

```python
import logging
import msal

tenant_id="<Your_MS365_Tenant_ID>"  # 例) "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id="<Your_App_Client_ID>"  # 例) "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
scope=[ "https://graph.microsoft.com/.default" ]
client_secret="<Your_App_Client_Secret>" # 例) "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"


authority=f"https://login.microsoftonline.com/{tenant_id}"

# Optional logging
# logging.basicConfig(level=logging.DEBUG)  # Enable DEBUG log for entire script
# logging.getLogger("msal").setLevel(logging.INFO)  # Optionally disable MSAL DEBUG logs


# If for whatever reason you plan to recreate same ClientApplication periodically,
# you shall create one global token cache and reuse it by each ClientApplication
global_token_cache = msal.TokenCache()  # The TokenCache() is in-memory.
    # See more options in https://msal-python.readthedocs.io/en/latest/#tokencache

# Create a preferably long-lived app instance, to avoid the overhead of app creation
global_app = msal.ConfidentialClientApplication(
    client_id,
    authority=authority,  # For Entra ID or External ID
    # oidc_authority=authority,  # For External ID with custom domain
    client_credential=client_secret,  # ENV VAR contains a quotation mark-less string
        # or json.loads(os.getenv('CLIENT_CREDENTIAL_JSON')),  # ENV VAR contains a JSON blob as a string
    token_cache=global_token_cache,  # Let this app (re)use an existing token cache.
        # If absent, ClientApplication will create its own empty token cache
    )
scopes = scope


def acquire_and_use_token():
    # Since MSAL 1.23, acquire_token_for_client(...) will automatically look up
    # a token from cache, and fall back to acquire a fresh token when needed.
    result = global_app.acquire_token_for_client(scopes=scopes)

    if "access_token" in result:
        print("Token was obtained from:", result["token_source"])  # Since MSAL 1.25
        return result
    else:
        print("Token acquisition failed", result)  # Examine result["error_description"] etc. to diagnose error


##############

result = acquire_and_use_token()
access_token=result["access_token"]
token_expire=result["expires_in"]

print(access_token)
print(token_expire)
```

# Graph API with Python-SDK
## Graph API with Raw Access_token のベースコード
Microsoft ID プラットフォームで入手した Access_token と Graph API-SDK を利用して Graph API を操作する方法について記載します。参考にしたコードは [こちら](https://github.com/microsoftgraph/msgraph-sdk-python/issues/501#issuecomment-1941292450) です。

以下のコードにおける```###### Graph API code ######``` 以下を変更することで、さまざまな Graph API に対応することができます。

```python
from azure.core.credentials import AccessToken
from msgraph import GraphServiceClient
import pprint


class RawAccessTokenProvider:
    """
    A simple credential provider that returns a raw access token for use with Azure SDK clients.
    """

    def __init__(self, access_token: str, expires_on: int) -> None:
        self._access_token = access_token
        self._expires_on = expires_on

    def get_token(self, *scopes, **kwargs) -> AccessToken:
        return AccessToken(self._access_token, self._expires_on)

#########

raw_access_token = access_token
expires_on = token_expire
    
# Usage
credentials = RawAccessTokenProvider(raw_access_token, expires_on)
client = GraphServiceClient(credentials=credentials)


###### Graph API code ######
```
なお、以下のコードは例として /me の get リクエストを行っています。アプリケーションの許可（Application permissions）では動作しません。ユーザーの許可（Delegated permissions）が必要です。

```python
me = await client.me.get()


print(me.display_name, me.mail)
# pprint.pprint(me)
```

:::note warn
以下、すべてのGraph APIコードは、上記の```###### Graph API code ######```以下に記述して下さい。そのままでは動きません。
:::


## SharePoint
### SharePoint: Tenant の全ての SharePoint Site を入手
- アクセス許可の種類: 
    - アプリケーションの許可 (Application permissions) のみ
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/site-getallsites?view=graph-rest-1.0&tabs=python
```python
import asyncio

async def site_all():
    sites = await client.sites.get()
    for site in sites.value:
        # print(site)
        print(site.name, site.web_url, site.id)

asyncio.ensure_future(site_all())


# <Task pending name='Task-9' coro=<site_all() running at /var/folders/fh/4xxxxxxxxxxxxxxxx/T/ipykernel_xxxxx/xxxxxxxxxx.py:3>>
# アプリ https://xxxxxxxxxx.sharepoint.com/sites/xxxxxxxxxxx xxxxxxxxxx.sharepoint.com,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
# YOUR_SITE_NO1 https://xxxxxxxxxx.sharepoint.com/sites/xxxxxxxxxxx xxxxxxxxxx.sharepoint.com,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
# YOUR_SITE_NO1 https://xxxxxxxxxx.sharepoint.com/sites/xxxxxxxxxxx xxxxxxxxxx.sharepoint.com,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
# YOUR_SITE_NO1 https://xxxxxxxxxx.sharepoint.com/sites/xxxxxxxxxxx xxxxxxxxxx.sharepoint.com,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
...
...
```

### SharePoint: SharePoint の Site を検索
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/site-getallsites?view=graph-rest-1.0&tabs=python

```python
from msgraph import GraphServiceClient
from msgraph.generated.sites.sites_request_builder import SitesRequestBuilder
from kiota_abstractions.base_request_configuration import RequestConfiguration
import asyncio


# graph_client = GraphServiceClient(credentials, scopes)

query_params = SitesRequestBuilder.SitesRequestBuilderGetQueryParameters(
		search = "department",
)

request_configuration = RequestConfiguration(
query_parameters = query_params,
)

result = await client.sites.get(request_configuration = request_configuration)

for item in result.value:
    print(item.display_name, item.id)

# YOUR_SITE_NO1 xxxxxxxxxx.sharepoint.com,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
```


### SharePoint: 特定の SharePoint Site の全ての Lists を入手
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/list-get?view=graph-rest-1.0&tabs=http
```python
async def listName_all(site_id):
    # lists = client.lists.by_site_id(site_id).get()
    lists = await client.sites.by_site_id(site_id).lists.get()
    # print(lists)
    # print(lists.value)
    for list_item in lists.value:
        # print(site)
        print(list_item.display_name, list_item.id)


site_id="<YOUR_SHAREPOINT_SITE_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
asyncio.ensure_future(listName_all(site_id))

# <Task pending name='Task-32' coro=<listName_all() running at /var/folders/fh/4_2mwd795m923rw64527lz180000gn/T/ipykernel_38039/4213516391.py:7>>
# YOUR_LIST_No1 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
# YOUR_LIST_No2 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
# YOUR_LIST_No3 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
```


### SharePoint: 特定の Lists の全ての Items の情報を入手
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/listitem-get?view=graph-rest-1.0&tabs=http


```python
## get items on the list
from msgraph import GraphServiceClient
from msgraph.generated.sites.item.lists.item.items.items_request_builder import ItemsRequestBuilder
from kiota_abstractions.base_request_configuration import RequestConfiguration


site_id="<YOUR_SHAREPOINT_SITE_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
list_id="<YOUR_SHAREPOINT_LIST_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx


query_params = ItemsRequestBuilder.ItemsRequestBuilderGetQueryParameters(
		# expand = ["fields($select=seatUuid)"],
        expand = ["fields"]
)

request_configuration = RequestConfiguration(
query_parameters = query_params,
)

result = await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.get(request_configuration = request_configuration)

for item in result.value:
    pprint.pprint(item.fields)

# FieldValueSet(additional_data={'@odata.etag': '"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx,2"',
#                                'Attachments': False,
#                                'AuthorLookupId': '15',
#                                'Category': 'Used',
#                                'ContentType': 'Item',
#                                'Created': DateTime(2021, 2, 23, 4, 48, 26, tzinfo=Timezone('UTC')),
#                                'Edit': '',
# ...
# ...
```

### SharePoint: 特定の Lists の特定の Item の情報を入手
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/listitem-get?view=graph-rest-1.0&tabs=http

```python
site_id="<YOUR_SHAREPOINT_SITE_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
list_id="<YOUR_SHAREPOINT_LIST_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
item_id="x" #ex.: 3


async def list_item():
    item = await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.by_list_item_id(item_id).get()
    pprint.pprint(item.fields)
    

asyncio.ensure_future(list_item())

# FieldValueSet(additional_data={'@odata.etag': '"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx,2"',
#                                'Attachments': False,
#                                'AuthorLookupId': '15',
#                                'Category': 'Used',
#                                'ContentType': 'Item',
#                                'Created': DateTime(2021, 2, 23, 4, 48, 26, tzinfo=Timezone('UTC')),
#                                'Edit': '',
# ...
# ...
```


### SharePoint: 特定の Lists の Items の情報をフィルターして入手
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/listitem-get?view=graph-rest-1.0&tabs=http

```python
## Get Items (filter with OData format) of the List
from msgraph import GraphServiceClient
from msgraph.generated.models.list_item import ListItem
from msgraph.generated.models.field_value_set import FieldValueSet
import uuid
import datetime
import pprint

site_id="<YOUR_SHAREPOINT_SITE_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
list_id="<YOUR_SHAREPOINT_LIST_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx



query_params = ItemsRequestBuilder.ItemsRequestBuilderGetQueryParameters(
		# expand = ["fields($select=seatUuid)"],
        # expand = ["fields"],
        # search = ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx"],
        # filter = ["fields/Title eq 'hogehoge'"],
        filter = ["fields/number eq X",]
)


request_configuration = RequestConfiguration(
query_parameters = query_params,
)

## Set <5000 limit filter.
request_configuration.headers.add("Prefer", "HonorNonIndexedQueriesWarningMayFailRandomly")


result = await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.get(request_configuration = request_configuration)

for item in result.value:
    print(item.id)

# 11
# 111
```


### SharePoint: 特定の Lists へ新しい Item を追加
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/listitem-create?view=graph-rest-1.0&tabs=http

```python
from msgraph import GraphServiceClient
from msgraph.generated.models.list_item import ListItem
from msgraph.generated.models.field_value_set import FieldValueSet

site_id="<YOUR_SHAREPOINT_SITE_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
list_id="<YOUR_SHAREPOINT_LIST_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx

request_body = ListItem(
	fields = FieldValueSet(
		additional_data = {
				"Title" : <YOUR_LIST_TITLE>, # Titleは必ず必要
				"text_value" : "ThisIsStr.",　# Listsの列: 必要に応じて
                "datetime" : "2024/07/01 01:00",　# Listsの列: 必要に応じて
                "user" : "hogehoge@hogehoge.onmicrosoft.com",　# Listsの列: 必要に応じて
				"number" : 32,　# Listsの列: 必要に応じて
                "truefalse" : False,　# Listsの列: 必要に応じて
		}
	),
)

result = await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.post(request_body)

```


### SharePoint: 特定の Lists の特定の Item を削除
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/listitem-delete?view=graph-rest-1.0&tabs=http


```python
site_id="<YOUR_SHAREPOINT_SITE_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
list_id="<YOUR_SHAREPOINT_LIST_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
item_id="x" #ex.: 3

await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.by_list_item_id(item_id).delete()
```

### SharePoint: 特定の Lists の特定の Item を更新
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/listitem-update?view=graph-rest-1.0&tabs=http


```python
from msgraph.generated.models.field_value_set import FieldValueSet

site_id="<YOUR_SHAREPOINT_SITE_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
list_id="<YOUR_SHAREPOINT_LIST_ID>" # ex.: db75cee1-xxxx-xxxx-xxxx-xxxxxxxxxx
item_id="x" #ex.: 3

request_body = FieldValueSet(
	additional_data = {
			"number" : 10002, # 変更したいListsの列のみ指定: 変更後の値
			"text_value" : "macbookAir", # 変更したいListsの列のみ指定: 変更後の値
	}
)

result = await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.by_list_item_id(item_id).fields.patch(request_body)
```


## Teams
### Teams: Tenant の全ての Team を入手
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/listitem-update?view=graph-rest-1.0&tabs=http

```python
result = await client.teams.get()

for item in result.value:
    print(item.display_name, item.id)

# YOUR_TEAM_1 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
# YOUR_TEAM_2 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
# ...
```

### Teams: 特定の Team の全ての channels を入手
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/channel-list?view=graph-rest-1.0&tabs=http

```python
team_id="<YOUR_TEAMS_TEAM_ID>" # ex.: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx


result = await client.teams.by_team_id(team_id).channels.get()

for item in result.value:
    print(item.display_name, item.id)

# YOUR_TEAMS_TEAM_CHANNEL_1 19:xxxxxxxxxxxxxxxxxxxxxxxx@thread.tacv2
# YOUR_TEAMS_TEAM_CHANNEL_2 19:xxxxxxxxxxxxxxxxxxxxxxxx@thread.tacv2
# YOUR_TEAMS_TEAM_CHANNEL_3 19:xxxxxxxxxxxxxxxxxxxxxxxx@thread.tacv2
# ...
```


### Teams: 特定の channel のチャット履歴を入手
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/chat-list-messages?view=graph-rest-1.0&tabs=http

```python
from msgraph.generated.chats.item.messages.messages_request_builder import MessagesRequestBuilder
from kiota_abstractions.base_request_configuration import RequestConfiguration
import pprint

chat_id=<YOUR_TEAMS_TEAM_CHANNEL_1> #ex.: 19:xxxxxxxxxxxxxxxxxxxxxxxx@thread.tacv2

query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
		top = 5, # いくつの書き込みを取得するか指定
)

request_configuration = RequestConfiguration(
query_parameters = query_params,
)

result = await client.chats.by_chat_id(chat_id).messages.get(request_configuration = request_configuration)

for message in result.value:
    pprint.pprint(message.attachments[0].content)

# None
# ('{\r\n'
#  '  "text": "hogehoge_1"\r\n'
#  '}')
# ('{\r\n'
#  '  "text": "hogehoge_1"\r\n'
# '}')
# ...
```


### Teams: 特定の chat へメッセージを送る
- アクセス許可の種類: 
    - 委任されたアクセス許可 (Delegated permissions)
    - アプリケーションの許可 (Application permissions)
- API Permission確認：
    - https://learn.microsoft.com/ja-jp/graph/api/chat-post-messages?view=graph-rest-1.0&tabs=http

```python
from msgraph.generated.models.chat_message import ChatMessage
from msgraph.generated.models.item_body import ItemBody

chat_id=<YOUR_TEAMS_TEAM_CHANNEL_1> #ex.: 19:xxxxxxxxxxxxxxxxxxxxxxxx@thread.tacv2

request_body = ChatMessage(
	body = ItemBody(
		content = "Hello world. it is test", # メッセージをここに書く
	),
)

result = await client.chats.by_chat_id(chat_id).messages.post(request_body)
```

# 今後の展望
以下、やりたいことリスト
 - Teamsの反応(Good Markなど)が押されたら特定のFlowを動かすなどしたい