feat: create translateController - /api/v1/statuses/:id/translate

This commit is contained in:
P. Reis 2024-10-07 15:02:04 -03:00
parent 8e58b1a7d4
commit b369b2171d
2 changed files with 95 additions and 0 deletions

View file

@ -109,6 +109,7 @@ import {
trendingStatusesController, trendingStatusesController,
trendingTagsController, trendingTagsController,
} from '@/controllers/api/trends.ts'; } from '@/controllers/api/trends.ts';
import { translateController } from '@/controllers/api/translate.ts';
import { errorHandler } from '@/controllers/error.ts'; import { errorHandler } from '@/controllers/error.ts';
import { frontendController } from '@/controllers/frontend.ts'; import { frontendController } from '@/controllers/frontend.ts';
import { metricsController } from '@/controllers/metrics.ts'; import { metricsController } from '@/controllers/metrics.ts';
@ -126,6 +127,8 @@ import { requireSigner } from '@/middleware/requireSigner.ts';
import { signerMiddleware } from '@/middleware/signerMiddleware.ts'; import { signerMiddleware } from '@/middleware/signerMiddleware.ts';
import { storeMiddleware } from '@/middleware/storeMiddleware.ts'; import { storeMiddleware } from '@/middleware/storeMiddleware.ts';
import { uploaderMiddleware } from '@/middleware/uploaderMiddleware.ts'; import { uploaderMiddleware } from '@/middleware/uploaderMiddleware.ts';
import { DittoTranslator } from '@/translators/translator.ts';
import { translatorMiddleware } from '@/middleware/translatorMiddleware.ts';
interface AppEnv extends HonoEnv { interface AppEnv extends HonoEnv {
Variables: { Variables: {
@ -141,6 +144,8 @@ interface AppEnv extends HonoEnv {
pagination: { since?: number; until?: number; limit: number }; pagination: { since?: number; until?: number; limit: number };
/** Normalized list pagination params. */ /** Normalized list pagination params. */
listPagination: { offset: number; limit: number }; listPagination: { offset: number; limit: number };
/** Translation service. */
translator?: DittoTranslator;
}; };
} }
@ -220,6 +225,7 @@ app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/bookmark', requireSigner, bookmarkC
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requireSigner, unbookmarkController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requireSigner, unbookmarkController);
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requireSigner, pinController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requireSigner, pinController);
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requireSigner, unpinController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requireSigner, unpinController);
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/translate', requireSigner, translatorMiddleware, translateController);
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', requireSigner, reblogStatusController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', requireSigner, reblogStatusController);
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', requireSigner, unreblogStatusController); app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', requireSigner, unreblogStatusController);
app.post('/api/v1/statuses', requireSigner, createStatusController); app.post('/api/v1/statuses', requireSigner, createStatusController);

View file

@ -0,0 +1,89 @@
import { LanguageCode } from 'iso-639-1';
import { z } from 'zod';
import { AppController } from '@/app.ts';
import { languageSchema } from '@/schema.ts';
import { Storages } from '@/storages.ts';
import { dittoTranslations, dittoTranslationsKey } from '@/translators/translator.ts';
import { parseBody } from '@/utils/api.ts';
import { getEvent } from '@/queries.ts';
import { renderStatus } from '@/views/mastodon/statuses.ts';
const translateSchema = z.object({
//lang: languageSchema, // Correct property name, as stated by Mastodon docs
target_language: languageSchema, // Property name soapbox sends
});
const translateController: AppController = async (c) => {
const result = translateSchema.safeParse(await parseBody(c.req.raw));
const { signal } = c.req.raw;
if (!result.success) {
return c.json({ error: 'Bad request.', schema: result.error }, 422);
}
const translator = c.get('translator');
if (!translator) {
return c.json({ error: 'No translator configured.' }, 500);
}
const { target_language } = result.data;
const targetLang = target_language;
const id = c.req.param('id');
const event = await getEvent(id, { signal });
if (!event) {
return c.json({ error: 'Record not found' }, 400);
}
const viewerPubkey = await c.get('signer')?.getPublicKey();
const kysely = await Storages.kysely();
let sourceLang = (await kysely
.selectFrom('nostr_events')
.select('language').where('id', '=', id)
.limit(1)
.execute())[0]?.language as LanguageCode | undefined;
if (!sourceLang) {
sourceLang = undefined;
}
if (targetLang.toLowerCase() === sourceLang?.toLowerCase()) {
return c.json({ error: 'Source and target languages are the same. No translation needed.' }, 400);
}
const status = await renderStatus(event, { viewerPubkey });
const translatedId = `${target_language}-${id}` as dittoTranslationsKey;
const translationCache = dittoTranslations.get(translatedId);
if (translationCache) {
return c.json(translationCache.data, 200);
}
const mediaAttachments = status?.media_attachments.map((value) => {
return {
id: value.id,
description: value.description ?? '',
};
}) ?? [];
try {
const translation = await translator.translate(
status?.content ?? '',
status?.spoiler_text ?? '',
mediaAttachments,
null,
sourceLang,
targetLang,
{ signal },
);
dittoTranslations.set(translatedId, translation);
return c.json(translation.data, 200);
} catch (_) {
return c.json({ error: 'Service Unavailable' }, 503);
}
};
export { translateController };