feat: ✨ Add post language data to backend and API
This commit is contained in:
parent
5b1cd11618
commit
4a7bad11c6
9 changed files with 74 additions and 4 deletions
|
@ -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.
|
||||||
|
|
11
packages/backend/migration/1695334243217-add-post-lang.js
Normal file
11
packages/backend/migration/1695334243217-add-post-lang.js
Normal 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"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
Loading…
Reference in a new issue