2023-07-27 07:31:52 +02:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-09-17 20:27:08 +02:00
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
|
|
|
import { ModuleRef } from '@nestjs/core';
|
2023-04-04 07:06:57 +02:00
|
|
|
import { In } from 'typeorm';
|
2022-09-17 20:27:08 +02:00
|
|
|
import { DI } from '@/di-symbols.js';
|
2023-09-15 07:28:29 +02:00
|
|
|
import type { AccessTokensRepository, FollowRequestsRepository, NotesRepository, MiUser, UsersRepository } from '@/models/_.js';
|
2022-09-17 20:27:08 +02:00
|
|
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
2023-08-16 10:51:28 +02:00
|
|
|
import type { MiNotification } from '@/models/entities/Notification.js';
|
|
|
|
import type { MiNote } from '@/models/entities/Note.js';
|
2023-03-10 06:22:37 +01:00
|
|
|
import type { Packed } from '@/misc/json-schema.js';
|
2022-12-31 00:43:13 +01:00
|
|
|
import { bindThis } from '@/decorators.js';
|
2023-03-03 12:43:31 +01:00
|
|
|
import { isNotNull } from '@/misc/is-not-null.js';
|
|
|
|
import { notificationTypes } from '@/types.js';
|
2022-09-17 20:27:08 +02:00
|
|
|
import type { OnModuleInit } from '@nestjs/common';
|
|
|
|
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
|
|
|
import type { UserEntityService } from './UserEntityService.js';
|
|
|
|
import type { NoteEntityService } from './NoteEntityService.js';
|
|
|
|
|
2023-03-03 12:43:31 +01:00
|
|
|
const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]);
|
|
|
|
|
2022-09-17 20:27:08 +02:00
|
|
|
@Injectable()
|
|
|
|
export class NotificationEntityService implements OnModuleInit {
|
|
|
|
private userEntityService: UserEntityService;
|
|
|
|
private noteEntityService: NoteEntityService;
|
|
|
|
private customEmojiService: CustomEmojiService;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
private moduleRef: ModuleRef,
|
|
|
|
|
2023-04-04 07:06:57 +02:00
|
|
|
@Inject(DI.notesRepository)
|
|
|
|
private notesRepository: NotesRepository,
|
|
|
|
|
|
|
|
@Inject(DI.usersRepository)
|
|
|
|
private usersRepository: UsersRepository,
|
2022-09-17 20:27:08 +02:00
|
|
|
|
2023-05-02 05:14:06 +02:00
|
|
|
@Inject(DI.followRequestsRepository)
|
|
|
|
private followRequestsRepository: FollowRequestsRepository,
|
|
|
|
|
2022-09-17 20:27:08 +02:00
|
|
|
@Inject(DI.accessTokensRepository)
|
|
|
|
private accessTokensRepository: AccessTokensRepository,
|
|
|
|
|
|
|
|
//private userEntityService: UserEntityService,
|
|
|
|
//private noteEntityService: NoteEntityService,
|
|
|
|
//private customEmojiService: CustomEmojiService,
|
|
|
|
) {
|
|
|
|
}
|
|
|
|
|
|
|
|
onModuleInit() {
|
|
|
|
this.userEntityService = this.moduleRef.get('UserEntityService');
|
|
|
|
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
|
|
|
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
|
|
|
}
|
|
|
|
|
2022-12-04 07:03:09 +01:00
|
|
|
@bindThis
|
2022-09-17 20:27:08 +02:00
|
|
|
public async pack(
|
2023-08-16 10:51:28 +02:00
|
|
|
src: MiNotification,
|
|
|
|
meId: MiUser['id'],
|
2023-04-04 10:32:09 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
2022-09-17 20:27:08 +02:00
|
|
|
options: {
|
2023-07-08 00:08:16 +02:00
|
|
|
|
2023-04-04 07:06:57 +02:00
|
|
|
},
|
|
|
|
hint?: {
|
2023-08-16 10:51:28 +02:00
|
|
|
packedNotes: Map<MiNote['id'], Packed<'Note'>>;
|
|
|
|
packedUsers: Map<MiUser['id'], Packed<'User'>>;
|
2022-09-17 20:27:08 +02:00
|
|
|
},
|
|
|
|
): Promise<Packed<'Notification'>> {
|
2023-04-04 07:06:57 +02:00
|
|
|
const notification = src;
|
2022-09-17 20:27:08 +02:00
|
|
|
const token = notification.appAccessTokenId ? await this.accessTokensRepository.findOneByOrFail({ id: notification.appAccessTokenId }) : null;
|
2023-03-03 12:43:31 +01:00
|
|
|
const noteIfNeed = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && notification.noteId != null ? (
|
2023-04-04 07:06:57 +02:00
|
|
|
hint?.packedNotes != null
|
|
|
|
? hint.packedNotes.get(notification.noteId)
|
|
|
|
: this.noteEntityService.pack(notification.noteId!, { id: meId }, {
|
2023-03-03 12:43:31 +01:00
|
|
|
detail: true,
|
|
|
|
})
|
|
|
|
) : undefined;
|
2023-04-04 07:06:57 +02:00
|
|
|
const userIfNeed = notification.notifierId != null ? (
|
|
|
|
hint?.packedUsers != null
|
|
|
|
? hint.packedUsers.get(notification.notifierId)
|
|
|
|
: this.userEntityService.pack(notification.notifierId!, { id: meId }, {
|
|
|
|
detail: false,
|
|
|
|
})
|
|
|
|
) : undefined;
|
2022-09-17 20:27:08 +02:00
|
|
|
|
|
|
|
return await awaitAll({
|
|
|
|
id: notification.id,
|
2023-04-04 07:06:57 +02:00
|
|
|
createdAt: new Date(notification.createdAt).toISOString(),
|
2022-09-17 20:27:08 +02:00
|
|
|
type: notification.type,
|
|
|
|
userId: notification.notifierId,
|
2023-04-04 07:06:57 +02:00
|
|
|
...(userIfNeed != null ? { user: userIfNeed } : {}),
|
2023-03-03 12:43:31 +01:00
|
|
|
...(noteIfNeed != null ? { note: noteIfNeed } : {}),
|
2022-09-17 20:27:08 +02:00
|
|
|
...(notification.type === 'reaction' ? {
|
|
|
|
reaction: notification.reaction,
|
|
|
|
} : {}),
|
2023-01-21 05:14:55 +01:00
|
|
|
...(notification.type === 'achievementEarned' ? {
|
|
|
|
achievement: notification.achievement,
|
|
|
|
} : {}),
|
2022-09-17 20:27:08 +02:00
|
|
|
...(notification.type === 'app' ? {
|
|
|
|
body: notification.customBody,
|
|
|
|
header: notification.customHeader ?? token?.name,
|
|
|
|
icon: notification.customIcon ?? token?.iconUrl,
|
|
|
|
} : {}),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-12-04 07:03:09 +01:00
|
|
|
@bindThis
|
2022-09-17 20:27:08 +02:00
|
|
|
public async packMany(
|
2023-08-16 10:51:28 +02:00
|
|
|
notifications: MiNotification[],
|
|
|
|
meId: MiUser['id'],
|
2022-09-17 20:27:08 +02:00
|
|
|
) {
|
|
|
|
if (notifications.length === 0) return [];
|
2023-03-03 12:26:44 +01:00
|
|
|
|
2023-04-06 08:09:21 +02:00
|
|
|
let validNotifications = notifications;
|
|
|
|
|
|
|
|
const noteIds = validNotifications.map(x => x.noteId).filter(isNotNull);
|
2023-04-04 07:06:57 +02:00
|
|
|
const notes = noteIds.length > 0 ? await this.notesRepository.find({
|
|
|
|
where: { id: In(noteIds) },
|
2023-04-06 12:48:24 +02:00
|
|
|
relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'],
|
2023-04-04 07:06:57 +02:00
|
|
|
}) : [];
|
2023-03-03 12:43:31 +01:00
|
|
|
const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, {
|
|
|
|
detail: true,
|
|
|
|
});
|
|
|
|
const packedNotes = new Map(packedNotesArray.map(p => [p.id, p]));
|
2023-01-26 07:48:12 +01:00
|
|
|
|
2023-04-06 08:09:21 +02:00
|
|
|
validNotifications = validNotifications.filter(x => x.noteId == null || packedNotes.has(x.noteId));
|
|
|
|
|
|
|
|
const userIds = validNotifications.map(x => x.notifierId).filter(isNotNull);
|
2023-04-04 07:06:57 +02:00
|
|
|
const users = userIds.length > 0 ? await this.usersRepository.find({
|
|
|
|
where: { id: In(userIds) },
|
|
|
|
}) : [];
|
|
|
|
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
|
|
|
|
detail: false,
|
|
|
|
});
|
|
|
|
const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
|
|
|
|
|
2023-05-02 05:14:06 +02:00
|
|
|
// 既に解決されたフォローリクエストの通知を除外
|
|
|
|
const followRequestNotifications = validNotifications.filter(x => x.type === 'receiveFollowRequest');
|
|
|
|
if (followRequestNotifications.length > 0) {
|
|
|
|
const reqs = await this.followRequestsRepository.find({
|
|
|
|
where: { followerId: In(followRequestNotifications.map(x => x.notifierId!)) },
|
|
|
|
});
|
|
|
|
validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
|
|
|
|
}
|
|
|
|
|
2023-04-06 08:09:21 +02:00
|
|
|
return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, {
|
2023-04-04 07:06:57 +02:00
|
|
|
packedNotes,
|
|
|
|
packedUsers,
|
2022-09-17 20:27:08 +02:00
|
|
|
})));
|
|
|
|
}
|
|
|
|
}
|