From 6c27842bd2a944df03f711ded50cfd9d353d102b Mon Sep 17 00:00:00 2001
From: mineblock11 <93472213+mineblock11@users.noreply.github.com>
Date: Fri, 19 May 2023 23:04:40 +0100
Subject: [PATCH 1/5] Project RSS Feed (atom1, json2, rss2)
---
package.json | 1 +
.../routes/feed/[feed_type]/project/[id].js | 68 +++++++++++++++++++
2 files changed, 69 insertions(+)
create mode 100644 server/routes/feed/[feed_type]/project/[id].js
diff --git a/package.json b/package.json
index 8066711b28..f8a39a6b15 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"dependencies": {
"@ltd/j-toml": "^1.38.0",
"dayjs": "^1.11.7",
+ "feed": "^4.2.2",
"floating-vue": "^2.0.0-beta.20",
"highlight.js": "^11.7.0",
"js-yaml": "^4.1.0",
diff --git a/server/routes/feed/[feed_type]/project/[id].js b/server/routes/feed/[feed_type]/project/[id].js
new file mode 100644
index 0000000000..5eb326a207
--- /dev/null
+++ b/server/routes/feed/[feed_type]/project/[id].js
@@ -0,0 +1,68 @@
+import { Feed } from 'feed'
+import { renderString } from '~/helpers/parse'
+
+export default defineEventHandler(async (event) => {
+ const config = useRuntimeConfig()
+ const API_URL = config.apiBaseUrl
+ const WEBSITE_URL = config.public.siteUrl
+
+ const projectInformation = await $fetch(API_URL + 'project/' + event.context.params.id)
+ const projectVersions = await $fetch(API_URL + 'project/' + event.context.params.id + '/version')
+ const projectTeam = await $fetch(API_URL + 'project/' + event.context.params.id + '/members')
+
+ let featuredImage = projectInformation.gallery.filter((image) => image.featured)[0]
+
+ if (featuredImage) {
+ featuredImage = featuredImage.url
+ }
+
+ const resultFeed = new Feed({
+ title: projectInformation.title,
+ id: projectInformation.id,
+ description: renderString(projectInformation.description),
+ generator: 'Modrinth',
+ link: WEBSITE_URL + `/${projectInformation.project_type}/${projectInformation.id}`,
+ language: 'en',
+ updated: new Date(projectInformation.updated),
+ favicon: projectInformation.icon_url ?? 'https://cdn.modrinth.com/placeholder.png',
+ image: featuredImage ?? undefined,
+ })
+
+ projectVersions.forEach((version) => {
+ resultFeed.addItem({
+ title: `New Version Released: ${version.name}`,
+ id: `release-${version.id}`,
+ link:
+ WEBSITE_URL +
+ `/${projectInformation.project_type}/${projectInformation.id}/version/${version.id}`,
+ content:
+ `This version is for ${version.game_versions.join(', ')}
` +
+ // Check for changelog length being greater than 1 to ensure no blank changelog section.
+ // Legacy changelog support.
+ `
Changelog
${renderString(
+ version.changelog.length > 1 ? version.changelog : 'No changelog was specified.'
+ )}`,
+ author: [
+ ...projectTeam.map((member) => {
+ return {
+ name: member.user.username,
+ link: WEBSITE_URL + `/user/${member.user.id}`,
+ }
+ }),
+ ],
+ date: new Date(version.date_published),
+ })
+ })
+
+ switch (event.context.params.feed_type) {
+ case 'rss':
+ return resultFeed.rss2()
+ case 'atom':
+ return resultFeed.atom1()
+ case 'json':
+ return resultFeed.json1()
+ default:
+ setResponseStatus(event, 404)
+ return 'Invalid Feed Type'
+ }
+})
From 55f7bb1f142d2e2337b143d5c956fa22c3d3bf53 Mon Sep 17 00:00:00 2001
From: mineblock11 <93472213+mineblock11@users.noreply.github.com>
Date: Fri, 19 May 2023 23:07:03 +0100
Subject: [PATCH 2/5] Make feed_type insensitive to capitals
---
server/routes/feed/[feed_type]/project/[id].js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/routes/feed/[feed_type]/project/[id].js b/server/routes/feed/[feed_type]/project/[id].js
index 5eb326a207..b961952396 100644
--- a/server/routes/feed/[feed_type]/project/[id].js
+++ b/server/routes/feed/[feed_type]/project/[id].js
@@ -54,7 +54,7 @@ export default defineEventHandler(async (event) => {
})
})
- switch (event.context.params.feed_type) {
+ switch (event.context.params.feed_type.toLowerCase()) {
case 'rss':
return resultFeed.rss2()
case 'atom':
From 881f01ffda38d7623cf5f5741ffefc54778605e0 Mon Sep 17 00:00:00 2001
From: Calum
Date: Sun, 21 May 2023 14:23:47 +0100
Subject: [PATCH 3/5] Notification Feed
---
.../routes/feed/[feed_type]/notifications.js | 67 +++++++++++++++++++
.../routes/feed/[feed_type]/project/[id].js | 15 +++--
2 files changed, 76 insertions(+), 6 deletions(-)
create mode 100644 server/routes/feed/[feed_type]/notifications.js
diff --git a/server/routes/feed/[feed_type]/notifications.js b/server/routes/feed/[feed_type]/notifications.js
new file mode 100644
index 0000000000..feaec6b0cf
--- /dev/null
+++ b/server/routes/feed/[feed_type]/notifications.js
@@ -0,0 +1,67 @@
+import { Feed } from 'feed'
+import { renderString } from '~/helpers/parse'
+
+export default defineEventHandler(async (event) => {
+ const config = useRuntimeConfig()
+ const API_URL = config.apiBaseUrl
+ const WEBSITE_URL = config.public.siteUrl
+ const authorization = getHeader(event, 'Authorization')
+
+ if (authorization === undefined) {
+ setResponseStatus(event, 401)
+ return 'Please pass a valid authentication token to view your notifications as an RSS feed.'
+ }
+
+ try {
+ const userInfo = await $fetch(API_URL + 'user', {
+ headers: {
+ Authorization: authorization,
+ },
+ })
+
+ const userNotifications = await $fetch(API_URL + `user/${userInfo.id}/notifications`, {
+ headers: {
+ Authorization: authorization,
+ },
+ })
+
+ const feed = new Feed({
+ title: `Notifications for ${userInfo.username}`,
+ link: WEBSITE_URL + '/notifications',
+ generator: 'Modrinth',
+ description: `${userInfo.username} has ${userNotifications.length} notification${
+ userNotifications.length === 1 ? '' : 's'
+ }`,
+ })
+
+ userNotifications.forEach((notification) => {
+ feed.addItem({
+ title: renderString(notification.title),
+ description: renderString(notification.text),
+ id: notification.id,
+ link: WEBSITE_URL + notification.link,
+ date: new Date(notification.created),
+ })
+ })
+
+ switch (event.context.params.feed_type.toLowerCase()) {
+ case 'rss':
+ setResponseHeader(event, 'Content-Type', 'application/rss+xml')
+ return feed.rss2()
+ case 'atom':
+ setResponseHeader(event, 'Content-Type', 'application/atom+xml')
+ return feed.atom1()
+ case 'json':
+ setResponseHeader(event, 'Content-Type', 'application/feed+json')
+ return feed.json1()
+ default:
+ setResponseStatus(event, 500)
+ return 'Invalid Feed Type'
+ }
+ } catch (e) {
+ setResponseStatus(event, 401)
+ return (
+ 'Please pass a valid authentication token to view your notifications as an RSS feed.\n\n' + e
+ )
+ }
+})
diff --git a/server/routes/feed/[feed_type]/project/[id].js b/server/routes/feed/[feed_type]/project/[id].js
index b961952396..1de561d06b 100644
--- a/server/routes/feed/[feed_type]/project/[id].js
+++ b/server/routes/feed/[feed_type]/project/[id].js
@@ -16,7 +16,7 @@ export default defineEventHandler(async (event) => {
featuredImage = featuredImage.url
}
- const resultFeed = new Feed({
+ const feed = new Feed({
title: projectInformation.title,
id: projectInformation.id,
description: renderString(projectInformation.description),
@@ -29,7 +29,7 @@ export default defineEventHandler(async (event) => {
})
projectVersions.forEach((version) => {
- resultFeed.addItem({
+ feed.addItem({
title: `New Version Released: ${version.name}`,
id: `release-${version.id}`,
link:
@@ -56,13 +56,16 @@ export default defineEventHandler(async (event) => {
switch (event.context.params.feed_type.toLowerCase()) {
case 'rss':
- return resultFeed.rss2()
+ setResponseHeader(event, 'Content-Type', 'application/rss+xml')
+ return feed.rss2()
case 'atom':
- return resultFeed.atom1()
+ setResponseHeader(event, 'Content-Type', 'application/atom+xml')
+ return feed.atom1()
case 'json':
- return resultFeed.json1()
+ setResponseHeader(event, 'Content-Type', 'application/feed+json')
+ return feed.json1()
default:
- setResponseStatus(event, 404)
+ setResponseStatus(event, 500)
return 'Invalid Feed Type'
}
})
From 10548978eec22a8a9b2c4491a57086dc0a94220d Mon Sep 17 00:00:00 2001
From: Calum
Date: Wed, 24 May 2023 21:03:50 +0100
Subject: [PATCH 4/5] Project RSS fixes.
---
helpers/parse.js | 20 +++++++++
.../routes/feed/[feed_type]/notifications.js | 5 +++
.../routes/feed/[feed_type]/project/[id].js | 41 ++++++++++++++-----
3 files changed, 55 insertions(+), 11 deletions(-)
diff --git a/helpers/parse.js b/helpers/parse.js
index 24487131e2..e5623a0f04 100644
--- a/helpers/parse.js
+++ b/helpers/parse.js
@@ -124,3 +124,23 @@ export const md = (options = {}) => {
}
export const renderString = (string) => configuredXss.process(md().render(string))
+
+export const escapeXmlAttr = (unsafe) => {
+ if (!unsafe) {
+ return
+ }
+ return unsafe.replace(/[<>&'"]/g, function (c) {
+ switch (c) {
+ case '<':
+ return '<'
+ case '>':
+ return '>'
+ case '&':
+ return '&'
+ case "'":
+ return '''
+ case '"':
+ return '"'
+ }
+ })
+}
diff --git a/server/routes/feed/[feed_type]/notifications.js b/server/routes/feed/[feed_type]/notifications.js
index feaec6b0cf..2eb2e27bea 100644
--- a/server/routes/feed/[feed_type]/notifications.js
+++ b/server/routes/feed/[feed_type]/notifications.js
@@ -32,6 +32,11 @@ export default defineEventHandler(async (event) => {
description: `${userInfo.username} has ${userNotifications.length} notification${
userNotifications.length === 1 ? '' : 's'
}`,
+ feedLinks: {
+ json: WEBSITE_URL + '/feed/json/notifications',
+ atom: WEBSITE_URL + '/feed/atom/notifications',
+ rss: WEBSITE_URL + '/feed/rss/notifications',
+ },
})
userNotifications.forEach((notification) => {
diff --git a/server/routes/feed/[feed_type]/project/[id].js b/server/routes/feed/[feed_type]/project/[id].js
index 1de561d06b..0b78c6a81f 100644
--- a/server/routes/feed/[feed_type]/project/[id].js
+++ b/server/routes/feed/[feed_type]/project/[id].js
@@ -1,5 +1,5 @@
import { Feed } from 'feed'
-import { renderString } from '~/helpers/parse'
+import { renderString, escapeXmlAttr } from '~/helpers/parse'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
@@ -18,8 +18,23 @@ export default defineEventHandler(async (event) => {
const feed = new Feed({
title: projectInformation.title,
- id: projectInformation.id,
- description: renderString(projectInformation.description),
+ id: WEBSITE_URL + `/${projectInformation.project_type}/${projectInformation.id}`,
+ description:
+ `${projectInformation.title} is a ${projectInformation.project_type} with ${
+ projectInformation.downloads
+ } download${projectInformation.downloads > 1 ? 's' : ''}` +
+ `${
+ projectInformation.followers > 0
+ ? 'and ' + projectInformation.followers + ' follower' + projectInformation.followers > 1
+ ? 's'
+ : ''
+ : ''
+ } that is available on Modrinth, an open-source platform to host mods, modpacks, shaders, resource packs, plugins and datapacks.`,
+ feedLinks: {
+ json: WEBSITE_URL + `/feed/json/project/${projectInformation.id}`,
+ atom: WEBSITE_URL + `/feed/atom/project/${projectInformation.id}`,
+ rss: WEBSITE_URL + `/feed/rss/project/${projectInformation.id}`,
+ },
generator: 'Modrinth',
link: WEBSITE_URL + `/${projectInformation.project_type}/${projectInformation.id}`,
language: 'en',
@@ -31,17 +46,21 @@ export default defineEventHandler(async (event) => {
projectVersions.forEach((version) => {
feed.addItem({
title: `New Version Released: ${version.name}`,
- id: `release-${version.id}`,
+ id:
+ WEBSITE_URL +
+ `/${projectInformation.project_type}/${projectInformation.id}/version/${version.id}`,
link:
WEBSITE_URL +
`/${projectInformation.project_type}/${projectInformation.id}/version/${version.id}`,
- content:
- `This version is for ${version.game_versions.join(', ')}
` +
- // Check for changelog length being greater than 1 to ensure no blank changelog section.
- // Legacy changelog support.
- `Changelog
${renderString(
- version.changelog.length > 1 ? version.changelog : 'No changelog was specified.'
- )}`,
+ content: escapeXmlAttr(
+ `This version is for ${version.loaders.join(
+ ', '
+ )} and works on the following Minecraft versions: ${version.game_versions.join(', ')}
` +
+ // Check for changelog length being greater than 1 to ensure no blank changelog section.
+ `Changelog
${renderString(
+ version.changelog.length > 1 ? version.changelog : 'No changelog was specified.'
+ )}`
+ ),
author: [
...projectTeam.map((member) => {
return {
From 909d706d2a7cbb88226677bbb1dca3eb25586c96 Mon Sep 17 00:00:00 2001
From: Calum
Date: Wed, 24 May 2023 21:13:36 +0100
Subject: [PATCH 5/5] Fix notification feed
---
server/routes/feed/[feed_type]/notifications.js | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/server/routes/feed/[feed_type]/notifications.js b/server/routes/feed/[feed_type]/notifications.js
index 2eb2e27bea..42909f8531 100644
--- a/server/routes/feed/[feed_type]/notifications.js
+++ b/server/routes/feed/[feed_type]/notifications.js
@@ -29,6 +29,7 @@ export default defineEventHandler(async (event) => {
title: `Notifications for ${userInfo.username}`,
link: WEBSITE_URL + '/notifications',
generator: 'Modrinth',
+ id: WEBSITE_URL + '/notifications',
description: `${userInfo.username} has ${userNotifications.length} notification${
userNotifications.length === 1 ? '' : 's'
}`,
@@ -41,11 +42,17 @@ export default defineEventHandler(async (event) => {
userNotifications.forEach((notification) => {
feed.addItem({
- title: renderString(notification.title),
+ title: notification.title,
description: renderString(notification.text),
- id: notification.id,
+ id: WEBSITE_URL + notification.link,
link: WEBSITE_URL + notification.link,
date: new Date(notification.created),
+ author: [
+ {
+ name: userInfo.username,
+ link: WEBSITE_URL + `/user/${userInfo.id}`,
+ },
+ ],
})
})
@@ -65,8 +72,6 @@ export default defineEventHandler(async (event) => {
}
} catch (e) {
setResponseStatus(event, 401)
- return (
- 'Please pass a valid authentication token to view your notifications as an RSS feed.\n\n' + e
- )
+ return 'There was an error generating the feed.\n\n' + e
}
})