banner
魔法少女秋小日

Qiuxiaori

github
email

Notionに基づくP.A.R.A実践(2)

Notion に基づく P.A.R.A 実践(2)#

Notion に基づく P.A.R.A 実践(1) が公開されて以来、このシステムは少しの迭代アップグレードを経て、現在も 4 ヶ月使用しており、順調に動作しています!

Obsidian との使用体験を比較すると、ソフトウェアを開く必要がなく、手動でデータを同期したり、プラグインページをいじったりする複雑なプロセスが省かれました。また、Notion のオープン API インターフェースは、利便性と遊び心をさらに高めており、Notion をさまざまなビューで見ることができる非常に使いやすいデータベースとして利用できます。Calendar のリリースは、私がずっと欲しかった機能をもたらしました:カレンダーの中で自分が気にかける内容をカスタマイズすることです。今はまだ少し粗いですが、これが私が求めていた機能です。ブラウザのプラグインを組み合わせることで、私のすべての 後で読む を Notion にまとめることができます。モバイル端末でも、読書ノートの記録をサポートするためにいくつかのショートカットを作成しました。テキストをコピーし、Apple の ダブルタップ と組み合わせるだけで、内容を Notion に同期できます。

全体の使用体験は非常にスムーズで、次に私がどのようなことを試したかを少し紹介します。

1. システムの変更#

  • Resource
    • 読む価値のある内容を記録するための Fav モジュールを追加しました。
    • 購入した物品 / サービスの使用体験を記録するための Thing モジュールを追加しました。
  • Archive
    • 内容が多い Resource データベースに週ごとのグループ分けを追加しました。

2. メールサービス - ノートの同期#

  1. メールサーバー

imapパッケージを使用してメールを監視し、mailparserパッケージでメール内容を解析します。

  • 参考コード

    const Imap = require('imap')
    const MailParser = require('mailparser').MailParser
    const moment = require('moment')
    const logger = require('./logger')('sdk/logger')
    let emailCount = 10
    console.log('-----サーバー起動-----' + moment().format('YYYY-MM-DD HH:mm:ss') + '-------------------------------------')
    
    let inflag = false
    
    function mailReceiver(userInfo, mailHandler) {
        let imap = new Imap(userInfo)
        imap.connect()
        setTimeout(() => imap.end(), 5 * 60 * 1000) // 5分ごとに接続を再構築
    
        imap.once('ready', () => {
            setTimeout(() => {
                if (!inflag) {
                    logger.warn(`接続できませんでした。再接続します。`)
                    imap.end()
                }
            }, 30_000)
    
            imap.openBox('INBOX', false, () => {
                inflag = true
                imap.once('mail', num => { })
                searchUnseen(imap, mailHandler)
            })
        })
    
        imap.on('mail', num => {
            if (num > emailCount) return // 大量のメールが流入するのを防ぐ
            searchUnseen(imap, mailHandler)
        })
    
        imap.once('error', err => {
            logger.error(`接続失敗、再接続します ${err.message}`)
            mailReceiver(userInfo, mailHandler)
        })
    
        imap.once('end', () => mailReceiver(userInfo, mailHandler))
    }
    
    function searchUnseen(imap, mailHandler) {
        let timestr = (new Date(+new Date() - (5 * 60 * 1000))).toISOString()
        imap.search(['UNSEEN', ['SINCE', timestr]], (err, results) => {
            if (err) {
                logger.error('検索エラー' + err.mesasge)
                imap.end()
                return
            }
            if (results.length == 0) return
    
            try {
                let f = imap.fetch(results, { bodies: '', markSeen: true })
    
                f.on('message', (msg, seqno) => {
                    msg.on('body', (stream, info) => {
                        let parser = new MailParser()
                        stream.pipe(parser)
                        let mail = {}
                        parser.on("headers", headers => mail.headers = headers)
                        parser.on("data", content => mail.content = content)
                        parser.on('end', () => mailHandler(mail))
                    })
    
                    msg.once('end', () => { })
                })
    
                f.once('error', err => {
                    logger.error(`メール受信エラー:${err.message}`)
                    imap.end()
                })
    
                f.once('end', () => imap.end())
    
            } catch (err) {
                logger.error('検索エラー' + err.mesasge)
                imap.end()
            }
        })
    }
    
    module.exports = mailReceiver
    
  1. Notion の同期

Notion の接続は内部統合と外部統合に分かれ、内部統合は比較的簡単で、トークンを取得するだけで済みます。外部統合は一整セットの認証を実装する必要がありますが、自分用には内部統合の方法で十分です。

詳細は公式ドキュメントを参照してください:最初の統合を構築する (notion.com)

  • 参考コード

    
    const { Client } = require("@notionhq/client")
    
    class PARA {
        constructor() {
            this.client = new Client({
                auth: process.env.NOTION_TOKEN, // 申請したトークン
            })
        }
    
        async findPageByName(pageName) {
            const res = await this.client.databases.query({
                "database_id": this.database_id,
                filter: {
                    property: 'Name',
                    rich_text: {
                        equals: pageName
                    },
                },
            })
            return res.results[0]
        }
    
        async addPage(item) {
            let { name, mediaType, author, startDate, endDate, remark, score, status, emoji, url } = item
            const page = await this.findPageByName(name)
            if (page) {
                return page
            }
    
            const data = {
                "parent": { "database_id":  this.database_id },
                "icon": {
                    "emoji": emoji || "🥬"
                },
                "cover": {
                    "external": {
                        "url": "https://upload.wikimedia.org/wikipedia/commons/6/62/Tuscankale.jpg"
                    }
                },
                "properties": {
                }
            }
            if (name) data.properties.Name = {
                "title": [
                    {
                        "text": {
                            "content": name
                        }
                    }
                ]
            }
            if (author) data.properties.Author = {
                "rich_text": [
                    {
                        "text": {
                            "content": author
                        }
                    }
                ]
            }
            if (remark) data.properties.Remark = {
                "rich_text": [
                    {
                        "text": {
                            "content": remark
                        }
                    }
                ]
            }
            if (status) data.properties.Status = {
                "status": {
                    "name": status
                }
            }
            if (startDate) data.properties.Dates = {
                "type": "date",
                "date": {
                    "start": startDate,
                }
            }
            if (endDate) data.properties.Dates = {
                "type": "date",
                "date": {
                    "end": endDate
                }
            }
            if (startDate && endDate) data.properties.Dates = {
                "type": "date",
                "date": {
                    "start": startDate,
                    "end": endDate
                }
            }
            if (mediaType) data.properties.MediaType = {
                "select": {
                    "name": mediaType
                }
            }
    
            if (url) data.properties.Url = {
                url
            }
            if (score) data.properties.Score = { "number": score }
            return this.client.pages.create(data)
        }
    
        async addBlock(item, content) {
            let page = await this.findPageByName(item.name)
            if (!page) {
                page = await this.addPage({ name: pageName })
            }
            const res = await this.client.blocks.children.append({
                block_id: page.id, 
                children: [{
                    "type": "paragraph",
                    "paragraph": {
                        "rich_text": [{
                            "type": "text",
                            "text": {
                                "content": content,
                                "link": null
                            }
                        }],
                        "color": "default"
                    }
                }]
            })
            return res.results[0]
        }
    }
    
    class ResouceseArt extends PARA {
        constructor() {
            super()
            this.database_id = '' // あなたのデータベースID
        }
    }
    
    module.exports = {
        ResouceseArt
    }
    
    
  1. **pm2でサービスを実行 **

/**
 * メールをNotionデータベースに同期するリスナー
 */
const mailListener = require('../sdk/mail-receiver')
const logger = require('../sdk/logger')('server/mail-sync-notion')
const { ResouceseArt, ResourceFlash, ResourceFav } = require('../sdk/notion-para')
let art = new ResouceseArt()

let userInfo = {
    user: '[email protected]',
    password: '',
    host: 'outlook.office365.com',
    port: 993,
    tls: true
}

mailListener(userInfo, async (mail) => {
    if (!mail) return
    const subject = mail.headers.get('subject')
    const text = mail.content.text
    try {
        if (subject.indexOf('flash') > -1) {
            const page = await flash.addPage({ name: text, url: text })
            logger.info('同期成功', { subject, text, page })
        } 
        
				// その他の処理ロジック
    } catch (err) {
        logger.error('Notionの同期に失敗しました', { err })
    }
})
  1. ショートカットの追加

    まず iPhone のメールアプリに送信者アカウントを追加し、次にメールを送信するショートカットを作成し、最後に設定でダブルタップを選択して作成したショートカットを選択します。

c6850124e261a3465b9dee00725d4e5
e8ba570d858c31e49a0bb8dc0a887f9
b59d5d294bbb21fac87a3eeefd5df6b

2.2 のコードを利用して TelegramBot や Cli などの方法で内容を Notion に同期することもできます。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。