FoundKey/packages/backend/src/server/api/endpoints/ap/show.ts
2024-03-22 09:41:45 +01:00

149 lines
3.7 KiB
TypeScript

import { createPerson } from '@/remote/activitypub/models/person.js';
import { createNote } from '@/remote/activitypub/models/note.js';
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
import { Resolver } from '@/remote/activitypub/resolver.js';
import { extractPunyHost } from '@/misc/convert-host.js';
import { Users, Notes } from '@/models/index.js';
import { Note } from '@/models/entities/note.js';
import { ILocalUser, User } from '@/models/entities/user.js';
import { isActor, isPost } from '@/remote/activitypub/type.js';
import { SchemaType } from '@/misc/schema.js';
import { HOUR } from '@/const.js';
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
import define from '@/server/api/define.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['federation'],
requireCredential: true,
description: 'Shows the requested object. If necessary, fetches the object from the remote server.',
limit: {
duration: HOUR,
max: 30,
},
errors: ['NO_SUCH_OBJECT'],
res: {
optional: false, nullable: false,
oneOf: [
{
type: 'object',
properties: {
type: {
type: 'string',
optional: false, nullable: false,
enum: ['User'],
},
object: {
type: 'object',
optional: false, nullable: false,
ref: 'UserDetailedNotMe',
},
},
},
{
type: 'object',
properties: {
type: {
type: 'string',
optional: false, nullable: false,
enum: ['Note'],
},
object: {
type: 'object',
optional: false, nullable: false,
ref: 'Note',
},
},
},
],
},
} as const;
export const paramDef = {
type: 'object',
properties: {
uri: { type: 'string' },
},
required: ['uri'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
const object = await fetchAny(ps.uri, me);
if (object) {
return object;
} else {
throw new ApiError('NO_SUCH_OBJECT');
}
});
/***
* Resolve a User or Note from a given URI
*/
async function fetchAny(uri: string, me: ILocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
// Stop if the host is blocked.
const host = extractPunyHost(uri);
if (await shouldBlockInstance(host)) {
return null;
}
// first try to fetch the object from the database
const dbResolver = new DbResolver();
let local = await mergePack(me, ...await Promise.all([
dbResolver.getUserFromApId(uri),
dbResolver.getNoteFromApId(uri),
]));
if (local != null) return local;
// getting the object from the database failed, fetch from remote
const resolver = new Resolver();
// allow redirect
const object = await resolver.resolve(uri, true) as any;
// If a URI other than the canonical id such as `/@user` is specified,
// the canonical URI is determined here for the first time.
//
// DB search again, since this may exist in the DB
if (uri !== object.id) {
local = await mergePack(me, ...await Promise.all([
dbResolver.getUserFromApId(object.id),
dbResolver.getNoteFromApId(object.id),
]));
if (local != null) return local;
}
return await mergePack(
me,
isActor(object) ? await createPerson(object, resolver) : null,
isPost(object) ? await createNote(object, resolver, true) : null,
);
}
async function mergePack(me: ILocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
if (user != null) {
return {
type: 'User',
object: await Users.pack(user, me, { detail: true }),
};
} else if (note != null) {
try {
const object = await Notes.pack(note, me, { detail: true });
return {
type: 'Note',
object,
};
} catch (e) {
return null;
}
}
return null;
}