Include event pointer tags for reactions

This commit is contained in:
thePR0M3TH3AN committed 2025-10-29 22:14:20 -04:00
1 parent 5173553f92
commit 41d751904f
4 files changed
+303 -18

No files matched your search

+159
View File
@@ -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
View File
@@ -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) {
+12 -1
View File
@@ -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
: "",
};
}
}
+33 -14
View File
@@ -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"]]);
}
);