feat: case insensitive names

This commit is contained in:
Alejandro Gómez
2025-12-16 00:00:12 +01:00
parent f4a0d5e669
commit 6f05f414d3
4 changed files with 135 additions and 24 deletions

View File

@@ -237,16 +237,86 @@ describe("resolveFilterAliases", () => {
expect(result.authors?.length).toBe(5000);
});
it("should handle mixed case aliases (should not match)", () => {
// Aliases are case-sensitive and should be lowercase
it("should handle mixed case aliases (case-insensitive)", () => {
// Aliases are case-insensitive for user convenience
const accountPubkey = "a".repeat(64);
const contacts = ["b".repeat(64), "c".repeat(64)];
const filter: NostrFilter = { authors: ["$Me", "$CONTACTS"] };
const result = resolveFilterAliases(filter, "a".repeat(64), [
"b".repeat(64),
]);
const result = resolveFilterAliases(filter, accountPubkey, contacts);
// These should NOT be resolved (case mismatch)
expect(result.authors).toContain("$Me");
expect(result.authors).toContain("$CONTACTS");
// These should be resolved despite case differences
expect(result.authors).toContain(accountPubkey);
expect(result.authors).toContain(contacts[0]);
expect(result.authors).toContain(contacts[1]);
expect(result.authors).not.toContain("$Me");
expect(result.authors).not.toContain("$CONTACTS");
});
});
describe("case-insensitive alias resolution", () => {
it("should resolve $ME (uppercase) in authors", () => {
const filter: NostrFilter = { authors: ["$ME"] };
const accountPubkey = "a".repeat(64);
const result = resolveFilterAliases(filter, accountPubkey, []);
expect(result.authors).toEqual([accountPubkey]);
expect(result.authors).not.toContain("$ME");
});
it("should resolve $Me (mixed case) in #p tags", () => {
const filter: NostrFilter = { "#p": ["$Me"] };
const accountPubkey = "a".repeat(64);
const result = resolveFilterAliases(filter, accountPubkey, []);
expect(result["#p"]).toEqual([accountPubkey]);
expect(result["#p"]).not.toContain("$Me");
});
it("should resolve $CONTACTS (uppercase) in authors", () => {
const filter: NostrFilter = { authors: ["$CONTACTS"] };
const contacts = ["a".repeat(64), "b".repeat(64)];
const result = resolveFilterAliases(filter, undefined, contacts);
expect(result.authors).toEqual(contacts);
expect(result.authors).not.toContain("$CONTACTS");
});
it("should resolve $Contacts (mixed case) in #P tags", () => {
const filter: NostrFilter = { "#P": ["$Contacts"] };
const contacts = ["a".repeat(64), "b".repeat(64)];
const result = resolveFilterAliases(filter, undefined, contacts);
expect(result["#P"]).toEqual(contacts);
expect(result["#P"]).not.toContain("$Contacts");
});
it("should handle multiple case variations in same filter", () => {
const filter: NostrFilter = {
authors: ["$me", "$ME", "$Me"],
"#p": ["$contacts", "$CONTACTS", "$Contacts"],
};
const accountPubkey = "a".repeat(64);
const contacts = ["b".repeat(64), "c".repeat(64)];
const result = resolveFilterAliases(filter, accountPubkey, contacts);
// Should deduplicate all variants of $me to single pubkey
expect(result.authors).toEqual([accountPubkey]);
// Should deduplicate all variants of $contacts
expect(result["#p"]).toEqual(contacts);
});
it("should handle sloppy typing with whitespace-like patterns", () => {
const filter: NostrFilter = {
authors: ["$ME", "$me", "$Me"],
"#P": ["$CONTACTS", "$contacts", "$Contacts"],
};
const accountPubkey = "a".repeat(64);
const contacts = ["b".repeat(64), "c".repeat(64)];
const result = resolveFilterAliases(filter, accountPubkey, contacts);
expect(result.authors?.length).toBe(1);
expect(result.authors).toContain(accountPubkey);
expect(result["#P"]).toEqual(contacts);
});
});

View File

@@ -26,7 +26,7 @@ export function getDisplayName(
}
/**
* Resolve $me and $contacts aliases in a Nostr filter
* Resolve $me and $contacts aliases in a Nostr filter (case-insensitive)
* @param filter - Filter that may contain $me or $contacts aliases
* @param accountPubkey - Current user's pubkey (for $me resolution)
* @param contacts - Array of contact pubkeys (for $contacts resolution)
@@ -44,11 +44,12 @@ export function resolveFilterAliases(
const resolvedAuthors: string[] = [];
for (const author of resolved.authors) {
if (author === "$me") {
const normalized = author.toLowerCase();
if (normalized === "$me") {
if (accountPubkey) {
resolvedAuthors.push(accountPubkey);
}
} else if (author === "$contacts") {
} else if (normalized === "$contacts") {
resolvedAuthors.push(...contacts);
} else {
resolvedAuthors.push(author);
@@ -64,11 +65,12 @@ export function resolveFilterAliases(
const resolvedPTags: string[] = [];
for (const pTag of resolved["#p"]) {
if (pTag === "$me") {
const normalized = pTag.toLowerCase();
if (normalized === "$me") {
if (accountPubkey) {
resolvedPTags.push(accountPubkey);
}
} else if (pTag === "$contacts") {
} else if (normalized === "$contacts") {
resolvedPTags.push(...contacts);
} else {
resolvedPTags.push(pTag);
@@ -84,11 +86,12 @@ export function resolveFilterAliases(
const resolvedPTagsUppercase: string[] = [];
for (const pTag of resolved["#P"]) {
if (pTag === "$me") {
const normalized = pTag.toLowerCase();
if (normalized === "$me") {
if (accountPubkey) {
resolvedPTagsUppercase.push(accountPubkey);
}
} else if (pTag === "$contacts") {
} else if (normalized === "$contacts") {
resolvedPTagsUppercase.push(...contacts);
} else {
resolvedPTagsUppercase.push(pTag);

View File

@@ -725,6 +725,41 @@ describe("parseReqCommand", () => {
});
});
describe("case-insensitive aliases", () => {
it("should normalize $ME to $me in authors", () => {
const result = parseReqCommand(["-a", "$ME"]);
expect(result.filter.authors).toContain("$me");
expect(result.needsAccount).toBe(true);
});
it("should normalize $CONTACTS to $contacts in authors", () => {
const result = parseReqCommand(["-a", "$CONTACTS"]);
expect(result.filter.authors).toContain("$contacts");
expect(result.needsAccount).toBe(true);
});
it("should normalize mixed case $Me to $me in #p tags", () => {
const result = parseReqCommand(["-p", "$Me"]);
expect(result.filter["#p"]).toContain("$me");
expect(result.needsAccount).toBe(true);
});
it("should normalize $CONTACTS to $contacts in #P tags", () => {
const result = parseReqCommand(["-P", "$CONTACTS"]);
expect(result.filter["#P"]).toContain("$contacts");
expect(result.needsAccount).toBe(true);
});
it("should handle mixed case aliases with other values", () => {
const hex = "a".repeat(64);
const result = parseReqCommand(["-a", `$ME,${hex},$Contacts`]);
expect(result.filter.authors).toContain("$me");
expect(result.filter.authors).toContain("$contacts");
expect(result.filter.authors).toContain(hex);
expect(result.needsAccount).toBe(true);
});
});
describe("$me alias in #p tags (-p)", () => {
it("should detect $me in #p tags", () => {
const result = parseReqCommand(["-p", "$me"]);

View File

@@ -127,9 +127,10 @@ export function parseReqCommand(args: string[]): ParsedReqCommand {
const values = nextArg.split(",").map((a) => a.trim());
for (const authorStr of values) {
if (!authorStr) continue;
// Check for $me and $contacts aliases
if (authorStr === "$me" || authorStr === "$contacts") {
authors.add(authorStr);
// Check for $me and $contacts aliases (case-insensitive)
const normalized = authorStr.toLowerCase();
if (normalized === "$me" || normalized === "$contacts") {
authors.add(normalized);
addedAny = true;
} else if (isNip05(authorStr)) {
// Check if it's a NIP-05 identifier
@@ -188,9 +189,10 @@ export function parseReqCommand(args: string[]): ParsedReqCommand {
const values = nextArg.split(",").map((p) => p.trim());
for (const pubkeyStr of values) {
if (!pubkeyStr) continue;
// Check for $me and $contacts aliases
if (pubkeyStr === "$me" || pubkeyStr === "$contacts") {
pTags.add(pubkeyStr);
// Check for $me and $contacts aliases (case-insensitive)
const normalized = pubkeyStr.toLowerCase();
if (normalized === "$me" || normalized === "$contacts") {
pTags.add(normalized);
addedAny = true;
} else if (isNip05(pubkeyStr)) {
// Check if it's a NIP-05 identifier
@@ -223,9 +225,10 @@ export function parseReqCommand(args: string[]): ParsedReqCommand {
const values = nextArg.split(",").map((p) => p.trim());
for (const pubkeyStr of values) {
if (!pubkeyStr) continue;
// Check for $me and $contacts aliases
if (pubkeyStr === "$me" || pubkeyStr === "$contacts") {
pTagsUppercase.add(pubkeyStr);
// Check for $me and $contacts aliases (case-insensitive)
const normalized = pubkeyStr.toLowerCase();
if (normalized === "$me" || normalized === "$contacts") {
pTagsUppercase.add(normalized);
addedAny = true;
} else if (isNip05(pubkeyStr)) {
// Check if it's a NIP-05 identifier