banner
魔法少女秋小日

Qiuxiaori

github
email

基于 Notion 的 P.A.R.A 实战(二)

基于 Notion 的 P.A.R.A 实战(二)#

基于 Notion 的 P.A.R.A 实战 (一) 发布后,这套系统又经过了些小的迭代升级,如今也使用四个月了,运行良好!

和 Obsidian 的使用体验相比较,免去了打开软件,手动同步数据,折腾插件页面等复杂流程;同时 Notion 开放的 api 接口进一步提高了方便程度和可玩性,你完全可以把 Notion 当作一个支持多种视图查看的很好用的数据库; Calendar 的发布也带来了我一直想要拥有的功能:在一个日历中定制自己关心的内容,尽管现在还略显粗糙,但是这就是我想要的功能;搭配浏览器的插件可以把我所有的 **「稍后在读」** 归拢在 Notion 里;手机端也另外写了一些快捷指令来支持对读书笔记的记录,只要复制文本再配合苹果的 轻点两下 就可以把内容同步到 Notion 了。

可以说整个使用体验都是很丝滑的,接下来稍微介绍下我都折腾了什么。

一,系统改动#

  • Resource
    • 新增了 Fav 模块,用来记录值得多次阅读的内容。
    • 新增了 Thing 模块,用来记录购买的物品 / 服务的使用体验。
  • Archive
    • 对内容较多的 Resource 数据表添加周分组。

二,邮件服务 - 同步笔记#

  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('search 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('search error' + err.mesasge)
                imap.end()
            }
        })
    }
    
    module.exports = mailReceiver
    
  1. Notion 同步

Notion 的对接分内部集成和外部集成,内部集成较为简单,获取到 token 即可,外部集成则需要实现一整套鉴权,自用采用内部集成的方式就可以了。

详见官方文档:Build your first integration (notion.com)

  • 参考代码

    
    const { Client } = require("@notionhq/client")
    
    class PARA {
        constructor() {
            this.client = new Client({
                auth: process.env.NOTION_TOKEN, // 申请的 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运行服务 **

/**
 * Mail 监听同步到 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。

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