@@ -5,102 +5,64 @@ export interface BlogPost {
55 imageUrl ?: string ;
66}
77
8- function extractTagContent ( xml : string , tag : string ) : string | undefined {
9- const regex = new RegExp ( `<${ tag } [^>]*>([\\s\\S]*?)<\\/${ tag } >` , "i" ) ;
10- const match = xml . match ( regex ) ;
11-
12- if ( ! match ?. [ 1 ] ) return undefined ;
13-
14- const value = match [ 1 ] . trim ( ) ;
15-
16- if ( value . startsWith ( "<![CDATA[" ) && value . endsWith ( "]]>" ) ) {
17- return value . slice ( 9 , - 3 ) . trim ( ) ;
18- }
19-
20- return value ;
8+ interface WordPressPost {
9+ id : number ;
10+ date : string ;
11+ link : string ;
12+ title : {
13+ rendered : string ;
14+ } ;
15+ excerpt : {
16+ rendered : string ;
17+ protected : boolean ;
18+ } ;
19+ jetpack_featured_media_url ?: string ;
2120}
2221
23- function extractImageUrl ( xml : string ) : string | undefined {
24- // First, try to find image in <figure class="wp-block-image"> or similar figure tags
25- const figureMatch = xml . match (
26- / < f i g u r e [ ^ > ] * c l a s s = [ " ' ] [ ^ " ' ] * w p - b l o c k - i m a g e [ ^ " ' ] * [ " ' ] [ ^ > ] * > ( [ \s \S ] * ?) < \/ f i g u r e > / i
27- ) ;
28- if ( figureMatch ?. [ 1 ] ) {
29- const imgMatch = figureMatch [ 1 ] . match ( / < i m g [ ^ > ] * s r c = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] [ ^ > ] * > / i) ;
30- if ( imgMatch ?. [ 1 ] ) {
31- return imgMatch [ 1 ] ;
32- }
33- }
34-
35- // Fallback: try any figure tag
36- const anyFigureMatch = xml . match (
37- / < f i g u r e [ ^ > ] * > ( [ \s \S ] * ?) < \/ f i g u r e > / i
38- ) ;
39- if ( anyFigureMatch ?. [ 1 ] ) {
40- const imgMatch = anyFigureMatch [ 1 ] . match ( / < i m g [ ^ > ] * s r c = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] [ ^ > ] * > / i) ;
41- if ( imgMatch ?. [ 1 ] ) {
42- return imgMatch [ 1 ] ;
43- }
44- }
45-
46- // Fallback: try media:content
47- const mediaMatch = xml . match (
48- / < m e d i a : c o n t e n t [ ^ > ] * u r l = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] [ ^ > ] * > / i
49- ) ;
50- if ( mediaMatch ?. [ 1 ] ) {
51- return mediaMatch [ 1 ] ;
52- }
53-
54- // Fallback: try enclosure
55- const enclosureMatch = xml . match (
56- / < e n c l o s u r e [ ^ > ] * u r l = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] [ ^ > ] * > / i
57- ) ;
58- if ( enclosureMatch ?. [ 1 ] ) {
59- return enclosureMatch [ 1 ] ;
60- }
61-
62- // Fallback: try content:encoded
63- const contentMatch = xml . match (
64- / < c o n t e n t : e n c o d e d [ ^ > ] * > ( [ \s \S ] * ?) < \/ c o n t e n t : e n c o d e d > / i
65- ) ;
66- const content = contentMatch ?. [ 1 ] ;
67-
68- if ( content ) {
69- const imgMatch = content . match ( / < i m g [ ^ > ] * s r c = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] [ ^ > ] * > / i) ;
70- if ( imgMatch ?. [ 1 ] ) {
71- return imgMatch [ 1 ] ;
72- }
73- }
74-
75- const covers = [
76- "/images/covers/1.jpg" ,
77- "/images/covers/2.jpg" ,
78- "/images/covers/3.jpg" ,
79- "/images/covers/4.jpg" ,
80- "/images/covers/5.jpg" ,
81- ] ;
82-
83- const randomIndex = Math . floor ( Math . random ( ) * covers . length ) ;
84- return covers [ randomIndex ] ;
22+ const WORDPRESS_POSTS_ENDPOINT =
23+ "https://rustfs.dev/wp-json/wp/v2/posts?per_page=5&orderby=date&order=desc&_fields=id,date,title,link,excerpt,jetpack_featured_media_url" ;
24+
25+ const BLOG_API_APPLICATION_USERNAME =
26+ process . env . BLOG_API_APPLICATION_USERNAME ;
27+ const BLOG_API_APPLICATION_PASSWORD =
28+ process . env . BLOG_API_APPLICATION_PASSWORD ;
29+
30+ const FALLBACK_COVERS = [
31+ "/images/covers/1.jpg" ,
32+ "/images/covers/2.jpg" ,
33+ "/images/covers/3.jpg" ,
34+ "/images/covers/4.jpg" ,
35+ "/images/covers/5.jpg" ,
36+ ] ;
37+
38+ function getRandomCover ( ) : string {
39+ const randomIndex = Math . floor ( Math . random ( ) * FALLBACK_COVERS . length ) ;
40+ return FALLBACK_COVERS [ randomIndex ] ;
8541}
8642
8743export async function getLatestBlogPosts ( limit = 3 ) : Promise < BlogPost [ ] > {
8844 try {
89- const response = await fetch ( "https://rustfs.dev/feed" , {
45+ const headers : Record < string , string > = {
46+ "User-Agent" :
47+ "Mozilla/5.0 (compatible; RustFSSiteBot/1.0; +https://rustfs.com)" ,
48+ Accept : "application/json" ,
49+ } ;
50+
51+ if ( BLOG_API_APPLICATION_USERNAME && BLOG_API_APPLICATION_PASSWORD ) {
52+ const basicToken = Buffer . from (
53+ `${ BLOG_API_APPLICATION_USERNAME } :${ BLOG_API_APPLICATION_PASSWORD } `
54+ ) . toString ( "base64" ) ;
55+ headers . Authorization = `Basic ${ basicToken } ` ;
56+ }
57+
58+ const response = await fetch ( WORDPRESS_POSTS_ENDPOINT , {
9059 next : { revalidate : 1800 } ,
91- // Some WordPress/security setups block default Node/undici user agents from CI/CD.
92- // Use a browser-like User-Agent and explicit Accept header to reduce false positives.
93- headers : {
94- "User-Agent" :
95- "Mozilla/5.0 (compatible; RustFSSiteBot/1.0; +https://rustfs.com)" ,
96- Accept :
97- "application/rss+xml, application/xml;q=0.9, text/xml;q=0.8, */*;q=0.7" ,
98- } ,
60+ headers,
9961 } ) ;
10062
10163 if ( ! response . ok ) {
10264 console . error (
103- "[RustFS Blog] Failed to fetch feed " ,
65+ "[RustFS Blog] Failed to fetch posts from WordPress API " ,
10466 JSON . stringify ( {
10567 status : response . status ,
10668 statusText : response . statusText ,
@@ -109,45 +71,44 @@ export async function getLatestBlogPosts(limit = 3): Promise<BlogPost[]> {
10971 ) ;
11072
11173 throw new Error (
112- `[RustFS Blog] Failed to fetch feed : ${ response . status } ${ response . statusText } `
74+ `[RustFS Blog] Failed to fetch posts : ${ response . status } ${ response . statusText } `
11375 ) ;
11476 }
11577
116- const xml = await response . text ( ) ;
78+ const data = ( await response . json ( ) ) as WordPressPost [ ] ;
11779
11880 console . log (
119- "[RustFS Blog] Feed fetched successfully" ,
81+ "[RustFS Blog] Posts fetched successfully from WordPress API " ,
12082 JSON . stringify ( {
12183 status : response . status ,
122- length : xml . length ,
84+ totalItems : Array . isArray ( data ) ? data . length : 0 ,
12385 } )
12486 ) ;
12587
126- const items = xml . match ( / < i t e m [ \s \S ] * ? < \/ i t e m > / gi ) ?? [ ] ;
88+ const safeData = Array . isArray ( data ) ? data : [ ] ;
12789
128- console . log (
129- "[RustFS Blog] Parsed items from feed" ,
130- JSON . stringify ( {
131- totalItems : items . length ,
132- limit,
133- } )
134- ) ;
90+ const posts : BlogPost [ ] = safeData . slice ( 0 , limit ) . map ( ( item ) => {
91+ const title =
92+ ( item . title && typeof item . title . rendered === "string"
93+ ? item . title . rendered
94+ : ""
95+ ) . trim ( ) || "Untitled" ;
13596
136- const posts : BlogPost [ ] = items . slice ( 0 , limit ) . map ( ( itemXml ) => {
137- const title = extractTagContent ( itemXml , "title" ) ?? "Untitled" ;
138- const link = extractTagContent ( itemXml , "link" ) ?? "#" ;
139- const pubDateRaw = extractTagContent ( itemXml , "pubDate" ) ;
140- const imageUrl = extractImageUrl ( itemXml ) ;
97+ const link = item . link || "#" ;
14198
14299 let pubDate : string | undefined ;
143-
144- if ( pubDateRaw ) {
145- const parsed = new Date ( pubDateRaw ) ;
100+ if ( item . date ) {
101+ const parsed = new Date ( item . date ) ;
146102 if ( ! Number . isNaN ( parsed . getTime ( ) ) ) {
147103 pubDate = parsed . toISOString ( ) ;
148104 }
149105 }
150106
107+ const imageUrl =
108+ ( item . jetpack_featured_media_url &&
109+ item . jetpack_featured_media_url . trim ( ) ) ||
110+ getRandomCover ( ) ;
111+
151112 return { title, link, pubDate, imageUrl } ;
152113 } ) ;
153114
0 commit comments