Merge branch 'develop' into perf-fe-lowres-time

This commit is contained in:
かっこかり 2025-09-08 07:45:45 +09:00 committed by GitHub
commit c12ed2bbb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 59 additions and 23 deletions

View File

@ -1,13 +1,11 @@
## 2025.9.0
### General
-
### Client
- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように
- Enhance: /flushページでサイトキャッシュをクリアできるようになりました
- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張
- Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように
- Enhance: CtrlキーCommandキーを押下しながらリンクをクリックすると新しいタブで開くように
- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上
- Fix: プッシュ通知を有効にできない問題を修正
- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正
@ -15,8 +13,7 @@
- Fix: エラー画像が横に引き伸ばされてしまう問題に対応
### Server
-
- Fix: webpなどの画像に対してセンシティブなメディアの検出が適用されていなかった問題を修正
## 2025.8.0

View File

@ -1644,7 +1644,7 @@ _serverSettings:
reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat."
remoteNotesCleaning: "Neteja automàtica de notes remotes"
remoteNotesCleaning_description: "Quan activis aquesta opció, periòdicament es netejaran les notes remotes que no es consultin, això evitarà que la base de dades se"
remoteNotesCleaningMaxProcessingDuration: "D'oració màxima del temps de funcionament del procés de neteja"
remoteNotesCleaningMaxProcessingDuration: "Duració màxima del temps de funcionament del procés de neteja"
remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes"
inquiryUrl: "URL de consulta "
inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació."

View File

@ -2137,7 +2137,7 @@ _aboutMisskey:
_displayOfSensitiveMedia:
respect: "Esconder medios marcados como sensibles"
ignore: "Mostrar medios marcados como sensibles"
force: "Esconder todala multimedia"
force: "Esconder toda la multimedia"
_instanceTicker:
none: "No mostrar"
remote: "Mostrar a usuarios remotos"

View File

@ -1215,6 +1215,7 @@ privacyPolicyUrl: "Ссылка на Политику Конфиденциаль
tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности"
avatarDecorations: "Украшения для аватара"
attach: "Прикрепить"
detachAll: "Убрать всё"
angle: "Угол"
flip: "Переворот"
showAvatarDecorations: "Показать украшения для аватара"
@ -1253,7 +1254,7 @@ clipNoteLimitExceeded: "К этому клипу больше нельзя до
performance: "Производительность"
modified: "Изменено"
signinWithPasskey: "Войдите в систему, используя свой пароль"
unknownWebAuthnKey: "Не известный ключ "
unknownWebAuthnKey: "Неизвестный ключ"
passkeyVerificationFailed: "Ошибка проверка ключа доступа "
messageToFollower: "Сообщение подписчикам"
testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. <strong>Не использовать это в рабочей среде</strong>"
@ -1268,8 +1269,11 @@ availableRoles: "Доступные роли"
federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах."
draft: "Черновик"
markAsSensitiveConfirm: "Отметить контент как чувствительный?"
preferences: "Основное"
resetToDefaultValue: "Сбросить настройки до стандартных"
syncBetweenDevices: "Синхронизировать между устройствами"
postForm: "Форма отправки"
textCount: "Количество символов"
information: "Описание"
inMinutes: "мин"
inDays: "сут"
@ -1281,6 +1285,11 @@ _chat:
send: "Отправить"
_settings:
webhook: "Вебхук"
preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям"
timelineAndNote: "Лента и заметки"
_chat:
showSenderName: "Показывать имя отправителя"
sendOnEnter: "Использовать Enter для отправки"
_delivery:
stop: "Заморожено"
_type:
@ -1557,6 +1566,12 @@ _achievements:
title: "Brain Diver"
description: "Опубликована ссылка на песню «Brain Diver»"
flavor: "Мисски-Мисски Ла-Ту-Ма"
_bubbleGameExplodingHead:
title: "🤯"
description: "Самый большой объект в Bubble game"
_bubbleGameDoubleExplodingHead:
title: "Двойной🤯"
description: "Два самых больших объекта в Bubble game одновременно!"
_role:
new: "Новая роль"
edit: "Изменить роль"

View File

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.9.0-alpha.1",
"version": "2025.9.0-alpha.2",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@ -29,7 +29,7 @@ export class AiService {
}
@bindThis
public async detectSensitive(path: string): Promise<nsfw.PredictionType[] | null> {
public async detectSensitive(source: string | Buffer): Promise<nsfw.PredictionType[] | null> {
try {
if (isSupportedCpu === undefined) {
isSupportedCpu = await this.computeIsSupportedCpu();
@ -51,7 +51,7 @@ export class AiService {
});
}
const buffer = await fs.promises.readFile(path);
const buffer = source instanceof Buffer ? source : await fs.promises.readFile(source);
const image = await tf.node.decodeImage(buffer, 3) as any;
try {
const predictions = await this.model.classify(image);

View File

@ -21,6 +21,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import type { PredictionType } from 'nsfwjs';
import { isMimeImage } from '@/misc/is-mime-image.js';
export type FileInfo = {
size: number;
@ -204,16 +205,7 @@ export class FileInfoService {
return [sensitive, porn];
}
if ([
'image/jpeg',
'image/png',
'image/webp',
].includes(mime)) {
const result = await this.aiService.detectSensitive(source);
if (result) {
[sensitive, porn] = judgePrediction(result);
}
} else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) {
if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) {
const [outDir, disposeOutDir] = await createTempDir();
try {
const command = FFmpeg()
@ -281,6 +273,23 @@ export class FileInfoService {
} finally {
disposeOutDir();
}
} else if (isMimeImage(mime, 'sharp-convertible-image-with-bmp')) {
/*
* tfjs-node sharp PNG
* 使299x299に事前にリサイズする
*/
const png = await (await sharpBmp(source, mime))
.resize(299, 299, {
withoutEnlargement: false,
})
.rotate()
.flatten({ background: { r: 119, g: 119, b: 119 } }) // 透過部分を18%グレーで塗りつぶす
.png()
.toBuffer();
const result = await this.aiService.detectSensitive(png);
if (result) {
[sensitive, porn] = judgePrediction(result);
}
}
return [sensitive, porn];

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<a ref="el" :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu">
<a ref="el" :href="to" :class="active ? activeClass : null" @click="nav" @contextmenu.prevent.stop="onContextmenu">
<slot></slot>
</a>
</template>
@ -86,6 +86,11 @@ function openWindow() {
}
function nav(ev: MouseEvent) {
// shift
if (ev.metaKey || ev.altKey || ev.ctrlKey) return;
ev.preventDefault();
if (behavior === 'browser') {
window.location.href = props.to;
return;

View File

@ -131,6 +131,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<hr>
<MkButton @click="forceCloudBackup">Force cloud backup</MkButton>
<hr>
<template v-if="$i.policies.chatAvailability !== 'unavailable'">
<MkButton @click="readAllChatMessages">Read all chat messages</MkButton>
@ -167,6 +171,7 @@ import { signout } from '@/signout.js';
import { migrateOldSettings } from '@/pref-migrate.js';
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
import { suggestReload } from '@/utility/reload-suggest.js';
import { cloudBackup } from '@/preferences/utility.js';
const $i = ensureSignin();
@ -224,6 +229,11 @@ function readAllChatMessages() {
os.apiWithDialog('chat/read-all', {});
}
async function forceCloudBackup() {
await cloudBackup();
os.success();
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);

View File

@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2025.9.0-alpha.1",
"version": "2025.9.0-alpha.2",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",