@sockethub/platform-feeds 4.0.0-alpha.8 → 4.0.0-beta.0

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 CHANGED
@@ -8,7 +8,7 @@ This platform fetches RSS and Atom feeds from URLs and converts feed entries int
8
8
  ActivityStreams objects. It handles various feed formats and provides structured data for
9
9
  web applications to consume feed content.
10
10
 
11
- ## Implemented Verbs (`@type`)
11
+ ## Implemented Verbs (`type`)
12
12
 
13
13
  * **fetch** - Retrieve and parse an RSS or Atom feed
14
14
 
@@ -18,33 +18,82 @@ web applications to consume feed content.
18
18
 
19
19
  ```json
20
20
  {
21
- "@type": "fetch",
22
- "context": "feeds",
21
+ "type": "fetch",
22
+ "@context": [
23
+ "https://www.w3.org/ns/activitystreams",
24
+ "https://sockethub.org/ns/context/v1.jsonld",
25
+ "https://sockethub.org/ns/context/platform/feeds/v1.jsonld"
26
+ ],
23
27
  "actor": {
24
- "@id": "https://example.com/feed.xml"
28
+ "id": "https://example.com/feed.xml",
29
+ "type": "feed"
25
30
  }
26
31
  }
27
32
  ```
28
33
 
34
+ #### Optional Fetch Parameters
35
+
36
+ A fetch request may carry an `object` with filtering parameters. The `object`
37
+ is strictly validated: only the parameters below are accepted, anything else
38
+ is rejected.
39
+
40
+ ```json
41
+ {
42
+ "type": "fetch",
43
+ "@context": [
44
+ "https://www.w3.org/ns/activitystreams",
45
+ "https://sockethub.org/ns/context/v1.jsonld",
46
+ "https://sockethub.org/ns/context/platform/feeds/v1.jsonld"
47
+ ],
48
+ "actor": {
49
+ "id": "https://example.com/feed.xml",
50
+ "type": "feed"
51
+ },
52
+ "object": {
53
+ "since": "2024-01-01T00:00:00.000Z",
54
+ "limit": 10
55
+ }
56
+ }
57
+ ```
58
+
59
+ * **since** (RFC3339 `date-time` string) — only return entries published at or
60
+ after this instant. Entries without a parseable publication date are
61
+ excluded when `since` is set, since they cannot be confirmed as recent
62
+ enough.
63
+ * **limit** (integer, minimum `1`) — return at most this many entries. Applied
64
+ after `since`, preserving feed order.
65
+
29
66
  ### Response Format
30
67
 
31
68
  Returns an ActivityStreams Collection with feed entries:
32
69
 
33
70
  ```json
34
71
  {
35
- "@type": "Collection",
36
- "context": "feeds",
72
+ "type": "collection",
73
+ "@context": [
74
+ "https://www.w3.org/ns/activitystreams",
75
+ "https://sockethub.org/ns/context/v1.jsonld",
76
+ "https://sockethub.org/ns/context/platform/feeds/v1.jsonld"
77
+ ],
78
+ "summary": "Example Blog",
37
79
  "totalItems": 10,
38
80
  "items": [
39
81
  {
40
- "@type": "Create",
82
+ "type": "post",
83
+ "@context": [
84
+ "https://www.w3.org/ns/activitystreams",
85
+ "https://sockethub.org/ns/context/v1.jsonld",
86
+ "https://sockethub.org/ns/context/platform/feeds/v1.jsonld"
87
+ ],
41
88
  "actor": {
42
- "@id": "https://example.com/feed.xml",
89
+ "id": "https://example.com/feed.xml",
90
+ "type": "feed",
43
91
  "name": "Example Blog"
44
92
  },
45
93
  "object": {
46
- "@type": "Article",
47
- "name": "Blog Post Title",
94
+ "id": "https://example.com/post/1",
95
+ "type": "note",
96
+ "title": "Blog Post Title",
48
97
  "content": "Post content...",
49
98
  "url": "https://example.com/post/1",
50
99
  "published": "2023-01-01T12:00:00Z"
@@ -54,6 +103,95 @@ Returns an ActivityStreams Collection with feed entries:
54
103
  }
55
104
  ```
56
105
 
