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