merge: Add importCompleted notification. Send importCompleted when antenna/customEmoji/muting/userList is imported (!1165)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1165

Closes #891

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Hazelnoot <acomputerdog@gmail.com>
This commit is contained in:
Hazelnoot 2025-07-21 18:42:29 +00:00
commit ed68230811
19 changed files with 163 additions and 39 deletions

4
locales/index.d.ts vendored
View file

@ -10378,6 +10378,10 @@ export interface Locale extends ILocale {
* Scheduled Note was posted
*/
"scheduledNotePosted": string;
/**
* Import of {x} has been completed
*/
"importOfXCompleted": ParameterizedString<"x">;
};
"_deck": {
/**

View file

@ -187,6 +187,10 @@ export class NotificationEntityService implements OnModuleInit {
exportedEntity: notification.exportedEntity,
fileId: notification.fileId,
} : {}),
...(notification.type === 'importCompleted' ? {
importedEntity: notification.importedEntity,
fileId: notification.fileId,
} : {}),
...(notification.type === 'scheduledNoteFailed' ? {
reason: notification.reason,
} : {}),

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { userExportableEntities } from '@/types.js';
import { userExportableEntities, userImportableEntities } from '@/types.js';
import { MiUser } from './User.js';
import { MiNote } from './Note.js';
import { MiAccessToken } from './AccessToken.js';
@ -92,6 +92,12 @@ export type MiNotification = {
createdAt: string;
exportedEntity: typeof userExportableEntities[number];
fileId: MiDriveFile['id'];
} | {
type: 'importCompleted';
id: string;
createdAt: string;
importedEntity: typeof userImportableEntities[number];
fileId?: MiDriveFile['id'];
} | {
type: 'login';
id: string;

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { notificationTypes, userExportableEntities } from '@/types.js';
import { notificationTypes, userExportableEntities, userImportableEntities } from '@/types.js';
const baseSchema = {
type: 'object',
@ -334,6 +334,26 @@ export const packedNotificationSchema = {
format: 'id',
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['importCompleted'],
},
importedEntity: {
type: 'string',
optional: false, nullable: false,
enum: userImportableEntities,
},
fileId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
}, {
type: 'object',
properties: {

View file

@ -12,6 +12,7 @@ import type { AntennasRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { DBAntennaImportJobData } from '../types.js';
import type * as Bull from 'bullmq';
@ -65,6 +66,7 @@ export class ImportAntennasProcessorService {
private queueLoggerService: QueueLoggerService,
private idService: IdService,
private globalEventService: GlobalEventService,
private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('import-antennas');
}
@ -106,6 +108,10 @@ export class ImportAntennasProcessorService {
this.logger.debug('Antenna created: ' + result.id);
this.globalEventService.publishInternalEvent('antennaCreated', result);
}
this.notificationService.createNotification(job.data.user.id, 'importCompleted', {
importedEntity: 'antenna',
});
} catch (err: any) {
this.logger.error('Error importing antennas:', err);
}

View file

@ -18,6 +18,7 @@ import { bindThis } from '@/decorators.js';
import type { Config } from '@/config.js';
import { renderInlineError } from '@/misc/render-inline-error.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import { NotificationService } from '@/core/NotificationService.js';
import type * as Bull from 'bullmq';
import type { DbUserImportJobData } from '../types.js';
@ -40,6 +41,7 @@ export class ImportCustomEmojisProcessorService {
private driveService: DriveService,
private downloadService: DownloadService,
private queueLoggerService: QueueLoggerService,
private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis');
}
@ -127,7 +129,12 @@ export class ImportCustomEmojisProcessorService {
cleanup();
this.logger.debug('Imported');
this.notificationService.createNotification(job.data.user.id, 'importCompleted', {
importedEntity: 'customEmoji',
fileId: file.id,
});
this.logger.debug('Imported', file.name);
} catch (e) {
this.logger.error('Error importing custom emojis:', e as Error);
cleanup();

View file

@ -16,6 +16,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { renderInlineError } from '@/misc/render-inline-error.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import { NotificationService } from '@/core/NotificationService.js';
import type * as Bull from 'bullmq';
import type { DbUserImportJobData } from '../types.js';
@ -35,6 +36,7 @@ export class ImportMutingProcessorService {
private remoteUserResolveService: RemoteUserResolveService,
private downloadService: DownloadService,
private queueLoggerService: QueueLoggerService,
private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('import-muting');
}
@ -99,6 +101,11 @@ export class ImportMutingProcessorService {
}
}
this.notificationService.createNotification(job.data.user.id, 'importCompleted', {
importedEntity: 'muting',
fileId: file.id,
});
this.logger.debug('Imported');
}
}

View file

@ -17,6 +17,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { renderInlineError } from '@/misc/render-inline-error.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import { NotificationService } from '@/core/NotificationService.js';
import type * as Bull from 'bullmq';
import type { DbUserImportJobData } from '../types.js';
@ -43,6 +44,7 @@ export class ImportUserListsProcessorService {
private remoteUserResolveService: RemoteUserResolveService,
private downloadService: DownloadService,
private queueLoggerService: QueueLoggerService,
private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('import-user-lists');
}
@ -109,6 +111,11 @@ export class ImportUserListsProcessorService {
}
}
this.notificationService.createNotification(job.data.user.id, 'importCompleted', {
importedEntity: 'userList',
fileId: file.id,
});
this.logger.debug('Imported');
}
}

View file

@ -39,6 +39,7 @@ export const notificationTypes = [
'chatRoomInvitationReceived',
'achievementEarned',
'exportCompleted',
'importCompleted',
'login',
'createToken',
'scheduledNoteFailed',

View file

@ -129,6 +129,7 @@ export const notificationTypes = [
'chatRoomInvitationReceived',
'achievementEarned',
'exportCompleted',
'importCompleted',
'login',
'createToken',
'test',

View file

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root">
<div :class="$style.head">
<MkAvatar v-if="['pollEnded', 'note', 'edited', 'scheduledNotePosted'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'createToken', 'scheduledNoteFailed'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'importCompleted', 'login', 'createToken', 'scheduledNoteFailed'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
@ -25,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_pollEnded]: notification.type === 'pollEnded',
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_importCompleted]: notification.type === 'importCompleted',
[$style.t_login]: notification.type === 'login',
[$style.t_createToken]: notification.type === 'createToken',
[$style.t_chatRoomInvitationReceived]: notification.type === 'chatRoomInvitationReceived',
@ -44,6 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
<i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i>
<i v-else-if="notification.type === 'importCompleted'" class="ti ti-archive"></i>
<i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i>
<i v-else-if="notification.type === 'createToken'" class="ti ti-key"></i>
<i v-else-if="notification.type === 'chatRoomInvitationReceived'" class="ti ti-messages"></i>
@ -75,6 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'createToken'">{{ i18n.ts._notification.createToken }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
<span v-else-if="notification.type === 'importCompleted'">{{ i18n.tsx._notification.importOfXCompleted({ x: importEntityName[notification.importedEntity] }) }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
@ -122,7 +125,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
{{ i18n.ts._achievements._types['_' + notification.achievement].title }}
</MkA>
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
<MkA v-else-if="notification.type === 'exportCompleted' || (notification.type === 'importCompleted' && notification.fileId)" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
{{ i18n.ts.showFile }}
</MkA>
<MkA v-else-if="notification.type === 'createToken'" :class="$style.text" to="/settings/apps">
@ -219,18 +222,10 @@ const props = withDefaults(defineProps<{
});
type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
type ImportCompletedNotification = Misskey.entities.Notification & { type: 'importCompleted' };
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<ExportCompletedNotification['exportedEntity'], string>;
const exportEntityName = Misskey.entities.exportEntityName(i18n);
const importEntityName = Misskey.entities.importEntityName(i18n);
const followRequestDone = ref(true);
const userDetailed: Ref<Misskey.entities.UserDetailed | null> = ref(null);
@ -398,7 +393,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
.t_exportCompleted {
.t_exportCompleted, .t_importCompleted {
background: var(--eventOther);
pointer-events: none;
}

View file

@ -85,7 +85,7 @@ import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
const $i = ensureSignin();
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] satisfies (typeof notificationTypes[number])[] as string[];
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted', 'importCompleted'] satisfies (typeof notificationTypes[number])[] as string[];
const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'createToken', 'scheduledNoteFailed', 'scheduledNotePosted'] satisfies (typeof notificationTypes[number])[] as string[];

View file

@ -164,6 +164,9 @@ type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBo
// @public (undocumented)
type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminDriveCleanRemoteFilesRequest = operations['admin___drive___clean-remote-files']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json'];
@ -1498,6 +1501,8 @@ declare namespace entities {
SigninWithPasskeyInitResponse,
SigninWithPasskeyResponse,
PartialRolePolicyOverride,
exportEntityName,
importEntityName,
EmptyRequest,
EmptyResponse,
AdminAbuseReportNotificationRecipientCreateRequest,
@ -1541,6 +1546,7 @@ declare namespace entities {
AdminDeclineUserRequest,
AdminDeleteAccountRequest,
AdminDeleteAllFilesOfAUserRequest,
AdminDriveCleanRemoteFilesRequest,
AdminDriveFilesRequest,
AdminDriveFilesResponse,
AdminDriveShowFileRequest,
@ -2230,6 +2236,19 @@ export { entities }
// @public (undocumented)
type Error_2 = components['schemas']['Error'];
// @public (undocumented)
const exportEntityName: (i18n: any) => {
readonly antenna: any;
readonly blocking: any;
readonly clip: any;
readonly customEmoji: any;
readonly favorite: any;
readonly following: any;
readonly muting: any;
readonly note: any;
readonly userList: any;
};
// @public (undocumented)
type FederationFollowersRequest = operations['federation___followers']['requestBody']['content']['application/json'];
@ -2607,6 +2626,19 @@ type IMoveRequest = operations['i___move']['requestBody']['content']['applicatio
// @public (undocumented)
type IMoveResponse = operations['i___move']['responses']['200']['content']['application/json'];
// @public (undocumented)
const importEntityName: (i18n: any) => {
readonly antenna: any;
readonly blocking: any;
readonly clip: any;
readonly customEmoji: any;
readonly favorite: any;
readonly following: any;
readonly muting: any;
readonly note: any;
readonly userList: any;
};
// @public (undocumented)
type INotificationsGroupedRequest = operations['i___notifications-grouped']['requestBody']['content']['application/json'];
@ -3919,7 +3951,7 @@ type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['respons
// Warnings were encountered during analysis:
//
// src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/entities.ts:51:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:234:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:244:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts

View file

@ -42,6 +42,7 @@ import type {
AdminDeclineUserRequest,
AdminDeleteAccountRequest,
AdminDeleteAllFilesOfAUserRequest,
AdminDriveCleanRemoteFilesRequest,
AdminDriveFilesRequest,
AdminDriveFilesResponse,
AdminDriveShowFileRequest,
@ -691,7 +692,7 @@ export type Endpoints = {
'admin/decline-user': { req: AdminDeclineUserRequest; res: EmptyResponse };
'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse };
'admin/delete-all-files-of-a-user': { req: AdminDeleteAllFilesOfAUserRequest; res: EmptyResponse };
'admin/drive/clean-remote-files': { req: EmptyRequest; res: EmptyResponse };
'admin/drive/clean-remote-files': { req: AdminDriveCleanRemoteFilesRequest; res: EmptyResponse };
'admin/drive/cleanup': { req: EmptyRequest; res: EmptyResponse };
'admin/drive/files': { req: AdminDriveFilesRequest; res: AdminDriveFilesResponse };
'admin/drive/show-file': { req: AdminDriveShowFileRequest; res: AdminDriveShowFileResponse };

View file

@ -45,6 +45,7 @@ export type AdminCwUserRequest = operations['admin___cw-user']['requestBody']['c
export type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json'];
export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json'];
export type AdminDriveCleanRemoteFilesRequest = operations['admin___drive___clean-remote-files']['requestBody']['content']['application/json'];
export type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json'];
export type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json'];
export type AdminDriveShowFileRequest = operations['admin___drive___show-file']['requestBody']['content']['application/json'];

View file

@ -4912,6 +4912,17 @@ export type components = {
exportedEntity: 'antenna' | 'blocking' | 'clip' | 'customEmoji' | 'favorite' | 'following' | 'muting' | 'note' | 'userList';
/** Format: id */
fileId: string;
}) | ({
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** @enum {string} */
type: 'importCompleted';
/** @enum {string} */
importedEntity: 'antenna' | 'blocking' | 'customEmoji' | 'following' | 'muting' | 'userList';
/** Format: id */
fileId: string;
}) | {
/** Format: id */
id: string;
@ -7583,6 +7594,14 @@ export type operations = {
* **Credential required**: *Yes* / **Permission**: *write:admin:drive*
*/
'admin___drive___clean-remote-files': {
requestBody: {
content: {
'application/json': {
olderThanSeconds?: number;
keepFilesInUse?: boolean;
};
};
};
responses: {
/** @description OK (without any results) */
204: {
@ -23881,8 +23900,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'importCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'importCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
};
};
};
@ -23949,8 +23968,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'importCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'importCompleted' | 'login' | 'createToken' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
};
};
};

View file

@ -4,6 +4,7 @@ import {
EmojiDetailed,
MeDetailed,
Note,
Notification,
Page,
Role,
RolePolicies,
@ -395,3 +396,20 @@ export type SigninWithPasskeyResponse = {
type Values<T extends Record<PropertyKey, unknown>> = T[keyof T];
export type PartialRolePolicyOverride = Partial<{ [k in keyof RolePolicies]: Omit<Values<Role['policies']>, 'value'> & { value: RolePolicies[k] } }>;
type ExportCompletedNotification = Notification & { type: 'exportCompleted' };
type ImportCompletedNotification = Notification & { type: 'importCompleted' };
export const exportEntityName = (i18n: any) => ({
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<ExportCompletedNotification['exportedEntity'], string>);
export const importEntityName = (i18n: any) => (exportEntityName(i18n) satisfies Record<ImportCompletedNotification['importedEntity'], string>);

View file

@ -6,6 +6,7 @@
/*
* Notification manager for SW
*/
import * as Misskey from 'misskey-js';
import type { BadgeNames, PushNotificationDataMap } from '@/types.js';
import { char2fileName } from '@/scripts/twemoji-base.js';
import { cli } from '@/scripts/operations.js';
@ -216,24 +217,17 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
data,
}];
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<typeof data.body.exportedEntity, string>;
return [i18n.tsx._notification.exportOfXCompleted({ x: entityName[data.body.exportedEntity] }), {
case 'exportCompleted':
return [i18n.tsx._notification.exportOfXCompleted({ x: Misskey.entities.exportEntityName(i18n)[data.body.exportedEntity] }), {
badge: iconUrl('circle-check'),
data,
}];
case 'importCompleted':
return [i18n.tsx._notification.importOfXCompleted({ x: Misskey.entities.importEntityName(i18n)[data.body.importedEntity] }), {
badge: iconUrl('circle-check'),
data,
}];
}
case 'pollEnded':
return [i18n.ts._notification.pollEnded, {

View file

@ -314,6 +314,7 @@ _notification:
edited: "Note got edited"
scheduledNoteFailed: "Posting scheduled note failed"
scheduledNotePosted: "Scheduled Note was posted"
importOfXCompleted: "Import of {x} has been completed"
_types:
renote: "Boosts"
edited: "Edits"