From b33a6cdfe0ac94b0f4df22dece8968478811bc1e Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Mon, 30 Sep 2024 13:53:30 -0300 Subject: [PATCH 01/10] feat: add TREND_LANGUAGES environment variable --- src/config.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/config.ts b/src/config.ts index 21fbbe01..0051d3d4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,5 @@ import os from 'node:os'; +import ISO6391, { LanguageCode } from 'iso-639-1'; import * as dotenv from '@std/dotenv'; import { getPublicKey, nip19 } from 'nostr-tools'; import { z } from 'zod'; @@ -247,6 +248,10 @@ class Conf { static get zapSplitsEnabled(): boolean { return optionalBooleanSchema.parse(Deno.env.get('ZAP_SPLITS_ENABLED')) ?? false; } + /** Filter trends by languages. */ + static get trendLanguages(): LanguageCode[] | undefined { + return Deno.env.get('TREND_LANGUAGES')?.split(',')?.filter(ISO6391.validate) as LanguageCode[]; + } /** Cache settings. */ static caches = { /** NIP-05 cache settings. */ From 61bc57c77851770636be2531c33fa0903734f347 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Mon, 30 Sep 2024 14:02:12 -0300 Subject: [PATCH 02/10] feat: support trendings by language --- src/trends.ts | 97 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/src/trends.ts b/src/trends.ts index 23f7ea4d..0583fd60 100644 --- a/src/trends.ts +++ b/src/trends.ts @@ -1,3 +1,4 @@ +import ISO6391, { LanguageCode } from 'iso-639-1'; import { NostrFilter } from '@nostrify/nostrify'; import { Stickynotes } from '@soapbox/stickynotes'; import { Kysely, sql } from 'kysely'; @@ -19,34 +20,49 @@ export async function getTrendingTagValues( tagNames: string[], /** Filter of eligible events. */ filter: NostrFilter, + /** Only return trending events of 'language' */ + language?: LanguageCode, ): Promise<{ value: string; authors: number; uses: number }[]> { - let query = kysely - .selectFrom([ - 'nostr_events', - sql<{ key: string; value: string }>`jsonb_each_text(nostr_events.tags_index)`.as('kv'), - sql<{ key: string; value: string }>`jsonb_array_elements_text(kv.value::jsonb)`.as('element'), - ]) - .select(({ fn }) => [ - fn('lower', ['element.value']).as('value'), - fn.agg('count', ['nostr_events.pubkey']).distinct().as('authors'), - fn.countAll().as('uses'), - ]) - .where('kv.key', '=', (eb) => eb.fn.any(eb.val(tagNames))) - .groupBy((eb) => eb.fn('lower', ['element.value'])) - .orderBy((eb) => eb.fn.agg('count', ['nostr_events.pubkey']).distinct(), 'desc'); + let query = kysely.with('trends', (db) => { + let query = db + .selectFrom([ + 'nostr_events', + sql<{ key: string; value: string }>`jsonb_each_text(nostr_events.tags_index)`.as('kv'), + sql<{ key: string; value: string }>`jsonb_array_elements_text(kv.value::jsonb)`.as('element'), + ]) + .select(({ fn }) => [ + fn('lower', ['element.value']).as('value'), + fn.agg('count', ['nostr_events.pubkey']).distinct().as('authors'), + fn.countAll().as('uses'), + ]) + .where('kv.key', '=', (eb) => eb.fn.any(eb.val(tagNames))) + .groupBy((eb) => eb.fn('lower', ['element.value'])) + .orderBy((eb) => eb.fn.agg('count', ['nostr_events.pubkey']).distinct(), 'desc'); - if (filter.kinds) { - query = query.where('nostr_events.kind', '=', ({ fn, val }) => fn.any(val(filter.kinds))); - } - if (filter.authors) { - query = query.where('nostr_events.pubkey', '=', ({ fn, val }) => fn.any(val(filter.authors))); - } - if (typeof filter.since === 'number') { - query = query.where('nostr_events.created_at', '>=', filter.since); - } - if (typeof filter.until === 'number') { - query = query.where('nostr_events.created_at', '<=', filter.until); + if (filter.kinds) { + query = query.where('nostr_events.kind', '=', ({ fn, val }) => fn.any(val(filter.kinds))); + } + if (filter.authors) { + query = query.where('nostr_events.pubkey', '=', ({ fn, val }) => fn.any(val(filter.authors))); + } + if (typeof filter.since === 'number') { + query = query.where('nostr_events.created_at', '>=', filter.since); + } + if (typeof filter.until === 'number') { + query = query.where('nostr_events.created_at', '<=', filter.until); + } + return query; + }) + .selectFrom(['trends']) + .innerJoin('nostr_events', 'trends.value', 'nostr_events.id') + .select(['value', 'authors', 'uses']); + + if (language) { + query = query.where('nostr_events.language', '=', language); } + + query = query.orderBy('authors desc'); + if (typeof filter.limit === 'number') { query = query.limit(filter.limit); } @@ -68,6 +84,7 @@ export async function updateTrendingTags( limit: number, extra = '', aliases?: string[], + language?: LanguageCode, ) { console.info(`Updating trending ${l}...`); const kysely = await Storages.kysely(); @@ -84,7 +101,7 @@ export async function updateTrendingTags( since: yesterday, until: now, limit, - }); + }, language); if (!trends.length) { console.info(`No trending ${l} found. Skipping.`); @@ -93,14 +110,19 @@ export async function updateTrendingTags( const signer = new AdminSigner(); + const tags = [ + ['L', 'pub.ditto.trends'], + ['l', l, 'pub.ditto.trends'], + ...trends.map(({ value, authors, uses }) => [tagName, value, extra, authors.toString(), uses.toString()]), + ]; + if (language) { + tags.push(['lang', language]); + } + const label = await signer.signEvent({ kind: 1985, content: '', - tags: [ - ['L', 'pub.ditto.trends'], - ['l', l, 'pub.ditto.trends'], - ...trends.map(({ value, authors, uses }) => [tagName, value, extra, authors.toString(), uses.toString()]), - ], + tags, created_at: Math.floor(Date.now() / 1000), }); @@ -122,8 +144,17 @@ export function updateTrendingZappedEvents(): Promise { } /** Update trending events. */ -export function updateTrendingEvents(): Promise { - return updateTrendingTags('#e', 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q']); +export async function updateTrendingEvents(): Promise { + const languages = Conf.trendLanguages; + if (!languages) return updateTrendingTags('#e', 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q']); + + const promise: Promise[] = []; + + for (const language of languages) { + promise.push(updateTrendingTags('#e', 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q'], language)); + } + + await Promise.allSettled(promise); } /** Update trending hashtags. */ From 5e23f4d6361dba28885e28ee346d9ae3d38c75f4 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Mon, 30 Sep 2024 14:03:22 -0300 Subject: [PATCH 03/10] test: trends without language and with language --- src/trends.test.ts | 103 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/trends.test.ts diff --git a/src/trends.test.ts b/src/trends.test.ts new file mode 100644 index 00000000..1788c496 --- /dev/null +++ b/src/trends.test.ts @@ -0,0 +1,103 @@ +import { assertEquals } from '@std/assert'; +import { generateSecretKey, NostrEvent } from 'nostr-tools'; + +import { getTrendingTagValues } from '@/trends.ts'; +import { createTestDB, genEvent } from '@/test.ts'; + +Deno.test("getTrendingTagValues(): 'e' tag and WITHOUT language parameter", async () => { + await using db = await createTestDB(); + + const events: NostrEvent[] = []; + + let sk = generateSecretKey(); + const post1 = genEvent({ kind: 1, content: 'SHOW ME THE MONEY' }, sk); + const numberOfAuthorsWhoLikedPost1 = 100; + const post1multiplier = 2; + const post1uses = numberOfAuthorsWhoLikedPost1 * post1multiplier; + for (let i = 0; i < numberOfAuthorsWhoLikedPost1; i++) { + const sk = generateSecretKey(); + events.push( + genEvent({ kind: 7, content: '+', tags: Array(post1multiplier).fill([...['e', post1.id]]) }, sk), + ); + } + events.push(post1); + + sk = generateSecretKey(); + const post2 = genEvent({ kind: 1, content: 'Ithaca' }, sk); + const numberOfAuthorsWhoLikedPost2 = 100; + const post2multiplier = 1; + const post2uses = numberOfAuthorsWhoLikedPost2 * post2multiplier; + for (let i = 0; i < numberOfAuthorsWhoLikedPost2; i++) { + const sk = generateSecretKey(); + events.push( + genEvent({ kind: 7, content: '+', tags: Array(post2multiplier).fill([...['e', post2.id]]) }, sk), + ); + } + events.push(post2); + + for (const event of events) { + await db.store.event(event); + } + + const trends = await getTrendingTagValues(db.kysely, ['e'], { kinds: [1, 7] }); + + const expected = [{ value: post1.id, authors: numberOfAuthorsWhoLikedPost1, uses: post1uses }, { + value: post2.id, + authors: numberOfAuthorsWhoLikedPost2, + uses: post2uses, + }]; + + assertEquals(trends, expected); +}); + +Deno.test("getTrendingTagValues(): 'e' tag and WITH language parameter", async () => { + await using db = await createTestDB(); + + const events: NostrEvent[] = []; + + let sk = generateSecretKey(); + const post1 = genEvent({ kind: 1, content: 'Irei cortar o cabelo.' }, sk); + const numberOfAuthorsWhoLikedPost1 = 100; + const post1multiplier = 2; + const post1uses = numberOfAuthorsWhoLikedPost1 * post1multiplier; + for (let i = 0; i < numberOfAuthorsWhoLikedPost1; i++) { + const sk = generateSecretKey(); + events.push( + genEvent({ kind: 7, content: '+', tags: Array(post1multiplier).fill([...['e', post1.id]]) }, sk), + ); + } + events.push(post1); + + sk = generateSecretKey(); + const post2 = genEvent({ kind: 1, content: 'Ithaca' }, sk); + const numberOfAuthorsWhoLikedPost2 = 100; + const post2multiplier = 1; + for (let i = 0; i < numberOfAuthorsWhoLikedPost2; i++) { + const sk = generateSecretKey(); + events.push( + genEvent({ kind: 7, content: '+', tags: Array(post2multiplier).fill([...['e', post2.id]]) }, sk), + ); + } + events.push(post2); + + for (const event of events) { + await db.store.event(event); + } + + await db.kysely.updateTable('nostr_events') + .set('language', 'pt') + .where('id', '=', post1.id) + .execute(); + + await db.kysely.updateTable('nostr_events') + .set('language', 'en') + .where('id', '=', post2.id) + .execute(); + + const trends = await getTrendingTagValues(db.kysely, ['e'], { kinds: [1, 7] }, 'pt'); + + // portuguese post + const expected = [{ value: post1.id, authors: numberOfAuthorsWhoLikedPost1, uses: post1uses }]; + + assertEquals(trends, expected); +}); From c0d9a90bfa6e1cfe829ac9b429ac14c169220203 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Mon, 30 Sep 2024 14:09:19 -0300 Subject: [PATCH 04/10] refactor: remove un-used variable --- src/trends.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trends.ts b/src/trends.ts index 0583fd60..4efaf831 100644 --- a/src/trends.ts +++ b/src/trends.ts @@ -1,4 +1,4 @@ -import ISO6391, { LanguageCode } from 'iso-639-1'; +import { LanguageCode } from 'iso-639-1'; import { NostrFilter } from '@nostrify/nostrify'; import { Stickynotes } from '@soapbox/stickynotes'; import { Kysely, sql } from 'kysely'; From b549cdef536aa9e6ee5d11f5272def0c77d25f23 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 1 Oct 2024 13:52:30 -0300 Subject: [PATCH 05/10] refactor: rename TREND_LANGUAGES to DITTO_LANGUAGES --- src/config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.ts b/src/config.ts index 0051d3d4..ae841997 100644 --- a/src/config.ts +++ b/src/config.ts @@ -248,9 +248,9 @@ class Conf { static get zapSplitsEnabled(): boolean { return optionalBooleanSchema.parse(Deno.env.get('ZAP_SPLITS_ENABLED')) ?? false; } - /** Filter trends by languages. */ - static get trendLanguages(): LanguageCode[] | undefined { - return Deno.env.get('TREND_LANGUAGES')?.split(',')?.filter(ISO6391.validate) as LanguageCode[]; + /** Languages this server wishes to highlight. Used when querying trends.*/ + static get preferredLanguages(): LanguageCode[] | undefined { + return Deno.env.get('DITTO_LANGUAGES')?.split(',')?.filter(ISO6391.validate) as LanguageCode[]; } /** Cache settings. */ static caches = { From d8b2c057b0f3f1bd1c30c131243fe1db0f9085b3 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 1 Oct 2024 13:58:08 -0300 Subject: [PATCH 06/10] feat: make trends fast again remove previous JOIN, now if a language is set, it will do '''query.where('trends.value', 'in', languagesIds);''', which is faster than a JOIN --- src/trends.ts | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/trends.ts b/src/trends.ts index 4efaf831..a60e28f3 100644 --- a/src/trends.ts +++ b/src/trends.ts @@ -1,5 +1,5 @@ import { LanguageCode } from 'iso-639-1'; -import { NostrFilter } from '@nostrify/nostrify'; +import { NostrEvent, NostrFilter } from '@nostrify/nostrify'; import { Stickynotes } from '@soapbox/stickynotes'; import { Kysely, sql } from 'kysely'; @@ -20,8 +20,8 @@ export async function getTrendingTagValues( tagNames: string[], /** Filter of eligible events. */ filter: NostrFilter, - /** Only return trending events of 'language' */ - language?: LanguageCode, + /** Results must be inside 'languagesIds' */ + languagesIds?: string[], ): Promise<{ value: string; authors: number; uses: number }[]> { let query = kysely.with('trends', (db) => { let query = db @@ -54,14 +54,13 @@ export async function getTrendingTagValues( return query; }) .selectFrom(['trends']) - .innerJoin('nostr_events', 'trends.value', 'nostr_events.id') .select(['value', 'authors', 'uses']); - if (language) { - query = query.where('nostr_events.language', '=', language); + if (languagesIds) { + query = query.where('trends.value', 'in', languagesIds); } - query = query.orderBy('authors desc'); + query = query.orderBy('authors desc').orderBy('uses desc'); if (typeof filter.limit === 'number') { query = query.limit(filter.limit); @@ -95,13 +94,24 @@ export async function updateTrendingTags( const tagNames = aliases ? [tagName, ...aliases] : [tagName]; + let languagesIds: NostrEvent['id'][] = []; + if (language) { + const result = (await kysely.selectFrom('nostr_events') + .select('id') + .where('language', '=', language) + .where('nostr_events.created_at', '>=', yesterday) + .where('nostr_events.created_at', '<=', now) + .execute()).map((event) => event.id); + languagesIds = result; + } + try { const trends = await getTrendingTagValues(kysely, tagNames, { kinds, since: yesterday, until: now, limit, - }, language); + }, languagesIds); if (!trends.length) { console.info(`No trending ${l} found. Skipping.`); @@ -110,19 +120,14 @@ export async function updateTrendingTags( const signer = new AdminSigner(); - const tags = [ - ['L', 'pub.ditto.trends'], - ['l', l, 'pub.ditto.trends'], - ...trends.map(({ value, authors, uses }) => [tagName, value, extra, authors.toString(), uses.toString()]), - ]; - if (language) { - tags.push(['lang', language]); - } - const label = await signer.signEvent({ kind: 1985, content: '', - tags, + tags: [ + ['L', 'pub.ditto.trends'], + ['l', languagesIds.length ? `${l}.${language}` : l, 'pub.ditto.trends'], + ...trends.map(({ value, authors, uses }) => [tagName, value, extra, authors.toString(), uses.toString()]), + ], created_at: Math.floor(Date.now() / 1000), }); @@ -145,7 +150,7 @@ export function updateTrendingZappedEvents(): Promise { /** Update trending events. */ export async function updateTrendingEvents(): Promise { - const languages = Conf.trendLanguages; + const languages = Conf.preferredLanguages; if (!languages) return updateTrendingTags('#e', 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q']); const promise: Promise[] = []; From 7c29c81226ce0a9f932f640bec1d7d1e737b9b27 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 1 Oct 2024 13:58:51 -0300 Subject: [PATCH 07/10] test: pass languagesIds in getTrendingTagValues() function --- src/trends.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/trends.test.ts b/src/trends.test.ts index 1788c496..66cae23b 100644 --- a/src/trends.test.ts +++ b/src/trends.test.ts @@ -94,7 +94,9 @@ Deno.test("getTrendingTagValues(): 'e' tag and WITH language parameter", async ( .where('id', '=', post2.id) .execute(); - const trends = await getTrendingTagValues(db.kysely, ['e'], { kinds: [1, 7] }, 'pt'); + const languagesIds = (await db.store.query([{ search: 'language:pt' }])).map((event) => event.id); + + const trends = await getTrendingTagValues(db.kysely, ['e'], { kinds: [1, 7] }, languagesIds); // portuguese post const expected = [{ value: post1.id, authors: numberOfAuthorsWhoLikedPost1, uses: post1uses }]; From e7f5e563f58d892f256408604aca13b5a692bc6f Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 1 Oct 2024 13:59:21 -0300 Subject: [PATCH 08/10] feat: load dotenv in script/trends.ts --- scripts/trends.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/trends.ts b/scripts/trends.ts index 6600f7e2..c6ff63e0 100644 --- a/scripts/trends.ts +++ b/scripts/trends.ts @@ -1,3 +1,4 @@ +import * as dotenv from '@std/dotenv'; import { z } from 'zod'; import { @@ -8,6 +9,12 @@ import { updateTrendingZappedEvents, } from '@/trends.ts'; +await dotenv.load({ + export: true, + defaultsPath: null, + examplePath: null, +}); + const trendSchema = z.enum(['pubkeys', 'zapped_events', 'events', 'hashtags', 'links']); const trends = trendSchema.array().parse(Deno.args); From a5def9fa6cedef0c67c3b1ce695f12ea462b7837 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 3 Oct 2024 18:16:23 -0300 Subject: [PATCH 09/10] refactor: just import config.ts directly instead of loading dotenv in trends.ts script --- scripts/trends.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/trends.ts b/scripts/trends.ts index c6ff63e0..627fb332 100644 --- a/scripts/trends.ts +++ b/scripts/trends.ts @@ -1,5 +1,5 @@ -import * as dotenv from '@std/dotenv'; import { z } from 'zod'; +import '@/config.ts'; import { updateTrendingEvents, @@ -9,12 +9,6 @@ import { updateTrendingZappedEvents, } from '@/trends.ts'; -await dotenv.load({ - export: true, - defaultsPath: null, - examplePath: null, -}); - const trendSchema = z.enum(['pubkeys', 'zapped_events', 'events', 'hashtags', 'links']); const trends = trendSchema.array().parse(Deno.args); From 67b0684a810399c62b2eb6964e7cf9c3860f099d Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 3 Oct 2024 19:40:29 -0300 Subject: [PATCH 10/10] refactor(trends.ts): move logic one level up, rename 'languagesIds' to 'values', remove WITH SQL statement --- src/trends.ts | 116 ++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 61 deletions(-) diff --git a/src/trends.ts b/src/trends.ts index a60e28f3..cf4f7c96 100644 --- a/src/trends.ts +++ b/src/trends.ts @@ -1,5 +1,4 @@ -import { LanguageCode } from 'iso-639-1'; -import { NostrEvent, NostrFilter } from '@nostrify/nostrify'; +import { NostrFilter } from '@nostrify/nostrify'; import { Stickynotes } from '@soapbox/stickynotes'; import { Kysely, sql } from 'kysely'; @@ -20,48 +19,39 @@ export async function getTrendingTagValues( tagNames: string[], /** Filter of eligible events. */ filter: NostrFilter, - /** Results must be inside 'languagesIds' */ - languagesIds?: string[], + /** If present, only tag values in this list are permitted to trend. */ + values?: string[], ): Promise<{ value: string; authors: number; uses: number }[]> { - let query = kysely.with('trends', (db) => { - let query = db - .selectFrom([ - 'nostr_events', - sql<{ key: string; value: string }>`jsonb_each_text(nostr_events.tags_index)`.as('kv'), - sql<{ key: string; value: string }>`jsonb_array_elements_text(kv.value::jsonb)`.as('element'), - ]) - .select(({ fn }) => [ - fn('lower', ['element.value']).as('value'), - fn.agg('count', ['nostr_events.pubkey']).distinct().as('authors'), - fn.countAll().as('uses'), - ]) - .where('kv.key', '=', (eb) => eb.fn.any(eb.val(tagNames))) - .groupBy((eb) => eb.fn('lower', ['element.value'])) - .orderBy((eb) => eb.fn.agg('count', ['nostr_events.pubkey']).distinct(), 'desc'); + let query = kysely + .selectFrom([ + 'nostr_events', + sql<{ key: string; value: string }>`jsonb_each_text(nostr_events.tags_index)`.as('kv'), + sql<{ key: string; value: string }>`jsonb_array_elements_text(kv.value::jsonb)`.as('element'), + ]) + .select(({ fn }) => [ + fn('lower', ['element.value']).as('value'), + fn.agg('count', ['nostr_events.pubkey']).distinct().as('authors'), + fn.countAll().as('uses'), + ]) + .where('kv.key', '=', (eb) => eb.fn.any(eb.val(tagNames))) + .groupBy((eb) => eb.fn('lower', ['element.value'])) + .orderBy('authors desc').orderBy('uses desc'); - if (filter.kinds) { - query = query.where('nostr_events.kind', '=', ({ fn, val }) => fn.any(val(filter.kinds))); - } - if (filter.authors) { - query = query.where('nostr_events.pubkey', '=', ({ fn, val }) => fn.any(val(filter.authors))); - } - if (typeof filter.since === 'number') { - query = query.where('nostr_events.created_at', '>=', filter.since); - } - if (typeof filter.until === 'number') { - query = query.where('nostr_events.created_at', '<=', filter.until); - } - return query; - }) - .selectFrom(['trends']) - .select(['value', 'authors', 'uses']); - - if (languagesIds) { - query = query.where('trends.value', 'in', languagesIds); + if (filter.kinds) { + query = query.where('nostr_events.kind', '=', ({ fn, val }) => fn.any(val(filter.kinds))); + } + if (filter.authors) { + query = query.where('nostr_events.pubkey', '=', ({ fn, val }) => fn.any(val(filter.authors))); + } + if (typeof filter.since === 'number') { + query = query.where('nostr_events.created_at', '>=', filter.since); + } + if (typeof filter.until === 'number') { + query = query.where('nostr_events.created_at', '<=', filter.until); + } + if (values) { + query = query.where('element.value', 'in', values); } - - query = query.orderBy('authors desc').orderBy('uses desc'); - if (typeof filter.limit === 'number') { query = query.limit(filter.limit); } @@ -83,7 +73,7 @@ export async function updateTrendingTags( limit: number, extra = '', aliases?: string[], - language?: LanguageCode, + values?: string[], ) { console.info(`Updating trending ${l}...`); const kysely = await Storages.kysely(); @@ -94,25 +84,15 @@ export async function updateTrendingTags( const tagNames = aliases ? [tagName, ...aliases] : [tagName]; - let languagesIds: NostrEvent['id'][] = []; - if (language) { - const result = (await kysely.selectFrom('nostr_events') - .select('id') - .where('language', '=', language) - .where('nostr_events.created_at', '>=', yesterday) - .where('nostr_events.created_at', '<=', now) - .execute()).map((event) => event.id); - languagesIds = result; - } - try { const trends = await getTrendingTagValues(kysely, tagNames, { kinds, since: yesterday, until: now, limit, - }, languagesIds); + }, values); + console.log(trends); if (!trends.length) { console.info(`No trending ${l} found. Skipping.`); return; @@ -125,7 +105,7 @@ export async function updateTrendingTags( content: '', tags: [ ['L', 'pub.ditto.trends'], - ['l', languagesIds.length ? `${l}.${language}` : l, 'pub.ditto.trends'], + ['l', l, 'pub.ditto.trends'], ...trends.map(({ value, authors, uses }) => [tagName, value, extra, authors.toString(), uses.toString()]), ], created_at: Math.floor(Date.now() / 1000), @@ -150,16 +130,30 @@ export function updateTrendingZappedEvents(): Promise { /** Update trending events. */ export async function updateTrendingEvents(): Promise { - const languages = Conf.preferredLanguages; - if (!languages) return updateTrendingTags('#e', 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q']); + const results: Promise[] = [ + updateTrendingTags('#e', 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q']), + ]; - const promise: Promise[] = []; + const kysely = await Storages.kysely(); - for (const language of languages) { - promise.push(updateTrendingTags('#e', 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q'], language)); + for (const language of Conf.preferredLanguages ?? []) { + const yesterday = Math.floor((Date.now() - Time.days(1)) / 1000); + const now = Math.floor(Date.now() / 1000); + + const rows = await kysely + .selectFrom('nostr_events') + .select('nostr_events.id') + .where('nostr_events.language', '=', language) + .where('nostr_events.created_at', '>=', yesterday) + .where('nostr_events.created_at', '<=', now) + .execute(); + + const ids = rows.map((row) => row.id); + + results.push(updateTrendingTags(`#e.${language}`, 'e', [1, 6, 7, 9735], 40, Conf.relay, ['q'], ids)); } - await Promise.allSettled(promise); + await Promise.allSettled(results); } /** Update trending hashtags. */