enhance: improve moderation log

This commit is contained in:
syuilo 2023-09-25 10:29:12 +09:00
parent 646a8d1a54
commit 5318532a8d
19 changed files with 209 additions and 60 deletions

View file

@ -14,7 +14,7 @@
## (unreleased) ## (unreleased)
### General ### General
- - Enhance: モデレーションログ機能の強化
### Client ### Client
- -
@ -22,7 +22,7 @@
### Server ### Server
- Fix: お知らせのページネーションが機能しない - Fix: お知らせのページネーションが機能しない
## 2023.9.0 (unreleased) ## 2023.9.0
### Note ### Note
- meilisearchを使用する場合、v1.2以上が必要です - meilisearchを使用する場合、v1.2以上が必要です

5
locales/index.d.ts vendored
View file

@ -1123,6 +1123,7 @@ export interface Locale {
"unnotifyNotes": string; "unnotifyNotes": string;
"authentication": string; "authentication": string;
"authenticationRequiredToContinue": string; "authenticationRequiredToContinue": string;
"dateAndTime": string;
"_announcement": { "_announcement": {
"forExistingUsers": string; "forExistingUsers": string;
"forExistingUsersDescription": string; "forExistingUsersDescription": string;
@ -2250,9 +2251,11 @@ export interface Locale {
}; };
}; };
"_moderationLogTypes": { "_moderationLogTypes": {
"createRole": string;
"deleteRole": string;
"updateRole": string;
"assignRole": string; "assignRole": string;
"unassignRole": string; "unassignRole": string;
"updateRole": string;
"suspend": string; "suspend": string;
"unsuspend": string; "unsuspend": string;
"addCustomEmoji": string; "addCustomEmoji": string;

View file

@ -1120,6 +1120,7 @@ notifyNotes: "投稿を通知"
unnotifyNotes: "投稿の通知を解除" unnotifyNotes: "投稿の通知を解除"
authentication: "認証" authentication: "認証"
authenticationRequiredToContinue: "続けるには認証を行ってください" authenticationRequiredToContinue: "続けるには認証を行ってください"
dateAndTime: "日時"
_announcement: _announcement:
forExistingUsers: "既存ユーザーのみ" forExistingUsers: "既存ユーザーのみ"
@ -2163,9 +2164,11 @@ _webhookSettings:
mention: "メンションされたとき" mention: "メンションされたとき"
_moderationLogTypes: _moderationLogTypes:
createRole: "ロールを作成"
deleteRole: "ロールを削除"
updateRole: "ロールを更新"
assignRole: "ロールへアサイン" assignRole: "ロールへアサイン"
unassignRole: "ロールのアサイン解除" unassignRole: "ロールのアサイン解除"
updateRole: "ロール設定更新"
suspend: "凍結" suspend: "凍結"
unsuspend: "凍結解除" unsuspend: "凍結解除"
addCustomEmoji: "カスタム絵文字追加" addCustomEmoji: "カスタム絵文字追加"

View file

@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm'; import { Brackets } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/_.js'; import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead, UsersRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js'; import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
@ -23,6 +23,9 @@ export class AnnouncementService {
@Inject(DI.announcementReadsRepository) @Inject(DI.announcementReadsRepository)
private announcementReadsRepository: AnnouncementReadsRepository, private announcementReadsRepository: AnnouncementReadsRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private idService: IdService, private idService: IdService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
@ -83,10 +86,13 @@ export class AnnouncementService {
}); });
if (moderator) { if (moderator) {
const user = await this.usersRepository.findOneByOrFail({ id: values.userId });
this.moderationLogService.log(moderator, 'createUserAnnouncement', { this.moderationLogService.log(moderator, 'createUserAnnouncement', {
announcementId: announcement.id, announcementId: announcement.id,
announcement: announcement, announcement: announcement,
userId: values.userId, userId: values.userId,
userUsername: user.username,
userHost: user.host,
}); });
} }
} else { } else {
@ -127,10 +133,14 @@ export class AnnouncementService {
if (moderator) { if (moderator) {
if (announcement.userId) { if (announcement.userId) {
const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
this.moderationLogService.log(moderator, 'updateUserAnnouncement', { this.moderationLogService.log(moderator, 'updateUserAnnouncement', {
announcementId: announcement.id, announcementId: announcement.id,
before: announcement, before: announcement,
after: after, after: after,
userId: announcement.userId,
userUsername: user.username,
userHost: user.host,
}); });
} else { } else {
this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', { this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', {

View file

@ -134,11 +134,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.localEmojisCache.refresh(); this.localEmojisCache.refresh();
const updated = await this.emojiEntityService.packDetailed(emoji.id); const packed = await this.emojiEntityService.packDetailed(emoji.id);
if (emoji.name === data.name) { if (emoji.name === data.name) {
this.globalEventService.publishBroadcastStream('emojiUpdated', { this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: [updated], emojis: [packed],
}); });
} else { } else {
this.globalEventService.publishBroadcastStream('emojiDeleted', { this.globalEventService.publishBroadcastStream('emojiDeleted', {
@ -146,11 +146,12 @@ export class CustomEmojiService implements OnApplicationShutdown {
}); });
this.globalEventService.publishBroadcastStream('emojiAdded', { this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: updated, emoji: packed,
}); });
} }
if (moderator) { if (moderator) {
const updated = await this.emojisRepository.findOneByOrFail({ id: id });
this.moderationLogService.log(moderator, 'updateCustomEmoji', { this.moderationLogService.log(moderator, 'updateCustomEmoji', {
emojiId: emoji.id, emojiId: emoji.id,
before: emoji, before: emoji,

View file

@ -686,15 +686,20 @@ export class DriveService {
if (await this.roleService.isModerator(updater) && (file.userId !== updater.id)) { if (await this.roleService.isModerator(updater) && (file.userId !== updater.id)) {
if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive) { if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive) {
const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
if (values.isSensitive) { if (values.isSensitive) {
this.moderationLogService.log(updater, 'markSensitiveDriveFile', { this.moderationLogService.log(updater, 'markSensitiveDriveFile', {
fileId: file.id, fileId: file.id,
fileUserId: file.userId, fileUserId: file.userId,
fileUserUsername: user?.username ?? null,
fileUserHost: user?.host ?? null,
}); });
} else { } else {
this.moderationLogService.log(updater, 'unmarkSensitiveDriveFile', { this.moderationLogService.log(updater, 'unmarkSensitiveDriveFile', {
fileId: file.id, fileId: file.id,
fileUserId: file.userId, fileUserId: file.userId,
fileUserUsername: user?.username ?? null,
fileUserHost: user?.host ?? null,
}); });
} }
} }
@ -795,9 +800,12 @@ export class DriveService {
} }
if (deleter && await this.roleService.isModerator(deleter) && (file.userId !== deleter.id)) { if (deleter && await this.roleService.isModerator(deleter) && (file.userId !== deleter.id)) {
const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
this.moderationLogService.log(deleter, 'deleteDriveFile', { this.moderationLogService.log(deleter, 'deleteDriveFile', {
fileId: file.id, fileId: file.id,
fileUserId: file.userId, fileUserId: file.userId,
fileUserUsername: user?.username ?? null,
fileUserHost: user?.host ?? null,
}); });
} }
} }

View file

@ -135,9 +135,12 @@ export class NoteDeleteService {
}); });
if (deleter && (note.userId !== deleter.id)) { if (deleter && (note.userId !== deleter.id)) {
const user = await this.usersRepository.findOneByOrFail({ id: note.userId });
this.moderationLogService.log(deleter, 'deleteNote', { this.moderationLogService.log(deleter, 'deleteNote', {
noteId: note.id, noteId: note.id,
noteUserId: note.userId, noteUserId: note.userId,
noteUserUsername: user.username,
noteUserHost: user.host,
note: note, note: note,
}); });
} }

View file

@ -412,10 +412,13 @@ export class RoleService implements OnApplicationShutdown {
this.globalEventService.publishInternalEvent('userRoleAssigned', created); this.globalEventService.publishInternalEvent('userRoleAssigned', created);
if (moderator) { if (moderator) {
const user = await this.usersRepository.findOneByOrFail({ id: userId });
this.moderationLogService.log(moderator, 'assignRole', { this.moderationLogService.log(moderator, 'assignRole', {
roleId: roleId, roleId: roleId,
roleName: role.name, roleName: role.name,
userId: userId, userId: userId,
userUsername: user.username,
userHost: user.host,
expiresAt: expiresAt ? expiresAt.toISOString() : null, expiresAt: expiresAt ? expiresAt.toISOString() : null,
}); });
} }
@ -445,11 +448,16 @@ export class RoleService implements OnApplicationShutdown {
this.globalEventService.publishInternalEvent('userRoleUnassigned', existing); this.globalEventService.publishInternalEvent('userRoleUnassigned', existing);
if (moderator) { if (moderator) {
const role = await this.rolesRepository.findOneByOrFail({ id: roleId }); const [user, role] = await Promise.all([
this.usersRepository.findOneByOrFail({ id: userId }),
this.rolesRepository.findOneByOrFail({ id: roleId }),
]);
this.moderationLogService.log(moderator, 'unassignRole', { this.moderationLogService.log(moderator, 'unassignRole', {
roleId: roleId, roleId: roleId,
roleName: role.name, roleName: role.name,
userId: userId, userId: userId,
userUsername: user.username,
userHost: user.host,
}); });
} }
} }
@ -473,6 +481,42 @@ export class RoleService implements OnApplicationShutdown {
redisPipeline.exec(); redisPipeline.exec();
} }
@bindThis
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
const date = new Date();
const created = await this.rolesRepository.insert({
id: this.idService.genId(),
createdAt: date,
updatedAt: date,
lastUsedAt: date,
name: values.name,
description: values.description,
color: values.color,
iconUrl: values.iconUrl,
target: values.target,
condFormula: values.condFormula,
isPublic: values.isPublic,
isAdministrator: values.isAdministrator,
isModerator: values.isModerator,
isExplorable: values.isExplorable,
asBadge: values.asBadge,
canEditMembersByModerator: values.canEditMembersByModerator,
displayOrder: values.displayOrder,
policies: values.policies,
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
this.globalEventService.publishInternalEvent('roleCreated', created);
if (moderator) {
this.moderationLogService.log(moderator, 'createRole', {
roleId: created.id,
role: created,
});
}
return created;
}
@bindThis @bindThis
public async update(role: MiRole, params: Partial<MiRole>, moderator?: MiUser): Promise<void> { public async update(role: MiRole, params: Partial<MiRole>, moderator?: MiUser): Promise<void> {
const date = new Date(); const date = new Date();

View file

@ -73,7 +73,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}); });
this.moderationLogService.log(me, 'resetPassword', { this.moderationLogService.log(me, 'resetPassword', {
targetId: user.id, userId: user.id,
userUsername: user.username,
userHost: user.host,
}); });
return { return {

View file

@ -5,11 +5,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RolesRepository } from '@/models/_.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
import { RoleService } from '@/core/RoleService.js';
export const meta = { export const meta = {
tags: ['admin', 'role'], tags: ['admin', 'role'],
@ -58,37 +55,11 @@ export const paramDef = {
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor( constructor(
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
private globalEventService: GlobalEventService,
private idService: IdService,
private roleEntityService: RoleEntityService, private roleEntityService: RoleEntityService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const date = new Date(); const created = await this.roleService.create(ps, me);
const created = await this.rolesRepository.insert({
id: this.idService.genId(),
createdAt: date,
updatedAt: date,
lastUsedAt: date,
name: ps.name,
description: ps.description,
color: ps.color,
iconUrl: ps.iconUrl,
target: ps.target,
condFormula: ps.condFormula,
isPublic: ps.isPublic,
isAdministrator: ps.isAdministrator,
isModerator: ps.isModerator,
isExplorable: ps.isExplorable,
asBadge: ps.asBadge,
canEditMembersByModerator: ps.canEditMembersByModerator,
displayOrder: ps.displayOrder,
policies: ps.policies,
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
this.globalEventService.publishInternalEvent('roleCreated', created);
return await this.roleEntityService.pack(created, me); return await this.roleEntityService.pack(created, me);
}); });

View file

@ -79,9 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchRole); throw new ApiError(meta.errors.noSuchRole);
} }
const date = new Date();
await this.roleService.update(role, { await this.roleService.update(role, {
updatedAt: date,
name: ps.name, name: ps.name,
description: ps.description, description: ps.description,
color: ps.color, color: ps.color,

View file

@ -61,7 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}); });
this.moderationLogService.log(me, 'suspend', { this.moderationLogService.log(me, 'suspend', {
targetId: user.id, userId: user.id,
userUsername: user.username,
userHost: user.host,
}); });
(async () => { (async () => {

View file

@ -46,7 +46,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}); });
this.moderationLogService.log(me, 'unsuspend', { this.moderationLogService.log(me, 'unsuspend', {
targetId: user.id, userId: user.id,
userUsername: user.username,
userHost: user.host,
}); });
this.userSuspendService.doPostUnsuspend(user); this.userSuspendService.doPostUnsuspend(user);

View file

@ -51,6 +51,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.moderationLogService.log(me, 'updateUserNote', { this.moderationLogService.log(me, 'updateUserNote', {
userId: user.id, userId: user.id,
userUsername: user.username,
userHost: user.host,
before: currentProfile.moderationNote, before: currentProfile.moderationNote,
after: ps.text, after: ps.text,
}); });

View file

@ -37,6 +37,7 @@ export const moderationLogTypes = [
'deleteCustomEmoji', 'deleteCustomEmoji',
'assignRole', 'assignRole',
'unassignRole', 'unassignRole',
'createRole',
'updateRole', 'updateRole',
'deleteRole', 'deleteRole',
'clearQueue', 'clearQueue',
@ -62,13 +63,19 @@ export type ModerationLogPayloads = {
after: any | null; after: any | null;
}; };
suspend: { suspend: {
targetId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
unsuspend: { unsuspend: {
targetId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
updateUserNote: { updateUserNote: {
userId: string; userId: string;
userUsername: string;
userHost: string | null;
before: string | null; before: string | null;
after: string | null; after: string | null;
}; };
@ -87,15 +94,23 @@ export type ModerationLogPayloads = {
}; };
assignRole: { assignRole: {
userId: string; userId: string;
userUsername: string;
userHost: string | null;
roleId: string; roleId: string;
roleName: string; roleName: string;
expiresAt: string | null; expiresAt: string | null;
}; };
unassignRole: { unassignRole: {
userId: string; userId: string;
userUsername: string;
userHost: string | null;
roleId: string; roleId: string;
roleName: string; roleName: string;
}; };
createRole: {
roleId: string;
role: any;
};
updateRole: { updateRole: {
roleId: string; roleId: string;
before: any; before: any;
@ -110,10 +125,14 @@ export type ModerationLogPayloads = {
deleteDriveFile: { deleteDriveFile: {
fileId: string; fileId: string;
fileUserId: string | null; fileUserId: string | null;
fileUserUsername: string | null;
fileUserHost: string | null;
}; };
deleteNote: { deleteNote: {
noteId: string; noteId: string;
noteUserId: string; noteUserId: string;
noteUserUsername: string;
noteUserHost: string | null;
note: any; note: any;
}; };
createGlobalAnnouncement: { createGlobalAnnouncement: {
@ -124,6 +143,8 @@ export type ModerationLogPayloads = {
announcementId: string; announcementId: string;
announcement: any; announcement: any;
userId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
updateGlobalAnnouncement: { updateGlobalAnnouncement: {
announcementId: string; announcementId: string;
@ -134,6 +155,9 @@ export type ModerationLogPayloads = {
announcementId: string; announcementId: string;
before: any; before: any;
after: any; after: any;
userId: string;
userUsername: string;
userHost: string | null;
}; };
deleteGlobalAnnouncement: { deleteGlobalAnnouncement: {
announcementId: string; announcementId: string;
@ -144,7 +168,9 @@ export type ModerationLogPayloads = {
announcement: any; announcement: any;
}; };
resetPassword: { resetPassword: {
targetId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
suspendRemoteInstance: { suspendRemoteInstance: {
id: string; id: string;
@ -157,9 +183,13 @@ export type ModerationLogPayloads = {
markSensitiveDriveFile: { markSensitiveDriveFile: {
fileId: string; fileId: string;
fileUserId: string | null; fileUserId: string | null;
fileUserUsername: string | null;
fileUserHost: string | null;
}; };
unmarkSensitiveDriveFile: { unmarkSensitiveDriveFile: {
fileId: string; fileId: string;
fileUserId: string | null; fileUserId: string | null;
fileUserUsername: string | null;
fileUserHost: string | null;
}; };
}; };

View file

@ -5,33 +5,61 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<MkFolder> <MkFolder>
<template #label>{{ i18n.ts._moderationLogTypes[log.type] }}</template> <template #label>
<b>{{ i18n.ts._moderationLogTypes[log.type] }}</b>
<span v-if="log.type === 'updateUserNote'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'suspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'unsuspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'assignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'unassignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'createRole'">: {{ log.info.role.name }}</span>
<span v-else-if="log.type === 'updateRole'">: {{ log.info.before.name }}</span>
<span v-else-if="log.type === 'deleteRole'">: {{ log.info.role.name }}</span>
<span v-else-if="log.type === 'updateCustomEmoji'">: {{ log.info.before.name }}</span>
<span v-else-if="log.type === 'markSensitiveDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
<span v-else-if="log.type === 'unmarkSensitiveDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
<span v-else-if="log.type === 'suspendRemoteInstance'">: {{ log.info.host }}</span>
<span v-else-if="log.type === 'unsuspendRemoteInstance'">: {{ log.info.host }}</span>
<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
</template>
<template #icon> <template #icon>
<MkAvatar :user="log.user" :class="$style.avatar"/> <MkAvatar :user="log.user" :class="$style.avatar"/>
</template> </template>
<template #suffix> <template #suffix>
<MkTime :time="log.createdAt" mode="detail"/> <MkTime :time="log.createdAt"/>
</template> </template>
<div :class="$style.root"> <div :class="$style.root">
<div>{{ i18n.ts.moderator }}: {{ log.userId }}</div> <div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
<div style="flex: 1;">{{ i18n.ts.moderator }}: <MkA :to="`/admin/user/${log.userId}`" class="_link">@{{ log.user?.username }}</MkA></div>
<div style="flex: 1;">{{ i18n.ts.dateAndTime }}: <MkTime :time="log.createdAt" mode="detail"/></div>
</div>
<template v-if="log.type === 'updateServerSettings'"> <template v-if="log.type === 'updateServerSettings'">
<div :class="$style.diff"> <div :class="$style.diff">
<CodeDiff :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/> <CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div> </div>
</template> </template>
<template v-else-if="log.type === 'updateUserNote'"> <template v-else-if="log.type === 'updateUserNote'">
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div> <div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
<div :class="$style.diff"> <div :class="$style.diff">
<CodeDiff :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/> <CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div> </div>
</template> </template>
<template v-else-if="log.type === 'suspend'"> <template v-else-if="log.type === 'suspend'">
<div>{{ i18n.ts.user }}: {{ log.info.targetId }}</div> <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
</template> </template>
<template v-else-if="log.type === 'unsuspend'"> <template v-else-if="log.type === 'unsuspend'">
<div>{{ i18n.ts.user }}: {{ log.info.targetId }}</div> <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
</template>
<template v-else-if="log.type === 'updateRole'">
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template> </template>
<template v-else-if="log.type === 'assignRole'"> <template v-else-if="log.type === 'assignRole'">
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div> <div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
@ -41,6 +69,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div> <div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
<div>{{ i18n.ts.role }}: {{ log.info.roleName }} [{{ log.info.roleId }}]</div> <div>{{ i18n.ts.role }}: {{ log.info.roleName }} [{{ log.info.roleId }}]</div>
</template> </template>
<template v-else-if="log.type === 'updateCustomEmoji'">
<div>{{ i18n.ts.emoji }}: {{ log.info.emojiId }}</div>
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
</div> </div>
</MkFolder> </MkFolder>
</template> </template>

View file

@ -2550,6 +2550,9 @@ type ModerationLog = {
} | { } | {
type: 'unassignRole'; type: 'unassignRole';
info: ModerationLogPayloads['unassignRole']; info: ModerationLogPayloads['unassignRole'];
} | {
type: 'createRole';
info: ModerationLogPayloads['createRole'];
} | { } | {
type: 'updateRole'; type: 'updateRole';
info: ModerationLogPayloads['updateRole']; info: ModerationLogPayloads['updateRole'];
@ -2604,7 +2607,7 @@ type ModerationLog = {
}); });
// @public (undocumented) // @public (undocumented)
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", "markSensitiveDriveFile", "unmarkSensitiveDriveFile"]; export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile"];
// @public (undocumented) // @public (undocumented)
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];

View file

@ -55,6 +55,7 @@ export const moderationLogTypes = [
'deleteCustomEmoji', 'deleteCustomEmoji',
'assignRole', 'assignRole',
'unassignRole', 'unassignRole',
'createRole',
'updateRole', 'updateRole',
'deleteRole', 'deleteRole',
'clearQueue', 'clearQueue',
@ -80,13 +81,19 @@ export type ModerationLogPayloads = {
after: any | null; after: any | null;
}; };
suspend: { suspend: {
targetId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
unsuspend: { unsuspend: {
targetId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
updateUserNote: { updateUserNote: {
userId: string; userId: string;
userUsername: string;
userHost: string | null;
before: string | null; before: string | null;
after: string | null; after: string | null;
}; };
@ -105,15 +112,23 @@ export type ModerationLogPayloads = {
}; };
assignRole: { assignRole: {
userId: string; userId: string;
userUsername: string;
userHost: string | null;
roleId: string; roleId: string;
roleName: string; roleName: string;
expiresAt: string | null; expiresAt: string | null;
}; };
unassignRole: { unassignRole: {
userId: string; userId: string;
userUsername: string;
userHost: string | null;
roleId: string; roleId: string;
roleName: string; roleName: string;
}; };
createRole: {
roleId: string;
role: any;
};
updateRole: { updateRole: {
roleId: string; roleId: string;
before: any; before: any;
@ -128,10 +143,14 @@ export type ModerationLogPayloads = {
deleteDriveFile: { deleteDriveFile: {
fileId: string; fileId: string;
fileUserId: string | null; fileUserId: string | null;
fileUserUsername: string | null;
fileUserHost: string | null;
}; };
deleteNote: { deleteNote: {
noteId: string; noteId: string;
noteUserId: string; noteUserId: string;
noteUserUsername: string;
noteUserHost: string | null;
note: any; note: any;
}; };
createGlobalAnnouncement: { createGlobalAnnouncement: {
@ -142,6 +161,8 @@ export type ModerationLogPayloads = {
announcementId: string; announcementId: string;
announcement: any; announcement: any;
userId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
updateGlobalAnnouncement: { updateGlobalAnnouncement: {
announcementId: string; announcementId: string;
@ -152,6 +173,9 @@ export type ModerationLogPayloads = {
announcementId: string; announcementId: string;
before: any; before: any;
after: any; after: any;
userId: string;
userUsername: string;
userHost: string | null;
}; };
deleteGlobalAnnouncement: { deleteGlobalAnnouncement: {
announcementId: string; announcementId: string;
@ -162,7 +186,9 @@ export type ModerationLogPayloads = {
announcement: any; announcement: any;
}; };
resetPassword: { resetPassword: {
targetId: string; userId: string;
userUsername: string;
userHost: string | null;
}; };
suspendRemoteInstance: { suspendRemoteInstance: {
id: string; id: string;
@ -175,9 +201,13 @@ export type ModerationLogPayloads = {
markSensitiveDriveFile: { markSensitiveDriveFile: {
fileId: string; fileId: string;
fileUserId: string | null; fileUserId: string | null;
fileUserUsername: string | null;
fileUserHost: string | null;
}; };
unmarkSensitiveDriveFile: { unmarkSensitiveDriveFile: {
fileId: string; fileId: string;
fileUserId: string | null; fileUserId: string | null;
fileUserUsername: string | null;
fileUserHost: string | null;
}; };
}; };

View file

@ -601,6 +601,9 @@ export type ModerationLog = {
} | { } | {
type: 'unassignRole'; type: 'unassignRole';
info: ModerationLogPayloads['unassignRole']; info: ModerationLogPayloads['unassignRole'];
} | {
type: 'createRole';
info: ModerationLogPayloads['createRole'];
} | { } | {
type: 'updateRole'; type: 'updateRole';
info: ModerationLogPayloads['updateRole']; info: ModerationLogPayloads['updateRole'];