From e6d1494a106ceb793998d82c5c072ea7544d7f2d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Aug 2023 13:34:15 -0500 Subject: [PATCH] streaming: make user stream mostly work, in a kind of hacky way --- src/controllers/api/oauth.ts | 2 +- src/controllers/api/streaming.ts | 25 ++++++++++++++++++------- src/deps.ts | 1 - src/queries.ts | 22 ++++++++++++++++------ src/utils.ts | 1 + 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/controllers/api/oauth.ts b/src/controllers/api/oauth.ts index c76412e8..e6a9bccd 100644 --- a/src/controllers/api/oauth.ts +++ b/src/controllers/api/oauth.ts @@ -1,4 +1,4 @@ -import { lodash, nip19, uuid62, z } from '@/deps.ts'; +import { lodash, nip19, z } from '@/deps.ts'; import { AppController } from '@/app.ts'; import { nostrNow } from '@/utils.ts'; import { parseBody } from '@/utils/web.ts'; diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index 2ea5844a..38573bd4 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -1,8 +1,10 @@ import { type AppController } from '@/app.ts'; -import { nip19, z } from '@/deps.ts'; +import { z } from '@/deps.ts'; import { type DittoFilter } from '@/filter.ts'; +import { getFeedPubkeys } from '@/queries.ts'; import { Sub } from '@/subs.ts'; import { toStatus } from '@/transformers/nostr-to-mastoapi.ts'; +import { bech32ToPubkey } from '@/utils.ts'; /** * Streaming timelines/categories. @@ -38,8 +40,8 @@ const streamingController: AppController = (c) => { return c.json({ error: 'Missing access token' }, 401); } - const match = token.match(new RegExp(`^${nip19.BECH32_REGEX.source}$`)); - if (!match) { + const pubkey = token ? bech32ToPubkey(token) : undefined; + if (!pubkey) { return c.json({ error: 'Invalid access token' }, 401); } @@ -57,7 +59,7 @@ const streamingController: AppController = (c) => { socket.onopen = async () => { if (!stream) return; - const filter = topicToFilter(stream, c.req.query()); + const filter = await topicToFilter(stream, pubkey, c.req.query()); if (filter) { for await (const event of Sub.sub(socket, '1', [filter])) { @@ -76,18 +78,27 @@ const streamingController: AppController = (c) => { return response; }; -function topicToFilter(topic: Stream, query?: Record): DittoFilter<1> | undefined { +async function topicToFilter( + topic: Stream, + pubkey: string, + query: Record, +): Promise | undefined> { switch (topic) { case 'public': return { kinds: [1] }; case 'public:local': return { kinds: [1], local: true }; case 'hashtag': - if (query?.tag) return { kinds: [1], '#t': [query.tag] }; + if (query.tag) return { kinds: [1], '#t': [query.tag] }; break; case 'hashtag:local': - if (query?.tag) return { kinds: [1], local: true, '#t': [query.tag] }; + if (query.tag) return { kinds: [1], local: true, '#t': [query.tag] }; break; + case 'user': + // HACK: this puts the user's entire contacts list into RAM, + // and then calls `matchFilters` over it. Refreshing the page + // is required after following a new user. + return { kinds: [1], authors: await getFeedPubkeys(pubkey) }; } } diff --git a/src/deps.ts b/src/deps.ts index dd887b67..1ac6d9f2 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -37,7 +37,6 @@ import 'npm:linkify-plugin-hashtag@^4.1.1'; export { default as mime } from 'npm:mime@^3.0.0'; export { unfurl } from 'npm:unfurl.js@^6.3.2'; export { default as TTLCache } from 'npm:@isaacs/ttlcache@^1.4.1'; -export { default as uuid62 } from 'npm:uuid62@^1.0.2'; // @deno-types="npm:@types/sanitize-html@2.9.0" export { default as sanitizeHtml } from 'npm:sanitize-html@^2.11.0'; export { default as ISO6391 } from 'npm:iso-639-1@2.1.15'; diff --git a/src/queries.ts b/src/queries.ts index aefc1f2b..f990a8ec 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -37,16 +37,25 @@ const getFollows = async (pubkey: string, timeout = 1000): Promise | un return event; }; -/** Get events from people the user follows. */ -async function getFeed(pubkey: string, params: PaginationParams): Promise[]> { - const event3 = await getFollows(pubkey); - if (!event3) return []; +/** Get pubkeys the user follows. */ +async function getFollowedPubkeys(pubkey: string): Promise { + const event = await getFollows(pubkey); + if (!event) return []; - const authors = event3.tags + return event.tags .filter((tag) => tag[0] === 'p') .map((tag) => tag[1]); +} - authors.push(event3.pubkey); // see own events in feed +/** Get pubkeys the user follows, including the user's own pubkey. */ +async function getFeedPubkeys(pubkey: string): Promise { + const authors = await getFollowedPubkeys(pubkey); + return [...authors, pubkey]; +} + +/** Get events from people the user follows. */ +async function getFeed(pubkey: string, params: PaginationParams): Promise[]> { + const authors = await getFeedPubkeys(pubkey); const filter: Filter<1> = { authors, @@ -103,6 +112,7 @@ export { getDescendants, getEvent, getFeed, + getFeedPubkeys, getFollows, getPublicFeed, isLocallyFollowed, diff --git a/src/utils.ts b/src/utils.ts index e2029822..7f47f313 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -102,6 +102,7 @@ function isFollowing(source: Event<3>, targetPubkey: string): boolean { } export { + bech32ToPubkey, eventAge, eventDateComparator, findTag,