@@ -22,7 +22,6 @@ import { IdService } from './IdService.js';
|
||||
import { ImageProcessingService } from './ImageProcessingService.js';
|
||||
import { InstanceActorService } from './InstanceActorService.js';
|
||||
import { InternalStorageService } from './InternalStorageService.js';
|
||||
import { MessagingService } from './MessagingService.js';
|
||||
import { MetaService } from './MetaService.js';
|
||||
import { MfmService } from './MfmService.js';
|
||||
import { ModerationLogService } from './ModerationLogService.js';
|
||||
@@ -82,7 +81,6 @@ import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js
|
||||
import { GalleryPostEntityService } from './entities/GalleryPostEntityService.js';
|
||||
import { HashtagEntityService } from './entities/HashtagEntityService.js';
|
||||
import { InstanceEntityService } from './entities/InstanceEntityService.js';
|
||||
import { MessagingMessageEntityService } from './entities/MessagingMessageEntityService.js';
|
||||
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
|
||||
import { MutingEntityService } from './entities/MutingEntityService.js';
|
||||
import { NoteEntityService } from './entities/NoteEntityService.js';
|
||||
@@ -146,7 +144,6 @@ const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
|
||||
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
|
||||
const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
|
||||
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
|
||||
const $MessagingService: Provider = { provide: 'MessagingService', useExisting: MessagingService };
|
||||
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
|
||||
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
|
||||
const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService };
|
||||
@@ -207,7 +204,6 @@ const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService
|
||||
const $GalleryPostEntityService: Provider = { provide: 'GalleryPostEntityService', useExisting: GalleryPostEntityService };
|
||||
const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useExisting: HashtagEntityService };
|
||||
const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService };
|
||||
const $MessagingMessageEntityService: Provider = { provide: 'MessagingMessageEntityService', useExisting: MessagingMessageEntityService };
|
||||
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
|
||||
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
|
||||
const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService };
|
||||
@@ -273,7 +269,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
ImageProcessingService,
|
||||
InstanceActorService,
|
||||
InternalStorageService,
|
||||
MessagingService,
|
||||
MetaService,
|
||||
MfmService,
|
||||
ModerationLogService,
|
||||
@@ -333,7 +328,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
GalleryPostEntityService,
|
||||
HashtagEntityService,
|
||||
InstanceEntityService,
|
||||
MessagingMessageEntityService,
|
||||
ModerationLogEntityService,
|
||||
MutingEntityService,
|
||||
NoteEntityService,
|
||||
@@ -394,7 +388,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$ImageProcessingService,
|
||||
$InstanceActorService,
|
||||
$InternalStorageService,
|
||||
$MessagingService,
|
||||
$MetaService,
|
||||
$MfmService,
|
||||
$ModerationLogService,
|
||||
@@ -454,7 +447,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$GalleryPostEntityService,
|
||||
$HashtagEntityService,
|
||||
$InstanceEntityService,
|
||||
$MessagingMessageEntityService,
|
||||
$ModerationLogEntityService,
|
||||
$MutingEntityService,
|
||||
$NoteEntityService,
|
||||
@@ -516,7 +508,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
ImageProcessingService,
|
||||
InstanceActorService,
|
||||
InternalStorageService,
|
||||
MessagingService,
|
||||
MetaService,
|
||||
MfmService,
|
||||
ModerationLogService,
|
||||
@@ -575,7 +566,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
GalleryPostEntityService,
|
||||
HashtagEntityService,
|
||||
InstanceEntityService,
|
||||
MessagingMessageEntityService,
|
||||
ModerationLogEntityService,
|
||||
MutingEntityService,
|
||||
NoteEntityService,
|
||||
@@ -636,7 +626,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$ImageProcessingService,
|
||||
$InstanceActorService,
|
||||
$InternalStorageService,
|
||||
$MessagingService,
|
||||
$MetaService,
|
||||
$MfmService,
|
||||
$ModerationLogService,
|
||||
@@ -695,7 +684,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$GalleryPostEntityService,
|
||||
$HashtagEntityService,
|
||||
$InstanceEntityService,
|
||||
$MessagingMessageEntityService,
|
||||
$ModerationLogEntityService,
|
||||
$MutingEntityService,
|
||||
$NoteEntityService,
|
||||
|
@@ -11,13 +11,9 @@ import type {
|
||||
AdminStreamTypes,
|
||||
AntennaStreamTypes,
|
||||
BroadcastTypes,
|
||||
ChannelStreamTypes,
|
||||
DriveStreamTypes,
|
||||
GroupMessagingStreamTypes,
|
||||
InternalStreamTypes,
|
||||
MainStreamTypes,
|
||||
MessagingIndexStreamTypes,
|
||||
MessagingStreamTypes,
|
||||
NoteStreamTypes,
|
||||
UserListStreamTypes,
|
||||
UserStreamTypes,
|
||||
@@ -83,11 +79,6 @@ export class GlobalEventService {
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishChannelStream<K extends keyof ChannelStreamTypes>(channelId: Channel['id'], type: K, value?: ChannelStreamTypes[K]): void {
|
||||
this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishUserListStream<K extends keyof UserListStreamTypes>(listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void {
|
||||
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
|
||||
@@ -98,21 +89,6 @@ export class GlobalEventService {
|
||||
this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishMessagingStream<K extends keyof MessagingStreamTypes>(userId: User['id'], otherpartyId: User['id'], type: K, value?: MessagingStreamTypes[K]): void {
|
||||
this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishGroupMessagingStream<K extends keyof GroupMessagingStreamTypes>(groupId: UserGroup['id'], type: K, value?: GroupMessagingStreamTypes[K]): void {
|
||||
this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishMessagingIndexStream<K extends keyof MessagingIndexStreamTypes>(userId: User['id'], type: K, value?: MessagingIndexStreamTypes[K]): void {
|
||||
this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishNotesStream(note: Packed<'Note'>): void {
|
||||
this.publish('notesStream', null, note);
|
||||
|
@@ -1,307 +0,0 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In, Not } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import type { User, RemoteUser } from '@/models/entities/User.js';
|
||||
import type { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { toArray } from '@/misc/prelude/array.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import type { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js';
|
||||
import { PushNotificationService } from '@/core/PushNotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
@Inject(DI.mutingsRepository)
|
||||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private messagingMessageEntityService: MessagingMessageEntityService,
|
||||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private apRendererService: ApRendererService,
|
||||
private queueService: QueueService,
|
||||
private pushNotificationService: PushNotificationService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) {
|
||||
const message = {
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
fileId: file ? file.id : null,
|
||||
recipientId: recipientUser ? recipientUser.id : null,
|
||||
groupId: recipientGroup ? recipientGroup.id : null,
|
||||
text: text ? text.trim() : null,
|
||||
userId: user.id,
|
||||
isRead: false,
|
||||
reads: [] as any[],
|
||||
uri,
|
||||
} as MessagingMessage;
|
||||
|
||||
await this.messagingMessagesRepository.insert(message);
|
||||
|
||||
const messageObj = await this.messagingMessageEntityService.pack(message);
|
||||
|
||||
if (recipientUser) {
|
||||
if (this.userEntityService.isLocalUser(user)) {
|
||||
// 自分のストリーム
|
||||
this.globalEventService.publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
|
||||
this.globalEventService.publishMessagingIndexStream(message.userId, 'message', messageObj);
|
||||
this.globalEventService.publishMainStream(message.userId, 'messagingMessage', messageObj);
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(recipientUser)) {
|
||||
// 相手のストリーム
|
||||
this.globalEventService.publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
|
||||
this.globalEventService.publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
|
||||
this.globalEventService.publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
|
||||
}
|
||||
} else if (recipientGroup) {
|
||||
// グループのストリーム
|
||||
this.globalEventService.publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
|
||||
|
||||
// メンバーのストリーム
|
||||
const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: recipientGroup.id });
|
||||
for (const joining of joinings) {
|
||||
this.globalEventService.publishMessagingIndexStream(joining.userId, 'message', messageObj);
|
||||
this.globalEventService.publishMainStream(joining.userId, 'messagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
|
||||
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
|
||||
setTimeout(async () => {
|
||||
const freshMessage = await this.messagingMessagesRepository.findOneBy({ id: message.id });
|
||||
if (freshMessage == null) return; // メッセージが削除されている場合もある
|
||||
|
||||
if (recipientUser && this.userEntityService.isLocalUser(recipientUser)) {
|
||||
if (freshMessage.isRead) return; // 既読
|
||||
|
||||
//#region ただしミュートされているなら発行しない
|
||||
const mute = await this.mutingsRepository.findBy({
|
||||
muterId: recipientUser.id,
|
||||
});
|
||||
if (mute.map(m => m.muteeId).includes(user.id)) return;
|
||||
//#endregion
|
||||
|
||||
this.globalEventService.publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
this.pushNotificationService.pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
} else if (recipientGroup) {
|
||||
const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: recipientGroup.id, userId: Not(user.id) });
|
||||
for (const joining of joinings) {
|
||||
if (freshMessage.reads.includes(joining.userId)) return; // 既読
|
||||
this.globalEventService.publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
this.pushNotificationService.pushNotification(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
if (recipientUser && this.userEntityService.isLocalUser(user) && this.userEntityService.isRemoteUser(recipientUser)) {
|
||||
const note = {
|
||||
id: message.id,
|
||||
createdAt: message.createdAt,
|
||||
fileIds: message.fileId ? [message.fileId] : [],
|
||||
text: message.text,
|
||||
userId: message.userId,
|
||||
visibility: 'specified',
|
||||
mentions: [recipientUser].map(u => u.id),
|
||||
mentionedRemoteUsers: JSON.stringify([recipientUser].map(u => ({
|
||||
uri: u.uri,
|
||||
username: u.username,
|
||||
host: u.host,
|
||||
}))),
|
||||
} as Note;
|
||||
|
||||
const activity = this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false, true), note));
|
||||
|
||||
this.queueService.deliver(user, activity, recipientUser.inbox);
|
||||
}
|
||||
return messageObj;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteMessage(message: MessagingMessage) {
|
||||
await this.messagingMessagesRepository.delete(message.id);
|
||||
this.postDeleteMessage(message);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async postDeleteMessage(message: MessagingMessage) {
|
||||
if (message.recipientId) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: message.userId });
|
||||
const recipient = await this.usersRepository.findOneByOrFail({ id: message.recipientId });
|
||||
|
||||
if (this.userEntityService.isLocalUser(user)) this.globalEventService.publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
|
||||
if (this.userEntityService.isLocalUser(recipient)) this.globalEventService.publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
|
||||
|
||||
if (this.userEntityService.isLocalUser(user) && this.userEntityService.isRemoteUser(recipient)) {
|
||||
const activity = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${message.id}`), user));
|
||||
this.queueService.deliver(user, activity, recipient.inbox);
|
||||
}
|
||||
} else if (message.groupId) {
|
||||
this.globalEventService.publishGroupMessagingStream(message.groupId, 'deleted', message.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark messages as read
|
||||
*/
|
||||
@bindThis
|
||||
public async readUserMessagingMessage(
|
||||
userId: User['id'],
|
||||
otherpartyId: User['id'],
|
||||
messageIds: MessagingMessage['id'][],
|
||||
) {
|
||||
if (messageIds.length === 0) return;
|
||||
|
||||
const messages = await this.messagingMessagesRepository.findBy({
|
||||
id: In(messageIds),
|
||||
});
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.recipientId !== userId) {
|
||||
throw new IdentifiableError('e140a4bf-49ce-4fb6-b67c-b78dadf6b52f', 'Access denied (user).');
|
||||
}
|
||||
}
|
||||
|
||||
// Update documents
|
||||
await this.messagingMessagesRepository.update({
|
||||
id: In(messageIds),
|
||||
userId: otherpartyId,
|
||||
recipientId: userId,
|
||||
isRead: false,
|
||||
}, {
|
||||
isRead: true,
|
||||
});
|
||||
|
||||
// Publish event
|
||||
this.globalEventService.publishMessagingStream(otherpartyId, userId, 'read', messageIds);
|
||||
this.globalEventService.publishMessagingIndexStream(userId, 'read', messageIds);
|
||||
|
||||
if (!await this.userEntityService.getHasUnreadMessagingMessage(userId)) {
|
||||
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
|
||||
this.globalEventService.publishMainStream(userId, 'readAllMessagingMessages');
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessages', undefined);
|
||||
} else {
|
||||
// そのユーザーとのメッセージで未読がなければイベント発行
|
||||
const count = await this.messagingMessagesRepository.count({
|
||||
where: {
|
||||
userId: otherpartyId,
|
||||
recipientId: userId,
|
||||
isRead: false,
|
||||
},
|
||||
take: 1,
|
||||
});
|
||||
|
||||
if (!count) {
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark messages as read
|
||||
*/
|
||||
@bindThis
|
||||
public async readGroupMessagingMessage(
|
||||
userId: User['id'],
|
||||
groupId: UserGroup['id'],
|
||||
messageIds: MessagingMessage['id'][],
|
||||
) {
|
||||
if (messageIds.length === 0) return;
|
||||
|
||||
// check joined
|
||||
const joining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userId: userId,
|
||||
userGroupId: groupId,
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).');
|
||||
}
|
||||
|
||||
const messages = await this.messagingMessagesRepository.findBy({
|
||||
id: In(messageIds),
|
||||
});
|
||||
|
||||
const reads: MessagingMessage['id'][] = [];
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.userId === userId) continue;
|
||||
if (message.reads.includes(userId)) continue;
|
||||
|
||||
// Update document
|
||||
await this.messagingMessagesRepository.createQueryBuilder().update()
|
||||
.set({
|
||||
reads: (() => `array_append("reads", '${joining.userId}')`) as any,
|
||||
})
|
||||
.where('id = :id', { id: message.id })
|
||||
.execute();
|
||||
|
||||
reads.push(message.id);
|
||||
}
|
||||
|
||||
// Publish event
|
||||
this.globalEventService.publishGroupMessagingStream(groupId, 'read', {
|
||||
ids: reads,
|
||||
userId: userId,
|
||||
});
|
||||
this.globalEventService.publishMessagingIndexStream(userId, 'read', reads);
|
||||
|
||||
if (!await this.userEntityService.getHasUnreadMessagingMessage(userId)) {
|
||||
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
|
||||
this.globalEventService.publishMainStream(userId, 'readAllMessagingMessages');
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessages', undefined);
|
||||
} else {
|
||||
// そのグループにおいて未読がなければイベント発行
|
||||
const unreadExist = await this.messagingMessagesRepository.createQueryBuilder('message')
|
||||
.where('message.groupId = :groupId', { groupId: groupId })
|
||||
.andWhere('message.userId != :userId', { userId: userId })
|
||||
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
|
||||
.andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない
|
||||
.getOne().then(x => x != null);
|
||||
|
||||
if (!unreadExist) {
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deliverReadActivity(user: { id: User['id']; host: null; }, recipient: RemoteUser, messages: MessagingMessage | MessagingMessage[]) {
|
||||
messages = toArray(messages).filter(x => x.uri);
|
||||
const contents = messages.map(x => this.apRendererService.renderRead(user, x));
|
||||
|
||||
if (contents.length > 1) {
|
||||
const collection = this.apRendererService.renderOrderedCollection(null, contents.length, undefined, undefined, contents);
|
||||
this.queueService.deliver(user, this.apRendererService.addContext(collection), recipient.inbox);
|
||||
} else {
|
||||
for (const content of contents) {
|
||||
this.queueService.deliver(user, this.apRendererService.addContext(content), recipient.inbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,15 +11,12 @@ import { bindThis } from '@/decorators.js';
|
||||
// Defined also packages/sw/types.ts#L13
|
||||
type pushNotificationsTypes = {
|
||||
'notification': Packed<'Notification'>;
|
||||
'unreadMessagingMessage': Packed<'MessagingMessage'>;
|
||||
'unreadAntennaNote': {
|
||||
antenna: { id: string, name: string };
|
||||
note: Packed<'Note'>;
|
||||
};
|
||||
'readNotifications': { notificationIds: string[] };
|
||||
'readAllNotifications': undefined;
|
||||
'readAllMessagingMessages': undefined;
|
||||
'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string };
|
||||
'readAntenna': { antennaId: string };
|
||||
'readAllAntennas': undefined;
|
||||
};
|
||||
@@ -40,11 +37,10 @@ function truncateBody<T extends keyof pushNotificationsTypes>(type: T, body: pus
|
||||
reply: undefined,
|
||||
renote: undefined,
|
||||
user: type === 'notification' ? undefined as any : body.note.user,
|
||||
}
|
||||
},
|
||||
} : {}),
|
||||
};
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -81,8 +77,6 @@ export class PushNotificationService {
|
||||
if ([
|
||||
'readNotifications',
|
||||
'readAllNotifications',
|
||||
'readAllMessagingMessages',
|
||||
'readAllMessagingMessagesOfARoom',
|
||||
'readAntenna',
|
||||
'readAllAntennas',
|
||||
].includes(type) && !subscription.sendReadMessage) continue;
|
||||
|
@@ -1,13 +1,12 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import escapeRegexp from 'escape-regexp';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import type { UserPublickey } from '@/models/entities/UserPublickey.js';
|
||||
import { UserCacheService } from '@/core/UserCacheService.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { getApId } from './type.js';
|
||||
@@ -42,9 +41,6 @@ export class ApDbResolverService {
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@@ -101,23 +97,6 @@ export class ApDbResolverService {
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> {
|
||||
const parsed = this.parseUri(value);
|
||||
|
||||
if (parsed.local) {
|
||||
if (parsed.type !== 'notes') return null;
|
||||
|
||||
return await this.messagingMessagesRepository.findOneBy({
|
||||
id: parsed.id,
|
||||
});
|
||||
} else {
|
||||
return await this.messagingMessagesRepository.findOneBy({
|
||||
uri: parsed.uri,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AP Person => Misskey User in DB
|
||||
*/
|
||||
|
@@ -19,8 +19,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { MessagingService } from '@/core/MessagingService.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { RemoteUser } from '@/models/entities/User.js';
|
||||
import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
@@ -51,9 +50,6 @@ export class ApInboxService {
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
@Inject(DI.abuseUserReportsRepository)
|
||||
private abuseUserReportsRepository: AbuseUserReportsRepository,
|
||||
|
||||
@@ -81,7 +77,6 @@ export class ApInboxService {
|
||||
private apPersonService: ApPersonService,
|
||||
private apQuestionService: ApQuestionService,
|
||||
private queueService: QueueService,
|
||||
private messagingService: MessagingService,
|
||||
) {
|
||||
this.logger = this.apLoggerService.logger;
|
||||
}
|
||||
@@ -124,8 +119,6 @@ export class ApInboxService {
|
||||
await this.delete(actor, activity);
|
||||
} else if (isUpdate(activity)) {
|
||||
await this.update(actor, activity);
|
||||
} else if (isRead(activity)) {
|
||||
await this.read(actor, activity);
|
||||
} else if (isFollow(activity)) {
|
||||
await this.follow(actor, activity);
|
||||
} else if (isAccept(activity)) {
|
||||
@@ -185,29 +178,6 @@ export class ApInboxService {
|
||||
}).then(() => 'ok');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async read(actor: RemoteUser, activity: IRead): Promise<string> {
|
||||
const id = await getApId(activity.object);
|
||||
|
||||
if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) {
|
||||
return `skip: Read to foreign host (${id})`;
|
||||
}
|
||||
|
||||
const messageId = id.split('/').pop();
|
||||
|
||||
const message = await this.messagingMessagesRepository.findOneBy({ id: messageId });
|
||||
if (message == null) {
|
||||
return 'skip: message not found';
|
||||
}
|
||||
|
||||
if (actor.id !== message.recipientId) {
|
||||
return 'skip: actor is not a message recipient';
|
||||
}
|
||||
|
||||
await this.messagingService.readUserMessagingMessage(message.recipientId!, message.userId, [message.id]);
|
||||
return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async accept(actor: RemoteUser, activity: IAccept): Promise<string> {
|
||||
const uri = activity.id ?? activity;
|
||||
@@ -504,16 +474,7 @@ export class ApInboxService {
|
||||
const note = await this.apDbResolverService.getNoteFromApId(uri);
|
||||
|
||||
if (note == null) {
|
||||
const message = await this.apDbResolverService.getMessageFromApId(uri);
|
||||
if (message == null) return 'message not found';
|
||||
|
||||
if (message.userId !== actor.id) {
|
||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
||||
}
|
||||
|
||||
await this.messagingService.deleteMessage(message);
|
||||
|
||||
return 'ok: message deleted';
|
||||
return 'message not found';
|
||||
}
|
||||
|
||||
if (note.userId !== actor.id) {
|
||||
|
@@ -13,7 +13,6 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { NoteReaction } from '@/models/entities/NoteReaction.js';
|
||||
import type { Emoji } from '@/models/entities/Emoji.js';
|
||||
import type { Poll } from '@/models/entities/Poll.js';
|
||||
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
|
||||
import type { PollVote } from '@/models/entities/PollVote.js';
|
||||
import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js';
|
||||
import { MfmService } from '@/core/MfmService.js';
|
||||
@@ -293,7 +292,7 @@ export class ApRendererService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async renderNote(note: Note, dive = true, isTalk = false): Promise<IPost> {
|
||||
public async renderNote(note: Note, dive = true): Promise<IPost> {
|
||||
const getPromisedFiles = async (ids: string[]) => {
|
||||
if (!ids || ids.length === 0) return [];
|
||||
const items = await this.driveFilesRepository.findBy({ id: In(ids) });
|
||||
@@ -407,11 +406,7 @@ export class ApRendererService {
|
||||
},
|
||||
})),
|
||||
} as const : {};
|
||||
|
||||
const asTalk = isTalk ? {
|
||||
_misskey_talk: true,
|
||||
} as const : {};
|
||||
|
||||
|
||||
return {
|
||||
id: `${this.config.url}/notes/${note.id}`,
|
||||
type: 'Note',
|
||||
@@ -433,7 +428,6 @@ export class ApRendererService {
|
||||
sensitive: note.cw != null || files.some(file => file.isSensitive),
|
||||
tag,
|
||||
...asPoll,
|
||||
...asTalk,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -532,15 +526,6 @@ export class ApRendererService {
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public renderRead(user: { id: User['id'] }, message: MessagingMessage): IRead {
|
||||
return {
|
||||
type: 'Read',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
object: message.uri!,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public renderReject(object: any, user: { id: User['id'] }): IReject {
|
||||
return {
|
||||
@@ -643,7 +628,6 @@ export class ApRendererService {
|
||||
'_misskey_quote': 'misskey:_misskey_quote',
|
||||
'_misskey_reaction': 'misskey:_misskey_reaction',
|
||||
'_misskey_votes': 'misskey:_misskey_votes',
|
||||
'_misskey_talk': 'misskey:_misskey_talk',
|
||||
'isCat': 'misskey:isCat',
|
||||
// vcard
|
||||
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MessagingMessagesRepository, PollsRepository, EmojisRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { PollsRepository, EmojisRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { RemoteUser } from '@/models/entities/User.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
@@ -16,7 +16,6 @@ import { IdService } from '@/core/IdService.js';
|
||||
import { PollService } from '@/core/PollService.js';
|
||||
import { StatusError } from '@/misc/status-error.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { MessagingService } from '@/core/MessagingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
@@ -47,9 +46,6 @@ export class ApNoteService {
|
||||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private apMfmService: ApMfmService,
|
||||
private apResolverService: ApResolverService,
|
||||
@@ -64,7 +60,6 @@ export class ApNoteService {
|
||||
private apImageService: ApImageService,
|
||||
private apQuestionService: ApQuestionService,
|
||||
private metaService: MetaService,
|
||||
private messagingService: MessagingService,
|
||||
private appLockService: AppLockService,
|
||||
private pollService: PollService,
|
||||
private noteCreateService: NoteCreateService,
|
||||
@@ -165,8 +160,6 @@ export class ApNoteService {
|
||||
}
|
||||
}
|
||||
|
||||
let isMessaging = note._misskey_talk && visibility === 'specified';
|
||||
|
||||
const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
|
||||
const apHashtags = await extractApHashtags(note.tag);
|
||||
|
||||
@@ -193,17 +186,6 @@ export class ApNoteService {
|
||||
return x;
|
||||
}
|
||||
}).catch(async err => {
|
||||
// トークだったらinReplyToのエラーは無視
|
||||
const uri = getApId(note.inReplyTo);
|
||||
if (uri.startsWith(this.config.url + '/')) {
|
||||
const id = uri.split('/').pop();
|
||||
const talk = await this.messagingMessagesRepository.findOneBy({ id });
|
||||
if (talk) {
|
||||
isMessaging = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`);
|
||||
throw err;
|
||||
})
|
||||
@@ -292,14 +274,7 @@ export class ApNoteService {
|
||||
const apEmojis = emojis.map(emoji => emoji.name);
|
||||
|
||||
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
|
||||
|
||||
if (isMessaging) {
|
||||
for (const recipient of visibleUsers) {
|
||||
await this.messagingService.createMessage(actor, recipient, undefined, text ?? undefined, (files && files.length > 0) ? files[0] : null, object.id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return await this.noteCreateService.create(actor, {
|
||||
createdAt: note.published ? new Date(note.published) : null,
|
||||
files,
|
||||
|
@@ -113,7 +113,6 @@ export interface IPost extends IObject {
|
||||
_misskey_quote?: string;
|
||||
_misskey_content?: string;
|
||||
quoteUrl?: string;
|
||||
_misskey_talk?: boolean;
|
||||
}
|
||||
|
||||
export interface IQuestion extends IObject {
|
||||
|
@@ -1,59 +0,0 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MessagingMessagesRepository } from '@/models/index.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import type { } from '@/models/entities/Blocking.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
import { UserGroupEntityService } from './UserGroupEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingMessageEntityService {
|
||||
constructor(
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async pack(
|
||||
src: MessagingMessage['id'] | MessagingMessage,
|
||||
me?: { id: User['id'] } | null | undefined,
|
||||
options?: {
|
||||
populateRecipient?: boolean,
|
||||
populateGroup?: boolean,
|
||||
},
|
||||
): Promise<Packed<'MessagingMessage'>> {
|
||||
const opts = options ?? {
|
||||
populateRecipient: true,
|
||||
populateGroup: true,
|
||||
};
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.messagingMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: message.createdAt.toISOString(),
|
||||
text: message.text,
|
||||
userId: message.userId,
|
||||
user: await this.userEntityService.pack(message.user ?? message.userId, me),
|
||||
recipientId: message.recipientId,
|
||||
recipient: message.recipientId && opts.populateRecipient ? await this.userEntityService.pack(message.recipient ?? message.recipientId, me) : undefined,
|
||||
groupId: message.groupId,
|
||||
group: message.groupId && opts.populateGroup ? await this.userGroupEntityService.pack(message.group ?? message.groupId) : undefined,
|
||||
fileId: message.fileId,
|
||||
file: message.fileId ? await this.driveFileEntityService.pack(message.fileId) : null,
|
||||
isRead: message.isRead,
|
||||
reads: message.reads,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import { Cache } from '@/misc/cache.js';
|
||||
import type { Instance } from '@/models/entities/Instance.js';
|
||||
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
|
||||
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js';
|
||||
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
@@ -102,9 +102,6 @@ export class UserEntityService implements OnModuleInit {
|
||||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
@@ -204,36 +201,6 @@ export class UserEntityService implements OnModuleInit {
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
|
||||
const mute = await this.mutingsRepository.findBy({
|
||||
muterId: userId,
|
||||
});
|
||||
|
||||
const joinings = await this.userGroupJoiningsRepository.findBy({ userId: userId });
|
||||
|
||||
const groupQs = Promise.all(joinings.map(j => this.messagingMessagesRepository.createQueryBuilder('message')
|
||||
.where('message.groupId = :groupId', { groupId: j.userGroupId })
|
||||
.andWhere('message.userId != :userId', { userId: userId })
|
||||
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
|
||||
.andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
|
||||
.getOne().then(x => x != null)));
|
||||
|
||||
const [withUser, withGroups] = await Promise.all([
|
||||
this.messagingMessagesRepository.count({
|
||||
where: {
|
||||
recipientId: userId,
|
||||
isRead: false,
|
||||
...(mute.length > 0 ? { userId: Not(In(mute.map(x => x.muteeId))) } : {}),
|
||||
},
|
||||
take: 1,
|
||||
}).then(count => count > 0),
|
||||
groupQs,
|
||||
]);
|
||||
|
||||
return withUser || withGroups.some(x => x);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
|
||||
const reads = await this.announcementReadsRepository.findBy({
|
||||
@@ -492,7 +459,6 @@ export class UserEntityService implements OnModuleInit {
|
||||
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
|
||||
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
|
||||
hasUnreadChannel: this.getHasUnreadChannel(user.id),
|
||||
hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
|
||||
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
||||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||
mutedWords: profile!.mutedWords,
|
||||
|
Reference in New Issue
Block a user