Merge branch 'develop' into refactor/types
This commit is contained in:
commit
cc452da6c5
16 changed files with 149 additions and 84 deletions
|
@ -5,30 +5,39 @@
|
|||
|
||||
## 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
|
||||
- [] client-side
|
||||
- [] not sure
|
||||
- [ ] server-side
|
||||
- [ ] client-side
|
||||
- [ ] not sure
|
||||
|
||||
<details>
|
||||
|
||||
### 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
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 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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
|
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 escape_sql;
|
||||
pub mod format_milliseconds;
|
||||
pub mod get_note_summary;
|
||||
pub mod mastodon_id;
|
||||
pub mod meta;
|
||||
pub mod nyaify;
|
||||
|
|
|
@ -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) => {
|
||||
if (!meta.enableServerMachineStats) return;
|
||||
});
|
||||
const meta = await fetchMeta(true);
|
||||
if (!meta.enableServerMachineStats) return;
|
||||
|
||||
async function tick() {
|
||||
const cpu = await cpuUsage();
|
||||
|
|
|
@ -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: {
|
||||
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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
||||
|
|
Loading…
Reference in a new issue