@rmdes/indiekit-endpoint-activitypub 2.6.2 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/reader.css +0 -48
- package/index.js +0 -7
- package/lib/controllers/compose.js +4 -121
- package/lib/inbox-listeners.js +2 -0
- package/locales/en.json +0 -6
- package/package.json +1 -1
- package/views/activitypub-compose.njk +4 -32
- package/views/partials/ap-item-card.njk +1 -1
- package/views/partials/ap-notification-card.njk +2 -2
- package/lib/controllers/note-object.js +0 -51
package/assets/reader.css
CHANGED
|
@@ -976,34 +976,6 @@
|
|
|
976
976
|
gap: var(--space-m);
|
|
977
977
|
}
|
|
978
978
|
|
|
979
|
-
.ap-compose__mode {
|
|
980
|
-
border: var(--border-width-thin) solid var(--color-outline);
|
|
981
|
-
border-radius: var(--border-radius-small);
|
|
982
|
-
display: flex;
|
|
983
|
-
flex-direction: column;
|
|
984
|
-
gap: var(--space-s);
|
|
985
|
-
padding: var(--space-m);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
.ap-compose__mode legend {
|
|
989
|
-
font-weight: 600;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
.ap-compose__mode-option {
|
|
993
|
-
cursor: pointer;
|
|
994
|
-
display: flex;
|
|
995
|
-
flex-wrap: wrap;
|
|
996
|
-
gap: var(--space-xs);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
.ap-compose__mode-hint {
|
|
1000
|
-
color: var(--color-on-offset);
|
|
1001
|
-
display: block;
|
|
1002
|
-
font-size: var(--font-size-s);
|
|
1003
|
-
margin-left: 1.5em;
|
|
1004
|
-
width: 100%;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
979
|
.ap-compose__editor {
|
|
1008
980
|
position: relative;
|
|
1009
981
|
}
|
|
@@ -1027,21 +999,6 @@
|
|
|
1027
999
|
outline-offset: -2px;
|
|
1028
1000
|
}
|
|
1029
1001
|
|
|
1030
|
-
.ap-compose__counter {
|
|
1031
|
-
font-size: var(--font-size-s);
|
|
1032
|
-
padding-top: var(--space-xs);
|
|
1033
|
-
text-align: right;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
.ap-compose__counter--warn {
|
|
1037
|
-
color: var(--color-yellow50);
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
.ap-compose__counter--over {
|
|
1041
|
-
color: var(--color-error);
|
|
1042
|
-
font-weight: 600;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
1002
|
.ap-compose__syndication {
|
|
1046
1003
|
border: var(--border-width-thin) solid var(--color-outline);
|
|
1047
1004
|
border-radius: var(--border-radius-small);
|
|
@@ -2837,11 +2794,6 @@
|
|
|
2837
2794
|
box-shadow: 0 2px 8px rgba(255, 255, 255, 0.06);
|
|
2838
2795
|
}
|
|
2839
2796
|
|
|
2840
|
-
/* --- Compose counter warning --- */
|
|
2841
|
-
.ap-compose__counter--warn {
|
|
2842
|
-
color: var(--color-yellow90);
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
2797
|
/* --- Tab badge federated: soften purple --- */
|
|
2846
2798
|
.ap-tab__badge--federated {
|
|
2847
2799
|
color: var(--color-purple90);
|
package/index.js
CHANGED
|
@@ -80,7 +80,6 @@ import { hashtagExploreApiController } from "./lib/controllers/hashtag-explore.j
|
|
|
80
80
|
import { publicProfileController } from "./lib/controllers/public-profile.js";
|
|
81
81
|
import { authorizeInteractionController } from "./lib/controllers/authorize-interaction.js";
|
|
82
82
|
import { myProfileController } from "./lib/controllers/my-profile.js";
|
|
83
|
-
import { noteObjectController } from "./lib/controllers/note-object.js";
|
|
84
83
|
import {
|
|
85
84
|
refollowPauseController,
|
|
86
85
|
refollowResumeController,
|
|
@@ -189,10 +188,6 @@ export default class ActivityPubEndpoint {
|
|
|
189
188
|
return self._fedifyMiddleware(req, res, next);
|
|
190
189
|
});
|
|
191
190
|
|
|
192
|
-
// Serve stored quick reply Notes as JSON-LD so remote servers can
|
|
193
|
-
// dereference the Note ID during Create activity verification.
|
|
194
|
-
router.get("/quick-replies/:id", noteObjectController(self));
|
|
195
|
-
|
|
196
191
|
// Authorize interaction — remote follow / subscribe endpoint.
|
|
197
192
|
// Remote servers redirect users here via the WebFinger subscribe template.
|
|
198
193
|
router.get("/authorize_interaction", authorizeInteractionController(self));
|
|
@@ -889,7 +884,6 @@ export default class ActivityPubEndpoint {
|
|
|
889
884
|
Indiekit.addCollection("ap_muted");
|
|
890
885
|
Indiekit.addCollection("ap_blocked");
|
|
891
886
|
Indiekit.addCollection("ap_interactions");
|
|
892
|
-
Indiekit.addCollection("ap_notes");
|
|
893
887
|
Indiekit.addCollection("ap_followed_tags");
|
|
894
888
|
// Explore tab collections
|
|
895
889
|
Indiekit.addCollection("ap_explore_tabs");
|
|
@@ -911,7 +905,6 @@ export default class ActivityPubEndpoint {
|
|
|
911
905
|
ap_muted: indiekitCollections.get("ap_muted"),
|
|
912
906
|
ap_blocked: indiekitCollections.get("ap_blocked"),
|
|
913
907
|
ap_interactions: indiekitCollections.get("ap_interactions"),
|
|
914
|
-
ap_notes: indiekitCollections.get("ap_notes"),
|
|
915
908
|
ap_followed_tags: indiekitCollections.get("ap_followed_tags"),
|
|
916
909
|
// Explore tab collections
|
|
917
910
|
ap_explore_tabs: indiekitCollections.get("ap_explore_tabs"),
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compose controllers — reply form via Micropub
|
|
2
|
+
* Compose controllers — reply form via Micropub.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Temporal } from "@js-temporal/polyfill";
|
|
6
5
|
import { getToken, validateToken } from "../csrf.js";
|
|
7
6
|
import { sanitizeContent } from "../timeline-store.js";
|
|
8
|
-
import { resolveAuthor } from "../resolve-author.js";
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Fetch syndication targets from the Micropub config endpoint.
|
|
@@ -155,7 +153,7 @@ export function composeController(mountPath, plugin) {
|
|
|
155
153
|
}
|
|
156
154
|
|
|
157
155
|
/**
|
|
158
|
-
* POST /admin/reader/compose — Submit reply via Micropub
|
|
156
|
+
* POST /admin/reader/compose — Submit reply via Micropub.
|
|
159
157
|
* @param {string} mountPath - Plugin mount path
|
|
160
158
|
* @param {object} plugin - ActivityPub plugin instance
|
|
161
159
|
*/
|
|
@@ -170,7 +168,7 @@ export function submitComposeController(mountPath, plugin) {
|
|
|
170
168
|
}
|
|
171
169
|
|
|
172
170
|
const { application } = request.app.locals;
|
|
173
|
-
const { content
|
|
171
|
+
const { content } = request.body;
|
|
174
172
|
const inReplyTo = request.body["in-reply-to"];
|
|
175
173
|
const syndicateTo = request.body["mp-syndicate-to"];
|
|
176
174
|
|
|
@@ -181,122 +179,7 @@ export function submitComposeController(mountPath, plugin) {
|
|
|
181
179
|
});
|
|
182
180
|
}
|
|
183
181
|
|
|
184
|
-
//
|
|
185
|
-
if (mode === "quick") {
|
|
186
|
-
if (!plugin._federation) {
|
|
187
|
-
return response.status(503).render("error", {
|
|
188
|
-
title: "Error",
|
|
189
|
-
content: "Federation not initialized",
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const { Create, Note } = await import("@fedify/fedify/vocab");
|
|
194
|
-
const handle = plugin.options.actor.handle;
|
|
195
|
-
const ctx = plugin._federation.createContext(
|
|
196
|
-
new URL(plugin._publicationUrl),
|
|
197
|
-
{ handle, publicationUrl: plugin._publicationUrl },
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
const uuid = crypto.randomUUID();
|
|
201
|
-
const baseUrl = plugin._publicationUrl.replace(/\/$/, "");
|
|
202
|
-
const noteId = `${baseUrl}/activitypub/quick-replies/${uuid}`;
|
|
203
|
-
const actorUri = ctx.getActorUri(handle);
|
|
204
|
-
|
|
205
|
-
const publicAddress = new URL(
|
|
206
|
-
"https://www.w3.org/ns/activitystreams#Public",
|
|
207
|
-
);
|
|
208
|
-
const followersUri = ctx.getFollowersUri(handle);
|
|
209
|
-
|
|
210
|
-
const documentLoader = await ctx.getDocumentLoader({
|
|
211
|
-
identifier: handle,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Resolve the original author BEFORE constructing the Note,
|
|
215
|
-
// so we can include them in cc (required for threading/notification)
|
|
216
|
-
let recipient = null;
|
|
217
|
-
if (inReplyTo) {
|
|
218
|
-
recipient = await resolveAuthor(
|
|
219
|
-
inReplyTo,
|
|
220
|
-
ctx,
|
|
221
|
-
documentLoader,
|
|
222
|
-
application?.collections,
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Build cc list: always include followers, add original author for replies
|
|
227
|
-
const ccList = [followersUri];
|
|
228
|
-
if (recipient?.id) {
|
|
229
|
-
ccList.push(recipient.id);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const note = new Note({
|
|
233
|
-
id: new URL(noteId),
|
|
234
|
-
attribution: actorUri,
|
|
235
|
-
content: content.trim(),
|
|
236
|
-
replyTarget: inReplyTo ? new URL(inReplyTo) : undefined,
|
|
237
|
-
published: Temporal.Now.instant(),
|
|
238
|
-
to: publicAddress,
|
|
239
|
-
ccs: ccList,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const create = new Create({
|
|
243
|
-
id: new URL(`${noteId}#activity`),
|
|
244
|
-
actor: actorUri,
|
|
245
|
-
object: note,
|
|
246
|
-
to: publicAddress,
|
|
247
|
-
ccs: ccList,
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// Store the Note so remote servers can dereference its ID
|
|
251
|
-
const ap_notes = application?.collections?.get("ap_notes");
|
|
252
|
-
if (ap_notes) {
|
|
253
|
-
await ap_notes.insertOne({
|
|
254
|
-
_id: uuid,
|
|
255
|
-
noteId,
|
|
256
|
-
actorUrl: actorUri.href,
|
|
257
|
-
content: content.trim(),
|
|
258
|
-
inReplyTo: inReplyTo || null,
|
|
259
|
-
published: new Date().toISOString(),
|
|
260
|
-
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
261
|
-
cc: ccList.map((u) => (u instanceof URL ? u.href : u.href || u)),
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Send to followers
|
|
266
|
-
await ctx.sendActivity({ identifier: handle }, "followers", create, {
|
|
267
|
-
preferSharedInbox: true,
|
|
268
|
-
syncCollection: true,
|
|
269
|
-
orderingKey: noteId,
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// Also send directly to the original author's inbox
|
|
273
|
-
if (recipient) {
|
|
274
|
-
try {
|
|
275
|
-
await ctx.sendActivity(
|
|
276
|
-
{ identifier: handle },
|
|
277
|
-
recipient,
|
|
278
|
-
create,
|
|
279
|
-
{ orderingKey: noteId },
|
|
280
|
-
);
|
|
281
|
-
console.info(
|
|
282
|
-
`[ActivityPub] Sent quick reply directly to ${recipient.id?.href || "author"}`,
|
|
283
|
-
);
|
|
284
|
-
} catch (error) {
|
|
285
|
-
console.warn(
|
|
286
|
-
`[ActivityPub] Direct delivery to author failed (quick reply):`,
|
|
287
|
-
error.message,
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
console.info(
|
|
293
|
-
`[ActivityPub] Sent quick reply${inReplyTo ? ` to ${inReplyTo}` : ""}`,
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
return response.redirect(`${mountPath}/admin/reader`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Micropub path — post as blog reply
|
|
182
|
+
// Post as blog reply via Micropub
|
|
300
183
|
const micropubEndpoint = application.micropubEndpoint;
|
|
301
184
|
|
|
302
185
|
if (!micropubEndpoint) {
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -431,6 +431,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
431
431
|
|
|
432
432
|
await addNotification(collections, {
|
|
433
433
|
uid: object.id?.href || `reply:${actorUrl}:${inReplyTo}`,
|
|
434
|
+
url: object.url?.href || object.id?.href || "",
|
|
434
435
|
type: "reply",
|
|
435
436
|
actorUrl: actorInfo.url,
|
|
436
437
|
actorName: actorInfo.name,
|
|
@@ -462,6 +463,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
462
463
|
|
|
463
464
|
await addNotification(collections, {
|
|
464
465
|
uid: object.id?.href || `mention:${actorUrl}:${object.id?.href}`,
|
|
466
|
+
url: object.url?.href || object.id?.href || "",
|
|
465
467
|
type: "mention",
|
|
466
468
|
actorUrl: actorInfo.url,
|
|
467
469
|
actorName: actorInfo.name,
|
package/locales/en.json
CHANGED
|
@@ -141,15 +141,9 @@
|
|
|
141
141
|
},
|
|
142
142
|
"compose": {
|
|
143
143
|
"title": "Compose reply",
|
|
144
|
-
"modeLabel": "Reply mode",
|
|
145
|
-
"modeMicropub": "Post as blog reply",
|
|
146
|
-
"modeMicropubHint": "Creates a permanent post on your blog, syndicated to the fediverse",
|
|
147
|
-
"modeQuick": "Quick reply",
|
|
148
|
-
"modeQuickHint": "Sends a reply directly to the fediverse (no blog post created)",
|
|
149
144
|
"placeholder": "Write your reply…",
|
|
150
145
|
"syndicateLabel": "Syndicate to",
|
|
151
146
|
"submitMicropub": "Post reply",
|
|
152
|
-
"submitQuick": "Send reply",
|
|
153
147
|
"cancel": "Cancel",
|
|
154
148
|
"errorEmpty": "Reply content cannot be empty"
|
|
155
149
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -21,50 +21,23 @@
|
|
|
21
21
|
</div>
|
|
22
22
|
{% endif %}
|
|
23
23
|
|
|
24
|
-
<form method="post" action="{{ mountPath }}/admin/reader/compose" class="ap-compose__form"
|
|
25
|
-
x-data="{
|
|
26
|
-
mode: 'micropub',
|
|
27
|
-
content: '',
|
|
28
|
-
maxChars: 500,
|
|
29
|
-
get remaining() { return this.maxChars - this.content.length; }
|
|
30
|
-
}">
|
|
24
|
+
<form method="post" action="{{ mountPath }}/admin/reader/compose" class="ap-compose__form">
|
|
31
25
|
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
32
26
|
{% if replyTo %}
|
|
33
27
|
<input type="hidden" name="in-reply-to" value="{{ replyTo }}">
|
|
34
28
|
{% endif %}
|
|
35
29
|
|
|
36
|
-
{# Mode toggle #}
|
|
37
|
-
<fieldset class="ap-compose__mode">
|
|
38
|
-
<legend>{{ __("activitypub.compose.modeLabel") }}</legend>
|
|
39
|
-
<label class="ap-compose__mode-option">
|
|
40
|
-
<input type="radio" name="mode" value="micropub" x-model="mode" checked>
|
|
41
|
-
{{ __("activitypub.compose.modeMicropub") }}
|
|
42
|
-
<span class="ap-compose__mode-hint">{{ __("activitypub.compose.modeMicropubHint") }}</span>
|
|
43
|
-
</label>
|
|
44
|
-
<label class="ap-compose__mode-option">
|
|
45
|
-
<input type="radio" name="mode" value="quick" x-model="mode">
|
|
46
|
-
{{ __("activitypub.compose.modeQuick") }}
|
|
47
|
-
<span class="ap-compose__mode-hint">{{ __("activitypub.compose.modeQuickHint") }}</span>
|
|
48
|
-
</label>
|
|
49
|
-
</fieldset>
|
|
50
|
-
|
|
51
30
|
{# Content textarea #}
|
|
52
31
|
<div class="ap-compose__editor">
|
|
53
32
|
<textarea name="content" class="ap-compose__textarea"
|
|
54
33
|
rows="6"
|
|
55
|
-
:maxlength="mode === 'quick' ? maxChars : undefined"
|
|
56
|
-
x-model="content"
|
|
57
34
|
placeholder="{{ __('activitypub.compose.placeholder') }}"
|
|
58
35
|
required></textarea>
|
|
59
|
-
<div class="ap-compose__counter" x-show="mode === 'quick'" x-cloak>
|
|
60
|
-
<span :class="{ 'ap-compose__counter--warn': remaining < 50, 'ap-compose__counter--over': remaining < 0 }"
|
|
61
|
-
x-text="remaining"></span>
|
|
62
|
-
</div>
|
|
63
36
|
</div>
|
|
64
37
|
|
|
65
|
-
{# Syndication targets
|
|
38
|
+
{# Syndication targets #}
|
|
66
39
|
{% if syndicationTargets.length > 0 %}
|
|
67
|
-
<fieldset class="ap-compose__syndication"
|
|
40
|
+
<fieldset class="ap-compose__syndication">
|
|
68
41
|
<legend>{{ __("activitypub.compose.syndicateLabel") }}</legend>
|
|
69
42
|
{% for target in syndicationTargets %}
|
|
70
43
|
<label class="ap-compose__syndication-target">
|
|
@@ -77,8 +50,7 @@
|
|
|
77
50
|
|
|
78
51
|
<div class="ap-compose__actions">
|
|
79
52
|
<button type="submit" class="ap-compose__submit">
|
|
80
|
-
|
|
81
|
-
<span x-show="mode === 'quick'">{{ __("activitypub.compose.submitQuick") }}</span>
|
|
53
|
+
{{ __("activitypub.compose.submitMicropub") }}
|
|
82
54
|
</button>
|
|
83
55
|
<a href="{{ mountPath }}/admin/reader" class="ap-compose__cancel">
|
|
84
56
|
{{ __("activitypub.compose.cancel") }}
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
if (this.error) setTimeout(() => this.error = '', 3000);
|
|
231
231
|
}
|
|
232
232
|
}">
|
|
233
|
-
<a href="{{ mountPath }}/admin/reader/compose?replyTo={{ itemUid | urlencode }}"
|
|
233
|
+
<a href="{{ mountPath }}/admin/reader/compose?replyTo={{ (itemUrl or itemUid) | urlencode }}"
|
|
234
234
|
class="ap-card__action ap-card__action--reply"
|
|
235
235
|
title="{{ __('activitypub.reader.actions.reply') }}">
|
|
236
236
|
↩ {{ __("activitypub.reader.actions.reply") }}{% if replyCount != null %}<span class="ap-card__count">{{ replyCount }}</span>{% endif %}
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
|
|
57
57
|
{% if item.type == "reply" or item.type == "mention" %}
|
|
58
58
|
<div class="ap-notification__actions">
|
|
59
|
-
<a href="{{ mountPath }}/admin/reader/compose?replyTo={{ item.uid | urlencode }}" class="ap-notification__reply-btn" title="{{ __('activitypub.reader.actions.reply') }}">
|
|
59
|
+
<a href="{{ mountPath }}/admin/reader/compose?replyTo={{ (item.url or item.uid) | urlencode }}" class="ap-notification__reply-btn" title="{{ __('activitypub.reader.actions.reply') }}">
|
|
60
60
|
↩ {{ __("activitypub.reader.actions.reply") }}
|
|
61
61
|
</a>
|
|
62
|
-
<a href="{{ mountPath }}/admin/reader/post?url={{ item.uid | urlencode }}" class="ap-notification__thread-btn" title="{{ __('activitypub.reader.post.title') }}">
|
|
62
|
+
<a href="{{ mountPath }}/admin/reader/post?url={{ (item.url or item.uid) | urlencode }}" class="ap-notification__thread-btn" title="{{ __('activitypub.reader.post.title') }}">
|
|
63
63
|
💬 {{ __("activitypub.notifications.viewThread") }}
|
|
64
64
|
</a>
|
|
65
65
|
</div>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public route handler for serving quick reply Notes as ActivityPub JSON-LD.
|
|
3
|
-
*
|
|
4
|
-
* Remote servers dereference Note IDs to verify Create activities.
|
|
5
|
-
* Without this, quick replies are rejected by servers that validate
|
|
6
|
-
* the Note's ID URL (Mastodon with Authorized Fetch, Bonfire, etc.).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* GET /quick-replies/:id — serve a stored Note as JSON-LD.
|
|
11
|
-
* @param {object} plugin - ActivityPub plugin instance
|
|
12
|
-
*/
|
|
13
|
-
export function noteObjectController(plugin) {
|
|
14
|
-
return async (request, response) => {
|
|
15
|
-
const { id } = request.params;
|
|
16
|
-
|
|
17
|
-
const { application } = request.app.locals;
|
|
18
|
-
const ap_notes = application?.collections?.get("ap_notes");
|
|
19
|
-
|
|
20
|
-
if (!ap_notes) {
|
|
21
|
-
return response.status(404).json({ error: "Not Found" });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const note = await ap_notes.findOne({ _id: id });
|
|
25
|
-
|
|
26
|
-
if (!note) {
|
|
27
|
-
return response.status(404).json({ error: "Not Found" });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const noteJson = {
|
|
31
|
-
"@context": "https://www.w3.org/ns/activitystreams",
|
|
32
|
-
id: note.noteId,
|
|
33
|
-
type: "Note",
|
|
34
|
-
attributedTo: note.actorUrl,
|
|
35
|
-
content: note.content,
|
|
36
|
-
published: note.published,
|
|
37
|
-
to: note.to,
|
|
38
|
-
cc: note.cc,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
if (note.inReplyTo) {
|
|
42
|
-
noteJson.inReplyTo = note.inReplyTo;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
response
|
|
46
|
-
.status(200)
|
|
47
|
-
.set("Content-Type", "application/activity+json; charset=utf-8")
|
|
48
|
-
.set("Cache-Control", "public, max-age=3600")
|
|
49
|
-
.json(noteJson);
|
|
50
|
-
};
|
|
51
|
-
}
|