From f660fc45648b0a4e089caf23ef541e5d362c7f16 Mon Sep 17 00:00:00 2001
From: COxDE <63153334+coxde@users.noreply.github.com>
Date: Tue, 27 Sep 2022 03:52:49 +0800
Subject: [PATCH] fix(route): Add favicon for Initium (#10904)
* fix(route): Add favicon for Initium
* refactor: migrate to v2
---
docs/new-media.md | 44 ++++++++
docs/traditional-media.md | 44 --------
lib/router.js | 4 +-
lib/routes/initium/full.js | 188 --------------------------------
lib/v2/theinitium/full.js | 169 ++++++++++++++++++++++++++++
lib/v2/theinitium/maintainer.js | 6 +
lib/v2/theinitium/radar.js | 25 +++++
lib/v2/theinitium/router.js | 3 +
8 files changed, 249 insertions(+), 234 deletions(-)
delete mode 100644 lib/routes/initium/full.js
create mode 100644 lib/v2/theinitium/full.js
create mode 100644 lib/v2/theinitium/maintainer.js
create mode 100644 lib/v2/theinitium/radar.js
create mode 100644 lib/v2/theinitium/router.js
diff --git a/docs/new-media.md b/docs/new-media.md
index 5ecbcfd81d..e41fd647c2 100644
--- a/docs/new-media.md
+++ b/docs/new-media.md
@@ -2022,6 +2022,50 @@ area 分区选项
+## 端传媒
+
+通过提取文章全文,以提供比官方源更佳的阅读体验。
+
+::: warning 注意
+
+付费内容全文可能需要登陆获取,详情见部署页面的配置模块。
+
+:::
+
+### 专题・栏目
+
+
+
+Type 栏目:
+
+| 最新 | 深度 | What’s New | 广场 | 科技 | 风物 | 特约 | ... |
+| ------ | ------- | ---------- | ----------------- | ---------- | ------- | ------- | --- |
+| latest | feature | news-brief | notes-and-letters | technology | culture | pick_up | ... |
+
+更多栏目名称可通过 及 获取。
+
+
+
+### 话题・标签
+
+
+
+### 作者
+
+
+
+### 个人订阅追踪动态
+
+
+
+::: warning 注意
+
+需要自建,详情见部署页面的配置模块。
+
+:::
+
+
+
## 多知网
### 首页
diff --git a/docs/traditional-media.md b/docs/traditional-media.md
index 2addd8ccda..98371dc0ef 100644
--- a/docs/traditional-media.md
+++ b/docs/traditional-media.md
@@ -881,50 +881,6 @@ IT・科学 tech_science
-## 端传媒
-
-通过提取文章全文,以提供比官方源更佳的阅读体验。
-
-::: warning 注意
-
-付费内容全文可能需要登陆获取,详情见部署页面的配置模块。
-
-:::
-
-### 专题・栏目
-
-
-
-Type 栏目:
-
-| 最新 | 深度 | What’s New | 广场 | 科技 | 风物 | 特约 | ... |
-| ------ | ------- | ---------- | ----------------- | ---------- | ------- | ------- | --- |
-| latest | feature | news-brief | notes-and-letters | technology | culture | pick_up | ... |
-
-更多栏目名称可通过 及 获取。
-
-
-
-### 话题・标签
-
-
-
-### 作者
-
-
-
-### 个人订阅追踪动态
-
-
-
-::: warning 注意
-
-需要自建,详情见部署页面的配置模块。
-
-:::
-
-
-
## 俄罗斯卫星通讯社
### 分类
diff --git a/lib/router.js b/lib/router.js
index 6fcf934329..9dec3517c0 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -2794,8 +2794,8 @@ router.get('/jijitang/publication', lazyloadRouteHandler('./routes/jijitang/publ
// router.get('/xwlb', lazyloadRouteHandler('./routes/xwlb/index'));
// 端传媒
-router.get('/initium/:type?/:language?', lazyloadRouteHandler('./routes/initium/full'));
-router.get('/theinitium/:model/:type?/:language?', lazyloadRouteHandler('./routes/initium/full'));
+// router.get('/initium/:type?/:language?', lazyloadRouteHandler('./routes/initium/full'));
+// router.get('/theinitium/:model/:type?/:language?', lazyloadRouteHandler('./routes/initium/full'));
// Grub Street
router.get('/grubstreet', lazyloadRouteHandler('./routes/grubstreet/index'));
diff --git a/lib/routes/initium/full.js b/lib/routes/initium/full.js
deleted file mode 100644
index 4a12ccd236..0000000000
--- a/lib/routes/initium/full.js
+++ /dev/null
@@ -1,188 +0,0 @@
-const got = require('@/utils/got');
-const config = require('@/config').value;
-const cheerio = require('cheerio');
-const resolve_url = require('url').resolve;
-const timezone = require('@/utils/timezone');
-
-module.exports = async (ctx) => {
- // model是channel/tag/etc.,而type是latest/feature/quest-academy这些一级栏目/标签/作者名的slug名。如果是追踪的话,那就是model是follow,type是articles。
- const model = ctx.params.model || 'channel';
- const type = ctx.params.type || 'latest';
- const language = ctx.params.language || 'zh-hans';
- let listurl;
- let listlink;
- switch (model) {
- case 'author':
- listurl = `https://api.theinitium.com/api/v2/author/?language=${language}&slug=${type}`;
- listlink = `https://theinitium.com/author/${type}/`;
- break;
- case 'follow':
- listurl = `https://api.theinitium.com/api/v2/user/follows/${type}/?language=${language}`;
- listlink = `https://theinitium.com/follow/`;
- break;
- case 'channel':
- listurl = `https://api.theinitium.com/api/v2/channel/articles/?language=${language}&slug=${type}`;
- listlink = `https://theinitium.com/channel/${type}/`;
- break;
- case 'tags':
- listurl = `https://api.theinitium.com/api/v2/tag/articles/?language=${language}&slug=${type}`;
- listlink = `https://theinitium.com/tags/${type}/`;
- break;
- }
-
- const key = {
- email: config.initium.username,
- password: config.initium.password,
- iapReceipt: config.initium.iap_receipt,
- };
- const body = JSON.stringify(key);
-
- let token;
- const cache = await ctx.cache.get('INITIUM_TOKEN');
- if (cache) {
- token = cache;
- } else if (config.initium.bearertoken) {
- token = config.initium.bearertoken;
- ctx.cache.set('INITIUM_TOKEN', config.initium.bearertoken);
- } else if (key.email === undefined) {
- token = 'Basic YW5vbnltb3VzOkdpQ2VMRWp4bnFCY1ZwbnA2Y0xzVXZKaWV2dlJRY0FYTHY=';
- } else {
- const login = await got({
- method: 'post',
- url: `https://api.theinitium.com/api/v2/auth/login/?language=${language}`,
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- Connection: 'keep-alive',
- Authorization: 'Basic YW5vbnltb3VzOkdpQ2VMRWp4bnFCY1ZwbnA2Y0xzVXZKaWV2dlJRY0FYTHY=',
- Origin: `https://theinitium.com/`,
- Referer: `https://theinitium.com/`,
- 'X-Client-Name': 'Web',
- },
- body,
- });
-
- /*
- const devices = login.data.access.devices;
-
- for (const key in devices) {
- const device = devices[key];
- if (device.status === 'logged_in' && !device.logout_at && device.platform === 'web') {
- token = 'Bearer ' + device.device_id;
- break;
- }
- }
- */
- token = 'Bearer ' + login.data.token;
- ctx.cache.set('INITIUM_TOKEN', token);
- }
-
- const headers = {
- Accept: '*/*',
- Connection: 'keep-alive',
- Authorization: token,
- 'X-IAP-Receipt': key.iapReceipt || '',
- };
-
- const response = await got({
- method: 'get',
- url: listurl,
- headers,
- });
-
- const name = response.data.name || (response.data[model] && response.data[model].name) || '追踪';
- // 从v1直升的channel和tags里面是digests,v2新增的author和follow出来都是results
- const articles = response.data.results ? response.data.results : response.data.digests;
- // 如果model=author,那就是avatar;否则都是cover,要么就没封面
- const image = response.data[model] && (response.data[model].cover || response.data[model].avatar);
-
- const getFullText = async (slug) => {
- let content = await ctx.cache.get(slug + language);
- if (content) {
- return content;
- }
-
- content = '';
-
- const response = await got({
- method: 'get',
- url: `https://api.theinitium.com/api/v2/article/detail/?language=${language}&slug=${slug}`,
- headers,
- });
-
- const data = response.data;
-
- if (data.lead.length) {
- content += '
「' + data.lead + '」
';
- }
- if (data.byline.length) {
- content += '' + data.byline + '
';
- }
- if (data.content) {
- content += data.content.replace('
', '').replace(/
-----------
.*$/g, '');
- } else if (data.type === 'html') {
- // 有时候编辑部会漏录入文章信息…………扶额。所以加这一个判断,如果确实是普通html文章,但又没有内容,说明是漏了,后面还要给guid手动加个标记,以便阅读器事后重抓。
- content += '内容为空,请稍后再来';
- } else if (data.type === 'web') {
- // 有时候文章并非普通html文章,而是带有互动内容等,表现为type为web,并且content里没有内容。我们也尽力抓点东西下来。
- // 或许可能还有其他未知情况,等碰到了再说吧。先这样留空也不碍事。
- const nonhtmlcontent = await got({
- method: 'get',
- url: data.web.url,
- });
- const webcontent = cheerio.load(nonhtmlcontent.body).html();
- content += webcontent;
- }
- if (data.paywall_enabled) {
- const google_bot_ua =
- 'Mozilla / 5.0(Linux; Android 6.0.1; Nexus 5X Build / MMB29P) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.92 Mobile Safari / 537.36(compatible; Googlebot / 2.1; +http://www.google.com/bot.html)';
- const accept_language = language + ';q=0.9';
- const pay_part = await got({
- method: 'get',
- url: `https://theinitium.com/article/${slug}`,
- headers: {
- 'user-agent': google_bot_ua,
- 'accept-language': accept_language,
- },
- });
- const $ = cheerio.load(pay_part.body);
- const pay_content = $('div.paywall').html();
- if (pay_content) {
- content += pay_content.replace('', '');
- }
- }
-
- ctx.cache.set(slug + language, content);
-
- return content;
- };
-
- const items = await Promise.all(
- articles
- .filter((a) => a.article)
- .slice(0, token === 'Basic YW5vbnltb3VzOkdpQ2VMRWp4bnFCY1ZwbnA2Y0xzVXZKaWV2dlJRY0FYTHY=' && key.iapReceipt === undefined ? 10 : articles.length)
- .map(async (item) => {
- item.article.date = timezone(item.article.date, +8);
- item.article.updated = timezone(item.article.updated, +8);
- const description = await getFullText(item.article.slug);
- return {
- title: item.article.headline,
- author: item.article.authors.length > 0 ? item.article.authors.map((x) => x.name).toString() : item.article.byline,
- category: item.article.channels.filter((x) => !x.homepage).map((x) => x.name),
- description,
- link: resolve_url('https://theinitium.com', item.article.url),
- pubDate: item.article.date,
- updated: item.article.updated,
- // 如果遇到编辑部漏录入情况,则给uuid做个手脚,以便阅读器到时重抓。
- guid: description.endsWith('内容为空,请稍后再来') ? item.article.uuid + '-I-am-empty' : item.article.uuid,
- };
- })
- );
-
- ctx.state.data = {
- title: `端传媒 - ${name}`,
- link: listlink,
- item: items,
- image,
- };
-};
diff --git a/lib/v2/theinitium/full.js b/lib/v2/theinitium/full.js
new file mode 100644
index 0000000000..742e3fb509
--- /dev/null
+++ b/lib/v2/theinitium/full.js
@@ -0,0 +1,169 @@
+const got = require('@/utils/got');
+const config = require('@/config').value;
+const cheerio = require('cheerio');
+const { parseDate } = require('@/utils/parse-date');
+
+const TOKEN = 'Basic YW5vbnltb3VzOkdpQ2VMRWp4bnFCY1ZwbnA2Y0xzVXZKaWV2dlJRY0FYTHY=';
+
+module.exports = async (ctx) => {
+ // model是channel/tag/etc.,而type是latest/feature/quest-academy这些一级栏目/标签/作者名的slug名。如果是追踪的话,那就是model是follow,type是articles。
+ const model = ctx.params.model ?? 'channel';
+ const type = ctx.params.type ?? 'latest';
+ const language = ctx.params.language ?? 'zh-hans';
+ let listUrl;
+ let listLink;
+ switch (model) {
+ case 'author':
+ listUrl = `https://api.theinitium.com/api/v2/author/?language=${language}&slug=${type}`;
+ listLink = `https://theinitium.com/author/${type}/`;
+ break;
+ case 'follow':
+ listUrl = `https://api.theinitium.com/api/v2/user/follows/${type}/?language=${language}`;
+ listLink = `https://theinitium.com/follow/`;
+ break;
+ case 'channel':
+ listUrl = `https://api.theinitium.com/api/v2/channel/articles/?language=${language}&slug=${type}`;
+ listLink = `https://theinitium.com/channel/${type}/`;
+ break;
+ case 'tags':
+ listUrl = `https://api.theinitium.com/api/v2/tag/articles/?language=${language}&slug=${type}`;
+ listLink = `https://theinitium.com/tags/${type}/`;
+ break;
+ }
+
+ const key = {
+ email: config.initium.username,
+ password: config.initium.password,
+ iapReceipt: config.initium.iap_receipt,
+ };
+ const body = JSON.stringify(key);
+
+ let token;
+ const cache = await ctx.cache.get('initium:token');
+ if (cache) {
+ token = cache;
+ } else if (config.initium.bearertoken) {
+ token = config.initium.bearertoken;
+ ctx.cache.set('initium:token', config.initium.bearertoken);
+ } else if (key.email === undefined) {
+ token = TOKEN;
+ } else {
+ const login = await got.post(`https://api.theinitium.com/api/v2/auth/login/?language=${language}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ Connection: 'keep-alive',
+ Authorization: TOKEN,
+ Origin: `https://theinitium.com/`,
+ Referer: `https://theinitium.com/`,
+ 'X-Client-Name': 'Web',
+ },
+ body,
+ });
+
+ /*
+ const devices = login.data.access.devices;
+
+ for (const key in devices) {
+ const device = devices[key];
+ if (device.status === 'logged_in' && !device.logout_at && device.platform === 'web') {
+ token = 'Bearer ' + device.device_id;
+ break;
+ }
+ }
+ */
+ token = 'Bearer ' + login.data.token;
+ ctx.cache.set('initium:token', token);
+ }
+
+ const headers = {
+ Accept: '*/*',
+ Connection: 'keep-alive',
+ Authorization: token,
+ 'X-IAP-Receipt': key.iapReceipt || '',
+ };
+
+ const response = await got(listUrl, {
+ headers,
+ });
+
+ const name = response.data.name || (response.data[model] && response.data[model].name) || '追踪';
+ // 从v1直升的channel和tags里面是digests,v2新增的author和follow出来都是results
+ const articles = response.data.results ? response.data.results : response.data.digests;
+ // 如果model=author,那就是avatar;否则都是cover,要么就没封面
+ const image = response.data[model] && (response.data[model].cover || response.data[model].avatar);
+
+ const getFullText = (slug) =>
+ ctx.cache.tryGet(`theinitium:${slug}:${language}`, async () => {
+ let content = '';
+ const { data } = await got(`https://api.theinitium.com/api/v2/article/detail/?language=${language}&slug=${slug}`, {
+ headers,
+ });
+
+ if (data.lead.length) {
+ content += '「' + data.lead + '」
';
+ }
+ if (data.byline.length) {
+ content += '' + data.byline + '
';
+ }
+ if (data.content) {
+ content += data.content.replace('
', '').replace(/
-----------
.*$/g, '');
+ } else if (data.type === 'html') {
+ // 有时候编辑部会漏录入文章信息…………扶额。所以加这一个判断,如果确实是普通html文章,但又没有内容,说明是漏了,后面还要给guid手动加个标记,以便阅读器事后重抓。
+ content += '内容为空,请稍后再来';
+ } else if (data.type === 'web') {
+ // 有时候文章并非普通html文章,而是带有互动内容等,表现为type为web,并且content里没有内容。我们也尽力抓点东西下来。
+ // 或许可能还有其他未知情况,等碰到了再说吧。先这样留空也不碍事。
+ const nonhtmlcontent = await got(data.web.url);
+ const webcontent = cheerio.load(nonhtmlcontent.body).html();
+ content += webcontent;
+ }
+ if (data.paywall_enabled) {
+ const google_bot_ua =
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.92 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
+ const accept_language = language + ';q=0.9';
+ const pay_part = await got(`https://theinitium.com/article/${slug}/`, {
+ headers: {
+ 'user-agent': google_bot_ua,
+ 'accept-language': accept_language,
+ },
+ });
+ const $ = cheerio.load(pay_part.body);
+ const pay_content = $('div.paywall').html();
+ if (pay_content) {
+ content += pay_content.replace('', '');
+ }
+ }
+ return content;
+ });
+
+ const items = await Promise.all(
+ articles
+ .filter((a) => a.article)
+ .slice(0, token === TOKEN && key.iapReceipt === undefined ? 25 : articles.length)
+ .map(async (item) => {
+ item.article.date = parseDate(item.article.date);
+ item.article.updated = parseDate(item.article.updated);
+ const description = await getFullText(item.article.slug);
+ return {
+ title: item.article.headline,
+ author: item.article.authors.length > 0 ? item.article.authors.map((x) => x.name).toString() : item.article.byline,
+ category: item.article.channels.filter((x) => !x.homepage).map((x) => x.name),
+ description,
+ link: new URL(item.article.url, 'https://theinitium.com').href,
+ pubDate: item.article.date,
+ updated: item.article.updated,
+ // 如果遇到编辑部漏录入情况,则给uuid做个手脚,以便阅读器到时重抓。
+ guid: description.endsWith('内容为空,请稍后再来') ? item.article.uuid + '-I-am-empty' : item.article.uuid,
+ };
+ })
+ );
+
+ ctx.state.data = {
+ title: `端传媒 - ${name}`,
+ link: listLink,
+ icon: 'https://theinitium.com/misc/about/logo192.png',
+ item: items,
+ image,
+ };
+};
diff --git a/lib/v2/theinitium/maintainer.js b/lib/v2/theinitium/maintainer.js
new file mode 100644
index 0000000000..6330ef9a0e
--- /dev/null
+++ b/lib/v2/theinitium/maintainer.js
@@ -0,0 +1,6 @@
+module.exports = {
+ '/author/:type/:language?': ['AgFlore'],
+ '/channel/:type?/:language?': ['prnake'],
+ '/follow/articles/:language?': ['AgFlore'],
+ '/tags/:type/:language?': ['AgFlore'],
+};
diff --git a/lib/v2/theinitium/radar.js b/lib/v2/theinitium/radar.js
new file mode 100644
index 0000000000..2b5af826e3
--- /dev/null
+++ b/lib/v2/theinitium/radar.js
@@ -0,0 +1,25 @@
+module.exports = {
+ 'theinitium.com': {
+ _name: '端传媒',
+ '.': [
+ {
+ title: '专题・栏目',
+ docs: 'https://docs.rsshub.app/new-media.html#duan-chuan-mei',
+ source: '/channel/:type',
+ target: '/theinitium/channel/:type',
+ },
+ {
+ title: '话题・标签',
+ docs: 'https://docs.rsshub.app/new-media.html#duan-chuan-mei',
+ source: '/tags/:type',
+ target: '/theinitions/tags/:type',
+ },
+ {
+ title: '作者',
+ docs: 'https://docs.rsshub.app/new-media.html#duan-chuan-mei',
+ source: '/author/:type',
+ target: '/theinitium/author/:type',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/theinitium/router.js b/lib/v2/theinitium/router.js
new file mode 100644
index 0000000000..21c8756c20
--- /dev/null
+++ b/lib/v2/theinitium/router.js
@@ -0,0 +1,3 @@
+module.exports = (router) => {
+ router.get('/:model?/:type?/:language?', require('./full'));
+};