Skip to content

Instantly share code, notes, and snippets.

@POD666
Last active December 29, 2024 01:48
Show Gist options
  • Save POD666/138ade98827e51d4b22523d88613bc0e to your computer and use it in GitHub Desktop.
Save POD666/138ade98827e51d4b22523d88613bc0e to your computer and use it in GitHub Desktop.
Notion page duplication requires recursive blocks population and extra tricks to duplicate images. Hope this example will help someone who also struggle with notion API.
import logging
import os
from io import BytesIO
import boto3
import click
import requests
logger = logging.getLogger(__name__)
s3 = boto3.client("s3")
# should be created manually and configured for public access
S3_BUCKET = "notion-images-for-page-duplication"
def request(request_func, *args, **kwargs):
"""Simple wrapper for requests with error handling"""
try:
response = request_func(*args, **kwargs)
response.raise_for_status()
except requests.exceptions.RequestException as e:
click.echo(args)
click.echo(kwargs)
click.echo(response.content)
raise e
return response
def upload_from_url_to_s3(url):
"""Download file to memory and upload to s3 with original filename"""
response = request(requests.get, url)
file_name = url.split("/")[-1].split("?")[0]
s3.upload_fileobj(
BytesIO(response.content),
S3_BUCKET,
file_name,
ExtraArgs={"ACL": "public-read"},
)
bucket_location = s3.get_bucket_location(Bucket=S3_BUCKET)["LocationConstraint"]
return f"https://s3-{bucket_location}.amazonaws.com/{S3_BUCKET}/{file_name}"
def recursive_block_creation(original_blocks, new_blocks, headers, depth=0):
"""
Recursively populate child blocks for a given lists of original blocks and new blocks
"""
blocks_count = len(original_blocks)
for i, (block, new_block) in enumerate(zip(original_blocks, new_blocks)):
click.echo(f"{' ' * depth}Block depth={depth} {i}/{blocks_count}")
if not block.get("has_children"):
continue
# Get child blocks
block_id = block["id"]
new_block_id = new_block["id"]
response = request(
requests.get,
f"https://api.notion.com/v1/blocks/{block_id}/children",
headers=headers,
)
children = response.json()["results"]
# Redefine image blocks.
# It's not possible to use notion image url for creating new image blocks
# https://developers.notion.com/reference/file-object#externally-hosted-files-vs-files-hosted-by-notion
# Workaround: use upload_from_url_to_s3 to download images from notion and upload them to s3 instead
# so that we can use s3 public link as extarnal for notion block creation
children = [
{
"type": "image",
"image": {
"external": {
"url": upload_from_url_to_s3(child["image"]["file"]["url"])
}
},
}
if child["type"] == "image"
else child
for child in children
]
# Append child blocks to a specific block
response = request(
requests.patch,
f"https://api.notion.com/v1/blocks/{new_block_id}/children",
json={"children": children},
headers=headers,
)
new_children = response.json()["results"]
recursive_block_creation(children, new_children, headers, depth=depth + 1)
@click.command()
@click.option(
"--new_title", help="New title for a page", required=False,
)
@click.option(
"--from_page_id", help="Specify page id to copy", required=False,
)
@click.option(
"--parent_page_id", help="Specify which page will be used as parent", required=False,
)
def duplicate_notion_page(new_title, from_page_id, parent_page_id):
NOTION_API_KEY = os.environ.get("NOTION_API_KEY")
if not NOTION_API_KEY:
raise click.UsageError("NOTION_API_KEY env variable not set")
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2021-08-16",
"Content-Type": "application/json",
}
# Get template blocks
response = request(
requests.get,
f"https://api.notion.com/v1/blocks/{from_page_id}/children?page_size=100",
headers=headers,
)
root_blocks = response.json()["results"]
# Create a new page from template with root blocks
response = request(
requests.post,
"https://api.notion.com/v1/pages",
json={
"parent": {"page_id": parent_page_id},
"properties": {
"title": [{"text": {"content": new_title}}]
},
"children": root_blocks,
},
headers=headers,
)
response_data = response.json()
new_page_url = response_data["url"]
new_page_id = response_data["id"]
# Retrieve new blocks from the new page
response = request(
requests.get,
f"https://api.notion.com/v1/blocks/{new_page_id}/children?page_size=100",
headers=headers,
)
new_root_blocks = response.json()["results"]
# Populate nested blocks
recursive_block_creation(root_blocks, new_root_blocks, headers)
# Done
# Usage example:
# `export NEW_PAGE_URL=$(script.py duplicate-notion-page --new_title ... | tail -1 )`
click.echo(new_page_url)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment