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
This commit is contained in:
Claude
2026-01-28 19:12:34 +00:00
parent e0725ae97e
commit f0a9c3a185

View File

@@ -1,437 +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 { getSeenRelays } from "applesauce-core/helpers/relays";
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]];
// Add q-tag for replies (per NIP-C7 quote tag format)
// Format: ["q", eventId, relayUrl, pubkey]
if (options?.replyTo) {
const replyEvent = eventStore.getEvent(options.replyTo);
if (replyEvent) {
// Get a relay hint from where we've seen this event
const seenRelays = getSeenRelays(replyEvent);
const relayHint =
seenRelays && seenRelays.size > 0 ? [...seenRelays][0] : "";
// Full q-tag with relay hint and author pubkey
tags.push(["q", options.replyTo, relayHint, replyEvent.pubkey]);
} else {
// Fallback: just the event ID
tags.push(["q", options.replyTo]);
}
}
// 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,
});
}
}