Merge pull request #284 from PR0M3TH3AN/codex/implement-subscriptions-feed-and-refactor-code

Refactor subscriptions feed to run through the engine
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-04 17:06:51 -04:00
commit b1b0b0f625
3 files changed
+629 -560

No files matched your search

+61
View File
@@ -24,6 +24,7 @@ import {
createBlacklistFilterStage,
createDedupeByRootStage,
createChronologicalSorter,
createSubscriptionAuthorsSource,
} from "./feedEngine/index.js";
import watchHistoryService from "./watchHistoryService.js";
import r2Service from "./services/r2Service.js";
@@ -33,6 +34,7 @@ import nostrService from "./services/nostrService.js";
import { initQuickR2Upload } from "./r2-quick.js";
import { createWatchHistoryRenderer } from "./historyView.js";
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
import { subscriptions } from "./subscriptions.js";
import {
initViewCounter,
subscribeToVideoViewCount,
@@ -183,6 +185,7 @@ class Application {
this.feedEngine.run = (...args) => this.feedEngine.runFeed(...args);
}
this.registerRecentFeed();
this.registerSubscriptionsFeed();
this.playbackService =
services.playbackService ||
@@ -4956,6 +4959,44 @@ class Application {
}
}
registerSubscriptionsFeed() {
if (!this.feedEngine || typeof this.feedEngine.registerFeed !== "function") {
return null;
}
const existingDefinition =
typeof this.feedEngine.getFeedDefinition === "function"
? this.feedEngine.getFeedDefinition("subscriptions")
: null;
if (existingDefinition) {
return existingDefinition;
}
try {
return this.feedEngine.registerFeed("subscriptions", {
source: createSubscriptionAuthorsSource({ service: this.nostrService }),
stages: [
createBlacklistFilterStage({
shouldIncludeVideo: (video, options) =>
this.nostrService.shouldIncludeVideo(video, options),
}),
createDedupeByRootStage({
dedupe: (videos) => this.dedupeVideosByRoot(videos),
}),
],
sorter: createChronologicalSorter(),
hooks: {
subscriptions: {
resolveAuthors: () => subscriptions.getSubscribedAuthors(),
},
},
});
} catch (error) {
console.warn("[Application] Failed to register subscriptions feed:", error);
return null;
}
}
buildRecentFeedRuntime() {
const blacklist =
this.blacklistedEventIds instanceof Set
@@ -5905,6 +5946,16 @@ class Application {
if (result?.ok) {
this.showSuccess("Creator added to the blacklist.");
subscriptions
.refreshActiveFeed({ reason: "admin-blacklist-update" })
.catch((error) => {
if (isDevMode) {
console.warn(
"[Subscriptions] Failed to refresh after blacklist update:",
error
);
}
});
} else {
const code = result?.error || "unknown";
switch (code) {
@@ -5984,6 +6035,16 @@ class Application {
this.populateBlockedList();
await this.loadVideos();
subscriptions
.refreshActiveFeed({ reason: "user-block-update" })
.catch((error) => {
if (isDevMode) {
console.warn(
"[Subscriptions] Failed to refresh after user block update:",
error
);
}
});
} catch (error) {
console.error("Failed to update personal block list:", error);
const message =
+421 -560
View File
File diff suppressed because it is too large. Load diff
+147
View File
@@ -0,0 +1,147 @@
import assert from "node:assert/strict";
if (typeof globalThis.window === "undefined") {
globalThis.window = {};
}
if (typeof globalThis.localStorage === "undefined") {
const storage = new Map();
globalThis.localStorage = {
getItem(key) {
return storage.has(key) ? storage.get(key) : null;
},
setItem(key, value) {
storage.set(String(key), String(value));
},
removeItem(key) {
storage.delete(key);
},
clear() {
storage.clear();
},
};
}
if (typeof window.localStorage === "undefined") {
window.localStorage = globalThis.localStorage;
}
const {
createFeedEngine,
createSubscriptionAuthorsSource,
createBlacklistFilterStage,
createDedupeByRootStage,
createChronologicalSorter,
} = await import("../js/feedEngine/index.js");
async function testBlockedCreatorsFiltered() {
const engine = createFeedEngine();
const feedName = "subscriptions-blocked";
const videos = [
{ id: "keep", pubkey: "pub1", created_at: 100, videoRootId: "root-keep" },
{ id: "blocked-event", pubkey: "pub1", created_at: 200, videoRootId: "root-bad" },
{ id: "blocked-author-event", pubkey: "blocked", created_at: 150 },
{ id: "ignored-author", pubkey: "other", created_at: 250 },
];
const service = {
async getFilteredActiveVideos() {
return videos;
},
};
engine.registerFeed(feedName, {
source: createSubscriptionAuthorsSource({ service }),
stages: [
createBlacklistFilterStage({
shouldIncludeVideo(video, { blacklistedEventIds, isAuthorBlocked }) {
if (blacklistedEventIds.has(video.id)) {
return false;
}
if (isAuthorBlocked(video.pubkey)) {
return false;
}
return true;
},
}),
createDedupeByRootStage(),
],
sorter: createChronologicalSorter(),
});
const result = await engine.runFeed(feedName, {
runtime: {
subscriptionAuthors: ["pub1", "blocked"],
authors: ["pub1", "blocked"],
blacklistedEventIds: new Set(["blocked-event"]),
isAuthorBlocked: (pubkey) => pubkey === "blocked",
},
});
assert.deepEqual(
result.videos.map((video) => video.id),
["keep"],
"subscriptions feed should drop blocked authors and events"
);
const reasons = result.metadata?.why || [];
const blacklistReasons = reasons.filter((entry) => entry.reason === "blacklist");
assert.equal(
blacklistReasons.length,
2,
"blocked entries should be logged in metadata"
);
}
async function testDuplicateRootsFiltered() {
const engine = createFeedEngine();
const feedName = "subscriptions-dedupe";
const videos = [
{ id: "older", pubkey: "pub1", created_at: 100, videoRootId: "root-a" },
{ id: "newer", pubkey: "pub1", created_at: 200, videoRootId: "root-a" },
{ id: "other", pubkey: "pub1", created_at: 150, videoRootId: "root-b" },
];
const service = {
async getFilteredActiveVideos() {
return videos;
},
};
engine.registerFeed(feedName, {
source: createSubscriptionAuthorsSource({ service }),
stages: [
createBlacklistFilterStage({ shouldIncludeVideo: () => true }),
createDedupeByRootStage(),
],
sorter: createChronologicalSorter(),
});
const result = await engine.runFeed(feedName, {
runtime: {
subscriptionAuthors: ["pub1"],
authors: ["pub1"],
blacklistedEventIds: new Set(),
isAuthorBlocked: () => false,
},
});
assert.deepEqual(
result.videos.map((video) => video.id),
["newer", "other"],
"dedupe stage should keep only the newest root version"
);
const dedupeReasons = result.metadata?.why?.filter(
(entry) => entry.reason === "older-root-version"
) || [];
assert.equal(dedupeReasons.length, 1, "older version should be recorded");
assert.equal(dedupeReasons[0].videoId, "older");
}
await testBlockedCreatorsFiltered();
await testDuplicateRootsFiltered();
console.log("subscriptions-feed tests passed");