Strictly follow Mastodon API's way of only returning one result of a lookup succeeds

This commit is contained in:
Alex Gleason 2024-08-07 14:41:16 -05:00
parent cdee2604a1
commit 385127761d
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3 changed files with 38 additions and 55 deletions

View file

@ -8,7 +8,7 @@ import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
import { booleanParamSchema, fileSchema } from '@/schema.ts';
import { Storages } from '@/storages.ts';
import { uploadFile } from '@/utils/upload.ts';
import { dedupeEvents, extractBech32, nostrNow } from '@/utils.ts';
import { extractBech32, nostrNow } from '@/utils.ts';
import { createEvent, paginated, parseBody, updateListEvent } from '@/utils/api.ts';
import { lookupAccount } from '@/utils/lookup.ts';
import { renderAccounts, renderEventAccounts, renderStatuses } from '@/views.ts';
@ -110,48 +110,36 @@ const accountSearchQuerySchema = z.object({
q: z.string().transform(decodeURIComponent),
resolve: booleanParamSchema.optional().transform(Boolean),
following: z.boolean().default(false),
limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)),
});
const accountSearchController: AppController = async (c) => {
const result = accountSearchQuerySchema.safeParse(c.req.query());
const { signal } = c.req.raw;
const { limit } = c.get('pagination');
const result = accountSearchQuerySchema.safeParse(c.req.query());
if (!result.success) {
return c.json({ error: 'Bad request', schema: result.error }, 422);
}
const { q, limit } = result.data;
const query = decodeURIComponent(q);
const query = decodeURIComponent(result.data.q);
const store = await Storages.search();
const bech32 = extractBech32(query);
const event = await lookupAccount(bech32 ?? query);
const [event, events] = await Promise.all([
lookupAccount(bech32 ?? query),
store.query([{ kinds: [0], search: query, limit }], { signal }),
]);
if (event) {
events.unshift(event);
if (!event && bech32) {
const pubkey = bech32ToPubkey(bech32);
return c.json(pubkey ? [await accountFromPubkey(pubkey)] : []);
}
const results = await hydrateEvents({
events: dedupeEvents(events),
store,
signal,
});
const events = await store.query([{ kinds: [0], search: query, limit }], { signal })
.then((events) => hydrateEvents({ events, store, signal }));
const accounts = await Promise.all(
results.map((event) => renderAccount(event)),
events.map((event) => renderAccount(event)),
);
// Render account from pubkey.
const pubkey = bech32ToPubkey(result.data.q);
if (pubkey && !accounts.find((account) => account.id === pubkey)) {
accounts.unshift(await accountFromPubkey(pubkey));
}
return c.json(accounts);
};

View file

@ -5,11 +5,11 @@ import { z } from 'zod';
import { AppController } from '@/app.ts';
import { booleanParamSchema } from '@/schema.ts';
import { Storages } from '@/storages.ts';
import { bech32ToPubkey, dedupeEvents, extractBech32 } from '@/utils.ts';
import { hydrateEvents } from '@/storages/hydrate.ts';
import { bech32ToPubkey, extractBech32 } from '@/utils.ts';
import { nip05Cache } from '@/utils/nip05.ts';
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
import { renderStatus } from '@/views/mastodon/statuses.ts';
import { hydrateEvents } from '@/storages/hydrate.ts';
/** Matches NIP-05 names with or without an @ in front. */
const ACCT_REGEX = /^@?(?:([\w.+-]+)@)?([\w.-]+)$/;
@ -33,39 +33,44 @@ const searchController: AppController = async (c) => {
return c.json({ error: 'Bad request', schema: result.error }, 422);
}
const [event, events] = await Promise.all([
lookupEvent(result.data, signal),
searchEvents(result.data, signal),
]);
const event = await lookupEvent(result.data, signal);
const bech32 = extractBech32(result.data.q);
if (event) {
events.unshift(event);
// Render account from pubkey.
if (!event && bech32) {
const pubkey = bech32ToPubkey(bech32);
return c.json({
accounts: pubkey ? [await accountFromPubkey(pubkey)] : [],
statuses: [],
hashtags: [],
});
}
let events: NostrEvent[] = [];
if (event) {
events = [event];
} else {
events = await searchEvents(result.data, signal);
}
const results = dedupeEvents(events);
const viewerPubkey = await c.get('signer')?.getPublicKey();
const [accounts, statuses] = await Promise.all([
Promise.all(
results
events
.filter((event) => event.kind === 0)
.map((event) => renderAccount(event))
.filter(Boolean),
),
Promise.all(
results
events
.filter((event) => event.kind === 1)
.map((event) => renderStatus(event, { viewerPubkey }))
.filter(Boolean),
),
]);
// Render account from pubkey.
const pubkey = bech32ToPubkey(result.data.q);
if (pubkey && !accounts.find((account) => account.id === pubkey)) {
accounts.unshift(await accountFromPubkey(pubkey));
}
return c.json({
accounts,
statuses,
@ -139,14 +144,10 @@ async function getLookupFilters({ q, type, resolve }: SearchQuery, signal: Abort
if (accounts) filters.push({ kinds: [0], authors: [result.data.pubkey] });
break;
case 'note':
if (statuses) {
filters.push({ kinds: [1], ids: [result.data] });
}
if (statuses) filters.push({ kinds: [1], ids: [result.data] });
break;
case 'nevent':
if (statuses) {
filters.push({ kinds: [1], ids: [result.data.id] });
}
if (statuses) filters.push({ kinds: [1], ids: [result.data.id] });
break;
}
} catch {

View file

@ -19,7 +19,7 @@ function bech32ToPubkey(bech32: string): string | undefined {
case 'npub':
return decoded.data;
}
} catch (_) {
} catch {
//
}
}
@ -104,11 +104,6 @@ async function sha256(message: string): Promise<string> {
return hashHex;
}
/** Deduplicate events by ID. */
function dedupeEvents(events: NostrEvent[]): NostrEvent[] {
return [...new Map(events.map((event) => [event.id, event])).values()];
}
/** Test whether the value is a Nostr ID. */
function isNostrId(value: unknown): boolean {
return n.id().safeParse(value).success;
@ -121,7 +116,6 @@ function isURL(value: unknown): boolean {
export {
bech32ToPubkey,
dedupeEvents,
eventAge,
extractBech32,
findTag,