@rmdes/indiekit-endpoint-rss 1.0.2 → 1.0.3
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/lib/rss-client.js +61 -11
- package/lib/utils.js +17 -1
- package/package.json +1 -1
package/lib/rss-client.js
CHANGED
|
@@ -158,26 +158,35 @@ export class RssClient {
|
|
|
158
158
|
!cat.startsWith("user/")
|
|
159
159
|
);
|
|
160
160
|
|
|
161
|
+
// For aggregators like FreshRSS, use origin info for the real source
|
|
162
|
+
const originTitle = item.origin?.title || null;
|
|
163
|
+
const originUrl = item.origin?.htmlUrl || null;
|
|
164
|
+
|
|
165
|
+
// Extract a meaningful description/summary
|
|
166
|
+
const description = this.extractDescription(content, item.summary);
|
|
167
|
+
|
|
161
168
|
return {
|
|
162
169
|
guid: item["frss:id"] || item.id || item.guid || link,
|
|
163
170
|
title: item.title || "Untitled",
|
|
164
171
|
link,
|
|
165
|
-
description
|
|
166
|
-
(typeof item.summary === "string" ? item.summary : "") ||
|
|
167
|
-
"",
|
|
172
|
+
description,
|
|
168
173
|
content,
|
|
169
|
-
author
|
|
174
|
+
// For author, prefer the actual author field, fall back to origin title only if no author
|
|
175
|
+
author: item.author || null,
|
|
170
176
|
pubDate,
|
|
171
|
-
imageUrl: this.extractJsonItemImage(item),
|
|
177
|
+
imageUrl: this.extractJsonItemImage(item, content),
|
|
172
178
|
categories,
|
|
173
179
|
enclosure: item.enclosure || null,
|
|
174
|
-
// Preserve FreshRSS-specific metadata
|
|
180
|
+
// Preserve FreshRSS-specific metadata - use origin for real source feed
|
|
175
181
|
origin: item.origin ? {
|
|
176
182
|
streamId: item.origin.streamId,
|
|
177
183
|
title: item.origin.title,
|
|
178
184
|
htmlUrl: item.origin.htmlUrl,
|
|
179
185
|
feedUrl: item.origin.feedUrl,
|
|
180
186
|
} : null,
|
|
187
|
+
// Denormalized fields for easier display
|
|
188
|
+
sourceTitle: originTitle,
|
|
189
|
+
sourceUrl: originUrl,
|
|
181
190
|
};
|
|
182
191
|
});
|
|
183
192
|
|
|
@@ -226,30 +235,71 @@ export class RssClient {
|
|
|
226
235
|
/**
|
|
227
236
|
* Extract image from JSON feed item
|
|
228
237
|
* @param {Object} item - JSON item
|
|
238
|
+
* @param {string} content - Pre-extracted content string
|
|
229
239
|
* @returns {string|null}
|
|
230
240
|
*/
|
|
231
|
-
extractJsonItemImage(item) {
|
|
241
|
+
extractJsonItemImage(item, content = "") {
|
|
232
242
|
// Direct image property
|
|
233
243
|
if (item.image) return item.image;
|
|
234
244
|
|
|
235
|
-
// Media content
|
|
245
|
+
// Media content (various formats)
|
|
236
246
|
if (item.media?.$?.url) return item.media.$.url;
|
|
247
|
+
if (item["media:content"]?.["$"]?.url) return item["media:content"]["$"].url;
|
|
248
|
+
if (item["media:thumbnail"]?.["$"]?.url) return item["media:thumbnail"]["$"].url;
|
|
237
249
|
|
|
238
250
|
// Enclosure
|
|
239
251
|
if (item.enclosure?.href && this.isImageUrl(item.enclosure.href)) {
|
|
240
252
|
return item.enclosure.href;
|
|
241
253
|
}
|
|
254
|
+
if (item.enclosure?.url && this.isImageUrl(item.enclosure.url)) {
|
|
255
|
+
return item.enclosure.url;
|
|
256
|
+
}
|
|
242
257
|
|
|
243
|
-
// Extract from content
|
|
244
|
-
|
|
245
|
-
if (typeof content === "string") {
|
|
258
|
+
// Extract from content HTML
|
|
259
|
+
if (typeof content === "string" && content.length > 0) {
|
|
246
260
|
const imgMatch = content.match(/<img[^>]+src=["']([^"']+)["']/i);
|
|
247
261
|
if (imgMatch) return imgMatch[1];
|
|
248
262
|
}
|
|
249
263
|
|
|
264
|
+
// Try item's own content fields
|
|
265
|
+
const itemContent = item.content?.content || item.content || item.summary?.content || "";
|
|
266
|
+
if (typeof itemContent === "string" && itemContent.length > 0) {
|
|
267
|
+
const imgMatch = itemContent.match(/<img[^>]+src=["']([^"']+)["']/i);
|
|
268
|
+
if (imgMatch) return imgMatch[1];
|
|
269
|
+
}
|
|
270
|
+
|
|
250
271
|
return null;
|
|
251
272
|
}
|
|
252
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Extract a clean description/summary from content
|
|
276
|
+
* @param {string} content - HTML content
|
|
277
|
+
* @param {Object|string} summary - Summary object or string
|
|
278
|
+
* @returns {string}
|
|
279
|
+
*/
|
|
280
|
+
extractDescription(content, summary) {
|
|
281
|
+
// First try the summary
|
|
282
|
+
let desc = "";
|
|
283
|
+
if (typeof summary === "string") {
|
|
284
|
+
desc = summary;
|
|
285
|
+
} else if (summary?.content) {
|
|
286
|
+
desc = summary.content;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// If no summary, extract from content
|
|
290
|
+
if (!desc && typeof content === "string" && content.length > 0) {
|
|
291
|
+
// Strip HTML tags and get first 300 chars
|
|
292
|
+
desc = content
|
|
293
|
+
.replace(/<[^>]+>/g, " ") // Remove HTML tags
|
|
294
|
+
.replace(/\s+/g, " ") // Normalize whitespace
|
|
295
|
+
.trim()
|
|
296
|
+
.slice(0, 300);
|
|
297
|
+
if (content.length > 300) desc += "...";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return desc;
|
|
301
|
+
}
|
|
302
|
+
|
|
253
303
|
/**
|
|
254
304
|
* Extract feed metadata
|
|
255
305
|
* @param {Object} parsed - Parsed feed object
|
package/lib/utils.js
CHANGED
|
@@ -99,6 +99,14 @@ function isImageType(type) {
|
|
|
99
99
|
export function formatItem(item, options = {}) {
|
|
100
100
|
const { includeContent = false, descriptionLength = 200 } = options;
|
|
101
101
|
|
|
102
|
+
// Handle description - use item.description if available, otherwise generate from content
|
|
103
|
+
let description = item.description;
|
|
104
|
+
if (!description && item.content) {
|
|
105
|
+
description = truncateText(item.content, descriptionLength);
|
|
106
|
+
} else if (description) {
|
|
107
|
+
description = truncateText(description, descriptionLength);
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
const formatted = {
|
|
103
111
|
id: item._id?.toString(),
|
|
104
112
|
feedId: item.feedId?.toString(),
|
|
@@ -106,14 +114,22 @@ export function formatItem(item, options = {}) {
|
|
|
106
114
|
guid: item.guid,
|
|
107
115
|
title: item.title,
|
|
108
116
|
link: item.link,
|
|
109
|
-
description:
|
|
117
|
+
description: description || "",
|
|
110
118
|
author: item.author,
|
|
111
119
|
pubDate: item.pubDate?.toISOString(),
|
|
112
120
|
imageUrl: item.imageUrl,
|
|
113
121
|
categories: item.categories || [],
|
|
114
122
|
fetchedAt: item.fetchedAt?.toISOString(),
|
|
123
|
+
// Source info for aggregators (like FreshRSS) - represents the original feed
|
|
124
|
+
sourceTitle: item.sourceTitle || null,
|
|
125
|
+
sourceUrl: item.sourceUrl || null,
|
|
115
126
|
};
|
|
116
127
|
|
|
128
|
+
// Include origin object if present (for aggregator metadata)
|
|
129
|
+
if (item.origin) {
|
|
130
|
+
formatted.origin = item.origin;
|
|
131
|
+
}
|
|
132
|
+
|
|
117
133
|
if (includeContent) {
|
|
118
134
|
formatted.content = sanitizeHtml(item.content);
|
|
119
135
|
}
|
package/package.json
CHANGED