mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
[search] Decode npub authors to hex — the Author filter matched nothing (npubs)
The Author field (and the author: token) passed values through as-typed, but the matcher compares against video.pubkey — HEX. So entering an npub (the only format users actually have) silently returned zero results; only raw hex worked. New normalizeAuthorValues (searchFilters.js, injectable decoder for tests): npub1* decodes via safeDecodeNpub to hex, hex passes through lowercased, and undecodable values are dropped — surfaced as parse errors on the author: token path. Applied in BOTH the parser and the filter modal's applyFilters, so the Author panel field and the token syntax now genuinely filter by creator. Tests: +2 in tests/search-filters.test.mjs (decode/passthrough/drop with an injected decoder; parser hex passthrough + garbage error). Lint + build green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
a6a6d4b0f7
commit
9afbc0cbbb
3 files changed
+82
-4
No files matched your search
@@ -1,3 +1,32 @@
|
|||||||
|
import { safeDecodeNpub } from "../utils/nostrHelpers.js";
|
||||||
|
|
||||||
|
const HEX64 = /^[0-9a-f]{64}$/;
|
||||||
|
|
||||||
|
// Authors arrive as npubs (what users actually have) or hex, but videos carry
|
||||||
|
// HEX pubkeys — without decoding, npub authors silently matched nothing.
|
||||||
|
// Undecodable values are dropped (returned in `dropped` for error surfaces).
|
||||||
|
export function normalizeAuthorValues(values, { decodeNpub = safeDecodeNpub } = {}) {
|
||||||
|
const normalized = [];
|
||||||
|
const dropped = [];
|
||||||
|
for (const raw of Array.isArray(values) ? values : []) {
|
||||||
|
const value = typeof raw === "string" ? raw.trim() : "";
|
||||||
|
if (!value) continue;
|
||||||
|
if (HEX64.test(value.toLowerCase())) {
|
||||||
|
normalized.push(value.toLowerCase());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (value.toLowerCase().startsWith("npub1")) {
|
||||||
|
const hex = typeof decodeNpub === "function" ? decodeNpub(value) : null;
|
||||||
|
if (hex && HEX64.test(hex.toLowerCase())) {
|
||||||
|
normalized.push(hex.toLowerCase());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dropped.push(value);
|
||||||
|
}
|
||||||
|
return { normalized, dropped };
|
||||||
|
}
|
||||||
|
|
||||||
const DATE_FORMAT_OPTIONS = {
|
const DATE_FORMAT_OPTIONS = {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
@@ -176,7 +205,13 @@ export function parseFilterQuery(inputString = "") {
|
|||||||
errors.push({ token, message: "Author value is empty." });
|
errors.push({ token, message: "Author value is empty." });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
filters.authorPubkeys.push(...values);
|
{
|
||||||
|
const { normalized, dropped } = normalizeAuthorValues(values);
|
||||||
|
filters.authorPubkeys.push(...normalized);
|
||||||
|
for (const value of dropped) {
|
||||||
|
errors.push({ token, message: `Could not decode author "${value}".` });
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "tag": {
|
case "tag": {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
buildSearchHashFromState,
|
buildSearchHashFromState,
|
||||||
resetSearchFilters,
|
resetSearchFilters,
|
||||||
} from "../../search/searchFilterState.js";
|
} from "../../search/searchFilterState.js";
|
||||||
import { DEFAULT_FILTERS } from "../../search/searchFilters.js";
|
import { DEFAULT_FILTERS, normalizeAuthorValues } from "../../search/searchFilters.js";
|
||||||
import { setHashView } from "../../hashView.js";
|
import { setHashView } from "../../hashView.js";
|
||||||
import { ALLOW_NSFW_CONTENT } from "../../config.js";
|
import { ALLOW_NSFW_CONTENT } from "../../config.js";
|
||||||
|
|
||||||
@@ -349,8 +349,9 @@ function applyFilters() {
|
|||||||
filters.sort = sortSelect.value;
|
filters.sort = sortSelect.value;
|
||||||
|
|
||||||
// Author
|
// Author
|
||||||
const authors = authorInput.value.split(/[, ]+/).map(s => s.trim()).filter(Boolean);
|
const rawAuthors = authorInput.value.split(/[, ]+/).map(s => s.trim()).filter(Boolean);
|
||||||
filters.authorPubkeys = authors;
|
// Decode npubs to hex — videos carry hex pubkeys, so raw npubs matched nothing.
|
||||||
|
filters.authorPubkeys = normalizeAuthorValues(rawAuthors).normalized;
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
const selectedTags = new Set();
|
const selectedTags = new Set();
|
||||||
|
|||||||
@@ -116,3 +116,45 @@ test("sort: trending weights views by recency", () => {
|
|||||||
);
|
);
|
||||||
assert.deepEqual(ranked, ["fresh-riser", "old-hit"]);
|
assert.deepEqual(ranked, ["fresh-riser", "old-hit"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Author values arrive as npubs but videos carry hex pubkeys — without decoding,
|
||||||
|
// npub authors silently matched nothing (the "wired but wrong" filter).
|
||||||
|
//
|
||||||
|
// test_integrity_note:
|
||||||
|
// change_type: ["new_tests"]
|
||||||
|
// scenarios:
|
||||||
|
// - id: SCN-search-author-npub
|
||||||
|
// given: "author values as npub / hex / garbage"
|
||||||
|
// when: "normalizeAuthorValues runs (with an injected decoder)"
|
||||||
|
// then: "npubs decode to hex, hex passes through lowercased, garbage is dropped with errors"
|
||||||
|
// observable_outcomes:
|
||||||
|
// - "npub -> decoded hex; HEX passthrough lowercased; undecodable -> dropped"
|
||||||
|
// - "parser author: token emits decode errors for garbage"
|
||||||
|
// determinism_controls:
|
||||||
|
// - "injected decodeNpub stub (no window.NostrTools dependency)"
|
||||||
|
// anti_cheat_rationale:
|
||||||
|
// prevents: ["hard-coded return value"]
|
||||||
|
// relaxation:
|
||||||
|
// did_relax_any_assertion: false
|
||||||
|
|
||||||
|
test("normalizeAuthorValues decodes npubs, passes hex, drops garbage", async () => {
|
||||||
|
const { normalizeAuthorValues } = await import("../js/search/searchFilters.js");
|
||||||
|
const HEX = "a".repeat(64);
|
||||||
|
const decodeNpub = (v) => (v === "npub1good" ? HEX : null);
|
||||||
|
|
||||||
|
const { normalized, dropped } = normalizeAuthorValues(
|
||||||
|
["npub1good", "B".repeat(64), "npub1bad", "garbage"],
|
||||||
|
{ decodeNpub },
|
||||||
|
);
|
||||||
|
assert.deepEqual(normalized, [HEX, "b".repeat(64)]);
|
||||||
|
assert.deepEqual(dropped, ["npub1bad", "garbage"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("author: token with hex passes into filters; garbage surfaces as an error", () => {
|
||||||
|
const HEX = "c".repeat(64);
|
||||||
|
const good = parseFilterQuery(`author:${HEX}`);
|
||||||
|
assert.deepEqual(good.filters.authorPubkeys, [HEX]);
|
||||||
|
const bad = parseFilterQuery("author:not-a-key");
|
||||||
|
assert.equal(bad.filters.authorPubkeys.length, 0);
|
||||||
|
assert.ok(bad.errors.some((e) => /decode author/i.test(e.message)));
|
||||||
|
});
|
||||||
Reference in new issue
Block a user