@rmdes/indiekit-endpoint-activitypub 1.1.16 → 1.1.18
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/README.md +1 -0
- package/assets/reader.css +48 -0
- package/index.js +14 -4
- package/lib/controllers/followers.js +1 -1
- package/lib/controllers/following.js +1 -1
- package/lib/controllers/resolve.js +109 -0
- package/lib/federation-setup.js +90 -69
- package/locales/en.json +7 -0
- package/package.json +1 -1
- package/views/activitypub-activities.njk +0 -2
- package/views/activitypub-compose.njk +0 -2
- package/views/activitypub-dashboard.njk +0 -2
- package/views/activitypub-featured-tags.njk +0 -2
- package/views/activitypub-featured.njk +0 -2
- package/views/activitypub-followers.njk +0 -2
- package/views/activitypub-following.njk +0 -2
- package/views/activitypub-migrate.njk +0 -2
- package/views/activitypub-moderation.njk +0 -2
- package/views/activitypub-notifications.njk +0 -2
- package/views/activitypub-post-detail.njk +0 -2
- package/views/activitypub-profile.njk +0 -2
- package/views/activitypub-reader.njk +7 -1
- package/views/activitypub-remote-profile.njk +0 -2
package/README.md
CHANGED
|
@@ -93,6 +93,7 @@ export default {
|
|
|
93
93
|
| `redisUrl` | string | `""` | Redis connection URL for delivery queue |
|
|
94
94
|
| `parallelWorkers` | number | `5` | Number of parallel delivery workers (requires Redis) |
|
|
95
95
|
| `actorType` | string | `"Person"` | Actor type: `Person`, `Service`, `Organization`, or `Group` |
|
|
96
|
+
| `logLevel` | string | `"warning"` | Fedify log level: `"debug"`, `"info"`, `"warning"`, `"error"`, `"fatal"` |
|
|
96
97
|
| `timelineRetention` | number | `1000` | Maximum timeline items to keep (0 = unlimited) |
|
|
97
98
|
|
|
98
99
|
### Redis (Recommended for Production)
|
package/assets/reader.css
CHANGED
|
@@ -4,6 +4,54 @@
|
|
|
4
4
|
* Uses Indiekit CSS custom properties for automatic dark mode support
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/* ==========================================================================
|
|
8
|
+
Fediverse Lookup
|
|
9
|
+
========================================================================== */
|
|
10
|
+
|
|
11
|
+
.ap-lookup {
|
|
12
|
+
display: flex;
|
|
13
|
+
gap: var(--space-xs);
|
|
14
|
+
margin-bottom: var(--space-m);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ap-lookup__input {
|
|
18
|
+
flex: 1;
|
|
19
|
+
padding: var(--space-s) var(--space-m);
|
|
20
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
21
|
+
border-radius: var(--border-radius-small);
|
|
22
|
+
background: var(--color-offset);
|
|
23
|
+
color: var(--color-on-background);
|
|
24
|
+
font-size: var(--font-size-m);
|
|
25
|
+
font-family: inherit;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.ap-lookup__input::placeholder {
|
|
29
|
+
color: var(--color-on-offset);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ap-lookup__input:focus {
|
|
33
|
+
outline: 2px solid var(--color-primary);
|
|
34
|
+
outline-offset: -1px;
|
|
35
|
+
border-color: var(--color-primary);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ap-lookup__btn {
|
|
39
|
+
padding: var(--space-s) var(--space-m);
|
|
40
|
+
border: var(--border-width-thin) solid var(--color-primary);
|
|
41
|
+
border-radius: var(--border-radius-small);
|
|
42
|
+
background: var(--color-primary);
|
|
43
|
+
color: var(--color-on-primary);
|
|
44
|
+
font-size: var(--font-size-m);
|
|
45
|
+
font-family: inherit;
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ap-lookup__btn:hover {
|
|
52
|
+
opacity: 0.9;
|
|
53
|
+
}
|
|
54
|
+
|
|
7
55
|
/* ==========================================================================
|
|
8
56
|
Tab Navigation
|
|
9
57
|
========================================================================== */
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
|
|
3
|
-
import { setupFederation } from "./lib/federation-setup.js";
|
|
3
|
+
import { setupFederation, buildPersonActor } from "./lib/federation-setup.js";
|
|
4
4
|
import {
|
|
5
5
|
createFedifyMiddleware,
|
|
6
6
|
} from "./lib/federation-bridge.js";
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
featuredTagsAddController,
|
|
58
58
|
featuredTagsRemoveController,
|
|
59
59
|
} from "./lib/controllers/featured-tags.js";
|
|
60
|
+
import { resolveController } from "./lib/controllers/resolve.js";
|
|
60
61
|
import {
|
|
61
62
|
refollowPauseController,
|
|
62
63
|
refollowResumeController,
|
|
@@ -81,6 +82,7 @@ const defaults = {
|
|
|
81
82
|
redisUrl: "",
|
|
82
83
|
parallelWorkers: 5,
|
|
83
84
|
actorType: "Person",
|
|
85
|
+
logLevel: "warning",
|
|
84
86
|
timelineRetention: 1000,
|
|
85
87
|
notificationRetentionDays: 30,
|
|
86
88
|
};
|
|
@@ -202,6 +204,7 @@ export default class ActivityPubEndpoint {
|
|
|
202
204
|
router.post("/admin/reader/unlike", unlikeController(mp, this));
|
|
203
205
|
router.post("/admin/reader/boost", boostController(mp, this));
|
|
204
206
|
router.post("/admin/reader/unboost", unboostController(mp, this));
|
|
207
|
+
router.get("/admin/reader/resolve", resolveController(mp, this));
|
|
205
208
|
router.get("/admin/reader/profile", remoteProfileController(mp, this));
|
|
206
209
|
router.get("/admin/reader/post", postDetailController(mp, this));
|
|
207
210
|
router.post("/admin/reader/follow", followController(mp, this));
|
|
@@ -687,9 +690,15 @@ export default class ActivityPubEndpoint {
|
|
|
687
690
|
{ handle, publicationUrl: this._publicationUrl },
|
|
688
691
|
);
|
|
689
692
|
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
|
|
693
|
+
// Build the full actor object (same as what the dispatcher serves).
|
|
694
|
+
// Note: ctx.getActor() only exists on RequestContext, not the base
|
|
695
|
+
// Context returned by createContext(), so we use the shared helper.
|
|
696
|
+
const actor = await buildPersonActor(
|
|
697
|
+
ctx,
|
|
698
|
+
handle,
|
|
699
|
+
this._collections,
|
|
700
|
+
this.options.actorType,
|
|
701
|
+
);
|
|
693
702
|
if (!actor) {
|
|
694
703
|
console.warn("[ActivityPub] broadcastActorUpdate: could not build actor");
|
|
695
704
|
return;
|
|
@@ -889,6 +898,7 @@ export default class ActivityPubEndpoint {
|
|
|
889
898
|
publicationUrl: this._publicationUrl,
|
|
890
899
|
parallelWorkers: this.options.parallelWorkers,
|
|
891
900
|
actorType: this.options.actorType,
|
|
901
|
+
logLevel: this.options.logLevel,
|
|
892
902
|
});
|
|
893
903
|
|
|
894
904
|
this._federation = federation;
|
|
@@ -32,7 +32,7 @@ export function followersController(mountPath) {
|
|
|
32
32
|
const cursor = buildCursor(page, totalPages, mountPath + "/admin/followers");
|
|
33
33
|
|
|
34
34
|
response.render("activitypub-followers", {
|
|
35
|
-
title: response.locals.__("activitypub.followers")
|
|
35
|
+
title: `${totalCount} ${response.locals.__("activitypub.followers")}`,
|
|
36
36
|
followers,
|
|
37
37
|
followerCount: totalCount,
|
|
38
38
|
mountPath,
|
|
@@ -32,7 +32,7 @@ export function followingController(mountPath) {
|
|
|
32
32
|
const cursor = buildCursor(page, totalPages, mountPath + "/admin/following");
|
|
33
33
|
|
|
34
34
|
response.render("activitypub-following", {
|
|
35
|
-
title: response.locals.__("activitypub.following")
|
|
35
|
+
title: `${totalCount} ${response.locals.__("activitypub.following")}`,
|
|
36
36
|
following,
|
|
37
37
|
followingCount: totalCount,
|
|
38
38
|
mountPath,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve controller — accepts any fediverse URL or handle, resolves it
|
|
3
|
+
* via lookupObject(), and redirects to the appropriate internal view.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
Article,
|
|
7
|
+
Note,
|
|
8
|
+
Person,
|
|
9
|
+
Service,
|
|
10
|
+
Application,
|
|
11
|
+
Organization,
|
|
12
|
+
Group,
|
|
13
|
+
} from "@fedify/fedify";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* GET /admin/reader/resolve?q=<url-or-handle>
|
|
17
|
+
* Resolves a fediverse URL or @user@domain handle and redirects to
|
|
18
|
+
* the post detail or remote profile view.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveController(mountPath, plugin) {
|
|
21
|
+
return async (request, response, next) => {
|
|
22
|
+
try {
|
|
23
|
+
const query = (request.query.q || "").trim();
|
|
24
|
+
|
|
25
|
+
if (!query) {
|
|
26
|
+
return response.redirect(`${mountPath}/admin/reader`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!plugin._federation) {
|
|
30
|
+
return response.status(503).render("error", {
|
|
31
|
+
title: "Error",
|
|
32
|
+
content: "Federation not initialized",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const handle = plugin.options.actor.handle;
|
|
37
|
+
const ctx = plugin._federation.createContext(
|
|
38
|
+
new URL(plugin._publicationUrl),
|
|
39
|
+
{ handle, publicationUrl: plugin._publicationUrl },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const documentLoader = await ctx.getDocumentLoader({
|
|
43
|
+
identifier: handle,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Determine if input is a URL or a handle
|
|
47
|
+
// lookupObject accepts: URLs, @user@domain, user@domain, acct:user@domain
|
|
48
|
+
let lookupInput;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// If it parses as a URL, pass as URL object
|
|
52
|
+
const parsed = new URL(query);
|
|
53
|
+
lookupInput = parsed;
|
|
54
|
+
} catch {
|
|
55
|
+
// Not a URL — treat as handle (strip leading @ if present)
|
|
56
|
+
lookupInput = query;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let object;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
object = await ctx.lookupObject(lookupInput, { documentLoader });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.warn(
|
|
65
|
+
`[resolve] lookupObject failed for "${query}":`,
|
|
66
|
+
error.message,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!object) {
|
|
71
|
+
return response.status(404).render("error", {
|
|
72
|
+
title: response.locals.__("activitypub.reader.resolve.notFoundTitle"),
|
|
73
|
+
content: response.locals.__(
|
|
74
|
+
"activitypub.reader.resolve.notFound",
|
|
75
|
+
),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Determine object type and redirect accordingly
|
|
80
|
+
const objectUrl =
|
|
81
|
+
object.id?.href || object.url?.href || query;
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
object instanceof Person ||
|
|
85
|
+
object instanceof Service ||
|
|
86
|
+
object instanceof Application ||
|
|
87
|
+
object instanceof Organization ||
|
|
88
|
+
object instanceof Group
|
|
89
|
+
) {
|
|
90
|
+
return response.redirect(
|
|
91
|
+
`${mountPath}/admin/reader/profile?url=${encodeURIComponent(objectUrl)}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (object instanceof Note || object instanceof Article) {
|
|
96
|
+
return response.redirect(
|
|
97
|
+
`${mountPath}/admin/reader/post?url=${encodeURIComponent(objectUrl)}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Unknown type — try post detail as fallback
|
|
102
|
+
return response.redirect(
|
|
103
|
+
`${mountPath}/admin/reader/post?url=${encodeURIComponent(objectUrl)}`,
|
|
104
|
+
);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
next(error);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
package/lib/federation-setup.js
CHANGED
|
@@ -60,6 +60,7 @@ export function setupFederation(options) {
|
|
|
60
60
|
publicationUrl = "",
|
|
61
61
|
parallelWorkers = 5,
|
|
62
62
|
actorType = "Person",
|
|
63
|
+
logLevel = "warning",
|
|
63
64
|
} = options;
|
|
64
65
|
|
|
65
66
|
// Map config string to Fedify actor class
|
|
@@ -67,6 +68,9 @@ export function setupFederation(options) {
|
|
|
67
68
|
const ActorClass = actorTypeMap[actorType] || Person;
|
|
68
69
|
|
|
69
70
|
// Configure LogTape for Fedify delivery logging (once per process)
|
|
71
|
+
// Valid levels: "debug" | "info" | "warning" | "error" | "fatal"
|
|
72
|
+
const validLevels = ["debug", "info", "warning", "error", "fatal"];
|
|
73
|
+
const resolvedLevel = validLevels.includes(logLevel) ? logLevel : "warning";
|
|
70
74
|
if (!_logtapeConfigured) {
|
|
71
75
|
_logtapeConfigured = true;
|
|
72
76
|
configure({
|
|
@@ -79,7 +83,7 @@ export function setupFederation(options) {
|
|
|
79
83
|
// All Fedify logs — federation, vocab, delivery, HTTP signatures
|
|
80
84
|
category: ["fedify"],
|
|
81
85
|
sinks: ["console"],
|
|
82
|
-
lowestLevel:
|
|
86
|
+
lowestLevel: resolvedLevel,
|
|
83
87
|
},
|
|
84
88
|
],
|
|
85
89
|
}).catch((error) => {
|
|
@@ -138,74 +142,7 @@ export function setupFederation(options) {
|
|
|
138
142
|
|
|
139
143
|
if (identifier !== handle) return null;
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
const keyPairs = await ctx.getActorKeyPairs(identifier);
|
|
143
|
-
|
|
144
|
-
const personOptions = {
|
|
145
|
-
id: ctx.getActorUri(identifier),
|
|
146
|
-
preferredUsername: identifier,
|
|
147
|
-
name: profile.name || identifier,
|
|
148
|
-
url: profile.url ? new URL(profile.url) : null,
|
|
149
|
-
inbox: ctx.getInboxUri(identifier),
|
|
150
|
-
outbox: ctx.getOutboxUri(identifier),
|
|
151
|
-
followers: ctx.getFollowersUri(identifier),
|
|
152
|
-
following: ctx.getFollowingUri(identifier),
|
|
153
|
-
liked: ctx.getLikedUri(identifier),
|
|
154
|
-
featured: ctx.getFeaturedUri(identifier),
|
|
155
|
-
featuredTags: ctx.getFeaturedTagsUri(identifier),
|
|
156
|
-
endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
|
|
157
|
-
manuallyApprovesFollowers:
|
|
158
|
-
profile.manuallyApprovesFollowers || false,
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
if (profile.summary) {
|
|
162
|
-
personOptions.summary = profile.summary;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (profile.icon) {
|
|
166
|
-
personOptions.icon = new Image({
|
|
167
|
-
url: new URL(profile.icon),
|
|
168
|
-
mediaType: guessImageMediaType(profile.icon),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (profile.image) {
|
|
173
|
-
personOptions.image = new Image({
|
|
174
|
-
url: new URL(profile.image),
|
|
175
|
-
mediaType: guessImageMediaType(profile.image),
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (keyPairs.length > 0) {
|
|
180
|
-
personOptions.publicKey = keyPairs[0].cryptographicKey;
|
|
181
|
-
personOptions.assertionMethods = keyPairs.map((k) => k.multikey);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (profile.attachments?.length > 0) {
|
|
185
|
-
personOptions.attachments = profile.attachments.map(
|
|
186
|
-
(att) =>
|
|
187
|
-
new PropertyValue({
|
|
188
|
-
name: att.name,
|
|
189
|
-
value: formatAttachmentValue(att.value),
|
|
190
|
-
}),
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (profile.alsoKnownAs?.length > 0) {
|
|
195
|
-
personOptions.alsoKnownAs = profile.alsoKnownAs.map(
|
|
196
|
-
(u) => new URL(u),
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (profile.createdAt) {
|
|
201
|
-
personOptions.published = Temporal.Instant.from(profile.createdAt);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Actor type from profile overrides config default
|
|
205
|
-
const profileActorType = profile.actorType || actorType;
|
|
206
|
-
const ResolvedActorClass = actorTypeMap[profileActorType] || ActorClass;
|
|
207
|
-
|
|
208
|
-
return new ResolvedActorClass(personOptions);
|
|
145
|
+
return buildPersonActor(ctx, identifier, collections, actorType);
|
|
209
146
|
},
|
|
210
147
|
)
|
|
211
148
|
.mapHandle((_ctx, username) => {
|
|
@@ -674,6 +611,90 @@ async function getProfile(collections) {
|
|
|
674
611
|
return doc || {};
|
|
675
612
|
}
|
|
676
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Build the Person/Service/Organization actor object from the stored profile.
|
|
616
|
+
* Used by both the actor dispatcher (for serving the actor to federation
|
|
617
|
+
* requests) and broadcastActorUpdate() (for sending Update activities).
|
|
618
|
+
*
|
|
619
|
+
* @param {object} ctx - Fedify context (base Context or RequestContext)
|
|
620
|
+
* @param {string} identifier - Actor handle (e.g. "rick")
|
|
621
|
+
* @param {object} collections - MongoDB collections
|
|
622
|
+
* @param {string} [defaultActorType="Person"] - Fallback actor type
|
|
623
|
+
* @returns {Promise<import("@fedify/fedify").Actor|null>}
|
|
624
|
+
*/
|
|
625
|
+
export async function buildPersonActor(
|
|
626
|
+
ctx,
|
|
627
|
+
identifier,
|
|
628
|
+
collections,
|
|
629
|
+
defaultActorType = "Person",
|
|
630
|
+
) {
|
|
631
|
+
const actorTypeMap = { Person, Service, Application, Organization, Group };
|
|
632
|
+
const profile = await getProfile(collections);
|
|
633
|
+
const keyPairs = await ctx.getActorKeyPairs(identifier);
|
|
634
|
+
|
|
635
|
+
const personOptions = {
|
|
636
|
+
id: ctx.getActorUri(identifier),
|
|
637
|
+
preferredUsername: identifier,
|
|
638
|
+
name: profile.name || identifier,
|
|
639
|
+
url: profile.url ? new URL(profile.url) : null,
|
|
640
|
+
inbox: ctx.getInboxUri(identifier),
|
|
641
|
+
outbox: ctx.getOutboxUri(identifier),
|
|
642
|
+
followers: ctx.getFollowersUri(identifier),
|
|
643
|
+
following: ctx.getFollowingUri(identifier),
|
|
644
|
+
liked: ctx.getLikedUri(identifier),
|
|
645
|
+
featured: ctx.getFeaturedUri(identifier),
|
|
646
|
+
featuredTags: ctx.getFeaturedTagsUri(identifier),
|
|
647
|
+
endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
|
|
648
|
+
manuallyApprovesFollowers: profile.manuallyApprovesFollowers || false,
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
if (profile.summary) {
|
|
652
|
+
personOptions.summary = profile.summary;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (profile.icon) {
|
|
656
|
+
personOptions.icon = new Image({
|
|
657
|
+
url: new URL(profile.icon),
|
|
658
|
+
mediaType: guessImageMediaType(profile.icon),
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (profile.image) {
|
|
663
|
+
personOptions.image = new Image({
|
|
664
|
+
url: new URL(profile.image),
|
|
665
|
+
mediaType: guessImageMediaType(profile.image),
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (keyPairs.length > 0) {
|
|
670
|
+
personOptions.publicKey = keyPairs[0].cryptographicKey;
|
|
671
|
+
personOptions.assertionMethods = keyPairs.map((k) => k.multikey);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (profile.attachments?.length > 0) {
|
|
675
|
+
personOptions.attachments = profile.attachments.map(
|
|
676
|
+
(att) =>
|
|
677
|
+
new PropertyValue({
|
|
678
|
+
name: att.name,
|
|
679
|
+
value: formatAttachmentValue(att.value),
|
|
680
|
+
}),
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (profile.alsoKnownAs?.length > 0) {
|
|
685
|
+
personOptions.alsoKnownAs = profile.alsoKnownAs.map((u) => new URL(u));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (profile.createdAt) {
|
|
689
|
+
personOptions.published = Temporal.Instant.from(profile.createdAt);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const profileActorType = profile.actorType || defaultActorType;
|
|
693
|
+
const ResolvedActorClass = actorTypeMap[profileActorType] || Person;
|
|
694
|
+
|
|
695
|
+
return new ResolvedActorClass(personOptions);
|
|
696
|
+
}
|
|
697
|
+
|
|
677
698
|
/**
|
|
678
699
|
* Import a PKCS#8 PEM private key using Web Crypto API.
|
|
679
700
|
* Fedify's importPem only handles PKCS#1, but Node.js crypto generates PKCS#8.
|
package/locales/en.json
CHANGED
|
@@ -191,6 +191,13 @@
|
|
|
191
191
|
"loadingThread": "Loading thread...",
|
|
192
192
|
"threadError": "Could not load full thread"
|
|
193
193
|
},
|
|
194
|
+
"resolve": {
|
|
195
|
+
"placeholder": "Paste a fediverse URL or @user@domain handle…",
|
|
196
|
+
"label": "Look up a fediverse post or account",
|
|
197
|
+
"button": "Look up",
|
|
198
|
+
"notFoundTitle": "Not found",
|
|
199
|
+
"notFound": "Could not find this post or account. The URL may be invalid, the server may be unavailable, or the content may have been deleted."
|
|
200
|
+
},
|
|
194
201
|
"linkPreview": {
|
|
195
202
|
"label": "Link preview"
|
|
196
203
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.18",
|
|
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",
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
{% from "pagination/macro.njk" import pagination with context %}
|
|
8
8
|
|
|
9
9
|
{% block content %}
|
|
10
|
-
{{ heading({ text: followingCount + " " + __("activitypub.following"), level: 1 }) }}
|
|
11
|
-
|
|
12
10
|
{% if following.length > 0 %}
|
|
13
11
|
{% for account in following %}
|
|
14
12
|
{% if account.source === "import" %}
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
{% from "prose/macro.njk" import prose with context %}
|
|
5
5
|
|
|
6
6
|
{% block readercontent %}
|
|
7
|
-
{
|
|
7
|
+
{# Fediverse lookup #}
|
|
8
|
+
<form action="{{ mountPath }}/admin/reader/resolve" method="get" class="ap-lookup">
|
|
9
|
+
<input type="text" name="q" class="ap-lookup__input"
|
|
10
|
+
placeholder="{{ __('activitypub.reader.resolve.placeholder') }}"
|
|
11
|
+
aria-label="{{ __('activitypub.reader.resolve.label') }}">
|
|
12
|
+
<button type="submit" class="ap-lookup__btn">{{ __("activitypub.reader.resolve.button") }}</button>
|
|
13
|
+
</form>
|
|
8
14
|
|
|
9
15
|
{# Tab navigation #}
|
|
10
16
|
<nav class="ap-tabs" role="tablist">
|