summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-02-26 23:18:30 -0500
committerHazelnoot <acomputerdog@gmail.com>2025-03-02 13:38:02 -0500
commit27d43879a24b9243657af50d32d459a36b6596ec (patch)
tree29c224698365a5163022a01fe2f7acd9793d4d9f
parentmerge: Remove assertActivityMatchesUrls in favor of three-way same-authority ... (diff)
downloadsharkey-27d43879a24b9243657af50d32d459a36b6596ec.tar.gz
sharkey-27d43879a24b9243657af50d32d459a36b6596ec.tar.bz2
sharkey-27d43879a24b9243657af50d32d459a36b6596ec.zip
add moderation logs for many endpoints
- `/admin/delete-all-files-of-a-user` - `/admin/nsfw-user` - `/admin/unnsfw-user` - `/admin/silence-user` - `/admin/unsilence-user` - `/admin/accounts/create` - `/admin/drive/clean-remote-files` - `/admin/drive/cleanup` - `/admin/emoji/set-category-bulk` - `/admin/emoji/set-license-bulk` - `/admin/emoji/set-aliases-bulk` - `/admin/emoji/add-aliases-bulk` - `/admin/emoji/remove-aliases-bulk` - `/admin/emoji/import-zip` - `/admin/federation/delete-all-files` - `/admin/federation/remove-all-following` - `/admin/promo/create` - `/admin/relay/add` - `/admin/relay/remove`
-rw-r--r--locales/index.d.ts72
-rw-r--r--packages/backend/src/server/api/endpoints/admin/accounts/create.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/nsfw-user.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/admin/promo/create.ts14
-rw-r--r--packages/backend/src/server/api/endpoints/admin/relays/add.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/relays/remove.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/admin/silence-user.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/admin/unnsfw-user.ts21
-rw-r--r--packages/backend/src/server/api/endpoints/admin/unsilence-user.ts22
-rw-r--r--packages/backend/src/types.ts81
-rw-r--r--packages/frontend/src/pages/admin/modlog.ModLog.vue55
-rw-r--r--packages/misskey-js/etc/misskey-js.api.md45
-rw-r--r--packages/misskey-js/src/entities.ts45
-rw-r--r--sharkey-locales/en-US.yml17
25 files changed, 480 insertions, 43 deletions
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 6a2790c9af..4907754394 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1403,7 +1403,7 @@ export interface Locale extends ILocale {
*/
"inputNewFileName": string;
/**
- * 新しいキャプションを入力してください
+ * Enter new alt text
*/
"inputNewDescription": string;
/**
@@ -2603,11 +2603,11 @@ export interface Locale extends ILocale {
*/
"description": string;
/**
- * キャプションを付ける
+ * Add alt text
*/
"describeFile": string;
/**
- * キャプションを入力
+ * Enter alt text
*/
"enterFileDescription": string;
/**
@@ -4084,7 +4084,7 @@ export interface Locale extends ILocale {
*/
"windowRestore": string;
/**
- * キャプション
+ * Alt text
*/
"caption": string;
/**
@@ -10254,6 +10254,66 @@ export interface Locale extends ILocale {
* Allowed quote posts from user
*/
"allowQuotesUser": string;
+ /**
+ * Cleared a user's drive files
+ */
+ "clearUserFiles": string;
+ /**
+ * Marked user as NSFW
+ */
+ "nsfwUser": string;
+ /**
+ * Un-marked user as NSFW
+ */
+ "unNsfwUser": string;
+ /**
+ * Silenced user
+ */
+ "silenceUser": string;
+ /**
+ * Un-silenced user
+ */
+ "unSilenceUser": string;
+ /**
+ * Created an account
+ */
+ "createAccount": string;
+ /**
+ * Cleared remote drive files
+ */
+ "clearRemoteFiles": string;
+ /**
+ * Cleared owner-less drive files
+ */
+ "clearOwnerlessFiles": string;
+ /**
+ * Updated custom emojis
+ */
+ "updateCustomEmojis": string;
+ /**
+ * Imported custom emojis
+ */
+ "importCustomEmojis": string;
+ /**
+ * Cleared an instance's drive files
+ */
+ "clearInstanceFiles": string;
+ /**
+ * Severed follow relations with an instance
+ */
+ "severFollowRelations": string;
+ /**
+ * Created a note promo
+ */
+ "createPromo": string;
+ /**
+ * Added a relay
+ */
+ "addRelay": string;
+ /**
+ * Removed a relay
+ */
+ "removeRelay": string;
};
"_fileViewer": {
/**
@@ -11601,6 +11661,10 @@ export interface Locale extends ILocale {
* Flash
*/
"flash": string;
+ /**
+ * Files removed:
+ */
+ "filesRemoved": string;
"_flash": {
/**
* Flash Content Hidden
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index 5843457676..1a47f56bc6 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -15,6 +15,7 @@ import type { Config } from '@/config.js';
import { ApiError } from '@/server/api/error.js';
import { Packed } from '@/misc/json-schema.js';
import { RoleService } from '@/core/RoleService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -96,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userEntityService: UserEntityService,
private signupService: SignupService,
private instanceActorService: InstanceActorService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, _me, token) => {
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
@@ -137,6 +139,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
approved: true,
});
+ if (me) {
+ await this.moderationLogService.log(me, 'createAccount', {
+ userId: account.id,
+ userUsername: account.username,
+ });
+ }
+
const res = await this.userEntityService.pack(account, account, {
schema: 'MeDetailed',
includeSecrets: true,
diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
index 747c9f48d0..8b4a450ccb 100644
--- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
@@ -8,6 +8,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DriveService } from '@/core/DriveService.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { CacheService } from '@/core/CacheService.js';
export const meta = {
tags: ['admin'],
@@ -30,14 +32,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
-
+ private readonly cacheService: CacheService,
+ private readonly moderationLogService: ModerationLogService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
+ const user = await this.cacheService.findUserById(ps.userId);
const files = await this.driveFilesRepository.findBy({
userId: ps.userId,
});
+ await this.moderationLogService.log(me, 'clearUserFiles', {
+ userId: ps.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ count: files.length,
+ });
+
for (const file of files) {
this.driveService.deleteFile(file, false, me);
}
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
index d420a929bd..9a7b3d5d62 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
@@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueueService } from '@/core/QueueService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -25,9 +26,11 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private queueService: QueueService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
- this.queueService.createCleanRemoteFilesJob();
+ await this.moderationLogService.log(me, 'clearRemoteFiles', {});
+ await this.queueService.createCleanRemoteFilesJob();
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
index d612572e2e..f5d20366cf 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
@@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DriveService } from '@/core/DriveService.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -29,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
-
+ private readonly moderationLogService: ModerationLogService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -37,6 +38,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userId: IsNull(),
});
+ await this.moderationLogService.log(me, 'clearOwnerlessFiles', {
+ count: files.length,
+ });
+
for (const file of files) {
this.driveService.deleteFile(file);
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index f4fc79bdb3..795b579041 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -32,8 +33,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
+ await this.moderationLogService.log(me, 'updateCustomEmojis', {
+ ids: ps.ids,
+ addAliases: ps.aliases,
+ });
await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
index 8e5f69c894..b3dc3978d1 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
@@ -3,9 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueueService } from '@/core/QueueService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import type { DriveFilesRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
secure: true,
@@ -25,9 +28,17 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private queueService: QueueService,
+ private readonly moderationLogService: ModerationLogService,
+ @Inject(DI.driveFilesRepository)
+ private readonly driveFilesRepository: DriveFilesRepository,
) {
super(meta, paramDef, async (ps, me) => {
- this.queueService.createImportCustomEmojisJob(me, ps.fileId);
+ const file = await driveFilesRepository.findOneByOrFail({ id: ps.fileId });
+ await this.moderationLogService.log(me, 'importCustomEmojis', {
+ fileId: file.id,
+ fileName: file.name,
+ });
+ await this.queueService.createImportCustomEmojisJob(me, ps.fileId);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
index e78620eac1..066eb1c7d9 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
@@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -32,8 +33,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
+ await this.moderationLogService.log(me, 'updateCustomEmojis', {
+ ids: ps.ids,
+ delAliases: ps.aliases,
+ });
await this.customEmojiService.removeAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
index 69fc8e0cb5..8980ef0c86 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
@@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -32,8 +33,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
+ await this.moderationLogService.log(me, 'updateCustomEmojis', {
+ ids: ps.ids,
+ setAliases: ps.aliases,
+ });
await this.customEmojiService.setAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index 679a9f9c42..2510349210 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -34,8 +35,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
+ await this.moderationLogService.log(me, 'updateCustomEmojis', {
+ ids: ps.ids,
+ category: ps.category,
+ });
await this.customEmojiService.setCategoryBulk(ps.ids, ps.category?.normalize('NFC') ?? null);
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
index 4ba99faab7..a0205ae24a 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
@@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -34,8 +35,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
+ await this.moderationLogService.log(me, 'updateCustomEmojis', {
+ ids: ps.ids,
+ license: ps.license,
+ });
await this.customEmojiService.setLicenseBulk(ps.ids, ps.license ?? null);
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
index 4a54c26009..89fd4be99c 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
@@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DriveService } from '@/core/DriveService.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -30,7 +31,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
-
+ private readonly moderationLogService: ModerationLogService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -38,6 +39,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userHost: ps.host,
});
+ await this.moderationLogService.log(me, 'clearInstanceFiles', {
+ host: ps.host,
+ count: files.length,
+ });
+
for (const file of files) {
this.driveService.deleteFile(file);
}
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
index 601c898f52..e5d85e1d57 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { QueueService } from '@/core/QueueService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -35,6 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private followingsRepository: FollowingsRepository,
private queueService: QueueService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const followings = await this.followingsRepository.findBy([
@@ -51,6 +53,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.usersRepository.findOneByOrFail({ id: f.followeeId }),
]).then(([from, to]) => [{ id: from.id }, { id: to.id }])));
+ await this.moderationLogService.log(me, 'severFollowRelations', {
+ host: ps.host,
+ });
+
this.queueService.createUnfollowJob(pairs.map(p => ({ from: p[0], to: p[1], silent: true })));
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts b/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts
index f64ba7f48a..194e793eda 100644
--- a/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts
@@ -5,9 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
+import type { UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { CacheService } from '@/core/CacheService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -28,20 +29,19 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.usersRepository)
- private readonly usersRepository: UsersRepository,
-
@Inject(DI.userProfilesRepository)
private readonly userProfilesRepository: UserProfilesRepository,
-
+ private readonly moderationLogService: ModerationLogService,
private readonly cacheService: CacheService,
) {
super(meta, paramDef, async (ps, me) => {
- const user = await this.usersRepository.findOneBy({ id: ps.userId });
+ const user = await this.cacheService.findUserById(ps.userId);
- if (user == null) {
- throw new Error('user not found');
- }
+ await this.moderationLogService.log(me, 'nsfwUser', {
+ userId: ps.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
await this.userProfilesRepository.update(user.id, {
alwaysMarkNsfw: true,
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index 1d32c6cc00..69891d5454 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -8,6 +8,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { PromoNotesRepository } from '@/models/_.js';
import { GetterService } from '@/server/api/GetterService.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { CacheService } from '@/core/CacheService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -46,7 +48,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.promoNotesRepository)
private promoNotesRepository: PromoNotesRepository,
-
+ private readonly moderationLogService: ModerationLogService,
+ private readonly cacheService: CacheService,
private getterService: GetterService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -61,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.alreadyPromoted);
}
+ const user = await this.cacheService.findUserById(note.userId);
+ await this.moderationLogService.log(me, 'createPromo', {
+ noteId: note.id,
+ noteUserId: user.id,
+ noteUserUsername: user.username,
+ noteUserHost: user.host,
+ note: note,
+ });
+
await this.promoNotesRepository.insert({
noteId: note.id,
expiresAt: new Date(ps.expiresAt),
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
index 3d7bc4567e..129f69aca9 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
@@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { RelayService } from '@/core/RelayService.js';
import { ApiError } from '../../../error.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -64,6 +65,7 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private relayService: RelayService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
try {
@@ -72,6 +74,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.invalidUrl);
}
+ await this.moderationLogService.log(me, 'addRelay', {
+ inbox: ps.inbox,
+ });
+
return await this.relayService.addRelay(ps.inbox);
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
index 1f6e773cd4..292f21fde9 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
@@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { RelayService } from '@/core/RelayService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -27,9 +28,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private relayService: RelayService,
+ private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
- return await this.relayService.removeRelay(ps.inbox);
+ await this.moderationLogService.log(me, 'removeRelay', {
+ inbox: ps.inbox,
+ });
+ await this.relayService.removeRelay(ps.inbox);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts
index 7e6045049a..eed21c6576 100644
--- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts
@@ -8,6 +8,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
export const meta = {
tags: ['admin'],
@@ -29,24 +32,32 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
- private roleService: RoleService,
+ private readonly usersRepository: UsersRepository,
+ private readonly cacheService: CacheService,
+ private readonly moderationLogService: ModerationLogService,
+ private readonly roleService: RoleService,
+ private readonly globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
- const user = await this.usersRepository.findOneBy({ id: ps.userId });
-
- if (user == null) {
- throw new Error('user not found');
- }
+ const user = await this.cacheService.findUserById(ps.userId);
if (await this.roleService.isModerator(user)) {
throw new Error('cannot silence moderator account');
}
+ await this.moderationLogService.log(me, 'silenceUser', {
+ userId: ps.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
+
await this.usersRepository.update(user.id, {
isSilenced: true,
});
+
+ this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
+ id: user.id,
+ });
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/unnsfw-user.ts b/packages/backend/src/server/api/endpoints/admin/unnsfw-user.ts
index 26588365e1..52a0c076be 100644
--- a/packages/backend/src/server/api/endpoints/admin/unnsfw-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unnsfw-user.ts
@@ -5,8 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
+import type { UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
+import { CacheService } from '@/core/CacheService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@@ -27,18 +29,19 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
+ private readonly cacheService: CacheService,
+ private readonly moderationLogService: ModerationLogService,
@Inject(DI.userProfilesRepository)
- private userProfilesRepository: UserProfilesRepository,
+ private readonly userProfilesRepository: UserProfilesRepository,
) {
super(meta, paramDef, async (ps, me) => {
- const user = await this.usersRepository.findOneBy({ id: ps.userId });
+ const user = await this.cacheService.findUserById(ps.userId);
- if (user == null) {
- throw new Error('user not found');
- }
+ await this.moderationLogService.log(me, 'unNsfwUser', {
+ userId: ps.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
await this.userProfilesRepository.update(user.id, {
alwaysMarkNsfw: false,
diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
index f92be0d8e0..9318943785 100644
--- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
@@ -7,6 +7,9 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
export const meta = {
tags: ['admin'],
@@ -28,18 +31,27 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
+ private readonly usersRepository: UsersRepository,
+ private readonly cacheService: CacheService,
+ private readonly moderationLogService: ModerationLogService,
+ private readonly globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
- const user = await this.usersRepository.findOneBy({ id: ps.userId });
+ const user = await this.cacheService.findUserById(ps.userId);
- if (user == null) {
- throw new Error('user not found');
- }
+ await this.moderationLogService.log(me, 'unSilenceUser', {
+ userId: ps.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
await this.usersRepository.update(user.id, {
isSilenced: false,
});
+
+ this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
+ id: user.id,
+ });
});
}
}
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index b5d982e3a5..977d6157bc 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -136,6 +136,21 @@ export const moderationLogTypes = [
'rejectQuotesUser',
'acceptQuotesInstance',
'rejectQuotesInstance',
+ 'clearUserFiles',
+ 'nsfwUser',
+ 'unNsfwUser',
+ 'silenceUser',
+ 'unSilenceUser',
+ 'createAccount',
+ 'clearRemoteFiles',
+ 'clearOwnerlessFiles',
+ 'updateCustomEmojis',
+ 'importCustomEmojis',
+ 'clearInstanceFiles',
+ 'severFollowRelations',
+ 'createPromo',
+ 'addRelay',
+ 'removeRelay',
] as const;
export type ModerationLogPayloads = {
@@ -439,6 +454,72 @@ export type ModerationLogPayloads = {
id: string;
host: string;
};
+ clearUserFiles: {
+ userId: string;
+ userUsername: string;
+ userHost: string | null;
+ count: number;
+ };
+ nsfwUser: {
+ userId: string;
+ userUsername: string;
+ userHost: string | null;
+ };
+ unNsfwUser: {
+ userId: string;
+ userUsername: string;
+ userHost: string | null;
+ };
+ silenceUser: {
+ userId: string;
+ userUsername: string;
+ userHost: string | null;
+ };
+ unSilenceUser: {
+ userId: string;
+ userUsername: string;
+ userHost: string | null;
+ };
+ createAccount: {
+ userId: string;
+ userUsername: string;
+ };
+ clearRemoteFiles: Record<string, never>;
+ clearOwnerlessFiles: {
+ count: number;
+ };
+ updateCustomEmojis: {
+ ids: string[],
+ category?: string | null,
+ license?: string | null,
+ setAliases?: string[],
+ addAliases?: string[],
+ delAliases?: string[],
+ },
+ importCustomEmojis: {
+ fileId: string,
+ fileName: string,
+ },
+ clearInstanceFiles: {
+ host: string;
+ count: number;
+ },
+ severFollowRelations: {
+ host: string;
+ },
+ createPromo: {
+ noteId: string,
+ noteUserId: string;
+ noteUserUsername: string;
+ noteUserHost: string | null;
+ note: any;
+ },
+ addRelay: {
+ inbox: string;
+ },
+ removeRelay: {
+ inbox: string;
+ },
};
export type Serialized<T> = {
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index 306a873173..a4731af47b 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -18,6 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only
'createAvatarDecoration',
'createSystemWebhook',
'createAbuseReportNotificationRecipient',
+ 'createAccount',
+ 'importCustomEmojis',
+ 'createPromo',
+ 'addRelay',
].includes(log.type),
[$style.logYellow]: [
'markSensitiveDriveFile',
@@ -30,6 +34,11 @@ SPDX-License-Identifier: AGPL-3.0-only
'acceptRemoteInstanceReports',
'rejectQuotesUser',
'acceptQuotesUser',
+ 'nsfwUser',
+ 'unNsfwUser',
+ 'silenceUser',
+ 'unSilenceUser',
+ 'updateCustomEmojis',
].includes(log.type),
[$style.logRed]: [
'suspend',
@@ -49,6 +58,12 @@ SPDX-License-Identifier: AGPL-3.0-only
'deletePage',
'deleteFlash',
'deleteGalleryPost',
+ 'clearUserFiles',
+ 'clearRemoteFiles',
+ 'clearOwnerlessFiles',
+ 'clearInstanceFiles',
+ 'severFollowRelations',
+ 'removeRelay',
].includes(log.type)
}"
>{{ i18n.ts._moderationLogTypes[log.type] }}</b>
@@ -100,6 +115,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'deletePage'">: @{{ log.info.pageUserUsername }}</span>
<span v-else-if="log.type === 'deleteFlash'">: @{{ log.info.flashUserUsername }}</span>
<span v-else-if="log.type === 'deleteGalleryPost'">: @{{ log.info.postUserUsername }}</span>
+ <span v-else-if="log.type === 'clearUserFiles'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'nsfwUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'unNsfwUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'silenceUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'unSilenceUser'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+ <span v-else-if="log.type === 'createAccount'">: @{{ log.info.userUsername }}</span>
+ <span v-else-if="log.type === 'importCustomEmojis'">: {{ log.info.fileName }}</span>
+ <span v-else-if="log.type === 'clearInstanceFiles'">: {{ log.info.host }}</span>
+ <span v-else-if="log.type === 'severFollowRelations'">: {{ log.info.host }}</span>
+ <span v-else-if="log.type === 'addRelay'">: {{ log.info.inbox }}</span>
+ <span v-else-if="log.type === 'removeRelay'">: {{ log.info.inbox }}</span>
</template>
<template v-if="log.user" #icon>
<MkAvatar :user="log.user" :class="$style.avatar"/>
@@ -205,6 +231,35 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div>
</template>
+ <template v-else-if="log.type === 'clearUserFiles'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
+ <div>{{ i18n.ts.filesRemoved }}: {{ log.info.count }}</div>
+ </template>
+ <template v-else-if="log.type === 'nsfwUser'">
+ <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 === 'unNsfwUser'">
+ <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 === 'silenceUser'">
+ <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 === 'unSilenceUser'">
+ <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 === 'createAccount'">
+ <div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}</MkA></div>
+ </template>
+ <template v-else-if="log.type === 'clearOwnerlessFiles'">
+ <div>{{ i18n.ts.filesRemoved }}: {{ log.info.count }}</div>
+ </template>
+ <template v-else-if="log.type === 'clearInstanceFiles'">
+ <div>{{ i18n.ts.host }}: <MkA :to="`/instance-info/${log.info.host}`" class="_link">{{ log.info.host }}</MkA></div>
+ <div>{{ i18n.ts.filesRemoved }}: {{ log.info.count }}</div>
+ </template>
+ <template v-else-if="log.type === 'severFollowRelations'">
+ <div>{{ i18n.ts.host }}: <MkA :to="`/instance-info/${log.info.host}`" class="_link">{{ log.info.host }}</MkA></div>
+ </template>
<details>
<summary>raw</summary>
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 24a5294c91..e5d4bb6143 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -2728,6 +2728,51 @@ type ModerationLog = {
} | {
type: 'deleteGalleryPost';
info: ModerationLogPayloads['deleteGalleryPost'];
+} | {
+ type: 'clearUserFiles';
+ info: ModerationLogPayloads['clearUserFiles'];
+} | {
+ type: 'nsfwUser';
+ info: ModerationLogPayloads['nsfwUser'];
+} | {
+ type: 'unNsfwUser';
+ info: ModerationLogPayloads['unNsfwUser'];
+} | {
+ type: 'silenceUser';
+ info: ModerationLogPayloads['silenceUser'];
+} | {
+ type: 'unSilenceUser';
+ info: ModerationLogPayloads['unSilenceUser'];
+} | {
+ type: 'createAccount';
+ info: ModerationLogPayloads['createAccount'];
+} | {
+ type: 'clearRemoteFiles';
+ info: ModerationLogPayloads['clearRemoteFiles'];
+} | {
+ type: 'clearOwnerlessFiles';
+ info: ModerationLogPayloads['clearOwnerlessFiles'];
+} | {
+ type: 'updateCustomEmojis';
+ info: ModerationLogPayloads['updateCustomEmojis'];
+} | {
+ type: 'importCustomEmojis';
+ info: ModerationLogPayloads['importCustomEmojis'];
+} | {
+ type: 'clearInstanceFiles';
+ info: ModerationLogPayloads['clearInstanceFiles'];
+} | {
+ type: 'severFollowRelations';
+ info: ModerationLogPayloads['severFollowRelations'];
+} | {
+ type: 'createPromo';
+ info: ModerationLogPayloads['createPromo'];
+} | {
+ type: 'addRelay';
+ info: ModerationLogPayloads['addRelay'];
+} | {
+ type: 'removeRelay';
+ info: ModerationLogPayloads['removeRelay'];
});
// @public (undocumented)
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 7b5d56f01c..3b31a6e531 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -228,6 +228,51 @@ export type ModerationLog = {
} | {
type: 'deleteGalleryPost';
info: ModerationLogPayloads['deleteGalleryPost'];
+} | {
+ type: 'clearUserFiles';
+ info: ModerationLogPayloads['clearUserFiles'];
+} | {
+ type: 'nsfwUser';
+ info: ModerationLogPayloads['nsfwUser'];
+} | {
+ type: 'unNsfwUser';
+ info: ModerationLogPayloads['unNsfwUser'];
+} | {
+ type: 'silenceUser';
+ info: ModerationLogPayloads['silenceUser'];
+} | {
+ type: 'unSilenceUser';
+ info: ModerationLogPayloads['unSilenceUser'];
+} | {
+ type: 'createAccount';
+ info: ModerationLogPayloads['createAccount'];
+} | {
+ type: 'clearRemoteFiles';
+ info: ModerationLogPayloads['clearRemoteFiles'];
+} | {
+ type: 'clearOwnerlessFiles';
+ info: ModerationLogPayloads['clearOwnerlessFiles'];
+} | {
+ type: 'updateCustomEmojis';
+ info: ModerationLogPayloads['updateCustomEmojis'];
+} | {
+ type: 'importCustomEmojis';
+ info: ModerationLogPayloads['importCustomEmojis'];
+} | {
+ type: 'clearInstanceFiles';
+ info: ModerationLogPayloads['clearInstanceFiles'];
+} | {
+ type: 'severFollowRelations';
+ info: ModerationLogPayloads['severFollowRelations'];
+} | {
+ type: 'createPromo';
+ info: ModerationLogPayloads['createPromo'];
+} | {
+ type: 'addRelay';
+ info: ModerationLogPayloads['addRelay'];
+} | {
+ type: 'removeRelay';
+ info: ModerationLogPayloads['removeRelay'];
});
export type ServerStats = {
diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml
index ca454d2272..7e69c9b97d 100644
--- a/sharkey-locales/en-US.yml
+++ b/sharkey-locales/en-US.yml
@@ -169,6 +169,7 @@ pinnedOnly: "Pinned"
blockingYou: "Blocking you"
warnExternalUrl: "Show warning when opening external URLs"
flash: "Flash"
+filesRemoved: "Files removed:"
_flash:
contentHidden: "Flash Content Hidden"
poweredByRuffle: "Powered by Ruffle."
@@ -320,6 +321,22 @@ _moderationLogTypes:
acceptRemoteInstanceReports: "Accepted reports from remote instance"
rejectQuotesUser: "Blocked/Stripped quote posts from user"
allowQuotesUser: "Allowed quote posts from user"
+ clearUserFiles: "Cleared a user's drive files"
+ nsfwUser: "Marked user as NSFW"
+ unNsfwUser: "Un-marked user as NSFW"
+ silenceUser: "Silenced user"
+ unSilenceUser: "Un-silenced user"
+ createAccount: "Created an account"
+ clearRemoteFiles: "Cleared remote drive files"
+ clearOwnerlessFiles: "Cleared owner-less drive files"
+ updateCustomEmojis: "Updated custom emojis"
+ importCustomEmojis: "Imported custom emojis"
+ clearInstanceFiles: "Cleared an instance's drive files"
+ severFollowRelations: "Severed follow relations with an instance"
+ createPromo: "Created a note promo"
+ addRelay: "Added a relay"
+ removeRelay: "Removed a relay"
+
_mfm:
uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks"
intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax."