我也来打造一个个人阅读追踪系统

国庆放假期间,偶然发现这篇文章《Serverless实战:打造个人阅读追踪系统》http://insights.thoughtworks.cn/serverless-combat/,太吸引我了。

进入互联网时代,知识的获取成本变得前所未有的低廉,但是无论再好的知识,若是没有对个人产生价值的话,那也只不过是一种信息噪音而已

随着时间的推移,稍后阅读的 Instapaper 里面的文章将会变得越来越多,就像我们在代码中所注释的 TODO:可能就变成了Never Do,「稍后阅读」也是一样地被广为诟病:Read it Later = Read Never。其实我发现文章堆积的一个永恒痛点就是没有有效的方式追踪自己的阅读需求与能力,其核心原因在于阅读的速度赶不上添加的速度。从而没办法可视化的评估阅读进度、合理安排阅读计划,也就没办法给予自己适当的奖励,长此以往必然将失去阅读的动力。

稍后阅读中永远读不完的痛点:缺乏追踪

摘自:《Serverless实战:打造个人阅读追踪系统》

在国庆「探亲」和「归隐山林」的我,利用「深夜」时间边学写实践,再结合自己的一些工具和使用场景,整理成这篇文章——「高大上的个人阅读追踪系统」

目标也很简单:

跟踪自己的阅读习惯和统计阅读量,别让「稍后阅读」变成了「再也不读」了

流程

我也来打造一个个人阅读追踪系统_第1张图片
Architecture - Content Management -1-

主要过程:根据文章阅读来源,利用 Workflow 将文章保存到 Instapaper 中,同时触发 IFTTT Applet 在 Github 和 ZenHub 中创建 Issue 来跟踪阅读情况;然后再有空的时候阅读 Instapaper 中的文章,并对文章进行归档,同时触发 IFTTT,Close Issue,并利用 ZenHub的图表功能,并对阅读进行汇总和后续分析。将「阅读」这件事形成「闭环」。

工具

1. Instapaper
2. ZenHub
3. IFTTT
4. LeanCloud
5. GitHub
6. Workflow
7. 印象笔记
8. Feedly Rss 阅读器

阅读文章来源

主要有「Feedly Rss 阅读器」、「微信」和「网站」三种来源。

通过 Feedly 方式很简单的,只要将喜欢文章分享到 Instapaper 上即可。

我也来打造一个个人阅读追踪系统_第2张图片

当从微信公众号或者网站看到好文章时,也只需「复制」文章的链接,下滑手机,使用 Workflow 神器 —— 「Clipboard to Instapaper」,保存到 Instapaper 上

我也来打造一个个人阅读追踪系统_第3张图片
WechatIMG120

Workflow 下载链接:
https://workflow.is/workflows/7d54eabb797647c4b92960b8318d8c2b

IFTTT 桥接器

我也来打造一个个人阅读追踪系统_第4张图片

IFTTT 的理念就像它的名字 If This Then That ,可以将两个不同的服务关联到一起,最典型的例子就是「如果明天下雨,那么就提醒我带雨伞」,在这个设定下,IFTTT 会定时查询第二天的天气预报,如果天气预报返回的结果是第二天下雨,那么 IFTTT 就会给我设置的手机号发送短信,通知我第二天出门别忘了带伞。

了解 IFTTT 可参考:https://sspai.com/post/39258

IFTTT:添加Instapaper文章后自动创建GitHub Issue

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

当从 Workflow 或者 Feedly 阅读器创建一条 Instarpaper 记录时,可以触发 IFTTT 的 Then 条件时,创建 GitHub 一条 Issue。

首先需要创建一个 Repository,来存放 Issues,如本文创建的:https://github.com/fanly/reading/issues

有了 Repository,就可以直接利用 IFTTT 创建自己的 Applet。


我也来打造一个个人阅读追踪系统_第5张图片

当接收到 IFTTT 的 notification 时,并且在 Activity 可以看到每次运行 Applet 的状态:

我也来打造一个个人阅读追踪系统_第6张图片

在 GitHub 中就能看到了创建了对应的 Issue:


我也来打造一个个人阅读追踪系统_第7张图片

