使用Telegram订阅pixiv新图

  1. 1. 第一步:RSS采集
  2. 2. 第二步:数据处理
  3. 3. 第三步:消息发送
  4. 4. 第四步:部署

作为一名lsp,时刻与在pixiv上关注画师的更新保持同步不失为一种重要的生活习惯。

但总觉得每天手动检查新图既费时又费力,有没有什么方便一些的方法来推送这些喜欢的图片,让每一张图都有机会展现它的光彩呢?

联想到一种常用的资讯订阅方式:RSS,我正好在RSSHub的文档中翻到了对于pixiv关注画师的新图的订阅接口。

但光是RSS订阅并不方便,还需要一个单独的阅读器,不如直接将这些资讯推送至通讯工具上吧。
之前,我使用的基于IFTTT的解决方案,简单地将RSSHub生成的内容推送至Telegram频道。但随着IFTTT的使用策略更改,非付费用户只能创建三个小程序(Applet)之后,我也就放弃了这个方案。毕竟是开发者,被商业平台束缚了手脚可不好,不如自己来写一个功能类似的处理工具,不但能锻炼代码能力,还能拥有更高的可自定义能力,岂不是更棒。

于是,就去查询各种API接口,整理资料。思路很简单:将数据通过RSS采集,整理之后通过Bot的API接口发送至Telegram频道。

又因为我习惯使用Node.JS进行开发,所以这一次也一样,使用Node.JS作为开发与部署平台。

第一步:RSS采集

首先是RSS的采集工作。为了避免费时费力的手动分析XML文件,这里直接使用了一个现成的组件:rss-parser。这个组件能获取RSS资讯,并转化成一个Object方便后续的处理工作。

npm i rss-parser --save自不用说,使用完成之后的传参调用方式也是非常简洁明快:

1
2
3
4
5
6
7
rss.parseURL(item.url, (err, feed) => {
if (err) {
console.error(err);
} else {
// 对feed内容的后续处理工作
}
});

也可以使用官方样例中的async与await异步函数处理方式,但是我比较懒,所以也就直接这样传递参数了吧。

测试时可以将获取的feed信息输出,以RSShub生成的数据为例,我们不难发现其实内部是一个对象数组:

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
{
items: [
{
title: '标题',
link: '链接',
pubDate: '发布时间',
author: '作者',
content: '内容...',
contentSnippet: '内容片段',
id: 'ID号',
isoDate: '发布时间(ISO)'
},
{
title: '标题',
link: '链接',
pubDate: '发布时间',
author: '作者',
content: '内容...',
contentSnippet: '内容片段',
id: 'ID号',
isoDate: '发布时间(ISO)'
},
{
......
}
],
link: 'https://www.pixiv.net/bookmark_new_illust.php',
feedUrl: undefined,
title: 'Pixiv关注的新作品',
lastBuildDate: '最后生成时间'
}

因而可以直接使用相关的字段进行内容处理。

第二步:数据处理

由于RSSHub设计的初衷是为了方便阅读,因而生成的内容格式还是以方便阅读的格式,并使用了pixiv.cat的图片传递接口为主要组成部分的。不过好在获取的内容中包含了我们需要的所有信息,因而可以使用一个正则表达式来提取。

不难发现,对于那些单张图片的内容,RSSHub将其处理成形如https://pixiv.cat/*ArtworkID*.jpg的链接;pixiv的ArtworkID目前也仅为数字,因而可以使用\d来匹配;而对于那些多图的内容而言,ArtworkID后的-PicID也是我们关注的关键。因而,我们可以使用这样的正则表达式来处理:

1
const picIdReg = /https:\/\/pixiv\.cat\/(\d+)-?(\d+)?\.(jpg|png|gif)/gi;

可以配合这条正则表达式,直接生成内容数组:

1
const artworks = [...item.content.matchAll(picIdReg)];

对于单图的输入,我们能得到形如这样的输出:

1
> Array ["https://pixiv.cat/*ArtworkID*.jpg", "*ArtworkID*", undefined, "jpg"]

