2023-03-03 02:13:12 +00:00
|
|
|
import { setTimeout } from 'node:timers/promises';
|
|
|
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
2023-04-04 22:52:49 +00:00
|
|
|
import { In } from 'typeorm';
|
2022-09-17 18:27:08 +00:00
|
|
|
import { DI } from '@/di-symbols.js';
|
|
|
|
import type { User } from '@/models/entities/User.js';
|
2023-03-10 05:22:37 +00:00
|
|
|
import type { Packed } from '@/misc/json-schema.js';
|
2022-09-17 18:27:08 +00:00
|
|
|
import type { Note } from '@/models/entities/Note.js';
|
|
|
|
import { IdService } from '@/core/IdService.js';
|
|
|
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
2023-04-04 22:52:49 +00:00
|
|
|
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/index.js';
|
2023-02-01 08:29:28 +00:00
|
|
|
import { bindThis } from '@/decorators.js';
|
2022-09-17 18:27:08 +00:00
|
|
|
|
|
|
|
@Injectable()
|
2023-03-03 02:13:12 +00:00
|
|
|
export class NoteReadService implements OnApplicationShutdown {
|
|
|
|
#shutdownController = new AbortController();
|
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
constructor(
|
|
|
|
@Inject(DI.noteUnreadsRepository)
|
|
|
|
private noteUnreadsRepository: NoteUnreadsRepository,
|
|
|
|
|
|
|
|
@Inject(DI.mutingsRepository)
|
|
|
|
private mutingsRepository: MutingsRepository,
|
|
|
|
|
|
|
|
@Inject(DI.noteThreadMutingsRepository)
|
|
|
|
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
|
|
|
|
|
|
|
|
private idService: IdService,
|
2023-02-04 01:02:03 +00:00
|
|
|
private globalEventService: GlobalEventService,
|
2022-09-17 18:27:08 +00:00
|
|
|
) {
|
|
|
|
}
|
|
|
|
|
2022-12-04 06:03:09 +00:00
|
|
|
@bindThis
|
2022-09-17 18:27:08 +00:00
|
|
|
public async insertNoteUnread(userId: User['id'], note: Note, params: {
|
|
|
|
// NOTE: isSpecifiedがtrueならisMentionedは必ずfalse
|
|
|
|
isSpecified: boolean;
|
|
|
|
isMentioned: boolean;
|
|
|
|
}): Promise<void> {
|
|
|
|
//#region ミュートしているなら無視
|
|
|
|
const mute = await this.mutingsRepository.findBy({
|
|
|
|
muterId: userId,
|
|
|
|
});
|
|
|
|
if (mute.map(m => m.muteeId).includes(note.userId)) return;
|
|
|
|
//#endregion
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
// スレッドミュート
|
|
|
|
const threadMute = await this.noteThreadMutingsRepository.findOneBy({
|
|
|
|
userId: userId,
|
|
|
|
threadId: note.threadId ?? note.id,
|
|
|
|
});
|
|
|
|
if (threadMute) return;
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
const unread = {
|
|
|
|
id: this.idService.genId(),
|
|
|
|
noteId: note.id,
|
|
|
|
userId: userId,
|
|
|
|
isSpecified: params.isSpecified,
|
|
|
|
isMentioned: params.isMentioned,
|
|
|
|
noteUserId: note.userId,
|
|
|
|
};
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
await this.noteUnreadsRepository.insert(unread);
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
|
2023-03-03 02:13:12 +00:00
|
|
|
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
|
2022-09-17 18:27:08 +00:00
|
|
|
const exist = await this.noteUnreadsRepository.findOneBy({ id: unread.id });
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
if (exist == null) return;
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
if (params.isMentioned) {
|
2023-02-04 01:02:03 +00:00
|
|
|
this.globalEventService.publishMainStream(userId, 'unreadMention', note.id);
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
|
|
|
if (params.isSpecified) {
|
2023-02-04 01:02:03 +00:00
|
|
|
this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id);
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
2023-03-03 02:13:12 +00:00
|
|
|
}, () => { /* aborted, ignore it */ });
|
|
|
|
}
|
2022-09-17 18:27:08 +00:00
|
|
|
|
2022-12-04 06:03:09 +00:00
|
|
|
@bindThis
|
2022-09-17 18:27:08 +00:00
|
|
|
public async read(
|
|
|
|
userId: User['id'],
|
|
|
|
notes: (Note | Packed<'Note'>)[],
|
|
|
|
): Promise<void> {
|
|
|
|
const readMentions: (Note | Packed<'Note'>)[] = [];
|
|
|
|
const readSpecifiedNotes: (Note | Packed<'Note'>)[] = [];
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
for (const note of notes) {
|
|
|
|
if (note.mentions && note.mentions.includes(userId)) {
|
|
|
|
readMentions.push(note);
|
|
|
|
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
|
|
|
|
readSpecifiedNotes.push(note);
|
|
|
|
}
|
|
|
|
}
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2023-04-04 22:52:49 +00:00
|
|
|
if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0)) {
|
2022-09-17 18:27:08 +00:00
|
|
|
// Remove the record
|
|
|
|
await this.noteUnreadsRepository.delete({
|
|
|
|
userId: userId,
|
2023-04-04 22:52:49 +00:00
|
|
|
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
|
2022-09-17 18:27:08 +00:00
|
|
|
});
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
// TODO: ↓まとめてクエリしたい
|
2023-07-07 22:08:16 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
this.noteUnreadsRepository.countBy({
|
|
|
|
userId: userId,
|
|
|
|
isMentioned: true,
|
|
|
|
}).then(mentionsCount => {
|
|
|
|
if (mentionsCount === 0) {
|
|
|
|
// 全て既読になったイベントを発行
|
2023-02-04 01:02:03 +00:00
|
|
|
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
|
|
|
});
|
2023-07-07 22:08:16 +00:00
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
this.noteUnreadsRepository.countBy({
|
|
|
|
userId: userId,
|
|
|
|
isSpecified: true,
|
|
|
|
}).then(specifiedCount => {
|
|
|
|
if (specifiedCount === 0) {
|
|
|
|
// 全て既読になったイベントを発行
|
2023-02-04 01:02:03 +00:00
|
|
|
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2023-03-03 02:13:12 +00:00
|
|
|
|
2023-05-29 04:21:26 +00:00
|
|
|
@bindThis
|
|
|
|
public dispose(): void {
|
2023-03-03 02:13:12 +00:00
|
|
|
this.#shutdownController.abort();
|
|
|
|
}
|
2023-05-29 04:21:26 +00:00
|
|
|
|
|
|
|
@bindThis
|
|
|
|
public onApplicationShutdown(signal?: string | undefined): void {
|
|
|
|
this.dispose();
|
|
|
|
}
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|