parent
							
								
									82f9d5501b
								
							
						
					
					
						commit
						e68278f93e
					
				
					 12 changed files with 116 additions and 6 deletions
				
			
		|  | @ -20,6 +20,7 @@ You should also include the user name that made the change. | |||
| 
 | ||||
| ### Improvements | ||||
| - インスタンスデフォルトテーマを設定できるように @syuilo | ||||
| - ミュートに期限を設定できるように @syuilo | ||||
| - プロフィールの追加情報を最大16まで保存できるように @syuilo | ||||
| - 連合チャートにPub&Subを追加 @syuilo | ||||
| - デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo | ||||
|  |  | |||
|  | @ -834,6 +834,12 @@ searchByGoogle: "ググる" | |||
| instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ" | ||||
| instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ" | ||||
| instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。" | ||||
| mutePeriod: "ミュートする期限" | ||||
| indefinitely: "無期限" | ||||
| tenMinutes: "10分" | ||||
| oneHour: "1時間" | ||||
| oneDay: "1日" | ||||
| oneWeek: "1週間" | ||||
| 
 | ||||
| _emailUnavailable: | ||||
|   used: "既に使用されています" | ||||
|  |  | |||
							
								
								
									
										13
									
								
								packages/backend/migration/1646387162108-mute-expires-at.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/backend/migration/1646387162108-mute-expires-at.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| export class muteExpiresAt1646387162108 { | ||||
|     name = 'muteExpiresAt1646387162108' | ||||
| 
 | ||||
|     async up(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "muting" ADD "expiresAt" TIMESTAMP WITH TIME ZONE`); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_c1fd1c3dfb0627aa36c253fd14" ON "muting" ("expiresAt") `); | ||||
|     } | ||||
| 
 | ||||
|     async down(queryRunner) { | ||||
|         await queryRunner.query(`DROP INDEX "public"."IDX_c1fd1c3dfb0627aa36c253fd14"`); | ||||
|         await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "expiresAt"`); | ||||
|     } | ||||
| } | ||||
|  | @ -14,6 +14,13 @@ export class Muting { | |||
| 	}) | ||||
| 	public createdAt: Date; | ||||
| 
 | ||||
| 	@Index() | ||||
| 	@Column('timestamp with time zone', { | ||||
| 		nullable: true, | ||||
| 		default: null, | ||||
| 	}) | ||||
| 	public expiresAt: Date | null; | ||||
| 
 | ||||
| 	@Index() | ||||
| 	@Column({ | ||||
| 		...id(), | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ export class MutingRepository extends Repository<Muting> { | |||
| 		return await awaitAll({ | ||||
| 			id: muting.id, | ||||
| 			createdAt: muting.createdAt.toISOString(), | ||||
| 			expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, | ||||
| 			muteeId: muting.muteeId, | ||||
| 			mutee: Users.pack(muting.muteeId, me, { | ||||
| 				detail: true, | ||||
|  |  | |||
|  | @ -12,6 +12,11 @@ export const packedMutingSchema = { | |||
| 			optional: false, nullable: false, | ||||
| 			format: 'date-time', | ||||
| 		}, | ||||
| 		expiresAt: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: true, | ||||
| 			format: 'date-time', | ||||
| 		}, | ||||
| 		muteeId: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
|  |  | |||
|  | @ -273,6 +273,11 @@ export default function() { | |||
| 		repeat: { cron: '0 0 * * *' }, | ||||
| 	}); | ||||
| 
 | ||||
| 	systemQueue.add('checkExpiredMutings', { | ||||
| 	}, { | ||||
| 		repeat: { cron: '*/5 * * * *' }, | ||||
| 	}); | ||||
| 
 | ||||
| 	processSystemQueue(systemQueue); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import { addFile } from '@/services/drive/add-file.js'; | |||
| import { format as dateFormat } from 'date-fns'; | ||||
| import { getFullApAccount } from '@/misc/convert-host.js'; | ||||
| import { Users, Mutings } from '@/models/index.js'; | ||||
| import { MoreThan } from 'typeorm'; | ||||
| import { IsNull, MoreThan } from 'typeorm'; | ||||
| import { DbUserJobData } from '@/queue/types.js'; | ||||
| 
 | ||||
| const logger = queueLogger.createSubLogger('export-mute'); | ||||
|  | @ -40,6 +40,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi | |||
| 		const mutes = await Mutings.find({ | ||||
| 			where: { | ||||
| 				muterId: user.id, | ||||
| 				expiresAt: IsNull(), | ||||
| 				...(cursor ? { id: MoreThan(cursor) } : {}), | ||||
| 			}, | ||||
| 			take: 100, | ||||
|  |  | |||
|  | @ -0,0 +1,30 @@ | |||
| import Bull from 'bull'; | ||||
| import { In } from 'typeorm'; | ||||
| import { Mutings } from '@/models/index.js'; | ||||
| import { queueLogger } from '../../logger.js'; | ||||
| import { publishUserEvent } from '@/services/stream.js'; | ||||
| 
 | ||||
| const logger = queueLogger.createSubLogger('check-expired-mutings'); | ||||
| 
 | ||||
| export async function checkExpiredMutings(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> { | ||||
| 	logger.info(`Checking expired mutings...`); | ||||
| 
 | ||||
| 	const expired = await Mutings.createQueryBuilder('muting') | ||||
| 		.where('muting.expiresAt IS NOT NULL') | ||||
| 		.andWhere('muting.expiresAt < :now', { now: new Date() }) | ||||
| 		.innerJoinAndSelect('muting.mutee', 'mutee') | ||||
| 		.getMany(); | ||||
| 
 | ||||
| 	if (expired.length > 0) { | ||||
| 		await Mutings.delete({ | ||||
| 			id: In(expired.map(m => m.id)), | ||||
| 		}); | ||||
| 
 | ||||
| 		for (const m of expired) { | ||||
| 			publishUserEvent(m.muterId, 'unmute', m.mutee!); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	logger.succ(`All expired mutings checked.`); | ||||
| 	done(); | ||||
| } | ||||
|  | @ -2,11 +2,13 @@ import Bull from 'bull'; | |||
| import { tickCharts } from './tick-charts.js'; | ||||
| import { resyncCharts } from './resync-charts.js'; | ||||
| import { cleanCharts } from './clean-charts.js'; | ||||
| import { checkExpiredMutings } from './check-expired-mutings.js'; | ||||
| 
 | ||||
| const jobs = { | ||||
| 	tickCharts, | ||||
| 	resyncCharts, | ||||
| 	cleanCharts, | ||||
| 	checkExpiredMutings, | ||||
| } as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>; | ||||
| 
 | ||||
| export default function(dbQueue: Bull.Queue<Record<string, unknown>>) { | ||||
|  |  | |||
|  | @ -38,6 +38,7 @@ export const paramDef = { | |||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		userId: { type: 'string', format: 'misskey:id' }, | ||||
| 		expiresAt: { type: 'integer', nullable: true }, | ||||
| 	}, | ||||
| 	required: ['userId'], | ||||
| } as const; | ||||
|  | @ -67,10 +68,15 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 		throw new ApiError(meta.errors.alreadyMuting); | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.expiresAt && ps.expiresAt <= Date.now()) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// Create mute
 | ||||
| 	await Mutings.insert({ | ||||
| 		id: genId(), | ||||
| 		createdAt: new Date(), | ||||
| 		expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, | ||||
| 		muterId: muter.id, | ||||
| 		muteeId: mutee.id, | ||||
| 	} as Muting); | ||||
|  |  | |||
|  | @ -56,11 +56,44 @@ export function getUserMenu(user) { | |||
| 	} | ||||
| 
 | ||||
| 	async function toggleMute() { | ||||
| 		os.apiWithDialog(user.isMuted ? 'mute/delete' : 'mute/create', { | ||||
| 			userId: user.id | ||||
| 		if (user.isMuted) { | ||||
| 			os.apiWithDialog('mute/delete', { | ||||
| 				userId: user.id, | ||||
| 			}).then(() => { | ||||
| 			user.isMuted = !user.isMuted; | ||||
| 				user.isMuted = false; | ||||
| 			}); | ||||
| 		} else { | ||||
| 			const { canceled, result: period } = await os.select({ | ||||
| 				title: i18n.ts.mutePeriod, | ||||
| 				items: [{ | ||||
| 					value: 'indefinitely', text: i18n.ts.indefinitely, | ||||
| 				}, { | ||||
| 					value: 'tenMinutes', text: i18n.ts.tenMinutes, | ||||
| 				}, { | ||||
| 					value: 'oneHour', text: i18n.ts.oneHour, | ||||
| 				}, { | ||||
| 					value: 'oneDay', text: i18n.ts.oneDay, | ||||
| 				}, { | ||||
| 					value: 'oneWeek', text: i18n.ts.oneWeek, | ||||
| 				}], | ||||
| 				default: 'indefinitely', | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
| 
 | ||||
| 			const expiresAt = period === 'indefinitely' ? null | ||||
| 				: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10) | ||||
| 				: period === 'oneHour' ? Date.now() + (1000 * 60 * 60) | ||||
| 				: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24) | ||||
| 				: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7) | ||||
| 				: null; | ||||
| 
 | ||||
| 			os.apiWithDialog('mute/create', { | ||||
| 				userId: user.id, | ||||
| 				expiresAt, | ||||
| 			}).then(() => { | ||||
| 				user.isMuted = true; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	async function toggleBlock() { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue