[NOTION] 노션 깃허브 블로그 자동 연동하기

업데이트:



※ 본 포스팅은 lourcode 님의 글을 참고하였습니다.(원본링크 : https://lourcode.kr/posts/Jekyll-기반-Github-Pages와-Notion-Page-연동/)

1. API 키 설정

1-1. 아래 주소 클릭

  https://www.notion.so/my-integrations

1-2. 새 API 통합 클릭

0

1-3. 연결된 워크스페이스를 확인하고 이름을 만들어주고 ‘제출’ 버튼 클릭

1

1-4. 시크릿키가 생성되면 표시 → 복사 를 순서대로 클릭하여 키값을 복사한다.

2

※ 시크릿키값은 secret_* 의 형태로 되어있다.

2. 노션에서 사용/연동 할 데이터베이스 생성

2-1. 아래와 같이 샘플용으로 만들어본다.(공유링크 : https://asteriskos.notion.site/Blog-DB-85109cf911b74a62a5041f6047d5792d?pvs=4)

3

2-2. 테이블에 마우스를 가져가 되면 옆에 더보기(::)가 표시되는데 클릭한다.

4

2-3. 링크복사를 누른다

https://www.notion.so/asteriskos/de2dcf74aff44030954dbd4d991c559b?v=1f7e542c866341c1a8d68c9c328c3faf&pvs=4

표시한 부분이 데이터베이스의 ID 이다. 기억해둔다.

3. 데이터베이스 페이지에 API연동

3-1. 데이터베이스 생성한 페이지 우측상단에 더보기(…) 를 클릭하여 API를 연동한다.

5

3-2. 연결할거냐는 팝업이뜨는데, ‘확인’ 버튼을 눌러 연결됨을 확인하자.

6

7

4. 깃허브 파일 설정

4-1. Settings → Secrets and variables → Actions 를 눌러 키를 생성해야한다.

   4-1-1. '1-4' 에서 확보한 노션토큰을 'NOTION_TOKEN'으로 저장한다.
   
   4-1-2. '2-3' 에서 확보한 DB ID를 'DATABASE_ID' 로 저장한다.
   
   4-1-2. '깃허브 personalKey'를 'GH_TOKEN' 으로 저장한다. 

8

4-2. root 폴더에 ‘_scripts’ 폴더를 생성하고, 생성한 폴더 안에 ‘notion-import.js’ 파일을 만들고 아래 내용을 입력한다.

const { Client } = require("@notionhq/client");
const { NotionToMarkdown } = require("notion-to-md");
const moment = require("moment");
const path = require("path");
const fs = require("fs");
const axios = require("axios");

const notion = new Client({
  auth: process.env.NOTION_TOKEN,
});

function escapeCodeBlock(body) {
  const regex = /```([\s\S]*?)```/g
  return body.replace(regex, function (match, htmlBlock) {
    return "\n```\n" + htmlBlock + "\n```\n";
  })
}

function replaceTitleOutsideRawBlocks(body) {
  const rawBlocks = [];
  const placeholder = "%%RAW_BLOCK%%";
  body = body.replace(/[\s\S]*?/g, (match) => {
    rawBlocks.push(match);
    return placeholder;
  });

  const regex = /\n#[^\n]+\n/g;
  body = body.replace(regex, function (match) {
    return "\n" + match.replace("\n#", "\n##");
  });

  rawBlocks.forEach(block => {
    body = body.replace(placeholder, block);
  });

  return body;
}

// passing notion client to the option
const n2m = new NotionToMarkdown({ notionClient: notion });

(async () => {
  // ensure directory exists
  const root = "_posts";
  fs.mkdirSync(root, { recursive: true });

  const databaseId = process.env.DATABASE_ID;
  let response = await notion.databases.query({
    database_id: databaseId,
    filter: {
      property: "배포",
      checkbox: {
        equals: true,
      },
    },
  });

  const pages = response.results;
  while (response.has_more) {
    const nextCursor = response.next_cursor;
    response = await notion.databases.query({
      database_id: databaseId,
      start_cursor: nextCursor,
      filter: {
        property: "배포",
        checkbox: {
          equals: true,
        },
      },
    });
    pages.push(...response.results);
  }

  for (const r of pages) {
    const id = r.id;
    // date
    let date = moment(r.created_time).format("YYYY-MM-DD");
    let pdate = r.properties?.["날짜"]?.["date"]?.["start"];
    if (pdate) {
      date = moment(pdate).format("YYYY-MM-DD");
    }
    // title
    let title = id;
    let ptitle = r.properties?.["게시물"]?.["title"];
    if (ptitle?.length > 0) {
      title = ptitle[0]?.["plain_text"];
    }
    // tags
    let tags = [];
    let ptags = r.properties?.["태그"]?.["multi_select"];
    for (const t of ptags) {
      const n = t?.["name"];
      if (n) {
        tags.push(n);
      }
    }
    // categories
    let cats = [];
    let pcats = r.properties?.["카테고리"]?.["multi_select"];
    for (const t of pcats) {
      const n = t?.["name"];
      if (n) {
        cats.push(n);
      }
    } 
    // frontmatter
    let fmtags = "";
    let fmcats = "";
    let fmassrtmnt = "";
    if (tags.length > 0) {
      fmtags += "[";
      for (const t of tags) {
        fmtags += t + ", ";
      }
      fmtags += "]";
    }

    if (assrtmnt.length > 0) {
      fmassrtmnt += "[";
      for (const t of assrtmnt) {
        fmassrtmnt += t ;
      }
      fmassrtmnt += "]";
    }
    const fm = `---
title: "${title}"
excerpt: ""
header: ""

categories:
    - ${fmcats}
tags:
    - ${fmtags}
last_modified_at: ${date}
---
<br><br>
`;
    const mdblocks = await n2m.pageToMarkdown(id);
    let body = n2m.toMarkdownString(mdblocks)["parent"];
    if (body === "") {
      continue;
    }
    body = escapeCodeBlock(body);
    body = replaceTitleOutsideRawBlocks(body);

    const ftitle = `${date}-${title.replaceAll(" ", "_")}.md`;

    let index = 0;
    let edited_md = body.replace(
      /!\[(.*?)\]\((.*?)\)/g,
      function (match, p1, p2, p3) {
        // const dirname = path.join("assets/img", ftitle);
        const dirname = path.join("upload", ftitle);
        if (!fs.existsSync(dirname)) {
          fs.mkdirSync(dirname, { recursive: true });
        }
        const filename = path.join(dirname, `${index}.png`);

        axios({
          method: "get",
          url: p2,
          responseType: "stream",
        })
          .then(function (response) {
            let file = fs.createWriteStream(`${filename}`);
            response.data.pipe(file);
          })
          .catch(function (error) {
            console.log(error);
          });

        let res;
        if (p1 === "") res = "";
        else res = `_${p1}_`;

        return `![${index++}](/${filename})${res}`;
      }
    );

    //writing to file
    fs.writeFile(path.join(root, ftitle), fm + edited_md, (err) => {
      if (err) {
        console.log(err);
      }
    });
  }
})();

4-3. 위 JavaScript 파일(notion-import.js)에 대한 dependencies 설정을 위해 ‘package.json’ 파일 하단에 아래 내용을 입력 해 준다.

name: "Build and Deploy"
on:
  repository_dispatch:
    types: [RUN_WORKFLOW_DISPATCH]
      
permissions:
  contents: write
  pages: write
  id-token: write

# Allow one concurrent deployment
concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  importer:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@master      
      - uses: actions/setup-node@v3
        with:
          node-version: "17"

      - run: npm install

      - run: node _scripts/notion-import.js
        env:
          NOTION_TOKEN: $
          DATABASE_ID: $

      - uses: stefanzweifel/git-auto-commit-action@v4
        env:
          GITHUB_TOKEN: $
        with:
          commit_message: "[BOT] Commit & Deploy from Notion"
          branch: main
          commit_user_name: morimin-bot 🤖
          commit_user_email: morimin@github.com
          commit_author: morimin-bot 🤖 <morimin@github.com>
 
  build:
    needs: importer
    runs-on: ubuntu-latest

    steps:           
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: main
          fetch-depth: 1

      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v1

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.1' # reads from a '.ruby-version' or '.tools-version' file if 'ruby-version' is omitted
          bundler-cache: true

      - name: Build site
        run: bundle exec jekyll b -d "_site$"
        env:
          JEKYLL_ENV: "production"

      - name: Test site
        run: |
          bundle exec htmlproofer _site --disable-external --check-html --allow_hash_href

      - name: Upload site artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: "_site$"

  deploy:
    environment:
      name: github-pages
      url: $
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1

4-4. root 폴더에 ‘.github’ 폴더를 생성하고, 생성한 폴더 안에 ‘workflows’ 폴더를 생성하고, 생성한 폴더 안에 ‘pages-deploy.yml’ 파일을 만들고 아래 내용을 입력한다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    .trigger-container {
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
    }

    .trigger-button {
      display: inline-block;
      margin-bottom: 10px;
      padding: 10px 20px;
      background-color: #4c9aff;
      color: white;
      font-size: 16px;
      border: none;
      cursor: pointer;
      border-radius: 4px;
      box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
      transition: background-color 0.3s;
    }

    .trigger-button:hover {
      background-color: #2e86ff;
    }

    .message {
      font-size: 16px;
      color: #333;
    }
  </style>
</head>
<body>
  <div class="trigger-container">
    <button id="triggerButton" class="trigger-button">UPLOAD</button>
    <div id="message" class="message"></div>
  </div>

  <script>
  document.getElementById("triggerButton").addEventListener("click", function() {
    var messageElement = document.getElementById("message");
    messageElement.textContent = "요청 전송 중...";

    var xhr = new XMLHttpRequest();
    xhr.open("POST", "https://api.github.com/repos/USERNAME/REPO_NAME/dispatches", true);
    xhr.setRequestHeader("Accept", "application/vnd.github.v3+json");
    xhr.setRequestHeader("Authorization", "Bearer GITHUB_ACCESS_TOKEN");
    xhr.setRequestHeader("Content-Type", "application/json");

    xhr.onload = function() {
      if (xhr.status === 204) {
        messageElement.textContent = "요청이 성공적으로 전송되었습니다." + xhr.status;
      } else {
        messageElement.textContent = "요청 전송에 실패했습니다.<br>상태 코드: " + xhr.status;
      }
    };

    xhr.onerror = function() {
      messageElement.textContent = "요청 전송 중 알 수 없는 오류가 발생했습니다.";
    };

    xhr.send(JSON.stringify({"event_type": "RUN_WORKFLOW_DISPATCH"}));
  });
</script>
</body>
</html>

4-5. 배포버튼을 만들기 위해, 아래 링크 중 본인의 내용에 맞게 수정(아래 하이라이트 부분)한다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    .trigger-container {
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
    }

    .trigger-button {
      display: inline-block;
      margin-bottom: 10px;
      padding: 10px 20px;
      background-color: #4c9aff;
      color: white;
      font-size: 16px;
      border: none;
      cursor: pointer;
      border-radius: 4px;
      box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
      transition: background-color 0.3s;
    }

    .trigger-button:hover {
      background-color: #2e86ff;
    }

    .message {
      font-size: 16px;
      color: #333;
    }
  </style>
</head>
<body>
  <div class="trigger-container">
    <button id="triggerButton" class="trigger-button">UPLOAD</button>
    <div id="message" class="message"></div>
  </div>

  <script>
  document.getElementById("triggerButton").addEventListener("click", function() {
    var messageElement = document.getElementById("message");
    messageElement.textContent = "요청 전송 중...";

    var xhr = new XMLHttpRequest();
    xhr.open("POST", "https://api.github.com/repos/USERNAME/REPO_NAME/dispatches", true);
    xhr.setRequestHeader("Accept", "application/vnd.github.v3+json");
    xhr.setRequestHeader("Authorization", "Bearer GITHUB_ACCESS_TOKEN");
    xhr.setRequestHeader("Content-Type", "application/json");

    xhr.onload = function() {
      if (xhr.status === 204) {
        messageElement.textContent = "요청이 성공적으로 전송되었습니다." + xhr.status;
      } else {
        messageElement.textContent = "요청 전송에 실패했습니다.<br>상태 코드: " + xhr.status;
      }
    };

    xhr.onerror = function() {
      messageElement.textContent = "요청 전송 중 알 수 없는 오류가 발생했습니다.";
    };

    xhr.send(JSON.stringify({"event_type": "RUN_WORKFLOW_DISPATCH"}));
  });
</script>
</body>
</html>

4-6. 배포 버튼을 링크로 만들기 위해 아래HTML블록생성 링크로 접속한다.

https://www.notion-tools.com/embeds/html

10

4-7. 위 ‘4-5’에서 만든 코드를 넣고 링크를 생성한다.

11

4-8. 노션에서 /embed 를 검색해서 임베드 블록을 생성한다.

12

4-9. 위 ‘4-7’에서 생성한 링크를 임베드 해준다.

13

4-10. 생성된 배포 버튼(임베디드된 링크)

14

4-11. 업로드 버튼을 눌르면, 깃액션을 통해 봇으로 배포되는 것을 확인할 수 있다.

15






◻ Sponsored by