mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Strictly follow Mastodon API's way of only returning one result of a lookup succeeds
This commit is contained in:
parent
cdee2604a1
commit
385127761d
3 changed files with 38 additions and 55 deletions
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue