RSSHub/lib/v2/theinitium/full.js

170 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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是followtype是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里面是digestsv2新增的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 += '<p>「' + data.lead + '」</p>';
}
if (data.byline.length) {
content += '<p>' + data.byline + '</p>';
}
if (data.content) {
content += data.content.replace('<figure class="advertisement"/><br/>', '').replace(/<br><br>-----------<br>.*$/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('<meta itemprop="isAccessibleForFree" content="false">', '');
}
}
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,
};
};