mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
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:
3 files changed
+629
-560
No files matched your search
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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");
|
||||
Reference in new issue
Block a user