mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 16:07:15 +02:00
feat: add profile metadata fallback for NIP-29 groups
When a NIP-29 group ID is a valid pubkey and the relay doesn't support NIP-29 (no kind 39000 metadata), fall back to using the pubkey's profile metadata (kind 0) for group name, description, and icon. This allows users to create simple group chats using their pubkey as the group identifier on relays that don't have full NIP-29 support. Changes: - Add isValidPubkey() helper to validate 64-char hex strings - Modify resolveConversation() to fetch profile when metadata is missing - Add comprehensive tests for pubkey validation and parsing - Prefer NIP-29 metadata over profile fallback when available Tests: 20 NIP-29 adapter tests passing, 1037 total tests passing Build: Successful
This commit is contained in:
@@ -180,4 +180,77 @@ describe("Nip29Adapter", () => {
|
||||
expect(capabilities.requiresRelay).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("profile fallback for pubkey group IDs", () => {
|
||||
it("should parse valid pubkey as group ID", () => {
|
||||
const validPubkey =
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d";
|
||||
const result = adapter.parseIdentifier(
|
||||
`wss://relay.example.com'${validPubkey}`,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "group",
|
||||
value: validPubkey,
|
||||
relays: ["wss://relay.example.com"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse uppercase pubkey as group ID", () => {
|
||||
const validPubkey =
|
||||
"3BF0C63FCB93463407AF97A5E5EE64FA883D107EF9E558472C4EB9AAAEFA459D";
|
||||
const result = adapter.parseIdentifier(
|
||||
`wss://relay.example.com'${validPubkey}`,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "group",
|
||||
value: validPubkey,
|
||||
relays: ["wss://relay.example.com"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse mixed case pubkey as group ID", () => {
|
||||
const validPubkey =
|
||||
"3bF0c63Fcb93463407aF97a5e5Ee64fA883d107eF9e558472c4eB9aaaEfa459D";
|
||||
const result = adapter.parseIdentifier(
|
||||
`wss://relay.example.com'${validPubkey}`,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "group",
|
||||
value: validPubkey,
|
||||
relays: ["wss://relay.example.com"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not treat short hex strings as valid pubkeys", () => {
|
||||
// Less than 64 characters should be treated as normal group IDs
|
||||
const shortHex = "3bf0c63f";
|
||||
const result = adapter.parseIdentifier(
|
||||
`wss://relay.example.com'${shortHex}`,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "group",
|
||||
value: shortHex,
|
||||
relays: ["wss://relay.example.com"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not treat non-hex strings as valid pubkeys", () => {
|
||||
// 64 characters but contains non-hex characters
|
||||
const nonHex =
|
||||
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
|
||||
const result = adapter.parseIdentifier(
|
||||
`wss://relay.example.com'${nonHex}`,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "group",
|
||||
value: nonHex,
|
||||
relays: ["wss://relay.example.com"],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Observable, firstValueFrom } from "rxjs";
|
||||
import { map, first, toArray } from "rxjs/operators";
|
||||
import type { Filter } from "nostr-tools";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { nip19, kinds } from "nostr-tools";
|
||||
import { ChatProtocolAdapter, type SendMessageOptions } from "./base-adapter";
|
||||
import type {
|
||||
Conversation,
|
||||
@@ -25,6 +25,15 @@ import {
|
||||
GroupMessageBlueprint,
|
||||
ReactionBlueprint,
|
||||
} from "applesauce-common/blueprints";
|
||||
import { profileLoader } from "@/services/loaders";
|
||||
import { getProfileContent } from "applesauce-core/helpers";
|
||||
|
||||
/**
|
||||
* Check if a string is a valid nostr pubkey (64 character hex string)
|
||||
*/
|
||||
function isValidPubkey(str: string): boolean {
|
||||
return /^[0-9a-f]{64}$/i.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* NIP-29 Adapter - Relay-Based Groups
|
||||
@@ -181,16 +190,53 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
}
|
||||
|
||||
// Extract group info from metadata event
|
||||
const title = metadataEvent
|
||||
let title = metadataEvent
|
||||
? getTagValues(metadataEvent, "name")[0] || groupId
|
||||
: groupId;
|
||||
const description = metadataEvent
|
||||
let description = metadataEvent
|
||||
? getTagValues(metadataEvent, "about")[0]
|
||||
: undefined;
|
||||
const icon = metadataEvent
|
||||
let icon = metadataEvent
|
||||
? getTagValues(metadataEvent, "picture")[0]
|
||||
: undefined;
|
||||
|
||||
// Fallback: If no metadata found and groupId is a valid pubkey, use profile metadata
|
||||
if (!metadataEvent && isValidPubkey(groupId)) {
|
||||
console.log(
|
||||
`[NIP-29] No group metadata found, groupId is valid pubkey. Fetching profile for ${groupId.slice(0, 8)}...`,
|
||||
);
|
||||
|
||||
try {
|
||||
// Fetch profile metadata (kind 0) for the pubkey
|
||||
const profileEvent = await firstValueFrom(
|
||||
profileLoader({
|
||||
kind: kinds.Metadata,
|
||||
pubkey: groupId,
|
||||
relays: [relayUrl], // Try the group relay first
|
||||
}),
|
||||
{ defaultValue: undefined },
|
||||
);
|
||||
|
||||
if (profileEvent) {
|
||||
const profileContent = getProfileContent(profileEvent);
|
||||
if (profileContent) {
|
||||
console.log(
|
||||
`[NIP-29] Using profile metadata as fallback for group ${groupId.slice(0, 8)}...`,
|
||||
);
|
||||
title = profileContent.display_name || profileContent.name || title;
|
||||
description = profileContent.about || description;
|
||||
icon = profileContent.picture || icon;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`[NIP-29] Failed to fetch profile fallback for ${groupId.slice(0, 8)}:`,
|
||||
error,
|
||||
);
|
||||
// Continue with default title (groupId) if profile fetch fails
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[NIP-29] Group title: ${title}`);
|
||||
|
||||
// Fetch admins (kind 39001) and members (kind 39002) in parallel
|
||||
|
||||
Reference in New Issue
Block a user