notion-py 使ってみた

# はじめに
 notion-py（非公式）についての記事を調べても日本語で書かれていることが少ないんですよね。自分が使ってみたことをまとめて置きます！
初めて記事を書いたので分かりにくい所もあると思いますがご理解下さい。

:::note warn
記事を書いた日（2022/02/05）での情報となるので注意してね。
:::

:::note alert
2022年3月17日更新
notion-pyは、新規で作成したtopページでもアクセスができなくなりました。
よって、実質notion-pyは動かないということになります。

-確定ではない情報-
2022年3月16日以前に作成したtopページであればこの記事に書いている内容は動くことを確認しています。
2022年3月16日のnotion側のアップデートにより使えなくなったと言えるでしょう。
:::

# notion-pyを使います
↓GitHubはこちら

https://github.com/jamalex/notion-py

# 使う技術
- python

# 使い方
使えるようにします

```
pip install notion
```

```python:基本となるコード
# notion-pyを使うためにimportする文を記述する
from notion.client import NotionClient

# `token_v2`はこの記事内にやり方を書いてるのでそちらを参考に貼り付ける
client = NotionClient(token_v2="<token_v2>")

# notionのTopページのURLを””内に貼り付ける
page = client.get_block("https://www.notion.so/terusibata/qiita-test-aa8c15c9a67b4baebe720ed754317666")

# ページのタイトルの取得
print("ページのタイトル : ", page.title)
```

## token_v2の取得方法
こちらの記事を参考しました

https://qiita.com/leechungkyu/items/cabc5a239ebd432afe0d#ready

webのNotionにログインしている状態
↓
F12（デベロッパーツールを開く）
↓
Application（アプリケーションタブをクリック）
↓
Cookie（左の一覧にある）
↓
token_v2の横にある長い文字列をコピー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/34832324-ce9d-0566-d1d4-01f2ee72c2c1.png)

# んでここまではちょっと調べたら書いてある
ここからの使い方が結構調べないとないのでここにまとめて置きます！
やりたいことに合わせて目次からどうぞ

:::note info
基本となるコードが書かれているという程で、その下に書くことで実行出来ます。
:::

:::note alert
notion-pyではサブページ（Topページの中にページを作成しているページ）に直接アクセスするとエラーが出るので注意
:::

## topページ（または今いる階層のページ）に追加する

### テキストを書き込みたい

```python:追加コード.テキスト
#必要に応じてインポートします
from notion.block import HeaderBlock
from notion.block import SubheaderBlock
from notion.block import SubsubheaderBlock
from notion.block import TextBlock

page.children.add_new(HeaderBlock, title='ヘッダー１を追加')
page.children.add_new(SubheaderBlock, title='ヘッダー2を追加')
page.children.add_new(SubsubheaderBlock, title='ヘッダー3を追加')
page.children.add_new(TextBlock, title='テキストを追加')

#2022/2/6追記
#テキスト内にリンクを入れた状態で作成する方法はこちら
page.children.add_new(TextBlock, title='リンクは[こちら](https://qiita.com/terusibata/items/aef67a87a5dd2bb3f7f6)')
```
実行例
![テキスト実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/3f99595a-07c6-92f8-1e8d-16ae5aac88aa.png)
追記実行例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/1d3779c1-4ddd-cf6b-678a-881115e50d9b.png)

### ToDoとして書き込みたい

```python:追加コード.ToDo
#必要に応じてインポートします
from notion.block import TodoBlock

#✔を入れていない状態なら以下のコードだけでよい
page.children.add_new(TodoBlock, title='✔前のtodoブロックを追加')
#✔をいれた状態で作成するには以下のコードを書くとよい
newchild = page.children.add_new(TodoBlock, title="✔後のtodoブロックを追加")
newchild.checked = True
```
実行例
![todo実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/72a68ad4-7ebb-b621-b72a-ce7c5096df6a.png)

### 箇条書きリストとして書き込みたい

```python:追加コード.箇条書きリスト
#必要に応じてインポートします
from notion.block import BulletedListBlock

page.children.add_new(BulletedListBlock, title='箇条書きリストを追加')
```
実行例
![箇条書きリスト実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/6d06b8ff-e811-e77f-d9e3-5192da41e323.png)

### 番号付きリストとして書き込みたい

```python:追加コード.番号付きリスト
#必要に応じてインポートします
from notion.block import NumberedListBlock

page.children.add_new(NumberedListBlock, title='番号付きリストを追加1')
page.children.add_new(NumberedListBlock, title='番号付きリストを追加2')
page.children.add_new(NumberedListBlock, title='番号付きリストを追加3')
```
実行例
![番号付きリスト実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/b769884e-5d93-4ebd-9449-326360f92430.png)

### 区切り線を書き込みたい

```python:追加コード.区切り線
#必要に応じてインポートします
from notion.block import DividerBlock

page.children.add_new(DividerBlock)
```
実行例
![区切り線実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/e528e97b-32c6-d050-f582-d80b4ea4fe8d.png)

### 引用として書き込みたい

```python:追加コード.引用
#必要に応じてインポートします
from notion.block import QuoteBlock

page.children.add_new(QuoteBlock, title='引用を追加')
```
実行例
![引用実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/73cac829-eaa2-3a3f-f3b2-3de30f605086.png)

### コールアウトとして書き込みたい

```python:追加コード.コールアウト
#必要に応じてインポートします
from notion.block import CalloutBlock

page.children.add_new(CalloutBlock, title='コールアウトを追加')
```
実行例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/6b447aa8-a8e8-fbff-a9ab-52690fe21eba.png)
※iconを変える方法が分かりませんでした。申し訳ありません。

### トグルリストとして書き込みたい

```python:追加コード.トグルリスト
#必要に応じてインポートします
from notion.block import ToggleBlock

#トグルリストをタイトルだけを付けて作成する場合は以下のコードでよい
page.children.add_new(ToggleBlock, title='トグルリストを追加')
#トグルリストの中に要素を入れたい場合は以下のコードのように書く(必要に応じてインポートしてください)
from notion.block import TextBlock
from notion.block import TodoBlock

newtoggle = page.children.add_new(ToggleBlock, title='トグルリストを追加')
newtoggle.children.add_new(TextBlock, title='トグルリストの中にテキストを追加')
newtoggle.children.add_new(TodoBlock, title='トグルリストの中に✔前のtodoブロックを追加')
```
実行例
![トグルリスト実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/7444bdbe-025d-f0f0-aa61-d959f498e49d.png)
### サプページを追加したい

```python:追加コード.サプページ
#必要に応じてインポートします
from notion.block import PageBlock

#サブページを作成してタイトルを入力するだけなら以下のコードでよい
page.children.add_new(PageBlock, title='サプページを追加')
```
実行例
![サブページ実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/d6111aad-512a-9fa2-2aba-37f14b3739b1.png)
まあ、サブページだけ作成して終わりのパターンはほとんどないですよね。
目次にある「サブページ内で出来ること」をご覧ください。

### ファイル(画像など)のアップロードをしたい

```python:追加コード.ファイル
#必要に応じてインポートします
from notion.block import ImageBlock
from notion.block import VideoBlock
from notion.block import AudioBlock

#画像アップロード
page.children.add_new(ImageBlock).upload_file("ここにファイルパス")
#動画アップロード
page.children.add_new(VideoBlock).upload_file("ここにファイルパス")
#音声アップロード
page.children.add_new(AudioBlock).upload_file("ここにファイルパス")
```
:::note warn
注意！
無料プランのnotionアカウントは1つのファイルにつき5MBまでとなります。
有料プランのnotionアカウントは1つのファイルにつき約5GBまでとなります。
:::

### 埋め込みとして書き込みたい

```python:追加コード.埋め込み
#必要に応じてインポートします
from notion.block import VideoBlock

#動画の埋め込み例
video = page.children.add_new(VideoBlock, width=700)
video.set_source_url("ここに動画のリンク")
```
※他のファイルも同様の方法で埋め込みができます。

### データベースを追加したい
notionのデータベース作成は2種類あります。「インライン」と「フルページ」です。
このやり方がなかなか見つからなかったので参考になれば嬉しいです。
また、これは初心者が作った一例ですので、コードが汚いですがご了承下さい。
#### 「インライン」のデータベース
年ごとに新たなデータベースを作成し、同じ年なら同じデータベース内に要素が追加されていくというコードになります。

```python:追加コード.inlineデータベース
from notion.block import CollectionViewBlock
from uuid import uuid1
from random import choice
import datetime

colors = [
  "default",
  "gray",
  "brown",
  "orange",
  "yellow",
  "green",
  "blue",
  "purple",
  "pink",
  "red",
]

schema = {
  "title": {
    "name": "タイトル",
    "type": "title"
  },
  "channel_select": {
    "name": "チャンネル",
    "type": "multi_select"
  },
  "URL":{
    "name": "動画リンク",
    "type": "url"
  },
  "date": {
    "name": "日付", 
    "type": "date"
  },
}

#データを代入
year = "2022年"
title = "テストタイトル"
channel = "テルシバタチャンネル"
URL = "https://youtube.com/channel/UCA910n1MOVJVW6GQ8Wymwrg"
Date = datetime.date(2022,2,5)

Collection_data = False
for child in page.children :
  try:
    if child.title==year:
      Collection_data = True
      channelnamedata_id=child
  except AttributeError:
    print("タイトルがない要素はスキップ")

if not Collection_data:
  print("データベースを作成します")
  #ここを変えることでインラインとフルページを切り替えれる
  cvb = page.children.add_new(CollectionViewBlock)
  cvb.collection = client.get_collection(client.create_record("collection", parent=cvb, schema=schema))
  cvb.title = year
  #ここでデータベースのview形式を変更できる
  view = cvb.views.add_new(view_type="table")
  row = cvb.collection.add_row()
else:
  row = channelnamedata_id.collection.add_row()
  view = channelnamedata_id
        
row.タイトル = title
#タグがない場合エラーになるので、以下の関数を実行しタグを作成する
add_new_multi_select_value("チャンネル", channel ,view.collection, color=None)
row.チャンネル = channel
row.動画リンク = URL
row.日付 = Date
```


