@@ -3,141 +3,74 @@ import { remark } from "remark";
3
3
import remarkRehype from "remark-rehype" ;
4
4
import rehypeRaw from "rehype-raw" ;
5
5
import rehypeStringify from "rehype-stringify" ;
6
- import { getCollection , type CollectionEntry } from "astro:content" ;
6
+ import { getCollection } from "astro:content" ;
7
+ import { Feed } from "feed" ;
7
8
8
- interface Post {
9
- title : string ;
10
- url : string ;
11
- date : string ;
12
- description : string ;
13
- content : string ;
14
- }
15
-
16
- const FEED_CONFIG = {
17
- title : "Prism Launcher" ,
18
- subtitle :
19
- "An Open Source Minecraft launcher with the ability to manage multiple instances, accounts and mods. Focused on user freedom and free redistributability." ,
20
-
21
- } as const ;
9
+ const DEFAULT_URL = new URL ( "https://prismlauncher.org" ) ;
22
10
23
11
const processor = remark ( )
24
12
. use ( remarkRehype , { allowDangerousHtml : true } )
25
13
. use ( rehypeRaw )
26
14
. use ( rehypeStringify ) ;
27
15
28
- const escapeXml = ( text : string ) : string =>
29
- text
30
- . replace ( / & / g, "&" )
31
- . replace ( / < / g, "<" )
32
- . replace ( / > / g, ">" )
33
- . replace ( / " / g, """ ) ;
16
+ export const GET : APIRoute = async ( {
17
+ site = DEFAULT_URL ,
18
+ url,
19
+ params : { feedName } ,
20
+ } ) => {
21
+ if ( feedName !== "feed" && feedName !== "short" ) {
22
+ return new Response ( null , {
23
+ status : 404 ,
24
+ } ) ;
25
+ }
26
+
27
+ const feed = new Feed ( {
28
+ title : "Prism Launcher" ,
29
+ description :
30
+ "An Open Source Minecraft launcher with the ability to manage multiple instances, accounts and mods. Focused on user freedom and free redistributability." ,
31
+ id : site . toString ( ) ,
32
+ feed : url . toString ( ) ,
33
+ copyright : "AGPL-3.0" ,
34
+ language : "en" ,
35
+ image : `${ site . toString ( ) } /img/favicon.png` ,
36
+ } ) ;
34
37
35
- async function processPost (
36
- post : CollectionEntry < "news" > ,
37
- siteUrl : string ,
38
- ) : Promise < Post > {
39
- const slug = post . data . slug || post . slug ;
38
+ const posts = await getCollection ( "news" , ( { data } ) => ! data . draft ) . then (
39
+ ( posts ) =>
40
+ posts . sort ( ( a , b ) => b . data . date . getTime ( ) - a . data . date . getTime ( ) ) ,
41
+ ) ;
40
42
41
- try {
43
+ for ( const post of posts ) {
44
+ const slug = post . data . slug || post . slug ;
45
+ const link = new URL ( `/news/${ slug } ` , site ) . toString ( ) ;
46
+
47
+ // TODO: use Astro's .render() in the future
42
48
const content = String ( await processor . process ( post . body ) )
43
- . replace ( / h r e f = " \/ ( [ ^ " ] * ) " / g, `href="${ siteUrl } $1"` )
44
- . replace ( / s r c = " \/ ( [ ^ " ] * ) " / g, `src="${ siteUrl } $1"` )
45
- . replace ( / h r e f = " # ( [ ^ " ] + ) " / g, `href="${ siteUrl } news/ ${ slug } /#$1"` ) ;
49
+ . replace ( / h r e f = " \/ ( [ ^ " ] * ) " / g, `href="${ site } $1"` )
50
+ . replace ( / s r c = " \/ ( [ ^ " ] * ) " / g, `src="${ site } $1"` )
51
+ . replace ( / h r e f = " # ( [ ^ " ] + ) " / g, `href="${ link } /#$1"` ) ;
46
52
47
- return {
53
+ feed . addItem ( {
48
54
title : post . data . title ,
49
- url : `${ siteUrl } news/${ slug } /` ,
50
- date : post . data . date . toISOString ( ) ,
55
+ id : link ,
56
+ link,
57
+ date : post . data . date ,
51
58
description : post . data . description ,
52
- content,
53
- } ;
54
- } catch ( error ) {
55
- console . warn ( `Failed to process post "${ post . data . title } ":` , error ) ;
56
- return {
57
- title : post . data . title ,
58
- url : `${ siteUrl } news/${ slug } /` ,
59
- date : post . data . date . toISOString ( ) ,
60
- description : post . data . description || "No description available" ,
61
- content : `<p>${ escapeXml ( post . data . description || "No description available" ) } </p>` ,
62
- } ;
63
- }
64
- }
65
-
66
- function generateFeed (
67
- entries : Post [ ] ,
68
- siteUrl : string ,
69
- updated : string ,
70
- short : boolean ,
71
- ) : string {
72
- return `<?xml version="1.0" encoding="utf-8"?>
73
- <feed xmlns="http://www.w3.org/2005/Atom">
74
- <title>${ FEED_CONFIG . title } </title>
75
- <subtitle>${ FEED_CONFIG . subtitle } </subtitle>
76
- <link href="${ siteUrl } feed/feed.xml" rel="self"/>
77
- <link href="${ siteUrl } "/>
78
- <updated>${ updated } </updated>
79
- <id>${ siteUrl } </id>
80
- <author>
81
- <name>${ FEED_CONFIG . title } </name>
82
- <email>${ FEED_CONFIG . email } </email>
83
- </author>
84
- ${ entries
85
- . map ( ( entry ) => {
86
- let content = short
87
- ? `<content type="string">${ escapeXml ( entry . description ) } </content>`
88
- : `<content type="html">${ escapeXml ( entry . content ) } </content>` ;
89
- return ` <entry>
90
- <title>${ escapeXml ( entry . title ) } </title>
91
- <link href="${ entry . url } "/>
92
- <updated>${ entry . date } </updated>
93
- <id>${ entry . url } </id>
94
- ${ content }
95
- </entry>` ;
96
- } )
97
- . join ( "\n" ) }
98
- </feed>` ;
99
- }
100
-
101
- export const GET : APIRoute = async ( { site, params : { feedName } } ) => {
102
- if ( feedName !== "feed" && feedName !== "short" ) {
103
- return new Response ( null , {
104
- status : 404 ,
59
+ content : feedName === "short" ? undefined : content ,
60
+ author : [
61
+ {
62
+ name : "Prism Launcher Team" ,
63
+ } ,
64
+ ] ,
105
65
} ) ;
106
66
}
107
- try {
108
- const posts = ( await getCollection ( "news" , ( { data } ) => ! data . draft ) ) . sort (
109
- ( a , b ) => b . data . date . getTime ( ) - a . data . date . getTime ( ) ,
110
- ) ;
111
- const siteUrl =
112
- ( site ?. toString ( ) || "https://prismlauncher.org/" ) . replace ( / \/ $ / , "" ) +
113
- "/" ;
114
67
115
- return new Response (
116
- generateFeed (
117
- await Promise . all ( posts . map ( ( post ) => processPost ( post , siteUrl ) ) ) ,
118
- siteUrl ,
119
- ( posts [ 0 ] ?. data . date || new Date ( ) ) . toISOString ( ) ,
120
- feedName === "short" ,
121
- ) ,
122
- {
123
- headers : {
124
- "Content-Type" : "application/atom+xml; charset=utf-8" ,
125
- "Cache-Control" : "public, max-age=3600" ,
126
- } ,
127
- } ,
128
- ) ;
129
- } catch ( error ) {
130
- console . error ( "Feed generation failed:" , error ) ;
131
- return new Response (
132
- import . meta. env . DEV
133
- ? `Feed error: ${ error instanceof Error ? error . message : "Unknown error" } `
134
- : "Internal Server Error" ,
135
- {
136
- status : 500 ,
137
- headers : { "Content-Type" : "text/plain" } ,
138
- } ,
139
- ) ;
140
- }
68
+ return new Response ( feed . atom1 ( ) , {
69
+ headers : {
70
+ "Content-Type" : "application/atom+xml; charset=utf-8" ,
71
+ "Cache-Control" : "public, max-age=3600" ,
72
+ } ,
73
+ } ) ;
141
74
} ;
142
75
143
76
export const getStaticPaths = ( ) => {
0 commit comments