feat(route): add 钛媒体专栏 (#18532)
This commit is contained in:
parent
64f6d388a4
commit
7ee74e4e87
|
|
@ -0,0 +1,298 @@
|
|||
import { type Data, type Route, ViewType } from '@/types';
|
||||
|
||||
import { type Context } from 'hono';
|
||||
|
||||
import { baseUrl, apiBaseUrl, processItems } from './util';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { id } = ctx.req.param();
|
||||
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
|
||||
|
||||
const targetUrl: string = new URL(`column/${id}`, baseUrl).href;
|
||||
const listApiUrl: string = new URL('v1/categories/multi_content/list', apiBaseUrl).href;
|
||||
|
||||
const query = {
|
||||
subtype: 'post',
|
||||
category_guid: id,
|
||||
};
|
||||
|
||||
return await processItems(limit, query, listApiUrl, targetUrl);
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/column/:id',
|
||||
name: '最新',
|
||||
url: 'www.tmtpost.com',
|
||||
maintainers: ['nczitzk'],
|
||||
handler,
|
||||
example: '/tmtpost/column/6916385',
|
||||
parameters: {
|
||||
id: {
|
||||
description: '专栏 id,可在对应专栏页 URL 中找到',
|
||||
options: [
|
||||
{
|
||||
label: 'AGI',
|
||||
value: '6916385',
|
||||
},
|
||||
{
|
||||
label: '出海',
|
||||
value: '6998081',
|
||||
},
|
||||
{
|
||||
label: '创新场景',
|
||||
value: '3882035',
|
||||
},
|
||||
{
|
||||
label: '钛度号',
|
||||
value: '6100587',
|
||||
},
|
||||
{
|
||||
label: '深度',
|
||||
value: '3189960',
|
||||
},
|
||||
{
|
||||
label: '焦点',
|
||||
value: '6043895',
|
||||
},
|
||||
{
|
||||
label: '创投',
|
||||
value: '5994956',
|
||||
},
|
||||
{
|
||||
label: '汽车',
|
||||
value: '2573550',
|
||||
},
|
||||
{
|
||||
label: '3C',
|
||||
value: '3615534',
|
||||
},
|
||||
{
|
||||
label: '消费',
|
||||
value: '3882530',
|
||||
},
|
||||
{
|
||||
label: '大健康',
|
||||
value: '3882507',
|
||||
},
|
||||
{
|
||||
label: '金融',
|
||||
value: '3882486',
|
||||
},
|
||||
{
|
||||
label: '钛智宏观',
|
||||
value: '4277188',
|
||||
},
|
||||
{
|
||||
label: '产业研究',
|
||||
value: '5506730',
|
||||
},
|
||||
{
|
||||
label: '地产',
|
||||
value: '3882499',
|
||||
},
|
||||
{
|
||||
label: '大公司',
|
||||
value: '2446153',
|
||||
},
|
||||
{
|
||||
label: 'IPO',
|
||||
value: '6043750',
|
||||
},
|
||||
{
|
||||
label: '钛度图闻',
|
||||
value: '5750087',
|
||||
},
|
||||
{
|
||||
label: '城视',
|
||||
value: '6998636',
|
||||
},
|
||||
{
|
||||
label: '创业家',
|
||||
value: '4273329',
|
||||
},
|
||||
{
|
||||
label: '人文',
|
||||
value: '6252390',
|
||||
},
|
||||
{
|
||||
label: '新职业研究所',
|
||||
value: '5750104',
|
||||
},
|
||||
{
|
||||
label: '科普',
|
||||
value: '5714992',
|
||||
},
|
||||
{
|
||||
label: '文娱',
|
||||
value: '2446157',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `:::tip
|
||||
若订阅 [AGI](https://www.tmtpost.com/column/6916385),网址为 \`https://www.tmtpost.com/column/6916385\`,请截取 \`https://www.tmtpost.com/column\` 到末尾的部分 \`6916385\` 作为 \`id\` 参数填入,此时目标路由为 [\`/tmtpost/column/6916385\`](https://rsshub.app/tmtpost/column/6916385)。
|
||||
:::
|
||||
|
||||
<details>
|
||||
<summary>更多分类</summary>
|
||||
|
||||
| [AGI](https://www.tmtpost.com/column/6916385) | [出海](https://www.tmtpost.com/column/6998081) | [创新场景](https://www.tmtpost.com/column/3882035) | [钛度号](https://www.tmtpost.com/column/6100587) | [深度](https://www.tmtpost.com/column/3189960) |
|
||||
| ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
|
||||
| [6916385](https://rsshub.app/tmtpost/column/6916385) | [6998081](https://rsshub.app/tmtpost/column/6998081) | [3882035](https://rsshub.app/tmtpost/column/3882035) | [6100587](https://rsshub.app/tmtpost/column/6100587) | [3189960](https://rsshub.app/tmtpost/column/3189960) |
|
||||
|
||||
| [焦点](https://www.tmtpost.com/column/6043895) | [创投](https://www.tmtpost.com/column/5994956) | [汽车](https://www.tmtpost.com/column/2573550) | [3C](https://www.tmtpost.com/column/3615534) | [消费](https://www.tmtpost.com/column/3882530) |
|
||||
| ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
|
||||
| [6043895](https://rsshub.app/tmtpost/column/6043895) | [5994956](https://rsshub.app/tmtpost/column/5994956) | [2573550](https://rsshub.app/tmtpost/column/2573550) | [3615534](https://rsshub.app/tmtpost/column/3615534) | [3882530](https://rsshub.app/tmtpost/column/3882530) |
|
||||
|
||||
| [大健康](https://www.tmtpost.com/column/3882507) | [金融](https://www.tmtpost.com/column/3882486) | [钛智宏观](https://www.tmtpost.com/column/4277188) | [产业研究](https://www.tmtpost.com/column/5506730) | [地产](https://www.tmtpost.com/column/3882499) |
|
||||
| ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
|
||||
| [3882507](https://rsshub.app/tmtpost/column/3882507) | [3882486](https://rsshub.app/tmtpost/column/3882486) | [4277188](https://rsshub.app/tmtpost/column/4277188) | [5506730](https://rsshub.app/tmtpost/column/5506730) | [3882499](https://rsshub.app/tmtpost/column/3882499) |
|
||||
|
||||
| [大公司](https://www.tmtpost.com/column/2446153) | [IPO](https://www.tmtpost.com/column/6043750) | [钛度图闻](https://www.tmtpost.com/column/5750087) | [城视](https://www.tmtpost.com/column/6998636) | [创业家](https://www.tmtpost.com/column/4273329) |
|
||||
| ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
|
||||
| [2446153](https://rsshub.app/tmtpost/column/2446153) | [6043750](https://rsshub.app/tmtpost/column/6043750) | [5750087](https://rsshub.app/tmtpost/column/5750087) | [6998636](https://rsshub.app/tmtpost/column/6998636) | [4273329](https://rsshub.app/tmtpost/column/4273329) |
|
||||
|
||||
| [人文](https://www.tmtpost.com/column/6252390) | [新职业研究所](https://www.tmtpost.com/column/5750104) | [科普](https://www.tmtpost.com/column/5714992) | [文娱](https://www.tmtpost.com/column/2446157) |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------- | ---------------------------------------------------- |
|
||||
| [6252390](https://rsshub.app/tmtpost/column/6252390) | [5750104](https://rsshub.app/tmtpost/column/5750104) | [5714992](https://rsshub.app/tmtpost/column/5714992) | [2446157](https://rsshub.app/tmtpost/column/2446157) |
|
||||
|
||||
</details>
|
||||
`,
|
||||
categories: ['new-media'],
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportRadar: true,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['www.tmtpost.com/column/:id'],
|
||||
target: '/column/:id',
|
||||
},
|
||||
{
|
||||
title: 'AGI',
|
||||
source: ['www.tmtpost.com/column/6916385'],
|
||||
target: '/column/6916385',
|
||||
},
|
||||
{
|
||||
title: '出海',
|
||||
source: ['www.tmtpost.com/column/6998081'],
|
||||
target: '/column/6998081',
|
||||
},
|
||||
{
|
||||
title: '创新场景',
|
||||
source: ['www.tmtpost.com/column/3882035'],
|
||||
target: '/column/3882035',
|
||||
},
|
||||
{
|
||||
title: '钛度号',
|
||||
source: ['www.tmtpost.com/column/6100587'],
|
||||
target: '/column/6100587',
|
||||
},
|
||||
{
|
||||
title: '深度',
|
||||
source: ['www.tmtpost.com/column/3189960'],
|
||||
target: '/column/3189960',
|
||||
},
|
||||
{
|
||||
title: '焦点',
|
||||
source: ['www.tmtpost.com/column/6043895'],
|
||||
target: '/column/6043895',
|
||||
},
|
||||
{
|
||||
title: '创投',
|
||||
source: ['www.tmtpost.com/column/5994956'],
|
||||
target: '/column/5994956',
|
||||
},
|
||||
{
|
||||
title: '汽车',
|
||||
source: ['www.tmtpost.com/column/2573550'],
|
||||
target: '/column/2573550',
|
||||
},
|
||||
{
|
||||
title: '3C',
|
||||
source: ['www.tmtpost.com/column/3615534'],
|
||||
target: '/column/3615534',
|
||||
},
|
||||
{
|
||||
title: '消费',
|
||||
source: ['www.tmtpost.com/column/3882530'],
|
||||
target: '/column/3882530',
|
||||
},
|
||||
{
|
||||
title: '大健康',
|
||||
source: ['www.tmtpost.com/column/3882507'],
|
||||
target: '/column/3882507',
|
||||
},
|
||||
{
|
||||
title: '金融',
|
||||
source: ['www.tmtpost.com/column/3882486'],
|
||||
target: '/column/3882486',
|
||||
},
|
||||
{
|
||||
title: '钛智宏观',
|
||||
source: ['www.tmtpost.com/column/4277188'],
|
||||
target: '/column/4277188',
|
||||
},
|
||||
{
|
||||
title: '产业研究',
|
||||
source: ['www.tmtpost.com/column/5506730'],
|
||||
target: '/column/5506730',
|
||||
},
|
||||
{
|
||||
title: '地产',
|
||||
source: ['www.tmtpost.com/column/3882499'],
|
||||
target: '/column/3882499',
|
||||
},
|
||||
{
|
||||
title: '大公司',
|
||||
source: ['www.tmtpost.com/column/2446153'],
|
||||
target: '/column/2446153',
|
||||
},
|
||||
{
|
||||
title: 'IPO',
|
||||
source: ['www.tmtpost.com/column/6043750'],
|
||||
target: '/column/6043750',
|
||||
},
|
||||
{
|
||||
title: '钛度图闻',
|
||||
source: ['www.tmtpost.com/column/5750087'],
|
||||
target: '/column/5750087',
|
||||
},
|
||||
{
|
||||
title: '城视',
|
||||
source: ['www.tmtpost.com/column/6998636'],
|
||||
target: '/column/6998636',
|
||||
},
|
||||
{
|
||||
title: '创业家',
|
||||
source: ['www.tmtpost.com/column/4273329'],
|
||||
target: '/column/4273329',
|
||||
},
|
||||
{
|
||||
title: '人文',
|
||||
source: ['www.tmtpost.com/column/6252390'],
|
||||
target: '/column/6252390',
|
||||
},
|
||||
{
|
||||
title: '新职业研究所',
|
||||
source: ['www.tmtpost.com/column/5750104'],
|
||||
target: '/column/5750104',
|
||||
},
|
||||
{
|
||||
title: '科普',
|
||||
source: ['www.tmtpost.com/column/5714992'],
|
||||
target: '/column/5714992',
|
||||
},
|
||||
{
|
||||
title: '文娱',
|
||||
source: ['www.tmtpost.com/column/2446157'],
|
||||
target: '/column/2446157',
|
||||
},
|
||||
],
|
||||
view: ViewType.Articles,
|
||||
};
|
||||
|
|
@ -1,211 +1,16 @@
|
|||
import { type Data, type DataItem, type Route, ViewType } from '@/types';
|
||||
import { type Data, type Route, ViewType } from '@/types';
|
||||
|
||||
import { art } from '@/utils/render';
|
||||
import cache from '@/utils/cache';
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
import { type CheerioAPI, load } from 'cheerio';
|
||||
import { type Context } from 'hono';
|
||||
import path from 'node:path';
|
||||
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
import { baseUrl, apiBaseUrl, processItems } from './util';
|
||||
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '1', 10);
|
||||
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
|
||||
|
||||
const baseUrl: string = 'https://www.tmtpost.com';
|
||||
const apiBaseUrl: string = 'https://api.tmtpost.com';
|
||||
const targetUrl: string = new URL('new', baseUrl).href;
|
||||
const listApiUrl: string = new URL('v1/lists/new', apiBaseUrl).href;
|
||||
const postApiUrl: string = new URL('v1/posts/', apiBaseUrl).href;
|
||||
|
||||
const headers = {
|
||||
'app-version': 'web1.0',
|
||||
};
|
||||
|
||||
const response = await ofetch(listApiUrl, {
|
||||
query: {
|
||||
limit,
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
const targetResponse = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(targetResponse);
|
||||
const language = $('html').attr('lang') ?? 'zh-CN';
|
||||
|
||||
let items: DataItem[] = [];
|
||||
|
||||
items = response.data.slice(0, limit).map((item): DataItem => {
|
||||
const title: string = item.title;
|
||||
const description: string = art(path.join(__dirname, 'templates/description.art'), {
|
||||
intro: item.summary,
|
||||
});
|
||||
const pubDate: number | string = item.time_published;
|
||||
const linkUrl: string | undefined = item.share_link;
|
||||
const guid: string = item.guid;
|
||||
const image: string | undefined = item.thumb_image?.original?.url;
|
||||
const updated: number | string = item.updated ?? pubDate;
|
||||
|
||||
const processedItem: DataItem = {
|
||||
title,
|
||||
description,
|
||||
pubDate: pubDate ? parseDate(pubDate, 'X') : undefined,
|
||||
link: linkUrl,
|
||||
guid,
|
||||
id: guid,
|
||||
content: {
|
||||
html: description,
|
||||
text: item.content ?? description,
|
||||
},
|
||||
image,
|
||||
banner: image,
|
||||
updated: updated ? parseDate(updated, 'X') : undefined,
|
||||
language,
|
||||
};
|
||||
|
||||
return processedItem;
|
||||
});
|
||||
|
||||
items = (
|
||||
await Promise.all(
|
||||
items.map((item) => {
|
||||
if (!item.link) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return cache.tryGet(item.link, async (): Promise<DataItem> => {
|
||||
const apiUrl: string = new URL(item.guid as string, postApiUrl).href;
|
||||
|
||||
const detailResponse = await ofetch(apiUrl, {
|
||||
query: {
|
||||
fields: ['authors', 'tags', 'featured_image', 'categories', 'stock_list', 'big_plate', 'concept_plate', 'plate', 'plate_list', 'share_link'].join(';'),
|
||||
},
|
||||
headers,
|
||||
});
|
||||
const data = detailResponse.data;
|
||||
|
||||
if (!data) {
|
||||
return item;
|
||||
}
|
||||
|
||||
const title: string = data.title;
|
||||
const description: string = art(path.join(__dirname, 'templates/description.art'), {
|
||||
intro: data.summary,
|
||||
description: data.main,
|
||||
});
|
||||
const pubDate: number | string = data.time_published;
|
||||
const linkUrl: string | undefined = data.share_link;
|
||||
const categories: string[] = [
|
||||
...new Set(
|
||||
(
|
||||
[...(data.categories ?? []), ...(data.stock_list ?? []), ...(data.big_plate ?? []), ...(data.concept_plate ?? []), ...(data.plate ?? []), ...(data.plate_list ?? []), ...(data.tags ?? [])].map(
|
||||
(c) => c.title ?? c.name ?? c.tag
|
||||
) as string[]
|
||||
).filter(Boolean)
|
||||
),
|
||||
];
|
||||
const authors: DataItem['author'] = data.authors?.map((author) => ({
|
||||
name: author.username,
|
||||
url: new URL(`user/${author.guid}`, baseUrl).href,
|
||||
avatar: author.avatar,
|
||||
}));
|
||||
const guid: string = `tmtpost-${data.post_guid}`;
|
||||
const image: string | undefined = data.images?.[0]?.url;
|
||||
const updated: number | string = data.time_updated;
|
||||
|
||||
let processedItem: DataItem = {
|
||||
title,
|
||||
description,
|
||||
pubDate: pubDate ? parseDate(pubDate, 'X') : undefined,
|
||||
link: linkUrl ?? item.link,
|
||||
category: categories,
|
||||
author: authors,
|
||||
guid,
|
||||
id: guid,
|
||||
content: {
|
||||
html: description,
|
||||
text: description,
|
||||
},
|
||||
image,
|
||||
banner: image,
|
||||
updated: updated ? parseDate(updated, 'X') : undefined,
|
||||
language,
|
||||
};
|
||||
|
||||
const enclosureUrl: string | undefined = data.audio;
|
||||
|
||||
if (enclosureUrl) {
|
||||
const enclosureType: string = `audio/${enclosureUrl.split(/\./).pop()}`;
|
||||
const itunesDuration: string | number | undefined = data.duration;
|
||||
|
||||
processedItem = {
|
||||
...processedItem,
|
||||
enclosure_url: enclosureUrl,
|
||||
enclosure_type: enclosureType,
|
||||
enclosure_title: title,
|
||||
enclosure_length: undefined,
|
||||
itunes_duration: itunesDuration,
|
||||
itunes_item_image: image,
|
||||
};
|
||||
}
|
||||
|
||||
const medias: Record<string, Record<string, string>> = {};
|
||||
|
||||
if (data.full_size_images ?? data.images) {
|
||||
const images = data.full_size_images ?? data.images;
|
||||
for (const media of images) {
|
||||
const url: string | undefined = media.url ?? media;
|
||||
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const medium: string = 'image';
|
||||
const count: number = Object.values(medias).filter((m) => m.medium === medium).length + 1;
|
||||
const key: string = `${medium}${count}`;
|
||||
|
||||
medias[key] = {
|
||||
url,
|
||||
medium,
|
||||
title,
|
||||
thumbnail: media.thumbnail ?? url,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
processedItem = {
|
||||
...processedItem,
|
||||
media: medias,
|
||||
};
|
||||
|
||||
return {
|
||||
...item,
|
||||
...processedItem,
|
||||
};
|
||||
});
|
||||
})
|
||||
)
|
||||
).filter((_): _ is DataItem => true);
|
||||
|
||||
const title: string = $('title').text();
|
||||
const author: string | undefined = title.split(/-/).pop();
|
||||
|
||||
return {
|
||||
title,
|
||||
description: $('meta[property="og:description"]').attr('content'),
|
||||
link: targetUrl,
|
||||
item: items,
|
||||
allowEmpty: true,
|
||||
image: $('meta[property="og:image"]').attr('content'),
|
||||
author: title.split(/-/).pop(),
|
||||
language,
|
||||
itunes_author: author,
|
||||
itunes_category: 'Technology',
|
||||
id: targetUrl,
|
||||
};
|
||||
return await processItems(limit, {}, listApiUrl, targetUrl);
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
import { type Data, type DataItem } from '@/types';
|
||||
|
||||
import { art } from '@/utils/render';
|
||||
import cache from '@/utils/cache';
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
import { type CheerioAPI, load } from 'cheerio';
|
||||
import path from 'node:path';
|
||||
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
|
||||
const baseUrl: string = 'https://www.tmtpost.com';
|
||||
const apiBaseUrl: string = 'https://api.tmtpost.com';
|
||||
const postApiUrl: string = new URL('v1/posts/', apiBaseUrl).href;
|
||||
|
||||
const headers = {
|
||||
'app-version': 'web1.0',
|
||||
};
|
||||
|
||||
const processItems = async (limit: number, query: Record<string, any>, apiUrl: string, targetUrl: string): Promise<Data> => {
|
||||
const response = await ofetch(apiUrl, {
|
||||
query: {
|
||||
limit,
|
||||
...query,
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
const targetResponse = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(targetResponse);
|
||||
const language = $('html').attr('lang') ?? 'zh-CN';
|
||||
|
||||
let items: DataItem[] = [];
|
||||
|
||||
items = response.data.slice(0, limit).map((item): DataItem => {
|
||||
const title: string = item.title;
|
||||
const description: string = art(path.join(__dirname, 'templates/description.art'), {
|
||||
intro: item.summary,
|
||||
});
|
||||
const pubDate: number | string = item.time_published;
|
||||
const linkUrl: string | undefined = item.share_link;
|
||||
const guid: string = item.guid;
|
||||
const image: string | undefined = item.thumb_image?.original?.url;
|
||||
const updated: number | string = item.updated ?? pubDate;
|
||||
|
||||
const processedItem: DataItem = {
|
||||
title,
|
||||
description,
|
||||
pubDate: pubDate ? parseDate(pubDate, 'X') : undefined,
|
||||
link: linkUrl,
|
||||
guid,
|
||||
id: guid,
|
||||
content: {
|
||||
html: description,
|
||||
text: item.content ?? description,
|
||||
},
|
||||
image,
|
||||
banner: image,
|
||||
updated: updated ? parseDate(updated, 'X') : undefined,
|
||||
language,
|
||||
};
|
||||
|
||||
return processedItem;
|
||||
});
|
||||
|
||||
items = (
|
||||
await Promise.all(
|
||||
items.map((item) => {
|
||||
if (!item.link) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return cache.tryGet(item.link, async (): Promise<DataItem> => {
|
||||
const detailApiUrl: string = new URL(item.guid as string, postApiUrl).href;
|
||||
|
||||
const detailResponse = await ofetch(detailApiUrl, {
|
||||
query: {
|
||||
fields: ['authors', 'tags', 'featured_image', 'categories', 'stock_list', 'big_plate', 'concept_plate', 'plate', 'plate_list', 'share_link'].join(';'),
|
||||
},
|
||||
headers,
|
||||
});
|
||||
const data = detailResponse.data;
|
||||
|
||||
if (!data) {
|
||||
return item;
|
||||
}
|
||||
|
||||
const title: string = data.title;
|
||||
const description: string = art(path.join(__dirname, 'templates/description.art'), {
|
||||
intro: data.summary,
|
||||
description: data.main,
|
||||
});
|
||||
const pubDate: number | string = data.time_published;
|
||||
const linkUrl: string | undefined = data.share_link;
|
||||
const categories: string[] = [
|
||||
...new Set(
|
||||
(
|
||||
[...(data.categories ?? []), ...(data.stock_list ?? []), ...(data.big_plate ?? []), ...(data.concept_plate ?? []), ...(data.plate ?? []), ...(data.plate_list ?? []), ...(data.tags ?? [])].map(
|
||||
(c) => c.title ?? c.name ?? c.tag
|
||||
) as string[]
|
||||
).filter(Boolean)
|
||||
),
|
||||
];
|
||||
const authors: DataItem['author'] = data.authors?.map((author) => ({
|
||||
name: author.username,
|
||||
url: new URL(`user/${author.guid}`, baseUrl).href,
|
||||
avatar: author.avatar,
|
||||
}));
|
||||
const guid: string = `tmtpost-${data.post_guid}`;
|
||||
const image: string | undefined = data.images?.[0]?.url;
|
||||
const updated: number | string = data.time_updated;
|
||||
|
||||
let processedItem: DataItem = {
|
||||
title,
|
||||
description,
|
||||
pubDate: pubDate ? parseDate(pubDate, 'X') : undefined,
|
||||
link: linkUrl ?? item.link,
|
||||
category: categories,
|
||||
author: authors,
|
||||
guid,
|
||||
id: guid,
|
||||
content: {
|
||||
html: description,
|
||||
text: description,
|
||||
},
|
||||
image,
|
||||
banner: image,
|
||||
updated: updated ? parseDate(updated, 'X') : undefined,
|
||||
language,
|
||||
};
|
||||
|
||||
const enclosureUrl: string | undefined = data.audio;
|
||||
|
||||
if (enclosureUrl) {
|
||||
const enclosureType: string = `audio/${enclosureUrl.split(/\./).pop()}`;
|
||||
const itunesDuration: string | number | undefined = data.duration;
|
||||
|
||||
processedItem = {
|
||||
...processedItem,
|
||||
enclosure_url: enclosureUrl,
|
||||
enclosure_type: enclosureType,
|
||||
enclosure_title: title,
|
||||
enclosure_length: undefined,
|
||||
itunes_duration: itunesDuration,
|
||||
itunes_item_image: image,
|
||||
};
|
||||
}
|
||||
|
||||
const medias: Record<string, Record<string, string>> = {};
|
||||
|
||||
if (data.full_size_images ?? data.images) {
|
||||
const images = data.full_size_images ?? data.images;
|
||||
for (const media of images) {
|
||||
const url: string | undefined = media.url ?? media;
|
||||
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const medium: string = 'image';
|
||||
const count: number = Object.values(medias).filter((m) => m.medium === medium).length + 1;
|
||||
const key: string = `${medium}${count}`;
|
||||
|
||||
medias[key] = {
|
||||
url,
|
||||
medium,
|
||||
title,
|
||||
thumbnail: media.thumbnail ?? url,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
processedItem = {
|
||||
...processedItem,
|
||||
media: medias,
|
||||
};
|
||||
|
||||
return {
|
||||
...item,
|
||||
...processedItem,
|
||||
};
|
||||
});
|
||||
})
|
||||
)
|
||||
).filter((_): _ is DataItem => true);
|
||||
|
||||
const title: string = $('title').text();
|
||||
const author: string | undefined = title.split(/-/).pop();
|
||||
|
||||
return {
|
||||
title,
|
||||
description: $('meta[property="og:description"]').attr('content'),
|
||||
link: targetUrl,
|
||||
item: items,
|
||||
allowEmpty: true,
|
||||
image: $('meta[property="og:image"]').attr('content'),
|
||||
author: title.split(/-/).pop(),
|
||||
language,
|
||||
itunes_author: author,
|
||||
itunes_category: 'Technology',
|
||||
id: targetUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export { baseUrl, apiBaseUrl, processItems };
|
||||
Loading…
Reference in New Issue