feat(apnews): Mobile API (#18819)

* feat(apnews): Mobile API

* .

* 更新 mobile-api.ts

* 更新 mobile-api.ts

* .
This commit is contained in:
Andvari
2025-04-12 03:17:12 +08:00
committed by GitHub
parent b0181eeea4
commit d8efe7b4c3
3 changed files with 95 additions and 79 deletions
-77
View File
@@ -1,77 +0,0 @@
import { Route, ViewType } from '@/types';
import { fetchArticle } from './utils';
import ofetch from '@/utils/ofetch';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/api/:tags?',
categories: ['traditional-media', 'popular'],
example: '/apnews/api/apf-topnews',
view: ViewType.Articles,
parameters: {
tags: {
description: 'Getting a list of articles from a public API based on tags.',
options: [
{ value: 'apf-topnews', label: 'Top News' },
{ value: 'apf-sports', label: 'Sports' },
{ value: 'apf-politics', label: 'Politics' },
{ value: 'apf-entertainment', label: 'Entertainment' },
{ value: 'apf-usnews', label: 'US News' },
{ value: 'apf-oddities', label: 'Oddities' },
{ value: 'apf-Travel', label: 'Travel' },
{ value: 'apf-technology', label: 'Technology' },
{ value: 'apf-lifestyle', label: 'Lifestyle' },
{ value: 'apf-business', label: 'Business' },
{ value: 'apf-Health', label: 'Health' },
{ value: 'apf-science', label: 'Science' },
{ value: 'apf-intlnews', label: 'International News' },
],
default: 'apf-topnews',
},
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['apnews.com/'],
},
],
name: 'News',
maintainers: ['dzx-dzx'],
handler,
};
async function handler(ctx) {
const { tags = 'apf-topnews' } = ctx.req.param();
const apiRootUrl = 'https://afs-prod.appspot.com/api/v2/feed/tag';
const url = `${apiRootUrl}?tags=${tags}`;
const res = await ofetch(url);
const list = res.cards
.map((e) => ({
title: e.contents[0]?.headline,
link: e.contents[0]?.localLinkUrl,
pubDate: timezone(parseDate(e.publishedDate), 0),
category: e.tagObjs.map((tag) => tag.name),
updated: timezone(parseDate(e.contents[0]?.updated), 0),
description: e.contents[0]?.storyHTML,
author: e.contents[0]?.reporters.map((author) => ({ name: author.displayName })),
}))
.sort((a, b) => b.pubDate - a.pubDate)
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20);
const items = ctx.req.query('fulltext') === 'true' ? await Promise.all(list.map((item) => fetchArticle(item))) : list;
return {
title: `${res.tagObjs[0].name} - AP News`,
item: items,
link: 'https://apnews.com',
};
}
+93
View File
@@ -0,0 +1,93 @@
import { Route, ViewType } from '@/types';
import { asyncPoolAll, fetchArticle } from './utils';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/mobile/:path{.+}?',
categories: ['traditional-media'],
example: '/apnews/mobile/ap-top-news',
view: ViewType.Articles,
parameters: {
path: {
description: 'Corresponding path from AP News website',
default: 'ap-top-news',
},
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['apnews.com/'],
},
],
name: 'News (from mobile client API)',
maintainers: ['dzx-dzx'],
handler,
};
async function handler(ctx) {
const path = ctx.req.param('path') ? `/${ctx.req.param('path')}` : '/hub/ap-top-news';
const apiRootUrl = 'https://apnews.com/graphql/delivery/ap/v1';
const res = await ofetch(apiRootUrl, {
query: {
operationName: 'ContentPageQuery',
variables: { path },
extensions: { persistedQuery: { version: 1, sha256Hash: '3bc305abbf62e9e632403a74cc86dc1cba51156d2313f09b3779efec51fc3acb' } },
},
});
const screen = res.data.Screen;
const list = [...screen.main.filter((e) => e.__typename === 'ColumnContainer').flatMap((_) => _.columns), ...screen.main.filter((e) => e.__typename !== 'ColumnContainer')]
.filter((e) => e.__typename !== 'GoogleDfPAdModule')
.flatMap((e) => {
switch (e.__typename) {
case 'PageListModule':
return e.items;
case 'VideoPlaylistModule':
return e.playlist;
default:
return;
}
})
.filter(Boolean)
.map((e) => {
if (e.__typename === 'PagePromo') {
return {
title: e.title,
link: e.url,
pubDate: parseDate(e.publishDateStamp),
category: e.category,
description: e.description,
guid: e.id,
};
} else if (e.__typename === 'VideoPlaylistItem') {
return {
title: e.title,
link: e.url,
description: e.description,
guid: e.contentId,
};
} else {
return;
}
})
.filter(Boolean)
.sort((a, b) => b.pubDate - a.pubDate)
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20);
const items = ctx.req.query('fulltext') === 'true' ? await asyncPoolAll(10, list, (item) => fetchArticle(item)) : list;
return {
title: screen.category ?? screen.title,
item: items,
link: 'https://apnews.com',
};
}
+2 -2
View File
@@ -38,6 +38,7 @@ export function fetchArticle(item) {
$('div.Enhancement').remove();
const section = $("meta[property='article:section']").attr('content');
return {
...item,
title: ldjson.headline,
pubDate: parseDate(ldjson.datePublished),
updated: parseDate(ldjson.dateModified),
@@ -45,7 +46,6 @@ export function fetchArticle(item) {
category: [...(section ? [section] : []), ...(ldjson.keywords ?? [])],
guid: $("meta[name='brightspot.contentId']").attr('content'),
author: ldjson.author,
...item,
};
} else {
// Live
@@ -56,11 +56,11 @@ export function fetchArticle(item) {
const pubDate = url.hash ? parseDate(Number.parseInt($(url.hash).parent().attr('data-posted-date-timestamp'), 10)) : parseDate(ldjson.coverageStartTime);
return {
...item,
category: ldjson.keywords,
pubDate,
description,
guid: $("meta[name='brightspot.contentId']").attr('content'),
...item,
};
}
});