From 42b5362fbe340455fdd529586ced4cae8b73b0a6 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 18 Sep 2024 07:54:47 +0800 Subject: [PATCH] feat(route): soundon (#16789) --- lib/routes/soundon/namespace.ts | 6 +++ lib/routes/soundon/podcast.ts | 80 +++++++++++++++++++++++++++++++++ lib/routes/soundon/types.ts | 67 +++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 lib/routes/soundon/namespace.ts create mode 100644 lib/routes/soundon/podcast.ts create mode 100644 lib/routes/soundon/types.ts diff --git a/lib/routes/soundon/namespace.ts b/lib/routes/soundon/namespace.ts new file mode 100644 index 0000000000000..e65a0431a6837 --- /dev/null +++ b/lib/routes/soundon/namespace.ts @@ -0,0 +1,6 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'SoundOn', + url: 'player.soundon.fm', +}; diff --git a/lib/routes/soundon/podcast.ts b/lib/routes/soundon/podcast.ts new file mode 100644 index 0000000000000..d8163dfc29e53 --- /dev/null +++ b/lib/routes/soundon/podcast.ts @@ -0,0 +1,80 @@ +import { Route, ViewType } from '@/types'; +import { config } from '@/config'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import { Podcast, PodcastInfo } from './types'; + +const handler = async (ctx) => { + const { id } = ctx.req.param(); + + const apiEndpoint = 'https://api.soundon.fm/v2/client'; + const apiToken = 'KilpEMLQeNzxmNBL55u5'; + + const podcastInfo = (await cache.tryGet(`soundon:${id}`, async () => { + const response = await ofetch(`${apiEndpoint}/podcasts/${id}`, { + headers: { + 'api-token': apiToken, + }, + }); + return response.data.data; + })) as PodcastInfo; + + const episodes = (await cache.tryGet( + `soundon:${id}:episodes`, + async () => { + const response = await ofetch(`${apiEndpoint}/podcasts/${id}/episodes`, { + headers: { + 'api-token': apiToken, + }, + }); + return response.data; + }, + config.cache.routeExpire, + false + )) as Podcast[]; + + const items = episodes.map(({ data: item }) => ({ + title: item.title, + description: item.contentEncoded, + link: item.url, + author: item.artistName, + pubDate: parseDate(item.publishDate), + itunes_item_image: item.cover, + enclosure_url: item.audioUrl, + enclosure_type: item.audioType, + itunes_duration: item.duration, + category: item.itunesKeywords, + })); + + return { + title: podcastInfo.title, + description: podcastInfo.description, + itunes_author: podcastInfo.artistName, + itunes_category: podcastInfo.itunesCategories.join(', '), + itunes_explicit: podcastInfo.explicit, + image: podcastInfo.cover, + language: podcastInfo.language, + link: podcastInfo.url, + item: items, + }; +}; + +export const route: Route = { + path: '/p/:id', + categories: ['multimedia'], + example: '/soundon/p/33a68cdc-18ad-4192-84cc-22bd7fdc6a31', + parameters: { id: 'Podcast ID' }, + features: { + supportPodcast: true, + }, + radar: [ + { + source: ['player.soundon.fm/p/:id'], + }, + ], + name: 'Podcast', + maintainers: ['TonyRL'], + view: ViewType.Audios, + handler, +}; diff --git a/lib/routes/soundon/types.ts b/lib/routes/soundon/types.ts new file mode 100644 index 0000000000000..94cf36daf39dd --- /dev/null +++ b/lib/routes/soundon/types.ts @@ -0,0 +1,67 @@ +export interface PodcastInfo { + id: string; + title: string; + channels: string[]; + feedUrl: string; + explicit: boolean; + description: string; + itunesCategories: string[]; + cover: string; + complete: boolean; + blocked: boolean; + lastIndexedAt: string; + publishDate: string; + copyright: string; + url: string; + tsv: string; + ownerEmail: string; + ownerName: string; + artistName: string; + language: string; + subtitle: string; + enableProductPage: boolean; + itunesType: string; + contentEncoded: string; + createdAt: string; + updatedAt: string; + donationUrl: string; + weight: number; + activated: boolean; + guid: string; + soundonId: string; +} + +export interface Podcast { + id: string; + updatedAt: string; + createdAt: string; + data: PodcastData; +} + +interface PodcastData { + id: string; + guid: string; + hash: string; + title: string; + audioUrl: string; + explicit: boolean; + description: string; + complete: boolean; + publishDate: string; + itunesKeywords: string[]; + audioType: string; + duration: number; + artistName: string; + url: string; + cover: string; + contentEncoded: string; + podcastId: string; + summary: string; + episodeType: string; + exclusiveType: string; + createdAt: string; + updatedAt: string; + weight: number; + keywords: string[]; + activated: boolean; +}