mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-10-31 05:24:13 +00:00 
			
		
		
		
	View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/925 Closes #911 and #969 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
		
						commit
						141bce2be7
					
				
					 33 changed files with 694 additions and 59 deletions
				
			
		
							
								
								
									
										80
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										80
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							|  | @ -1403,7 +1403,7 @@ export interface Locale extends ILocale { | ||||||
|      */ |      */ | ||||||
|     "inputNewFileName": string; |     "inputNewFileName": string; | ||||||
|     /** |     /** | ||||||
|      * 新しいキャプションを入力してください |      * Enter new alt text | ||||||
|      */ |      */ | ||||||
|     "inputNewDescription": string; |     "inputNewDescription": string; | ||||||
|     /** |     /** | ||||||
|  | @ -2603,11 +2603,11 @@ export interface Locale extends ILocale { | ||||||
|      */ |      */ | ||||||
|     "description": string; |     "description": string; | ||||||
|     /** |     /** | ||||||
|      * キャプションを付ける |      * Add alt text | ||||||
|      */ |      */ | ||||||
|     "describeFile": string; |     "describeFile": string; | ||||||
|     /** |     /** | ||||||
|      * キャプションを入力 |      * Enter alt text | ||||||
|      */ |      */ | ||||||
|     "enterFileDescription": string; |     "enterFileDescription": string; | ||||||
|     /** |     /** | ||||||
|  | @ -4084,7 +4084,7 @@ export interface Locale extends ILocale { | ||||||
|      */ |      */ | ||||||
|     "windowRestore": string; |     "windowRestore": string; | ||||||
|     /** |     /** | ||||||
|      * キャプション |      * Alt text | ||||||
|      */ |      */ | ||||||
|     "caption": string; |     "caption": string; | ||||||
|     /** |     /** | ||||||
|  | @ -10254,6 +10254,66 @@ export interface Locale extends ILocale { | ||||||
|          * Allowed quote posts from user |          * Allowed quote posts from user | ||||||
|          */ |          */ | ||||||
|         "allowQuotesUser": string; |         "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": { |     "_fileViewer": { | ||||||
|         /** |         /** | ||||||
|  | @ -11601,6 +11661,18 @@ export interface Locale extends ILocale { | ||||||
|      * Flash |      * Flash | ||||||
|      */ |      */ | ||||||
|     "flash": string; |     "flash": string; | ||||||
|  |     /** | ||||||
|  |      * Files removed | ||||||
|  |      */ | ||||||
|  |     "filesRemoved": string; | ||||||
|  |     /** | ||||||
|  |      * File imported | ||||||
|  |      */ | ||||||
|  |     "fileImported": string; | ||||||
|  |     /** | ||||||
|  |      * Failed to load note | ||||||
|  |      */ | ||||||
|  |     "cannotLoadNote": string; | ||||||
|     "_flash": { |     "_flash": { | ||||||
|         /** |         /** | ||||||
|          * Flash Content Hidden |          * Flash Content Hidden | ||||||
|  |  | ||||||
|  | @ -162,7 +162,6 @@ export class NoteDeleteService { | ||||||
| 				noteUserId: note.userId, | 				noteUserId: note.userId, | ||||||
| 				noteUserUsername: user.username, | 				noteUserUsername: user.username, | ||||||
| 				noteUserHost: user.host, | 				noteUserHost: user.host, | ||||||
| 				note: note, |  | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import type { Config } from '@/config.js'; | ||||||
| import { ApiError } from '@/server/api/error.js'; | import { ApiError } from '@/server/api/error.js'; | ||||||
| import { Packed } from '@/misc/json-schema.js'; | import { Packed } from '@/misc/json-schema.js'; | ||||||
| import { RoleService } from '@/core/RoleService.js'; | import { RoleService } from '@/core/RoleService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
|  | @ -96,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 		private userEntityService: UserEntityService, | 		private userEntityService: UserEntityService, | ||||||
| 		private signupService: SignupService, | 		private signupService: SignupService, | ||||||
| 		private instanceActorService: InstanceActorService, | 		private instanceActorService: InstanceActorService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, _me, token) => { | 		super(meta, paramDef, async (ps, _me, token) => { | ||||||
| 			const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null; | 			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, | 				approved: true, | ||||||
| 			}); | 			}); | ||||||
| 
 | 
 | ||||||
|  | 			if (me) { | ||||||
|  | 				await this.moderationLogService.log(me, 'createAccount', { | ||||||
|  | 					userId: account.id, | ||||||
|  | 					userUsername: account.username, | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			const res = await this.userEntityService.pack(account, account, { | 			const res = await this.userEntityService.pack(account, account, { | ||||||
| 				schema: 'MeDetailed', | 				schema: 'MeDetailed', | ||||||
| 				includeSecrets: true, | 				includeSecrets: true, | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { DriveFilesRepository } from '@/models/_.js'; | import type { DriveFilesRepository } from '@/models/_.js'; | ||||||
| import { DriveService } from '@/core/DriveService.js'; | import { DriveService } from '@/core/DriveService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
|  | import { CacheService } from '@/core/CacheService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
|  | @ -30,14 +32,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 	constructor( | 	constructor( | ||||||
| 		@Inject(DI.driveFilesRepository) | 		@Inject(DI.driveFilesRepository) | ||||||
| 		private driveFilesRepository: DriveFilesRepository, | 		private driveFilesRepository: DriveFilesRepository, | ||||||
| 
 | 		private readonly cacheService: CacheService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 		private driveService: DriveService, | 		private driveService: DriveService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
|  | 			const user = await this.cacheService.findUserById(ps.userId); | ||||||
| 			const files = await this.driveFilesRepository.findBy({ | 			const files = await this.driveFilesRepository.findBy({ | ||||||
| 				userId: ps.userId, | 				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) { | 			for (const file of files) { | ||||||
| 				this.driveService.deleteFile(file, false, me); | 				this.driveService.deleteFile(file, false, me); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { QueueService } from '@/core/QueueService.js'; | import { QueueService } from '@/core/QueueService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private queueService: QueueService, | 		private queueService: QueueService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
| 			this.queueService.createCleanRemoteFilesJob(); | 			await this.moderationLogService.log(me, 'clearRemoteFiles', {}); | ||||||
|  | 			await this.queueService.createCleanRemoteFilesJob(); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { DriveFilesRepository } from '@/models/_.js'; | import type { DriveFilesRepository } from '@/models/_.js'; | ||||||
| import { DriveService } from '@/core/DriveService.js'; | import { DriveService } from '@/core/DriveService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
|  | @ -29,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 	constructor( | 	constructor( | ||||||
| 		@Inject(DI.driveFilesRepository) | 		@Inject(DI.driveFilesRepository) | ||||||
| 		private driveFilesRepository: DriveFilesRepository, | 		private driveFilesRepository: DriveFilesRepository, | ||||||
| 
 | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 		private driveService: DriveService, | 		private driveService: DriveService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
|  | @ -37,6 +38,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 				userId: IsNull(), | 				userId: IsNull(), | ||||||
| 			}); | 			}); | ||||||
| 
 | 
 | ||||||
|  | 			await this.moderationLogService.log(me, 'clearOwnerlessFiles', { | ||||||
|  | 				count: files.length, | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
| 			for (const file of files) { | 			for (const file of files) { | ||||||
| 				this.driveService.deleteFile(file); | 				this.driveService.deleteFile(file); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private customEmojiService: CustomEmojiService, | 		private customEmojiService: CustomEmojiService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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'))); | 			await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC'))); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -3,9 +3,12 @@ | ||||||
|  * SPDX-License-Identifier: AGPL-3.0-only |  * 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 { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { QueueService } from '@/core/QueueService.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 = { | export const meta = { | ||||||
| 	secure: true, | 	secure: true, | ||||||
|  | @ -25,9 +28,16 @@ export const paramDef = { | ||||||
| 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( | ||||||
| 		private queueService: QueueService, | 		private queueService: QueueService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
|  | 		@Inject(DI.driveFilesRepository) | ||||||
|  | 		private readonly driveFilesRepository: DriveFilesRepository, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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', { | ||||||
|  | 				fileName: file.name, | ||||||
|  | 			}); | ||||||
|  | 			await this.queueService.createImportCustomEmojisJob(me, ps.fileId); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private customEmojiService: CustomEmojiService, | 		private customEmojiService: CustomEmojiService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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'))); | 			await this.customEmojiService.removeAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC'))); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private customEmojiService: CustomEmojiService, | 		private customEmojiService: CustomEmojiService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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'))); | 			await this.customEmojiService.setAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC'))); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private customEmojiService: CustomEmojiService, | 		private customEmojiService: CustomEmojiService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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); | 			await this.customEmojiService.setCategoryBulk(ps.ids, ps.category?.normalize('NFC') ?? null); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private customEmojiService: CustomEmojiService, | 		private customEmojiService: CustomEmojiService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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); | 			await this.customEmojiService.setLicenseBulk(ps.ids, ps.license ?? null); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { DriveFilesRepository } from '@/models/_.js'; | import type { DriveFilesRepository } from '@/models/_.js'; | ||||||
| import { DriveService } from '@/core/DriveService.js'; | import { DriveService } from '@/core/DriveService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
|  | @ -30,7 +31,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 	constructor( | 	constructor( | ||||||
| 		@Inject(DI.driveFilesRepository) | 		@Inject(DI.driveFilesRepository) | ||||||
| 		private driveFilesRepository: DriveFilesRepository, | 		private driveFilesRepository: DriveFilesRepository, | ||||||
| 
 | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 		private driveService: DriveService, | 		private driveService: DriveService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
|  | @ -38,6 +39,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 				userHost: ps.host, | 				userHost: ps.host, | ||||||
| 			}); | 			}); | ||||||
| 
 | 
 | ||||||
|  | 			await this.moderationLogService.log(me, 'clearInstanceFiles', { | ||||||
|  | 				host: ps.host, | ||||||
|  | 				count: files.length, | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
| 			for (const file of files) { | 			for (const file of files) { | ||||||
| 				this.driveService.deleteFile(file); | 				this.driveService.deleteFile(file); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { FollowingsRepository, UsersRepository } from '@/models/_.js'; | import type { FollowingsRepository, UsersRepository } from '@/models/_.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { QueueService } from '@/core/QueueService.js'; | import { QueueService } from '@/core/QueueService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
|  | @ -35,6 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 		private followingsRepository: FollowingsRepository, | 		private followingsRepository: FollowingsRepository, | ||||||
| 
 | 
 | ||||||
| 		private queueService: QueueService, | 		private queueService: QueueService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
| 			const followings = await this.followingsRepository.findBy([ | 			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 }), | 				this.usersRepository.findOneByOrFail({ id: f.followeeId }), | ||||||
| 			]).then(([from, to]) => [{ id: from.id }, { id: to.id }]))); | 			]).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 }))); | 			this.queueService.createUnfollowJob(pairs.map(p => ({ from: p[0], to: p[1], silent: true }))); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -5,9 +5,10 @@ | ||||||
| 
 | 
 | ||||||
| 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 { UsersRepository, UserProfilesRepository } from '@/models/_.js'; | import type { UserProfilesRepository } from '@/models/_.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { CacheService } from '@/core/CacheService.js'; | import { CacheService } from '@/core/CacheService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
|  | @ -28,20 +29,19 @@ 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.usersRepository) |  | ||||||
| 		private readonly usersRepository: UsersRepository, |  | ||||||
| 
 |  | ||||||
| 		@Inject(DI.userProfilesRepository) | 		@Inject(DI.userProfilesRepository) | ||||||
| 		private readonly userProfilesRepository: UserProfilesRepository, | 		private readonly userProfilesRepository: UserProfilesRepository, | ||||||
| 
 | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 		private readonly cacheService: CacheService, | 		private readonly cacheService: CacheService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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) { | 			await this.moderationLogService.log(me, 'nsfwUser', { | ||||||
| 				throw new Error('user not found'); | 				userId: ps.userId, | ||||||
| 			} | 				userUsername: user.username, | ||||||
|  | 				userHost: user.host, | ||||||
|  | 			}); | ||||||
| 
 | 
 | ||||||
| 			await this.userProfilesRepository.update(user.id, { | 			await this.userProfilesRepository.update(user.id, { | ||||||
| 				alwaysMarkNsfw: true, | 				alwaysMarkNsfw: true, | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { PromoNotesRepository } from '@/models/_.js'; | import type { PromoNotesRepository } from '@/models/_.js'; | ||||||
| import { GetterService } from '@/server/api/GetterService.js'; | import { GetterService } from '@/server/api/GetterService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
|  | import { CacheService } from '@/core/CacheService.js'; | ||||||
| import { ApiError } from '../../../error.js'; | import { ApiError } from '../../../error.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
|  | @ -46,7 +48,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 	constructor( | 	constructor( | ||||||
| 		@Inject(DI.promoNotesRepository) | 		@Inject(DI.promoNotesRepository) | ||||||
| 		private promoNotesRepository: PromoNotesRepository, | 		private promoNotesRepository: PromoNotesRepository, | ||||||
| 
 | 		private readonly moderationLogService: ModerationLogService, | ||||||
|  | 		private readonly cacheService: CacheService, | ||||||
| 		private getterService: GetterService, | 		private getterService: GetterService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
|  | @ -61,6 +64,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 				throw new ApiError(meta.errors.alreadyPromoted); | 				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, | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
| 			await this.promoNotesRepository.insert({ | 			await this.promoNotesRepository.insert({ | ||||||
| 				noteId: note.id, | 				noteId: note.id, | ||||||
| 				expiresAt: new Date(ps.expiresAt), | 				expiresAt: new Date(ps.expiresAt), | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { RelayService } from '@/core/RelayService.js'; | import { RelayService } from '@/core/RelayService.js'; | ||||||
| import { ApiError } from '../../../error.js'; | import { ApiError } from '../../../error.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private relayService: RelayService, | 		private relayService: RelayService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
| 			try { | 			try { | ||||||
|  | @ -72,6 +74,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 				throw new ApiError(meta.errors.invalidUrl); | 				throw new ApiError(meta.errors.invalidUrl); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			await this.moderationLogService.log(me, 'addRelay', { | ||||||
|  | 				inbox: ps.inbox, | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
| 			return await this.relayService.addRelay(ps.inbox); | 			return await this.relayService.addRelay(ps.inbox); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| import { Injectable } from '@nestjs/common'; | import { Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { RelayService } from '@/core/RelayService.js'; | import { RelayService } from '@/core/RelayService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		private relayService: RelayService, | 		private relayService: RelayService, | ||||||
|  | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,6 +8,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { UsersRepository } from '@/models/_.js'; | import type { UsersRepository } from '@/models/_.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { RoleService } from '@/core/RoleService.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 = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		@Inject(DI.usersRepository) | 		@Inject(DI.usersRepository) | ||||||
| 		private usersRepository: UsersRepository, | 		private readonly usersRepository: UsersRepository, | ||||||
| 
 | 		private readonly cacheService: CacheService, | ||||||
| 		private roleService: RoleService, | 		private readonly moderationLogService: ModerationLogService, | ||||||
|  | 		private readonly roleService: RoleService, | ||||||
|  | 		private readonly globalEventService: GlobalEventService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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'); |  | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			if (await this.roleService.isModerator(user)) { | 			if (await this.roleService.isModerator(user)) { | ||||||
| 				throw new Error('cannot silence moderator account'); | 				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, { | 			await this.usersRepository.update(user.id, { | ||||||
| 				isSilenced: true, | 				isSilenced: true, | ||||||
| 			}); | 			}); | ||||||
|  | 
 | ||||||
|  | 			this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', { | ||||||
|  | 				id: user.id, | ||||||
|  | 			}); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,8 +5,10 @@ | ||||||
| 
 | 
 | ||||||
| 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 { UsersRepository, UserProfilesRepository } from '@/models/_.js'; | import type { UserProfilesRepository } from '@/models/_.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { CacheService } from '@/core/CacheService.js'; | ||||||
|  | import { ModerationLogService } from '@/core/ModerationLogService.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
|  | @ -27,18 +29,19 @@ 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.usersRepository) | 		private readonly cacheService: CacheService, | ||||||
| 		private usersRepository: UsersRepository, | 		private readonly moderationLogService: ModerationLogService, | ||||||
| 
 |  | ||||||
| 		@Inject(DI.userProfilesRepository) | 		@Inject(DI.userProfilesRepository) | ||||||
| 		private userProfilesRepository: UserProfilesRepository, | 		private readonly userProfilesRepository: UserProfilesRepository, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		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) { | 			await this.moderationLogService.log(me, 'unNsfwUser', { | ||||||
| 				throw new Error('user not found'); | 				userId: ps.userId, | ||||||
| 			} | 				userUsername: user.username, | ||||||
|  | 				userHost: user.host, | ||||||
|  | 			}); | ||||||
| 
 | 
 | ||||||
| 			await this.userProfilesRepository.update(user.id, { | 			await this.userProfilesRepository.update(user.id, { | ||||||
| 				alwaysMarkNsfw: false, | 				alwaysMarkNsfw: false, | ||||||
|  |  | ||||||
|  | @ -7,6 +7,9 @@ import { Inject, Injectable } from '@nestjs/common'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { UsersRepository } from '@/models/_.js'; | import type { UsersRepository } from '@/models/_.js'; | ||||||
| import { DI } from '@/di-symbols.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 = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	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
 | export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||||
| 	constructor( | 	constructor( | ||||||
| 		@Inject(DI.usersRepository) | 		@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) => { | 		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) { | 			await this.moderationLogService.log(me, 'unSilenceUser', { | ||||||
| 				throw new Error('user not found'); | 				userId: ps.userId, | ||||||
| 			} | 				userUsername: user.username, | ||||||
|  | 				userHost: user.host, | ||||||
|  | 			}); | ||||||
| 
 | 
 | ||||||
| 			await this.usersRepository.update(user.id, { | 			await this.usersRepository.update(user.id, { | ||||||
| 				isSilenced: false, | 				isSilenced: false, | ||||||
| 			}); | 			}); | ||||||
|  | 
 | ||||||
|  | 			this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', { | ||||||
|  | 				id: user.id, | ||||||
|  | 			}); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -78,7 +78,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 					flashId: flash.id, | 					flashId: flash.id, | ||||||
| 					flashUserId: flash.userId, | 					flashUserId: flash.userId, | ||||||
| 					flashUserUsername: user.username, | 					flashUserUsername: user.username, | ||||||
| 					flash, |  | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | @ -78,7 +78,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 					postId: post.id, | 					postId: post.id, | ||||||
| 					postUserId: post.userId, | 					postUserId: post.userId, | ||||||
| 					postUserUsername: user.username, | 					postUserUsername: user.username, | ||||||
| 					post, |  | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | @ -79,7 +79,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 					pageId: page.id, | 					pageId: page.id, | ||||||
| 					pageUserId: page.userId, | 					pageUserId: page.userId, | ||||||
| 					pageUserUsername: user.username, | 					pageUserUsername: user.username, | ||||||
| 					page, |  | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | @ -136,6 +136,21 @@ export const moderationLogTypes = [ | ||||||
| 	'rejectQuotesUser', | 	'rejectQuotesUser', | ||||||
| 	'acceptQuotesInstance', | 	'acceptQuotesInstance', | ||||||
| 	'rejectQuotesInstance', | 	'rejectQuotesInstance', | ||||||
|  | 	'clearUserFiles', | ||||||
|  | 	'nsfwUser', | ||||||
|  | 	'unNsfwUser', | ||||||
|  | 	'silenceUser', | ||||||
|  | 	'unSilenceUser', | ||||||
|  | 	'createAccount', | ||||||
|  | 	'clearRemoteFiles', | ||||||
|  | 	'clearOwnerlessFiles', | ||||||
|  | 	'updateCustomEmojis', | ||||||
|  | 	'importCustomEmojis', | ||||||
|  | 	'clearInstanceFiles', | ||||||
|  | 	'severFollowRelations', | ||||||
|  | 	'createPromo', | ||||||
|  | 	'addRelay', | ||||||
|  | 	'removeRelay', | ||||||
| ] as const; | ] as const; | ||||||
| 
 | 
 | ||||||
| export type ModerationLogPayloads = { | export type ModerationLogPayloads = { | ||||||
|  | @ -224,7 +239,6 @@ export type ModerationLogPayloads = { | ||||||
| 		noteUserId: string; | 		noteUserId: string; | ||||||
| 		noteUserUsername: string; | 		noteUserUsername: string; | ||||||
| 		noteUserHost: string | null; | 		noteUserHost: string | null; | ||||||
| 		note: any; |  | ||||||
| 	}; | 	}; | ||||||
| 	createGlobalAnnouncement: { | 	createGlobalAnnouncement: { | ||||||
| 		announcementId: string; | 		announcementId: string; | ||||||
|  | @ -407,19 +421,16 @@ export type ModerationLogPayloads = { | ||||||
| 		pageId: string; | 		pageId: string; | ||||||
| 		pageUserId: string; | 		pageUserId: string; | ||||||
| 		pageUserUsername: string; | 		pageUserUsername: string; | ||||||
| 		page: any; |  | ||||||
| 	}; | 	}; | ||||||
| 	deleteFlash: { | 	deleteFlash: { | ||||||
| 		flashId: string; | 		flashId: string; | ||||||
| 		flashUserId: string; | 		flashUserId: string; | ||||||
| 		flashUserUsername: string; | 		flashUserUsername: string; | ||||||
| 		flash: any; |  | ||||||
| 	}; | 	}; | ||||||
| 	deleteGalleryPost: { | 	deleteGalleryPost: { | ||||||
| 		postId: string; | 		postId: string; | ||||||
| 		postUserId: string; | 		postUserId: string; | ||||||
| 		postUserUsername: string; | 		postUserUsername: string; | ||||||
| 		post: any; |  | ||||||
| 	}; | 	}; | ||||||
| 	acceptQuotesUser: { | 	acceptQuotesUser: { | ||||||
| 		userId: string, | 		userId: string, | ||||||
|  | @ -439,6 +450,70 @@ export type ModerationLogPayloads = { | ||||||
| 		id: string; | 		id: string; | ||||||
| 		host: 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: { | ||||||
|  | 		fileName: string, | ||||||
|  | 	}, | ||||||
|  | 	clearInstanceFiles: { | ||||||
|  | 		host: string; | ||||||
|  | 		count: number; | ||||||
|  | 	}, | ||||||
|  | 	severFollowRelations: { | ||||||
|  | 		host: string; | ||||||
|  | 	}, | ||||||
|  | 	createPromo: { | ||||||
|  | 		noteId: string, | ||||||
|  | 		noteUserId: string; | ||||||
|  | 		noteUserUsername: string; | ||||||
|  | 		noteUserHost: string | null; | ||||||
|  | 	}, | ||||||
|  | 	addRelay: { | ||||||
|  | 		inbox: string; | ||||||
|  | 	}, | ||||||
|  | 	removeRelay: { | ||||||
|  | 		inbox: string; | ||||||
|  | 	}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export type Serialized<T> = { | export type Serialized<T> = { | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								packages/frontend/src/components/DynamicNote.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								packages/frontend/src/components/DynamicNote.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | <!-- | ||||||
|  | SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors | ||||||
|  | SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  | <XNote | ||||||
|  | 	ref="rootEl" | ||||||
|  | 	:note="note" | ||||||
|  | 	:pinned="pinned" | ||||||
|  | 	:mock="mock" | ||||||
|  | 	:withHardMute="withHardMute" | ||||||
|  | 	@reaction="emoji => emit('reaction', emoji)" | ||||||
|  | 	@removeReaction="emoji => emit('removeReaction', emoji)" | ||||||
|  | /> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import * as Misskey from 'misskey-js'; | ||||||
|  | import { computed, defineAsyncComponent, shallowRef } from 'vue'; | ||||||
|  | import type { ComponentExposed } from 'vue-component-type-helpers'; | ||||||
|  | import type MkNote from '@/components/MkNote.vue'; | ||||||
|  | import type SkNote from '@/components/SkNote.vue'; | ||||||
|  | import { defaultStore } from '@/store'; | ||||||
|  | 
 | ||||||
|  | const XNote = computed(() => | ||||||
|  | 	defineAsyncComponent(() => | ||||||
|  | 		defaultStore.reactiveState.noteDesign.value === 'misskey' | ||||||
|  | 			? import('@/components/MkNote.vue') | ||||||
|  | 			: import('@/components/SkNote.vue'), | ||||||
|  | 	), | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | const rootEl = shallowRef<ComponentExposed<typeof MkNote | typeof SkNote>>(); | ||||||
|  | 
 | ||||||
|  | defineExpose({ rootEl }); | ||||||
|  | 
 | ||||||
|  | defineProps<{ | ||||||
|  | 	note: Misskey.entities.Note; | ||||||
|  | 	pinned?: boolean; | ||||||
|  | 	mock?: boolean; | ||||||
|  | 	withHardMute?: boolean; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits<{ | ||||||
|  | 	(ev: 'reaction', emoji: string): void; | ||||||
|  | 	(ev: 'removeReaction', emoji: string): void; | ||||||
|  | }>(); | ||||||
|  | </script> | ||||||
							
								
								
									
										69
									
								
								packages/frontend/src/components/SkFetchNote.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								packages/frontend/src/components/SkFetchNote.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | <!-- | ||||||
|  | SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors | ||||||
|  | SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  | <MkLazy @show="showing = true"> | ||||||
|  | 	<MkLoading v-if="state === 'loading'"/> | ||||||
|  | 
 | ||||||
|  | 	<div v-if="state === 'error'">{{ i18n.ts.cannotLoadNote }}</div> | ||||||
|  | 
 | ||||||
|  | 	<DynamicNote v-if="state === 'done' && note" :note="note"/> | ||||||
|  | </MkLazy> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { ref, watch } from 'vue'; | ||||||
|  | import * as Misskey from 'misskey-js'; | ||||||
|  | import { i18n } from '@/i18n.js'; | ||||||
|  | import { misskeyApi } from '@/scripts/misskey-api'; | ||||||
|  | import DynamicNote from '@/components/DynamicNote.vue'; | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<{ | ||||||
|  | 	noteId: string, | ||||||
|  | 	lazy?: boolean, | ||||||
|  | }>(), { | ||||||
|  | 	lazy: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | // Lazy-load, unless props.lazy is false. | ||||||
|  | // eslint-disable-next-line vue/no-setup-props-reactivity-loss | ||||||
|  | const showing = ref(!props.lazy); | ||||||
|  | const state = ref<'loading' | 'error' | 'done'>('loading'); | ||||||
|  | const note = ref<Misskey.entities.Note | null>(null); | ||||||
|  | 
 | ||||||
|  | watch( | ||||||
|  | 	[ | ||||||
|  | 		() => props.noteId, | ||||||
|  | 		() => showing.value, | ||||||
|  | 	], | ||||||
|  | 	async ([noteId, show]) => { | ||||||
|  | 		// Wait until the note is visible to avoid bombarding the API with requests. | ||||||
|  | 		if (!show) return; | ||||||
|  | 
 | ||||||
|  | 		// Unload the old note | ||||||
|  | 		note.value = null; | ||||||
|  | 		state.value = 'loading'; | ||||||
|  | 
 | ||||||
|  | 		// Fetch the new note | ||||||
|  | 		const newNote = await misskeyApi('notes/show', { noteId }).catch(() => null); | ||||||
|  | 
 | ||||||
|  | 		// Check for race conditions (ex. the note changed again while the first request was still running) | ||||||
|  | 		if (noteId !== props.noteId) return; | ||||||
|  | 
 | ||||||
|  | 		// Check for errors | ||||||
|  | 		if (!newNote) { | ||||||
|  | 			state.value = 'error'; | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Display the new note | ||||||
|  | 		note.value = newNote; | ||||||
|  | 		state.value = 'done'; | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		immediate: true, | ||||||
|  | 	}, | ||||||
|  | ); | ||||||
|  | </script> | ||||||
|  | @ -16,10 +16,20 @@ import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, shallowRef } fr | ||||||
| const rootEl = shallowRef<HTMLDivElement>(); | const rootEl = shallowRef<HTMLDivElement>(); | ||||||
| const showing = ref(false); | const showing = ref(false); | ||||||
| 
 | 
 | ||||||
|  | const emit = defineEmits<{ | ||||||
|  | 	(ev: 'show'): void, | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
| const observer = new IntersectionObserver( | const observer = new IntersectionObserver( | ||||||
| 	(entries) => { | 	(entries) => { | ||||||
| 		if (entries.some((entry) => entry.isIntersecting)) { | 		if (entries.some((entry) => entry.isIntersecting)) { | ||||||
| 			showing.value = true; | 			showing.value = true; | ||||||
|  | 
 | ||||||
|  | 			// Disconnect to avoid observer soft-leaks | ||||||
|  | 			observer.disconnect(); | ||||||
|  | 
 | ||||||
|  | 			// Notify containing element to trigger edge logic | ||||||
|  | 			emit('show'); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| ); | ); | ||||||
|  |  | ||||||
|  | @ -18,6 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 					'createAvatarDecoration', | 					'createAvatarDecoration', | ||||||
| 					'createSystemWebhook', | 					'createSystemWebhook', | ||||||
| 					'createAbuseReportNotificationRecipient', | 					'createAbuseReportNotificationRecipient', | ||||||
|  | 					'createAccount', | ||||||
|  | 					'importCustomEmojis', | ||||||
|  | 					'createPromo', | ||||||
|  | 					'addRelay', | ||||||
| 				].includes(log.type), | 				].includes(log.type), | ||||||
| 				[$style.logYellow]: [ | 				[$style.logYellow]: [ | ||||||
| 					'markSensitiveDriveFile', | 					'markSensitiveDriveFile', | ||||||
|  | @ -30,6 +34,11 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 					'acceptRemoteInstanceReports', | 					'acceptRemoteInstanceReports', | ||||||
| 					'rejectQuotesUser', | 					'rejectQuotesUser', | ||||||
| 					'acceptQuotesUser', | 					'acceptQuotesUser', | ||||||
|  | 					'nsfwUser', | ||||||
|  | 					'unNsfwUser', | ||||||
|  | 					'silenceUser', | ||||||
|  | 					'unSilenceUser', | ||||||
|  | 					'updateCustomEmojis', | ||||||
| 				].includes(log.type), | 				].includes(log.type), | ||||||
| 				[$style.logRed]: [ | 				[$style.logRed]: [ | ||||||
| 					'suspend', | 					'suspend', | ||||||
|  | @ -49,6 +58,12 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 					'deletePage', | 					'deletePage', | ||||||
| 					'deleteFlash', | 					'deleteFlash', | ||||||
| 					'deleteGalleryPost', | 					'deleteGalleryPost', | ||||||
|  | 					'clearUserFiles', | ||||||
|  | 					'clearRemoteFiles', | ||||||
|  | 					'clearOwnerlessFiles', | ||||||
|  | 					'clearInstanceFiles', | ||||||
|  | 					'severFollowRelations', | ||||||
|  | 					'removeRelay', | ||||||
| 				].includes(log.type) | 				].includes(log.type) | ||||||
| 			}" | 			}" | ||||||
| 		>{{ i18n.ts._moderationLogTypes[log.type] }}</b> | 		>{{ i18n.ts._moderationLogTypes[log.type] }}</b> | ||||||
|  | @ -100,6 +115,19 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 		<span v-else-if="log.type === 'deletePage'">: @{{ log.info.pageUserUsername }}</span> | 		<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 === 'deleteFlash'">: @{{ log.info.flashUserUsername }}</span> | ||||||
| 		<span v-else-if="log.type === 'deleteGalleryPost'">: @{{ log.info.postUserUsername }}</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 === 'clearOwnerlessFiles'">: {{ log.info.count }}</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 === 'createPromo'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</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> | ||||||
| 	<template v-if="log.user" #icon> | 	<template v-if="log.user" #icon> | ||||||
| 		<MkAvatar :user="log.user" :class="$style.avatar"/> | 		<MkAvatar :user="log.user" :class="$style.avatar"/> | ||||||
|  | @ -205,6 +233,47 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 				<CodeDiff :context="5" :hideHeader="true" :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 === '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 === 'importCustomEmojis'"> | ||||||
|  | 			<div>{{ i18n.ts.fileImported }}: {{ log.info.fileName }}</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> | ||||||
|  | 		<template v-else-if="log.type === 'createPromo'"> | ||||||
|  | 			<SkFetchNote :noteId="log.info.noteId"/> | ||||||
|  | 		</template> | ||||||
|  | 		<template v-else-if="log.type === 'addRelay'"> | ||||||
|  | 			<div>{{ i18n.ts.inboxUrl }}: {{ log.info.inbox }}</div> | ||||||
|  | 		</template> | ||||||
|  | 		<template v-else-if="log.type === 'removeRelay'"> | ||||||
|  | 			<div>{{ i18n.ts.inboxUrl }}: {{ log.info.inbox }}</div> | ||||||
|  | 		</template> | ||||||
| 
 | 
 | ||||||
| 		<details> | 		<details> | ||||||
| 			<summary>raw</summary> | 			<summary>raw</summary> | ||||||
|  | @ -220,6 +289,7 @@ import { CodeDiff } from 'v-code-diff'; | ||||||
| import JSON5 from 'json5'; | import JSON5 from 'json5'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import MkFolder from '@/components/MkFolder.vue'; | import MkFolder from '@/components/MkFolder.vue'; | ||||||
|  | import SkFetchNote from '@/components/SkFetchNote.vue'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	log: Misskey.entities.ModerationLog; | 	log: Misskey.entities.ModerationLog; | ||||||
|  |  | ||||||
|  | @ -2728,6 +2728,51 @@ type ModerationLog = { | ||||||
| } | { | } | { | ||||||
|     type: 'deleteGalleryPost'; |     type: 'deleteGalleryPost'; | ||||||
|     info: ModerationLogPayloads['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) | // @public (undocumented) | ||||||
|  |  | ||||||
|  | @ -4,12 +4,8 @@ import type { | ||||||
| 	Ad, | 	Ad, | ||||||
| 	Announcement, | 	Announcement, | ||||||
| 	EmojiDetailed, | 	EmojiDetailed, | ||||||
| 	Flash, |  | ||||||
| 	GalleryPost, |  | ||||||
| 	InviteCode, | 	InviteCode, | ||||||
| 	MetaDetailed, | 	MetaDetailed, | ||||||
| 	Note, |  | ||||||
| 	Page, |  | ||||||
| 	Role, | 	Role, | ||||||
| 	ReversiGameDetailed, | 	ReversiGameDetailed, | ||||||
| 	SystemWebhook, | 	SystemWebhook, | ||||||
|  | @ -295,7 +291,6 @@ export type ModerationLogPayloads = { | ||||||
| 		noteUserId: string; | 		noteUserId: string; | ||||||
| 		noteUserUsername: string; | 		noteUserUsername: string; | ||||||
| 		noteUserHost: string | null; | 		noteUserHost: string | null; | ||||||
| 		note: Note; |  | ||||||
| 	}; | 	}; | ||||||
| 	createGlobalAnnouncement: { | 	createGlobalAnnouncement: { | ||||||
| 		announcementId: string; | 		announcementId: string; | ||||||
|  | @ -478,19 +473,16 @@ export type ModerationLogPayloads = { | ||||||
| 		pageId: string; | 		pageId: string; | ||||||
| 		pageUserId: string; | 		pageUserId: string; | ||||||
| 		pageUserUsername: string; | 		pageUserUsername: string; | ||||||
| 		page: Page; |  | ||||||
| 	}; | 	}; | ||||||
| 	deleteFlash: { | 	deleteFlash: { | ||||||
| 		flashId: string; | 		flashId: string; | ||||||
| 		flashUserId: string; | 		flashUserId: string; | ||||||
| 		flashUserUsername: string; | 		flashUserUsername: string; | ||||||
| 		flash: Flash; |  | ||||||
| 	}; | 	}; | ||||||
| 	deleteGalleryPost: { | 	deleteGalleryPost: { | ||||||
| 		postId: string; | 		postId: string; | ||||||
| 		postUserId: string; | 		postUserId: string; | ||||||
| 		postUserUsername: string; | 		postUserUsername: string; | ||||||
| 		post: GalleryPost; |  | ||||||
| 	}; | 	}; | ||||||
| 	acceptQuotesUser: { | 	acceptQuotesUser: { | ||||||
| 		userId: string, | 		userId: string, | ||||||
|  | @ -510,4 +502,69 @@ export type ModerationLogPayloads = { | ||||||
| 		id: string; | 		id: string; | ||||||
| 		host: 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: { | ||||||
|  | 		fileName: string, | ||||||
|  | 	}; | ||||||
|  | 	clearInstanceFiles: { | ||||||
|  | 		host: string; | ||||||
|  | 		count: number; | ||||||
|  | 	}; | ||||||
|  | 	severFollowRelations: { | ||||||
|  | 		host: string; | ||||||
|  | 	}; | ||||||
|  | 	createPromo: { | ||||||
|  | 		noteId: string, | ||||||
|  | 		noteUserId: string; | ||||||
|  | 		noteUserUsername: string; | ||||||
|  | 		noteUserHost: string | null; | ||||||
|  | 	}; | ||||||
|  | 	addRelay: { | ||||||
|  | 		inbox: string; | ||||||
|  | 	}; | ||||||
|  | 	removeRelay: { | ||||||
|  | 		inbox: string; | ||||||
|  | 	}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -228,6 +228,51 @@ export type ModerationLog = { | ||||||
| } | { | } | { | ||||||
| 	type: 'deleteGalleryPost'; | 	type: 'deleteGalleryPost'; | ||||||
| 	info: ModerationLogPayloads['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 = { | export type ServerStats = { | ||||||
|  |  | ||||||
|  | @ -169,6 +169,9 @@ pinnedOnly: "Pinned" | ||||||
| blockingYou: "Blocking you" | blockingYou: "Blocking you" | ||||||
| warnExternalUrl: "Show warning when opening external URLs" | warnExternalUrl: "Show warning when opening external URLs" | ||||||
| flash: "Flash" | flash: "Flash" | ||||||
|  | filesRemoved: "Files removed" | ||||||
|  | fileImported: "File imported" | ||||||
|  | cannotLoadNote: "Failed to load note" | ||||||
| _flash: | _flash: | ||||||
|   contentHidden: "Flash Content Hidden" |   contentHidden: "Flash Content Hidden" | ||||||
|   poweredByRuffle: "Powered by Ruffle." |   poweredByRuffle: "Powered by Ruffle." | ||||||
|  | @ -320,6 +323,22 @@ _moderationLogTypes: | ||||||
|   acceptRemoteInstanceReports: "Accepted reports from remote instance" |   acceptRemoteInstanceReports: "Accepted reports from remote instance" | ||||||
|   rejectQuotesUser: "Blocked/Stripped quote posts from user" |   rejectQuotesUser: "Blocked/Stripped quote posts from user" | ||||||
|   allowQuotesUser: "Allowed 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: | _mfm: | ||||||
|   uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks" |   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." |   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." | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue