feat: support raw coordinate format (kind:pubkey:d) in -e flag

The -e flag now accepts raw a-tag coordinates like `30023:pubkey:article`
in addition to naddr bech32 encoding. Both route to #a tag filtering.

Examples:
- req -e 30023:abc123...:my-article    # Raw coordinate
- req -e naddr1...                      # Bech32 encoded (same effect)
This commit is contained in:
Claude
2026-01-22 13:20:00 +00:00
parent 337faa0dbb
commit abc359f90b
3 changed files with 104 additions and 2 deletions

View File

@@ -363,6 +363,93 @@ describe("parseReqCommand", () => {
});
});
describe("raw coordinate support (kind:pubkey:d)", () => {
it("should parse raw coordinate and populate filter['#a']", () => {
const pubkey = "a".repeat(64);
const coordinate = `30023:${pubkey}:my-article`;
const result = parseReqCommand(["-e", coordinate]);
expect(result.filter["#a"]).toBeDefined();
expect(result.filter["#a"]).toHaveLength(1);
expect(result.filter["#a"]).toEqual([coordinate]);
expect(result.filter["#e"]).toBeUndefined();
});
it("should normalize pubkey to lowercase", () => {
const pubkey = "A".repeat(64);
const coordinate = `30023:${pubkey}:my-article`;
const result = parseReqCommand(["-e", coordinate]);
expect(result.filter["#a"]).toEqual([
`30023:${"a".repeat(64)}:my-article`,
]);
});
it("should handle empty d-tag identifier", () => {
const pubkey = "a".repeat(64);
const coordinate = `30023:${pubkey}:`;
const result = parseReqCommand(["-e", coordinate]);
expect(result.filter["#a"]).toEqual([coordinate]);
});
it("should handle d-tag with special characters", () => {
const pubkey = "a".repeat(64);
const coordinate = `30023:${pubkey}:my-article/with:special-chars`;
const result = parseReqCommand(["-e", coordinate]);
expect(result.filter["#a"]).toEqual([coordinate]);
});
it("should handle different kind numbers", () => {
const pubkey = "a".repeat(64);
const result = parseReqCommand([
"-e",
`0:${pubkey}:,30000:${pubkey}:list,30023:${pubkey}:article`,
]);
expect(result.filter["#a"]).toHaveLength(3);
expect(result.filter["#a"]).toContain(`0:${pubkey}:`);
expect(result.filter["#a"]).toContain(`30000:${pubkey}:list`);
expect(result.filter["#a"]).toContain(`30023:${pubkey}:article`);
});
it("should combine with naddr coordinates", () => {
const pubkey = "a".repeat(64);
const rawCoord = `30023:${pubkey}:raw-article`;
const naddr = nip19.naddrEncode({
kind: 30023,
pubkey: pubkey,
identifier: "encoded-article",
});
const result = parseReqCommand(["-e", `${rawCoord},${naddr}`]);
expect(result.filter["#a"]).toHaveLength(2);
expect(result.filter["#a"]).toContain(rawCoord);
expect(result.filter["#a"]).toContain(
`30023:${pubkey}:encoded-article`,
);
});
it("should ignore invalid coordinate formats", () => {
// Missing parts
const result1 = parseReqCommand(["-e", "30023:abc"]);
expect(result1.filter["#a"]).toBeUndefined();
// Invalid pubkey (not 64 hex chars)
const result2 = parseReqCommand(["-e", "30023:abc123:article"]);
expect(result2.filter["#a"]).toBeUndefined();
// Invalid kind (not a number)
const result3 = parseReqCommand([
"-e",
`abc:${"a".repeat(64)}:article`,
]);
expect(result3.filter["#a"]).toBeUndefined();
});
});
describe("mixed format support", () => {
it("should handle comma-separated mix of all formats (all to tags)", () => {
const hex = "a".repeat(64);

View File

@@ -679,6 +679,20 @@ function parseEventIdentifier(value: string): ParsedEventIdentifier | null {
};
}
// Raw coordinate: kind:pubkey:identifier → #a tag
// Format: <kind>:<pubkey>:<d-tag> (e.g., 30023:abc123...:article-name)
const coordinateMatch = value.match(/^(\d+):([a-fA-F0-9]{64}):(.*)$/);
if (coordinateMatch) {
const [, kindStr, pubkey, identifier] = coordinateMatch;
const kind = parseInt(kindStr, 10);
if (!isNaN(kind)) {
return {
type: "tag-address",
value: `${kind}:${pubkey.toLowerCase()}:${identifier}`,
};
}
}
return null;
}

View File

@@ -181,9 +181,9 @@ export const manPages: Record<string, ManPageEntry> = {
"Direct event lookup by ID (filter.ids). Fetch specific events by their ID. Supports note1, nevent1 (with relay hints), or raw hex. Comma-separated values supported: -i note1...,nevent1...,abc123...",
},
{
flag: "-e <note|nevent|naddr|hex>",
flag: "-e <note|nevent|naddr|coordinate|hex>",
description:
"Tag-based filtering (#e/#a tags). Find events that reference the specified events or addresses. Supports note1, nevent1, naddr1, or raw hex. Comma-separated values supported: -e note1...,naddr1...",
"Tag-based filtering (#e/#a tags). Find events that reference the specified events or addresses. Supports note1, nevent1, naddr1, raw coordinates (kind:pubkey:d-tag), or hex. Comma-separated values supported: -e note1...,30023:pubkey:article",
},
{
flag: "-p <npub|hex|nip05|$me|$contacts>",
@@ -264,6 +264,7 @@ export const manPages: Record<string, ManPageEntry> = {
"req -i note1abc123... Direct lookup: fetch event by ID",
"req -i nevent1... Direct lookup: fetch event by nevent (uses relay hints)",
"req -e note1abc123... -k 1 Tag filtering: find notes that reply to or reference event",
"req -e 30023:pubkey...:article-name -k 1,7 Tag filtering: find events referencing addressable event",
"req -t nostr,grimoire,bitcoin -l 50 Get 50 events tagged #nostr, #grimoire, or #bitcoin",
"req --tag a 30023:7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194:grimoire Get events referencing addressable event (#a tag)",
"req -T r grimoire.rocks Get events referencing URL (#r tag)",