mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Include event pointer tags for reactions
This commit is contained in:
1 parent
5173553f92
commit
41d751904f
4 files changed
+303
-18
No files matched your search
@@ -14,6 +14,119 @@ function sanitizeRelayList(primary, fallback) {
|
||||
return RELAY_URLS;
|
||||
}
|
||||
|
||||
function normalizeString(value) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function extractEventIdFromPointerInput(pointerInput) {
|
||||
if (!pointerInput) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (Array.isArray(pointerInput)) {
|
||||
if (
|
||||
pointerInput.length >= 2 &&
|
||||
pointerInput[0] === "e" &&
|
||||
typeof pointerInput[1] === "string"
|
||||
) {
|
||||
const candidate = pointerInput[1].trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
if (typeof pointerInput !== "object") {
|
||||
return "";
|
||||
}
|
||||
|
||||
const candidateKeys = ["eventId", "pointerEventId", "id"];
|
||||
for (const key of candidateKeys) {
|
||||
const candidate = normalizeString(pointerInput[key]);
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedType = normalizeString(pointerInput.type);
|
||||
if (normalizedType === "e") {
|
||||
const candidate = normalizeString(pointerInput.value);
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (pointerInput.pointer) {
|
||||
const nested = extractEventIdFromPointerInput(pointerInput.pointer);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
|
||||
if (pointerInput.tag) {
|
||||
const nested = extractEventIdFromPointerInput(pointerInput.tag);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function extractEventRelayFromPointerInput(pointerInput) {
|
||||
if (!pointerInput) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (Array.isArray(pointerInput)) {
|
||||
if (
|
||||
pointerInput.length >= 3 &&
|
||||
pointerInput[0] === "e" &&
|
||||
typeof pointerInput[2] === "string"
|
||||
) {
|
||||
const candidate = pointerInput[2].trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
if (typeof pointerInput !== "object") {
|
||||
return "";
|
||||
}
|
||||
|
||||
const candidateRelay = normalizeString(pointerInput.eventRelay);
|
||||
if (candidateRelay) {
|
||||
return candidateRelay;
|
||||
}
|
||||
|
||||
const normalizedType = normalizeString(pointerInput.type);
|
||||
if (normalizedType === "e") {
|
||||
const relayCandidate = normalizeString(pointerInput.relay);
|
||||
if (relayCandidate) {
|
||||
return relayCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (pointerInput.pointer) {
|
||||
const nested = extractEventRelayFromPointerInput(pointerInput.pointer);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
|
||||
if (pointerInput.tag) {
|
||||
const nested = extractEventRelayFromPointerInput(pointerInput.tag);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
export async function publishVideoReaction(
|
||||
client,
|
||||
pointerInput,
|
||||
@@ -69,6 +182,51 @@ export async function publishVideoReaction(
|
||||
? ["e", pointer.value, pointerRelay]
|
||||
: ["e", pointer.value];
|
||||
|
||||
const pointerTags = [pointerTag];
|
||||
|
||||
let pointerEventId = normalizeString(pointer.type === "e" ? pointer.value : "");
|
||||
if (!pointerEventId) {
|
||||
pointerEventId = extractEventIdFromPointerInput(pointerInput);
|
||||
}
|
||||
if (!pointerEventId) {
|
||||
pointerEventId = normalizeString(options.pointerEventId);
|
||||
}
|
||||
if (!pointerEventId && options.video) {
|
||||
pointerEventId =
|
||||
extractEventIdFromPointerInput(options.video.pointerInfo) ||
|
||||
normalizeString(options.video.pointerEventId) ||
|
||||
normalizeString(options.video.eventId) ||
|
||||
normalizeString(options.video.id);
|
||||
}
|
||||
|
||||
let pointerEventRelay = normalizeString(
|
||||
pointer.type === "e" ? pointer.relay : ""
|
||||
);
|
||||
if (!pointerEventRelay) {
|
||||
pointerEventRelay = extractEventRelayFromPointerInput(pointerInput);
|
||||
}
|
||||
if (!pointerEventRelay) {
|
||||
pointerEventRelay = normalizeString(options.pointerEventRelay);
|
||||
}
|
||||
if (!pointerEventRelay && options.video) {
|
||||
pointerEventRelay =
|
||||
normalizeString(options.video.pointerInfo?.eventRelay) ||
|
||||
extractEventRelayFromPointerInput(options.video.pointerInfo) ||
|
||||
normalizeString(options.video.eventRelay) ||
|
||||
normalizeString(options.video.relay);
|
||||
}
|
||||
if (!pointerEventRelay && pointerRelay) {
|
||||
pointerEventRelay = pointerRelay;
|
||||
}
|
||||
|
||||
if (pointerEventId) {
|
||||
if (pointerEventRelay) {
|
||||
pointerTags.push(["e", pointerEventId, pointerEventRelay]);
|
||||
} else {
|
||||
pointerTags.push(["e", pointerEventId]);
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedPointer = {
|
||||
type: pointer.type,
|
||||
value: pointer.value,
|
||||
@@ -105,6 +263,7 @@ export async function publishVideoReaction(
|
||||
created_at: createdAt,
|
||||
pointerValue: pointer.value,
|
||||
pointerTag,
|
||||
pointerTags,
|
||||
targetPointer: normalizedPointer,
|
||||
targetAuthorPubkey: explicitTargetAuthor,
|
||||
additionalTags,
|
||||
|
||||
+99
-3
@@ -495,6 +495,99 @@ function collectPointerTags(schema, {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function mergePointerTags(pointerTags = []) {
|
||||
if (!Array.isArray(pointerTags) || pointerTags.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const merged = [];
|
||||
const pointerIndexByKey = new Map();
|
||||
|
||||
const toTrimmedString = (value) => {
|
||||
if (typeof value === "string") {
|
||||
return value.trim();
|
||||
}
|
||||
if (value === undefined || value === null) {
|
||||
return "";
|
||||
}
|
||||
return String(value).trim();
|
||||
};
|
||||
|
||||
pointerTags.forEach((tag) => {
|
||||
if (!Array.isArray(tag) || tag.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedType = toTrimmedString(tag[0]);
|
||||
if (!normalizedType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const canonicalType =
|
||||
normalizedType === "a" ? "a" : normalizedType === "e" ? "e" : normalizedType;
|
||||
|
||||
const normalizedValue = toTrimmedString(tag[1]);
|
||||
if (!normalizedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sanitizedTag = [canonicalType, normalizedValue];
|
||||
for (let index = 2; index < tag.length; index += 1) {
|
||||
const extra = toTrimmedString(tag[index]);
|
||||
if (extra) {
|
||||
sanitizedTag.push(extra);
|
||||
}
|
||||
}
|
||||
|
||||
if (canonicalType !== "a" && canonicalType !== "e") {
|
||||
merged.push(sanitizedTag);
|
||||
return;
|
||||
}
|
||||
|
||||
const dedupeKey = `${canonicalType}:${normalizedValue.toLowerCase()}`;
|
||||
const relayHint =
|
||||
sanitizedTag.length > 2 && typeof sanitizedTag[2] === "string"
|
||||
? sanitizedTag[2].trim()
|
||||
: "";
|
||||
|
||||
if (!pointerIndexByKey.has(dedupeKey)) {
|
||||
if (relayHint && sanitizedTag.length > 2) {
|
||||
sanitizedTag[2] = relayHint;
|
||||
}
|
||||
const index = merged.length;
|
||||
merged.push(sanitizedTag);
|
||||
pointerIndexByKey.set(dedupeKey, { index, relay: relayHint });
|
||||
return;
|
||||
}
|
||||
|
||||
const existingEntry = pointerIndexByKey.get(dedupeKey);
|
||||
const existingTag = merged[existingEntry.index];
|
||||
const existingRelay =
|
||||
existingTag.length > 2 && typeof existingTag[2] === "string"
|
||||
? existingTag[2].trim()
|
||||
: "";
|
||||
const hasExistingRelay = Boolean(existingRelay);
|
||||
const hasNewRelay = Boolean(relayHint);
|
||||
|
||||
if (
|
||||
(!hasExistingRelay && hasNewRelay) ||
|
||||
(hasExistingRelay && hasNewRelay && existingRelay !== relayHint)
|
||||
) {
|
||||
const replacement = sanitizedTag.slice();
|
||||
if (relayHint && replacement.length > 2) {
|
||||
replacement[2] = relayHint;
|
||||
}
|
||||
merged[existingEntry.index] = replacement;
|
||||
pointerIndexByKey.set(dedupeKey, {
|
||||
index: existingEntry.index,
|
||||
relay: relayHint,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
export function buildVideoPostEvent({
|
||||
pubkey,
|
||||
created_at,
|
||||
@@ -766,8 +859,11 @@ export function buildReactionEvent({
|
||||
pointerTag,
|
||||
pointerTags,
|
||||
});
|
||||
normalizedPointerTags.forEach((tag) => {
|
||||
tags.push(tag);
|
||||
const mergedPointerTags = mergePointerTags(normalizedPointerTags);
|
||||
mergedPointerTags.forEach((tag) => {
|
||||
if (Array.isArray(tag) && tag.length >= 2) {
|
||||
tags.push(tag);
|
||||
}
|
||||
});
|
||||
|
||||
const resolvePointerDetails = (pointerCandidate) => {
|
||||
@@ -808,7 +904,7 @@ export function buildReactionEvent({
|
||||
return explicitPointer;
|
||||
}
|
||||
|
||||
const pointerTagEntry = normalizedPointerTags.find(
|
||||
const pointerTagEntry = mergedPointerTags.find(
|
||||
(tag) => Array.isArray(tag) && tag.length >= 2 && (tag[0] === "a" || tag[0] === "e")
|
||||
);
|
||||
if (!pointerTagEntry) {
|
||||
|
||||
@@ -42,7 +42,18 @@ export function resolveVideoPointer({
|
||||
for (const pointer of pointerCandidates) {
|
||||
const key = pointerArrayToKey(pointer);
|
||||
if (key) {
|
||||
return { pointer, key };
|
||||
const pointerEventId =
|
||||
pointer[0] === "e" && typeof pointer[1] === "string"
|
||||
? pointer[1].trim()
|
||||
: fallbackId;
|
||||
return {
|
||||
pointer,
|
||||
key,
|
||||
eventId:
|
||||
typeof pointerEventId === "string" && pointerEventId
|
||||
? pointerEventId
|
||||
: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,19 +21,38 @@ test("buildReactionEvent includes pointer and author tags when pubkey provided",
|
||||
assert.deepEqual(authorTags, [["p", "author-pubkey", "wss://relay.example"]]);
|
||||
});
|
||||
|
||||
test("buildReactionEvent derives author pubkey from address pointer", () => {
|
||||
const event = buildReactionEvent({
|
||||
pubkey: "viewer",
|
||||
created_at: 1700000001,
|
||||
pointerValue: "30078:deadbeefcafebabe:clip-1",
|
||||
pointerTag: ["a", "30078:deadbeefcafebabe:clip-1"],
|
||||
targetPointer: { type: "a", value: "30078:deadbeefcafebabe:clip-1" },
|
||||
content: "+",
|
||||
});
|
||||
test(
|
||||
"buildReactionEvent merges address and event pointers for addressable targets",
|
||||
() => {
|
||||
const event = buildReactionEvent({
|
||||
pubkey: "viewer",
|
||||
created_at: 1700000001,
|
||||
pointerValue: "30078:deadbeefcafebabe:clip-1",
|
||||
pointerTag: ["a", "30078:deadbeefcafebabe:clip-1"],
|
||||
pointerTags: [
|
||||
["a", "30078:deadbeefcafebabe:clip-1", "wss://relay.example"],
|
||||
["e", "event-id"],
|
||||
["e", "event-id", "wss://event.relay"],
|
||||
],
|
||||
targetPointer: {
|
||||
type: "a",
|
||||
value: "30078:deadbeefcafebabe:clip-1",
|
||||
relay: "wss://relay.example",
|
||||
},
|
||||
content: "+",
|
||||
});
|
||||
|
||||
const addressTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "a");
|
||||
assert.deepEqual(addressTags, [["a", "30078:deadbeefcafebabe:clip-1"]]);
|
||||
const addressTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "a");
|
||||
assert.deepEqual(addressTags, [[
|
||||
"a",
|
||||
"30078:deadbeefcafebabe:clip-1",
|
||||
"wss://relay.example",
|
||||
]]);
|
||||
|
||||
const authorTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "p");
|
||||
assert.deepEqual(authorTags, [["p", "deadbeefcafebabe"]]);
|
||||
});
|
||||
const eventTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "e");
|
||||
assert.deepEqual(eventTags, [["e", "event-id", "wss://event.relay"]]);
|
||||
|
||||
const authorTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "p");
|
||||
assert.deepEqual(authorTags, [["p", "deadbeefcafebabe", "wss://relay.example"]]);
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user