1
1
import { parseString } from "xml2js"
2
2
3
- import type {
4
- AtomElement ,
5
- AtomResult ,
6
- RSSChannel ,
7
- RSSItem ,
8
- RSSResult ,
9
- } from "../types"
3
+ import type { AtomElement , AtomResult , RSSItem , RSSResult } from "../types"
10
4
import { isValidDate } from "../utils/date"
11
5
12
6
/**
@@ -18,108 +12,118 @@ export const fetchRSS = async (xmlUrl: string | string[]) => {
18
12
const urls = Array . isArray ( xmlUrl ) ? xmlUrl : [ xmlUrl ]
19
13
const allItems : RSSItem [ ] [ ] = [ ]
20
14
for ( const url of urls ) {
21
- const response = ( await fetchXml ( url ) ) as RSSResult | AtomResult
15
+ try {
16
+ const response = ( await fetchXml ( url ) ) as RSSResult | AtomResult
22
17
23
- if ( "rss" in response ) {
24
- const [ mainChannel ] = response . rss . channel as RSSChannel [ ]
25
- const [ source ] = mainChannel . title
26
- const [ sourceUrl ] = mainChannel . link
27
- const channelImage = mainChannel . image ? mainChannel . image [ 0 ] . url [ 0 ] : ""
18
+ if ( "rss" in response ) {
19
+ const [ mainChannel ] = response . rss . channel
20
+ const [ source ] = mainChannel . title
21
+ const [ sourceUrl ] = mainChannel . link
22
+ const channelImage = mainChannel . image
23
+ ? mainChannel . image [ 0 ] . url [ 0 ]
24
+ : ""
28
25
29
- const parsedRssItems = mainChannel . item
30
- // Filter out items with invalid dates
31
- . filter ( ( item ) => {
32
- if ( ! item . pubDate ) return false
33
- const [ pubDate ] = item . pubDate
34
- return isValidDate ( pubDate )
35
- } )
36
- // Sort by pubDate (most recent is first in array
37
- . sort ( ( a , b ) => {
38
- const dateA = new Date ( a . pubDate [ 0 ] )
39
- const dateB = new Date ( b . pubDate [ 0 ] )
40
- return dateB . getTime ( ) - dateA . getTime ( )
41
- } )
42
- // Map to RSSItem object
43
- . map ( ( item ) => {
44
- const getImgSrc = ( ) => {
45
- if ( url . includes ( "medium.com/feed/" ) )
46
- return item [ "content:encoded" ] ?. [ 0 ] . match (
47
- / h t t p s ? : \/ \/ [ ^ " ] * ?\. ( j p e ? g | p n g | w e b p ) / g
48
- )
49
- if ( item . enclosure ) return item . enclosure [ 0 ] . $ . url
50
- if ( item [ "media:content" ] ) return item [ "media:content" ] [ 0 ] . $ . url
51
- return channelImage
52
- }
53
- return {
54
- pubDate : item . pubDate [ 0 ] ,
55
- title : item . title [ 0 ] ,
56
- link : item . link [ 0 ] ,
57
- imgSrc : getImgSrc ( ) ,
58
- source,
59
- sourceUrl,
60
- sourceFeedUrl : url ,
61
- } as RSSItem
62
- } )
26
+ const parsedRssItems = mainChannel . item
27
+ // Filter out items with invalid dates
28
+ . filter ( ( item ) => {
29
+ if ( ! item . pubDate ) return false
30
+ const [ pubDate ] = item . pubDate
31
+ return isValidDate ( pubDate )
32
+ } )
33
+ // Sort by pubDate (most recent is first in array
34
+ . sort ( ( a , b ) => {
35
+ const dateA = new Date ( a . pubDate [ 0 ] )
36
+ const dateB = new Date ( b . pubDate [ 0 ] )
37
+ return dateB . getTime ( ) - dateA . getTime ( )
38
+ } )
39
+ // Map to RSSItem object
40
+ . map ( ( item ) => {
41
+ const getImgSrc = ( ) => {
42
+ if ( url . includes ( "medium.com/feed/" ) )
43
+ return item [ "content:encoded" ] ?. [ 0 ] . match (
44
+ / h t t p s ? : \/ \/ [ ^ " ] * ?\. ( j p e ? g | p n g | w e b p ) / g
45
+ )
46
+ if ( item . enclosure ) return item . enclosure [ 0 ] . $ . url
47
+ if ( item [ "media:content" ] ) return item [ "media:content" ] [ 0 ] . $ . url
48
+ return channelImage
49
+ }
50
+ return {
51
+ pubDate : item . pubDate [ 0 ] ,
52
+ title : item . title [ 0 ] ,
53
+ link : item . link [ 0 ] ,
54
+ imgSrc : getImgSrc ( ) ,
55
+ source,
56
+ sourceUrl,
57
+ sourceFeedUrl : url ,
58
+ }
59
+ } )
63
60
64
- allItems . push ( parsedRssItems )
65
- } else if ( "feed" in response ) {
66
- const [ source ] = response . feed . title
67
- const [ sourceUrl ] = response . feed . id
68
- const feedImage = response . feed . icon ?. [ 0 ]
61
+ allItems . push ( parsedRssItems )
62
+ } else if ( "feed" in response ) {
63
+ const [ source ] = response . feed . title
64
+ const [ sourceUrl ] = response . feed . id
65
+ const feedImage = response . feed . icon ?. [ 0 ]
69
66
70
- const parsedAtomItems = response . feed . entry
71
- // Filter out items with invalid dates
72
- . filter ( ( entry ) => {
73
- if ( ! entry . updated ) return false
74
- const [ published ] = entry . updated
75
- return isValidDate ( published )
76
- } )
77
- // Sort by published (most recent is first in array
78
- . sort ( ( a , b ) => {
79
- const dateA = new Date ( a . updated [ 0 ] )
80
- const dateB = new Date ( b . updated [ 0 ] )
81
- return dateB . getTime ( ) - dateA . getTime ( )
82
- } )
83
- // Map to RSSItem object
84
- . map ( ( entry ) => {
85
- const getString = ( el ?: AtomElement [ ] ) : string => {
86
- if ( ! el ) return ""
87
- const [ firstEl ] = el
88
- if ( typeof firstEl === "string" ) return firstEl
89
- return firstEl . _ || ""
90
- }
91
- const getHref = ( ) : string => {
92
- if ( ! entry . link ) {
93
- console . warn ( `No link found for RSS url: ${ url } ` )
94
- return ""
67
+ const parsedAtomItems = response . feed . entry
68
+ // Filter out items with invalid dates
69
+ . filter ( ( entry ) => {
70
+ if ( ! entry . updated ) return false
71
+ const [ published ] = entry . updated
72
+ return isValidDate ( published )
73
+ } )
74
+ // Sort by published (most recent is first in array
75
+ . sort ( ( a , b ) => {
76
+ const dateA = new Date ( a . updated [ 0 ] )
77
+ const dateB = new Date ( b . updated [ 0 ] )
78
+ return dateB . getTime ( ) - dateA . getTime ( )
79
+ } )
80
+ // Map to RSSItem object
81
+ . map ( ( entry ) => {
82
+ const getString = ( el ?: AtomElement [ ] ) : string => {
83
+ if ( ! el ) return ""
84
+ const [ firstEl ] = el
85
+ if ( typeof firstEl === "string" ) return firstEl
86
+ return firstEl . _ || ""
87
+ }
88
+ const getHref = ( ) : string => {
89
+ if ( ! entry . link ) {
90
+ console . warn ( `No link found for RSS url: ${ url } ` )
91
+ return ""
92
+ }
93
+ const link = entry . link [ 0 ]
94
+ if ( typeof link === "string" ) return link
95
+ return link . $ . href || ""
96
+ }
97
+ const getImgSrc = ( ) : string => {
98
+ const imgRegEx = / h t t p s ? : \/ \/ [ ^ " ] * ?\. ( j p e ? g | p n g | w e b p ) / g
99
+ const contentMatch = getString ( entry . content ) . match ( imgRegEx )
100
+ if ( contentMatch ) return contentMatch [ 0 ]
101
+ const summaryMatch = getString ( entry . summary ) . match ( imgRegEx )
102
+ if ( summaryMatch ) return summaryMatch [ 0 ]
103
+ return feedImage || ""
95
104
}
96
- const link = entry . link [ 0 ]
97
- if ( typeof link === "string" ) return link
98
- return link . $ . href || ""
99
- }
100
- const getImgSrc = ( ) : string => {
101
- const imgRegEx = / h t t p s ? : \/ \/ [ ^ " ] * ?\. ( j p e ? g | p n g | w e b p ) / g
102
- const contentMatch = getString ( entry . content ) . match ( imgRegEx )
103
- if ( contentMatch ) return contentMatch [ 0 ]
104
- const summaryMatch = getString ( entry . summary ) . match ( imgRegEx )
105
- if ( summaryMatch ) return summaryMatch [ 0 ]
106
- return feedImage || ""
107
- }
108
- return {
109
- pubDate : entry . updated [ 0 ] ,
110
- title : getString ( entry . title ) ,
111
- link : getHref ( ) ,
112
- imgSrc : getImgSrc ( ) ,
113
- source,
114
- sourceUrl,
115
- sourceFeedUrl : url ,
116
- } as RSSItem
117
- } )
105
+ return {
106
+ pubDate : entry . updated [ 0 ] ,
107
+ title : getString ( entry . title ) ,
108
+ link : getHref ( ) ,
109
+ imgSrc : getImgSrc ( ) ,
110
+ source,
111
+ sourceUrl,
112
+ sourceFeedUrl : url ,
113
+ }
114
+ } )
118
115
119
- allItems . push ( parsedAtomItems )
116
+ allItems . push ( parsedAtomItems )
117
+ }
118
+ } catch ( error ) {
119
+ console . error (
120
+ `Failed to fetch or parse RSS feed from ${ url } :` ,
121
+ error instanceof Error ? error . message : error
122
+ )
123
+ continue
120
124
}
121
125
}
122
- return allItems as RSSItem [ ] [ ]
126
+ return allItems
123
127
}
124
128
125
129
/**
@@ -132,14 +136,11 @@ export const fetchXml = async (url: string) => {
132
136
try {
133
137
const response = await fetch ( url )
134
138
const xml = await response . text ( )
135
- let returnObject : Record < string , unknown > = { }
136
- parseString ( xml , ( err , result ) => {
137
- if ( err ) {
138
- throw err // Throw the error to be caught by the outer try-catch
139
- }
140
- returnObject = result
139
+ return await new Promise < Record < string , unknown > > ( ( resolve , reject ) => {
140
+ parseString ( xml , ( err , result ) => {
141
+ err ? reject ( err ) : resolve ( result )
142
+ } )
141
143
} )
142
- return returnObject
143
144
} catch ( error ) {
144
145
console . error ( "Error fetching or parsing XML:" , url , error )
145
146
throw error
0 commit comments