整个操作流程很方便 。如:在微信公众号看到一篇好文章,先复制文章链接,接着调起 Workflow,将文章保存到 Instapaper 中,最后 IFTTT 会实时监控 Instapaper,发现有新的 item created,就会触发 Then 语句,将文章的 title 和 des,以及链接保存到 Github 的 Issue 中。

如果说只是需要将想看的文章放到 Instapaper。我想到这就可以结束了。根据这篇《Serverless实战:打造个人阅读追踪系统》文章:

但仅仅只是添加一个 Issue 还不够,这时候还需要将这个 Issue 加入到指定的 Milestone 以便利用 ZenHub 的图表功能。

这里就需要利用 GitHub 的 Webhooks 和需要我们编写代码,来接收 Webhooks 请求后,创建 Issue 的 Milestone 值和创建 ZenHub。

GitHub

GitHub,作为开发人员,对它再熟悉不过了,如何很好使用 Github 是程序员的必修课。Webhooks 的使用可以作为很多第三方的模仿对象。每个 Webhook 都可以定制化,在什么情况下触发 Webhook,这点和 IFTTT 的 IF 很像,只是 then 交给了我们自己接收处理。

我也来打造一个个人阅读追踪系统_第8张图片

这里创建 ZenHub 时,会选择和哪个 Repository 绑定,同理,也是在 Webhooks 创建一条 Webhook 来接收 Repository 的变化。

我也来打造一个个人阅读追踪系统_第9张图片

GitHub 的 api 文档也是我们学习的地方。如需要「更新Issue 的 milestone」:

我也来打造一个个人阅读追踪系统_第10张图片

更多的 GitHub API 查看:https://developer.github.com/v3/

有了接口,我们还需要权限 (access token),在 https://github.com/settings/tokens/ 中创建 personal access token:

我也来打造一个个人阅读追踪系统_第11张图片

有了 Webhook,GitHub API,和 access token,万事俱备只欠东风了。

Serverless 初探和 LeanCloud 选择

Serverless 架构,或者称为无服务器架构,是最近几年新出来的一种架构风格。对于 Serverless 来说,只是用户不用更多的去关注和考虑服务器的相关内容和配置了,甚至也不需要再去考虑服务器的规格大小、存储空间、带宽、自动扩缩容问题等等;同时,也不需要再对服务器进行运维了,无需不断的打系统补丁、应用补丁、无需进行数据备份、软件配置、环境更新等工作了。

但是没有服务器,如何来将程序、应用运行起来呢?这里要介绍的是 Serverless 包含的两个概念:函数即服务,Function as a Service FaaS,后端即服务,Backend as a Service BaaS。

总之一句话:「只关注实现的功能,剩下的交给 Serverless 服务商处理了。

更多 Serverless 的知识可以参考:

https://www.qcloud.com/community/article/782918

正如《Serverless实战:打造个人阅读追踪系统》作者使用的是 Webtask,国内如腾讯云、阿里云,国外的 Oracle 开源的 Fn project (https://github.com/fnproject/fn/blob/master/docs/serverless.md) 等,这几天我都尝试了一遍,但个人觉得国外的访问多多少少一点「跨国」不稳定,国内的两家都需要去配置一些「东西」,我试了试还是决定暂时不用,各位看官可以尝试的。

最后我还是使用 LeanCloud (https://leancloud.cn) Nodejs 云引擎 —— 更多的是一个容器,而且完全可以充当 Serverles 来操作,具体的开发可以参考官网,而且本人对 LeanCloud 情有独钟。

注:在之前的文章中,介绍如何开发公众号自动回复功能时,使用的也是 LeanCloud。

搭建公众号自动回复功能

让我们开始写接收 Webhook 的代码吧:

server.route({
        method: 'POST',
        path: '/*****',
        handler: function (request, reply) {
            const GITHUB_ACCESS_TOKEN = '******************'
            const ZENHUB_ACCESS_TOKEN = '******************'
            const REPO_ID = '*******'
            // const { GITHUB_ACCESS_TOKEN, ZENHUB_ACCESS_TOKEN } = req.webtaskContext.secrets
            const { action, issue } = request.payload
            const { url, title, html_url, number, body } = issue

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

            if (action === 'opened') {
                // 保存数据到 lean

                let read = new Read();
                // const book = request.payload;
                read.set('title', title);
                read.set('number', number);
                read.set('url', url);
                read.set('body', pub.reconvert(body));
                read.save().then(function (blog) {
                    // 成功保存之后,执行其他逻辑.
                    console.log('成功保存:New object created with objectId: ' + read.id);
                    // reply(blog);
                }, function (error) {
                    // 失败之后执行其他逻辑
                    console.log('Failed to create new object, with error message: ' + error.message);
                    // return reply(Boom.wrap(error, 'error'));
                });

                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) => reply(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)
                )
            }

            reply({ message: 'issue updated!' })
        }
    });

