Add public timeline, fix limit param

This commit is contained in:
Alex Gleason 2023-07-07 15:07:20 -05:00
parent cacf51ea36
commit d4eef9c2af
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
4 changed files with 37 additions and 23 deletions

View File

@ -11,7 +11,7 @@ import {
} from './controllers/api/accounts.ts'; } from './controllers/api/accounts.ts';
import { appCredentialsController, createAppController } from './controllers/api/apps.ts'; import { appCredentialsController, createAppController } from './controllers/api/apps.ts';
import { emptyArrayController, emptyObjectController } from './controllers/api/fallback.ts'; import { emptyArrayController, emptyObjectController } from './controllers/api/fallback.ts';
import { homeController } from './controllers/api/timelines.ts'; import { homeController, publicController } from './controllers/api/timelines.ts';
import instanceController from './controllers/api/instance.ts'; import instanceController from './controllers/api/instance.ts';
import { createTokenController, oauthAuthorizeController, oauthController } from './controllers/api/oauth.ts'; import { createTokenController, oauthAuthorizeController, oauthController } from './controllers/api/oauth.ts';
import { frontendConfigController } from './controllers/api/pleroma.ts'; import { frontendConfigController } from './controllers/api/pleroma.ts';
@ -75,6 +75,7 @@ app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/favourite', favouriteController);
app.post('/api/v1/statuses', requireAuth, createStatusController); app.post('/api/v1/statuses', requireAuth, createStatusController);
app.get('/api/v1/timelines/home', requireAuth, homeController); app.get('/api/v1/timelines/home', requireAuth, homeController);
app.get('/api/v1/timelines/public', publicController);
app.get('/api/v1/preferences', preferencesController); app.get('/api/v1/preferences', preferencesController);
app.get('/api/v1/search', searchController); app.get('/api/v1/search', searchController);
@ -92,7 +93,6 @@ app.get('/api/v1/blocks', emptyArrayController);
app.get('/api/v1/mutes', emptyArrayController); app.get('/api/v1/mutes', emptyArrayController);
app.get('/api/v1/domain_blocks', emptyArrayController); app.get('/api/v1/domain_blocks', emptyArrayController);
app.get('/api/v1/markers', emptyObjectController); app.get('/api/v1/markers', emptyObjectController);
app.get('/api/v1/timelines/public', emptyArrayController);
app.get('/api/v1/conversations', emptyArrayController); app.get('/api/v1/conversations', emptyArrayController);
app.get('/api/v1/favourites', emptyArrayController); app.get('/api/v1/favourites', emptyArrayController);
app.get('/api/v1/lists', emptyArrayController); app.get('/api/v1/lists', emptyArrayController);

View File

