@rmdes/indiekit-endpoint-microsub 1.0.63 → 1.0.65

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.
Files changed (38) hide show
  1. package/lib/cache/redis.js +0 -31
  2. package/lib/controllers/block.js +0 -2
  3. package/lib/controllers/channels.js +0 -2
  4. package/lib/controllers/events.js +0 -2
  5. package/lib/controllers/follow.js +0 -2
  6. package/lib/controllers/mute.js +0 -2
  7. package/lib/controllers/preview.js +0 -2
  8. package/lib/controllers/reader/channel.js +0 -1
  9. package/lib/controllers/reader/index.js +2 -38
  10. package/lib/controllers/search.js +0 -2
  11. package/lib/controllers/timeline.js +0 -2
  12. package/lib/feeds/discovery.js +1 -1
  13. package/lib/feeds/normalizer.js +2 -2
  14. package/lib/media/proxy.js +5 -5
  15. package/lib/polling/scheduler.js +4 -14
  16. package/lib/polling/tier.js +6 -51
  17. package/lib/realtime/broker.js +3 -75
  18. package/lib/storage/channels.js +1 -1
  19. package/lib/storage/feeds.js +1 -17
  20. package/lib/storage/filters.js +26 -197
  21. package/lib/storage/items-read-state.js +30 -59
  22. package/lib/storage/items-retention.js +1 -3
  23. package/lib/storage/items.js +0 -14
  24. package/lib/utils/blogroll-notify.js +3 -3
  25. package/lib/utils/constants.js +7 -0
  26. package/lib/utils/jf2.js +0 -109
  27. package/lib/utils/pagination.js +4 -11
  28. package/lib/utils/sanitize.js +1 -2
  29. package/lib/utils/validation.js +0 -10
  30. package/lib/webmention/processor.js +2 -95
  31. package/lib/websub/handler.js +2 -2
  32. package/lib/websub/subscriber.js +0 -17
  33. package/locales/en.json +3 -27
  34. package/package.json +1 -4
  35. package/lib/search/indexer.js +0 -90
  36. package/lib/storage/items-search.js +0 -34
  37. package/lib/storage/read-state.js +0 -109
  38. package/lib/websub/discovery.js +0 -129
@@ -1,117 +1,22 @@
1
1
  /**
2
- * Filter storage operations (mute, block, channel filters)
2
+ * Item filtering: exclude-types and exclude-regex predicates applied during
3
+ * feed ingestion. Used by `lib/polling/processor.js` when deciding whether a
4
+ * newly-parsed item should be stored.
5
+ *
6
+ * Historical note: this module previously also held mute/block storage
7
+ * (microsub_muted / microsub_blocked operations). That code path was abandoned
8
+ * — those collections are managed directly by `lib/controllers/mute.js` and
9
+ * `lib/controllers/block.js` against MongoDB without an intermediate storage
10
+ * helper layer. Only the per-channel exclude filters remain here.
11
+ *
3
12
  * @module storage/filters
4
13
  */
5
14
 
6
- import { ObjectId } from "mongodb";
7
-
8
- /**
9
- * Get muted collection
10
- * @param {object} application - Indiekit application
11
- * @returns {object} MongoDB collection
12
- */
13
- function getMutedCollection(application) {
14
- return application.collections.get("microsub_muted");
15
- }
16
-
17
- /**
18
- * Get blocked collection
19
- * @param {object} application - Indiekit application
20
- * @returns {object} MongoDB collection
21
- */
22
- function getBlockedCollection(application) {
23
- return application.collections.get("microsub_blocked");
24
- }
25
-
26
- /**
27
- * Check if a URL is muted for a user/channel
28
- * @param {object} application - Indiekit application
29
- * @param {string} userId - User ID
30
- * @param {ObjectId|string} channelId - Channel ObjectId
31
- * @param {string} url - URL to check
32
- * @returns {Promise<boolean>} Whether the URL is muted
33
- */
34
- export async function isMuted(application, userId, channelId, url) {
35
- const collection = getMutedCollection(application);
36
- const channelObjectId =
37
- typeof channelId === "string" ? new ObjectId(channelId) : channelId;
38
-
39
- // Check for channel-specific mute
40
- const channelMute = await collection.findOne({
41
- userId,
42
- channelId: channelObjectId,
43
- url,
44
- });
45
- if (channelMute) return true;
46
-
47
- // Check for global mute (no channelId)
48
- const globalMute = await collection.findOne({
49
- userId,
50
- channelId: { $exists: false },
51
- url,
52
- });
53
- return !!globalMute;
54
- }
55
-
56
- /**
57
- * Check if a URL is blocked for a user
58
- * @param {object} application - Indiekit application
59
- * @param {string} userId - User ID
60
- * @param {string} url - URL to check
61
- * @returns {Promise<boolean>} Whether the URL is blocked
62
- */
63
- export async function isBlocked(application, userId, url) {
64
- const collection = getBlockedCollection(application);
65
- const blocked = await collection.findOne({ userId, url });
66
- return !!blocked;
67
- }
68
-
69
- /**
70
- * Check if an item passes all filters
71
- * @param {object} application - Indiekit application
72
- * @param {string} userId - User ID
73
- * @param {object} channel - Channel document with settings
74
- * @param {object} item - Feed item to check
75
- * @returns {Promise<boolean>} Whether the item passes all filters
76
- */
77
- export async function passesAllFilters(application, userId, channel, item) {
78
- // Check if author URL is blocked
79
- if (
80
- item.author?.url &&
81
- (await isBlocked(application, userId, item.author.url))
82
- ) {
83
- return false;
84
- }
85
-
86
- // Check if source URL is muted
87
- if (
88
- item._source?.url &&
89
- (await isMuted(application, userId, channel._id, item._source.url))
90
- ) {
91
- return false;
92
- }
93
-
94
- // Check channel settings filters
95
- if (channel?.settings) {
96
- // Check excludeTypes
97
- if (!passesTypeFilter(item, channel.settings)) {
98
- return false;
99
- }
100
-
101
- // Check excludeRegex
102
- if (!passesRegexFilter(item, channel.settings)) {
103
- return false;
104
- }
105
- }
106
-
107
- return true;
108
- }
109
-
110
15
  /**
111
- * Check if an item passes the excludeTypes filter
16
+ * Check if an item passes the channel.settings.excludeTypes filter.
112
17
  * @param {object} item - Feed item
113
18
  * @param {object} settings - Channel settings
114
- * @returns {boolean} Whether the item passes
19
+ * @returns {boolean} True when the item should be kept
115
20
  */
116
21
  export function passesTypeFilter(item, settings) {
117
22
  if (!settings.excludeTypes || settings.excludeTypes.length === 0) {
@@ -123,10 +28,10 @@ export function passesTypeFilter(item, settings) {
123
28
  }
124
29
 
125
30
  /**
126
- * Check if an item passes the excludeRegex filter
31
+ * Check if an item passes the channel.settings.excludeRegex filter.
127
32
  * @param {object} item - Feed item
128
33
  * @param {object} settings - Channel settings
129
- * @returns {boolean} Whether the item passes
34
+ * @returns {boolean} True when the item should be kept
130
35
  */
131
36
  export function passesRegexFilter(item, settings) {
132
37
  if (!settings.excludeRegex) {
@@ -146,17 +51,26 @@ export function passesRegexFilter(item, settings) {
146
51
 
147
52
  return !regex.test(searchText);
148
53
  } catch {
149
- // Invalid regex, skip filter
54
+ // Invalid regex skip the filter rather than rejecting every item.
150
55
  return true;
151
56
  }
152
57
  }
153
58
 
154
59
  /**
155
- * Detect the interaction type of an item
60
+ * Classify an item by its interaction property. Internal helper for
61
+ * passesTypeFilter — only its symbolic return value (one of "like" | "repost"
62
+ * | "bookmark" | "reply" | "rsvp" | "checkin" | "post") is compared against
63
+ * the excludeTypes list.
64
+ *
65
+ * Note: a similar but stricter classifier exists in `lib/utils/jf2.js` for
66
+ * API-response shaping. The two cannot trivially be merged because this one
67
+ * treats kebab-case keys only and emits a "post" default that the jf2 one
68
+ * doesn't.
69
+ *
156
70
  * @param {object} item - Feed item
157
71
  * @returns {string} Interaction type
158
72
  */
159
- export function detectInteractionType(item) {
73
+ function detectInteractionType(item) {
160
74
  if (item["like-of"] && item["like-of"].length > 0) {
161
75
  return "like";
162
76
  }
@@ -178,88 +92,3 @@ export function detectInteractionType(item) {
178
92
 
179
93
  return "post";
180
94
  }
181
-
182
- /**
183
- * Get all muted URLs for a user/channel
184
- * @param {object} application - Indiekit application
185
- * @param {string} userId - User ID
186
- * @param {ObjectId|string} [channelId] - Channel ObjectId (optional, for channel-specific)
187
- * @returns {Promise<Array>} Array of muted URLs
188
- */
189
- export async function getMutedUrls(application, userId, channelId) {
190
- const collection = getMutedCollection(application);
191
- const filter = { userId };
192
-
193
- if (channelId) {
194
- const channelObjectId =
195
- typeof channelId === "string" ? new ObjectId(channelId) : channelId;
196
- filter.channelId = channelObjectId;
197
- }
198
-
199
- // eslint-disable-next-line unicorn/no-array-callback-reference -- filter is MongoDB query object
200
- const muted = await collection.find(filter).toArray();
201
- return muted.map((m) => m.url);
202
- }
203
-
204
- /**
205
- * Get all blocked URLs for a user
206
- * @param {object} application - Indiekit application
207
- * @param {string} userId - User ID
208
- * @returns {Promise<Array>} Array of blocked URLs
209
- */
210
- export async function getBlockedUrls(application, userId) {
211
- const collection = getBlockedCollection(application);
212
- const blocked = await collection.find({ userId }).toArray();
213
- return blocked.map((b) => b.url);
214
- }
215
-
216
- /**
217
- * Update channel filter settings
218
- * @param {object} application - Indiekit application
219
- * @param {ObjectId|string} channelId - Channel ObjectId
220
- * @param {object} filters - Filter settings to update
221
- * @param {Array} [filters.excludeTypes] - Post types to exclude
222
- * @param {string} [filters.excludeRegex] - Regex pattern to exclude
223
- * @returns {Promise<object>} Updated channel
224
- */
225
- export async function updateChannelFilters(application, channelId, filters) {
226
- const collection = application.collections.get("microsub_channels");
227
- const channelObjectId =
228
- typeof channelId === "string" ? new ObjectId(channelId) : channelId;
229
-
230
- const updateFields = {};
231
-
232
- if (filters.excludeTypes !== undefined) {
233
- updateFields["settings.excludeTypes"] = filters.excludeTypes;
234
- }
235
-
236
- if (filters.excludeRegex !== undefined) {
237
- updateFields["settings.excludeRegex"] = filters.excludeRegex;
238
- }
239
-
240
- const result = await collection.findOneAndUpdate(
241
- { _id: channelObjectId },
242
- { $set: updateFields },
243
- { returnDocument: "after" },
244
- );
245
-
246
- return result;
247
- }
248
-
249
- /**
250
- * Create indexes for filter collections
251
- * @param {object} application - Indiekit application
252
- * @returns {Promise<void>}
253
- */
254
- export async function createFilterIndexes(application) {
255
- const mutedCollection = getMutedCollection(application);
256
- const blockedCollection = getBlockedCollection(application);
257
-
258
- // Muted collection indexes
259
- await mutedCollection.createIndex({ userId: 1, channelId: 1, url: 1 });
260
- await mutedCollection.createIndex({ userId: 1 });
261
-
262
- // Blocked collection indexes
263
- await blockedCollection.createIndex({ userId: 1, url: 1 }, { unique: true });
264
- await blockedCollection.createIndex({ userId: 1 });
265
- }
@@ -5,22 +5,17 @@
5
5
 
6
6
  import { ObjectId } from "mongodb";
7
7
 
8
- import { UNREAD_RETENTION_DAYS } from "../utils/constants.js";
8
+ import {
9
+ MAX_FULL_READ_ITEMS,
10
+ UNREAD_RETENTION_DAYS,
11
+ } from "../utils/constants.js";
9
12
  import { getCollection } from "./items.js";
10
13
 
11
- // Maximum number of full read items to keep per channel before stripping content.
12
- // Items beyond this limit are converted to lightweight dedup skeletons (channelId,
13
- // uid, readBy) so the poller doesn't re-ingest them as new unread entries.
14
- const MAX_FULL_READ_ITEMS = 200;
15
-
16
14
  /**
17
15
  * Cleanup old read items by stripping content but preserving dedup skeletons.
18
- * This prevents the vicious cycle where deleted read items get re-ingested as
16
+ * Prevents the vicious cycle where deleted read items get re-ingested as
19
17
  * unread by the poller because the dedup record (channelId + uid) was destroyed.
20
18
  *
21
- * AP items (feedId: null) are hard-deleted instead of stripped, since no poller
22
- * re-ingests them — they arrive via inbox push and don't need dedup skeletons.
23
- *
24
19
  * @param {object} collection - MongoDB collection
25
20
  * @param {ObjectId} channelObjectId - Channel ObjectId
26
21
  * @param {string} userId - User ID
@@ -32,7 +27,6 @@ async function cleanupOldReadItems(collection, channelObjectId, userId) {
32
27
  });
33
28
 
34
29
  if (readCount > MAX_FULL_READ_ITEMS) {
35
- // Find old read items beyond the retention limit
36
30
  const itemsToCleanup = await collection
37
31
  .find({
38
32
  channelId: channelObjectId,
@@ -41,59 +35,36 @@ async function cleanupOldReadItems(collection, channelObjectId, userId) {
41
35
  })
42
36
  .sort({ published: -1, _id: -1 })
43
37
  .skip(MAX_FULL_READ_ITEMS)
44
- .project({ _id: 1, feedId: 1 })
38
+ .project({ _id: 1 })
45
39
  .toArray();
46
40
 
47
41
  if (itemsToCleanup.length === 0) return;
48
42
 
49
- // Separate AP items (feedId: null) from RSS items (feedId: ObjectId)
50
- const apItemIds = [];
51
- const rssItemIds = [];
52
- for (const item of itemsToCleanup) {
53
- if (item.feedId) {
54
- rssItemIds.push(item._id);
55
- } else {
56
- apItemIds.push(item._id);
57
- }
58
- }
59
-
60
- // Hard-delete AP items — no poller to re-ingest, skeletons are useless
61
- if (apItemIds.length > 0) {
62
- const deleted = await collection.deleteMany({
63
- _id: { $in: apItemIds },
64
- });
65
- console.info(
66
- `[Microsub] Deleted ${deleted.deletedCount} old AP read items`,
67
- );
68
- }
69
-
70
- // Strip RSS items to dedup skeletons — poller would re-ingest if deleted
71
- if (rssItemIds.length > 0) {
72
- const stripped = await collection.updateMany(
73
- { _id: { $in: rssItemIds } },
74
- {
75
- $set: { _stripped: true },
76
- $unset: {
77
- name: "",
78
- content: "",
79
- summary: "",
80
- author: "",
81
- category: "",
82
- photo: "",
83
- video: "",
84
- audio: "",
85
- likeOf: "",
86
- repostOf: "",
87
- bookmarkOf: "",
88
- inReplyTo: "",
89
- source: "",
90
- },
43
+ const ids = itemsToCleanup.map((item) => item._id);
44
+ const stripped = await collection.updateMany(
45
+ { _id: { $in: ids } },
46
+ {
47
+ $set: { _stripped: true },
48
+ $unset: {
49
+ name: "",
50
+ content: "",
51
+ summary: "",
52
+ author: "",
53
+ category: "",
54
+ photo: "",
55
+ video: "",
56
+ audio: "",
57
+ likeOf: "",
58
+ repostOf: "",
59
+ bookmarkOf: "",
60
+ inReplyTo: "",
61
+ source: "",
91
62
  },
92
- );
93
- console.info(
94
- `[Microsub] Stripped ${stripped.modifiedCount} old RSS read items (keeping ${MAX_FULL_READ_ITEMS} full)`,
95
- );
96
- }
63
+ },
64
+ );
65
+ console.info(
66
+ `[Microsub] Stripped ${stripped.modifiedCount} old read items (keeping ${MAX_FULL_READ_ITEMS} full)`,
67
+ );
97
68
  }
98
69
  }
99
70
 
@@ -3,11 +3,9 @@
3
3
  * @module storage/items-retention
4
4
  */
5
5
 
6
+ import { MAX_FULL_READ_ITEMS } from "../utils/constants.js";
6
7
  import { getCollection } from "./items.js";
7
8
 
8
- // Maximum number of full read items to keep per channel before stripping content.
9
- const MAX_FULL_READ_ITEMS = 200;
10
-
11
9
  // Global retention defaults. Each can be overridden per channel via
12
10
  // channel.settings.{maxItems,maxItemsPerFeed,maxUnreadAgeDays}. The "notifications"
13
11
  // channel is exempt from these caps entirely — webmentions are high-signal and
@@ -323,20 +323,6 @@ export async function getItemById(application, id, userId) {
323
323
  return transformToJf2(item, userId);
324
324
  }
325
325
 
326
- /**
327
- * Get items by UIDs
328
- * @param {object} application - Indiekit application
329
- * @param {Array} uids - Array of item UIDs
330
- * @param {string} [userId] - User ID for read state
331
- * @returns {Promise<Array>} Array of jf2 items
332
- */
333
- export async function getItemsByUids(application, uids, userId) {
334
- const collection = getCollection(application);
335
-
336
- const items = await collection.find({ uid: { $in: uids } }).toArray();
337
- return items.map((item) => transformToJf2(item, userId));
338
- }
339
-
340
326
  /**
341
327
  * Remove items from channel
342
328
  * @param {object} application - Indiekit application
@@ -36,7 +36,7 @@ export async function notifyBlogroll(application, action, data) {
36
36
  status: "deleted",
37
37
  });
38
38
  if (deleted) {
39
- console.log(
39
+ console.info(
40
40
  `[Microsub→Blogroll] Skipping follow for ${data.url} — previously deleted by user`,
41
41
  );
42
42
  return;
@@ -76,7 +76,7 @@ export async function notifyBlogroll(application, action, data) {
76
76
  { upsert: true },
77
77
  );
78
78
 
79
- console.log(`[Microsub→Blogroll] Added/updated feed ${data.url}`);
79
+ console.info(`[Microsub→Blogroll] Added/updated feed ${data.url}`);
80
80
  } else if (action === "unfollow") {
81
81
  // Soft-delete the blog entry if it came from microsub
82
82
  const result = await collection.updateOne(
@@ -96,7 +96,7 @@ export async function notifyBlogroll(application, action, data) {
96
96
  );
97
97
 
98
98
  if (result.modifiedCount > 0) {
99
- console.log(`[Microsub→Blogroll] Soft-deleted feed ${data.url}`);
99
+ console.info(`[Microsub→Blogroll] Soft-deleted feed ${data.url}`);
100
100
  }
101
101
  }
102
102
  }
@@ -5,3 +5,10 @@
5
5
 
6
6
  /** Retention period for unread count queries (only count recent items) */
7
7
  export const UNREAD_RETENTION_DAYS = 30;
8
+
9
+ /**
10
+ * Maximum number of full read items to keep per channel/user before stripping
11
+ * content. Items beyond this limit are converted to lightweight dedup skeletons
12
+ * (channelId, uid, readBy) so the poller doesn't re-ingest them as new unread.
13
+ */
14
+ export const MAX_FULL_READ_ITEMS = 200;
package/lib/utils/jf2.js CHANGED
@@ -29,115 +29,6 @@ export function generateChannelUid() {
29
29
  return result;
30
30
  }
31
31
 
32
- /**
33
- * Create a jf2 Item from normalized feed data
34
- * @param {object} data - Normalized item data
35
- * @param {object} source - Feed source metadata
36
- * @returns {object} jf2 Item object
37
- */
38
- export function createJf2Item(data, source) {
39
- return {
40
- type: "entry",
41
- uid: data.uid,
42
- url: data.url,
43
- name: data.name || undefined,
44
- content: data.content || undefined,
45
- summary: data.summary || undefined,
46
- published: data.published,
47
- updated: data.updated || undefined,
48
- author: data.author || undefined,
49
- category: data.category || [],
50
- photo: data.photo || [],
51
- video: data.video || [],
52
- audio: data.audio || [],
53
- // Interaction types
54
- "like-of": data.likeOf || [],
55
- "repost-of": data.repostOf || [],
56
- "bookmark-of": data.bookmarkOf || [],
57
- "in-reply-to": data.inReplyTo || [],
58
- // Internal properties (prefixed with _)
59
- _id: data._id,
60
- _is_read: data._is_read || false,
61
- _source: source,
62
- };
63
- }
64
-
65
- /**
66
- * Create a jf2 Card (author/person)
67
- * @param {object} data - Author data
68
- * @returns {object} jf2 Card object
69
- */
70
- export function createJf2Card(data) {
71
- if (!data) return;
72
-
73
- return {
74
- type: "card",
75
- name: data.name || undefined,
76
- url: data.url || undefined,
77
- photo: data.photo || undefined,
78
- };
79
- }
80
-
81
- /**
82
- * Create a jf2 Content object
83
- * @param {string} text - Plain text content
84
- * @param {string} html - HTML content
85
- * @returns {object|undefined} jf2 Content object
86
- */
87
- export function createJf2Content(text, html) {
88
- if (!text && !html) return;
89
-
90
- return {
91
- text: text || stripHtml(html),
92
- html: html || undefined,
93
- };
94
- }
95
-
96
- /**
97
- * Strip HTML tags from string
98
- * @param {string} html - HTML string
99
- * @returns {string} Plain text
100
- */
101
- export function stripHtml(html) {
102
- if (!html) return "";
103
- return html.replaceAll(/<[^>]*>/g, "").trim();
104
- }
105
-
106
- /**
107
- * Create a jf2 Feed response
108
- * @param {object} options - Feed options
109
- * @param {Array} options.items - Array of jf2 items
110
- * @param {object} options.paging - Pagination cursors
111
- * @returns {object} jf2 Feed object
112
- */
113
- export function createJf2Feed({ items, paging }) {
114
- const feed = {
115
- items: items || [],
116
- };
117
-
118
- if (paging) {
119
- feed.paging = {};
120
- if (paging.before) feed.paging.before = paging.before;
121
- if (paging.after) feed.paging.after = paging.after;
122
- }
123
-
124
- return feed;
125
- }
126
-
127
- /**
128
- * Create a Channel response object
129
- * @param {object} channel - Channel data
130
- * @param {number} unreadCount - Number of unread items
131
- * @returns {object} Channel object for API response
132
- */
133
- export function createChannelResponse(channel, unreadCount = 0) {
134
- return {
135
- uid: channel.uid,
136
- name: channel.name,
137
- unread: unreadCount > 0 ? unreadCount : false,
138
- };
139
- }
140
-
141
32
  /**
142
33
  * Create a Feed response object
143
34
  * @param {object} feed - Feed data
@@ -11,7 +11,7 @@ import { ObjectId } from "mongodb";
11
11
  * @param {string} id - Item ID
12
12
  * @returns {string} Base64-encoded cursor
13
13
  */
14
- export function encodeCursor(timestamp, id) {
14
+ function encodeCursor(timestamp, id) {
15
15
  const data = {
16
16
  t: timestamp instanceof Date ? timestamp.toISOString() : timestamp,
17
17
  i: id.toString(),
@@ -24,7 +24,7 @@ export function encodeCursor(timestamp, id) {
24
24
  * @param {string} cursor - Base64-encoded cursor
25
25
  * @returns {object|null} Decoded cursor with timestamp and id
26
26
  */
27
- export function decodeCursor(cursor) {
27
+ function decodeCursor(cursor) {
28
28
  if (!cursor) return;
29
29
 
30
30
  try {
@@ -133,15 +133,8 @@ export function generatePagingCursors(items, limit, hasMore, before) {
133
133
  return paging;
134
134
  }
135
135
 
136
- /**
137
- * Default pagination limit
138
- */
139
- export const DEFAULT_LIMIT = 20;
140
-
141
- /**
142
- * Maximum pagination limit
143
- */
144
- export const MAX_LIMIT = 100;
136
+ const DEFAULT_LIMIT = 20;
137
+ const MAX_LIMIT = 100;
145
138
 
146
139
  /**
147
140
  * Parse and validate limit parameter
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Shared HTML sanitization configuration
3
- * Used by both RSS/Atom normalizer and ActivityPub outbox fetcher
2
+ * Shared HTML sanitization configuration used by the feed normalizers.
4
3
  * @module utils/sanitize
5
4
  */
6
5
 
@@ -23,16 +23,6 @@ export const VALID_ACTIONS = [
23
23
  "events",
24
24
  ];
25
25
 
26
- /**
27
- * Valid channel methods
28
- */
29
- export const VALID_CHANNEL_METHODS = ["delete", "order"];
30
-
31
- /**
32
- * Valid timeline methods
33
- */
34
- export const VALID_TIMELINE_METHODS = ["mark_read", "mark_read_source", "mark_unread", "remove"];
35
-
36
26
  /**
37
27
  * Valid exclude types for channel filtering
38
28
  */