From 30969ad81799b7688ae3565066a57ad4d2dee83d Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 18 Apr 2024 05:02:00 +0900 Subject: [PATCH] refactor (backend): port get-note-summary to backend-rs I removed trim() as it wasn't strictly neccessary --- packages/backend-rs/index.d.ts | 13 ++- packages/backend-rs/index.js | 3 +- .../backend-rs/src/misc/check_word_mute.rs | 3 +- .../backend-rs/src/misc/get_note_summary.rs | 90 +++++++++++++++++++ packages/backend-rs/src/misc/mod.rs | 1 + packages/backend/src/misc/get-note-summary.ts | 53 ----------- packages/backend/src/models/schema/note.ts | 9 +- packages/backend/src/server/web/index.ts | 11 ++- .../backend/src/services/push-notification.ts | 11 ++- 9 files changed, 123 insertions(+), 71 deletions(-) create mode 100644 packages/backend-rs/src/misc/get_note_summary.rs delete mode 100644 packages/backend/src/misc/get-note-summary.ts diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index ae050e01d8..5ee69969c6 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -128,7 +128,8 @@ export interface Acct { } export function stringToAcct(acct: string): Acct export function acctToString(acct: Acct): string -export interface NoteLike { +/** TODO: handle name collisions better */ +export interface NoteLikeForCheckWordMute { fileIds: Array userId: string | null text: string | null @@ -136,7 +137,7 @@ export interface NoteLike { renoteId: string | null replyId: string | null } -export function checkWordMute(note: NoteLike, mutedWordLists: Array>, mutedPatterns: Array): Promise +export function checkWordMute(note: NoteLikeForCheckWordMute, mutedWordLists: Array>, mutedPatterns: Array): Promise export function getFullApAccount(username: string, host?: string | undefined | null): string export function isSelfHost(host?: string | undefined | null): boolean export function isSameOrigin(uri: string): boolean @@ -147,6 +148,14 @@ export function sqlLikeEscape(src: string): string export function safeForSql(src: string): boolean /** Convert milliseconds to a human readable string */ export function formatMilliseconds(milliseconds: number): string +/** TODO: handle name collisions better */ +export interface NoteLikeForGetNoteSummary { + fileIds: Array + text: string | null + cw: string | null + hasPoll: boolean +} +export function getNoteSummary(note: NoteLikeForGetNoteSummary): string export function toMastodonId(firefishId: string): string | null export function fromMastodonId(mastodonId: string): string | null export function fetchMeta(useCache: boolean): Promise diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index 6acd3ca68d..1ea7bb5bed 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding +const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding module.exports.readEnvironmentConfig = readEnvironmentConfig module.exports.readServerConfig = readServerConfig @@ -326,6 +326,7 @@ module.exports.isUnicodeEmoji = isUnicodeEmoji module.exports.sqlLikeEscape = sqlLikeEscape module.exports.safeForSql = safeForSql module.exports.formatMilliseconds = formatMilliseconds +module.exports.getNoteSummary = getNoteSummary module.exports.toMastodonId = toMastodonId module.exports.fromMastodonId = fromMastodonId module.exports.fetchMeta = fetchMeta diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs index 801175c2af..18b550c29b 100644 --- a/packages/backend-rs/src/misc/check_word_mute.rs +++ b/packages/backend-rs/src/misc/check_word_mute.rs @@ -4,7 +4,8 @@ use once_cell::sync::Lazy; use regex::Regex; use sea_orm::{prelude::*, QuerySelect}; -#[crate::export(object)] +/// TODO: handle name collisions better +#[crate::export(object, js_name = "NoteLikeForCheckWordMute")] pub struct NoteLike { pub file_ids: Vec, pub user_id: Option, diff --git a/packages/backend-rs/src/misc/get_note_summary.rs b/packages/backend-rs/src/misc/get_note_summary.rs new file mode 100644 index 0000000000..3b759b04f5 --- /dev/null +++ b/packages/backend-rs/src/misc/get_note_summary.rs @@ -0,0 +1,90 @@ +/// TODO: handle name collisions better +#[crate::export(object, js_name = "NoteLikeForGetNoteSummary")] +pub struct NoteLike { + pub file_ids: Vec, + pub text: Option, + pub cw: Option, + pub has_poll: bool, +} + +#[crate::export] +pub fn get_note_summary(note: NoteLike) -> String { + let mut buf: Vec = vec![]; + + if let Some(cw) = note.cw { + buf.push(cw) + } else if let Some(text) = note.text { + buf.push(text) + } + + match note.file_ids.len() { + 0 => (), + 1 => buf.push("📎".to_string()), + n => buf.push(format!("📎 ({})", n)), + }; + + if note.has_poll { + buf.push("📊".to_string()) + } + + buf.join(" ") +} + +#[cfg(test)] +mod unit_test { + use super::{get_note_summary, NoteLike}; + use pretty_assertions::assert_eq; + + #[test] + fn test_note_summary() { + let note = NoteLike { + file_ids: vec![], + text: Some("Hello world!".to_string()), + cw: None, + has_poll: false, + }; + assert_eq!(get_note_summary(note), "Hello world!"); + + let note_with_cw = NoteLike { + file_ids: vec![], + text: Some("Hello world!".to_string()), + cw: Some("Content warning".to_string()), + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_cw), "Content warning"); + + let note_with_file_and_cw = NoteLike { + file_ids: vec!["9s7fmcqogiq4igin".to_string()], + text: None, + cw: Some("Selfie, no ec".to_string()), + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_file_and_cw), "Selfie, no ec 📎"); + + let note_with_files_only = NoteLike { + file_ids: vec![ + "9s7fmcqogiq4igin".to_string(), + "9s7qrld5u14cey98".to_string(), + "9s7gebs5zgts4kca".to_string(), + "9s5z3e4vefqd29ee".to_string(), + ], + text: None, + cw: None, + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_files_only), "📎 (4)"); + + let note_all = NoteLike { + file_ids: vec![ + "9s7fmcqogiq4igin".to_string(), + "9s7qrld5u14cey98".to_string(), + "9s7gebs5zgts4kca".to_string(), + "9s5z3e4vefqd29ee".to_string(), + ], + text: Some("Hello world!".to_string()), + cw: Some("Content warning".to_string()), + has_poll: true, + }; + assert_eq!(get_note_summary(note_all), "Content warning 📎 (4) 📊"); + } +} diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 45fd31cdcd..a9d7074dbf 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -4,6 +4,7 @@ pub mod convert_host; pub mod emoji; pub mod escape_sql; pub mod format_milliseconds; +pub mod get_note_summary; pub mod mastodon_id; pub mod meta; pub mod nyaify; diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts deleted file mode 100644 index 0a662e434e..0000000000 --- a/packages/backend/src/misc/get-note-summary.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Packed } from "./schema.js"; - -/** - * 投稿を表す文字列を取得します。 - * @param {*} note (packされた)投稿 - */ -export const getNoteSummary = (note: Packed<"Note">): string => { - if (note.deletedAt) { - return "❌"; - } - - let summary = ""; - - // 本文 - if (note.cw != null) { - summary += note.cw; - } else { - summary += note.text ? note.text : ""; - } - - // ファイルが添付されているとき - if ((note.files || []).length !== 0) { - const len = note.files?.length; - summary += ` 📎${len !== 1 ? ` (${len})` : ""}`; - } - - // 投票が添付されているとき - if (note.poll) { - summary += " 📊"; - } - - /* - // 返信のとき - if (note.replyId) { - if (note.reply) { - summary += `\n\nRE: ${getNoteSummary(note.reply)}`; - } else { - summary += '\n\nRE: ...'; - } - } - - // Renoteのとき - if (note.renoteId) { - if (note.renote) { - summary += `\n\nRN: ${getNoteSummary(note.renote)}`; - } else { - summary += '\n\nRN: ...'; - } - } - */ - - return summary.trim(); -}; diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index 7dcdbc9b03..fff872b69f 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -28,7 +28,7 @@ export const packedNoteSchema = { }, cw: { type: "string", - optional: true, + optional: false, nullable: true, }, userId: { @@ -98,7 +98,7 @@ export const packedNoteSchema = { }, fileIds: { type: "array", - optional: true, + optional: false, nullable: false, items: { type: "string", @@ -128,6 +128,11 @@ export const packedNoteSchema = { nullable: false, }, }, + hasPoll: { + type: "boolean", + optional: false, + nullable: false, + }, poll: { type: "object", optional: true, diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 6473073370..939fcfab14 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -27,8 +27,7 @@ import { Emojis, GalleryPosts, } from "@/models/index.js"; -import { stringToAcct } from "backend-rs"; -import { getNoteSummary } from "@/misc/get-note-summary.js"; +import { getNoteSummary, stringToAcct } from "backend-rs"; import { queues } from "@/queue/queues.js"; import { genOpenapiSpec } from "../api/openapi/gen-spec.js"; import { urlPreviewHandler } from "./url-preview.js"; @@ -517,8 +516,8 @@ router.get("/notes/:note", async (ctx, next) => { }); try { - if (note) { - const _note = await Notes.pack(note); + if (note != null) { + const packedNote = await Notes.pack(note); const profile = await UserProfiles.findOneByOrFail({ userId: note.userId, @@ -526,13 +525,13 @@ router.get("/notes/:note", async (ctx, next) => { const meta = await fetchMeta(true); await ctx.render("note", { ...metaToPugArgs(meta), - note: _note, + note: packedNote, profile, avatarUrl: await Users.getAvatarUrl( await Users.findOneByOrFail({ id: note.userId }), ), // TODO: Let locale changeable by instance setting - summary: getNoteSummary(_note), + summary: getNoteSummary(note), }); ctx.set("Cache-Control", "public, max-age=15"); diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 1a772ff9c5..3f1f2cfb1a 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -1,9 +1,8 @@ import push from "web-push"; import config from "@/config/index.js"; import { SwSubscriptions } from "@/models/index.js"; -import { fetchMeta } from "backend-rs"; +import { fetchMeta, getNoteSummary } from "backend-rs"; import type { Packed } from "@/misc/schema.js"; -import { getNoteSummary } from "@/misc/get-note-summary.js"; // Defined also packages/sw/types.ts#L14-L21 type pushNotificationsTypes = { @@ -17,15 +16,15 @@ type pushNotificationsTypes = { // プッシュメッセージサーバーには文字数制限があるため、内容を削減します function truncateNotification(notification: Packed<"Notification">): any { - if (notification.note) { + if (notification.note != null) { return { ...notification, note: { ...notification.note, - // textをgetNoteSummaryしたものに置き換える + // replace the text with summary text: getNoteSummary( - notification.type === "renote" - ? (notification.note.renote as Packed<"Note">) + notification.type === "renote" && notification.note.renote != null + ? notification.note.renote : notification.note, ),