mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-04 09:41:13 +02:00
fix: build proper q-tag with relay hint and author pubkey for replies (#224)
* fix: build proper q-tag with relay hint and author pubkey for replies When sending replies in NIP-29 and NIP-C7 adapters, now build the full q-tag format per NIP-C7 spec: ["q", eventId, relayUrl, pubkey] Previously only the event ID was included, making it harder for clients to fetch the referenced event. Now: - NIP-29: includes group relay URL and author pubkey - NIP-C7: includes seen relay hint and author pubkey https://claude.ai/code/session_01Jy51Ayk57fzaFuuFFm1j1K * chore: remove unused NIP-C7 adapter The NIP-C7 adapter was already disabled/commented out everywhere. Removing the file to reduce dead code. https://claude.ai/code/session_01Jy51Ayk57fzaFuuFFm1j1K * chore: remove NIP-C7 references from docs and code - Remove nip-c7 from ChatProtocol type - Remove commented NIP-C7 adapter imports and switch cases - Update comments to reference NIP-29 instead of NIP-C7 - Update kind 9 renderer docs to reference NIP-29 - Clean up chat-parser docs and error messages https://claude.ai/code/session_01Jy51Ayk57fzaFuuFFm1j1K --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,6 @@ import type {
|
||||
LiveActivityMetadata,
|
||||
} from "@/types/chat";
|
||||
import { CHAT_KINDS } from "@/types/chat";
|
||||
// import { NipC7Adapter } from "@/lib/chat/adapters/nip-c7-adapter"; // Coming soon
|
||||
import { Nip10Adapter } from "@/lib/chat/adapters/nip-10-adapter";
|
||||
import { Nip29Adapter } from "@/lib/chat/adapters/nip-29-adapter";
|
||||
import { Nip53Adapter } from "@/lib/chat/adapters/nip-53-adapter";
|
||||
@@ -1243,8 +1242,6 @@ function getAdapter(protocol: ChatProtocol): ChatProtocolAdapter {
|
||||
switch (protocol) {
|
||||
case "nip-10":
|
||||
return new Nip10Adapter();
|
||||
// case "nip-c7": // Phase 1 - Simple chat (coming soon)
|
||||
// return new NipC7Adapter();
|
||||
case "nip-29":
|
||||
return new Nip29Adapter();
|
||||
// case "nip-17": // Phase 2 - Encrypted DMs (coming soon)
|
||||
|
||||
@@ -24,7 +24,6 @@ import { getEventDisplayTitle } from "@/lib/event-title";
|
||||
import { UserName } from "./nostr/UserName";
|
||||
import { getTagValues } from "@/lib/nostr-utils";
|
||||
import { getSemanticAuthor } from "@/lib/semantic-author";
|
||||
// import { NipC7Adapter } from "@/lib/chat/adapters/nip-c7-adapter"; // Coming soon
|
||||
import { Nip29Adapter } from "@/lib/chat/adapters/nip-29-adapter";
|
||||
import type { ChatProtocol, ProtocolIdentifier } from "@/types/chat";
|
||||
import { useState, useEffect } from "react";
|
||||
@@ -742,8 +741,6 @@ function useDynamicTitle(window: WindowInstance): WindowTitleData {
|
||||
// Currently only NIP-29 is supported
|
||||
const getAdapter = () => {
|
||||
switch (protocol) {
|
||||
// case "nip-c7": // Coming soon
|
||||
// return new NipC7Adapter();
|
||||
case "nip-29":
|
||||
return new Nip29Adapter();
|
||||
default:
|
||||
|
||||
@@ -9,13 +9,13 @@ import { isValidHexEventId } from "@/lib/nostr-validation";
|
||||
import { InlineReplySkeleton } from "@/components/ui/skeleton";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 9 - Chat Message (NIP-C7)
|
||||
* Renderer for Kind 9 - Chat Message (NIP-29)
|
||||
* Displays chat messages with optional quoted parent message
|
||||
*/
|
||||
export function Kind9Renderer({ event, depth = 0 }: BaseEventProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
|
||||
// Parse 'q' tag for quoted parent message (NIP-C7 reply format)
|
||||
// Parse 'q' tag for quoted parent message
|
||||
const quotedEventIds = getTagValues(event, "q");
|
||||
const quotedEventId = quotedEventIds[0]; // First q tag
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ const kindRenderers: Record<number, React.ComponentType<BaseEventProps>> = {
|
||||
6: RepostRenderer, // Repost
|
||||
7: Kind7Renderer, // Reaction
|
||||
8: BadgeAwardRenderer, // Badge Award (NIP-58)
|
||||
9: Kind9Renderer, // Chat Message (NIP-C7)
|
||||
9: Kind9Renderer, // Chat Message (NIP-29)
|
||||
11: Kind1Renderer, // Public Thread Reply (NIP-10)
|
||||
16: RepostRenderer, // Generic Repost
|
||||
17: Kind7Renderer, // Reaction (NIP-25)
|
||||
|
||||
@@ -89,7 +89,7 @@ describe("parseChatCommand", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error for npub (NIP-C7 disabled)", () => {
|
||||
it("should throw error for npub (DMs not yet supported)", () => {
|
||||
expect(() => parseChatCommand(["npub1xyz"])).toThrow(
|
||||
/Unable to determine chat protocol/,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ChatCommandResult, GroupListIdentifier } from "@/types/chat";
|
||||
// import { NipC7Adapter } from "./chat/adapters/nip-c7-adapter";
|
||||
import { Nip10Adapter } from "./chat/adapters/nip-10-adapter";
|
||||
import { Nip29Adapter } from "./chat/adapters/nip-29-adapter";
|
||||
import { Nip53Adapter } from "./chat/adapters/nip-53-adapter";
|
||||
@@ -17,7 +16,6 @@ import { nip19 } from "nostr-tools";
|
||||
* 3. NIP-28 (channels) - specific event format (kind 40)
|
||||
* 4. NIP-29 (groups) - specific group ID format
|
||||
* 5. NIP-53 (live chat) - specific addressable format (kind 30311)
|
||||
* 6. NIP-C7 (simple chat) - fallback for generic pubkeys
|
||||
*
|
||||
* @param args - Command arguments (first arg is the identifier)
|
||||
* @returns Parsed result with protocol and identifier
|
||||
@@ -67,9 +65,8 @@ export function parseChatCommand(args: string[]): ChatCommandResult {
|
||||
new Nip10Adapter(), // NIP-10 - Thread chat (nevent/note)
|
||||
// new Nip17Adapter(), // Phase 2
|
||||
// new Nip28Adapter(), // Phase 3
|
||||
new Nip29Adapter(), // Phase 4 - Relay groups
|
||||
new Nip53Adapter(), // Phase 5 - Live activity chat
|
||||
// new NipC7Adapter(), // Phase 1 - Simple chat (disabled for now)
|
||||
new Nip29Adapter(), // NIP-29 - Relay groups
|
||||
new Nip53Adapter(), // NIP-53 - Live activity chat
|
||||
];
|
||||
|
||||
for (const adapter of adapters) {
|
||||
@@ -106,6 +103,6 @@ Currently supported formats:
|
||||
chat naddr1... (group list address)
|
||||
|
||||
More formats coming soon:
|
||||
- npub/nprofile/hex pubkey (NIP-C7/NIP-17 direct messages)`,
|
||||
- npub/nprofile/hex pubkey (NIP-17 direct messages)`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -459,9 +459,18 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
},
|
||||
);
|
||||
|
||||
// Add q-tag for replies (NIP-29 specific, not in blueprint yet)
|
||||
// Add q-tag for replies (quote tag format)
|
||||
// Format: ["q", eventId, relayUrl, pubkey]
|
||||
if (options?.replyTo) {
|
||||
draft.tags.push(["q", options.replyTo]);
|
||||
// Look up the event to get the author's pubkey for the q-tag
|
||||
const replyEvent = eventStore.getEvent(options.replyTo);
|
||||
if (replyEvent) {
|
||||
// Full q-tag with relay hint and author pubkey
|
||||
draft.tags.push(["q", options.replyTo, relayUrl, replyEvent.pubkey]);
|
||||
} else {
|
||||
// Fallback: at minimum include the relay hint since we know it
|
||||
draft.tags.push(["q", options.replyTo, relayUrl]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add NIP-92 imeta tags for blob attachments (not yet handled by applesauce)
|
||||
@@ -545,7 +554,7 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
getCapabilities(): ChatCapabilities {
|
||||
return {
|
||||
supportsEncryption: false, // kind 9 messages are public
|
||||
supportsThreading: true, // q-tag replies (NIP-C7 style)
|
||||
supportsThreading: true, // q-tag replies
|
||||
supportsModeration: true, // kind 9005/9006 for delete/ban
|
||||
supportsRoles: true, // admin, moderator, member
|
||||
supportsGroupManagement: true, // join/leave via kind 9021
|
||||
@@ -1108,7 +1117,7 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
}
|
||||
|
||||
// Regular chat message (kind 9)
|
||||
// Look for reply q-tags (NIP-29 uses q-tags like NIP-C7)
|
||||
// Look for reply q-tags
|
||||
// Use getQuotePointer to extract full EventPointer with relay hints
|
||||
const replyTo = getQuotePointer(event);
|
||||
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
import { Observable, firstValueFrom } from "rxjs";
|
||||
import { map, first } from "rxjs/operators";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import type { Filter } from "nostr-tools";
|
||||
import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
|
||||
import { ChatProtocolAdapter, type SendMessageOptions } from "./base-adapter";
|
||||
import type {
|
||||
Conversation,
|
||||
Message,
|
||||
ProtocolIdentifier,
|
||||
ChatCapabilities,
|
||||
LoadMessagesOptions,
|
||||
} from "@/types/chat";
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
import eventStore from "@/services/event-store";
|
||||
import pool from "@/services/relay-pool";
|
||||
import { publishEvent } from "@/services/hub";
|
||||
import accountManager from "@/services/accounts";
|
||||
import { isNip05, resolveNip05 } from "@/lib/nip05";
|
||||
import { getDisplayName, getQuotePointer } from "@/lib/nostr-utils";
|
||||
import { isValidHexPubkey } from "@/lib/nostr-validation";
|
||||
import { getProfileContent } from "applesauce-core/helpers";
|
||||
import { EventFactory } from "applesauce-core/event-factory";
|
||||
import { ReactionBlueprint } from "applesauce-common/blueprints";
|
||||
|
||||
/**
|
||||
* NIP-C7 Adapter - Simple Chat (Kind 9)
|
||||
*
|
||||
* Features:
|
||||
* - Direct messaging between users
|
||||
* - Quote-based threading (q-tag)
|
||||
* - No encryption
|
||||
* - Uses outbox relays
|
||||
*/
|
||||
export class NipC7Adapter extends ChatProtocolAdapter {
|
||||
readonly protocol = "nip-c7" as const;
|
||||
readonly type = "dm" as const;
|
||||
|
||||
/**
|
||||
* Parse identifier - accepts npub, nprofile, hex pubkey, or NIP-05
|
||||
*/
|
||||
parseIdentifier(input: string): ProtocolIdentifier | null {
|
||||
// Try bech32 decoding (npub/nprofile)
|
||||
try {
|
||||
const decoded = nip19.decode(input);
|
||||
if (decoded.type === "npub") {
|
||||
return {
|
||||
type: "chat-partner",
|
||||
value: decoded.data,
|
||||
};
|
||||
}
|
||||
if (decoded.type === "nprofile") {
|
||||
return {
|
||||
type: "chat-partner",
|
||||
value: decoded.data.pubkey,
|
||||
relays: decoded.data.relays,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// Not bech32, try other formats
|
||||
}
|
||||
|
||||
// Try hex pubkey
|
||||
if (isValidHexPubkey(input)) {
|
||||
return {
|
||||
type: "chat-partner",
|
||||
value: input,
|
||||
};
|
||||
}
|
||||
|
||||
// Try NIP-05
|
||||
if (isNip05(input)) {
|
||||
return {
|
||||
type: "chat-partner-nip05",
|
||||
value: input,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve conversation from identifier
|
||||
*/
|
||||
async resolveConversation(
|
||||
identifier: ProtocolIdentifier,
|
||||
): Promise<Conversation> {
|
||||
let pubkey: string;
|
||||
|
||||
// Resolve NIP-05 if needed
|
||||
if (identifier.type === "chat-partner-nip05") {
|
||||
const resolved = await resolveNip05(identifier.value);
|
||||
if (!resolved) {
|
||||
throw new Error(`Failed to resolve NIP-05: ${identifier.value}`);
|
||||
}
|
||||
pubkey = resolved;
|
||||
} else if (
|
||||
identifier.type === "chat-partner" ||
|
||||
identifier.type === "dm-recipient"
|
||||
) {
|
||||
pubkey = identifier.value;
|
||||
} else {
|
||||
throw new Error(
|
||||
`NIP-C7 adapter cannot handle identifier type: ${identifier.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
const activePubkey = accountManager.active$.value?.pubkey;
|
||||
if (!activePubkey) {
|
||||
throw new Error("No active account");
|
||||
}
|
||||
|
||||
// Get display name for partner
|
||||
const metadataEvent = await this.getMetadata(pubkey);
|
||||
const metadata = metadataEvent
|
||||
? getProfileContent(metadataEvent)
|
||||
: undefined;
|
||||
const title = getDisplayName(pubkey, metadata);
|
||||
|
||||
return {
|
||||
id: `nip-c7:${pubkey}`,
|
||||
type: "dm",
|
||||
protocol: "nip-c7",
|
||||
title,
|
||||
participants: [
|
||||
{ pubkey: activePubkey, role: "member" },
|
||||
{ pubkey, role: "member" },
|
||||
],
|
||||
unreadCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load messages between active user and conversation partner
|
||||
*/
|
||||
loadMessages(
|
||||
conversation: Conversation,
|
||||
options?: LoadMessagesOptions,
|
||||
): Observable<Message[]> {
|
||||
const activePubkey = accountManager.active$.value?.pubkey;
|
||||
if (!activePubkey) {
|
||||
throw new Error("No active account");
|
||||
}
|
||||
|
||||
const partner = conversation.participants.find(
|
||||
(p) => p.pubkey !== activePubkey,
|
||||
);
|
||||
if (!partner) {
|
||||
throw new Error("No conversation partner found");
|
||||
}
|
||||
|
||||
// Subscribe to kind 9 messages between users
|
||||
const filter: Filter = {
|
||||
kinds: [9],
|
||||
authors: [activePubkey, partner.pubkey],
|
||||
"#p": [activePubkey, partner.pubkey],
|
||||
limit: options?.limit || 50,
|
||||
};
|
||||
|
||||
if (options?.before) {
|
||||
filter.until = options.before;
|
||||
}
|
||||
if (options?.after) {
|
||||
filter.since = options.after;
|
||||
}
|
||||
|
||||
// Start subscription to populate EventStore
|
||||
pool
|
||||
.subscription([], [filter], {
|
||||
eventStore, // Automatically add to store
|
||||
})
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
if (typeof response === "string") {
|
||||
// EOSE received
|
||||
console.log("[NIP-C7] EOSE received for messages");
|
||||
} else {
|
||||
// Event received
|
||||
console.log(
|
||||
`[NIP-C7] Received message: ${response.id.slice(0, 8)}...`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Return observable from EventStore which will update automatically
|
||||
return eventStore.timeline(filter).pipe(
|
||||
map((events) => {
|
||||
console.log(`[NIP-C7] Timeline has ${events.length} messages`);
|
||||
return events
|
||||
.map((event) => this.eventToMessage(event, conversation.id))
|
||||
.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more historical messages (pagination)
|
||||
*/
|
||||
async loadMoreMessages(
|
||||
_conversation: Conversation,
|
||||
_before: number,
|
||||
): Promise<Message[]> {
|
||||
// For now, return empty - pagination to be implemented in Phase 6
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message
|
||||
*/
|
||||
async sendMessage(
|
||||
conversation: Conversation,
|
||||
content: string,
|
||||
options?: SendMessageOptions,
|
||||
): Promise<void> {
|
||||
const activePubkey = accountManager.active$.value?.pubkey;
|
||||
const activeSigner = accountManager.active$.value?.signer;
|
||||
|
||||
if (!activePubkey || !activeSigner) {
|
||||
throw new Error("No active account or signer");
|
||||
}
|
||||
|
||||
const partner = conversation.participants.find(
|
||||
(p) => p.pubkey !== activePubkey,
|
||||
);
|
||||
if (!partner) {
|
||||
throw new Error("No conversation partner found");
|
||||
}
|
||||
|
||||
// Create event factory and sign event
|
||||
const factory = new EventFactory();
|
||||
factory.setSigner(activeSigner);
|
||||
|
||||
const tags: string[][] = [["p", partner.pubkey]];
|
||||
if (options?.replyTo) {
|
||||
tags.push(["q", options.replyTo]); // NIP-C7 quote tag for threading
|
||||
}
|
||||
|
||||
// Add NIP-30 emoji tags
|
||||
if (options?.emojiTags) {
|
||||
for (const emoji of options.emojiTags) {
|
||||
tags.push(["emoji", emoji.shortcode, emoji.url]);
|
||||
}
|
||||
}
|
||||
|
||||
const draft = await factory.build({ kind: 9, content, tags });
|
||||
const event = await factory.sign(draft);
|
||||
await publishEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reaction (kind 7) to a message
|
||||
*/
|
||||
async sendReaction(
|
||||
conversation: Conversation,
|
||||
messageId: string,
|
||||
emoji: string,
|
||||
customEmoji?: { shortcode: string; url: string },
|
||||
): Promise<void> {
|
||||
const activePubkey = accountManager.active$.value?.pubkey;
|
||||
const activeSigner = accountManager.active$.value?.signer;
|
||||
|
||||
if (!activePubkey || !activeSigner) {
|
||||
throw new Error("No active account or signer");
|
||||
}
|
||||
|
||||
const partner = conversation.participants.find(
|
||||
(p) => p.pubkey !== activePubkey,
|
||||
);
|
||||
if (!partner) {
|
||||
throw new Error("No conversation partner found");
|
||||
}
|
||||
|
||||
// Fetch the message being reacted to
|
||||
const messageEvent = await firstValueFrom(eventStore.event(messageId), {
|
||||
defaultValue: undefined,
|
||||
});
|
||||
|
||||
if (!messageEvent) {
|
||||
throw new Error("Message event not found");
|
||||
}
|
||||
|
||||
// Create event factory
|
||||
const factory = new EventFactory();
|
||||
factory.setSigner(activeSigner);
|
||||
|
||||
// Use ReactionBlueprint - auto-handles e-tag, k-tag, p-tag, custom emoji
|
||||
const emojiArg = customEmoji
|
||||
? { shortcode: customEmoji.shortcode, url: customEmoji.url }
|
||||
: emoji;
|
||||
|
||||
const draft = await factory.create(
|
||||
ReactionBlueprint,
|
||||
messageEvent,
|
||||
emojiArg,
|
||||
);
|
||||
|
||||
// Note: ReactionBlueprint already adds p-tag for message author
|
||||
// For NIP-C7, we might want to ensure partner is tagged if different from author
|
||||
// but the blueprint should handle this correctly
|
||||
|
||||
// Sign the event
|
||||
const event = await factory.sign(draft);
|
||||
await publishEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get protocol capabilities
|
||||
*/
|
||||
getCapabilities(): ChatCapabilities {
|
||||
return {
|
||||
supportsEncryption: false,
|
||||
supportsThreading: true, // q-tag quotes
|
||||
supportsModeration: false,
|
||||
supportsRoles: false,
|
||||
supportsGroupManagement: false,
|
||||
canCreateConversations: true,
|
||||
requiresRelay: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a replied-to message
|
||||
* First checks EventStore, then fetches from relays if needed
|
||||
*/
|
||||
async loadReplyMessage(
|
||||
_conversation: Conversation,
|
||||
pointer: EventPointer | AddressPointer,
|
||||
): Promise<NostrEvent | null> {
|
||||
// Extract event ID from pointer (EventPointer has 'id', AddressPointer doesn't)
|
||||
const eventId = "id" in pointer ? pointer.id : null;
|
||||
|
||||
if (!eventId) {
|
||||
console.warn(
|
||||
"[NIP-C7] AddressPointer not supported for loadReplyMessage",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// First check EventStore - might already be loaded
|
||||
const cachedEvent = await eventStore
|
||||
.event(eventId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
if (cachedEvent) {
|
||||
return cachedEvent;
|
||||
}
|
||||
|
||||
// Not in store, fetch from relay pool (use pointer relays if available)
|
||||
const relays = pointer.relays || [];
|
||||
console.log(
|
||||
`[NIP-C7] Fetching reply message ${eventId.slice(0, 8)}... from ${relays.length > 0 ? relays.join(", ") : "global pool"}`,
|
||||
);
|
||||
|
||||
const filter: Filter = {
|
||||
ids: [eventId],
|
||||
limit: 1,
|
||||
};
|
||||
|
||||
const events: NostrEvent[] = [];
|
||||
const obs = pool.subscription(relays, [filter], { eventStore });
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
console.log(
|
||||
`[NIP-C7] Reply message fetch timeout for ${eventId.slice(0, 8)}...`,
|
||||
);
|
||||
resolve();
|
||||
}, 3000);
|
||||
|
||||
const sub = obs.subscribe({
|
||||
next: (response) => {
|
||||
if (typeof response === "string") {
|
||||
// EOSE received
|
||||
clearTimeout(timeout);
|
||||
sub.unsubscribe();
|
||||
resolve();
|
||||
} else {
|
||||
// Event received
|
||||
events.push(response);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
clearTimeout(timeout);
|
||||
console.error(`[NIP-C7] Reply message fetch error:`, err);
|
||||
sub.unsubscribe();
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return events[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Convert Nostr event to Message
|
||||
*/
|
||||
private eventToMessage(event: NostrEvent, conversationId: string): Message {
|
||||
// Use getQuotePointer to extract full EventPointer with relay hints
|
||||
const replyTo = getQuotePointer(event);
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
conversationId,
|
||||
author: event.pubkey,
|
||||
content: event.content,
|
||||
timestamp: event.created_at,
|
||||
replyTo: replyTo || undefined,
|
||||
protocol: "nip-c7",
|
||||
event,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get user metadata
|
||||
*/
|
||||
private async getMetadata(pubkey: string): Promise<NostrEvent | undefined> {
|
||||
return firstValueFrom(eventStore.replaceable(0, pubkey), {
|
||||
defaultValue: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,7 @@ export const CHAT_KINDS = [
|
||||
/**
|
||||
* Chat protocol identifier
|
||||
*/
|
||||
export type ChatProtocol =
|
||||
| "nip-c7"
|
||||
| "nip-17"
|
||||
| "nip-28"
|
||||
| "nip-29"
|
||||
| "nip-53"
|
||||
| "nip-10";
|
||||
export type ChatProtocol = "nip-17" | "nip-28" | "nip-29" | "nip-53" | "nip-10";
|
||||
|
||||
/**
|
||||
* Conversation type
|
||||
@@ -171,7 +165,7 @@ export interface LiveActivityIdentifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* NIP-C7/NIP-17 direct message identifier (resolved pubkey)
|
||||
* NIP-17 direct message identifier (resolved pubkey)
|
||||
*/
|
||||
export interface DMIdentifier {
|
||||
type: "dm-recipient" | "chat-partner";
|
||||
@@ -182,7 +176,7 @@ export interface DMIdentifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* NIP-C7 NIP-05 identifier (needs resolution)
|
||||
* NIP-05 identifier for DMs (needs resolution)
|
||||
*/
|
||||
export interface NIP05Identifier {
|
||||
type: "chat-partner-nip05";
|
||||
|
||||
Reference in New Issue
Block a user