feat: render description's html

chore: refactor to get episodes data from rss feed
This commit is contained in:
2024-04-20 16:58:52 +02:00
parent 8daddc383f
commit 730090ca9e
14 changed files with 1718 additions and 1351 deletions

View File

@@ -4,9 +4,13 @@ export class PodcastExtraDTO {
id: number;
trackCount: number;
images: { small: string; medium: string; large: string };
author: string;
title: string;
constructor(data: PodcastDetails) {
this.id = data.trackId;
this.author = data.artistName;
this.title = data.collectionName;
this.trackCount = data.trackCount;
this.images = {
small: data.artworkUrl60,

View File

@@ -13,10 +13,10 @@ export function useTopPodcastsQuery() {
});
}
export function usePodcastEpisodesQuery(podcastId?: string) {
export function usePodcastQuery(podcastId?: string) {
return useQuery({
queryKey: [QUERY_KEYS.PODCAST_EPISODES, podcastId],
queryFn: () => podcastsService.getEpisodesByPodcastId(podcastId ?? ""),
queryFn: () => podcastsService.getPodcastById(podcastId ?? ""),
enabled: !!podcastId,
});
}

View File

@@ -1,6 +1,6 @@
import { GetEpisodesResponse, GetTopPodcastsResponse } from "./podcasts.types";
import { PodcastDTO } from "./dto/podcast.dto";
import { EpisodeDto } from "./dto/episode.dto";
import { RssParser } from "../rss-parser";
import { PodcastExtraDTO } from "./dto/podcast-extra.dto";
class PodcastsService {
@@ -15,32 +15,46 @@ class PodcastsService {
return data.feed.entry.map((podcast) => new PodcastDTO(podcast));
}
async getEpisodesByPodcastId(podcastId: string) {
const url = new URL(`${this.baseUrl}/lookup`);
const params = {
id: podcastId,
media: "podcast",
entity: "podcastEpisode",
limit: "20",
};
url.search = new URLSearchParams(params).toString();
async getPodcastById(podcastId: string) {
try {
const url = new URL(`${this.baseUrl}/lookup`);
const params = {
id: podcastId,
media: "podcast",
entity: "podcast",
limit: "1",
};
const response: GetEpisodesResponse = await this.fetchWithoutCors(
url.toString(),
);
url.search = new URLSearchParams(params).toString();
let podcast: PodcastExtraDTO = {} as PodcastExtraDTO;
const episodes: EpisodeDto[] = [];
const response: GetEpisodesResponse = await this.fetchWithoutCors(
url.toString(),
);
const podcastExtra = new PodcastExtraDTO(response.results[0]);
const podcastFromFeed = await this.getPodcastFeed(
response.results[0].feedUrl,
);
return { ...podcastExtra, ...podcastFromFeed };
} catch (error) {
console.error("Error fetching podcast data:", error);
throw error;
}
}
response.results.forEach((entry) => {
if (entry.kind === "podcast-episode") {
episodes.push(new EpisodeDto(entry));
} else if (entry.kind === "podcast") {
podcast = new PodcastExtraDTO(entry);
}
});
async getPodcastFeed(sourceURL: string) {
try {
// todo: only use allorigins after getting a cors error
// const response = await fetch(
// `https://api.allorigins.win/raw?url=${encodeURIComponent(sourceURL)}`,
// );
const response = await fetch(sourceURL);
const rss = await response.text();
return { episodes, podcast: podcast };
return RssParser.parse(rss);
} catch (error) {
console.error("Error fetching and parsing data:", error);
throw error;
}
}
//TODO: move into a separate service

View File

@@ -165,7 +165,7 @@ export interface Id2 {
export interface GetEpisodesResponse {
resultCount: number;
results: Array<Episode | PodcastDetails>;
results: Array<PodcastDetails>;
}
export interface Episode {

View File

@@ -0,0 +1 @@
export * from "./rss-parser";

View File

@@ -0,0 +1,76 @@
export interface Episode {
id: string;
releaseDate: string;
audioUrl: string;
title: string;
durationSeconds: number;
description: string;
}
export interface Podcast {
description: string;
episodes: Episode[];
}
export class RssParser {
public static parse(rss: string): Podcast {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(rss, "text/xml");
const channel = xmlDoc.querySelector("channel");
const description = this.getElementInnerHtml(channel, "description");
const episodes: Episode[] = [];
const items = Array.from(xmlDoc.querySelectorAll("item"));
items.forEach((item) => {
const id = this.getElementInnerHtml(item, "guid");
const releaseDate = this.getElementInnerHtml(item, "pubDate");
const audioUrl =
this.getElementByTagName(item, "enclosure")?.getAttribute("url") ?? "";
const episodeTitle = this.getElementInnerHtml(item, "title");
const durationSeconds = parseInt(
this.getElementInnerHtml(item, "itunes:duration") ?? "0",
);
const episodeDescription =
this.getElementInnerHtml(item, "content:encoded") ??
this.getElementInnerHtml(item, "description");
episodes.push({
id,
releaseDate,
audioUrl,
title: episodeTitle,
durationSeconds,
description: episodeDescription,
});
});
return {
description,
episodes,
};
}
public static getElementByTagName(element: Element | null, tagName: string) {
return element?.getElementsByTagName(tagName)[0];
}
public static getElementInnerHtml(element: Element | null, tagName: string) {
return this.cleanCDATA(
this.getElementByTagName(element, tagName)?.innerHTML ?? "",
);
}
public static cleanCDATA(data?: string) {
if (!data) {
return "";
}
return data.replace("<![CDATA[", "").replace("]]>", "");
}
}