@rapthi/podca-ts 1.0.4 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +18 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -6
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/dist/index.cjs
CHANGED
|
@@ -11,9 +11,22 @@ var _ItunesSearch = class _ItunesSearch {
|
|
|
11
11
|
try {
|
|
12
12
|
const response = await fetch(searchUrlWithParams);
|
|
13
13
|
if (!response.ok) {
|
|
14
|
-
throw new Error(
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
throw new Error(`Failed to fetch data from iTunes Search API: ${response.status}}`);
|
|
15
|
+
}
|
|
16
|
+
return await response.json();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof Error) {
|
|
19
|
+
throw new Error(`Fetch failed: ${error.message}`);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async lookupById(id) {
|
|
25
|
+
const lookupUrl = `${_ItunesSearch.ITUNES_LOOKUP_URL}?id=${id}`;
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(lookupUrl);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to fetch data from iTunes Lookup API: ${response.status}`);
|
|
17
30
|
}
|
|
18
31
|
return await response.json();
|
|
19
32
|
} catch (error) {
|
|
@@ -34,6 +47,7 @@ var _ItunesSearch = class _ItunesSearch {
|
|
|
34
47
|
}
|
|
35
48
|
};
|
|
36
49
|
_ItunesSearch.ITUNES_SEARCH_URL = "https://itunes.apple.com/search";
|
|
50
|
+
_ItunesSearch.ITUNES_LOOKUP_URL = "https://itunes.apple.com/lookup";
|
|
37
51
|
var ItunesSearch = _ItunesSearch;
|
|
38
52
|
var PodcastLoader = class {
|
|
39
53
|
constructor() {
|
|
@@ -52,9 +66,7 @@ var PodcastLoader = class {
|
|
|
52
66
|
try {
|
|
53
67
|
const response = await fetch(feedUrl, { signal: controller.signal });
|
|
54
68
|
if (!response.ok) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Failed to fetch podcast feed: ${response.status} ${response.statusText}`
|
|
57
|
-
);
|
|
69
|
+
throw new Error(`Failed to fetch podcast feed: ${response.status} ${response.statusText}`);
|
|
58
70
|
}
|
|
59
71
|
const xmlString = await response.text();
|
|
60
72
|
if (!xmlString.trim()) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/itunes-search/itunes-search.ts","../src/podcast/podcast.ts"],"names":["parseFeedToJson"],"mappings":";;;;;AAGO,IAAM,aAAA,GAAN,MAAM,aAAA,CAAa;AAAA,EAGxB,WAAA,GAAc;AAAA,EAAC;AAAA,EAEf,MAAM,OAA4B,MAAA,EAA8D;AAC9F,IAAA,MAAM,mBAAA,GAAsB,IAAA,CAAK,cAAA,CAAe,MAAM,CAAA;AAEtD,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,mBAAmB,CAAA;AAEhD,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAEhB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,6CAAA,EAAgD,SAAS,MAAM,CAAA,CAAA;AAAA,SACjE;AAAA,MACF;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC7B,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,iBAAiB,KAAA,EAAO;AAC1B,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,cAAA,EAAiB,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,MAClD;AAEA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,eAAoC,MAAA,EAAuC;AACjF,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,aAAA,CAAa,iBAAiB,CAAA;AAElD,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjD,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,GAAA,CAAI,YAAA,CAAa,MAAA,CAAO,GAAA,EAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,MAC5C;AAAA,IACF;AAEA,IAAA,OAAO,IAAI,QAAA,EAAS;AAAA,EACtB;AACF,CAAA;AAvCa,aAAA,CACa,iBAAA,GAAoB,iCAAA;AADvC,IAAM,YAAA,GAAN;AC8CA,IAAM,gBAAN,MAAoB;AAAA,EAApB,WAAA,GAAA;AACL,IAAA,IAAA,CAAiB,gBAAA,GAAmB,GAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpC,MAAM,mBAAmB,OAAA,EAAmC;AAC1D,IAAA,IAAA,CAAK,YAAY,OAAO,CAAA;AAExB,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,YAAY,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,gBAAgB,CAAA;AAE5E,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,OAAA,EAAS,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAEnE,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,8BAAA,EAAiC,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,SACzE;AAAA,MACF;AAEA,MAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AAEtC,MAAA,IAAI,CAAC,SAAA,CAAU,IAAA,EAAK,EAAG;AACrB,QAAA,MAAM,IAAI,MAAM,uBAAuB,CAAA;AAAA,MACzC;AAEA,MAAA,MAAM,cAAA,GAAiB,MAAMA,6BAAA,CAAgB,SAAS,CAAA;AACtD,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,cAAA,CAAe,cAAc,CAAA;AAElD,MAAA,IAAA,CAAK,gBAAgB,OAAO,CAAA;AAE5B,MAAA,OAAO,IAAA,CAAK,WAAW,OAAO,CAAA;AAAA,IAChC,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,KAAA,YAAiB,KAAA,IAAS,KAAA,CAAM,IAAA,KAAS,YAAA,EAAc;AACzD,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,IAAA,CAAK,gBAAgB,CAAA,EAAA,CAAI,CAAA;AAAA,MACjF;AAEA,MAAA,IAAI,iBAAiB,KAAA,EAAO;AAC1B,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,MACjE;AAEA,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,SAAS,CAAA;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,YAAY,OAAA,EAAuB;AACzC,IAAA,IAAI;AACF,MAAA,IAAI,IAAI,OAAO,CAAA;AAAA,IACjB,CAAA,CAAA,MAAQ;AACN,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,OAAO,CAAA,CAAE,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEQ,eAAe,cAAA,EAA4C;AACjE,IAAA,MAAM,OAAA,GAAU,gBAAgB,GAAA,EAAK,OAAA;AAErC,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,IAC9D;AAEA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEQ,gBAAgB,OAAA,EAA2B;AACjD,IAAA,IAAI,CAAC,QAAQ,KAAA,EAAO;AAClB,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE;AAEA,IAAA,IAAI,CAAC,QAAQ,IAAA,EAAM;AACjB,MAAA,MAAM,IAAI,MAAM,qDAAqD,CAAA;AAAA,IACvE;AAAA,EACF;AAAA,EAEQ,WAAW,OAAA,EAA8B;AAC/C,IAAA,OAAO;AAAA,MACL,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,MAAM,OAAA,CAAQ,IAAA;AAAA,MACd,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,UAAA,EAAY,IAAA,CAAK,aAAA,CAAc,OAAA,CAAQ,iBAAiB,CAAC,CAAA;AAAA,MACzD,QAAA,EAAU,OAAA,CAAQ,iBAAiB,CAAA,KAAM,MAAA;AAAA,MACzC,QAAA,EAAU,OAAA,CAAQ,cAAc,CAAA,GAAI,QAAQ,CAAA;AAAA,MAC5C,MAAA,EAAQ,QAAQ,eAAe,CAAA;AAAA,MAC/B,SAAA,EAAW,QAAQ,WAAW,CAAA;AAAA,MAC9B,UAAA,EAAY,OAAA,CAAQ,iBAAiB,CAAA,GAAI,OAAO,CAAA;AAAA,MAChD,IAAA,EAAM,QAAQ,aAAa,CAAA;AAAA,MAC3B,QAAA,EAAU,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,IAAI;AAAA,KACzC;AAAA,EACF;AAAA,EAEQ,cAAc,UAAA,EAAmD;AACvE,IAAA,IAAI,CAAC,UAAA,EAAY;AAAC,MAAA,OAAO,EAAC;AAAA,IAAE;AAE5B,IAAA,OAAO,UAAA,CAAW,OAAA,CAAQ,CAAC,QAAA,KAAa;AAAA,MACtC,EAAE,IAAA,EAAM,QAAA,CAAS,QAAQ,CAAA,EAAE;AAAA,MAC3B,GAAI,QAAA,CAAS,iBAAiB,CAAA,EAAG,IAAI,CAAC,GAAA,MAAS,EAAE,IAAA,EAAM,GAAA,CAAI,QAAQ,CAAA,EAAE,CAAE,KAAK;AAAC,KAC9E,CAAA;AAAA,EACH;AAAA,EAEQ,YAAY,KAAA,EAA4C;AAC9D,IAAA,IAAI,CAAC,KAAA,EAAO;AAAC,MAAA,OAAO,EAAC;AAAA,IAAE;AAEvB,IAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,MAAU;AAAA,MAC1B,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,SAAA,EAAW,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,SAAS,CAAA;AAAA,MAC3C,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAAA,MACvB,SAAS,IAAA,CAAK,IAAA;AAAA,MACd,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,iBAAA,EAAmB,KAAK,iBAAiB,CAAA;AAAA,MACzC,QAAA,EAAU,IAAA,CAAK,cAAc,CAAA,GAAI,QAAQ,CAAA;AAAA,MACzC,QAAA,EAAU,IAAA,CAAK,iBAAiB,CAAA,KAAM,KAAA;AAAA,MACtC,MAAA,EAAQ,KAAK,gBAAgB,CAAA;AAAA,MAC7B,MAAA,EAAQ,KAAK,eAAe,CAAA;AAAA,MAC5B,IAAA,EAAM,KAAK,oBAAoB;AAAA,KACjC,CAAE,CAAA;AAAA,EACJ;AAAA,EAEQ,aAAa,SAAA,EAA8D;AACjF,IAAA,IAAI,CAAC,SAAA,GAAY,CAAC,CAAA,EAAG;AAAC,MAAA,OAAO,MAAA;AAAA,IAAU;AAEvC,IAAA,OAAO;AAAA,MACL,GAAA,EAAK,SAAA,CAAU,CAAC,CAAA,CAAE,OAAO,CAAA;AAAA,MACzB,IAAA,EAAM,SAAA,CAAU,CAAC,CAAA,CAAE,QAAQ,CAAA;AAAA,MAC3B,MAAA,EAAQ,SAAA,CAAU,CAAC,CAAA,CAAE,UAAU;AAAA,KACjC;AAAA,EACF;AACF","file":"index.cjs","sourcesContent":["import type { ITunesSearchParams, MediaType } from './itunes-search-options.js';\nimport type { ITunesSearchResponse } from './itunes-search-result.js';\n\nexport class ItunesSearch {\n private static readonly ITUNES_SEARCH_URL = 'https://itunes.apple.com/search';\n\n constructor() {}\n\n async search<T extends MediaType>(option: ITunesSearchParams<T>): Promise<ITunesSearchResponse> {\n const searchUrlWithParams = this.buildSearchUrl(option);\n\n try {\n const response = await fetch(searchUrlWithParams);\n\n if (!response.ok) {\n // noinspection ExceptionCaughtLocallyJS\n throw new Error(\n `Failed to fetch data from iTunes Search API: ${response.status}}`,\n );\n }\n\n return await response.json() as ITunesSearchResponse;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Fetch failed: ${error.message}`);\n }\n\n throw error;\n }\n }\n\n private buildSearchUrl<T extends MediaType>(option: ITunesSearchParams<T>): string {\n const url = new URL(ItunesSearch.ITUNES_SEARCH_URL);\n\n for (const [key, value] of Object.entries(option)) {\n if (value !== undefined) {\n url.searchParams.append(key, String(value));\n }\n }\n\n return url.toString();\n }\n}\n","import { parseFeedToJson } from '@sesamy/podcast-parser';\n\ninterface RawCategory {\n '@_text': string;\n 'itunes:category'?: RawCategory[];\n}\n\ninterface RawEnclosure {\n '@_url': string;\n '@_type': string;\n '@_length': string;\n}\n\ninterface RawEpisode {\n title?: string;\n guid: { '#text': string };\n link?: string;\n pubDate?: string;\n description?: string;\n 'itunes:duration'?: string | number;\n 'itunes:image'?: { '@_href': string };\n 'itunes:explicit'?: 'yes' | 'no';\n 'itunes:episode'?: number;\n 'itunes:season'?: number;\n 'itunes:episodeType'?: string;\n enclosure?: RawEnclosure[];\n}\n\ninterface RawChannel {\n title: string;\n description?: string;\n link: string;\n language?: string;\n 'itunes:category'?: RawCategory[];\n 'itunes:explicit'?: 'true' | 'false';\n 'itunes:image'?: { '@_href': string };\n 'itunes:author'?: string;\n copyright?: string;\n 'podcast:funding'?: { '@_url': string };\n 'itunes:type'?: string;\n item?: RawEpisode[];\n}\n\ninterface RawPodcastFeed {\n rss?: {\n channel?: RawChannel;\n };\n}\n\nexport class PodcastLoader {\n private readonly FETCH_TIMEOUT_MS = 30000;\n\n /**\n * Fetches and parses a podcast feed from the given URL\n * @param feedUrl - The URL of the podcast feed\n * @returns A Promise resolving to the parsed Podcast\n * @throws Error if the feed is invalid or the fetch fails\n */\n async getPodcastFromFeed(feedUrl: string): Promise<Podcast> {\n this.validateUrl(feedUrl);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.FETCH_TIMEOUT_MS);\n\n try {\n const response = await fetch(feedUrl, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch podcast feed: ${response.status} ${response.statusText}`,\n );\n }\n\n const xmlString = await response.text();\n\n if (!xmlString.trim()) {\n throw new Error('Podcast feed is empty');\n }\n\n const podcastFromXml = await parseFeedToJson(xmlString) as RawPodcastFeed;\n const channel = this.extractChannel(podcastFromXml);\n\n this.validateChannel(channel);\n\n return this.mapChannel(channel);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Podcast feed request timeout after ${this.FETCH_TIMEOUT_MS}ms`);\n }\n\n if (error instanceof Error) {\n throw new Error(`Failed to load podcast feed: ${error.message}`);\n }\n\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private validateUrl(feedUrl: string): void {\n try {\n new URL(feedUrl);\n } catch {\n throw new Error(`Invalid feed URL: ${feedUrl}`);\n }\n }\n\n private extractChannel(podcastFromXml: RawPodcastFeed): RawChannel {\n const channel = podcastFromXml?.rss?.channel;\n\n if (!channel) {\n throw new Error('Invalid podcast feed: missing channel data');\n }\n\n return channel;\n }\n\n private validateChannel(channel: RawChannel): void {\n if (!channel.title) {\n throw new Error('Invalid podcast feed: missing required field \"title\"');\n }\n\n if (!channel.link) {\n throw new Error('Invalid podcast feed: missing required field \"link\"');\n }\n }\n\n private mapChannel(channel: RawChannel): Podcast {\n return {\n title: channel.title,\n description: channel.description,\n link: channel.link,\n language: channel.language,\n categories: this.mapCategories(channel['itunes:category']),\n explicit: channel['itunes:explicit'] === 'true',\n imageUrl: channel['itunes:image']?.['@_href'],\n author: channel['itunes:author'],\n copyright: channel['copyright'],\n fundingUrl: channel['podcast:funding']?.['@_url'],\n type: channel['itunes:type'],\n episodes: this.mapEpisodes(channel.item),\n };\n }\n\n private mapCategories(categories: RawCategory[] | undefined): Category[] {\n if (!categories) {return [];}\n\n return categories.flatMap((category) => [\n { name: category['@_text'] },\n ...(category['itunes:category']?.map((sub) => ({ name: sub['@_text'] })) || []),\n ]);\n }\n\n private mapEpisodes(items: RawEpisode[] | undefined): Episode[] {\n if (!items) {return [];}\n\n return items.map((item) => ({\n title: item.title,\n enclosure: this.mapEnclosure(item.enclosure),\n guid: item.guid['#text'],\n linkUrl: item.link,\n pubDate: item.pubDate,\n description: item.description,\n durationInSeconds: item['itunes:duration'],\n imageUrl: item['itunes:image']?.['@_href'],\n explicit: item['itunes:explicit'] === 'yes',\n number: item['itunes:episode'],\n season: item['itunes:season'],\n type: item['itunes:episodeType'],\n }));\n }\n\n private mapEnclosure(enclosure: RawEnclosure[] | undefined): Enclosure | undefined {\n if (!enclosure?.[0]) {return undefined;}\n\n return {\n url: enclosure[0]['@_url'],\n type: enclosure[0]['@_type'],\n length: enclosure[0]['@_length'],\n };\n }\n}\n\nexport interface Category {\n name: string;\n}\n\nexport interface Episode {\n title: string | undefined;\n enclosure: Enclosure | undefined;\n guid: string;\n linkUrl?: string;\n pubDate?: string;\n description?: string;\n durationInSeconds?: string | number | undefined;\n imageUrl?: string;\n explicit?: boolean;\n number?: number;\n season?: number;\n type?: string | undefined;\n}\n\nexport interface Enclosure {\n length: string;\n type: string;\n url: string;\n}\n\nexport interface Podcast {\n title: string;\n description: string | undefined;\n link: string;\n language: string | undefined;\n categories: Category[];\n explicit: boolean;\n imageUrl?: string;\n author?: string;\n copyright?: string;\n fundingUrl?: string;\n type?: string;\n complete?: boolean;\n episodes?: Episode[];\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/itunes-search/itunes-search.ts","../src/podcast/podcast.ts"],"names":["parseFeedToJson"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ITunesSearchParams, MediaType } from './itunes-search-options.js';\nimport type { ITunesSearchResponse } from './itunes-search-result.js';\n\nexport class ItunesSearch {\n private static readonly ITUNES_SEARCH_URL = 'https://itunes.apple.com/search';\n private static readonly ITUNES_LOOKUP_URL = 'https://itunes.apple.com/lookup';\n\n constructor() {}\n\n async search<T extends MediaType>(option: ITunesSearchParams<T>): Promise<ITunesSearchResponse> {\n const searchUrlWithParams = this.buildSearchUrl(option);\n\n try {\n const response = await fetch(searchUrlWithParams);\n\n if (!response.ok) {\n // noinspection ExceptionCaughtLocallyJS\n throw new Error(`Failed to fetch data from iTunes Search API: ${response.status}}`);\n }\n\n return (await response.json()) as ITunesSearchResponse;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Fetch failed: ${error.message}`);\n }\n\n throw error;\n }\n }\n\n async lookupById(id: number): Promise<ITunesSearchResponse> {\n const lookupUrl = `${ItunesSearch.ITUNES_LOOKUP_URL}?id=${id}`;\n\n try {\n const response = await fetch(lookupUrl);\n\n if (!response.ok) {\n // noinspection ExceptionCaughtLocallyJS\n throw new Error(`Failed to fetch data from iTunes Lookup API: ${response.status}`);\n }\n\n return (await response.json()) as ITunesSearchResponse;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Fetch failed: ${error.message}`);\n }\n\n throw error;\n }\n }\n\n private buildSearchUrl<T extends MediaType>(option: ITunesSearchParams<T>): string {\n const url = new URL(ItunesSearch.ITUNES_SEARCH_URL);\n\n for (const [key, value] of Object.entries(option)) {\n if (value !== undefined) {\n url.searchParams.append(key, String(value));\n }\n }\n\n return url.toString();\n }\n}\n","import { parseFeedToJson } from '@sesamy/podcast-parser';\n\ninterface RawCategory {\n '@_text': string;\n 'itunes:category'?: RawCategory[];\n}\n\ninterface RawEnclosure {\n '@_url': string;\n '@_type': string;\n '@_length': string;\n}\n\ninterface RawEpisode {\n title?: string;\n guid: { '#text': string };\n link?: string;\n pubDate?: string;\n description?: string;\n 'itunes:duration'?: string | number;\n 'itunes:image'?: { '@_href': string };\n 'itunes:explicit'?: 'yes' | 'no';\n 'itunes:episode'?: number;\n 'itunes:season'?: number;\n 'itunes:episodeType'?: string;\n enclosure?: RawEnclosure[];\n}\n\ninterface RawChannel {\n title: string;\n description?: string;\n link: string;\n language?: string;\n 'itunes:category'?: RawCategory[];\n 'itunes:explicit'?: 'true' | 'false';\n 'itunes:image'?: { '@_href': string };\n 'itunes:author'?: string;\n copyright?: string;\n 'podcast:funding'?: { '@_url': string };\n 'itunes:type'?: string;\n item?: RawEpisode[];\n}\n\ninterface RawPodcastFeed {\n rss?: {\n channel?: RawChannel;\n };\n}\n\nexport class PodcastLoader {\n private readonly FETCH_TIMEOUT_MS = 30000;\n\n /**\n * Fetches and parses a podcast feed from the given URL\n * @param feedUrl - The URL of the podcast feed\n * @returns A Promise resolving to the parsed Podcast\n * @throws Error if the feed is invalid or the fetch fails\n */\n async getPodcastFromFeed(feedUrl: string): Promise<Podcast> {\n this.validateUrl(feedUrl);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.FETCH_TIMEOUT_MS);\n\n try {\n const response = await fetch(feedUrl, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch podcast feed: ${response.status} ${response.statusText}`);\n }\n\n const xmlString = await response.text();\n\n if (!xmlString.trim()) {\n throw new Error('Podcast feed is empty');\n }\n\n const podcastFromXml = (await parseFeedToJson(xmlString)) as RawPodcastFeed;\n const channel = this.extractChannel(podcastFromXml);\n\n this.validateChannel(channel);\n\n return this.mapChannel(channel);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Podcast feed request timeout after ${this.FETCH_TIMEOUT_MS}ms`);\n }\n\n if (error instanceof Error) {\n throw new Error(`Failed to load podcast feed: ${error.message}`);\n }\n\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private validateUrl(feedUrl: string): void {\n try {\n new URL(feedUrl);\n } catch {\n throw new Error(`Invalid feed URL: ${feedUrl}`);\n }\n }\n\n private extractChannel(podcastFromXml: RawPodcastFeed): RawChannel {\n const channel = podcastFromXml?.rss?.channel;\n\n if (!channel) {\n throw new Error('Invalid podcast feed: missing channel data');\n }\n\n return channel;\n }\n\n private validateChannel(channel: RawChannel): void {\n if (!channel.title) {\n throw new Error('Invalid podcast feed: missing required field \"title\"');\n }\n\n if (!channel.link) {\n throw new Error('Invalid podcast feed: missing required field \"link\"');\n }\n }\n\n private mapChannel(channel: RawChannel): Podcast {\n return {\n title: channel.title,\n description: channel.description,\n link: channel.link,\n language: channel.language,\n categories: this.mapCategories(channel['itunes:category']),\n explicit: channel['itunes:explicit'] === 'true',\n imageUrl: channel['itunes:image']?.['@_href'],\n author: channel['itunes:author'],\n copyright: channel['copyright'],\n fundingUrl: channel['podcast:funding']?.['@_url'],\n type: channel['itunes:type'],\n episodes: this.mapEpisodes(channel.item),\n };\n }\n\n private mapCategories(categories: RawCategory[] | undefined): Category[] {\n if (!categories) {\n return [];\n }\n\n return categories.flatMap((category) => [\n { name: category['@_text'] },\n ...(category['itunes:category']?.map((sub) => ({ name: sub['@_text'] })) || []),\n ]);\n }\n\n private mapEpisodes(items: RawEpisode[] | undefined): Episode[] {\n if (!items) {\n return [];\n }\n\n return items.map((item) => ({\n title: item.title,\n enclosure: this.mapEnclosure(item.enclosure),\n guid: item.guid['#text'],\n linkUrl: item.link,\n pubDate: item.pubDate,\n description: item.description,\n durationInSeconds: item['itunes:duration'],\n imageUrl: item['itunes:image']?.['@_href'],\n explicit: item['itunes:explicit'] === 'yes',\n number: item['itunes:episode'],\n season: item['itunes:season'],\n type: item['itunes:episodeType'],\n }));\n }\n\n private mapEnclosure(enclosure: RawEnclosure[] | undefined): Enclosure | undefined {\n if (!enclosure?.[0]) {\n return undefined;\n }\n\n return {\n url: enclosure[0]['@_url'],\n type: enclosure[0]['@_type'],\n length: enclosure[0]['@_length'],\n };\n }\n}\n\nexport interface Category {\n name: string;\n}\n\nexport interface Episode {\n title: string | undefined;\n enclosure: Enclosure | undefined;\n guid: string;\n linkUrl?: string;\n pubDate?: string;\n description?: string;\n durationInSeconds?: string | number | undefined;\n imageUrl?: string;\n explicit?: boolean;\n number?: number;\n season?: number;\n type?: string | undefined;\n}\n\nexport interface Enclosure {\n length: string;\n type: string;\n url: string;\n}\n\nexport interface Podcast {\n title: string;\n description: string | undefined;\n link: string;\n language: string | undefined;\n categories: Category[];\n explicit: boolean;\n imageUrl?: string;\n author?: string;\n copyright?: string;\n fundingUrl?: string;\n type?: string;\n complete?: boolean;\n episodes?: Episode[];\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -55,8 +55,10 @@ interface ITunesSearchResponse {
|
|
|
55
55
|
|
|
56
56
|
declare class ItunesSearch {
|
|
57
57
|
private static readonly ITUNES_SEARCH_URL;
|
|
58
|
+
private static readonly ITUNES_LOOKUP_URL;
|
|
58
59
|
constructor();
|
|
59
60
|
search<T extends MediaType>(option: ITunesSearchParams<T>): Promise<ITunesSearchResponse>;
|
|
61
|
+
lookupById(id: number): Promise<ITunesSearchResponse>;
|
|
60
62
|
private buildSearchUrl;
|
|
61
63
|
}
|
|
62
64
|
|
package/dist/index.d.ts
CHANGED
|
@@ -55,8 +55,10 @@ interface ITunesSearchResponse {
|
|
|
55
55
|
|
|
56
56
|
declare class ItunesSearch {
|
|
57
57
|
private static readonly ITUNES_SEARCH_URL;
|
|
58
|
+
private static readonly ITUNES_LOOKUP_URL;
|
|
58
59
|
constructor();
|
|
59
60
|
search<T extends MediaType>(option: ITunesSearchParams<T>): Promise<ITunesSearchResponse>;
|
|
61
|
+
lookupById(id: number): Promise<ITunesSearchResponse>;
|
|
60
62
|
private buildSearchUrl;
|
|
61
63
|
}
|
|
62
64
|
|
package/dist/index.js
CHANGED
|
@@ -9,9 +9,22 @@ var _ItunesSearch = class _ItunesSearch {
|
|
|
9
9
|
try {
|
|
10
10
|
const response = await fetch(searchUrlWithParams);
|
|
11
11
|
if (!response.ok) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
throw new Error(`Failed to fetch data from iTunes Search API: ${response.status}}`);
|
|
13
|
+
}
|
|
14
|
+
return await response.json();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof Error) {
|
|
17
|
+
throw new Error(`Fetch failed: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async lookupById(id) {
|
|
23
|
+
const lookupUrl = `${_ItunesSearch.ITUNES_LOOKUP_URL}?id=${id}`;
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(lookupUrl);
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`Failed to fetch data from iTunes Lookup API: ${response.status}`);
|
|
15
28
|
}
|
|
16
29
|
return await response.json();
|
|
17
30
|
} catch (error) {
|
|
@@ -32,6 +45,7 @@ var _ItunesSearch = class _ItunesSearch {
|
|
|
32
45
|
}
|
|
33
46
|
};
|
|
34
47
|
_ItunesSearch.ITUNES_SEARCH_URL = "https://itunes.apple.com/search";
|
|
48
|
+
_ItunesSearch.ITUNES_LOOKUP_URL = "https://itunes.apple.com/lookup";
|
|
35
49
|
var ItunesSearch = _ItunesSearch;
|
|
36
50
|
var PodcastLoader = class {
|
|
37
51
|
constructor() {
|
|
@@ -50,9 +64,7 @@ var PodcastLoader = class {
|
|
|
50
64
|
try {
|
|
51
65
|
const response = await fetch(feedUrl, { signal: controller.signal });
|
|
52
66
|
if (!response.ok) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Failed to fetch podcast feed: ${response.status} ${response.statusText}`
|
|
55
|
-
);
|
|
67
|
+
throw new Error(`Failed to fetch podcast feed: ${response.status} ${response.statusText}`);
|
|
56
68
|
}
|
|
57
69
|
const xmlString = await response.text();
|
|
58
70
|
if (!xmlString.trim()) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/itunes-search/itunes-search.ts","../src/podcast/podcast.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ITunesSearchParams, MediaType } from './itunes-search-options.js';\nimport type { ITunesSearchResponse } from './itunes-search-result.js';\n\nexport class ItunesSearch {\n private static readonly ITUNES_SEARCH_URL = 'https://itunes.apple.com/search';\n\n constructor() {}\n\n async search<T extends MediaType>(option: ITunesSearchParams<T>): Promise<ITunesSearchResponse> {\n const searchUrlWithParams = this.buildSearchUrl(option);\n\n try {\n const response = await fetch(searchUrlWithParams);\n\n if (!response.ok) {\n // noinspection ExceptionCaughtLocallyJS\n throw new Error(\n `Failed to fetch data from iTunes Search API: ${response.status}}`,\n );\n }\n\n return await response.json() as ITunesSearchResponse;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Fetch failed: ${error.message}`);\n }\n\n throw error;\n }\n }\n\n private buildSearchUrl<T extends MediaType>(option: ITunesSearchParams<T>): string {\n const url = new URL(ItunesSearch.ITUNES_SEARCH_URL);\n\n for (const [key, value] of Object.entries(option)) {\n if (value !== undefined) {\n url.searchParams.append(key, String(value));\n }\n }\n\n return url.toString();\n }\n}\n","import { parseFeedToJson } from '@sesamy/podcast-parser';\n\ninterface RawCategory {\n '@_text': string;\n 'itunes:category'?: RawCategory[];\n}\n\ninterface RawEnclosure {\n '@_url': string;\n '@_type': string;\n '@_length': string;\n}\n\ninterface RawEpisode {\n title?: string;\n guid: { '#text': string };\n link?: string;\n pubDate?: string;\n description?: string;\n 'itunes:duration'?: string | number;\n 'itunes:image'?: { '@_href': string };\n 'itunes:explicit'?: 'yes' | 'no';\n 'itunes:episode'?: number;\n 'itunes:season'?: number;\n 'itunes:episodeType'?: string;\n enclosure?: RawEnclosure[];\n}\n\ninterface RawChannel {\n title: string;\n description?: string;\n link: string;\n language?: string;\n 'itunes:category'?: RawCategory[];\n 'itunes:explicit'?: 'true' | 'false';\n 'itunes:image'?: { '@_href': string };\n 'itunes:author'?: string;\n copyright?: string;\n 'podcast:funding'?: { '@_url': string };\n 'itunes:type'?: string;\n item?: RawEpisode[];\n}\n\ninterface RawPodcastFeed {\n rss?: {\n channel?: RawChannel;\n };\n}\n\nexport class PodcastLoader {\n private readonly FETCH_TIMEOUT_MS = 30000;\n\n /**\n * Fetches and parses a podcast feed from the given URL\n * @param feedUrl - The URL of the podcast feed\n * @returns A Promise resolving to the parsed Podcast\n * @throws Error if the feed is invalid or the fetch fails\n */\n async getPodcastFromFeed(feedUrl: string): Promise<Podcast> {\n this.validateUrl(feedUrl);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.FETCH_TIMEOUT_MS);\n\n try {\n const response = await fetch(feedUrl, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch podcast feed: ${response.status} ${response.statusText}`,\n );\n }\n\n const xmlString = await response.text();\n\n if (!xmlString.trim()) {\n throw new Error('Podcast feed is empty');\n }\n\n const podcastFromXml = await parseFeedToJson(xmlString) as RawPodcastFeed;\n const channel = this.extractChannel(podcastFromXml);\n\n this.validateChannel(channel);\n\n return this.mapChannel(channel);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Podcast feed request timeout after ${this.FETCH_TIMEOUT_MS}ms`);\n }\n\n if (error instanceof Error) {\n throw new Error(`Failed to load podcast feed: ${error.message}`);\n }\n\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private validateUrl(feedUrl: string): void {\n try {\n new URL(feedUrl);\n } catch {\n throw new Error(`Invalid feed URL: ${feedUrl}`);\n }\n }\n\n private extractChannel(podcastFromXml: RawPodcastFeed): RawChannel {\n const channel = podcastFromXml?.rss?.channel;\n\n if (!channel) {\n throw new Error('Invalid podcast feed: missing channel data');\n }\n\n return channel;\n }\n\n private validateChannel(channel: RawChannel): void {\n if (!channel.title) {\n throw new Error('Invalid podcast feed: missing required field \"title\"');\n }\n\n if (!channel.link) {\n throw new Error('Invalid podcast feed: missing required field \"link\"');\n }\n }\n\n private mapChannel(channel: RawChannel): Podcast {\n return {\n title: channel.title,\n description: channel.description,\n link: channel.link,\n language: channel.language,\n categories: this.mapCategories(channel['itunes:category']),\n explicit: channel['itunes:explicit'] === 'true',\n imageUrl: channel['itunes:image']?.['@_href'],\n author: channel['itunes:author'],\n copyright: channel['copyright'],\n fundingUrl: channel['podcast:funding']?.['@_url'],\n type: channel['itunes:type'],\n episodes: this.mapEpisodes(channel.item),\n };\n }\n\n private mapCategories(categories: RawCategory[] | undefined): Category[] {\n if (!categories) {return [];}\n\n return categories.flatMap((category) => [\n { name: category['@_text'] },\n ...(category['itunes:category']?.map((sub) => ({ name: sub['@_text'] })) || []),\n ]);\n }\n\n private mapEpisodes(items: RawEpisode[] | undefined): Episode[] {\n if (!items) {return [];}\n\n return items.map((item) => ({\n title: item.title,\n enclosure: this.mapEnclosure(item.enclosure),\n guid: item.guid['#text'],\n linkUrl: item.link,\n pubDate: item.pubDate,\n description: item.description,\n durationInSeconds: item['itunes:duration'],\n imageUrl: item['itunes:image']?.['@_href'],\n explicit: item['itunes:explicit'] === 'yes',\n number: item['itunes:episode'],\n season: item['itunes:season'],\n type: item['itunes:episodeType'],\n }));\n }\n\n private mapEnclosure(enclosure: RawEnclosure[] | undefined): Enclosure | undefined {\n if (!enclosure?.[0]) {return undefined;}\n\n return {\n url: enclosure[0]['@_url'],\n type: enclosure[0]['@_type'],\n length: enclosure[0]['@_length'],\n };\n }\n}\n\nexport interface Category {\n name: string;\n}\n\nexport interface Episode {\n title: string | undefined;\n enclosure: Enclosure | undefined;\n guid: string;\n linkUrl?: string;\n pubDate?: string;\n description?: string;\n durationInSeconds?: string | number | undefined;\n imageUrl?: string;\n explicit?: boolean;\n number?: number;\n season?: number;\n type?: string | undefined;\n}\n\nexport interface Enclosure {\n length: string;\n type: string;\n url: string;\n}\n\nexport interface Podcast {\n title: string;\n description: string | undefined;\n link: string;\n language: string | undefined;\n categories: Category[];\n explicit: boolean;\n imageUrl?: string;\n author?: string;\n copyright?: string;\n fundingUrl?: string;\n type?: string;\n complete?: boolean;\n episodes?: Episode[];\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/itunes-search/itunes-search.ts","../src/podcast/podcast.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ITunesSearchParams, MediaType } from './itunes-search-options.js';\nimport type { ITunesSearchResponse } from './itunes-search-result.js';\n\nexport class ItunesSearch {\n private static readonly ITUNES_SEARCH_URL = 'https://itunes.apple.com/search';\n private static readonly ITUNES_LOOKUP_URL = 'https://itunes.apple.com/lookup';\n\n constructor() {}\n\n async search<T extends MediaType>(option: ITunesSearchParams<T>): Promise<ITunesSearchResponse> {\n const searchUrlWithParams = this.buildSearchUrl(option);\n\n try {\n const response = await fetch(searchUrlWithParams);\n\n if (!response.ok) {\n // noinspection ExceptionCaughtLocallyJS\n throw new Error(`Failed to fetch data from iTunes Search API: ${response.status}}`);\n }\n\n return (await response.json()) as ITunesSearchResponse;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Fetch failed: ${error.message}`);\n }\n\n throw error;\n }\n }\n\n async lookupById(id: number): Promise<ITunesSearchResponse> {\n const lookupUrl = `${ItunesSearch.ITUNES_LOOKUP_URL}?id=${id}`;\n\n try {\n const response = await fetch(lookupUrl);\n\n if (!response.ok) {\n // noinspection ExceptionCaughtLocallyJS\n throw new Error(`Failed to fetch data from iTunes Lookup API: ${response.status}`);\n }\n\n return (await response.json()) as ITunesSearchResponse;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Fetch failed: ${error.message}`);\n }\n\n throw error;\n }\n }\n\n private buildSearchUrl<T extends MediaType>(option: ITunesSearchParams<T>): string {\n const url = new URL(ItunesSearch.ITUNES_SEARCH_URL);\n\n for (const [key, value] of Object.entries(option)) {\n if (value !== undefined) {\n url.searchParams.append(key, String(value));\n }\n }\n\n return url.toString();\n }\n}\n","import { parseFeedToJson } from '@sesamy/podcast-parser';\n\ninterface RawCategory {\n '@_text': string;\n 'itunes:category'?: RawCategory[];\n}\n\ninterface RawEnclosure {\n '@_url': string;\n '@_type': string;\n '@_length': string;\n}\n\ninterface RawEpisode {\n title?: string;\n guid: { '#text': string };\n link?: string;\n pubDate?: string;\n description?: string;\n 'itunes:duration'?: string | number;\n 'itunes:image'?: { '@_href': string };\n 'itunes:explicit'?: 'yes' | 'no';\n 'itunes:episode'?: number;\n 'itunes:season'?: number;\n 'itunes:episodeType'?: string;\n enclosure?: RawEnclosure[];\n}\n\ninterface RawChannel {\n title: string;\n description?: string;\n link: string;\n language?: string;\n 'itunes:category'?: RawCategory[];\n 'itunes:explicit'?: 'true' | 'false';\n 'itunes:image'?: { '@_href': string };\n 'itunes:author'?: string;\n copyright?: string;\n 'podcast:funding'?: { '@_url': string };\n 'itunes:type'?: string;\n item?: RawEpisode[];\n}\n\ninterface RawPodcastFeed {\n rss?: {\n channel?: RawChannel;\n };\n}\n\nexport class PodcastLoader {\n private readonly FETCH_TIMEOUT_MS = 30000;\n\n /**\n * Fetches and parses a podcast feed from the given URL\n * @param feedUrl - The URL of the podcast feed\n * @returns A Promise resolving to the parsed Podcast\n * @throws Error if the feed is invalid or the fetch fails\n */\n async getPodcastFromFeed(feedUrl: string): Promise<Podcast> {\n this.validateUrl(feedUrl);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.FETCH_TIMEOUT_MS);\n\n try {\n const response = await fetch(feedUrl, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch podcast feed: ${response.status} ${response.statusText}`);\n }\n\n const xmlString = await response.text();\n\n if (!xmlString.trim()) {\n throw new Error('Podcast feed is empty');\n }\n\n const podcastFromXml = (await parseFeedToJson(xmlString)) as RawPodcastFeed;\n const channel = this.extractChannel(podcastFromXml);\n\n this.validateChannel(channel);\n\n return this.mapChannel(channel);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Podcast feed request timeout after ${this.FETCH_TIMEOUT_MS}ms`);\n }\n\n if (error instanceof Error) {\n throw new Error(`Failed to load podcast feed: ${error.message}`);\n }\n\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private validateUrl(feedUrl: string): void {\n try {\n new URL(feedUrl);\n } catch {\n throw new Error(`Invalid feed URL: ${feedUrl}`);\n }\n }\n\n private extractChannel(podcastFromXml: RawPodcastFeed): RawChannel {\n const channel = podcastFromXml?.rss?.channel;\n\n if (!channel) {\n throw new Error('Invalid podcast feed: missing channel data');\n }\n\n return channel;\n }\n\n private validateChannel(channel: RawChannel): void {\n if (!channel.title) {\n throw new Error('Invalid podcast feed: missing required field \"title\"');\n }\n\n if (!channel.link) {\n throw new Error('Invalid podcast feed: missing required field \"link\"');\n }\n }\n\n private mapChannel(channel: RawChannel): Podcast {\n return {\n title: channel.title,\n description: channel.description,\n link: channel.link,\n language: channel.language,\n categories: this.mapCategories(channel['itunes:category']),\n explicit: channel['itunes:explicit'] === 'true',\n imageUrl: channel['itunes:image']?.['@_href'],\n author: channel['itunes:author'],\n copyright: channel['copyright'],\n fundingUrl: channel['podcast:funding']?.['@_url'],\n type: channel['itunes:type'],\n episodes: this.mapEpisodes(channel.item),\n };\n }\n\n private mapCategories(categories: RawCategory[] | undefined): Category[] {\n if (!categories) {\n return [];\n }\n\n return categories.flatMap((category) => [\n { name: category['@_text'] },\n ...(category['itunes:category']?.map((sub) => ({ name: sub['@_text'] })) || []),\n ]);\n }\n\n private mapEpisodes(items: RawEpisode[] | undefined): Episode[] {\n if (!items) {\n return [];\n }\n\n return items.map((item) => ({\n title: item.title,\n enclosure: this.mapEnclosure(item.enclosure),\n guid: item.guid['#text'],\n linkUrl: item.link,\n pubDate: item.pubDate,\n description: item.description,\n durationInSeconds: item['itunes:duration'],\n imageUrl: item['itunes:image']?.['@_href'],\n explicit: item['itunes:explicit'] === 'yes',\n number: item['itunes:episode'],\n season: item['itunes:season'],\n type: item['itunes:episodeType'],\n }));\n }\n\n private mapEnclosure(enclosure: RawEnclosure[] | undefined): Enclosure | undefined {\n if (!enclosure?.[0]) {\n return undefined;\n }\n\n return {\n url: enclosure[0]['@_url'],\n type: enclosure[0]['@_type'],\n length: enclosure[0]['@_length'],\n };\n }\n}\n\nexport interface Category {\n name: string;\n}\n\nexport interface Episode {\n title: string | undefined;\n enclosure: Enclosure | undefined;\n guid: string;\n linkUrl?: string;\n pubDate?: string;\n description?: string;\n durationInSeconds?: string | number | undefined;\n imageUrl?: string;\n explicit?: boolean;\n number?: number;\n season?: number;\n type?: string | undefined;\n}\n\nexport interface Enclosure {\n length: string;\n type: string;\n url: string;\n}\n\nexport interface Podcast {\n title: string;\n description: string | undefined;\n link: string;\n language: string | undefined;\n categories: Category[];\n explicit: boolean;\n imageUrl?: string;\n author?: string;\n copyright?: string;\n fundingUrl?: string;\n type?: string;\n complete?: boolean;\n episodes?: Episode[];\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rapthi/podca-ts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "A TypeScript library for parsing and managing podcast feeds from RSS/iTunes feeds",
|
|
5
5
|
"author": "Thierry Rapillard <rapthi@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"format:check": "prettier --check 'src/**/*.{ts,json}'",
|
|
50
50
|
"type-check": "tsc --noEmit",
|
|
51
51
|
"prepublishOnly": "npm run build && npm run test && npm run lint",
|
|
52
|
-
"ci": "npm run type-check && npm run format:check && npm run lint && npm run test
|
|
52
|
+
"ci": "npm run type-check && npm run format:check && npm run lint && npm run test && npm run build"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@sesamy/podcast-parser": "^1.11.0"
|
|
@@ -69,5 +69,9 @@
|
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": ">=18.0.0"
|
|
72
|
+
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public",
|
|
75
|
+
"provenance": true
|
|
72
76
|
}
|
|
73
77
|
}
|