mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Handle video-only comment targets
This commit is contained in:
1 parent
4adc3ec852
commit
7dc59af49b
2 files changed
+207
-66
No files matched your search
+62
-42
@@ -223,19 +223,38 @@ function normalizeCommentTarget(targetInput = {}, overrides = {}) {
|
||||
target.participantRelay,
|
||||
);
|
||||
|
||||
if (!videoEventId) {
|
||||
const normalizedVideoEventId =
|
||||
typeof videoEventId === "string" ? videoEventId.trim() : "";
|
||||
const normalizedVideoDefinitionAddress =
|
||||
typeof videoDefinitionAddress === "string" ? videoDefinitionAddress.trim() : "";
|
||||
const normalizedVideoEventRelay =
|
||||
typeof videoEventRelay === "string" ? videoEventRelay.trim() : "";
|
||||
const normalizedVideoDefinitionRelay =
|
||||
typeof videoDefinitionRelay === "string" ? videoDefinitionRelay.trim() : "";
|
||||
const normalizedParentCommentId =
|
||||
typeof parentCommentId === "string" ? parentCommentId.trim() : "";
|
||||
const normalizedParentCommentRelay =
|
||||
typeof parentCommentRelay === "string" ? parentCommentRelay.trim() : "";
|
||||
const normalizedThreadParticipantPubkey =
|
||||
typeof threadParticipantPubkey === "string" ? threadParticipantPubkey.trim() : "";
|
||||
const normalizedThreadParticipantRelay =
|
||||
typeof threadParticipantRelay === "string"
|
||||
? threadParticipantRelay.trim()
|
||||
: "";
|
||||
|
||||
if (!normalizedVideoEventId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
videoEventId,
|
||||
videoEventRelay,
|
||||
videoDefinitionAddress,
|
||||
videoDefinitionRelay,
|
||||
parentCommentId,
|
||||
parentCommentRelay,
|
||||
threadParticipantPubkey,
|
||||
threadParticipantRelay,
|
||||
videoEventId: normalizedVideoEventId,
|
||||
videoEventRelay: normalizedVideoEventRelay,
|
||||
videoDefinitionAddress: normalizedVideoDefinitionAddress,
|
||||
videoDefinitionRelay: normalizedVideoDefinitionRelay,
|
||||
parentCommentId: normalizedParentCommentId,
|
||||
parentCommentRelay: normalizedParentCommentRelay,
|
||||
threadParticipantPubkey: normalizedThreadParticipantPubkey,
|
||||
threadParticipantRelay: normalizedThreadParticipantRelay,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -272,41 +291,35 @@ function createVideoCommentFilters(targetInput, options = {}) {
|
||||
}
|
||||
|
||||
const filters = [];
|
||||
const baseFilter = { kinds: [COMMENT_EVENT_KIND] };
|
||||
|
||||
const eventFilter = {
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#e": [descriptor.videoEventId],
|
||||
};
|
||||
filters.push(applyFilterOptions(eventFilter, options));
|
||||
|
||||
if (
|
||||
descriptor.parentCommentId &&
|
||||
descriptor.parentCommentId !== descriptor.videoEventId
|
||||
) {
|
||||
const parentFilter = {
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#e": [descriptor.parentCommentId],
|
||||
};
|
||||
filters.push(applyFilterOptions(parentFilter, options));
|
||||
}
|
||||
|
||||
if (descriptor.videoDefinitionAddress) {
|
||||
baseFilter["#a"] = [descriptor.videoDefinitionAddress];
|
||||
}
|
||||
|
||||
if (descriptor.parentCommentId) {
|
||||
baseFilter["#e"] = [descriptor.parentCommentId];
|
||||
}
|
||||
|
||||
if (!descriptor.videoDefinitionAddress && descriptor.videoEventId) {
|
||||
baseFilter["#e"] = baseFilter["#e"] || [];
|
||||
if (!baseFilter["#e"].includes(descriptor.videoEventId)) {
|
||||
baseFilter["#e"].push(descriptor.videoEventId);
|
||||
}
|
||||
}
|
||||
|
||||
filters.push(applyFilterOptions(baseFilter, options));
|
||||
|
||||
const shouldIncludeLegacyFilter =
|
||||
Boolean(descriptor.videoDefinitionAddress) && Boolean(descriptor.videoEventId);
|
||||
|
||||
if (shouldIncludeLegacyFilter) {
|
||||
const legacyFilter = {
|
||||
const definitionFilter = {
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#e": [descriptor.videoEventId],
|
||||
"#a": [descriptor.videoDefinitionAddress],
|
||||
};
|
||||
|
||||
if (descriptor.parentCommentId) {
|
||||
if (!legacyFilter["#e"].includes(descriptor.parentCommentId)) {
|
||||
legacyFilter["#e"].push(descriptor.parentCommentId);
|
||||
}
|
||||
definitionFilter["#e"] = [descriptor.parentCommentId];
|
||||
}
|
||||
|
||||
filters.push(applyFilterOptions(legacyFilter, options));
|
||||
filters.push(applyFilterOptions(definitionFilter, options));
|
||||
}
|
||||
|
||||
return { descriptor, filters };
|
||||
@@ -341,10 +354,14 @@ function isVideoCommentEvent(event, descriptor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetDescriptor =
|
||||
descriptor && typeof descriptor === "object" ? descriptor : {};
|
||||
|
||||
const tags = Array.isArray(event.tags) ? event.tags : [];
|
||||
const requiresParentTag = Boolean(descriptor.parentCommentId);
|
||||
const requiresAddressMatch = Boolean(descriptor.videoDefinitionAddress);
|
||||
const requiresEventMatch = !requiresAddressMatch && Boolean(descriptor.videoEventId);
|
||||
const requiresParentTag = Boolean(targetDescriptor.parentCommentId);
|
||||
const requiresAddressMatch = Boolean(targetDescriptor.videoDefinitionAddress);
|
||||
const requiresEventMatch =
|
||||
!requiresAddressMatch && Boolean(targetDescriptor.videoEventId);
|
||||
|
||||
let hasEventTag = !requiresEventMatch;
|
||||
let hasDefinitionTag = !requiresAddressMatch;
|
||||
@@ -356,14 +373,17 @@ function isVideoCommentEvent(event, descriptor) {
|
||||
}
|
||||
const [name, value] = tag;
|
||||
if (name === "e" && typeof value === "string") {
|
||||
if (value === descriptor.videoEventId) {
|
||||
if (value === targetDescriptor.videoEventId) {
|
||||
hasEventTag = true;
|
||||
}
|
||||
if (descriptor.parentCommentId && value === descriptor.parentCommentId) {
|
||||
if (
|
||||
targetDescriptor.parentCommentId &&
|
||||
value === targetDescriptor.parentCommentId
|
||||
) {
|
||||
hasParentTag = true;
|
||||
}
|
||||
} else if (name === "a" && typeof value === "string") {
|
||||
if (value === descriptor.videoDefinitionAddress) {
|
||||
if (value === targetDescriptor.videoDefinitionAddress) {
|
||||
hasDefinitionTag = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,42 @@ test("publishComment accepts legacy targets with only an event id", async () =>
|
||||
assert.deepEqual(aTags, [], "no address tag should be emitted when unavailable");
|
||||
});
|
||||
|
||||
test("listVideoComments builds filters that prefer #a pointers", async () => {
|
||||
test("publishComment emits only video event #e tag when address and parent are absent", async () => {
|
||||
const { client, publishCalls } = createMockClient();
|
||||
|
||||
const signEventWithPrivateKey = (event) => ({ ...event, id: "solo" });
|
||||
|
||||
const result = await publishComment(
|
||||
client,
|
||||
{
|
||||
videoEventId: "video-only",
|
||||
},
|
||||
{ content: "Solo" },
|
||||
{
|
||||
resolveActiveSigner: () => null,
|
||||
shouldRequestExtensionPermissions: () => false,
|
||||
signEventWithPrivateKey,
|
||||
DEFAULT_NIP07_PERMISSION_METHODS: [],
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(result.ok, true, "publishing should succeed with only a video id");
|
||||
assert.equal(publishCalls.length, 1, "comment should be published once");
|
||||
|
||||
const publishedEvent = publishCalls[0]?.event;
|
||||
const tags = buildCommentEventTags(publishedEvent);
|
||||
const eTags = tags.filter((tag) => Array.isArray(tag) && tag[0] === "e");
|
||||
const aTags = tags.filter((tag) => Array.isArray(tag) && tag[0] === "a");
|
||||
|
||||
assert.deepEqual(
|
||||
eTags,
|
||||
[["e", "video-only"]],
|
||||
"event should carry the video pointer via #e",
|
||||
);
|
||||
assert.deepEqual(aTags, [], "event should omit #a tags when no address is provided");
|
||||
});
|
||||
|
||||
test("listVideoComments builds filters with #e primary and #a secondary", async () => {
|
||||
const matchingEventLatest = {
|
||||
id: "comment-2",
|
||||
kind: COMMENT_EVENT_KIND,
|
||||
@@ -289,45 +324,75 @@ test("listVideoComments builds filters that prefer #a pointers", async () => {
|
||||
["wss://history"],
|
||||
"list should use caller provided relays",
|
||||
);
|
||||
assert.equal(receivedFilters.filters.length, 2, "legacy and preferred filters should be emitted");
|
||||
const [primaryFilter, legacyFilter] = receivedFilters.filters;
|
||||
assert.equal(
|
||||
primaryFilter.kinds[0],
|
||||
receivedFilters.filters.length,
|
||||
3,
|
||||
"event, parent, and address filters should be emitted",
|
||||
);
|
||||
const [eventFilter, parentFilter, definitionFilter] = receivedFilters.filters;
|
||||
assert.equal(
|
||||
eventFilter.kinds[0],
|
||||
COMMENT_EVENT_KIND,
|
||||
"primary filter should target comment kind",
|
||||
"event filter should target comment kind",
|
||||
);
|
||||
assert.deepEqual(
|
||||
primaryFilter["#a"],
|
||||
["30078:author:clip"],
|
||||
"primary filter should bind the video definition via #a",
|
||||
eventFilter["#e"],
|
||||
["video-1"],
|
||||
"event filter should target the video via #e",
|
||||
);
|
||||
assert.deepEqual(
|
||||
primaryFilter["#e"],
|
||||
["parent-1"],
|
||||
"primary filter should include the parent pointer via #e",
|
||||
parentFilter,
|
||||
{
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#e": ["parent-1"],
|
||||
since: 1700000000,
|
||||
limit: 10,
|
||||
},
|
||||
"parent filter should scope the thread via #e",
|
||||
);
|
||||
assert.equal(
|
||||
eventFilter.since,
|
||||
1700000000,
|
||||
"since option should propagate to event filter",
|
||||
);
|
||||
assert.equal(
|
||||
eventFilter.limit,
|
||||
10,
|
||||
"limit option should propagate to event filter",
|
||||
);
|
||||
assert.equal(primaryFilter.since, 1700000000, "since option should propagate to primary filter");
|
||||
assert.equal(primaryFilter.limit, 10, "limit option should propagate to primary filter");
|
||||
|
||||
assert.equal(
|
||||
legacyFilter.kinds[0],
|
||||
definitionFilter.kinds[0],
|
||||
COMMENT_EVENT_KIND,
|
||||
"legacy filter should target comment kind",
|
||||
"definition filter should target comment kind",
|
||||
);
|
||||
assert.deepEqual(
|
||||
legacyFilter["#e"],
|
||||
["video-1", "parent-1"],
|
||||
"legacy filter should target the video id for backward compatibility",
|
||||
definitionFilter["#a"],
|
||||
["30078:author:clip"],
|
||||
"definition filter should bind the video definition via #a",
|
||||
);
|
||||
assert.deepEqual(
|
||||
definitionFilter["#e"],
|
||||
["parent-1"],
|
||||
"definition filter should continue scoping to the parent comment",
|
||||
);
|
||||
assert.equal(
|
||||
definitionFilter.since,
|
||||
1700000000,
|
||||
"since option should propagate to definition filter",
|
||||
);
|
||||
assert.equal(
|
||||
definitionFilter.limit,
|
||||
10,
|
||||
"limit option should propagate to definition filter",
|
||||
);
|
||||
assert.equal(legacyFilter.since, 1700000000, "since option should propagate to legacy filter");
|
||||
assert.equal(legacyFilter.limit, 10, "limit option should propagate to legacy filter");
|
||||
});
|
||||
|
||||
test("listVideoComments supports legacy targets without a definition address", async () => {
|
||||
const legacyEvent = {
|
||||
id: "legacy-comment",
|
||||
kind: COMMENT_EVENT_KIND,
|
||||
tags: [["e", "legacy-video"], ["e", "legacy-parent"]],
|
||||
tags: [["e", "legacy-video"]],
|
||||
};
|
||||
|
||||
const { client, pool } = createMockClient();
|
||||
@@ -417,15 +482,19 @@ test("subscribeVideoComments forwards matching events and cleans up unsubscribe"
|
||||
[
|
||||
{
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#a": ["30078:author:clip"],
|
||||
"#e": ["video-1"],
|
||||
},
|
||||
{
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#e": ["parent-1"],
|
||||
},
|
||||
{
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#e": ["video-1", "parent-1"],
|
||||
"#a": ["30078:author:clip"],
|
||||
"#e": ["parent-1"],
|
||||
},
|
||||
],
|
||||
"subscription should emit preferred and legacy filters",
|
||||
"subscription should emit event, parent, and address filters",
|
||||
);
|
||||
|
||||
handler({
|
||||
@@ -451,3 +520,55 @@ test("subscribeVideoComments forwards matching events and cleans up unsubscribe"
|
||||
|
||||
assert.equal(unsubCalls, 1, "underlying unsubscribe should only run once");
|
||||
});
|
||||
|
||||
test("subscribeVideoComments supports video targets without a definition address", async () => {
|
||||
const { client, pool } = createMockClient();
|
||||
|
||||
let handler = null;
|
||||
let subscriptionArgs = null;
|
||||
pool.sub = (relays, filters) => {
|
||||
subscriptionArgs = { relays, filters };
|
||||
return {
|
||||
on(name, cb) {
|
||||
if (name === "event") {
|
||||
handler = cb;
|
||||
}
|
||||
},
|
||||
unsub() {},
|
||||
};
|
||||
};
|
||||
|
||||
const receivedEvents = [];
|
||||
subscribeVideoComments(
|
||||
client,
|
||||
{ videoEventId: "legacy-video" },
|
||||
{
|
||||
onEvent: (event) => {
|
||||
receivedEvents.push(event);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.ok(subscriptionArgs, "subscription should capture filters");
|
||||
assert.deepEqual(
|
||||
subscriptionArgs.filters,
|
||||
[
|
||||
{
|
||||
kinds: [COMMENT_EVENT_KIND],
|
||||
"#e": ["legacy-video"],
|
||||
},
|
||||
],
|
||||
"subscription should emit only the video #e filter when address is absent",
|
||||
);
|
||||
|
||||
assert.ok(handler, "event handler should be registered");
|
||||
|
||||
handler({
|
||||
id: "legacy-comment",
|
||||
kind: COMMENT_EVENT_KIND,
|
||||
tags: [["e", "legacy-video"]],
|
||||
});
|
||||
|
||||
assert.equal(receivedEvents.length, 1, "legacy comment should be forwarded");
|
||||
assert.equal(receivedEvents[0].id, "legacy-comment", "comment payload should be delivered");
|
||||
});
|
||||
Reference in new issue
Block a user