From 3e85052754b1c86e3c3b16d2e29b5aef6db04ccf Mon Sep 17 00:00:00 2001
From: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Date: Thu, 29 Aug 2024 18:57:44 +0900
Subject: fix(backend): correct `app`-type notification schema (#14471)
---
packages/backend/src/models/Notification.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'packages/backend/src/models/Notification.ts')
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index df88b99636..87d8c16cb3 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -85,7 +85,7 @@ export type MiNotification = {
/**
* アプリ通知のbody
*/
- customBody: string | null;
+ customBody: string;
/**
* アプリ通知のheader
--
cgit v1.2.3-freya
From d8a2eeb7ed44a4e25426ca9b7c6c85b8c62d3106 Mon Sep 17 00:00:00 2001
From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Thu, 26 Sep 2024 14:15:03 +0900
Subject: feat: エクスポート完了時に通知を発行するように (#14484)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: エクスポート完了時に通知を発行するように
* Update Changelog
* entitity -> entity
* fix: ペイロードを含むように
* fix icon
* exportableEntities -> userExportableEntities
---
CHANGELOG.md | 1 +
locales/index.d.ts | 16 ++++++++++++
locales/ja-JP.yml | 4 +++
.../src/core/entities/NotificationEntityService.ts | 4 +++
packages/backend/src/models/Notification.ts | 8 ++++++
.../backend/src/models/json-schema/notification.ts | 22 +++++++++++++++-
.../processors/ExportAntennasProcessorService.ts | 7 ++++++
.../processors/ExportBlockingProcessorService.ts | 7 ++++++
.../processors/ExportClipsProcessorService.ts | 7 ++++++
.../ExportCustomEmojisProcessorService.ts | 8 ++++++
.../processors/ExportFavoritesProcessorService.ts | 7 ++++++
.../processors/ExportFollowingProcessorService.ts | 7 ++++++
.../processors/ExportMutingProcessorService.ts | 7 ++++++
.../processors/ExportNotesProcessorService.ts | 7 ++++++
.../processors/ExportUserListsProcessorService.ts | 7 ++++++
packages/backend/src/types.ts | 16 ++++++++++++
packages/frontend-shared/js/const.ts | 2 ++
.../frontend/src/components/MkNotification.vue | 29 +++++++++++++++++++++-
.../frontend/src/pages/settings/notifications.vue | 2 +-
packages/misskey-js/src/autogen/types.ts | 19 +++++++++++---
packages/sw/src/scripts/create-notification.ts | 19 ++++++++++++++
21 files changed, 199 insertions(+), 7 deletions(-)
(limited to 'packages/backend/src/models/Notification.ts')
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 086ff8dcc8..a1d2e950b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680)
+- Feat: データエクスポートが完了した際に通知を発行するように
- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように
- Enhance: 依存関係の更新
- Enhance: l10nの更新
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 1250aa4f4d..a52ee8d808 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1352,6 +1352,10 @@ export interface Locale extends ILocale {
* ファイルを追加
*/
"addFile": string;
+ /**
+ * ファイルを表示
+ */
+ "showFile": string;
/**
* ドライブは空です
*/
@@ -9253,6 +9257,10 @@ export interface Locale extends ILocale {
* 通知の履歴をリセットする
*/
"flushNotification": string;
+ /**
+ * {x}のエクスポートが完了しました
+ */
+ "exportOfXCompleted": ParameterizedString<"x">;
"_types": {
/**
* すべて
@@ -9306,6 +9314,14 @@ export interface Locale extends ILocale {
* 実績の獲得
*/
"achievementEarned": string;
+ /**
+ * エクスポートが完了した
+ */
+ "exportCompleted": string;
+ /**
+ * 通知のテスト
+ */
+ "test": string;
/**
* 連携アプリからの通知
*/
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 6c5df3e658..75c895a230 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -334,6 +334,7 @@ renameFolder: "フォルダー名を変更"
deleteFolder: "フォルダーを削除"
folder: "フォルダー"
addFile: "ファイルを追加"
+showFile: "ファイルを表示"
emptyDrive: "ドライブは空です"
emptyFolder: "フォルダーは空です"
unableToDelete: "削除できません"
@@ -2443,6 +2444,7 @@ _notification:
renotedBySomeUsers: "{n}人がリノートしました"
followedBySomeUsers: "{n}人にフォローされました"
flushNotification: "通知の履歴をリセットする"
+ exportOfXCompleted: "{x}のエクスポートが完了しました"
_types:
all: "すべて"
@@ -2458,6 +2460,8 @@ _notification:
followRequestAccepted: "フォローが受理された"
roleAssigned: "ロールが付与された"
achievementEarned: "実績の獲得"
+ exportCompleted: "エクスポートが完了した"
+ test: "通知のテスト"
app: "連携アプリからの通知"
_actions:
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index f393513510..1b61a6195d 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -162,6 +162,10 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement,
} : {}),
+ ...(notification.type === 'exportCompleted' ? {
+ exportedEntity: notification.exportedEntity,
+ fileId: notification.fileId,
+ } : {}),
...(notification.type === 'app' ? {
body: notification.customBody,
header: notification.customHeader,
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 87d8c16cb3..2c5b75f577 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -7,6 +7,8 @@ import { MiUser } from './User.js';
import { MiNote } from './Note.js';
import { MiAccessToken } from './AccessToken.js';
import { MiRole } from './Role.js';
+import { MiDriveFile } from './DriveFile.js';
+import { userExportableEntities } from '@/types.js';
export type MiNotification = {
type: 'note';
@@ -77,6 +79,12 @@ export type MiNotification = {
id: string;
createdAt: string;
achievement: string;
+} | {
+ type: 'exportCompleted';
+ id: string;
+ createdAt: string;
+ exportedEntity: typeof userExportableEntities[number];
+ fileId: MiDriveFile['id'];
} | {
type: 'app';
id: string;
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index b05ec8b762..bbec2e397f 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -4,7 +4,7 @@
*/
import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
-import { notificationTypes } from '@/types.js';
+import { notificationTypes, userExportableEntities } from '@/types.js';
const baseSchema = {
type: 'object',
@@ -298,6 +298,26 @@ export const packedNotificationSchema = {
enum: ACHIEVEMENT_TYPES,
},
},
+ }, {
+ type: 'object',
+ properties: {
+ ...baseSchema.properties,
+ type: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: ['exportCompleted'],
+ },
+ exportedEntity: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: userExportableEntities,
+ },
+ fileId: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'id',
+ },
+ },
}, {
type: 'object',
properties: {
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
index 88c4ea29c0..b3111865ad 100644
--- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
@@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js';
import { bindThis } from '@/decorators.js';
import { createTemp } from '@/misc/create-temp.js';
import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type { DBExportAntennasData } from '../types.js';
import type * as Bull from 'bullmq';
@@ -35,6 +36,7 @@ export class ExportAntennasProcessorService {
private driveService: DriveService,
private utilityService: UtilityService,
private queueLoggerService: QueueLoggerService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas');
}
@@ -95,6 +97,11 @@ export class ExportAntennasProcessorService {
const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
this.logger.succ('Exported to: ' + driveFile.id);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'antenna',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
index 6ec3c18786..ecc439db69 100644
--- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
@@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { createTemp } from '@/misc/create-temp.js';
import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@@ -30,6 +31,7 @@ export class ExportBlockingProcessorService {
private blockingsRepository: BlockingsRepository,
private utilityService: UtilityService,
+ private notificationService: NotificationService,
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
) {
@@ -109,6 +111,11 @@ export class ExportBlockingProcessorService {
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'blocking',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
index 01eab26e96..583ddbb745 100644
--- a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
@@ -19,6 +19,7 @@ import { bindThis } from '@/decorators.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbJobDataWithUser } from '../types.js';
@@ -43,6 +44,7 @@ export class ExportClipsProcessorService {
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
private idService: IdService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-clips');
}
@@ -79,6 +81,11 @@ export class ExportClipsProcessorService {
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'clip',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
index e4eb4791bd..e237cd4975 100644
--- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
@@ -16,6 +16,7 @@ import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { createTemp, createTempDir } from '@/misc/create-temp.js';
import { DownloadService } from '@/core/DownloadService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@@ -37,6 +38,7 @@ export class ExportCustomEmojisProcessorService {
private driveService: DriveService,
private downloadService: DownloadService,
private queueLoggerService: QueueLoggerService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis');
}
@@ -134,6 +136,12 @@ export class ExportCustomEmojisProcessorService {
const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'customEmoji',
+ fileId: driveFile.id,
+ });
+
cleanup();
archiveCleanup();
resolve();
diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
index 7bb626dd31..b81feece01 100644
--- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
@@ -16,6 +16,7 @@ import type { MiPoll } from '@/models/Poll.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbJobDataWithUser } from '../types.js';
@@ -37,6 +38,7 @@ export class ExportFavoritesProcessorService {
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
private idService: IdService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites');
}
@@ -123,6 +125,11 @@ export class ExportFavoritesProcessorService {
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'favorite',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
index 1cc80e66d7..903f962515 100644
--- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
@@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js';
import { createTemp } from '@/misc/create-temp.js';
import type { MiFollowing } from '@/models/Following.js';
import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@@ -36,6 +37,7 @@ export class ExportFollowingProcessorService {
private utilityService: UtilityService,
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-following');
}
@@ -113,6 +115,11 @@ export class ExportFollowingProcessorService {
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'following',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
index 243b74f2c2..f9867ade29 100644
--- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
@@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { createTemp } from '@/misc/create-temp.js';
import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@@ -32,6 +33,7 @@ export class ExportMutingProcessorService {
private utilityService: UtilityService,
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-muting');
}
@@ -110,6 +112,11 @@ export class ExportMutingProcessorService {
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'muting',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
index c7611012d7..9e2b678219 100644
--- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
@@ -18,6 +18,7 @@ import { bindThis } from '@/decorators.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { JsonArrayStream } from '@/misc/JsonArrayStream.js';
import { FileWriterStream } from '@/misc/FileWriterStream.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
@@ -112,6 +113,7 @@ export class ExportNotesProcessorService {
private queueLoggerService: QueueLoggerService,
private driveFileEntityService: DriveFileEntityService,
private idService: IdService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-notes');
}
@@ -150,6 +152,11 @@ export class ExportNotesProcessorService {
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'note',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
index ee87cff5d3..c483d79854 100644
--- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
@@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { createTemp } from '@/misc/create-temp.js';
import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@@ -35,6 +36,7 @@ export class ExportUserListsProcessorService {
private utilityService: UtilityService,
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
+ private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists');
}
@@ -89,6 +91,11 @@ export class ExportUserListsProcessorService {
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
this.logger.succ(`Exported to: ${driveFile.id}`);
+
+ this.notificationService.createNotification(user.id, 'exportCompleted', {
+ exportedEntity: 'userList',
+ fileId: driveFile.id,
+ });
} finally {
cleanup();
}
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index e852cf5ae2..5854c6b392 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -16,6 +16,7 @@
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
* roleAssigned - ロールが付与された
* achievementEarned - 実績を獲得
+ * exportCompleted - エクスポートが完了
* app - アプリ通知
* test - テスト通知(サーバー側)
*/
@@ -32,6 +33,7 @@ export const notificationTypes = [
'followRequestAccepted',
'roleAssigned',
'achievementEarned',
+ 'exportCompleted',
'app',
'test',
] as const;
@@ -51,6 +53,20 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
export const followingVisibilities = ['public', 'followers', 'private'] as const;
export const followersVisibilities = ['public', 'followers', 'private'] as const;
+/**
+ * ユーザーがエクスポートできるものの種類
+ *
+ * (主にエクスポート完了通知で使用するものであり、既存のDBの名称等と必ずしも一致しない)
+ */
+export const userExportableEntities = ['antenna', 'blocking', 'clip', 'customEmoji', 'favorite', 'following', 'muting', 'note', 'userList'] as const;
+
+/**
+ * ユーザーがインポートできるものの種類
+ *
+ * (主にインポート完了通知で使用するものであり、既存のDBの名称等と必ずしも一致しない)
+ */
+export const userImportableEntities = ['antenna', 'blocking', 'customEmoji', 'following', 'muting', 'userList'] as const;
+
export const moderationLogTypes = [
'updateServerSettings',
'suspend',
diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts
index b62a69ba24..aec4a4a58b 100644
--- a/packages/frontend-shared/js/const.ts
+++ b/packages/frontend-shared/js/const.ts
@@ -67,6 +67,8 @@ export const notificationTypes = [
'followRequestAccepted',
'roleAssigned',
'achievementEarned',
+ 'exportCompleted',
+ 'test',
'app',
] as const;
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index 738cba2134..3989c61776 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -13,7 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
+
@@ -37,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
+
@@ -57,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._notification.roleAssigned }}
{{ i18n.ts._notification.achievementEarned }}
{{ i18n.ts._notification.testNotification }}
+ {{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}
{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}
{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}
@@ -98,6 +102,9 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._achievements._types['_' + notification.achievement].title }}
+
+ {{ i18n.ts.showFile }}
+
{{ i18n.ts.youGotNewFollower }}
@@ -161,6 +168,20 @@ const props = withDefaults(defineProps<{
full: false,
});
+type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
+
+const exportEntityName = {
+ antenna: i18n.ts.antennas,
+ blocking: i18n.ts.blockedUsers,
+ clip: i18n.ts.clips,
+ customEmoji: i18n.ts.customEmojis,
+ favorite: i18n.ts.favorites,
+ following: i18n.ts.following,
+ muting: i18n.ts.mutedUsers,
+ note: i18n.ts.notes,
+ userList: i18n.ts.lists,
+} as const satisfies Record;
+
const followRequestDone = ref(false);
const acceptFollowRequest = () => {
@@ -298,6 +319,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
+.t_exportCompleted {
+ padding: 3px;
+ background: var(--eventOther);
+ pointer-events: none;
+}
+
.t_roleAssigned {
padding: 3px;
background: var(--eventOther);
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index cce671a7cb..53b3bd4936 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -73,7 +73,7 @@ import { notificationTypes } from '@@/js/const.js';
const $i = signinRequired();
-const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned'];
+const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned', 'test', 'exportCompleted'] as const satisfies (typeof notificationTypes[number])[];
const allowButton = shallowRef>();
const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 5d5bc52956..d60ead8294 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -4274,6 +4274,17 @@ export type components = {
type: 'achievementEarned';
/** @enum {string} */
achievement: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead';
+ }) | ({
+ /** Format: id */
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ /** @enum {string} */
+ type: 'exportCompleted';
+ /** @enum {string} */
+ exportedEntity: 'antenna' | 'blocking' | 'clip' | 'customEmoji' | 'favorite' | 'following' | 'muting' | 'note' | 'userList';
+ /** Format: id */
+ fileId: string;
}) | ({
/** Format: id */
id: string;
@@ -18530,8 +18541,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
- includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
- excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+ includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+ excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
};
};
};
@@ -18598,8 +18609,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
- includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
- excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+ includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+ excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
};
};
};
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 3c37657958..2b7dfd4f2d 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -210,6 +210,25 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
tag: `achievement:${data.body.achievement}`,
}];
+ case 'exportCompleted': {
+ const entityName = {
+ antenna: i18n.ts.antennas,
+ blocking: i18n.ts.blockedUsers,
+ clip: i18n.ts.clips,
+ customEmoji: i18n.ts.customEmojis,
+ favorite: i18n.ts.favorites,
+ following: i18n.ts.following,
+ muting: i18n.ts.mutedUsers,
+ note: i18n.ts.notes,
+ userList: i18n.ts.lists,
+ } as const satisfies Record;
+
+ return [i18n.tsx._notification.exportOfXCompleted({ x: entityName[data.body.exportedEntity] }), {
+ badge: iconUrl('circle-check'),
+ data,
+ }];
+ }
+
case 'pollEnded':
return [i18n.ts._notification.pollEnded, {
body: data.body.note.text ?? '',
--
cgit v1.2.3-freya
From 28e9d4e483902771ddd20018f9e48b2cd0ea0673 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Sat, 28 Sep 2024 09:55:21 +0900
Subject: feat: フォローされた際のメッセージを設定できるようにする (#14430)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: フォローされた際のメッセージを設定できるようにする
Resolve #14425
* Update CHANGELOG.md
* 既にフォローしているユーザーのメッセージも見れるように
* Update packages/frontend/src/components/MkNotification.vue
Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
* fix indent
* Update users.ts
* wip
* Update users.ts
---------
Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
---
CHANGELOG.md | 1 +
locales/index.d.ts | 12 ++++++++++
locales/ja-JP.yml | 3 +++
.../migration/1723944246767-followedMessage.js | 16 +++++++++++++
packages/backend/src/core/UserFollowingService.ts | 15 +++++++-----
.../src/core/activitypub/ApRendererService.ts | 1 +
.../backend/src/core/activitypub/misc/contexts.ts | 1 +
.../src/core/activitypub/models/ApPersonService.ts | 12 ++++++----
packages/backend/src/core/activitypub/type.ts | 1 +
.../src/core/entities/NotificationEntityService.ts | 7 ++++--
.../backend/src/core/entities/UserEntityService.ts | 4 +++-
packages/backend/src/models/Notification.ts | 1 +
packages/backend/src/models/User.ts | 1 +
packages/backend/src/models/UserProfile.ts | 8 +++++++
.../backend/src/models/json-schema/notification.ts | 4 ++++
packages/backend/src/models/json-schema/user.ts | 8 +++++++
.../src/server/api/endpoints/admin/show-user.ts | 5 ++++
.../backend/src/server/api/endpoints/i/update.ts | 5 ++--
packages/backend/test/e2e/users.ts | 7 +++++-
packages/frontend-shared/themes/_dark.json5 | 1 +
packages/frontend-shared/themes/_light.json5 | 1 +
.../frontend/src/components/MkFollowButton.vue | 2 +-
packages/frontend/src/components/MkNote.vue | 2 +-
.../frontend/src/components/MkNoteDetailed.vue | 2 +-
.../frontend/src/components/MkNotification.vue | 17 +++++++++++++-
packages/frontend/src/pages/settings/profile.vue | 27 +++++++++++++++-------
packages/frontend/src/pages/user/home.vue | 14 +++++++++++
packages/frontend/src/style.scss | 7 ------
packages/misskey-js/src/autogen/types.ts | 9 ++++++--
29 files changed, 156 insertions(+), 38 deletions(-)
create mode 100644 packages/backend/migration/1723944246767-followedMessage.js
(limited to 'packages/backend/src/models/Notification.ts')
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1d2e950b1..eec73bdaac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
- 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください
- Feat: パスキーでログインボタンを実装 (#14574)
+- Feat: フォローされた際のメッセージを設定できるように
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680)
diff --git a/locales/index.d.ts b/locales/index.d.ts
index a52ee8d808..4510d861aa 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -8725,6 +8725,18 @@ export interface Locale extends ILocale {
* 最大{max}つまでデコレーションを付けられます。
*/
"avatarDecorationMax": ParameterizedString<"max">;
+ /**
+ * フォローされた時のメッセージ
+ */
+ "followedMessage": string;
+ /**
+ * フォローされた時に相手に表示するメッセージを設定できます。
+ */
+ "followedMessageDescription": string;
+ /**
+ * フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。
+ */
+ "followedMessageDescriptionForLockedAccount": string;
};
"_exportOrImport": {
/**
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 75c895a230..a524ce1d3e 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -2297,6 +2297,9 @@ _profile:
changeBanner: "バナー画像を変更"
verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
+ followedMessage: "フォローされた時のメッセージ"
+ followedMessageDescription: "フォローされた時に相手に表示するメッセージを設定できます。"
+ followedMessageDescriptionForLockedAccount: "フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。"
_exportOrImport:
allNotes: "全てのノート"
diff --git a/packages/backend/migration/1723944246767-followedMessage.js b/packages/backend/migration/1723944246767-followedMessage.js
new file mode 100644
index 0000000000..fc9ad1cb85
--- /dev/null
+++ b/packages/backend/migration/1723944246767-followedMessage.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class FollowedMessage1723944246767 {
+ name = 'FollowedMessage1723944246767';
+
+ async up(queryRunner) {
+ await queryRunner.query('ALTER TABLE "user_profile" ADD "followedMessage" character varying(256)');
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query('ALTER TABLE "user_profile" DROP COLUMN "followedMessage"');
+ }
+}
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 3f1c6b7125..77e7b60bea 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -275,16 +275,19 @@ export class UserFollowingService implements OnModuleInit {
followeeId: followee.id,
followerId: follower.id,
});
-
- // 通知を作成
- if (follower.host === null) {
- this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
- }, followee.id);
- }
}
if (alreadyFollowed) return;
+ // 通知を作成
+ if (follower.host === null) {
+ const profile = await this.cacheService.userProfileCache.fetch(followee.id);
+
+ this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
+ message: profile.followedMessage,
+ }, followee.id);
+ }
+
this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
const [followeeUser, followerUser] = await Promise.all([
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 98e944f347..fba8947f03 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -494,6 +494,7 @@ export class ApRendererService {
name: user.name,
summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null,
_misskey_summary: profile.description,
+ _misskey_followedMessage: profile.followedMessage,
icon: avatar ? this.renderImage(avatar) : null,
image: banner ? this.renderImage(banner) : null,
tag,
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index feb8c42c56..3dd85b9b86 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -554,6 +554,7 @@ const extension_context_definition = {
'_misskey_reaction': 'misskey:_misskey_reaction',
'_misskey_votes': 'misskey:_misskey_votes',
'_misskey_summary': 'misskey:_misskey_summary',
+ '_misskey_followedMessage': 'misskey:_misskey_followedMessage',
'isCat': 'misskey:isCat',
// vcard
vcard: 'http://www.w3.org/2006/vcard/ns#',
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 39c18e5e15..e042a85782 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -45,7 +45,7 @@ import type { ApNoteService } from './ApNoteService.js';
import type { ApMfmService } from '../ApMfmService.js';
import type { ApResolverService, Resolver } from '../ApResolverService.js';
import type { ApLoggerService } from '../ApLoggerService.js';
-// eslint-disable-next-line @typescript-eslint/consistent-type-imports
+
import type { ApImageService } from './ApImageService.js';
import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
@@ -307,8 +307,8 @@ export class ApPersonService implements OnModuleInit {
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
}
return 'private';
- })
- )
+ }),
+ ),
);
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -370,6 +370,7 @@ export class ApPersonService implements OnModuleInit {
await transactionalEntityManager.save(new MiUserProfile({
userId: user.id,
description: _description,
+ followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
url,
fields,
followingVisibility,
@@ -494,8 +495,8 @@ export class ApPersonService implements OnModuleInit {
return undefined;
}
return 'private';
- })
- )
+ }),
+ ),
);
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -566,6 +567,7 @@ export class ApPersonService implements OnModuleInit {
url,
fields,
description: _description,
+ followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
followingVisibility,
followersVisibility,
birthday: bday?.[0] ?? null,
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 16812b7a4d..154965b9d5 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -13,6 +13,7 @@ export interface IObject {
name?: string | null;
summary?: string;
_misskey_summary?: string;
+ _misskey_followedMessage?: string | null;
published?: string;
cc?: ApObject;
to?: ApObject;
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 1b61a6195d..dff6968f9c 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -59,7 +59,7 @@ export class NotificationEntityService implements OnModuleInit {
async #packInternal (
src: T,
meId: MiUser['id'],
- // eslint-disable-next-line @typescript-eslint/ban-types
+
options: {
checkValidNotifier?: boolean;
},
@@ -159,6 +159,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'roleAssigned' ? {
role: role,
} : {}),
+ ...(notification.type === 'followRequestAccepted' ? {
+ message: notification.message,
+ } : {}),
...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement,
} : {}),
@@ -233,7 +236,7 @@ export class NotificationEntityService implements OnModuleInit {
public async pack(
src: MiNotification | MiGroupedNotification,
meId: MiUser['id'],
- // eslint-disable-next-line @typescript-eslint/ban-types
+
options: {
checkValidNotifier?: boolean;
},
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 9bf568bc90..69e2d6fc89 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -508,7 +508,7 @@ export class UserEntityService implements OnModuleInit {
name: r.name,
iconUrl: r.iconUrl,
displayOrder: r.displayOrder,
- }))
+ })),
) : undefined,
...(isDetailed ? {
@@ -567,6 +567,7 @@ export class UserEntityService implements OnModuleInit {
...(isDetailed && isMe ? {
avatarId: user.avatarId,
bannerId: user.bannerId,
+ followedMessage: profile!.followedMessage,
isModerator: isModerator,
isAdmin: isAdmin,
injectFeaturedNote: profile!.injectFeaturedNote,
@@ -635,6 +636,7 @@ export class UserEntityService implements OnModuleInit {
isRenoteMuted: relation.isRenoteMuted,
notify: relation.following?.notify ?? 'none',
withReplies: relation.following?.withReplies ?? false,
+ followedMessage: relation.isFollowing ? profile!.followedMessage : undefined,
} : {}),
} as Promiseable>;
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 2c5b75f577..c1d3d42134 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -69,6 +69,7 @@ export type MiNotification = {
id: string;
createdAt: string;
notifierId: MiUser['id'];
+ message: string | null;
} | {
type: 'roleAssigned';
id: string;
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index 9e2d7a3444..21362235ab 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -289,5 +289,6 @@ export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toStr
export const passwordSchema = { type: 'string', minLength: 1 } as const;
export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 1500 } as const;
+export const followedMessageSchema = { type: 'string', minLength: 1, maxLength: 256 } as const;
export const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
export const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts
index 7dbe0b3717..5544555296 100644
--- a/packages/backend/src/models/UserProfile.ts
+++ b/packages/backend/src/models/UserProfile.ts
@@ -42,6 +42,14 @@ export class MiUserProfile {
})
public description: string | null;
+ // フォローされた際のメッセージ
+ @Column('varchar', {
+ length: 256, nullable: true,
+ })
+ public followedMessage: string | null;
+
+ // TODO: 鍵アカウントの場合の、フォローリクエスト受信時のメッセージも設定できるようにする
+
@Column('jsonb', {
default: [],
})
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index bbec2e397f..2645010491 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -267,6 +267,10 @@ export const packedNotificationSchema = {
optional: false, nullable: false,
format: 'id',
},
+ message: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
},
}, {
type: 'object',
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 947a9317d7..16c8a5a097 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -370,6 +370,10 @@ export const packedUserDetailedNotMeOnlySchema = {
ref: 'RoleLite',
},
},
+ followedMessage: {
+ type: 'string',
+ nullable: true, optional: true,
+ },
memo: {
type: 'string',
nullable: true, optional: false,
@@ -437,6 +441,10 @@ export const packedMeDetailedOnlySchema = {
nullable: true, optional: false,
format: 'id',
},
+ followedMessage: {
+ type: 'string',
+ nullable: true, optional: false,
+ },
isModerator: {
type: 'boolean',
nullable: true, optional: false,
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index 5a1c05f41a..655bd32bce 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -31,6 +31,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ followedMessage: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
autoAcceptFollowed: {
type: 'boolean',
optional: false, nullable: false,
@@ -226,6 +230,7 @@ export default class extends Endpoint { // eslint-
return {
email: profile.email,
emailVerified: profile.emailVerified,
+ followedMessage: profile.followedMessage,
autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle,
preventAiLearning: profile.preventAiLearning,
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index a1e2fa5e4c..798bd98cf1 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -13,9 +13,8 @@ import { extractHashtags } from '@/misc/extract-hashtags.js';
import * as Acct from '@/misc/acct.js';
import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
-import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js';
+import { birthdaySchema, descriptionSchema, followedMessageSchema, locationSchema, nameSchema } from '@/models/User.js';
import type { MiUserProfile } from '@/models/UserProfile.js';
-import { notificationTypes } from '@/types.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { langmap } from '@/misc/langmap.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -134,6 +133,7 @@ export const paramDef = {
properties: {
name: { ...nameSchema, nullable: true },
description: { ...descriptionSchema, nullable: true },
+ followedMessage: { ...followedMessageSchema, nullable: true },
location: { ...locationSchema, nullable: true },
birthday: { ...birthdaySchema, nullable: true },
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
@@ -267,6 +267,7 @@ export default class extends Endpoint { // eslint-
}
}
if (ps.description !== undefined) profileUpdates.description = ps.description;
+ if (ps.followedMessage !== undefined) profileUpdates.followedMessage = ps.followedMessage;
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 61fd759932..8ebe9af792 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -7,9 +7,9 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { inspect } from 'node:util';
-import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { api, post, role, signup, successfulApiCall, uploadFile } from '../utils.js';
import type * as misskey from 'misskey-js';
+import { DEFAULT_POLICIES } from '@/core/RoleService.js';
describe('ユーザー', () => {
// エンティティとしてのユーザーを主眼においたテストを記述する
@@ -105,6 +105,7 @@ describe('ユーザー', () => {
isRenoteMuted: user.isRenoteMuted ?? false,
notify: user.notify ?? 'none',
withReplies: user.withReplies ?? false,
+ followedMessage: user.isFollowing ? (user.followedMessage ?? null) : undefined,
});
};
@@ -114,6 +115,7 @@ describe('ユーザー', () => {
...userDetailedNotMe(user),
avatarId: user.avatarId,
bannerId: user.bannerId,
+ followedMessage: user.followedMessage,
isModerator: user.isModerator,
isAdmin: user.isAdmin,
injectFeaturedNote: user.injectFeaturedNote,
@@ -350,6 +352,7 @@ describe('ユーザー', () => {
// MeDetailedOnly
assert.strictEqual(response.avatarId, null);
assert.strictEqual(response.bannerId, null);
+ assert.strictEqual(response.followedMessage, null);
assert.strictEqual(response.isModerator, false);
assert.strictEqual(response.isAdmin, false);
assert.strictEqual(response.injectFeaturedNote, true);
@@ -413,6 +416,8 @@ describe('ユーザー', () => {
{ parameters: () => ({ description: 'x'.repeat(1500) }) },
{ parameters: () => ({ description: 'x' }) },
{ parameters: () => ({ description: 'My description' }) },
+ { parameters: () => ({ followedMessage: null }) },
+ { parameters: () => ({ followedMessage: 'Thank you' }) },
{ parameters: () => ({ location: null }) },
{ parameters: () => ({ location: 'x'.repeat(50) }) },
{ parameters: () => ({ location: 'x' }) },
diff --git a/packages/frontend-shared/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5
index 23a9549a93..eb5fda3dc0 100644
--- a/packages/frontend-shared/themes/_dark.json5
+++ b/packages/frontend-shared/themes/_dark.json5
@@ -13,6 +13,7 @@
accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',
+ love: '#dd2e44',
focus: ':alpha<0.3<@accent',
bg: '#000',
acrylicBg: ':alpha<0.5<@bg',
diff --git a/packages/frontend-shared/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5
index 96b2ddd06c..e0196dcbf3 100644
--- a/packages/frontend-shared/themes/_light.json5
+++ b/packages/frontend-shared/themes/_light.json5
@@ -13,6 +13,7 @@
accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',
+ love: '#dd2e44',
focus: ':alpha<0.3<@accent',
bg: '#fff',
acrylicBg: ':alpha<0.5<@bg',
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index 370d5f75c5..0de52906ed 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.processing }}
- {{ i18n.ts.unfollow }}
+ {{ i18n.ts.youFollowing }}
{{ i18n.ts.followRequest }}
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index b6bab27820..0fdb938e25 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -119,7 +119,7 @@ SPDX-License-Identifier: AGPL-3.0-only
+
@@ -460,6 +465,11 @@ onUnmounted(() => {
box-shadow: 1px 1px 3px rgba(#000, 0.2);
}
+ > .followedMessage {
+ padding: 24px 24px 0 154px;
+ font-size: 0.9em;
+ }
+
> .roles {
padding: 24px 24px 0 154px;
font-size: 0.95em;
@@ -642,6 +652,10 @@ onUnmounted(() => {
margin: auto;
}
+ > .followedMessage {
+ padding: 16px 16px 0 16px;
+ }
+
> .roles {
padding: 16px 16px 0 16px;
justify-content: center;
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index 28a16fd6d1..b835096b15 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -18,13 +18,6 @@
--minBottomSpacing: var(--minBottomSpacingMobile);
//--ad: rgb(255 169 0 / 10%);
- --eventFollow: #36aed2;
- --eventRenote: #36d298;
- --eventReply: #007aff;
- --eventReactionHeart: #dd2e44;
- --eventReaction: #e99a0b;
- --eventAchievement: #cb9a11;
- --eventOther: #88a6b7;
@media (max-width: 500px) {
--margin: var(--marginHalf);
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index d60ead8294..0dff85183f 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3789,6 +3789,7 @@ export type components = {
/** @default false */
securityKeys: boolean;
roles: components['schemas']['RoleLite'][];
+ followedMessage?: string | null;
memo: string | null;
moderationNote?: string;
isFollowing?: boolean;
@@ -3808,6 +3809,7 @@ export type components = {
avatarId: string | null;
/** Format: id */
bannerId: string | null;
+ followedMessage: string | null;
isModerator: boolean | null;
isAdmin: boolean | null;
injectFeaturedNote: boolean;
@@ -4247,7 +4249,7 @@ export type components = {
user: components['schemas']['UserLite'];
/** Format: id */
userId: string;
- } | {
+ } | ({
/** Format: id */
id: string;
/** Format: date-time */
@@ -4257,7 +4259,8 @@ export type components = {
user: components['schemas']['UserLite'];
/** Format: id */
userId: string;
- } | {
+ message: string | null;
+ }) | {
/** Format: id */
id: string;
/** Format: date-time */
@@ -8935,6 +8938,7 @@ export type operations = {
'application/json': {
email: string | null;
emailVerified: boolean;
+ followedMessage: string | null;
autoAcceptFollowed: boolean;
noCrawle: boolean;
preventAiLearning: boolean;
@@ -19663,6 +19667,7 @@ export type operations = {
'application/json': {
name?: string | null;
description?: string | null;
+ followedMessage?: string | null;
location?: string | null;
birthday?: string | null;
/** @enum {string|null} */
--
cgit v1.2.3-freya