Add public timeline, fix limit param
This commit is contained in:
parent
cacf51ea36
commit
d4eef9c2af
|
@ -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);
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue