Files
grimoire/src/types/chat.ts
Alejandro c4687da3ef Add multi-room group chat support (#70)
* feat: add multi-room group chat interface (GroupListViewer)

Add a Discord/Slack-style multi-room chat interface for NIP-29 groups:

- New GroupListViewer component with split layout:
  - Left panel: List of groups from kind 10009, sorted by recency
  - Right panel: Full chat view for selected group
- Loads group metadata (kind 39000) for icons and names
- Tracks latest messages (kind 9) for activity-based sorting
2026-01-13 10:30:10 +01:00

249 lines
5.5 KiB
TypeScript

import type { NostrEvent } from "./nostr";
/**
* Chat protocol identifier
*/
export type ChatProtocol = "nip-c7" | "nip-17" | "nip-28" | "nip-29" | "nip-53";
/**
* Conversation type
*/
export type ConversationType = "dm" | "channel" | "group" | "live-chat";
/**
* Participant role in a conversation
*/
export type ParticipantRole = "admin" | "moderator" | "member" | "host";
/**
* Participant in a conversation
*/
export interface Participant {
pubkey: string;
role?: ParticipantRole;
permissions?: string[];
}
/**
* Live activity metadata for NIP-53
*/
export interface LiveActivityMetadata {
status: "planned" | "live" | "ended";
streaming?: string;
recording?: string;
starts?: number;
ends?: number;
hostPubkey: string;
currentParticipants?: number;
totalParticipants?: number;
hashtags: string[];
relays: string[];
}
/**
* Protocol-specific conversation metadata
*/
export interface ConversationMetadata {
// NIP-28 channel
channelEvent?: NostrEvent; // kind 40 creation event
// NIP-29 group
groupId?: string; // host'group-id format
relayUrl?: string; // Relay enforcing group rules
description?: string; // Group description
icon?: string; // Group icon/picture URL
// NIP-53 live chat
activityAddress?: {
kind: number;
pubkey: string;
identifier: string;
};
liveActivity?: LiveActivityMetadata;
// NIP-17 DM
encrypted?: boolean;
giftWrapped?: boolean;
}
/**
* Generic conversation abstraction
* Works across all messaging protocols
*/
export interface Conversation {
id: string; // Protocol-specific identifier
type: ConversationType;
protocol: ChatProtocol;
title: string;
participants: Participant[];
metadata?: ConversationMetadata;
lastMessage?: Message;
unreadCount: number;
}
/**
* Message metadata (reactions, zaps, encryption status, etc.)
*/
export interface MessageMetadata {
encrypted?: boolean;
reactions?: NostrEvent[];
zaps?: NostrEvent[];
deleted?: boolean;
hidden?: boolean; // NIP-28 channel hide
// Zap-specific metadata (for type: "zap" messages)
zapAmount?: number; // Amount in sats
zapRecipient?: string; // Pubkey of zap recipient
// NIP-61 nutzap-specific metadata
nutzapUnit?: string; // Unit for nutzap amount (sat, usd, eur, etc.)
}
/**
* Message type - system messages for events like join/leave, user messages for chat, zaps for stream tips
*/
export type MessageType = "user" | "system" | "zap";
/**
* Generic message abstraction
* Works across all messaging protocols
*/
export interface Message {
id: string;
conversationId: string;
author: string; // pubkey
content: string;
timestamp: number;
type?: MessageType; // Defaults to "user" if not specified
replyTo?: string; // Parent message ID
metadata?: MessageMetadata;
protocol: ChatProtocol;
event: NostrEvent; // Original Nostr event for verification
}
/**
* NIP-29 group identifier
*/
export interface GroupIdentifier {
type: "group";
/** Group ID (e.g., "bitcoin-dev") */
value: string;
/** Relay URL where the group is hosted (required for NIP-29) */
relays: string[];
}
/**
* NIP-53 live activity identifier
*/
export interface LiveActivityIdentifier {
type: "live-activity";
/** Address pointer for the live activity */
value: {
kind: 30311;
pubkey: string;
identifier: string;
};
/** Relay hints from naddr encoding */
relays?: string[];
}
/**
* NIP-C7/NIP-17 direct message identifier (resolved pubkey)
*/
export interface DMIdentifier {
type: "dm-recipient" | "chat-partner";
/** Recipient pubkey (hex) */
value: string;
/** Relay hints */
relays?: string[];
}
/**
* NIP-C7 NIP-05 identifier (needs resolution)
*/
export interface NIP05Identifier {
type: "chat-partner-nip05";
/** NIP-05 address to resolve */
value: string;
/** Relay hints */
relays?: string[];
}
/**
* NIP-28 channel identifier (future)
*/
export interface ChannelIdentifier {
type: "channel";
/** Channel creation event ID or address */
value: string;
/** Relay hints */
relays?: string[];
}
/**
* Group list identifier (kind 10009)
* Used to open multi-room chat interface
*/
export interface GroupListIdentifier {
type: "group-list";
/** Address pointer for the group list (kind 10009) */
value: {
kind: 10009;
pubkey: string;
identifier: string;
};
/** Relay hints from naddr encoding */
relays?: string[];
}
/**
* Protocol-specific identifier - discriminated union
* Returned by adapter parseIdentifier()
*/
export type ProtocolIdentifier =
| GroupIdentifier
| LiveActivityIdentifier
| DMIdentifier
| NIP05Identifier
| ChannelIdentifier
| GroupListIdentifier;
/**
* Chat command parsing result
*/
export interface ChatCommandResult {
protocol: ChatProtocol;
identifier: ProtocolIdentifier;
adapter: any; // Will be ChatProtocolAdapter but avoiding circular dependency
}
/**
* Message loading options
*/
export interface LoadMessagesOptions {
limit?: number;
before?: number; // Unix timestamp
after?: number; // Unix timestamp
}
/**
* Conversation creation parameters
*/
export interface CreateConversationParams {
type: ConversationType;
title?: string;
participants: string[]; // pubkeys
metadata?: Record<string, any>;
}
/**
* Chat capabilities - what features a protocol supports
*/
export interface ChatCapabilities {
supportsEncryption: boolean;
supportsThreading: boolean;
supportsModeration: boolean;
supportsRoles: boolean;
supportsGroupManagement: boolean;
canCreateConversations: boolean;
requiresRelay: boolean;
}