diff --git a/CHANGELOG.md b/CHANGELOG.md
index eef54a4eb4..c527ac5e90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 4b6c8b97c3..63878bf1b7 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -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ó."
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index ac983aae37..8a1d2c458b 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -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"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index aea69f6f24..f633e1488f 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -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. Не использовать это в рабочей среде"
@@ -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: "Изменить роль"
diff --git a/package.json b/package.json
index faafb9c264..1e7c8507cc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "misskey",
- "version": "2025.9.0-alpha.1",
+ "version": "2025.9.0-alpha.2",
"codename": "nasubi",
"repository": {
"type": "git",
diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts
index 248a9b8979..23ab8082ed 100644
--- a/packages/backend/src/core/AiService.ts
+++ b/packages/backend/src/core/AiService.ts
@@ -29,7 +29,7 @@ export class AiService {
}
@bindThis
- public async detectSensitive(path: string): Promise {
+ public async detectSensitive(source: string | Buffer): Promise {
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);
diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts
index 6250d4d3a1..62a7d24afb 100644
--- a/packages/backend/src/core/FileInfoService.ts
+++ b/packages/backend/src/core/FileInfoService.ts
@@ -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];
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index ae1b4549ec..99693a4c00 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
+
@@ -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;
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index 730cce183a..41b799bead 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -131,6 +131,10 @@ SPDX-License-Identifier: AGPL-3.0-only
+ Force cloud backup
+
+
+
Read all chat messages
@@ -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(() => []);
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 344b625ca7..4ee76d23f0 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -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",