mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-10-30 21:14:12 +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; | ||||
|     /** | ||||
|      * 新しいキャプションを入力してください | ||||
|      * 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,18 @@ export interface Locale extends ILocale { | |||
|      * Flash | ||||
|      */ | ||||
|     "flash": string; | ||||
|     /** | ||||
|      * Files removed | ||||
|      */ | ||||
|     "filesRemoved": string; | ||||
|     /** | ||||
|      * File imported | ||||
|      */ | ||||
|     "fileImported": string; | ||||
|     /** | ||||
|      * Failed to load note | ||||
|      */ | ||||
|     "cannotLoadNote": string; | ||||
|     "_flash": { | ||||
|         /** | ||||
|          * Flash Content Hidden | ||||
|  |  | |||
|  | @ -162,7 +162,6 @@ export class NoteDeleteService { | |||
| 				noteUserId: note.userId, | ||||
| 				noteUserUsername: user.username, | ||||
| 				noteUserHost: user.host, | ||||
| 				note: note, | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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); | ||||
| 			} | ||||
|  |  | |||
|  | @ -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(); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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); | ||||
| 			} | ||||
|  |  | |||
|  | @ -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'))); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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,16 @@ 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', { | ||||
| 				fileName: file.name, | ||||
| 			}); | ||||
| 			await this.queueService.createImportCustomEmojisJob(me, ps.fileId); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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'))); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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'))); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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); | ||||
| 			} | ||||
|  |  | |||
|  | @ -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 }))); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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,14 @@ 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, | ||||
| 			}); | ||||
| 
 | ||||
| 			await this.promoNotesRepository.insert({ | ||||
| 				noteId: note.id, | ||||
| 				expiresAt: new Date(ps.expiresAt), | ||||
|  |  | |||
|  | @ -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); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -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); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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, | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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, | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -78,7 +78,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 					flashId: flash.id, | ||||
| 					flashUserId: flash.userId, | ||||
| 					flashUserUsername: user.username, | ||||
| 					flash, | ||||
| 				}); | ||||
| 			} | ||||
| 		}); | ||||
|  |  | |||
|  | @ -78,7 +78,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 					postId: post.id, | ||||
| 					postUserId: post.userId, | ||||
| 					postUserUsername: user.username, | ||||
| 					post, | ||||
| 				}); | ||||
| 			} | ||||
| 		}); | ||||
|  |  | |||
|  | @ -79,7 +79,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 					pageId: page.id, | ||||
| 					pageUserId: page.userId, | ||||
| 					pageUserUsername: user.username, | ||||
| 					page, | ||||
| 				}); | ||||
| 			} | ||||
| 		}); | ||||
|  |  | |||
|  | @ -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 = { | ||||
|  | @ -224,7 +239,6 @@ export type ModerationLogPayloads = { | |||
| 		noteUserId: string; | ||||
| 		noteUserUsername: string; | ||||
| 		noteUserHost: string | null; | ||||
| 		note: any; | ||||
| 	}; | ||||
| 	createGlobalAnnouncement: { | ||||
| 		announcementId: string; | ||||
|  | @ -407,19 +421,16 @@ export type ModerationLogPayloads = { | |||
| 		pageId: string; | ||||
| 		pageUserId: string; | ||||
| 		pageUserUsername: string; | ||||
| 		page: any; | ||||
| 	}; | ||||
| 	deleteFlash: { | ||||
| 		flashId: string; | ||||
| 		flashUserId: string; | ||||
| 		flashUserUsername: string; | ||||
| 		flash: any; | ||||
| 	}; | ||||
| 	deleteGalleryPost: { | ||||
| 		postId: string; | ||||
| 		postUserId: string; | ||||
| 		postUserUsername: string; | ||||
| 		post: any; | ||||
| 	}; | ||||
| 	acceptQuotesUser: { | ||||
| 		userId: string, | ||||
|  | @ -439,6 +450,70 @@ 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: { | ||||
| 		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> = { | ||||
|  |  | |||
							
								
								
									
										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 showing = ref(false); | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'show'): void, | ||||
| }>(); | ||||
| 
 | ||||
| const observer = new IntersectionObserver( | ||||
| 	(entries) => { | ||||
| 		if (entries.some((entry) => entry.isIntersecting)) { | ||||
| 			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', | ||||
| 					'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,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 === '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 === '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 v-if="log.user" #icon> | ||||
| 		<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"/> | ||||
| 			</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 === '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> | ||||
| 			<summary>raw</summary> | ||||
|  | @ -220,6 +289,7 @@ import { CodeDiff } from 'v-code-diff'; | |||
| import JSON5 from 'json5'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import MkFolder from '@/components/MkFolder.vue'; | ||||
| import SkFetchNote from '@/components/SkFetchNote.vue'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
| 	log: Misskey.entities.ModerationLog; | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -4,12 +4,8 @@ import type { | |||
| 	Ad, | ||||
| 	Announcement, | ||||
| 	EmojiDetailed, | ||||
| 	Flash, | ||||
| 	GalleryPost, | ||||
| 	InviteCode, | ||||
| 	MetaDetailed, | ||||
| 	Note, | ||||
| 	Page, | ||||
| 	Role, | ||||
| 	ReversiGameDetailed, | ||||
| 	SystemWebhook, | ||||
|  | @ -295,7 +291,6 @@ export type ModerationLogPayloads = { | |||
| 		noteUserId: string; | ||||
| 		noteUserUsername: string; | ||||
| 		noteUserHost: string | null; | ||||
| 		note: Note; | ||||
| 	}; | ||||
| 	createGlobalAnnouncement: { | ||||
| 		announcementId: string; | ||||
|  | @ -478,19 +473,16 @@ export type ModerationLogPayloads = { | |||
| 		pageId: string; | ||||
| 		pageUserId: string; | ||||
| 		pageUserUsername: string; | ||||
| 		page: Page; | ||||
| 	}; | ||||
| 	deleteFlash: { | ||||
| 		flashId: string; | ||||
| 		flashUserId: string; | ||||
| 		flashUserUsername: string; | ||||
| 		flash: Flash; | ||||
| 	}; | ||||
| 	deleteGalleryPost: { | ||||
| 		postId: string; | ||||
| 		postUserId: string; | ||||
| 		postUserUsername: string; | ||||
| 		post: GalleryPost; | ||||
| 	}; | ||||
| 	acceptQuotesUser: { | ||||
| 		userId: string, | ||||
|  | @ -510,4 +502,69 @@ 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: { | ||||
| 		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'; | ||||
| 	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 = { | ||||
|  |  | |||
|  | @ -169,6 +169,9 @@ pinnedOnly: "Pinned" | |||
| blockingYou: "Blocking you" | ||||
| warnExternalUrl: "Show warning when opening external URLs" | ||||
| flash: "Flash" | ||||
| filesRemoved: "Files removed" | ||||
| fileImported: "File imported" | ||||
| cannotLoadNote: "Failed to load note" | ||||
| _flash: | ||||
|   contentHidden: "Flash Content Hidden" | ||||
|   poweredByRuffle: "Powered by Ruffle." | ||||
|  | @ -320,6 +323,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." | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue