banner
魔法少女秋小日

Qiuxiaori

github
email

P.A.R.A Practical Application Based on Notion (Part Two)

Notion-based P.A.R.A Practical Application (Part 2)#

Since the release of Notion-based P.A.R.A Practical Application (Part 1), this system has undergone some minor iterative upgrades and has been in use for four months now, running smoothly!

Compared to the experience of using Obsidian, it eliminates the complex processes of opening software, manually syncing data, and fiddling with plugin pages; at the same time, Notion's open API interface further enhances convenience and playability. You can fully treat Notion as a very useful database that supports multiple viewing options. The release of the Calendar feature has also brought the functionality I have always wanted: customizing the content I care about in one calendar. Although it is still a bit rough around the edges, this is exactly the functionality I wanted. Coupled with browser plugins, I can consolidate all my "Read Later" items in Notion; I have also written some shortcuts on the mobile side to support recording reading notes. As long as I copy the text and use Apple's "Double Tap", I can sync the content to Notion.

The overall user experience can be described as very smooth. Next, I will briefly introduce what I have been working on.

1. System Changes#

  • Resource
    • Added a Fav module to record content worth reading multiple times.
    • Added a Thing module to record the usage experience of purchased items/services.
  • Archive
    • Added weekly grouping to the Resource data table for content with a lot of entries.

2. Email Service - Sync Notes#

  1. Email Server

Using the imap package to listen for emails and the mailparser package to parse email content.

  • Reference Code

    const Imap = require('imap')
    const MailParser = require('mailparser').MailParser
    const moment = require('moment')
    const logger = require('./logger')('sdk/logger')
    let emailCount = 10
    console.log('-----Server Started-----' + 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) // Reconnect every 5 minutes
    
        imap.once('ready', () => {
            setTimeout(() => {
                if (!inflag) {
                    logger.warn(`Unable to connect, reconnecting.`)
                    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 // Prevent a flood of emails
            searchUnseen(imap, mailHandler)
        })
    
        imap.once('error', err => {
            logger.error(`Connection failed, reconnecting ${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('search error' + err.message)
                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(`Error receiving email: ${err.message}`)
                    imap.end()
                })
    
                f.once('end', () => imap.end())
    
            } catch (err) {
                logger.error('search error' + err.message)
                imap.end()
            }
        })
    }
    
    module.exports = mailReceiver
    
  1. Notion Sync

Notion integration is divided into internal and external integration. Internal integration is relatively simple; you just need to obtain a token. External integration requires implementing a complete set of authentication. For personal use, the internal integration method is sufficient.

See the official documentation for details: Build your first integration (notion.com)

  • Reference Code

    
    const { Client } = require("@notionhq/client")
    
    class PARA {
        constructor() {
            this.client = new Client({
                auth: process.env.NOTION_TOKEN, // The token applied for
            })
        }
    
        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 = '' // Your database id
        }
    }
    
    module.exports = {
        ResouceseArt
    }
    
    
  1. **pm2 Run Service**

/**
 * Mail listener syncs to Notion database
 */
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('Sync successful', { subject, text, page })
        } 
        
				// Other processing logic
    } catch (err) {
        logger.error('Failed to sync Notion', { err })
    }
})
  1. Add Shortcuts

    First, add the sender's account in the built-in Mail app on iPhone, then write a shortcut to send emails, and finally select the created shortcut in the settings for double-tap.

c6850124e261a3465b9dee00725d4e5
e8ba570d858c31e49a0bb8dc0a887f9
b59d5d294bbb21fac87a3eeefd5df6b

You can also use the code from 2.2 to add TelegramBot, CLI, and other methods to sync content to Notion.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.