对于多图的输入,我们能得到形如这样的输出:

1
2
3
> Array ["https://pixiv.cat/*ArtworkID*-1.jpg", "*ArtworkID*", "1", "jpg"]
> Array ["https://pixiv.cat/*ArtworkID*-2.jpg", "*ArtworkID*", "2", "jpg"]
> Array ["https://pixiv.cat/*ArtworkID*-3.jpg", "*ArtworkID*", "3", "jpg"]

考虑到Telegram对于图片大小的限制,选择使用预览图发送、使用完整图下载的方案。

通过查看pixiv前端页面的源码,不难发现预览图的地址是由前缀、发布时间(UTC+9)、作品ID和一些固定组合搭配而成。发布时间可以由RSS item中的isoDate来手动计算获取,我们可以写一个整合表达式来完成预览图地址的装配工作:

1
2
3
4
5
6
const pubTime = new Date(item.isoDate);

const pubTime_JP = new Date(pubTime.getTime() + 9 * 3600 * 1000);
const pubTimeString = `${pubTime_JP.getUTCFullYear()}/${to2String(pubTime_JP.getUTCMonth() + 1)}/${to2String(pubTime_JP.getUTCDate())}/${to2String(pubTime_JP.getUTCHours())}/${to2String(pubTime_JP.getUTCMinutes())}/${to2String(pubTime_JP.getUTCSeconds())}`;

const previewAddr = `https://i.pximg.net/img-master/img/${pubTimeString}/${pic[1]}_p${typeof pic[2] === 'undefined' ? 0 : pic[2] - 1}_master1200.${pic[3]}`;

pic表示一个Array(如样例中的一行);为了整合处理单图的情况,针对pic[2]进行了额外的处理与判定:pixiv的预览图以0开始编码,单图也会保留此编码内容。

因而最终我们能组合出所需要的预览图片地址。至于下载地址,交给pixiv.cat去处理即可。

第三步:消息发送

之前IFTTT时候使用的是直接推送订阅内容、让Telegram自动获取的方式,因而导致了大图片(≥5MB)无法有效获取、图片缓存请求限制等的问题,因而此次我们加入请求缓冲队列和预览图片,尝试解决以上的问题。

本来打算使用Telegraf作为推送框架,但是想了想发现好像也就一个请求信息,所以就直接使用了got进行POST请求的发送。根据Telegram的Bot API文档,可以整理出如下的请求样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const apiBaseUrl = `https://api.telegram.org/bot${confData.bot.token}`;
const sendPic = (picItem) => {
got('sendPhoto', {
method: 'POST',
prefixUrl: apiBaseUrl,
json: {
chat_id: confData.bot.chat,
photo: picItem.preview,
caption: picItem.text,
reply_markup: {
inline_keyboard: [
[{
text: '🌏',
url: picItem.url
}, {
text: '⤵',
url: picItem.pic
}]
]
}
}
});

};

直接调用了Telegram的sendPhoto发送图片接口来发送请求数据,其中加入了消息下的内联小键盘inline_keyboard,为提供图片的原始链接按钮和直接下载按钮,以方便用户的操作。如有其他的好想法,也可制作成相关的功能组件。Bot API Token可以向@BotFather申请,Chat ID可以使用@频道名,而不必纠结于一长串的频道ID。

为了避免每次启动会将所有的图片加入队列等待发送,同时考虑到这个项目提供的所有数据都是按照时间进行排序的,因而维护一个timestamp,初始化为当前时间,每次将待发送的图片时间中记录最晚值,之后查询时仅发送更新的内容并更新时间戳标记即可。

第四步:部署

为了进一步为用户提供方便,依旧加入了祖传的js-yaml组件来读取yml配置文件。并综合以上内容,最终整合的版本,已经发布到了Github上的PhanDream仓库中;您可以使用诸如pm2等方式进行运行:

1
pm2 start bot.js --name phandream

当一切准备完成后,过一会就能发现频道中出现新的图片啦~