import { NextResponse } from "next/server"
import fs from "fs"
import path from "path"
interface ChangelogEntry {
version: string
date: string
content: string
url: string
title: string
}
function escapeXml(text: string): string {
return text
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
function formatContentForRSS(content: string): string {
return (
content
.replace(/https:\/\/macrimi\.github\.io\/ProxMenux/g, "https://proxmenux.com")
.replace(/`([^`]+)`/g, "$1")
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, url) => {
let absoluteUrl = url
if (url.startsWith("/")) {
absoluteUrl = `https://proxmenux.com${url}`
} else if (!url.startsWith("http://") && !url.startsWith("https://")) {
absoluteUrl = `https://proxmenux.com/${url}`
}
return `
`
})
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
.replace(/^### (.+)$/gm, "$1
")
.replace(/\*\*(.*?)\*\*/g, "$1")
.replace(/```[\s\S]*?```/g, (match) => {
const code = match.replace(/```/g, "").trim()
return `${code}
`
})
.replace(/^- (.+)$/gm, "$1")
.replace(/(.*?<\/li>\s*)+/g, (match) => ``)
.replace(/^---$/gm, '
')
.replace(/\n/g, "
")
.replace(/\s+/g, " ")
.trim()
)
}
async function parseChangelog(): Promise {
try {
const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md")
if (!fs.existsSync(changelogPath)) {
return []
}
const fileContents = fs.readFileSync(changelogPath, "utf8")
const entries: ChangelogEntry[] = []
const lines = fileContents.split("\n")
let currentEntry: Partial | null = null
let contentLines: string[] = []
for (const line of lines) {
const versionMatch = line.match(/^##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
const dateMatch = line.match(/^##\s+(\d{4}-\d{2}-\d{2})$/)
if (versionMatch || dateMatch) {
if (currentEntry && contentLines.length > 0) {
const rawContent = contentLines.join("\n").trim()
currentEntry.content = formatContentForRSS(rawContent)
if (currentEntry.version && currentEntry.date && currentEntry.title) {
entries.push(currentEntry as ChangelogEntry)
}
}
if (versionMatch) {
const version = versionMatch[1]
const date = versionMatch[2]
currentEntry = {
version,
date,
url: `https://proxmenux.com/changelog#${version}`,
title: `ProxMenux ${version}`,
}
} else if (dateMatch) {
const date = dateMatch[1]
currentEntry = {
version: date,
date,
url: `https://proxmenux.com/changelog#${date}`,
title: `ProxMenux Update ${date}`,
}
}
contentLines = []
} else if (currentEntry && line.trim()) {
if (contentLines.length > 0 || line.trim() !== "") {
contentLines.push(line)
}
}
}
if (currentEntry && contentLines.length > 0) {
const rawContent = contentLines.join("\n").trim()
currentEntry.content = formatContentForRSS(rawContent)
if (currentEntry.version && currentEntry.date && currentEntry.title) {
entries.push(currentEntry as ChangelogEntry)
}
}
return entries.slice(0, 20)
} catch (error) {
console.error("Error parsing changelog:", error)
return []
}
}
export async function GET() {
const entries = await parseChangelog()
const siteUrl = "https://proxmenux.com"
const rssXml = `
ProxMenux Changelog
Latest updates and changes in ProxMenux - An Interactive Menu for Proxmox VE Management
${siteUrl}/changelog
en-US
${new Date().toUTCString()}
ProxMenux RSS Generator
60
${entries
.map(
(entry) => `
-
${escapeXml(entry.title)}
${escapeXml(entry.content.replace(/<[^>]*>/g, '').substring(0, 200))}...
${entry.url}
${entry.url}
${new Date(entry.date).toUTCString()}
Changelog
`,
)
.join("")}
`
return new NextResponse(rssXml, {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
"Cache-Control": "public, max-age=3600, s-maxage=3600",
},
})
}