@rmdes/indiekit-endpoint-webmention-io 1.2.0 → 1.2.2
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 +4 -4
- package/lib/controllers/blocklist.js +1 -1
- package/lib/controllers/dashboard.js +7 -2
- package/lib/hcard.js +1 -1
- package/lib/storage/blocklist.js +1 -1
- package/lib/storage/webmentions.js +5 -2
- package/lib/sync.js +9 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Webmention moderation endpoint for [Indiekit](https://getindiekit.com). Syncs we
|
|
|
7
7
|
- **Background Sync**: Automatically fetches webmentions from webmention.io every 15 minutes (configurable)
|
|
8
8
|
- **Moderation Dashboard**: Admin UI for hiding/unhiding webmentions
|
|
9
9
|
- **Domain Blocking**: Block spam domains (hides all mentions, blocks future ones)
|
|
10
|
-
- **Privacy Removal**:
|
|
10
|
+
- **Privacy Removal**: permanently delete a domain's mentions from this database and block it (webmention.io keeps its own copy)
|
|
11
11
|
- **Public JSON API**: Drop-in replacement for webmention.io API with server-side caching
|
|
12
12
|
- **MongoDB Storage**: Persistent storage with indexes for fast queries
|
|
13
13
|
- **Incremental Sync**: Only fetches new webmentions since last sync (efficient)
|
|
@@ -59,7 +59,7 @@ Visit `/webmentions` in your Indiekit admin panel to:
|
|
|
59
59
|
- Filter by type (likes/replies/reposts/mentions)
|
|
60
60
|
- Hide/unhide individual webmentions
|
|
61
61
|
- Block spam domains
|
|
62
|
-
- Remove mentions for privacy requests (
|
|
62
|
+
- Remove mentions for privacy requests (local copy only)
|
|
63
63
|
|
|
64
64
|
### Manual Sync
|
|
65
65
|
|
|
@@ -147,14 +147,14 @@ Body: domain=spam.example.com
|
|
|
147
147
|
- Adds domain to blocklist
|
|
148
148
|
- Future mentions from this domain are filtered during sync
|
|
149
149
|
|
|
150
|
-
#### Privacy removal
|
|
150
|
+
#### Privacy removal
|
|
151
151
|
```bash
|
|
152
152
|
POST /webmentions/privacy-remove
|
|
153
153
|
Body: domain=user-request.example.com
|
|
154
154
|
```
|
|
155
155
|
- **Permanently deletes** all mentions from the domain
|
|
156
156
|
- Adds domain to blocklist with reason="privacy"
|
|
157
|
-
- Irreversible
|
|
157
|
+
- Irreversible here; the mention still exists on webmention.io, ask them separately for removal at the source
|
|
158
158
|
|
|
159
159
|
#### Unblock a domain
|
|
160
160
|
```bash
|
|
@@ -142,7 +142,7 @@ export const dashboardController = {
|
|
|
142
142
|
response.status(500).render("error", {
|
|
143
143
|
title: "Error",
|
|
144
144
|
message: "Failed to load webmentions",
|
|
145
|
-
error: error.message,
|
|
145
|
+
error: error instanceof Error ? error.message : String(error),
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
},
|
|
@@ -248,7 +248,12 @@ export const dashboardController = {
|
|
|
248
248
|
* @type {import("express").RequestHandler}
|
|
249
249
|
*/
|
|
250
250
|
async quarantineDomainHandler(request, response) {
|
|
251
|
-
|
|
251
|
+
// This plug-in hangs its mount path and database getter on the shared
|
|
252
|
+
// application object at init(); the global type does not know them.
|
|
253
|
+
const application =
|
|
254
|
+
/** @type {IndiekitApplication & { webmentionEndpoint: string, getWebmentionDb: () => { collection: (name: string) => object } }} */ (
|
|
255
|
+
request.app.locals.application
|
|
256
|
+
);
|
|
252
257
|
|
|
253
258
|
try {
|
|
254
259
|
const { domain, reason } = request.body;
|
package/lib/hcard.js
CHANGED
|
@@ -52,7 +52,7 @@ export async function discoverAuthorData(domain, cacheCollection) {
|
|
|
52
52
|
result = await fetchHcardData(domain);
|
|
53
53
|
} catch (error) {
|
|
54
54
|
console.log(
|
|
55
|
-
`[Webmentions] h-card discovery failed for ${domain}: ${error.message}`,
|
|
55
|
+
`[Webmentions] h-card discovery failed for ${domain}: ${error instanceof Error ? error.message : String(error)}`,
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
package/lib/storage/blocklist.js
CHANGED
|
@@ -44,7 +44,7 @@ export async function blockDomain(
|
|
|
44
44
|
return true;
|
|
45
45
|
} catch (error) {
|
|
46
46
|
// Duplicate key — domain already blocked
|
|
47
|
-
if (error.code === 11_000) {
|
|
47
|
+
if (/** @type {{ code?: number }} */ (error).code === 11_000) {
|
|
48
48
|
// Update reason and count
|
|
49
49
|
// Re-applying an action to a listed domain also moves it between
|
|
50
50
|
// quarantine and block, which is how "promote to block" works.
|
|
@@ -176,7 +176,7 @@ export async function getWebmentions(collection, options = {}) {
|
|
|
176
176
|
/**
|
|
177
177
|
* Get webmention counts
|
|
178
178
|
* @param {object} collection - MongoDB collection
|
|
179
|
-
* @returns {Promise<{total: number, hidden: number, visible: number}>}
|
|
179
|
+
* @returns {Promise<{total: number, hidden: number, quarantined: number, visible: number}>}
|
|
180
180
|
*/
|
|
181
181
|
export async function getWebmentionCounts(collection) {
|
|
182
182
|
const total = await collection.countDocuments({});
|
|
@@ -263,7 +263,10 @@ export async function hideByDomain(collection, domain, reason = "blocklist") {
|
|
|
263
263
|
*/
|
|
264
264
|
export async function unhideByDomain(collection, domain) {
|
|
265
265
|
const result = await collection.updateMany(
|
|
266
|
-
{
|
|
266
|
+
{
|
|
267
|
+
sourceDomain: normaliseDomain(domain) ?? domain,
|
|
268
|
+
hiddenReason: "blocklist",
|
|
269
|
+
},
|
|
267
270
|
{ $set: { hidden: false, hiddenAt: null, hiddenReason: null } },
|
|
268
271
|
);
|
|
269
272
|
return result.modifiedCount;
|
package/lib/sync.js
CHANGED
|
@@ -189,10 +189,11 @@ export async function runSync(dbOrIndiekit, options) {
|
|
|
189
189
|
mentionsEnriched: enriched,
|
|
190
190
|
};
|
|
191
191
|
} catch (error) {
|
|
192
|
-
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
syncState.lastError = message;
|
|
193
194
|
syncState.syncing = false;
|
|
194
|
-
console.error("[Webmentions] Sync failed:",
|
|
195
|
-
return { error:
|
|
195
|
+
console.error("[Webmentions] Sync failed:", message);
|
|
196
|
+
return { error: message };
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
199
|
|
|
@@ -297,10 +298,11 @@ export async function runFullSync(dbOrIndiekit, options) {
|
|
|
297
298
|
mentionsEnriched: enriched,
|
|
298
299
|
};
|
|
299
300
|
} catch (error) {
|
|
300
|
-
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
syncState.lastError = message;
|
|
301
303
|
syncState.syncing = false;
|
|
302
|
-
console.error("[Webmentions] Full sync failed:",
|
|
303
|
-
return { error:
|
|
304
|
+
console.error("[Webmentions] Full sync failed:", message);
|
|
305
|
+
return { error: message };
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
308
|
|
|
@@ -344,7 +346,7 @@ async function enrichMissingPhotos(db, wmCollection) {
|
|
|
344
346
|
await delay(200);
|
|
345
347
|
}
|
|
346
348
|
} catch (error) {
|
|
347
|
-
console.error("[Webmentions] h-card enrichment error:", error
|
|
349
|
+
console.error("[Webmentions] h-card enrichment error:", error);
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
return totalUpdated;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-webmention-io",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Webmention moderation endpoint for Indiekit. Syncs webmentions from webmention.io into MongoDB with delete, block, and privacy removal capabilities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|