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#
- 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
- 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 }
**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 })
}
})
-
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.
You can also use the code from 2.2 to add TelegramBot, CLI, and other methods to sync content to Notion.