refactor (backend): port get-note-summary to backend-rs
I removed trim() as it wasn't strictly neccessary
This commit is contained in:
parent
8337863ed3
commit
30969ad817
9 changed files with 123 additions and 71 deletions
13
packages/backend-rs/index.d.ts
vendored
13
packages/backend-rs/index.d.ts
vendored
|
@ -128,7 +128,8 @@ export interface Acct {
|
||||||
}
|
}
|
||||||
export function stringToAcct(acct: string): Acct
|
export function stringToAcct(acct: string): Acct
|
||||||
export function acctToString(acct: Acct): string
|
export function acctToString(acct: Acct): string
|
||||||
export interface NoteLike {
|
/** TODO: handle name collisions better */
|
||||||
|
export interface NoteLikeForCheckWordMute {
|
||||||
fileIds: Array<string>
|
fileIds: Array<string>
|
||||||
userId: string | null
|
userId: string | null
|
||||||
text: string | null
|
text: string | null
|
||||||
|
@ -136,7 +137,7 @@ export interface NoteLike {
|
||||||
renoteId: string | null
|
renoteId: string | null
|
||||||
replyId: string | null
|
replyId: string | null
|
||||||
}
|
}
|
||||||
export function checkWordMute(note: NoteLike, mutedWordLists: Array<Array<string>>, mutedPatterns: Array<string>): Promise<boolean>
|
export function checkWordMute(note: NoteLikeForCheckWordMute, mutedWordLists: Array<Array<string>>, mutedPatterns: Array<string>): Promise<boolean>
|
||||||
export function getFullApAccount(username: string, host?: string | undefined | null): string
|
export function getFullApAccount(username: string, host?: string | undefined | null): string
|
||||||
export function isSelfHost(host?: string | undefined | null): boolean
|
export function isSelfHost(host?: string | undefined | null): boolean
|
||||||
export function isSameOrigin(uri: string): boolean
|
export function isSameOrigin(uri: string): boolean
|
||||||
|
@ -147,6 +148,14 @@ export function sqlLikeEscape(src: string): string
|
||||||
export function safeForSql(src: string): boolean
|
export function safeForSql(src: string): boolean
|
||||||
/** Convert milliseconds to a human readable string */
|
/** Convert milliseconds to a human readable string */
|
||||||
export function formatMilliseconds(milliseconds: number): string
|
export function formatMilliseconds(milliseconds: number): string
|
||||||
|
/** TODO: handle name collisions better */
|
||||||
|
export interface NoteLikeForGetNoteSummary {
|
||||||
|
fileIds: Array<string>
|
||||||
|
text: string | null
|
||||||
|
cw: string | null
|
||||||
|
hasPoll: boolean
|
||||||
|
}
|
||||||
|
export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
|
||||||
export function toMastodonId(firefishId: string): string | null
|
export function toMastodonId(firefishId: string): string | null
|
||||||
export function fromMastodonId(mastodonId: string): string | null
|
export function fromMastodonId(mastodonId: string): string | null
|
||||||
export function fetchMeta(useCache: boolean): Promise<Meta>
|
export function fetchMeta(useCache: boolean): Promise<Meta>
|
||||||
|
|
|
@ -310,7 +310,7 @@ if (!nativeBinding) {
|
||||||
throw new Error(`Failed to load native binding`)
|
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.readEnvironmentConfig = readEnvironmentConfig
|
||||||
module.exports.readServerConfig = readServerConfig
|
module.exports.readServerConfig = readServerConfig
|
||||||
|
@ -326,6 +326,7 @@ module.exports.isUnicodeEmoji = isUnicodeEmoji
|
||||||
module.exports.sqlLikeEscape = sqlLikeEscape
|
module.exports.sqlLikeEscape = sqlLikeEscape
|
||||||
module.exports.safeForSql = safeForSql
|
module.exports.safeForSql = safeForSql
|
||||||
module.exports.formatMilliseconds = formatMilliseconds
|
module.exports.formatMilliseconds = formatMilliseconds
|
||||||
|
module.exports.getNoteSummary = getNoteSummary
|
||||||
module.exports.toMastodonId = toMastodonId
|
module.exports.toMastodonId = toMastodonId
|
||||||
module.exports.fromMastodonId = fromMastodonId
|
module.exports.fromMastodonId = fromMastodonId
|
||||||
module.exports.fetchMeta = fetchMeta
|
module.exports.fetchMeta = fetchMeta
|
||||||
|
|
|
@ -4,7 +4,8 @@ use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sea_orm::{prelude::*, QuerySelect};
|
use sea_orm::{prelude::*, QuerySelect};
|
||||||
|
|
||||||
#[crate::export(object)]
|
/// TODO: handle name collisions better
|
||||||
|
#[crate::export(object, js_name = "NoteLikeForCheckWordMute")]
|
||||||
pub struct NoteLike {
|
pub struct NoteLike {
|
||||||
pub file_ids: Vec<String>,
|
pub file_ids: Vec<String>,
|
||||||
pub user_id: Option<String>,
|
pub user_id: Option<String>,
|
||||||
|
|
90
packages/backend-rs/src/misc/get_note_summary.rs
Normal file
90
packages/backend-rs/src/misc/get_note_summary.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/// TODO: handle name collisions better
|
||||||
|
#[crate::export(object, js_name = "NoteLikeForGetNoteSummary")]
|
||||||
|
pub struct NoteLike {
|
||||||
|
pub file_ids: Vec<String>,
|
||||||
|
pub text: Option<String>,
|
||||||
|
pub cw: Option<String>,
|
||||||
|
pub has_poll: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::export]
|
||||||
|
pub fn get_note_summary(note: NoteLike) -> String {
|
||||||
|
let mut buf: Vec<String> = 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) 📊");
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ pub mod convert_host;
|
||||||
pub mod emoji;
|
pub mod emoji;
|
||||||
pub mod escape_sql;
|
pub mod escape_sql;
|
||||||
pub mod format_milliseconds;
|
pub mod format_milliseconds;
|
||||||
|
pub mod get_note_summary;
|
||||||
pub mod mastodon_id;
|
pub mod mastodon_id;
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
pub mod nyaify;
|
pub mod nyaify;
|
||||||
|
|
|
@ -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();
|
|
||||||
};
|
|
|
@ -28,7 +28,7 @@ export const packedNoteSchema = {
|
||||||
},
|
},
|
||||||
cw: {
|
cw: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: true,
|
optional: false,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
},
|
},
|
||||||
userId: {
|
userId: {
|
||||||
|
@ -98,7 +98,7 @@ export const packedNoteSchema = {
|
||||||
},
|
},
|
||||||
fileIds: {
|
fileIds: {
|
||||||
type: "array",
|
type: "array",
|
||||||
optional: true,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
items: {
|
items: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -128,6 +128,11 @@ export const packedNoteSchema = {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
hasPoll: {
|
||||||
|
type: "boolean",
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
poll: {
|
poll: {
|
||||||
type: "object",
|
type: "object",
|
||||||
optional: true,
|
optional: true,
|
||||||
|
|
|
@ -27,8 +27,7 @@ import {
|
||||||
Emojis,
|
Emojis,
|
||||||
GalleryPosts,
|
GalleryPosts,
|
||||||
} from "@/models/index.js";
|
} from "@/models/index.js";
|
||||||
import { stringToAcct } from "backend-rs";
|
import { getNoteSummary, stringToAcct } from "backend-rs";
|
||||||
import { getNoteSummary } from "@/misc/get-note-summary.js";
|
|
||||||
import { queues } from "@/queue/queues.js";
|
import { queues } from "@/queue/queues.js";
|
||||||
import { genOpenapiSpec } from "../api/openapi/gen-spec.js";
|
import { genOpenapiSpec } from "../api/openapi/gen-spec.js";
|
||||||
import { urlPreviewHandler } from "./url-preview.js";
|
import { urlPreviewHandler } from "./url-preview.js";
|
||||||
|
@ -517,8 +516,8 @@ router.get("/notes/:note", async (ctx, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (note) {
|
if (note != null) {
|
||||||
const _note = await Notes.pack(note);
|
const packedNote = await Notes.pack(note);
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({
|
const profile = await UserProfiles.findOneByOrFail({
|
||||||
userId: note.userId,
|
userId: note.userId,
|
||||||
|
@ -526,13 +525,13 @@ router.get("/notes/:note", async (ctx, next) => {
|
||||||
const meta = await fetchMeta(true);
|
const meta = await fetchMeta(true);
|
||||||
await ctx.render("note", {
|
await ctx.render("note", {
|
||||||
...metaToPugArgs(meta),
|
...metaToPugArgs(meta),
|
||||||
note: _note,
|
note: packedNote,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: await Users.getAvatarUrl(
|
avatarUrl: await Users.getAvatarUrl(
|
||||||
await Users.findOneByOrFail({ id: note.userId }),
|
await Users.findOneByOrFail({ id: note.userId }),
|
||||||
),
|
),
|
||||||
// TODO: Let locale changeable by instance setting
|
// TODO: Let locale changeable by instance setting
|
||||||
summary: getNoteSummary(_note),
|
summary: getNoteSummary(note),
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.set("Cache-Control", "public, max-age=15");
|
ctx.set("Cache-Control", "public, max-age=15");
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import push from "web-push";
|
import push from "web-push";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import { SwSubscriptions } from "@/models/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 type { Packed } from "@/misc/schema.js";
|
||||||
import { getNoteSummary } from "@/misc/get-note-summary.js";
|
|
||||||
|
|
||||||
// Defined also packages/sw/types.ts#L14-L21
|
// Defined also packages/sw/types.ts#L14-L21
|
||||||
type pushNotificationsTypes = {
|
type pushNotificationsTypes = {
|
||||||
|
@ -17,15 +16,15 @@ type pushNotificationsTypes = {
|
||||||
|
|
||||||
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
|
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
|
||||||
function truncateNotification(notification: Packed<"Notification">): any {
|
function truncateNotification(notification: Packed<"Notification">): any {
|
||||||
if (notification.note) {
|
if (notification.note != null) {
|
||||||
return {
|
return {
|
||||||
...notification,
|
...notification,
|
||||||
note: {
|
note: {
|
||||||
...notification.note,
|
...notification.note,
|
||||||
// textをgetNoteSummaryしたものに置き換える
|
// replace the text with summary
|
||||||
text: getNoteSummary(
|
text: getNoteSummary(
|
||||||
notification.type === "renote"
|
notification.type === "renote" && notification.note.renote != null
|
||||||
? (notification.note.renote as Packed<"Note">)
|
? notification.note.renote
|
||||||
: notification.note,
|
: notification.note,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue