偶然看到<<我也来打造一个个人阅读追踪系统>>,觉得这个想法非常不错。于是利用工作外的时间,经过半个月的折腾,初步搭成了一个自动化的个人阅读追踪系统。在这个系统搭成后,才发现 Read it Later = Read Never 离自己好近。

工具

包括但不仅仅局限于下面的工具,这里我使用Instapaper作为稍后阅读的工具,IFTTT作为连接InstapaperCloud的中枢,而WebTask作为我的云端处理工具,负责真正请求各类 API。

工作流程

在工作和生活中,我们经常会把文章保存到各种渠道中,比如印象笔记、Instapaper、Pocket 等等,而这时,文章分散到各处,不利于我们了解和统计自己的阅读情况。这时是否可以在文章存档到各处后,自动在 GItHub 中创建一个 Issues,而日后对文章的评论和圈点也会体现在 Issues 中,而且还有一个可视化的工具,详尽的展示了这一段时间内你的阅读情况,从而可以达到展示和督促的作用。

工作流程

初始化 WebTask

webTask 作为Serverless架构的服务提供者,可以很方便的让我们在云端执行各类任务,在本文中,它的主要作用就是请求 GitHub 的 API,以实现各类功能,比如给某个 Issues 添加评论、关闭某个 Issues…

安装命令行工具

1
2
npm install -g wt-cli
wt init <YOUR-EMAIL>

创建项目

新建一个 node 工程并添加 index.js 文件,补充如下代码:

1
2
3
4
5
6
7
8
9
10
11
const Express = require('express')
const Webtask = require('webtask-tools')
const bodyParser = require('body-parser')
const app = Express()

app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

require('./routes/reading')(app)

module.exports = Webtask.fromExpress(app)

创建/routes/reading.js 文件,这里以在仓库创建 Issues 为例,完整代码可参考xuexiaoao/demo.serverless-mern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
require('es6-promise').polyfill()
require('isomorphic-fetch')

const REPO_OWNER = 'xuexiaoao'
const REPO_NAME = 'reading'
const REPO_ID = 107835589

module.exports = (app) => {
app.post('/reading', (req, res) => {
const { GITHUB_ACCESS_TOKEN, ZENHUB_ACCESS_TOKEN, ZENHUB_ACCESS_TOKEN_V4 } = req.webtaskContext.secrets
const { action, issue } = JSON.parse(req.body.payload)
const { url, html_url, number } = issue

console.info(`[BEGIN] issue updated with action: ${action}`)

if (action === 'opened') {
fetch(`${url}?access_token=${GITHUB_ACCESS_TOKEN}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ milestone: 1, }),
}).then(
() => console.info(`[END] set milestone successful! ${html_url}`),
(e) => res.json(e),
)
} else if (action === 'milestoned') {
fetch(`https://api.zenhub.io/p1/repositories/${REPO_ID}/issues/${number}/estimate?access_token=${ZENHUB_ACCESS_TOKEN}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ estimate: 1 }),
}).then(
() => console.info(`[END] Set estimate successful! ${html_url}`),
(e) => console.error(`[END] Failed to set estimate! ${html_url}`, e),
)
fetch(`https://api.zenhub.io/v4/reports/release/59ec29a2bedf0a4d94322b26/items/issues?access_token=${ZENHUB_ACCESS_TOKEN_V4}`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `add_issues%5B0%5D%5Bissue_number%5D=${number}&add_issues%5B0%5D%5Brepo_id%5D=${REPO_ID}`,
}).then(
() => console.info(`[END] set release successful! ${html_url}`),
(e) => console.error(`[END] Failed to set release! ${html_url}`, e),
)
}

res.json({ message: 'issue updated!' })
},
)
}

这里有几个地方需要注意:

  • REPO_OWNER:这是你的 github 名字
  • REPO_NAME:这是你的仓库名称,之后所有的 Issues 都会创建到这里
  • REPO_ID:这个 id 可以通过 github api 获得:https://api.github.com/users/${REPO_OWNER}
  • GITHUB_ACCESS_TOKEN:这个没什么好说的就是在 github 上新建一个 Personal access tokens
  • ZENHUB_ACCESS_TOKEN:这个是在 zenhub 新建一个 token
  • ZENHUB_ACCESS_TOKEN_V4:这个官方 api 没有说明,但是可以打开 zenhub 对应仓库的浏览器开发者工具,任意找一个是 api.zenhub.io 的请求,在 Request Headers 中找到 x-authentication-token 对应的值就是这个 token 了

上传到 webtask

这里需要使用上面准备的 token,然后执行命令即可。由于使用 webtask 的上下文绑定工具webtask-tools,它会对一些敏感信息,比如我们的 token 进行加密,所以不必担心 token 泄漏的问题。

1
wt create index --bundle --secret GITHUB_ACCESS_TOKEN=$GITHUB_ACCESS_TOKEN --secret ZENHUB_ACCESS_TOKEN=$ZENHUB_ACCESS_TOKEN --secret ZENHUB_ACCESS_TOKEN_V4=$ZENHUB_ACCESS_TOKEN_V4

使用 IFTTT 自动化处理

那如何保证一有文章存入Instapaper中,就会新建一个 Issues 呢,这里就需要 IFTTT 这个神器了,这里不对 IFTTT 做详细介绍,如果有机会,可以另开一篇文章,专门介绍 IFTTT。

得益于 IFTTT 非常丰富的第三方服务,IFTTT 可以直接创建 Instapaper 与 GitHub Issue 相集成的 Applet:If new item saved, then create a new issue - IFTTT,就可以在当 Instapaper 新增文章的时候,自动在 GitHub 所指定的仓库 xuexiaoao/reading 中创建一个新的 Issue 并添加相应的标题、链接以及描述等相关信息

这里给出把文章保存到 Instapaper 后,自动在仓库创建 Issues 的例子,其他的操作都可以类似处理,如果有疑问,可以在本文留言。

集成 ZenHub

现在仅仅会自动新建 Issues,而我们还需要把 Issues 添加到 Milestone 中,就可以利用 Zenhub 的图表功能,可视化的查看和分析你的阅读情况。

Github 提供 WebHock 功能,方便的让我们监控仓库的各类事件,从而触发某个自定义的功能,而这些功能我们已经放到了 WebTask 上面了。

create_webhock

这里你需要改的就是Payload URL,它是你在执行上传到 webtask后得到的一个 URL,其余的可以参考上图。

其他功能示例

在 Instapaper 做笔记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
app.post('/reading-note', (req, res) => {
const { GITHUB_ACCESS_TOKEN } = req.webtaskContext.secrets

const title = req.query.title
const note = req.body.note
const highlightedText = req.body.highlightedText
console.info('[BEGIN]', { title, note, highlightedText })

let keyword = encodeURIComponent(title.replace(/\s/g, '+'))
console.info('[KEYWORD]', keyword)

fetch(`https://api.github.com/search/issues?q=${keyword}%20repo:xuexiaoao/reading%20is:open`)
.then(response => response.json())
.then(data => {
console.info('[RESULT]', data)
if (data.total_count > 0) {
data.items.forEach(({ url, html_url }) =>
fetch(`${url}/comments?access_token=${GITHUB_ACCESS_TOKEN}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: `> ${highlightedText} <br/><br/> > ${note}` }),
})
.then(() => console.info(`[END] added comment successful! ${html_url}`))
.catch(err => res.json('error', { error: err })))
res.json({ message: 'Added comment into issue successful!' })
}
res.json({ error: 'Not Found!' })
})
.catch(err => res.json('error', { error: err }))
})

Instapaper 归档文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
app.get('/reading', (req, res) => {
const { GITHUB_ACCESS_TOKEN } = req.webtaskContext.secrets

console.info('[BEGIN]', req.query)
const title = req.query.title

let keyword = encodeURIComponent(title.replace(/\s/g, '+'))
console.info('[KEYWORD]', keyword)

fetch(`https://api.github.com/search/issues?q=${keyword}%20repo:xuexiaoao/reading`)
.then(response => response.json())
.then(data => {
console.info('[RESULT]', data)
if (data.total_count > 0) {
data.items.forEach(({ url, html_url }) =>
fetch(`${url}?access_token=${GITHUB_ACCESS_TOKEN}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ state: 'closed' }),
})
.then(() => console.info(`[END] issue closed successful! ${html_url}`))
.catch(err => res.json('error', { error: err })))
res.json({ message: 'Closed issue successful!' })
} else {
console.info('[RESULT]', data)

fetch(`https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/issues?access_token=${GITHUB_ACCESS_TOKEN}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title }),
})
.then(response => response.json())
.then(({ url, html_url }) => {
console.info(`[END] issue created successful! ${html_url}`)
fetch(`${url}?access_token=${GITHUB_ACCESS_TOKEN}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ state: 'closed' }),
})
.then(() => console.info(`[END] issue closed successful! ${html_url}`))
.catch(err => res.json('error', { error: err }))
})
.catch(err => res.json('error', { error: err }))
}
res.json({ error: 'Finished achieve reading item!' })
})
.catch(err => res.json('error', { error: err }))
})

总结

至此,一个自动化的阅读追踪系统已经搭建完成,现在我们就可以查看每天乃至每周的阅读情况了,你只需要定期的查看阅读效果,就可以对自己的阅读计划进行调整和变更。

当然还有一些问题需要处理,比如对文章进行分类以使这套系统更有参考性,是否需要把在 Instapaper 中的笔记保存到自己的知识库中。

参考资料

留言

2018-01-13

⬆︎TOP