mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-24 23:12:34 +08:00
feat(route/hupu): news route for teams
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// https://games.mobileapi.hupu.com/3/8.2.30/basketballapi/teamStandingList?offline=json&competitionLeagueType=nba&competitionType=nba&season=2025-2026&competitionStageType=REGULAR&client=EB5576BA-C415-43F7-850F-11B13E7991EB
|
||||
import { result } from './response/teamStandingList.json';
|
||||
|
||||
const NBA_TEAMS = [...result.rankTypeListMap.E, ...result.rankTypeListMap.W];
|
||||
|
||||
const NBA_TEAM_NAMES: Record<string, string> = {
|
||||
活塞: 'Pistons',
|
||||
尼克斯: 'Knicks',
|
||||
猛龙: 'Raptors',
|
||||
热火: 'Heat',
|
||||
凯尔特人: 'Celtics',
|
||||
魔术: 'Magic',
|
||||
'76人': '76ers',
|
||||
骑士: 'Cavaliers',
|
||||
老鹰: 'Hawks',
|
||||
雄鹿: 'Bucks',
|
||||
公牛: 'Bulls',
|
||||
黄蜂: 'Hornets',
|
||||
篮网: 'Nets',
|
||||
步行者: 'Pacers',
|
||||
奇才: 'Wizards',
|
||||
雷霆: 'Thunder',
|
||||
湖人: 'Lakers',
|
||||
火箭: 'Rockets',
|
||||
马刺: 'Spurs',
|
||||
掘金: 'Nuggets',
|
||||
森林狼: 'Timberwolves',
|
||||
太阳: 'Suns',
|
||||
勇士: 'Warriors',
|
||||
灰熊: 'Grizzlies',
|
||||
开拓者: 'Trail Blazers',
|
||||
爵士: 'Jazz',
|
||||
独行侠: 'Mavericks',
|
||||
快船: 'Clippers',
|
||||
国王: 'Kings',
|
||||
鹈鹕: 'Pelicans',
|
||||
};
|
||||
|
||||
export const NBA_TEAMS_ID_MAP = NBA_TEAMS.reduce(
|
||||
(map, team) => {
|
||||
const englishName = NBA_TEAM_NAMES[team.teamName];
|
||||
map[englishName.toLowerCase()] = team;
|
||||
return map;
|
||||
},
|
||||
{} as Record<string, (typeof NBA_TEAMS)[number] | undefined>
|
||||
);
|
||||
@@ -1,13 +1,11 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import type { Data, DataItem, Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate, parseRelativeDate } from '@/utils/parse-date';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
|
||||
import type { HomePostItem, HupuApiResponse, NewsDataItem } from './types';
|
||||
import { isHomePostItem } from './types';
|
||||
import { getEntryDetails } from './utils';
|
||||
|
||||
const categories = {
|
||||
nba: {
|
||||
@@ -112,85 +110,7 @@ export const route: Route = {
|
||||
} satisfies DataItem)
|
||||
);
|
||||
|
||||
items = await Promise.all(
|
||||
items
|
||||
.filter((item) => item.link && !/subject/.test(item.link))
|
||||
.map((item) =>
|
||||
cache.tryGet(item.link!, async () => {
|
||||
try {
|
||||
const detailResponse = await got({
|
||||
method: 'get',
|
||||
url: item.link,
|
||||
});
|
||||
|
||||
const content = load(detailResponse.data);
|
||||
|
||||
const author = content('.bbs-user-info-name, .bbs-user-wrapper-content-name-span').text();
|
||||
const pubDateString = content('.second-line-user-info span:not([class])').text();
|
||||
// Possible formats: 10:21, 45分钟前, 09-15 19:57
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentDate = new Date();
|
||||
const monthDayTimePattern = /^(\d{2})-(\d{2}) (\d{2}):(\d{2})$/;
|
||||
const timeOnlyPattern = /^(\d{1,2}):(\d{2})$/;
|
||||
let processedDateString = pubDateString;
|
||||
|
||||
if (monthDayTimePattern.test(pubDateString)) {
|
||||
processedDateString = `${currentYear}-${pubDateString}`;
|
||||
} else if (timeOnlyPattern.test(pubDateString)) {
|
||||
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(currentDate.getDate()).padStart(2, '0');
|
||||
processedDateString = `${currentYear}-${month}-${day} ${pubDateString}`;
|
||||
}
|
||||
|
||||
const pubDate = [item.pubDate, timezone(parseDate(processedDateString), +8), timezone(parseRelativeDate(pubDateString), +8)].find((d) => d instanceof Date && !Number.isNaN(d.getTime()));
|
||||
const categories = content('.basketballTobbs_tag > a, .tag-player-team')
|
||||
.toArray()
|
||||
.map((c) => content(c).text())
|
||||
.filter(Boolean);
|
||||
|
||||
content('.basketballTobbs_tag').remove();
|
||||
content('.hupu-img').each(function () {
|
||||
const imgSrc = content(this).attr('data-gif') || content(this).attr('data-origin') || content(this).attr('src');
|
||||
content(this).parent().html(`<img src="${imgSrc}">`);
|
||||
});
|
||||
|
||||
// 分别获取内容元素
|
||||
const descriptionParts: string[] = [];
|
||||
|
||||
// 获取主要内容
|
||||
const mainContent = content('#bbs-thread-content, .bbs-content-font').html();
|
||||
if (mainContent) {
|
||||
descriptionParts.push(mainContent);
|
||||
}
|
||||
|
||||
// 单独处理视频部分
|
||||
const videoWrapper = content('.header-video-wrapper');
|
||||
if (videoWrapper.length > 0) {
|
||||
const videoElement = videoWrapper.find('video');
|
||||
if (videoElement.length > 0) {
|
||||
const videoHtml = videoElement.prop('outerHTML');
|
||||
if (videoHtml) {
|
||||
descriptionParts.push(videoHtml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const description = descriptionParts.length > 0 ? descriptionParts.join('') : undefined;
|
||||
|
||||
return {
|
||||
...item,
|
||||
author,
|
||||
category: categories.length > 0 ? categories : item.category,
|
||||
description,
|
||||
pubDate,
|
||||
};
|
||||
} catch {
|
||||
// no-empty
|
||||
return item;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
items = await Promise.all(items.filter((item) => item.link && !/subject/.test(item.link)).map((item) => getEntryDetails(item)));
|
||||
|
||||
return {
|
||||
title: `虎扑 - ${categories[category].title}`,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Data, DataItem, Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
|
||||
import { NBA_TEAMS_ID_MAP } from './consts';
|
||||
import { getEntryDetails } from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: ['/news/:team'],
|
||||
name: '队伍新闻',
|
||||
url: 'm.hupu.com',
|
||||
maintainers: ['hyoban'],
|
||||
example: '/news/Spurs',
|
||||
parameters: {
|
||||
team: {
|
||||
description: '全小写的英文队名,例如:spurs, lakers, warriors 等等',
|
||||
},
|
||||
},
|
||||
categories: ['bbs'],
|
||||
handler: async (ctx): Promise<Data> => {
|
||||
const team = NBA_TEAMS_ID_MAP[ctx.req.param('team')];
|
||||
const teamId = team?.teamId;
|
||||
if (!teamId) {
|
||||
throw new Error('Invalid team name');
|
||||
}
|
||||
const data = await ofetch(`https://games.mobileapi.hupu.com/3/7.5.60/basketballapi/news/v2/teamNewsById?cateGoryCode=basketball&clientId=93977196&newsId=0&teamId=${teamId}`);
|
||||
|
||||
let items: DataItem[] = data.result.map((item) => ({
|
||||
title: item.title,
|
||||
guid: item.tid,
|
||||
link: `https://m.hupu.com/bbs/${item.tid}`,
|
||||
pubDate: timezone(parseDate(item.publishTime), +8),
|
||||
}));
|
||||
|
||||
items = await Promise.all(items.map((item) => getEntryDetails(item)));
|
||||
|
||||
return {
|
||||
title: `虎扑 - ${team.teamName} 新闻`,
|
||||
link: 'https://m.hupu.com',
|
||||
item: items,
|
||||
} as Data;
|
||||
},
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
import { load } from 'cheerio';
|
||||
|
||||
import type { DataItem } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { parseDate, parseRelativeDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
|
||||
export function getEntryDetails(item: DataItem): Promise<DataItem> {
|
||||
if (!item.link) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
return cache.tryGet(item.link!, async () => {
|
||||
try {
|
||||
const detailResponse = await got({
|
||||
method: 'get',
|
||||
url: item.link,
|
||||
});
|
||||
|
||||
const content = load(detailResponse.data);
|
||||
|
||||
const author = content('.bbs-user-info-name, .bbs-user-wrapper-content-name-span').text();
|
||||
const pubDateString = content('.second-line-user-info span:not([class])').text();
|
||||
// Possible formats: 10:21, 45分钟前, 09-15 19:57
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentDate = new Date();
|
||||
const monthDayTimePattern = /^(\d{2})-(\d{2}) (\d{2}):(\d{2})$/;
|
||||
const timeOnlyPattern = /^(\d{1,2}):(\d{2})$/;
|
||||
let processedDateString = pubDateString;
|
||||
|
||||
if (monthDayTimePattern.test(pubDateString)) {
|
||||
processedDateString = `${currentYear}-${pubDateString}`;
|
||||
} else if (timeOnlyPattern.test(pubDateString)) {
|
||||
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(currentDate.getDate()).padStart(2, '0');
|
||||
processedDateString = `${currentYear}-${month}-${day} ${pubDateString}`;
|
||||
}
|
||||
|
||||
const pubDate = [item.pubDate, timezone(parseDate(processedDateString), +8), timezone(parseRelativeDate(pubDateString), +8)].find((d) => d instanceof Date && !Number.isNaN(d.getTime()));
|
||||
const categories = content('.basketballTobbs_tag > a, .tag-player-team')
|
||||
.toArray()
|
||||
.map((c) => content(c).text())
|
||||
.filter(Boolean);
|
||||
|
||||
content('.basketballTobbs_tag').remove();
|
||||
content('.hupu-img').each(function () {
|
||||
const imgSrc = content(this).attr('data-gif') || content(this).attr('data-origin') || content(this).attr('src');
|
||||
content(this).parent().html(`<img src="${imgSrc}">`);
|
||||
});
|
||||
|
||||
// 分别获取内容元素
|
||||
const descriptionParts: string[] = [];
|
||||
|
||||
// 获取主要内容
|
||||
const mainContent = content('#bbs-thread-content, .bbs-content-font').html();
|
||||
if (mainContent) {
|
||||
descriptionParts.push(mainContent);
|
||||
}
|
||||
|
||||
// 单独处理视频部分
|
||||
const videoWrapper = content('.header-video-wrapper');
|
||||
if (videoWrapper.length > 0) {
|
||||
const videoElement = videoWrapper.find('video');
|
||||
if (videoElement.length > 0) {
|
||||
const videoHtml = videoElement.prop('outerHTML');
|
||||
if (videoHtml) {
|
||||
descriptionParts.push(videoHtml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const description = descriptionParts.length > 0 ? descriptionParts.join('') : undefined;
|
||||
|
||||
return {
|
||||
...item,
|
||||
author,
|
||||
category: categories.length > 0 ? categories : item.category,
|
||||
description,
|
||||
pubDate,
|
||||
};
|
||||
} catch {
|
||||
// no-empty
|
||||
return item;
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user