Merge branch 'develop' into refactor/types

This commit is contained in:
naskya 2024-04-18 05:03:32 +09:00
commit cc452da6c5
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
16 changed files with 149 additions and 84 deletions

View file

@ -5,10 +5,13 @@
## What happened? <!-- Please give us a brief description of what happened. -->
## What did you expect to happen? <!-- Please give us a brief description of what you expected to happen. -->
## Version <!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
## What type of issue is this? <!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
- [ ] server-side
@ -19,16 +22,22 @@
### Instance <!-- What instance of firefish are you using? -->
### What browser are you using? (client-side issues only)
### What operating system are you using? (client-side issues only)
### How do you deploy Firefish on your server? (server-side issues only)
### What operating system are you using? (Server-side issues only)
### Relevant log output <!-- Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. -->
</details>
## Contribution Guidelines

View file

@ -5,12 +5,16 @@
## What feature would you like implemented? <!-- Please give us a brief description of what you'd like. -->
## Why should we add this feature? <!-- Please give us a brief description of why your feature is important. -->
## Version <!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
## Instance <!-- What instance of firefish are you using? -->
## Contribution Guidelines
By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines

View file

@ -2,6 +2,7 @@
## What does this PR do? <!-- Please give us a brief description of what this PR does. -->
## Contribution Guidelines
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] This change is reviewed in an issue / This is a minor bug fix

View file

@ -17,6 +17,7 @@ services:
# - web
environment:
NODE_ENV: production
NODE_OPTIONS: --max-old-space-size=3072
volumes:
- ./custom:/firefish/custom:ro
- ./files:/firefish/files

View file

@ -154,7 +154,7 @@ sudo apt install ffmpeg
1. Build
```sh
pnpm install --frozen-lockfile
NODE_ENV=production pnpm run build
NODE_ENV=production NODE_OPTIONS='--max-old-space-size=3072' pnpm run build
```
1. Execute database migrations
```sh
@ -242,6 +242,7 @@ In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefi
WorkingDirectory=/home/firefish/firefish
Environment="NODE_ENV=production"
Environment="npm_config_cache=/tmp"
Environment="NODE_OPTIONS=--max-old-space-size=3072"
# uncomment the following line if you use jemalloc (note that the path varies on different environments)
# Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
StandardOutput=journal

View file

@ -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<string>
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<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 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<string>
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<Meta>

View file

@ -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

View file

@ -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<String>,
pub user_id: Option<String>,

View 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) 📊");
}
}

View file

@ -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;

View file

@ -13,16 +13,15 @@ const round = (num: number) => Math.round(num * 10) / 10;
/**
* Report server stats regularly
*/
export default function () {
export default async function () {
const log = [] as any[];
ev.on("requestServerStatsLog", (x) => {
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50));
});
fetchMeta(true).then((meta) => {
const meta = await fetchMeta(true);
if (!meta.enableServerMachineStats) return;
});
async function tick() {
const cpu = await cpuUsage();

View file

@ -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();
};

View file

@ -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,

View file

@ -44,9 +44,7 @@ export default define(meta, paramDef, async (ps, me) => {
),
);
return await Users.packMany(
users.filter((x) => x !== undefined) as User[],
me,
{ detail: true },
);
return await Users.packMany(users.filter((x) => x != null) as User[], me, {
detail: true,
});
});

View file

@ -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");

View file

@ -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,
),