Last active
May 15, 2025 21:06
-
-
Save m4rkk/4ff0052c2572114ee5ea38a09f8ddc88 to your computer and use it in GitHub Desktop.
Publish MarkDown document to a Notion page. I used ChatGPTExporter to export threads to .MD files manually; then post process with this to fix up code blocks and push it to Notion.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # md2n.sh — Move MarkDown document into notion. | |
| # | |
| # DESCRIPTION: | |
| # Preserves code blocks and provides a copy button on each block. | |
| # Yaml blocks are suspect, so patch them up with pandoc. | |
| # | |
| # DEPENDENCIES: | |
| # - Pandoc - tried and true document converter tool. | |
| # - fix-yaml.lua; this simple lua parser is expected at ~/bin/fix-yaml.lua, | |
| # it depends on pandoc to do the work once a block is identified. | |
| # - Python - pushes to Notion. | |
| # - API keys, Notion User Token V2, and parent page ID expected to be found | |
| # in a .env file in your ~/secrets/notion_keys.env. | |
| # - ChatGPTExport or some other tool to generate the MarkDown file from threads. | |
| # | |
| # USAGE: | |
| # md2n.sh <markdown-file> [page-title] | |
| # | |
| # AUTHOR: | |
| # m4rkk | |
| # | |
| set -euo pipefail | |
| # Load environment variables from well-known locations | |
| CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/secrets}" | |
| for envfile in "$PWD/.env" "$CONFIG_DIR/notion_keys.env"; do | |
| if [ -f "$envfile" ]; then | |
| echo "Loading environment from $envfile" | |
| set -o allexport | |
| # shellcheck disable=SC1091 | |
| source "$envfile" | |
| set +o allexport | |
| break | |
| fi | |
| done | |
| usage() { | |
| echo "Usage: $(basename "$0") <markdown-file> [page-title]" | |
| exit 1 | |
| } | |
| if [ $# -lt 1 ] || [ $# -gt 2 ]; then | |
| usage | |
| fi | |
| SRC="$1" | |
| # Derive title from filename if not provided | |
| title_param="${2:-}" | |
| TITLE="${title_param:-$(basename "$SRC" .md)}" | |
| TMP="$(mktemp /tmp/fixed.XXXXXX).md" | |
| # Verify required environment variables | |
| : "${NOTION_TOKEN_V2:?Environment variable NOTION_TOKEN_V2 must be set (or defined in .env)}" | |
| : "${NOTION_PARENT_PAGE_ID:?Environment variable NOTION_PARENT_PAGE_ID must be set (or defined in .env)}" | |
| # 1) Fix YAML fences | |
| pandoc -f markdown -t gfm+yaml_metadata_block --lua-filter=$HOME/bin/fix-yaml.lua \ | |
| -o "$TMP" "$SRC" | |
| # 2) Import into Notion with custom title | |
| PARENT_URL="https://www.notion.so/${NOTION_PARENT_PAGE_ID}" | |
| python3 - <<EOF | |
| import os | |
| from notion.client import NotionClient | |
| from notion.block import PageBlock | |
| from md2notion.upload import upload | |
| token = os.environ['NOTION_TOKEN_V2'] | |
| parent_url = "$PARENT_URL" | |
| title = "$TITLE" | |
| file_path = "$TMP" | |
| client = NotionClient(token_v2=token) | |
| parent = client.get_block(parent_url) | |
| # Create a new child page with the given title | |
| new_page = parent.children.add_new(PageBlock, title=title) | |
| # Upload markdown content into that new page | |
| with open(file_path, "r", encoding="utf-8") as mdFile: | |
| upload(mdFile, new_page) | |
| EOF | |
| # Clean up | |
| echo -e "\nImported to Notion under parent ${NOTION_PARENT_PAGE_ID} with title \"${TITLE}\"" | |
| rm "$TMP" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- fix-yaml.lua | |
| -- Part of the md2n.sh solution to publish ChatGPT threads as Notion pages. See md2n.sh for details. | |
| -- Author: m4rkk | |
| function CodeBlock(el) | |
| -- if it was a fenced block with no info-string… | |
| if #el.classes == 0 then | |
| -- and the first line looks like YAML key:value… | |
| local first = el.text:match("([^\n]+)") | |
| if first and first:match("^%s*[%w_%-]+%s*:%s*.+") then | |
| return pandoc.CodeBlock(el.text, {class="yaml"}) | |
| end | |
| end | |
| return el | |
| end |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It's a little wonky - need to finish this when time permits