Generate RSS feed from markdown news (#282)

* generate rss feed from markdown news

* Fix comments

* also add auto discovery to news sub page, add feed butto next to news title
This commit is contained in:
Thomas Ritter 2023-12-18 23:24:33 +01:00 committed by GitHub
parent e83637e1f0
commit 24e1b3e06e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 152 additions and 4 deletions

View file

@ -38,6 +38,7 @@ COPY tsconfig.json \
webpack.config.js \
.babelrc \
generate_translations.mjs \
generate_rss_feed.mjs \
tailwind.config.js \
./

63
generate_rss_feed.mjs Normal file
View file

@ -0,0 +1,63 @@
import fs from "fs-extra";
import MarkdownIt from "markdown-it";
import { Builder } from "xml2js";
async function generateRSSFeed() {
const md = new MarkdownIt();
const posts = [];
const files = await fs.readdir("./src/assets/news"); // a folder with markdown files
const newestFirstFiles = Array.from(files).reverse();
for (const file of newestFirstFiles) {
if (file.endsWith(".md")) {
const content = await fs.readFile(`./src/assets/news/${file}`, "utf8");
const lines = content.split("\n");
const title = lines[0].replace(/^# /, "");
const dateFromFileName = file.slice(0, 10);
const date = new Date(dateFromFileName).toUTCString();
const htmlContent = md.render(content);
const link = `https://join-lemmy.org/news/${file
.replace(".md", "")
.replaceAll(" ", "_")}`;
posts.push({ title, date, content: htmlContent, link });
}
}
const rss = {
rss: {
$: {
version: "2.0",
"xmlns:atom": "http://www.w3.org/2005/Atom",
},
channel: [
{
"atom:link": {
$: {
href: "https://join-lemmy.org/feed.xml",
rel: "self",
type: "application/rss+xml",
},
},
title: "join-lemmy.org News",
link: "https://join-lemmy.org/",
description: "News about Lemmy, a link aggregator for the fediverse.",
item: posts.map(post => ({
title: post.title,
link: post.link,
description: post.content,
pubDate: post.date,
guid: post.link,
})),
},
],
},
};
const builder = new Builder();
const xml = builder.buildObject(rss);
await fs.writeFile("./dist/feed.xml", xml);
}
generateRSSFeed().then(() => console.log("RSS feed generated successfully."));

View file

@ -10,8 +10,9 @@
"crawl": "node crawl.mjs",
"update-donations": "node update_donations.mjs",
"lint": "node generate_translations.mjs && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"",
"prebuild:dev": "yarn clean && node generate_translations.mjs && yarn tailwind",
"prebuild:prod": "yarn clean && node generate_translations.mjs && yarn tailwind",
"prebuild:dev": "yarn clean && node generate_translations.mjs && yarn tailwind && yarn generate_rss_feed",
"prebuild:prod": "yarn clean && node generate_translations.mjs && yarn tailwind && yarn generate_rss_feed",
"generate_rss_feed": "node generate_rss_feed.mjs",
"tailwind": "tailwindcss -i ./src/style.css -o ./dist/styles/styles.css --minify",
"prepare": "husky install",
"start": "yarn build:dev --watch & yarn tailwind --watch"
@ -35,6 +36,7 @@
"countries-list": "^3.0.6",
"daisyui": "^3.9.4",
"express": "~4.18.2",
"fs-extra": "^11.2.0",
"i18next": "^23.6.0",
"inferno": "^8.2.2",
"inferno-create-element": "^8.2.2",
@ -50,7 +52,8 @@
"tailwindcss": "^3.3.5",
"webpack": "5.89.0",
"webpack-cli": "^5.1.4",
"webpack-node-externals": "^3.0.0"
"webpack-node-externals": "^3.0.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/express": "^4.17.20",

17
src/assets/images/rss.svg Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 455.731 455.731" xml:space="preserve">
<g>
<rect x="0" y="0" style="fill:#F78422;" width="455.731" height="455.731"/>
<g>
<path style="fill:#FFFFFF;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348
c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348
C391.986,303.103,357.971,220.923,296.208,159.16z"/>
<path style="fill:#FFFFFF;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348
C282.429,270.196,184.507,172.273,64.143,172.273z"/>
<circle style="fill:#FFFFFF;" cx="109.833" cy="346.26" r="46.088"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 898 B

View file

@ -17,6 +17,7 @@ server.use(express.urlencoded({ extended: false }) as RequestHandler);
server.use("/static", express.static(path.resolve("./dist")));
server.use("/docs", express.static(path.resolve("./dist/assets/docs")));
server.use("/api", express.static(path.resolve("./dist/assets/api")));
server.use("/feed.xml", express.static(path.resolve("./dist/feed.xml")));
function erudaInit(): string {
if (process.env["NODE_ENV"] == "development") {

View file

@ -490,6 +490,12 @@ export class Main extends Component<Props, State> {
<InstancePicker reset={this.state.resetInstancePicker} />
<Helmet title={title}>
<meta property={"title"} content={title} />
<link
rel="alternate"
type="application/rss+xml"
title="RSS Feed for join-lemmy.org"
href="/feed.xml"
/>
</Helmet>
<img
src="/static/assets/images/world_background.svg"

View file

@ -45,7 +45,12 @@ function previewMarkdown(markdown: string): string {
}
const TitleBlock = () => (
<div className="pt-16 text-center text-4xl font-bold mb-8">{title}</div>
<div className="pt-16 text-center text-4xl font-bold mb-8">
{title}
<a href="/feed.xml" className="ml-4 inline-block">
<img src="/static/assets/images/rss.svg" width={24} />
</a>
</div>
);
const NewsCards = () => buildNewsInfoArray().map(n => <NewsCard news={n} />);
@ -99,6 +104,12 @@ export class News extends Component<any, any> {
<div className="container mx-auto px-4">
<Helmet title={title}>
<meta property={"title"} content={title} />
<link
rel="alternate"
type="application/rss+xml"
title="RSS Feed for join-lemmy.org"
href="/feed.xml"
/>
</Helmet>
<TitleBlock />
<NewsCards />

View file

@ -3949,6 +3949,15 @@ from2@^2.1.0:
inherits "^2.0.1"
readable-stream "^2.0.0"
fs-extra@^11.2.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-minipass@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
@ -4236,6 +4245,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
graceful-fs@~4.1.11:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
@ -5121,6 +5135,15 @@ json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsonparse@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
@ -7400,6 +7423,11 @@ sass@^1.69.5:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sax@>=0.6.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
schema-utils@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
@ -8393,6 +8421,11 @@ unique-string@^1.0.0:
dependencies:
crypto-random-string "^1.0.0"
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@ -8799,6 +8832,19 @@ xdg-basedir@^3.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
xml2js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499"
integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"