@rmdes/indiekit-endpoint-microsub 1.0.64 → 1.0.66
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/styles.css +8 -0
- package/lib/cache/redis.js +0 -31
- package/lib/controllers/block.js +0 -2
- package/lib/controllers/channels.js +0 -2
- package/lib/controllers/events.js +0 -2
- package/lib/controllers/follow.js +0 -2
- package/lib/controllers/mute.js +0 -2
- package/lib/controllers/preview.js +0 -2
- package/lib/controllers/reader/index.js +2 -38
- package/lib/controllers/search.js +0 -2
- package/lib/controllers/timeline.js +0 -2
- package/lib/feeds/discovery.js +1 -1
- package/lib/feeds/normalizer.js +2 -2
- package/lib/media/proxy.js +5 -5
- package/lib/polling/processor.js +5 -0
- package/lib/polling/scheduler.js +0 -10
- package/lib/polling/tier.js +6 -51
- package/lib/realtime/broker.js +3 -75
- package/lib/storage/channels.js +1 -1
- package/lib/storage/feeds.js +1 -17
- package/lib/storage/filters.js +26 -197
- package/lib/storage/items.js +72 -15
- package/lib/utils/pagination.js +4 -11
- package/lib/utils/validation.js +0 -10
- package/lib/webmention/processor.js +0 -93
- package/lib/websub/subscriber.js +0 -17
- package/package.json +1 -4
- package/views/partials/item-card-compact.njk +4 -1
- package/views/partials/item-card.njk +10 -8
- package/lib/storage/read-state.js +0 -109
- package/lib/websub/discovery.js +0 -129
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Read state tracking utilities
|
|
3
|
-
* @module storage/read-state
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { markItemsRead, markItemsUnread, getUnreadCount } from "./items-read-state.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Mark entries as read for a user
|
|
10
|
-
* @param {object} application - Indiekit application
|
|
11
|
-
* @param {string} channelUid - Channel UID
|
|
12
|
-
* @param {Array} entries - Entry IDs to mark as read
|
|
13
|
-
* @param {string} userId - User ID
|
|
14
|
-
* @returns {Promise<number>} Number of entries marked
|
|
15
|
-
*/
|
|
16
|
-
export async function markRead(application, channelUid, entries, userId) {
|
|
17
|
-
const channelsCollection = application.collections.get("microsub_channels");
|
|
18
|
-
const channel = await channelsCollection.findOne({ uid: channelUid });
|
|
19
|
-
|
|
20
|
-
if (!channel) {
|
|
21
|
-
return 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return markItemsRead(application, channel._id, entries, userId);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Mark entries as unread for a user
|
|
29
|
-
* @param {object} application - Indiekit application
|
|
30
|
-
* @param {string} channelUid - Channel UID
|
|
31
|
-
* @param {Array} entries - Entry IDs to mark as unread
|
|
32
|
-
* @param {string} userId - User ID
|
|
33
|
-
* @returns {Promise<number>} Number of entries marked
|
|
34
|
-
*/
|
|
35
|
-
export async function markUnread(application, channelUid, entries, userId) {
|
|
36
|
-
const channelsCollection = application.collections.get("microsub_channels");
|
|
37
|
-
const channel = await channelsCollection.findOne({ uid: channelUid });
|
|
38
|
-
|
|
39
|
-
if (!channel) {
|
|
40
|
-
return 0;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return markItemsUnread(application, channel._id, entries, userId);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get unread count for a channel
|
|
48
|
-
* @param {object} application - Indiekit application
|
|
49
|
-
* @param {string} channelUid - Channel UID
|
|
50
|
-
* @param {string} userId - User ID
|
|
51
|
-
* @returns {Promise<number>} Unread count
|
|
52
|
-
*/
|
|
53
|
-
export async function getChannelUnreadCount(application, channelUid, userId) {
|
|
54
|
-
const channelsCollection = application.collections.get("microsub_channels");
|
|
55
|
-
const channel = await channelsCollection.findOne({ uid: channelUid });
|
|
56
|
-
|
|
57
|
-
if (!channel) {
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return getUnreadCount(application, channel._id, userId);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get unread counts for all channels
|
|
66
|
-
* @param {object} application - Indiekit application
|
|
67
|
-
* @param {string} userId - User ID
|
|
68
|
-
* @returns {Promise<Map>} Map of channel UID to unread count
|
|
69
|
-
*/
|
|
70
|
-
export async function getAllUnreadCounts(application, userId) {
|
|
71
|
-
const channelsCollection = application.collections.get("microsub_channels");
|
|
72
|
-
const itemsCollection = application.collections.get("microsub_items");
|
|
73
|
-
|
|
74
|
-
// Aggregate unread counts per channel
|
|
75
|
-
const pipeline = [
|
|
76
|
-
{
|
|
77
|
-
$match: {
|
|
78
|
-
readBy: { $ne: userId },
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
$group: {
|
|
83
|
-
_id: "$channelId",
|
|
84
|
-
count: { $sum: 1 },
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
const results = await itemsCollection.aggregate(pipeline).toArray();
|
|
90
|
-
|
|
91
|
-
// Get channel UIDs
|
|
92
|
-
const channelIds = results.map((r) => r._id);
|
|
93
|
-
const channels = await channelsCollection
|
|
94
|
-
.find({ _id: { $in: channelIds } })
|
|
95
|
-
.toArray();
|
|
96
|
-
|
|
97
|
-
const channelMap = new Map(channels.map((c) => [c._id.toString(), c.uid]));
|
|
98
|
-
|
|
99
|
-
// Build result map
|
|
100
|
-
const unreadCounts = new Map();
|
|
101
|
-
for (const result of results) {
|
|
102
|
-
const uid = channelMap.get(result._id.toString());
|
|
103
|
-
if (uid) {
|
|
104
|
-
unreadCounts.set(uid, result.count);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return unreadCounts;
|
|
109
|
-
}
|
package/lib/websub/discovery.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WebSub hub discovery
|
|
3
|
-
* @module websub/discovery
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Discover WebSub hub from HTTP response headers and content
|
|
8
|
-
* @param {object} response - Fetch response object
|
|
9
|
-
* @param {string} content - Response body content
|
|
10
|
-
* @returns {object|undefined} WebSub info { hub, self }
|
|
11
|
-
*/
|
|
12
|
-
export function discoverWebsub(response, content) {
|
|
13
|
-
// Try to find hub and self URLs from Link headers first
|
|
14
|
-
const linkHeader = response.headers.get("link");
|
|
15
|
-
const fromHeaders = linkHeader ? parseLinkHeader(linkHeader) : {};
|
|
16
|
-
|
|
17
|
-
// Fall back to content parsing
|
|
18
|
-
const fromContent = parseContentForLinks(content);
|
|
19
|
-
|
|
20
|
-
const hub = fromHeaders.hub || fromContent.hub;
|
|
21
|
-
const self = fromHeaders.self || fromContent.self;
|
|
22
|
-
|
|
23
|
-
if (hub) {
|
|
24
|
-
return { hub, self };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Parse Link header for hub and self URLs
|
|
32
|
-
* @param {string} linkHeader - Link header value
|
|
33
|
-
* @returns {object} { hub, self }
|
|
34
|
-
*/
|
|
35
|
-
function parseLinkHeader(linkHeader) {
|
|
36
|
-
const result = {};
|
|
37
|
-
const links = linkHeader.split(",");
|
|
38
|
-
|
|
39
|
-
for (const link of links) {
|
|
40
|
-
const parts = link.trim().split(";");
|
|
41
|
-
if (parts.length < 2) continue;
|
|
42
|
-
|
|
43
|
-
const urlMatch = parts[0].match(/<([^>]+)>/);
|
|
44
|
-
if (!urlMatch) continue;
|
|
45
|
-
|
|
46
|
-
const url = urlMatch[1];
|
|
47
|
-
const relationship = parts
|
|
48
|
-
.slice(1)
|
|
49
|
-
.find((p) => p.trim().startsWith("rel="))
|
|
50
|
-
?.match(/rel=["']?([^"'\s;]+)["']?/)?.[1];
|
|
51
|
-
|
|
52
|
-
if (relationship === "hub") {
|
|
53
|
-
result.hub = url;
|
|
54
|
-
} else if (relationship === "self") {
|
|
55
|
-
result.self = url;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parse content for hub and self URLs (Atom, RSS, HTML)
|
|
64
|
-
* @param {string} content - Response body
|
|
65
|
-
* @returns {object} { hub, self }
|
|
66
|
-
*/
|
|
67
|
-
function parseContentForLinks(content) {
|
|
68
|
-
const result = {};
|
|
69
|
-
|
|
70
|
-
// Try HTML <link> elements
|
|
71
|
-
const htmlHubMatch = content.match(
|
|
72
|
-
/<link[^>]+rel=["']?hub["']?[^>]+href=["']([^"']+)["']/i,
|
|
73
|
-
);
|
|
74
|
-
if (htmlHubMatch) {
|
|
75
|
-
result.hub = htmlHubMatch[1];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const htmlSelfMatch = content.match(
|
|
79
|
-
/<link[^>]+rel=["']?self["']?[^>]+href=["']([^"']+)["']/i,
|
|
80
|
-
);
|
|
81
|
-
if (htmlSelfMatch) {
|
|
82
|
-
result.self = htmlSelfMatch[1];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Also try the reverse order (href before rel)
|
|
86
|
-
if (!result.hub) {
|
|
87
|
-
const htmlHubMatch2 = content.match(
|
|
88
|
-
/<link[^>]+href=["']([^"']+)["'][^>]+rel=["']?hub["']?/i,
|
|
89
|
-
);
|
|
90
|
-
if (htmlHubMatch2) {
|
|
91
|
-
result.hub = htmlHubMatch2[1];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!result.self) {
|
|
96
|
-
const htmlSelfMatch2 = content.match(
|
|
97
|
-
/<link[^>]+href=["']([^"']+)["'][^>]+rel=["']?self["']?/i,
|
|
98
|
-
);
|
|
99
|
-
if (htmlSelfMatch2) {
|
|
100
|
-
result.self = htmlSelfMatch2[1];
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Try Atom <link> elements
|
|
105
|
-
if (!result.hub) {
|
|
106
|
-
const atomHubMatch = content.match(
|
|
107
|
-
/<atom:link[^>]+rel=["']?hub["']?[^>]+href=["']([^"']+)["']/i,
|
|
108
|
-
);
|
|
109
|
-
if (atomHubMatch) {
|
|
110
|
-
result.hub = atomHubMatch[1];
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Check if a hub URL is valid
|
|
119
|
-
* @param {string} hubUrl - Hub URL to validate
|
|
120
|
-
* @returns {boolean} Whether the URL is valid
|
|
121
|
-
*/
|
|
122
|
-
export function isValidHubUrl(hubUrl) {
|
|
123
|
-
try {
|
|
124
|
-
const url = new URL(hubUrl);
|
|
125
|
-
return url.protocol === "https:" || url.protocol === "http:";
|
|
126
|
-
} catch {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|