相信大家都能看得懂该功能,当接收到 Issue 的 opened事件时,先将 Issue主要字段存储到 LeanCloud中,便于后续统计和 search 操作,然后再请求 GitHub 和 ZenHub 的接口做对应的处理。

我也来打造一个个人阅读追踪系统_第12张图片
我也来打造一个个人阅读追踪系统_第13张图片

到目前为止我们算完成了「稍后阅读」文章的第一步了。但这不是我们的目标,我们的目标是:当在 Instapaper 阅读完一篇文章后,对文章进行归档,然后更新 GitHub Issues 来 close 对应的 Issue,这样才会利用到 ZenHub 的统计视图功能。

所以需要再创建一个 IFTTT Applet 来完成对应的操作,但IFTTT 没有对 close issue 的 Then 操作,需要利用到 IFTTT 的 Webhook 功能,即将 close issue 的方法由我们自己来写,这时候我们又需要利用「Serverless」了。

close issue

我也来打造一个个人阅读追踪系统_第14张图片

Issue close 函数:

server.route({
        method: 'GET',
        path: '/******',
        handler: function (request, reply) {
            const GITHUB_ACCESS_TOKEN = '********'

            const words = request.query.title
            // console.log('get title' + title)

            // const words = pub.ascii(title);

            console.log('get words ' + words)

            const titleQuery = new AV.Query(Read)
            titleQuery.contains('title', words);

            const bodyQuery = new AV.Query(Read)
            bodyQuery.contains('body', words);

            const wordsQuery = AV.Query.or(titleQuery, bodyQuery);

            wordsQuery.descending('createdAt');
            wordsQuery.limit(1);
            wordsQuery.find().then(function (results) {
                console.log('get results' + JSON.stringify(results))
                if (results.length === 1) {
                    let url = results[0].get('url')
                    console.log(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! ${url}`)
                    })
                        .catch(err => console.error(err))
                }
                reply({ message: 'issue closed!' })
            }, function (error) {
                reply({ message: 'error' })
            });
        }
    });

函数即功能,功能即服务

总结

有了这一整套「稍后阅读」流程,和阅读后归档更新 GitHub 和 ZenHub Issue 状态来追踪阅读。阅读这件事就形成闭环了,我们就可以知道了每天甚至每周一共读了多少文章,有多少文章「死在 Instapaper」中,每篇文章的阅读处理时间大概多长时间等等。

我也来打造一个个人阅读追踪系统_第15张图片

至于其他统计,和统计之后,如何来提升我们的阅读和如何对文章进行分类等操作,有待于继续完善。

最后附上一个对于喜欢的文章我们如何进行汇总呢,这里我还是使用 IFTTT 的 Applet,当你对某一篇文章喜欢时,只要触碰「❤️」按钮,即可将文章汇总到印象笔记中,看图不说话:

我也来打造一个个人阅读追踪系统_第16张图片

最后在印象笔记中:

我也来打造一个个人阅读追踪系统_第17张图片

这个也将结合到我之前的文章:《我是这么制作「coding01 日报」的》我是这么制作「coding01 日报」的 中,不断完善我制作日报的流程,并系统化。

「完」


coding01 期待您继续关注

我也来打造一个个人阅读追踪系统_第18张图片
qrcode

也很感谢您能看到这了

我也来打造一个个人阅读追踪系统_第19张图片
qrcode

你可能感兴趣的:(我也来打造一个个人阅读追踪系统)