commit 14e6b44d7a2504a91669bac92cac3c8d56460e15
parent 5266c5d59b2b46e1a351c7b4421fc54b359def2a
Author: William Casarin <jb55@jb55.com>
Date: Sat, 1 Nov 2025 13:02:33 -0700
metadata: count reposts
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
4 files changed, 150 insertions(+), 31 deletions(-)
diff --git a/src/metadata.c b/src/metadata.c
@@ -138,9 +138,9 @@ int ndb_note_meta_builder_init(struct ndb_note_meta_builder *builder, unsigned c
}
/* note flags are stored in the header entry */
-uint32_t ndb_note_meta_flags(struct ndb_note_meta *meta)
+uint64_t *ndb_note_meta_flags(struct ndb_note_meta *meta)
{
- return meta->flags;
+ return &meta->flags;
}
/* note flags are stored in the header entry */
@@ -271,7 +271,7 @@ struct ndb_note_meta_entry *ndb_note_meta_builder_find_entry(
void ndb_note_meta_reaction_set(struct ndb_note_meta_entry *entry, uint32_t count, union ndb_reaction_str str)
{
entry->type = NDB_NOTE_META_REACTION;
- entry->flags = 0;
+ entry->aux2.flags = 0;
entry->aux.value = count;
entry->payload.reaction_str = str;
}
@@ -281,10 +281,12 @@ void ndb_note_meta_counts_set(struct ndb_note_meta_entry *entry,
uint32_t total_reactions,
uint16_t quotes,
uint16_t direct_replies,
- uint32_t thread_replies)
+ uint32_t thread_replies,
+ uint16_t reposts)
{
entry->type = NDB_NOTE_META_COUNTS;
entry->aux.total_reactions = total_reactions;
+ entry->aux2.reposts = reposts;
entry->payload.counts.quotes = quotes;
entry->payload.counts.direct_replies = direct_replies;
entry->payload.counts.thread_replies = thread_replies;
@@ -398,6 +400,11 @@ uint16_t *ndb_note_meta_counts_quotes(struct ndb_note_meta_entry *entry)
return &entry->payload.counts.quotes;
}
+uint16_t *ndb_note_meta_counts_reposts(struct ndb_note_meta_entry *entry)
+{
+ return &entry->aux2.reposts;
+}
+
void ndb_note_meta_reaction_set_count(struct ndb_note_meta_entry *entry, uint32_t count)
{
entry->aux.value = count;
@@ -428,7 +435,8 @@ void print_note_meta(struct ndb_note_meta *meta)
printf("%s%d ", strbuf, *ndb_note_meta_reaction_count(entry));
break;
case NDB_NOTE_META_COUNTS:
- printf("quotes %d\treplies %d\tall_replies %d\treactions %d\t",
+ printf("reposts %d\tquotes %d\treplies %d\tall_replies %d\treactions %d\t",
+ *ndb_note_meta_counts_reposts(entry),
*ndb_note_meta_counts_quotes(entry),
*ndb_note_meta_counts_direct_replies(entry),
*ndb_note_meta_counts_thread_replies(entry),
diff --git a/src/metadata.h b/src/metadata.h
@@ -25,7 +25,11 @@ enum ndb_meta_clone_result ndb_note_meta_clone_with_entry(
struct ndb_note_meta_entry {
// 4 byte entry header
uint16_t type;
- uint16_t flags;
+
+ union {
+ uint16_t flags;
+ uint16_t reposts;
+ } aux2;
// additional 4 bytes of aux storage for payloads that are >8 bytes
//
diff --git a/src/nostrdb.c b/src/nostrdb.c
@@ -2252,6 +2252,61 @@ cleanup:
return 1;
}
+static int ndb_count_reposts(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count)
+{
+ MDB_val k, v;
+ MDB_cursor *cur;
+ MDB_dbi db;
+ int rc;
+ unsigned char *keybuf;
+ struct ndb_note *note;
+ uint64_t note_key, kind;
+ char buffer[41]; /* 1 + 32 + 8 */
+
+ *count = 0;
+ db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS];
+
+ /* we will iterate q tags for this particular id */
+ if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) {
+ fprintf(stderr, "ndb_count_quotes: mdb_cursor_open failed, error %d\n", rc);
+ return 0;
+ }
+
+ buffer[0] = 'e';
+ memcpy(&buffer[1], note_id, 32);
+ memset(&buffer[33], 0x00, 8);
+
+ k.mv_data = buffer;
+ k.mv_size = sizeof(buffer);
+ v.mv_data = NULL;
+ v.mv_size = 0;
+
+ if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE))
+ goto cleanup;
+
+ do {
+ keybuf = (unsigned char *)k.mv_data;
+ note_key = *((uint64_t*)v.mv_data);
+
+ if (k.mv_size < sizeof(buffer))
+ break;
+ if (keybuf[0] != 'e')
+ break;
+ if (memcmp(&keybuf[1], note_id, 32) != 0)
+ break;
+ if (!(note = ndb_get_note_by_key(txn, note_key, NULL)))
+ continue;
+ kind = ndb_note_kind(note);
+ if (!(kind == 6 || kind == 16))
+ continue;
+ (*count)++;
+ } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0);
+
+cleanup:
+ mdb_cursor_close(cur);
+ return 1;
+}
+
/* count all of the quote reposts for a note id */
static int ndb_count_quotes(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count)
{
@@ -2291,6 +2346,11 @@ static int ndb_count_quotes(struct ndb_txn *txn, const unsigned char *note_id, u
break;
if (memcmp(&keybuf[1], note_id, 32) != 0)
break;
+ /* TODO(jb55): technically we should check to see if this is a kind 1.
+ * there could be other kinds with q tags that reference this note
+ *
+ * Starting to think we should have tag-kind index
+ */
(*count)++;
if (mdb_cursor_get(cur, &k, &v, MDB_NEXT))
@@ -2309,9 +2369,9 @@ static int ndb_note_meta_builder_counts(struct ndb_txn *txn,
struct ndb_note_meta_builder *builder)
{
uint32_t thread_replies, total_reactions;
- uint16_t direct_replies, quotes;
+ uint16_t direct_replies, quotes, reposts;
struct ndb_note_meta_entry *entry;
- int rcs[3];
+ int rcs[4];
quotes = 0;
direct_replies = 0;
@@ -2321,13 +2381,14 @@ static int ndb_note_meta_builder_counts(struct ndb_txn *txn,
rcs[0] = ndb_rebuild_reaction_metadata(txn, note_id, builder, &total_reactions);
rcs[1] = ndb_count_quotes(txn, note_id, "es);
rcs[2] = ndb_count_replies(txn, note_id, &direct_replies, &thread_replies);
+ rcs[3] = ndb_count_reposts(txn, note_id, &reposts);
- if (!rcs[0] && !rcs[1] && !rcs[2]) {
+ if (!rcs[0] && !rcs[1] && !rcs[2] && !rcs[3]) {
return 0;
}
/* no entry needed */
- if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0) {
+ if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0 && reposts == 0) {
return 0;
}
@@ -2335,7 +2396,7 @@ static int ndb_note_meta_builder_counts(struct ndb_txn *txn,
return 0;
}
- ndb_note_meta_counts_set(entry, total_reactions, quotes, direct_replies, thread_replies);
+ ndb_note_meta_counts_set(entry, total_reactions, quotes, direct_replies, thread_replies, reposts);
return 1;
}
@@ -3785,21 +3846,6 @@ struct ndb_note_meta *ndb_get_note_meta(struct ndb_txn *txn, const unsigned char
return v.mv_data;
}
-static void print_meta_data(struct ndb_note_meta *meta)
-{
- struct ndb_note_meta_entry *entry;
- int i;
-
- printf("<metadata ver:%d count:%d dt_size:%d flags:%ld>\n",
- meta->version, meta->count, meta->data_table_size, meta->flags);
-
- for (i = 0; i < meta->count; i++) {
- entry = ndb_note_meta_entry_at(meta, i);
- printf("<meta_entry type:%d flags:%d aux:%d payload:%" PRIx64 "\n",
- entry->type, entry->flags, entry->aux.value, entry->payload.value);
- }
-}
-
/* write reaction stats if its a valid reaction */
static int ndb_process_reaction(
struct ndb_txn *txn,
@@ -3894,7 +3940,7 @@ static int ndb_increment_total_reactions(
case NDB_META_CLONE_FAILED:
return 0;
case NDB_META_CLONE_NEW_ENTRY:
- ndb_note_meta_counts_set(entry, 1, 0, 0, 0);
+ ndb_note_meta_counts_set(entry, 1, 0, 0, 0, 0);
break;
case NDB_META_CLONE_EXISTING_ENTRY:
total_reactions = ndb_note_meta_counts_total_reactions(entry);
@@ -5832,7 +5878,7 @@ static int ndb_increment_quote_metadata(
case NDB_META_CLONE_FAILED:
return 0;
case NDB_META_CLONE_NEW_ENTRY:
- ndb_note_meta_counts_set(entry, 0, 1, 0, 0);
+ ndb_note_meta_counts_set(entry, 0, 1, 0, 0, 0);
break;
case NDB_META_CLONE_EXISTING_ENTRY:
quotes = ndb_note_meta_counts_quotes(entry);
@@ -5879,7 +5925,7 @@ static int ndb_increment_direct_reply_metadata(
case NDB_META_CLONE_FAILED:
return 0;
case NDB_META_CLONE_NEW_ENTRY:
- ndb_note_meta_counts_set(entry, 0, 0, 1, 0);
+ ndb_note_meta_counts_set(entry, 0, 0, 1, 0, 0);
break;
case NDB_META_CLONE_EXISTING_ENTRY:
direct_replies = ndb_note_meta_counts_direct_replies(entry);
@@ -5926,7 +5972,7 @@ static int ndb_increment_thread_reply_metadata(
case NDB_META_CLONE_FAILED:
return 0;
case NDB_META_CLONE_NEW_ENTRY:
- ndb_note_meta_counts_set(entry, 0, 0, 0, 1);
+ ndb_note_meta_counts_set(entry, 0, 0, 0, 1, 0);
break;
case NDB_META_CLONE_EXISTING_ENTRY:
replies = ndb_note_meta_counts_thread_replies(entry);
@@ -5949,6 +5995,63 @@ static int ndb_increment_thread_reply_metadata(
return 1;
}
+/* update reply count metadata for a specific note id */
+static int ndb_increment_repost_metadata(
+ struct ndb_txn *txn,
+ unsigned char *id,
+ unsigned char *scratch,
+ size_t scratch_size)
+{
+ MDB_val key, val;
+ uint16_t *reposts;
+ struct ndb_note_meta *meta;
+ struct ndb_note_meta_entry *entry;
+ int rc;
+
+ meta = ndb_get_note_meta(txn, id);
+ rc = ndb_note_meta_clone_with_entry(&meta, &entry,
+ NDB_NOTE_META_COUNTS,
+ NULL, /* payload to match. only relevant for reactions */
+ scratch,
+ scratch_size);
+
+ switch (rc) {
+ case NDB_META_CLONE_FAILED:
+ return 0;
+ case NDB_META_CLONE_NEW_ENTRY:
+ ndb_note_meta_counts_set(entry, 0, 0, 0, 0, 1);
+ break;
+ case NDB_META_CLONE_EXISTING_ENTRY:
+ reposts = ndb_note_meta_counts_reposts(entry);
+ (*reposts)++;
+ break;
+ }
+
+ key.mv_data = id;
+ key.mv_size = 32;
+
+ val.mv_data = meta;
+ val.mv_size = ndb_note_meta_total_size(meta);
+ assert((val.mv_size % 8) == 0);
+
+ if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) {
+ ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc));
+ return 0;
+ }
+
+ return 1;
+}
+
+static void ndb_process_repost_stats(struct ndb_txn *txn, struct ndb_note *note, unsigned char *scratch, size_t scratch_size)
+{
+ unsigned char *reposted_note_id;
+ reposted_note_id = ndb_note_first_tag_id(note, 'e');
+
+ /* find q tag to see if we are quoting anything */
+ if (reposted_note_id) {
+ ndb_increment_repost_metadata(txn, reposted_note_id, scratch, scratch_size);
+ }
+}
/* process quote and reply count metadata */
static void ndb_process_note_stats(
@@ -6044,6 +6147,8 @@ static uint64_t ndb_write_note(struct ndb_txn *txn,
ndb_process_note_stats(txn, note->note, scratch, scratch_size);
} else if (kind == 7 && !ndb_flag_set(ndb_flags, NDB_FLAG_NO_STATS)) {
ndb_write_reaction_stats(txn, note->note, scratch, scratch_size);
+ } else if (kind == 6 || kind == 16) {
+ ndb_process_repost_stats(txn, note->note, scratch, scratch_size);
}
return note_key;
diff --git a/src/nostrdb.h b/src/nostrdb.h
@@ -665,10 +665,12 @@ struct ndb_note_meta_entry *ndb_note_meta_entries(struct ndb_note_meta *meta);
struct ndb_note_meta_entry *ndb_note_meta_add_entry(struct ndb_note_meta_builder *builder);
size_t ndb_note_meta_total_size(struct ndb_note_meta *meta);
void ndb_note_meta_reaction_set(struct ndb_note_meta_entry *entry, uint32_t count, union ndb_reaction_str str);
-void ndb_note_meta_counts_set(struct ndb_note_meta_entry *entry, uint32_t total_reactions, uint16_t quotes, uint16_t direct_replies, uint32_t thread_replies);
+void ndb_note_meta_counts_set(struct ndb_note_meta_entry *entry, uint32_t total_reactions, uint16_t quotes, uint16_t direct_replies, uint32_t thread_replies, uint16_t reposts);
uint32_t *ndb_note_meta_reaction_count(struct ndb_note_meta_entry *entry);
struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, uint16_t type, uint64_t *payload);
uint16_t *ndb_note_meta_counts_quotes(struct ndb_note_meta_entry *entry);
+uint16_t *ndb_note_meta_counts_reposts(struct ndb_note_meta_entry *entry);
+uint64_t *ndb_note_meta_flags(struct ndb_note_meta *meta);
uint32_t *ndb_note_meta_counts_total_reactions(struct ndb_note_meta_entry *entry);
uint16_t *ndb_note_meta_counts_direct_replies(struct ndb_note_meta_entry *entry);
uint32_t *ndb_note_meta_counts_thread_replies(struct ndb_note_meta_entry *entry);