mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
feat(api): ✨ Add RSS and Atom feed functionality
This commit is contained in:
parent
70aff2df68
commit
3832328aaf
61
api/api/v1/accounts/[id]/feed.atom.ts
Normal file
61
api/api/v1/accounts/[id]/feed.atom.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { RolePermission } from "@versia/client/schemas";
|
||||||
|
import { describeRoute } from "hono-openapi";
|
||||||
|
import { resolver, validator } from "hono-openapi/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { apiRoute, auth, handleZodError, withUserParam } from "@/api";
|
||||||
|
import { getFeed } from "@/rss";
|
||||||
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
|
|
||||||
|
export default apiRoute((app) =>
|
||||||
|
app.get(
|
||||||
|
"/api/v1/accounts/:id/feed.atom",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get account's Atom feed",
|
||||||
|
description:
|
||||||
|
"Statuses posted to the given account, in Atom 1.0 format.",
|
||||||
|
tags: ["Accounts"],
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Statuses posted to the given account.",
|
||||||
|
content: {
|
||||||
|
"application/atom+xml": {
|
||||||
|
schema: resolver(z.any()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
404: ApiError.accountNotFound().schema,
|
||||||
|
422: ApiError.validationFailed().schema,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
withUserParam,
|
||||||
|
auth({
|
||||||
|
auth: false,
|
||||||
|
permissions: [
|
||||||
|
RolePermission.ViewNotes,
|
||||||
|
RolePermission.ViewAccounts,
|
||||||
|
],
|
||||||
|
scopes: ["read:statuses"],
|
||||||
|
}),
|
||||||
|
validator(
|
||||||
|
"query",
|
||||||
|
z.object({
|
||||||
|
page: z.coerce.number().default(0).openapi({
|
||||||
|
description: "Page number to fetch. Defaults to 0.",
|
||||||
|
example: 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handleZodError,
|
||||||
|
),
|
||||||
|
async (context) => {
|
||||||
|
const otherUser = context.get("user");
|
||||||
|
|
||||||
|
const { page } = context.req.valid("query");
|
||||||
|
|
||||||
|
const feed = await getFeed(otherUser, page);
|
||||||
|
|
||||||
|
context.header("Content-Type", "application/atom+xml");
|
||||||
|
|
||||||
|
return context.body(feed.atom1(), 200);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
60
api/api/v1/accounts/[id]/feed.rss.ts
Normal file
60
api/api/v1/accounts/[id]/feed.rss.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { RolePermission } from "@versia/client/schemas";
|
||||||
|
import { describeRoute } from "hono-openapi";
|
||||||
|
import { resolver, validator } from "hono-openapi/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { apiRoute, auth, handleZodError, withUserParam } from "@/api";
|
||||||
|
import { getFeed } from "@/rss";
|
||||||
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
|
|
||||||
|
export default apiRoute((app) =>
|
||||||
|
app.get(
|
||||||
|
"/api/v1/accounts/:id/feed.rss",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get account's RSS feed",
|
||||||
|
description: "Statuses posted to the given account, in RSS format.",
|
||||||
|
tags: ["Accounts"],
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Statuses posted to the given account.",
|
||||||
|
content: {
|
||||||
|
"application/rss+xml": {
|
||||||
|
schema: resolver(z.any()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
404: ApiError.accountNotFound().schema,
|
||||||
|
422: ApiError.validationFailed().schema,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
withUserParam,
|
||||||
|
auth({
|
||||||
|
auth: false,
|
||||||
|
permissions: [
|
||||||
|
RolePermission.ViewNotes,
|
||||||
|
RolePermission.ViewAccounts,
|
||||||
|
],
|
||||||
|
scopes: ["read:statuses"],
|
||||||
|
}),
|
||||||
|
validator(
|
||||||
|
"query",
|
||||||
|
z.object({
|
||||||
|
page: z.coerce.number().default(0).openapi({
|
||||||
|
description: "Page number to fetch. Defaults to 0.",
|
||||||
|
example: 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handleZodError,
|
||||||
|
),
|
||||||
|
async (context) => {
|
||||||
|
const otherUser = context.get("user");
|
||||||
|
|
||||||
|
const { page } = context.req.valid("query");
|
||||||
|
|
||||||
|
const feed = await getFeed(otherUser, page);
|
||||||
|
|
||||||
|
context.header("Content-Type", "application/rss+xml");
|
||||||
|
|
||||||
|
return context.body(feed.rss2(), 200);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
7
bun.lock
7
bun.lock
|
|
@ -28,6 +28,7 @@
|
||||||
"clerc": "^0.44.0",
|
"clerc": "^0.44.0",
|
||||||
"confbox": "^0.2.2",
|
"confbox": "^0.2.2",
|
||||||
"drizzle-orm": "^0.43.1",
|
"drizzle-orm": "^0.43.1",
|
||||||
|
"feed": "^4.2.2",
|
||||||
"hono": "^4.7.8",
|
"hono": "^4.7.8",
|
||||||
"hono-openapi": "^0.4.8",
|
"hono-openapi": "^0.4.8",
|
||||||
"hono-rate-limiter": "^0.4.2",
|
"hono-rate-limiter": "^0.4.2",
|
||||||
|
|
@ -804,6 +805,8 @@
|
||||||
|
|
||||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||||
|
|
||||||
|
"feed": ["feed@4.2.2", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ=="],
|
||||||
|
|
||||||
"figures": ["figures@5.0.0", "", { "dependencies": { "escape-string-regexp": "^5.0.0", "is-unicode-supported": "^1.2.0" } }, "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg=="],
|
"figures": ["figures@5.0.0", "", { "dependencies": { "escape-string-regexp": "^5.0.0", "is-unicode-supported": "^1.2.0" } }, "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg=="],
|
||||||
|
|
||||||
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
|
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
|
||||||
|
|
@ -1158,6 +1161,8 @@
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
|
"sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
|
||||||
|
|
||||||
"search-insights": ["search-insights@2.17.3", "", {}, "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ=="],
|
"search-insights": ["search-insights@2.17.3", "", {}, "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ=="],
|
||||||
|
|
||||||
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
|
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
|
||||||
|
|
@ -1312,6 +1317,8 @@
|
||||||
|
|
||||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
|
"xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="],
|
||||||
|
|
||||||
"xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="],
|
"xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="],
|
||||||
|
|
||||||
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ in
|
||||||
|
|
||||||
pnpmDeps = pnpm.fetchDeps {
|
pnpmDeps = pnpm.fetchDeps {
|
||||||
inherit (finalAttrs) pname version src pnpmInstallFlags;
|
inherit (finalAttrs) pname version src pnpmInstallFlags;
|
||||||
hash = "sha256-fY6Rx4wSI5e5d6ACj9Jh08lpWI38qeiOZQyY7+eI/c4=";
|
hash = "sha256-VPFYBNYbdwa1a3RVv4aieXMyzIGt3PGrRzQbih3WM8Y=";
|
||||||
};
|
};
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@
|
||||||
"clerc": "^0.44.0",
|
"clerc": "^0.44.0",
|
||||||
"confbox": "^0.2.2",
|
"confbox": "^0.2.2",
|
||||||
"drizzle-orm": "^0.43.1",
|
"drizzle-orm": "^0.43.1",
|
||||||
|
"feed": "^4.2.2",
|
||||||
"hono": "^4.7.8",
|
"hono": "^4.7.8",
|
||||||
"hono-openapi": "^0.4.8",
|
"hono-openapi": "^0.4.8",
|
||||||
"hono-rate-limiter": "^0.4.2",
|
"hono-rate-limiter": "^0.4.2",
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,9 @@ importers:
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.43.1
|
specifier: ^0.43.1
|
||||||
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(bun-types@1.2.11)
|
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(bun-types@1.2.11)
|
||||||
|
feed:
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
hono:
|
hono:
|
||||||
specifier: ^4.7.8
|
specifier: ^4.7.8
|
||||||
version: 4.7.8
|
version: 4.7.8
|
||||||
|
|
@ -2231,6 +2234,10 @@ packages:
|
||||||
fastq@1.19.1:
|
fastq@1.19.1:
|
||||||
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
||||||
|
|
||||||
|
feed@4.2.2:
|
||||||
|
resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
figures@5.0.0:
|
figures@5.0.0:
|
||||||
resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==}
|
resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
@ -2946,6 +2953,9 @@ packages:
|
||||||
safer-buffer@2.1.2:
|
safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
|
sax@1.4.1:
|
||||||
|
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
|
||||||
|
|
||||||
search-insights@2.17.3:
|
search-insights@2.17.3:
|
||||||
resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==}
|
resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==}
|
||||||
|
|
||||||
|
|
@ -3285,6 +3295,10 @@ packages:
|
||||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
xml-js@1.6.11:
|
||||||
|
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
xss@1.0.15:
|
xss@1.0.15:
|
||||||
resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}
|
resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
|
|
@ -5138,6 +5152,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.1.0
|
reusify: 1.1.0
|
||||||
|
|
||||||
|
feed@4.2.2:
|
||||||
|
dependencies:
|
||||||
|
xml-js: 1.6.11
|
||||||
|
|
||||||
figures@5.0.0:
|
figures@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp: 5.0.0
|
escape-string-regexp: 5.0.0
|
||||||
|
|
@ -5853,6 +5871,8 @@ snapshots:
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
|
sax@1.4.1: {}
|
||||||
|
|
||||||
search-insights@2.17.3: {}
|
search-insights@2.17.3: {}
|
||||||
|
|
||||||
section-matter@1.0.0:
|
section-matter@1.0.0:
|
||||||
|
|
@ -6253,6 +6273,10 @@ snapshots:
|
||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
strip-ansi: 7.1.0
|
strip-ansi: 7.1.0
|
||||||
|
|
||||||
|
xml-js@1.6.11:
|
||||||
|
dependencies:
|
||||||
|
sax: 1.4.1
|
||||||
|
|
||||||
xss@1.0.15:
|
xss@1.0.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
|
|
|
||||||
68
utils/rss.ts
Normal file
68
utils/rss.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
|
import { Feed } from "feed";
|
||||||
|
import { Note } from "~/classes/database/note";
|
||||||
|
import type { User } from "~/classes/database/user";
|
||||||
|
import { config } from "~/config";
|
||||||
|
import { Notes } from "~/drizzle/schema";
|
||||||
|
|
||||||
|
export const getFeed = async (user: User, page = 0): Promise<Feed> => {
|
||||||
|
const notes = await Note.manyFromSql(
|
||||||
|
and(
|
||||||
|
eq(Notes.authorId, user.id),
|
||||||
|
// Visibility check
|
||||||
|
inArray(Notes.visibility, ["public", "unlisted"]),
|
||||||
|
),
|
||||||
|
undefined,
|
||||||
|
20,
|
||||||
|
page * 20,
|
||||||
|
);
|
||||||
|
|
||||||
|
const feed = new Feed({
|
||||||
|
link: new URL(
|
||||||
|
`/api/v1/accounts/${user.id}/feed.rss`,
|
||||||
|
config.http.base_url,
|
||||||
|
).href,
|
||||||
|
id: new URL(
|
||||||
|
`/api/v1/accounts/${user.id}/feed.rss`,
|
||||||
|
config.http.base_url,
|
||||||
|
).href,
|
||||||
|
language: user.data.source?.language || undefined,
|
||||||
|
image: user.getAvatarUrl().href,
|
||||||
|
copyright: `All rights reserved ${new Date().getFullYear()} @${user.data.username}`,
|
||||||
|
feedLinks: {
|
||||||
|
atom: new URL(
|
||||||
|
`/api/v1/accounts/${user.id}/feed.atom`,
|
||||||
|
config.http.base_url,
|
||||||
|
).href,
|
||||||
|
rss: new URL(
|
||||||
|
`/api/v1/accounts/${user.id}/feed.rss`,
|
||||||
|
config.http.base_url,
|
||||||
|
).href,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
name: user.data.displayName || user.data.username,
|
||||||
|
link: new URL(`/@${user.data.username}`, config.http.base_url).href,
|
||||||
|
},
|
||||||
|
description: `Public statuses posted by @${user.data.username}`,
|
||||||
|
title: user.data.displayName || user.data.username,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const note of notes) {
|
||||||
|
feed.addItem({
|
||||||
|
link: new URL(
|
||||||
|
`/@${user.data.username}/${note.id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
).href,
|
||||||
|
content: note.data.content,
|
||||||
|
date: new Date(note.data.createdAt),
|
||||||
|
id: new URL(
|
||||||
|
`/@${user.data.username}/${note.id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
).href,
|
||||||
|
published: new Date(note.data.createdAt),
|
||||||
|
title: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return feed;
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue