nostr-addressable-event-dual-tag-query

Par divinevideo · divine-mobile

Corrige les bugs "le compteur affiche X mais la liste est vide" lors de l'interrogation d'événements liés (commentaires, réactions, zaps, reposts) pour les événements adressables Nostr (Kind 30000-39999). À utiliser quand : (1) l'API REST affiche un nombre d'interactions > 0 mais la requête WebSocket renvoie un résultat vide, (2) des commentaires/réactions existent mais l'application affiche "Aucun commentaire" ou similaire, (3) vous travaillez avec des commentaires NIP-22, des réactions NIP-25 ou des reposts NIP-18 sur des événements vidéo Kind 34235/34236 ou d'autres événements adressables. Cause racine : les clients peuvent référencer des événements adressables via un tag E (ID d'événement) ou un tag A (format kind:pubkey:d-tag), et n'interroger qu'un seul tag fait manquer les événements taggés avec l'autre.

npx skills add https://github.com/divinevideo/divine-mobile --skill nostr-addressable-event-dual-tag-query

Requête Dual-Tag pour Événement Adressable Nostr

Problème

Lors de l'interrogation d'événements associés (commentaires, réactions, reposts, zaps) pour des événements adressables Nostr (Kind 30000-39999), la requête retourne moins de résultats ou aucun résultat même si un service d'indexation affiche le compte correct. Cela se produit car les événements adressables peuvent être référencés de deux façons, et différents clients utilisent différentes méthodes.

Contexte / Conditions de déclenchement

Utilisez cette skill quand :

  • Une vidéo/post affiche un compte de 5 commentaires, mais la modal de commentaires dit « Aucun commentaire »
  • Une API REST (comme Funnelcake) retourne les bons comptes d'engagement, mais les requêtes WebSocket retournent du vide
  • Vous travaillez avec des commentaires NIP-22 (Kind 1111) sur des événements adressables
  • Vous travaillez avec des réactions NIP-25 (Kind 7) sur des événements adressables
  • Vous travaillez avec des reposts NIP-18 (Kind 6/16) sur des événements adressables
  • Une feature quelconque interrogeant des événements qui référencent des événements Kind 30000-39999

Cause racine

Pour les événements adressables (Kind 30000-39999, comme les vidéos Kind 34236), les événements associés peuvent référencer la cible en utilisant soit :

  1. E tag : Référence par ID d'événement (hex 64 caractères)

    ["E", "abc123...def456", "", "author-pubkey"]
  2. A tag : Référence par identifiant adressable (kind:pubkey:d-tag)

    ["A", "34236:abc123...def456:my-video-id", "", "author-pubkey"]

Différents clients et indexeurs Nostr utilisent différentes conventions :

  • Certains utilisent toujours les E tags (basés sur l'ID d'événement)
  • D'autres préfèrent les A tags pour les événements adressables (en suivant strictement NIP-22)
  • Certains utilisent les deux tags

Si votre app n'interroge que par un type de tag, vous manquerez les événements taggés avec l'autre.

Solution

1. Mettre à jour la classe Filter (si nécessaire)

Assurez-vous que votre classe Filter supporte les requêtes avec A tag en majuscule :

// Dans la classe Filter
List<String>? uppercaseA;  // Ajouter ce champ

// Dans toJson()
if (uppercaseA != null) {
  data['#A'] = uppercaseA;
}

2. Interroger par BOTH Tags

Lors du chargement d'événements associés, exécutez deux requêtes parallèles et fusionnez les résultats :

Future<List<Event>> loadRelatedEvents({
  required String eventId,
  required String? addressableId,  // Format: "kind:pubkey:d-tag"
}) async {
  // Interroger par E tag (event ID)
  final filterByE = Filter(
    kinds: [targetKind],
    uppercaseE: [eventId],
  );

  // Si addressable ID disponible, interroger aussi par A tag
  if (addressableId != null && addressableId.isNotEmpty) {
    final filterByA = Filter(
      kinds: [targetKind],
      uppercaseA: [addressableId],
    );

    // Exécuter les deux requêtes en parallèle
    final results = await Future.wait([
      nostrClient.queryEvents([filterByE]),
      nostrClient.queryEvents([filterByA]),
    ]);

    // Fusionner et dédupliquer par event ID
    final eventMap = <String, Event>{};
    for (final event in results[0]) {
      eventMap[event.id] = event;
    }
    for (final event in results[1]) {
      eventMap[event.id] = event;
    }

    return eventMap.values.toList();
  }

  return nostrClient.queryEvents([filterByE]);
}

3. Poster avec BOTH Tags

Lors de la création d'événements associés, incluez les E et A tags pour une compatibilité maximale :

final tags = <List<String>>[
  // Toujours inclure E tag
  ['E', rootEventId, '', authorPubkey],

  // Inclure A tag pour les événements adressables
  if (rootAddressableId != null && rootAddressableId.isNotEmpty)
    ['A', rootAddressableId, '', authorPubkey],

  // ... autres tags
];

4. Construire l'ID Adressable

Construisez l'identifiant adressable à partir des métadonnées d'événement :

String? get addressableId {
  if (dTag == null) return null;
  return '$kind:$pubkey:$dTag';  // ex: "34236:abc123...:my-video-id"
}

Vérification

Après implémentation :

  1. Trouvez un événement où l'API REST affiche count > 0 mais votre app affichait vide
  2. Vérifiez que la liste affiche maintenant les éléments attendus
  3. Postez un nouvel événement associé (commentaire/réaction)
  4. Vérifiez qu'il apparaît lors de l'interrogation par E ou A tag

Exemple

Avant correction - interroge seulement par E tag :

final filter = Filter(
  kinds: [1111],  // Commentaires
  uppercaseE: [videoEventId],  // Seulement E tag !
);
// Retourne 0 commentaires même si 5 existent (taggés avec A)

Après correction - interroge les deux tags :

final filterByE = Filter(kinds: [1111], uppercaseE: [videoEventId]);
final filterByA = Filter(kinds: [1111], uppercaseA: [addressableId]);

final results = await Future.wait([
  client.queryEvents([filterByE]),
  client.queryEvents([filterByA]),
]);
// Retourne les 5 commentaires peu importe comment ils ont été taggés

Notes

  • Ce pattern s'applique à TOUTE feature qui interroge des événements référençant des événements adressables
  • Le d-tag peut contenir des deux-points, donc analysez les ID adressables avec soin : parts.sublist(2).join(':')
  • Pour les requêtes de compte (NIP-45), prenez le maximum des deux comptes (peut sur-compter si les événements ont les deux tags)
  • Certains relays peuvent ne pas supporter les requêtes de filtre #A - testez avec vos relays cibles
  • C'est particulièrement important pour l'interopérabilité entre différents clients Nostr

NIPs affectés

  • NIP-22 : Commentaires (Kind 1111) - utilise E/A tags pour scope root
  • NIP-25 : Réactions (Kind 7) - référence l'événement cible
  • NIP-18 : Reposts (Kind 6/16) - référence l'événement reposté
  • NIP-33 : Parameterized Replaceable Events (Kind 30000-39999) - définit le format adressable
  • NIP-71 : Événements vidéo (Kind 34235/34236) - cas d'usage courant pour ce pattern

Références

Skills similaires