106
+ ### Detailed Response Example
107
+
108
+ ```json
109
+ {
110
+ "@context": [
111
+ "https://www.w3.org/ns/activitystreams",
112
+ "https://sockethub.org/ns/context/v1.jsonld",
113
+ "https://sockethub.org/ns/context/platform/feeds/v1.jsonld"
114
+ ],
115
+ "type": "collection",
116
+ "summary": "Best Feed Inc.",
117
+ "totalItems": 10,
118
+ "items": [
119
+ {
120
+ "@context": [
121
+ "https://www.w3.org/ns/activitystreams",
122
+ "https://sockethub.org/ns/context/v1.jsonld",
123
+ "https://sockethub.org/ns/context/platform/feeds/v1.jsonld"
124
+ ],
125
+ "type": "post",
126
+ "actor": {
127
+ "type": "feed",
128
+ "name": "Best Feed Inc.",
129
+ "id": "http://blog.example.com/rss",
130
+ "description": "Where the best feed comes to be the best",
131
+ "image": "http://blog.example.com/images/bestfeed.jpg",
132
+ "link": "http://blog.example.com",
133
+ "categories": ["best", "feed", "animals"],
134
+ "language": "en",
135
+ "author": "John Doe"
136
+ },
137
+ "object": {
138
+ "id": "http://blog.example.com/articles/about-stuff",
139
+ "type": "note",
140
+ "title": "About stuff...",
141
+ "url": "http://blog.example.com/articles/about-stuff",
142
+ "published": "2013-05-28T12:00:00.000Z",
143
+ "datenum": 1369742400000,
144
+ "brief": "Brief synopsis of stuff...",
145
+ "content": "Once upon a time...",
146
+ "contentType": "text"
147
+ }
148
+ }
149
+ ]
150
+ }
151
+ ```
152
+
153
+ ## Request Hardening (SSRF Guard)
154
+
155
+ Because the server fetches whatever URL a client supplies as `actor.id`, feed
156
+ requests are hardened against server-side request forgery (SSRF) and resource
157
+ exhaustion, using the shared guarded fetch dispatcher from `@sockethub/util`:
158
+
159
+ * Only `http:` and `https:` URLs are accepted.
160
+ * The connection is refused if the destination resolves to a loopback, private,
161
+ link-local, carrier-grade NAT, or otherwise non-public address — validated at
162
+ the connection layer, so it also covers every redirect hop (no
163
+ check-then-fetch rebinding gap). IPv4-mapped, IPv4-compatible, and NAT64 IPv6
164
+ spellings of those ranges are normalized and blocked as well.
165
+ * Response bodies are capped (a missing or lying `Content-Length` does not
166
+ bypass the cap).
167
+ * Non-2xx responses fail the job with a descriptive error.
168
+
169
+ ### `allowPrivateAddresses`
170
+
171
+ Set in the Sockethub config file under this platform's `packageConfig` entry:
172
+
173
+ ```json
174
+ {
175
+ "packageConfig": {
176
+ "@sockethub/platform-feeds": {
177
+ "allowPrivateAddresses": true
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ This disables **only** the private/loopback destination checks. Scheme
184
+ validation, the redirect limit, and the response size cap always remain in
185
+ effect.
186
+
187
+ This is an escape hatch for development and test harnesses that serve feed
188
+ fixtures from `localhost`, or for self-hosted deployments that intentionally
189
+ fetch intranet feeds. **Do not enable it on a server that accepts requests
190
+ from untrusted clients** — it allows any client to make the server issue HTTP
191
+ requests to internal services, including cloud metadata endpoints such as
192
+ `169.254.169.254`. It defaults to off; the full guard is active unless
193
+ `allowPrivateAddresses` is explicitly `true`.
194
+
57
195
  ## Supported Feed Formats
58
196
 
59
197
  * **RSS 2.0**: Standard RSS feeds
@@ -0,0 +1,107 @@
1
+ /**
2
+ * This is a platform for Sockethub implementing Atom/RSS fetching functionality.
3
+ *
4
+ * Developed by Nick Jennings (https://github.com/silverbucket)
5
+ *
6
+ * Sockethub is licensed under the LGPLv3.
7
+ * See the LICENSE file for details.
8
+ *
9
+ * The latest version of this module can be found here:
10
+ * git://github.com/sockethub/sockethub.git
11
+ *
12
+ * For more information about Sockethub visit http://sockethub.org/.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
+ */
18
+ import type { ActivityStream, PlatformCallback, PlatformConfig, PlatformInterface, PlatformSchemaStruct, PlatformSession } from "@sockethub/schemas";
19
+ import { type Episode } from "podparse";
20
+ import { type PlatformFeedsActivityObject, type PlatformFeedsActivityStream } from "./types.js";
21
+ /**
22
+ * Class: Feeds
23
+ *
24
+ * Handles all actions related to fetching feeds.
25
+ *
26
+ * Current supported feed types:
27
+ *
28
+ * - RSS (1 & 2)
29
+ *
30
+ * - Atom
31
+ *
32
+ * Uses the `podparse` module as a base tool fetching feeds.
33
+ *
34
+ * https://github.com/Tombarr/podcast-feed-parser
35
+ *
36
+ */
37
+ export default class Feeds implements PlatformInterface {
38
+ id: string;
39
+ private readonly log;
40
+ private dispatcher?;
41
+ config: PlatformConfig;
42
+ /**
43
+ * @constructor
44
+ * @param session - a unique session object for this platform instance
45
+ */
46
+ constructor(session: PlatformSession);
47
+ get schema(): PlatformSchemaStruct;
48
+ /**
49
+ * Stateless platforms are always ready to handle jobs.
50
+ */
51
+ isInitialized(): boolean;
52
+ /**
53
+ * Fetch feeds from specified source. Upon completion, it will send back a
54
+ * response to the original request with a complete ActivityStreams Collection
55
+ * containing all feed items and metadata.
56
+ *
57
+ * The request may carry an optional `object` with filtering parameters:
58
+ * `since` (RFC3339 date-time; only entries published at or after this
59
+ * instant, undated entries excluded) and `limit` (integer >= 1; cap on
60
+ * returned entries, applied after `since`, feed order preserved).
61
+ *
62
+ * @param job - Activity streams object containing job data with actor.id as feed URL
63
+ * @param done - Callback function that receives (error, ASCollection)
64
+ * See the package README for canonical request and response payload
65
+ * examples.
66
+ */
67
+ fetch(job: ActivityStream, done: PlatformCallback): void;
68
+ /**
69
+ * Cleanup method called when platform instance is being shut down.
70
+ *
71
+ * @param done - Callback function to signal completion
72
+ */
73
+ cleanup(done: PlatformCallback): void;
74
+ /**
75
+ * The SSRF-guarded undici dispatcher, created once per instance (its
76
+ * `allowPrivateAddresses` setting comes from packageConfig, fixed before the
77
+ * first job) and reused so connections/timers are pooled.
78
+ */
79
+ private getDispatcher;
80
+ private makeRequest;
81
+ private fetchFeed;
82
+ }
83
+ /**
84
+ * Optional parameters a client may attach to a `fetch` request `object`.
85
+ * Mirrors the strict `messages.object` schema; both must stay in sync.
86
+ */
87
+ export interface FeedFetchParams {
88
+ since?: string;
89
+ limit?: number;
90
+ }
91
+ /**
92
+ * Extract the supported fetch parameters from a request `object`. Inbound
93
+ * messages are already validated against the strict `messages` schema on the
94
+ * server, so this only needs to read the recognized fields defensively (the
95
+ * platform may also be called directly, e.g. in tests).
96
+ */
97
+ export declare function extractFetchParams(object: unknown): FeedFetchParams;
98
+ /**
99
+ * Apply the `since` and `limit` fetch parameters to built feed entries.
100
+ *
101
+ * - `since`: drop entries published before the given instant. Entries without a
102
+ * parseable date (`datenum === 0`) are excluded when `since` is set, since we
103
+ * cannot confirm they are recent enough. An unparseable `since` is ignored.
104
+ * - `limit`: return at most `limit` entries, preserving feed order.
105
+ */
106
+ export declare function applyFetchFilters(articles: Array<PlatformFeedsActivityStream>, params: FeedFetchParams): Array<PlatformFeedsActivityStream>;
107
+ export declare function buildFeedItem(item: Episode, channelUrl: string): PlatformFeedsActivityObject;