feat: Add post language data to backend and API

This commit is contained in:
Essem 2023-09-22 01:29:33 +00:00 committed by Kainoa Kanter
parent 5b1cd11618
commit 4a7bad11c6
9 changed files with 74 additions and 4 deletions

View file

@ -4,6 +4,8 @@ Breaking changes are indecated by the :warning: icon.
## v1.0.5 (unreleased) ## v1.0.5 (unreleased)
- Added `lang` parameter to `notes/create` and `notes/edit`.
### dev11 ### dev11
- :warning: `notes/translate` now requires credentials. - :warning: `notes/translate` now requires credentials.

View file

@ -0,0 +1,11 @@
export class AddPostLang1695334243217 {
name = 'AddPostLang1695334243217'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "lang" character varying(10)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "lang"`);
}
}

View file

@ -66,6 +66,12 @@ export class Note {
}) })
public text: string | null; public text: string | null;
@Column("varchar", {
length: 10,
nullable: true,
})
public lang: string | null;
@Column("varchar", { @Column("varchar", {
length: 256, length: 256,
nullable: true, nullable: true,

View file

@ -199,8 +199,6 @@ export const NoteRepository = db.getRepository(Note).extend({
host, host,
); );
const lang =
detectLanguage(`${note.cw ?? ""}\n${note.text ?? ""}`) ?? "unknown";
const reactionEmoji = await populateEmojis(reactionEmojiNames, host); const reactionEmoji = await populateEmojis(reactionEmojiNames, host);
const packed: Packed<"Note"> = await awaitAll({ const packed: Packed<"Note"> = await awaitAll({
id: note.id, id: note.id,
@ -260,7 +258,7 @@ export const NoteRepository = db.getRepository(Note).extend({
: undefined, : undefined,
} }
: {}), : {}),
lang: lang, lang: note.lang,
}); });
if (packed.user.isCat && packed.user.speakAsCat && packed.text) { if (packed.user.isCat && packed.user.speakAsCat && packed.text) {

View file

@ -53,6 +53,7 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
import { truncate } from "@/misc/truncate.js"; import { truncate } from "@/misc/truncate.js";
import { type Size, getEmojiSize } from "@/misc/emoji-meta.js"; import { type Size, getEmojiSize } from "@/misc/emoji-meta.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { langmap } from "@/misc/langmap.js";
const logger = apLogger; const logger = apLogger;
@ -305,11 +306,20 @@ export async function createNote(
// Text parsing // Text parsing
let text: string | null = null; let text: string | null = null;
let lang: string | null = null;
if ( if (
note.source?.mediaType === "text/x.misskeymarkdown" && note.source?.mediaType === "text/x.misskeymarkdown" &&
typeof note.source?.content === "string" typeof note.source?.content === "string"
) { ) {
text = note.source.content; text = note.source.content;
if (note.contentMap != null) {
const key = Object.keys(note.contentMap)[0];
lang = Object.keys(langmap).includes(key) ? key : null;
}
} else if (note.contentMap != null) {
const entry = Object.entries(note.contentMap)[0];
lang = Object.keys(langmap).includes(entry[0]) ? entry[0] : null;
text = htmlToMfm(entry[1], note.tag);
} else if (typeof note.content === "string") { } else if (typeof note.content === "string") {
text = htmlToMfm(note.content, note.tag); text = htmlToMfm(note.content, note.tag);
} }
@ -378,6 +388,7 @@ export async function createNote(
name: note.name, name: note.name,
cw, cw,
text, text,
lang,
localOnly: false, localOnly: false,
visibility, visibility,
visibleUsers, visibleUsers,
@ -565,11 +576,20 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
// Text parsing // Text parsing
let text: string | null = null; let text: string | null = null;
let lang: string | null = null;
if ( if (
post.source?.mediaType === "text/x.misskeymarkdown" && post.source?.mediaType === "text/x.misskeymarkdown" &&
typeof post.source?.content === "string" typeof post.source?.content === "string"
) { ) {
text = post.source.content; text = post.source.content;
if (post.contentMap != null) {
const key = Object.keys(post.contentMap)[0];
lang = Object.keys(langmap).includes(key) ? key : null;
}
} else if (post.contentMap != null) {
const entry = Object.entries(post.contentMap)[0];
lang = Object.keys(langmap).includes(entry[0]) ? entry[0] : null;
text = htmlToMfm(entry[1], post.tag);
} else if (typeof post.content === "string") { } else if (typeof post.content === "string") {
text = htmlToMfm(post.content, post.tag); text = htmlToMfm(post.content, post.tag);
} }
@ -663,6 +683,9 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
if (text && text !== note.text) { if (text && text !== note.text) {
update.text = text; update.text = text;
} }
if (lang && lang !== note.lang) {
update.lang = lang;
}
if (cw !== note.cw) { if (cw !== note.cw) {
update.cw = cw ? cw : null; update.cw = cw ? cw : null;
} }

View file

@ -115,7 +115,7 @@ export default async function renderNote(
}), }),
); );
const lang = detectLanguage(text); const lang = note.lang ?? detectLanguage(text);
const contentMap = lang ? { const contentMap = lang ? {
[lang]: content [lang]: content
} : null; } : null;

View file

@ -108,6 +108,7 @@ export const paramDef = {
}, },
}, },
text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
lang: { type: "string", nullable: true, maxLength: 10 },
cw: { type: "string", nullable: true, maxLength: 100 }, cw: { type: "string", nullable: true, maxLength: 100 },
localOnly: { type: "boolean", default: false }, localOnly: { type: "boolean", default: false },
noExtractMentions: { type: "boolean", default: false }, noExtractMentions: { type: "boolean", default: false },
@ -294,6 +295,7 @@ export default define(meta, paramDef, async (ps, user) => {
} }
: undefined, : undefined,
text: ps.text || undefined, text: ps.text || undefined,
lang: ps.lang,
reply, reply,
renote, renote,
cw: ps.cw, cw: ps.cw,

View file

@ -35,6 +35,8 @@ import renderUpdate from "@/remote/activitypub/renderer/update.js";
import { deliverToRelays } from "@/services/relay.js"; import { deliverToRelays } from "@/services/relay.js";
// import { deliverQuestionUpdate } from "@/services/note/polls/update.js"; // import { deliverQuestionUpdate } from "@/services/note/polls/update.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { detect as detectLanguage } from "tinyld";
import { langmap } from "@/misc/langmap.js";
export const meta = { export const meta = {
tags: ["notes"], tags: ["notes"],
@ -169,6 +171,7 @@ export const paramDef = {
}, },
}, },
text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
lang: { type: "string", nullable: true, maxLength: 10 },
cw: { type: "string", nullable: true, maxLength: 250 }, cw: { type: "string", nullable: true, maxLength: 250 },
localOnly: { type: "boolean", default: false }, localOnly: { type: "boolean", default: false },
noExtractMentions: { type: "boolean", default: false }, noExtractMentions: { type: "boolean", default: false },
@ -375,6 +378,15 @@ export default define(meta, paramDef, async (ps, user) => {
ps.text = null; ps.text = null;
} }
if (ps.lang) {
ps.lang = ps.lang.trim();
if (!Object.keys(langmap).includes(ps.lang.trim())) throw new Error("invalid param");
} else if (ps.text) {
ps.lang = detectLanguage(ps.text);
} else {
ps.lang = null;
}
let tags = []; let tags = [];
let emojis = []; let emojis = [];
let mentionedUsers = []; let mentionedUsers = [];
@ -532,6 +544,9 @@ export default define(meta, paramDef, async (ps, user) => {
if (ps.text !== note.text) { if (ps.text !== note.text) {
update.text = ps.text; update.text = ps.text;
} }
if (ps.lang !== note.lang) {
update.lang = ps.lang;
}
if (ps.cw !== note.cw || (ps.cw && !note.cw)) { if (ps.cw !== note.cw || (ps.cw && !note.cw)) {
update.cw = ps.cw; update.cw = ps.cw;
} }

View file

@ -67,6 +67,8 @@ import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
import meilisearch from "../../db/meilisearch.js"; import meilisearch from "../../db/meilisearch.js";
import { redisClient } from "@/db/redis.js"; import { redisClient } from "@/db/redis.js";
import { Mutex } from "redis-semaphore"; import { Mutex } from "redis-semaphore";
import { detect as detectLanguage } from "tinyld";
import { langmap } from "@/misc/langmap.js";
const mutedWordsCache = new Cache< const mutedWordsCache = new Cache<
{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[] { userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
@ -139,6 +141,7 @@ type Option = {
createdAt?: Date | null; createdAt?: Date | null;
name?: string | null; name?: string | null;
text?: string | null; text?: string | null;
lang?: string | null;
reply?: Note | null; reply?: Note | null;
renote?: Note | null; renote?: Note | null;
files?: DriveFile[] | null; files?: DriveFile[] | null;
@ -276,6 +279,15 @@ export default async (
data.text = null; data.text = null;
} }
if (data.lang) {
data.lang = data.lang.trim();
if (!Object.keys(langmap).includes(data.lang.trim())) throw new Error("invalid param");
} else if (data.text) {
data.lang = detectLanguage(data.text);
} else {
data.lang = null;
}
let tags = data.apHashtags; let tags = data.apHashtags;
let emojis = data.apEmojis; let emojis = data.apEmojis;
let mentionedUsers = data.apMentions; let mentionedUsers = data.apMentions;
@ -712,6 +724,7 @@ async function insertNote(
: null, : null,
name: data.name, name: data.name,
text: data.text, text: data.text,
lang: data.lang,
hasPoll: data.poll != null, hasPoll: data.poll != null,
cw: data.cw == null ? null : data.cw, cw: data.cw == null ? null : data.cw,
tags: tags.map((tag) => normalizeForSearch(tag)), tags: tags.map((tag) => normalizeForSearch(tag)),