improve moderation log

This commit is contained in:
syuilo 2023-09-24 10:57:24 +09:00
parent ed983a5baf
commit 8e5a90589d
11 changed files with 81 additions and 16 deletions

2
locales/index.d.ts vendored
View file

@ -2256,6 +2256,8 @@ export interface Locale {
"suspend": string; "suspend": string;
"unsuspend": string; "unsuspend": string;
"addCustomEmoji": string; "addCustomEmoji": string;
"updateCustomEmoji": string;
"deleteCustomEmoji": string;
"updateServerSettings": string; "updateServerSettings": string;
"updateUserNote": string; "updateUserNote": string;
"deleteDriveFile": string; "deleteDriveFile": string;

View file

@ -2169,6 +2169,8 @@ _moderationLogTypes:
suspend: "凍結" suspend: "凍結"
unsuspend: "凍結解除" unsuspend: "凍結解除"
addCustomEmoji: "カスタム絵文字追加" addCustomEmoji: "カスタム絵文字追加"
updateCustomEmoji: "カスタム絵文字更新"
deleteCustomEmoji: "カスタム絵文字削除"
updateServerSettings: "サーバー設定更新" updateServerSettings: "サーバー設定更新"
updateUserNote: "モデレーションノート更新" updateUserNote: "モデレーションノート更新"
deleteDriveFile: "ファイルを削除" deleteDriveFile: "ファイルを削除"

View file

@ -12,12 +12,13 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiEmoji } from '@/models/Emoji.js'; import type { MiEmoji } from '@/models/Emoji.js';
import type { EmojisRepository, MiRole } from '@/models/_.js'; import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js'; import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { query } from '@/misc/prelude/url.js'; import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/server/api/stream/types.js'; import type { Serialized } from '@/server/api/stream/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/; const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
@ -36,6 +37,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
private utilityService: UtilityService, private utilityService: UtilityService,
private idService: IdService, private idService: IdService,
private emojiEntityService: EmojiEntityService, private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
) { ) {
this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12); this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12);
@ -66,7 +68,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive: boolean; isSensitive: boolean;
localOnly: boolean; localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][]; roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
}): Promise<MiEmoji> { }, moderator?: MiUser): Promise<MiEmoji> {
const emoji = await this.emojisRepository.insert({ const emoji = await this.emojisRepository.insert({
id: this.idService.genId(), id: this.idService.genId(),
updatedAt: new Date(), updatedAt: new Date(),
@ -89,6 +91,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.globalEventService.publishBroadcastStream('emojiAdded', { this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(emoji.id), emoji: await this.emojiEntityService.packDetailed(emoji.id),
}); });
if (moderator) {
this.moderationLogService.log(moderator, 'addCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}
} }
return emoji; return emoji;
@ -104,7 +113,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive?: boolean; isSensitive?: boolean;
localOnly?: boolean; localOnly?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][]; roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
}): Promise<void> { }, moderator?: MiUser): Promise<void> {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() }); const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists'); if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
@ -140,6 +149,14 @@ export class CustomEmojiService implements OnApplicationShutdown {
emoji: updated, emoji: updated,
}); });
} }
if (moderator) {
this.moderationLogService.log(moderator, 'updateCustomEmoji', {
emojiId: emoji.id,
before: emoji,
after: updated,
});
}
} }
@bindThis @bindThis
@ -231,7 +248,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public async delete(id: MiEmoji['id']) { public async delete(id: MiEmoji['id'], moderator?: MiUser) {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
await this.emojisRepository.delete(emoji.id); await this.emojisRepository.delete(emoji.id);
@ -241,16 +258,30 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.globalEventService.publishBroadcastStream('emojiDeleted', { this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.packDetailed(emoji)], emojis: [await this.emojiEntityService.packDetailed(emoji)],
}); });
if (moderator) {
this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}
} }
@bindThis @bindThis
public async deleteBulk(ids: MiEmoji['id'][]) { public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
const emojis = await this.emojisRepository.findBy({ const emojis = await this.emojisRepository.findBy({
id: In(ids), id: In(ids),
}); });
for (const emoji of emojis) { for (const emoji of emojis) {
await this.emojisRepository.delete(emoji.id); await this.emojisRepository.delete(emoji.id);
if (moderator) {
this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}
} }
this.localEmojisCache.refresh(); this.localEmojisCache.refresh();

View file

@ -8,7 +8,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository } from '@/models/_.js'; import type { DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { ApiError } from '../../../error.js'; import { ApiError } from '../../../error.js';
@ -61,7 +60,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService, private customEmojiService: CustomEmojiService,
private emojiEntityService: EmojiEntityService, private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
@ -77,11 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive ?? false, isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false, localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [], roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
}); }, me);
this.moderationLogService.log(me, 'addCustomEmoji', {
emojiId: emoji.id,
});
return this.emojiEntityService.packDetailed(emoji); return this.emojiEntityService.packDetailed(emoji);
}); });

View file

@ -30,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService, private customEmojiService: CustomEmojiService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.deleteBulk(ps.ids); await this.customEmojiService.deleteBulk(ps.ids, me);
}); });
} }
} }

View file

@ -36,7 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService, private customEmojiService: CustomEmojiService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.delete(ps.id); await this.customEmojiService.delete(ps.id, me);
}); });
} }
} }

View file

@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive, isSensitive: ps.isSensitive,
localOnly: ps.localOnly, localOnly: ps.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction, roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
}); }, me);
}); });
} }
} }

View file

@ -33,6 +33,8 @@ export const moderationLogTypes = [
'unsuspend', 'unsuspend',
'updateUserNote', 'updateUserNote',
'addCustomEmoji', 'addCustomEmoji',
'updateCustomEmoji',
'deleteCustomEmoji',
'assignRole', 'assignRole',
'unassignRole', 'unassignRole',
'updateRole', 'updateRole',
@ -70,6 +72,16 @@ export type ModerationLogPayloads = {
}; };
addCustomEmoji: { addCustomEmoji: {
emojiId: string; emojiId: string;
emoji: any;
};
updateCustomEmoji: {
emojiId: string;
before: any;
after: any;
};
deleteCustomEmoji: {
emojiId: string;
emoji: any;
}; };
assignRole: { assignRole: {
userId: string; userId: string;

View file

@ -2538,6 +2538,12 @@ type ModerationLog = {
} | { } | {
type: 'addCustomEmoji'; type: 'addCustomEmoji';
info: ModerationLogPayloads['addCustomEmoji']; info: ModerationLogPayloads['addCustomEmoji'];
} | {
type: 'updateCustomEmoji';
info: ModerationLogPayloads['updateCustomEmoji'];
} | {
type: 'deleteCustomEmoji';
info: ModerationLogPayloads['deleteCustomEmoji'];
} | { } | {
type: 'assignRole'; type: 'assignRole';
info: ModerationLogPayloads['assignRole']; info: ModerationLogPayloads['assignRole'];
@ -2592,7 +2598,7 @@ type ModerationLog = {
}); });
// @public (undocumented) // @public (undocumented)
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"]; export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"];
// @public (undocumented) // @public (undocumented)
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];

View file

@ -51,6 +51,8 @@ export const moderationLogTypes = [
'unsuspend', 'unsuspend',
'updateUserNote', 'updateUserNote',
'addCustomEmoji', 'addCustomEmoji',
'updateCustomEmoji',
'deleteCustomEmoji',
'assignRole', 'assignRole',
'unassignRole', 'unassignRole',
'updateRole', 'updateRole',
@ -88,6 +90,16 @@ export type ModerationLogPayloads = {
}; };
addCustomEmoji: { addCustomEmoji: {
emojiId: string; emojiId: string;
emoji: any;
};
updateCustomEmoji: {
emojiId: string;
before: any;
after: any;
};
deleteCustomEmoji: {
emojiId: string;
emoji: any;
}; };
assignRole: { assignRole: {
userId: string; userId: string;

View file

@ -589,6 +589,12 @@ export type ModerationLog = {
} | { } | {
type: 'addCustomEmoji'; type: 'addCustomEmoji';
info: ModerationLogPayloads['addCustomEmoji']; info: ModerationLogPayloads['addCustomEmoji'];
} | {
type: 'updateCustomEmoji';
info: ModerationLogPayloads['updateCustomEmoji'];
} | {
type: 'deleteCustomEmoji';
info: ModerationLogPayloads['deleteCustomEmoji'];
} | { } | {
type: 'assignRole'; type: 'assignRole';
info: ModerationLogPayloads['assignRole']; info: ModerationLogPayloads['assignRole'];