From c969738736186bd00efeaf6dab2b8a5871d8f9e2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 2 Jun 2024 13:15:05 -0500 Subject: [PATCH] Save trending hashtags as labels --- src/cron.ts | 26 ++++++++++++++++++++++ src/trends/trending-hashtags.ts | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/trends/trending-hashtags.ts diff --git a/src/cron.ts b/src/cron.ts index 707f738..7f8f46a 100644 --- a/src/cron.ts +++ b/src/cron.ts @@ -5,6 +5,7 @@ import { getTrendingNotes } from '@/trends/trending-notes.ts'; import { Time } from '@/utils/time.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; import { handleEvent } from '@/pipeline.ts'; +import { getTrendingHashtags } from '@/trends/trending-hashtags.ts'; const console = new Stickynotes('ditto:trends'); @@ -32,7 +33,32 @@ async function updateTrendingNotesCache() { console.info('Trending notes cache updated.'); } +async function updateTrendingHashtagsCache() { + console.info('Updating trending hashtags cache...'); + const kysely = await DittoDB.getInstance(); + const yesterday = Math.floor((Date.now() - Time.days(1)) / 1000); + const signal = AbortSignal.timeout(1000); + + const hashtags = await getTrendingHashtags(kysely, { since: yesterday, limit: 20, threshold: 3 }); + const signer = new AdminSigner(); + + const label = await signer.signEvent({ + kind: 1985, + content: '', + tags: [ + ['L', 'pub.ditto.trends'], + ['l', 'hashtags', 'pub.ditto.trends'], + ...hashtags.map(({ tag }) => ['t', tag]), + ], + created_at: Math.floor(Date.now() / 1000), + }); + + await handleEvent(label, signal); + console.info('Trending hashtags cache updated.'); +} + /** Start cron jobs for the application. */ export function cron() { Deno.cron('update trending notes cache', { minute: { every: 15 } }, updateTrendingNotesCache); + Deno.cron('update trending hashtags cache', { dayOfMonth: { every: 1 } }, updateTrendingHashtagsCache); } diff --git a/src/trends/trending-hashtags.ts b/src/trends/trending-hashtags.ts new file mode 100644 index 0000000..a70acb9 --- /dev/null +++ b/src/trends/trending-hashtags.ts @@ -0,0 +1,38 @@ +import { Kysely } from 'kysely'; + +import { DittoTables } from '@/db/DittoTables.ts'; + +interface GetTrendingHashtagsOpts { + /** Unix timestamp in _seconds_ for the starting point of this query. */ + since: number; + /** Maximum number of trending hashtags to return. */ + limit: number; + /** Minimum number of unique accounts that have used a hashtag to be considered trending. */ + threshold: number; +} + +/** Get the trending hashtags in the given time frame. */ +export async function getTrendingHashtags( + /** Kysely instance to execute queries on. */ + kysely: Kysely, + /** Options for this query. */ + opts: GetTrendingHashtagsOpts, +): Promise<{ tag: string; accounts: number; uses: number }[]> { + const { since, limit, threshold } = opts; + + return await kysely + .selectFrom('nostr_tags') + .innerJoin('nostr_events', 'nostr_events.id', 'nostr_tags.event_id') + .select(({ fn }) => [ + 'nostr_tags.value as tag', + fn.agg('count', ['nostr_events.pubkey']).distinct().as('accounts'), + fn.countAll().as('uses'), + ]) + .where('nostr_tags.name', '=', 't') + .where('nostr_events.created_at', '>', since) + .groupBy('nostr_tags.value') + .having((c) => c(c.fn.agg('count', ['nostr_events.pubkey']).distinct(), '>=', threshold)) + .orderBy((c) => c.fn.agg('count', ['nostr_events.pubkey']).distinct(), 'desc') + .limit(limit) + .execute(); +}