fix: correct NIP-29 member list processing order

Per NIP-29 spec, kind 39002 (members list) includes ALL members
(both admins and regular members). The previous implementation
processed kind 39001 (admins) first, which could cause admins
appearing only in kind 39002 to be incorrectly labeled as "member".

Fixed by:
- Processing kind 39002 first (all members with "member" role)
- Then processing kind 39001 to upgrade admins with proper roles

This ensures admins always get their correct role (admin/moderator/host)
even if they appear in both events.
This commit is contained in:
Claude
2026-01-19 22:00:59 +00:00
parent 83b3b0e416
commit 404b996cf7

View File

@@ -250,7 +250,21 @@ export class Nip29Adapter extends ChatProtocolAdapter {
// Extract participants from both admins and members events
const participantsMap = new Map<string, Participant>();
// Process kind:39001 (admins with roles)
// Process kind:39002 FIRST (members list includes ALL members)
// Per NIP-29: kind 39002 includes both admins and regular members
const memberEvents = participantEvents.filter((e) => e.kind === 39002);
for (const event of memberEvents) {
// Each p tag: ["p", "<pubkey>"]
for (const tag of event.tags) {
if (tag[0] === "p" && tag[1]) {
const pubkey = tag[1];
// Add as member (will be upgraded to admin role if in kind 39001)
participantsMap.set(pubkey, { pubkey, role: "member" });
}
}
}
// Process kind:39001 SECOND (upgrade admins with their roles)
const adminEvents = participantEvents.filter((e) => e.kind === 39001);
for (const event of adminEvents) {
// Each p tag: ["p", "<pubkey>", "<role1>", "<role2>", ...]
@@ -259,26 +273,12 @@ export class Nip29Adapter extends ChatProtocolAdapter {
const pubkey = tag[1];
const roles = tag.slice(2).filter((r) => r); // Get all roles after pubkey
const primaryRole = normalizeRole(roles[0]); // Use first role as primary
// Upgrade to admin role (overwrites "member" if already in map)
participantsMap.set(pubkey, { pubkey, role: primaryRole });
}
}
}
// Process kind:39002 (members without roles)
const memberEvents = participantEvents.filter((e) => e.kind === 39002);
for (const event of memberEvents) {
// Each p tag: ["p", "<pubkey>"]
for (const tag of event.tags) {
if (tag[0] === "p" && tag[1]) {
const pubkey = tag[1];
// Only add if not already in map (admins take precedence)
if (!participantsMap.has(pubkey)) {
participantsMap.set(pubkey, { pubkey, role: "member" });
}
}
}
}
const participants = Array.from(participantsMap.values());
console.log(