@rmdes/indiekit-endpoint-activitypub 3.7.4 → 3.7.5
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.
|
@@ -41,12 +41,16 @@ export function federationMgmtController(mountPath, plugin) {
|
|
|
41
41
|
|
|
42
42
|
const redisUrl = plugin.options.redisUrl || "";
|
|
43
43
|
|
|
44
|
-
// Parallel: collection stats + posts + recent activities
|
|
45
|
-
const
|
|
44
|
+
// Parallel: collection stats + posts + recent activities + moderation data
|
|
45
|
+
const pluginCollections = plugin._collections || {};
|
|
46
|
+
const [collectionStats, postsResult, recentActivities, blockedServers, blockedAccounts, mutedAccounts] =
|
|
46
47
|
await Promise.all([
|
|
47
48
|
getCollectionStats(collections, { redisUrl }),
|
|
48
49
|
getPaginatedPosts(collections, request.query.page),
|
|
49
50
|
getRecentActivities(collections),
|
|
51
|
+
pluginCollections.ap_blocked_servers?.find({}).sort({ blockedAt: -1 }).toArray() || [],
|
|
52
|
+
pluginCollections.ap_blocked?.find({}).sort({ blockedAt: -1 }).toArray() || [],
|
|
53
|
+
pluginCollections.ap_muted?.find({}).sort({ mutedAt: -1 }).toArray() || [],
|
|
50
54
|
]);
|
|
51
55
|
|
|
52
56
|
const csrfToken = getToken(request.session);
|
|
@@ -62,6 +66,9 @@ export function federationMgmtController(mountPath, plugin) {
|
|
|
62
66
|
posts: postsResult.posts,
|
|
63
67
|
cursor: postsResult.cursor,
|
|
64
68
|
recentActivities,
|
|
69
|
+
blockedServers: blockedServers || [],
|
|
70
|
+
blockedAccounts: blockedAccounts || [],
|
|
71
|
+
mutedAccounts: mutedAccounts || [],
|
|
65
72
|
csrfToken,
|
|
66
73
|
mountPath,
|
|
67
74
|
publicationUrl: plugin._publicationUrl,
|
|
@@ -170,11 +170,12 @@ router.get("/api/v1/accounts/relationships", async (req, res, next) => {
|
|
|
170
170
|
|
|
171
171
|
const collections = req.app.locals.mastodonCollections;
|
|
172
172
|
|
|
173
|
-
const [followers, following, blocked, muted] = await Promise.all([
|
|
173
|
+
const [followers, following, blocked, muted, blockedServers] = await Promise.all([
|
|
174
174
|
collections.ap_followers.find({}).toArray(),
|
|
175
175
|
collections.ap_following.find({}).toArray(),
|
|
176
176
|
collections.ap_blocked.find({}).toArray(),
|
|
177
177
|
collections.ap_muted.find({}).toArray(),
|
|
178
|
+
collections.ap_blocked_servers?.find({}).toArray() || [],
|
|
178
179
|
]);
|
|
179
180
|
|
|
180
181
|
const followerIds = new Set(followers.map((f) => remoteActorId(f.actorUrl)));
|
|
@@ -182,6 +183,21 @@ router.get("/api/v1/accounts/relationships", async (req, res, next) => {
|
|
|
182
183
|
const blockedIds = new Set(blocked.map((b) => remoteActorId(b.url)));
|
|
183
184
|
const mutedIds = new Set(muted.filter((m) => m.url).map((m) => remoteActorId(m.url)));
|
|
184
185
|
|
|
186
|
+
// Build domain-blocked actor ID set by checking known actors against blocked server hostnames
|
|
187
|
+
const blockedDomains = new Set(blockedServers.map((s) => s.hostname).filter(Boolean));
|
|
188
|
+
const domainBlockedIds = new Set();
|
|
189
|
+
if (blockedDomains.size > 0) {
|
|
190
|
+
const allActors = [...followers, ...following];
|
|
191
|
+
for (const actor of allActors) {
|
|
192
|
+
try {
|
|
193
|
+
const domain = new URL(actor.actorUrl).hostname;
|
|
194
|
+
if (blockedDomains.has(domain)) {
|
|
195
|
+
domainBlockedIds.add(remoteActorId(actor.actorUrl));
|
|
196
|
+
}
|
|
197
|
+
} catch { /* skip invalid URLs */ }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
185
201
|
const relationships = ids.map((id) => ({
|
|
186
202
|
id,
|
|
187
203
|
following: followingIds.has(id),
|
|
@@ -195,7 +211,7 @@ router.get("/api/v1/accounts/relationships", async (req, res, next) => {
|
|
|
195
211
|
muting_notifications: mutedIds.has(id),
|
|
196
212
|
requested: false,
|
|
197
213
|
requested_by: false,
|
|
198
|
-
domain_blocking:
|
|
214
|
+
domain_blocking: domainBlockedIds.has(id),
|
|
199
215
|
endorsed: false,
|
|
200
216
|
note: "",
|
|
201
217
|
}));
|
|
@@ -314,8 +314,15 @@ router.get("/api/v1/conversations", (req, res) => {
|
|
|
314
314
|
|
|
315
315
|
// ─── Domain blocks ──────────────────────────────────────────────────────────
|
|
316
316
|
|
|
317
|
-
router.get("/api/v1/domain_blocks", (req, res) => {
|
|
318
|
-
|
|
317
|
+
router.get("/api/v1/domain_blocks", async (req, res) => {
|
|
318
|
+
try {
|
|
319
|
+
const collections = req.app.locals.mastodonCollections;
|
|
320
|
+
if (!collections?.ap_blocked_servers) return res.json([]);
|
|
321
|
+
const docs = await collections.ap_blocked_servers.find({}).toArray();
|
|
322
|
+
res.json(docs.map((d) => d.hostname).filter(Boolean));
|
|
323
|
+
} catch {
|
|
324
|
+
res.json([]);
|
|
325
|
+
}
|
|
319
326
|
});
|
|
320
327
|
|
|
321
328
|
// ─── Endorsements ───────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.5",
|
|
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",
|
|
@@ -116,6 +116,53 @@
|
|
|
116
116
|
{% endif %}
|
|
117
117
|
</section>
|
|
118
118
|
|
|
119
|
+
{# --- Moderation Overview --- #}
|
|
120
|
+
<section class="ap-federation__section">
|
|
121
|
+
<h2>Moderation</h2>
|
|
122
|
+
{% if blockedServers.length > 0 %}
|
|
123
|
+
<h3>Blocked servers ({{ blockedServers.length }})</h3>
|
|
124
|
+
<div class="ap-federation__stats-grid">
|
|
125
|
+
{% for server in blockedServers %}
|
|
126
|
+
<div class="ap-federation__stat-card">
|
|
127
|
+
<span class="ap-federation__stat-label">🚫 {{ server.hostname }}</span>
|
|
128
|
+
{% if server.blockedAt %}
|
|
129
|
+
<span class="ap-federation__stat-count" style="font-size: 0.75rem; opacity: 0.7">{{ server.blockedAt | date("PPp") }}</span>
|
|
130
|
+
{% endif %}
|
|
131
|
+
</div>
|
|
132
|
+
{% endfor %}
|
|
133
|
+
</div>
|
|
134
|
+
{% else %}
|
|
135
|
+
{{ prose({ text: "No servers blocked." }) }}
|
|
136
|
+
{% endif %}
|
|
137
|
+
|
|
138
|
+
{% if blockedAccounts.length > 0 %}
|
|
139
|
+
<h3>Blocked accounts ({{ blockedAccounts.length }})</h3>
|
|
140
|
+
<div class="ap-federation__stats-grid">
|
|
141
|
+
{% for account in blockedAccounts %}
|
|
142
|
+
<div class="ap-federation__stat-card">
|
|
143
|
+
<span class="ap-federation__stat-label">🚫 {{ account.url or account.handle or "Unknown" }}</span>
|
|
144
|
+
{% if account.blockedAt %}
|
|
145
|
+
<span class="ap-federation__stat-count" style="font-size: 0.75rem; opacity: 0.7">{{ account.blockedAt | date("PPp") }}</span>
|
|
146
|
+
{% endif %}
|
|
147
|
+
</div>
|
|
148
|
+
{% endfor %}
|
|
149
|
+
</div>
|
|
150
|
+
{% else %}
|
|
151
|
+
{{ prose({ text: "No accounts blocked." }) }}
|
|
152
|
+
{% endif %}
|
|
153
|
+
|
|
154
|
+
{% if mutedAccounts.length > 0 %}
|
|
155
|
+
<h3>Muted ({{ mutedAccounts.length }})</h3>
|
|
156
|
+
<div class="ap-federation__stats-grid">
|
|
157
|
+
{% for muted in mutedAccounts %}
|
|
158
|
+
<div class="ap-federation__stat-card">
|
|
159
|
+
<span class="ap-federation__stat-label">🔇 {{ muted.url or muted.keyword or "Unknown" }}</span>
|
|
160
|
+
</div>
|
|
161
|
+
{% endfor %}
|
|
162
|
+
</div>
|
|
163
|
+
{% endif %}
|
|
164
|
+
</section>
|
|
165
|
+
|
|
119
166
|
{# --- JSON Modal --- #}
|
|
120
167
|
<div class="ap-federation__modal-overlay" x-show="jsonModalOpen" x-cloak
|
|
121
168
|
@click.self="jsonModalOpen = false" @keydown.escape.window="jsonModalOpen = false">
|