11import * as parser from "xml2js" ;
2- import { isRawFeed , isRawFeedChannel , isValidSubstackFeed } from "./typeguards" ;
3- import { RawFeedChannel , RawItem , SubstackItem } from "./types" ;
2+ import {
3+ isRawFeed ,
4+ isRawFeedChannel ,
5+ isRawGoodreadsFeed ,
6+ isRawGoodreadsFeedChannel ,
7+ isRawGoodreadsFeedRSS ,
8+ isRawGoodreadsItem ,
9+ isValidClientSideFeed ,
10+ isValidGoodreadsClientSideFeed ,
11+ } from "./typeguards" ;
12+ import {
13+ GoodreadsItem ,
14+ RawFeedChannel ,
15+ RawGoodreadsItem ,
16+ RawItem ,
17+ SubstackItem ,
18+ } from "./types" ;
419
520const CORS_PROXY = "https://api.allorigins.win/get?url=" ;
621const isBrowser = typeof document !== "undefined" ;
@@ -37,7 +52,7 @@ const transformRawItem = (item: RawItem): SubstackItem => {
3752 } ;
3853} ;
3954
40- // Public API
55+ // Substack Public API
4156
4257export const getSubstackFeed = async (
4358 feedUrl : string ,
@@ -50,7 +65,7 @@ export const getSubstackFeed = async (
5065 return parseXML ( rawXML , callback ) ;
5166 }
5267 // NOTE: client side call
53- if ( ! isValidSubstackFeed ( rawXML ) )
68+ if ( ! isValidClientSideFeed ( rawXML ) )
5469 throw new Error ( "Error occurred fetching Feed from Substack" ) ;
5570 await parseXML ( rawXML . contents , callback ) ;
5671} ;
@@ -71,3 +86,58 @@ export const getPosts = (channels: RawFeedChannel[]) => {
7186 const channel = channels [ 0 ] ;
7287 return channel . item . map ( transformRawItem ) ;
7388} ;
89+
90+ // Goodreads Feed Parser
91+ // Utils
92+ const transformRawGoodreadsItem = ( item : RawGoodreadsItem ) : GoodreadsItem => {
93+ if ( ! isRawGoodreadsItem ( item ) )
94+ throw new Error ( "Goodreads item is not in the correct format" ) ;
95+ return {
96+ title : item . title [ 0 ] ,
97+ link : item . link [ 0 ] ,
98+ book_image_url : item [ "book_image_url" ] [ 0 ] ,
99+ author_name : item [ "author_name" ] [ 0 ] ,
100+ book_description : item [ "book_description" ] [ 0 ] ,
101+ } ;
102+ } ;
103+ // Internal API
104+ const getRawXMLGoodreadsFeed = async ( feedUrl : string ) => {
105+ try {
106+ const path = isBrowser
107+ ? `${ CORS_PROXY } ${ encodeURIComponent ( feedUrl ) } `
108+ : feedUrl ;
109+ const promise = await fetch ( path ) ;
110+ if ( promise . ok ) return isBrowser ? promise . json ( ) : promise . text ( ) ;
111+ } catch ( e ) {
112+ throw new Error ( "Error occurred fetching Feed from Goodreads" ) ;
113+ }
114+ } ;
115+
116+ // Public API
117+ export const getGoodreadsFeed = async (
118+ feedUrl : string ,
119+ /* eslint-disable @typescript-eslint/no-explicit-any */
120+ callback ?: ( err : Error | null , result : unknown ) => void ,
121+ ) : Promise < unknown > => {
122+ const rawXML = await getRawXMLGoodreadsFeed ( feedUrl ) ;
123+ // NOTE: server side call
124+ if ( ! isBrowser ) {
125+ return parseXML ( rawXML , callback ) ;
126+ }
127+ // NOTE: client side call
128+ if ( ! isValidGoodreadsClientSideFeed ( rawXML ) )
129+ throw new Error ( "Error occurred fetching Feed from Goodreads" ) ;
130+ await parseXML ( rawXML . contents , callback ) ;
131+ } ;
132+ export const getGoodreadsFeedItems = ( rawFeed : unknown ) : GoodreadsItem [ ] => {
133+ if ( ! isRawGoodreadsFeed ( rawFeed ) )
134+ throw new Error ( "Goodreads feed is not in the correct format" ) ;
135+ if ( ! isRawGoodreadsFeedRSS ( rawFeed . rss ) )
136+ throw new Error ( "Goodreads RSS feed is not in the correct format" ) ;
137+ const channels = rawFeed . rss . channel . filter ( isRawGoodreadsFeedChannel ) ;
138+ if ( channels . length === 0 )
139+ throw new Error ( "Goodreads feed does not contain any channels" ) ;
140+ const channel = channels [ 0 ] ;
141+ if ( ! Array . isArray ( channel . item ) ) return [ ] ;
142+ return channel . item . filter ( isRawGoodreadsItem ) . map ( transformRawGoodreadsItem ) ;
143+ } ;
0 commit comments