```python:追加コード.add_new_multi_select_value
def add_new_multi_select_value(prop, value, collection,color=None):
    """`prop`が、マルチセレクトの名前となります"""
    if color is None:
        color = choice(colors)

    collection_schema = collection.get("schema")
    prop_schema = next(
        (v for k, v in collection_schema.items() if v["name"] == prop), None
    )
    if not prop_schema:
        raise ValueError(
           f'"{prop}"はデータベースに存在しない要素です'
        )
    if prop_schema["type"] != "multi_select":
        raise ValueError(f'"{prop}"はマルチセレクトではありません')

    if "options" not in prop_schema: prop_schema["options"] = []
    dupe = next(
        (o for o in prop_schema["options"] if o["value"] == value), None
    )
    if dupe:
        print("すでにあるタグです")

    prop_schema["options"].append(
        {"id": str(uuid1()), "value": value, "color": color}
    )
    collection.set("schema", collection_schema)
```
実行例
![インライン実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/790dc627-92c1-acfe-f787-8c39eb8564f9.png)

#### 「フルページ」のデータベース
インラインのデータベースとの違いは、インポートとコード1行だけです。

```python:追加コード.フルページデータベース
from notion.block import CollectionViewPageBlock

#ここを変えることでインラインとフルページを切り替えれる
cvb = page.children.add_new(CollectionViewPageBlock)
```
実行例
![フルページ実行例画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/3a7827e4-8036-b40e-2ab8-ae3b9024ccc3.png)

#### データベース共通で変更できること
```python:変更コード.データベース共通変更可能
#ここでデータベースのview形式を変更できる
view = cvb.views.add_new(view_type="table")
```
ここの行の"table"内を変更するとview形式を変更することができます。
##### "board"に変更した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/e24e445d-523d-daba-1ec3-21e16f585d1c.png)
##### "timeline"に変更した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/7d18ba03-8a54-ad45-bbf6-8336985bf95e.png)
##### "calendar"に変更した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/193ed585-16f4-5f2d-b268-d74b845a4f90.png)
##### "list"に変更した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/d30b3e59-334d-fb0f-e839-0a6380877989.png)
##### "gallery"に変更した場合(中に画像がある場合はおすすめ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/3a51dcf4-8bba-f8bb-7577-eca957527fef.png)

## サプページ内で出来ること
サプページでは、topページと同じことが出来ます。また、サプページの中にサプページの作成を**実質無限**に行うことが出来ます。

が、

**サプページに直接アクセスすることが出来ません。**notion-pyのバグかもしれません。
よって、サプページ内にブロックを追加するにはtopページから辿っていきましょう

### サプページへの辿り方
#### 新たにページを作成する場合

```python:追加コード.サプページへの辿り方
#必要に応じてインポートします
from notion.block import PageBlock
from notion.block import TextBlock

#サブページのデータをnewpageに代入する
newpage = page.children.add_new(PageBlock, title='サプページを追加')
#newpageをtopページ(現在の階層のページ)として扱うことができます
#よって上記に書かれていることを全て同様に実行することができます
newpage.children.add_new(TextBlock, title='サブページの中にテキストを追加')
```
実行例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/eb6f909d-cbd0-2951-27ec-9b68bd89b644.png)
↓　ページの中身
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668564/fe075607-3111-2589-c968-e46cbf40a6bc.png)
このようにすれば階層をどんどん深くすることができます。

#### 同じページを指定して辿る場合

```python:追加コード.指定して辿る
#必要に応じてインポートします
from notion.block import PageBlock
from notion.block import TextBlock

#タイトルに一致するページを検索する
key = "ここにタイトル"

nextpagedata = False
for child in page.children :
  try:
    if child.title==key:
      nextpagedata = True
      nextpage=child
  except AttributeError:
    print("タイトルがない要素はスキップ")

if nextpagedata:
  nextpage.children.add_new(TextBlock, title='サブページの中にテキストを追加')
else:
  print("一致するタイトルのページはありませんでした")
  #ここに新しいページを作るコードを書くとよい
  #page.children.add_new(PageBlock, title='サプページを追加')
```
タイトルで検索すると同じタイトルがある場合は使えないのでchikd.idで検索するとよいかもしれません。

# 最後に
ご覧いただきありがとうございます。
notionに自動でアップロードするプログラムを作っています！

### 動かなかったプログラム
データベースの一覧を取得するコードが自分の環境では動きませんでした。

```python:追加コード.動かなかったプログラム
for row in cv.collection.get_rows():
  print(row)
```