@ -3,7 +3,7 @@ import { type Event, type SignedEvent } from '@/event.ts';
import { Conf } from './config.ts'; import { Conf } from './config.ts';
import { eventDateComparator, nostrNow } from './utils.ts'; import { eventDateComparator, type PaginationParams } from './utils.ts';
const db = await Deno.openKv(); const db = await Deno.openKv();
@ -99,9 +99,14 @@ const getEvent = async <K extends number = number>(id: string, kind?: K): Promis
}; };
/** Get a Nostr `set_medatadata` event for a user's pubkey. */ /** Get a Nostr `set_medatadata` event for a user's pubkey. */
const getAuthor = async (pubkey: string): Promise<SignedEvent<0> | undefined> => { const getAuthor = async (pubkey: string, timeout = 1000): Promise<SignedEvent<0> | undefined> => {
const author = new Author(getPool(), Conf.poolRelays, pubkey); const author = new Author(getPool(), Conf.poolRelays, pubkey);
const event: SignedEvent<0> | null = await new Promise((resolve) => author.metaData(resolve, 0));
const event: SignedEvent<0> | null = await new Promise((resolve) => {
setTimeout(resolve, timeout, null);
return author.metaData(resolve, 0);
});
return event?.pubkey === pubkey ? event : undefined; return event?.pubkey === pubkey ? event : undefined;
}; };
@ -119,16 +124,8 @@ const getFollows = async (pubkey: string): Promise<SignedEvent<3> | undefined> =
} }
}; };
interface PaginationParams {
since?: number;
until?: number;
limit?: number;
}
/** Get events from people the user follows. */ /** Get events from people the user follows. */
async function getFeed(event3: Event<3>, params: PaginationParams = {}): Promise<SignedEvent<1>[]> { async function getFeed(event3: Event<3>, params: PaginationParams): Promise<SignedEvent<1>[]> {
const limit = Math.max(params.limit ?? 20, 40);
const authors = event3.tags const authors = event3.tags
.filter((tag) => tag[0] === 'p') .filter((tag) => tag[0] === 'p')
.map((tag) => tag[1]); .map((tag) => tag[1]);
@ -138,15 +135,19 @@ async function getFeed(event3: Event<3>, params: PaginationParams = {}): Promise
const filter: Filter = { const filter: Filter = {
authors, authors,
kinds: [1], kinds: [1],
since: params.since, ...params,
until: params.until ?? nostrNow(),
limit,
}; };
const results = await getFilter(filter, { timeout: 5000 }) as SignedEvent<1>[]; const results = await getFilter(filter, { timeout: 5000 }) as SignedEvent<1>[];
return results.sort(eventDateComparator); return results.sort(eventDateComparator);
} }
/** Get a feed of all known text notes. */
async function getPublicFeed(params: PaginationParams): Promise<SignedEvent<1>[]> {
const results = await getFilter({ kinds: [1], ...params }, { timeout: 5000 });
return results.sort(eventDateComparator);
}
async function getAncestors(event: Event<1>, result = [] as Event<1>[]): Promise<Event<1>[]> { async function getAncestors(event: Event<1>, result = [] as Event<1>[]): Promise<Event<1>[]> {
if (result.length < 100) { if (result.length < 100) {
const replyTag = findReplyTag(event); const replyTag = findReplyTag(event);
@ -179,4 +180,4 @@ function publish(event: SignedEvent, relays = Conf.publishRelays): void {
} }
} }
export { getAncestors, getAuthor, getDescendants, getEvent, getFeed, getFilter, getFollows, publish }; export { getAncestors, getAuthor, getDescendants, getEvent, getFeed, getFilter, getFollows, getPublicFeed, publish };

View File

@ -1,11 +1,11 @@
import { getFeed, getFollows } from '@/client.ts'; import { getFeed, getFollows, getPublicFeed } from '@/client.ts';
import { toStatus } from '@/transmute.ts'; import { toStatus } from '@/transmute.ts';
import { buildLinkHeader, paginationSchema } from '@/utils.ts'; import { buildLinkHeader, paginationSchema } from '@/utils.ts';
import type { AppController } from '@/app.ts'; import type { AppController } from '@/app.ts';
const homeController: AppController = async (c) => { const homeController: AppController = async (c) => {
const { since, until } = paginationSchema.parse(c.req.query()); const params = paginationSchema.parse(c.req.query());
const pubkey = c.get('pubkey')!; const pubkey = c.get('pubkey')!;
const follows = await getFollows(pubkey); const follows = await getFollows(pubkey);
@ -13,7 +13,7 @@ const homeController: AppController = async (c) => {
return c.json([]); return c.json([]);
} }
const events = await getFeed(follows, { since, until }); const events = await getFeed(follows, params);
if (!events.length) { if (!events.length) {
return c.json([]); return c.json([]);
} }
@ -24,4 +24,13 @@ const homeController: AppController = async (c) => {
return c.json(statuses, 200, link ? { link } : undefined); return c.json(statuses, 200, link ? { link } : undefined);
}; };
export { homeController }; const publicController: AppController = async (c) => {
const params = paginationSchema.parse(c.req.query());
const events = await getPublicFeed(params);
const statuses = (await Promise.all(events.map(toStatus))).filter(Boolean);
const link = buildLinkHeader(c.req.url, events);
return c.json(statuses, 200, link ? { link } : undefined);
};
export { homeController, publicController };

View File

@ -78,9 +78,12 @@ async function parseBody(req: Request): Promise<unknown> {
const paginationSchema = z.object({ const paginationSchema = z.object({
since: z.coerce.number().optional().catch(undefined), since: z.coerce.number().optional().catch(undefined),
until: z.coerce.number().optional().catch(undefined), until: z.coerce.number().catch(nostrNow()),
limit: z.coerce.number().min(0).max(40).catch(20),
}); });
type PaginationParams = z.infer<typeof paginationSchema>;
function buildLinkHeader(url: string, events: Event[]): string | undefined { function buildLinkHeader(url: string, events: Event[]): string | undefined {
if (!events.length) return; if (!events.length) return;
const firstEvent = events[0]; const firstEvent = events[0];
@ -103,6 +106,7 @@ export {
lookupAccount, lookupAccount,
type Nip05, type Nip05,
nostrNow, nostrNow,
type PaginationParams,
paginationSchema, paginationSchema,
parseBody, parseBody,
parseNip05, parseNip05,