フォロワー数、フォロー数もConditional roleで利用できるように
This commit is contained in:
		
							parent
							
								
									39c058a4bb
								
							
						
					
					
						commit
						4151087d3c
					
				
					 10 changed files with 79 additions and 9 deletions
				
			
		|  | @ -968,6 +968,10 @@ _role: | ||||||
|     isRemote: "リモートユーザー" |     isRemote: "リモートユーザー" | ||||||
|     createdLessThan: "アカウント作成から~以内" |     createdLessThan: "アカウント作成から~以内" | ||||||
|     createdMoreThan: "アカウント作成から~経過" |     createdMoreThan: "アカウント作成から~経過" | ||||||
|  |     followersLessThanOrEq: "フォロワー数が~以下" | ||||||
|  |     followersMoreThanOrEq: "フォロワー数が~以上" | ||||||
|  |     followingLessThanOrEq: "フォロー数が~以下" | ||||||
|  |     followingMoreThanOrEq: "フォロー数が~以上" | ||||||
|     and: "~かつ~" |     and: "~かつ~" | ||||||
|     or: "~または~" |     or: "~または~" | ||||||
|     not: "~ではない" |     not: "~ではない" | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ import { DI } from '@/di-symbols.js'; | ||||||
| import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; | import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; | ||||||
| import { UtilityService } from '@/core/UtilityService.js'; | import { UtilityService } from '@/core/UtilityService.js'; | ||||||
| import { bindThis } from '@/decorators.js'; | import { bindThis } from '@/decorators.js'; | ||||||
|  | import { StreamMessages } from '@/server/api/stream/types.js'; | ||||||
| import type { OnApplicationShutdown } from '@nestjs/common'; | import type { OnApplicationShutdown } from '@nestjs/common'; | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
|  | @ -73,7 +74,7 @@ export class AntennaService implements OnApplicationShutdown { | ||||||
| 		const obj = JSON.parse(data); | 		const obj = JSON.parse(data); | ||||||
| 
 | 
 | ||||||
| 		if (obj.channel === 'internal') { | 		if (obj.channel === 'internal') { | ||||||
| 			const { type, body } = obj.message; | 			const { type, body } = obj.message as StreamMessages['internal']['payload']; | ||||||
| 			switch (type) { | 			switch (type) { | ||||||
| 				case 'antennaCreated': | 				case 'antennaCreated': | ||||||
| 					this.antennas.push(body); | 					this.antennas.push(body); | ||||||
|  |  | ||||||
|  | @ -4,8 +4,9 @@ import Redis from 'ioredis'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { Meta } from '@/models/entities/Meta.js'; | import { Meta } from '@/models/entities/Meta.js'; | ||||||
| import { GlobalEventService } from '@/core/GlobalEventService.js'; | import { GlobalEventService } from '@/core/GlobalEventService.js'; | ||||||
| import type { OnApplicationShutdown } from '@nestjs/common'; |  | ||||||
| import { bindThis } from '@/decorators.js'; | import { bindThis } from '@/decorators.js'; | ||||||
|  | import { StreamMessages } from '@/server/api/stream/types.js'; | ||||||
|  | import type { OnApplicationShutdown } from '@nestjs/common'; | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
| export class MetaService implements OnApplicationShutdown { | export class MetaService implements OnApplicationShutdown { | ||||||
|  | @ -40,7 +41,7 @@ export class MetaService implements OnApplicationShutdown { | ||||||
| 		const obj = JSON.parse(data); | 		const obj = JSON.parse(data); | ||||||
| 
 | 
 | ||||||
| 		if (obj.channel === 'internal') { | 		if (obj.channel === 'internal') { | ||||||
| 			const { type, body } = obj.message; | 			const { type, body } = obj.message as StreamMessages['internal']['payload']; | ||||||
| 			switch (type) { | 			switch (type) { | ||||||
| 				case 'metaUpdated': { | 				case 'metaUpdated': { | ||||||
| 					this.cache = body; | 					this.cache = body; | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import { MetaService } from '@/core/MetaService.js'; | ||||||
| import { UserCacheService } from '@/core/UserCacheService.js'; | import { UserCacheService } from '@/core/UserCacheService.js'; | ||||||
| import { RoleCondFormulaValue } from '@/models/entities/Role.js'; | import { RoleCondFormulaValue } from '@/models/entities/Role.js'; | ||||||
| import { UserEntityService } from '@/core/entities/UserEntityService.js'; | import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||||
|  | import { StreamMessages } from '@/server/api/stream/types.js'; | ||||||
| import type { OnApplicationShutdown } from '@nestjs/common'; | import type { OnApplicationShutdown } from '@nestjs/common'; | ||||||
| 
 | 
 | ||||||
| export type RoleOptions = { | export type RoleOptions = { | ||||||
|  | @ -69,7 +70,7 @@ export class RoleService implements OnApplicationShutdown { | ||||||
| 		const obj = JSON.parse(data); | 		const obj = JSON.parse(data); | ||||||
| 
 | 
 | ||||||
| 		if (obj.channel === 'internal') { | 		if (obj.channel === 'internal') { | ||||||
| 			const { type, body } = obj.message; | 			const { type, body } = obj.message as StreamMessages['internal']['payload']; | ||||||
| 			switch (type) { | 			switch (type) { | ||||||
| 				case 'roleCreated': { | 				case 'roleCreated': { | ||||||
| 					const cached = this.rolesCache.get(null); | 					const cached = this.rolesCache.get(null); | ||||||
|  | @ -147,6 +148,18 @@ export class RoleService implements OnApplicationShutdown { | ||||||
| 				case 'createdMoreThan': { | 				case 'createdMoreThan': { | ||||||
| 					return user.createdAt.getTime() < (Date.now() - (value.sec * 1000)); | 					return user.createdAt.getTime() < (Date.now() - (value.sec * 1000)); | ||||||
| 				} | 				} | ||||||
|  | 				case 'followersLessThanOrEq': { | ||||||
|  | 					return user.followersCount <= value.value; | ||||||
|  | 				} | ||||||
|  | 				case 'followersMoreThanOrEq': { | ||||||
|  | 					return user.followersCount >= value.value; | ||||||
|  | 				} | ||||||
|  | 				case 'followingLessThanOrEq': { | ||||||
|  | 					return user.followingCount <= value.value; | ||||||
|  | 				} | ||||||
|  | 				case 'followingMoreThanOrEq': { | ||||||
|  | 					return user.followingCount >= value.value; | ||||||
|  | 				} | ||||||
| 				default: | 				default: | ||||||
| 					return false; | 					return false; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/mode | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { UserEntityService } from '@/core/entities/UserEntityService.js'; | import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||||
| import { bindThis } from '@/decorators.js'; | import { bindThis } from '@/decorators.js'; | ||||||
|  | import { StreamMessages } from '@/server/api/stream/types.js'; | ||||||
| import type { OnApplicationShutdown } from '@nestjs/common'; | import type { OnApplicationShutdown } from '@nestjs/common'; | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
|  | @ -39,7 +40,7 @@ export class UserCacheService implements OnApplicationShutdown { | ||||||
| 		const obj = JSON.parse(data); | 		const obj = JSON.parse(data); | ||||||
| 
 | 
 | ||||||
| 		if (obj.channel === 'internal') { | 		if (obj.channel === 'internal') { | ||||||
| 			const { type, body } = obj.message; | 			const { type, body } = obj.message as StreamMessages['internal']['payload']; | ||||||
| 			switch (type) { | 			switch (type) { | ||||||
| 				case 'userChangeSuspendedState': | 				case 'userChangeSuspendedState': | ||||||
| 				case 'remoteUserUpdated': { | 				case 'remoteUserUpdated': { | ||||||
|  | @ -62,6 +63,13 @@ export class UserCacheService implements OnApplicationShutdown { | ||||||
| 					this.localUserByNativeTokenCache.set(body.newToken, user); | 					this.localUserByNativeTokenCache.set(body.newToken, user); | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
|  | 				case 'follow': { | ||||||
|  | 					const follower = this.userByIdCache.get(body.followerId); | ||||||
|  | 					if (follower) follower.followingCount++; | ||||||
|  | 					const followee = this.userByIdCache.get(body.followeeId); | ||||||
|  | 					if (followee) followee.followersCount++; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
| 				default: | 				default: | ||||||
| 					break; | 					break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -62,6 +62,7 @@ export class UserFollowingService { | ||||||
| 		private federatedInstanceService: FederatedInstanceService, | 		private federatedInstanceService: FederatedInstanceService, | ||||||
| 		private webhookService: WebhookService, | 		private webhookService: WebhookService, | ||||||
| 		private apRendererService: ApRendererService, | 		private apRendererService: ApRendererService, | ||||||
|  | 		private globalEventService: GlobalEventService, | ||||||
| 		private perUserFollowingChart: PerUserFollowingChart, | 		private perUserFollowingChart: PerUserFollowingChart, | ||||||
| 		private instanceChart: InstanceChart, | 		private instanceChart: InstanceChart, | ||||||
| 	) { | 	) { | ||||||
|  | @ -195,6 +196,8 @@ export class UserFollowingService { | ||||||
| 		} | 		} | ||||||
| 	 | 	 | ||||||
| 		if (alreadyFollowed) return; | 		if (alreadyFollowed) return; | ||||||
|  | 
 | ||||||
|  | 		this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id }); | ||||||
| 	 | 	 | ||||||
| 		//#region Increment counts
 | 		//#region Increment counts
 | ||||||
| 		await Promise.all([ | 		await Promise.all([ | ||||||
|  | @ -314,6 +317,8 @@ export class UserFollowingService { | ||||||
| 		follower: {id: User['id']; host: User['host']; }, | 		follower: {id: User['id']; host: User['host']; }, | ||||||
| 		followee: { id: User['id']; host: User['host']; }, | 		followee: { id: User['id']; host: User['host']; }, | ||||||
| 	): Promise<void> { | 	): Promise<void> { | ||||||
|  | 		this.globalEventService.publishInternalEvent('unfollow', { followerId: follower.id, followeeId: followee.id }); | ||||||
|  | 	 | ||||||
| 		//#region Decrement following / followers counts
 | 		//#region Decrement following / followers counts
 | ||||||
| 		await Promise.all([ | 		await Promise.all([ | ||||||
| 			this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1), | 			this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1), | ||||||
|  |  | ||||||
|  | @ -3,8 +3,9 @@ import Redis from 'ioredis'; | ||||||
| import type { WebhooksRepository } from '@/models/index.js'; | import type { WebhooksRepository } from '@/models/index.js'; | ||||||
| import type { Webhook } from '@/models/entities/Webhook.js'; | import type { Webhook } from '@/models/entities/Webhook.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import type { OnApplicationShutdown } from '@nestjs/common'; |  | ||||||
| import { bindThis } from '@/decorators.js'; | import { bindThis } from '@/decorators.js'; | ||||||
|  | import { StreamMessages } from '@/server/api/stream/types.js'; | ||||||
|  | import type { OnApplicationShutdown } from '@nestjs/common'; | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
| export class WebhookService implements OnApplicationShutdown { | export class WebhookService implements OnApplicationShutdown { | ||||||
|  | @ -39,7 +40,7 @@ export class WebhookService implements OnApplicationShutdown { | ||||||
| 		const obj = JSON.parse(data); | 		const obj = JSON.parse(data); | ||||||
| 
 | 
 | ||||||
| 		if (obj.channel === 'internal') { | 		if (obj.channel === 'internal') { | ||||||
| 			const { type, body } = obj.message; | 			const { type, body } = obj.message as StreamMessages['internal']['payload']; | ||||||
| 			switch (type) { | 			switch (type) { | ||||||
| 				case 'webhookCreated': | 				case 'webhookCreated': | ||||||
| 					if (body.active) { | 					if (body.active) { | ||||||
|  |  | ||||||
|  | @ -34,6 +34,26 @@ type CondFormulaValueCreatedMoreThan = { | ||||||
| 	sec: number; | 	sec: number; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | type CondFormulaValueFollowersLessThanOrEq = { | ||||||
|  | 	type: 'followersLessThanOrEq'; | ||||||
|  | 	value: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueFollowersMoreThanOrEq = { | ||||||
|  | 	type: 'followersMoreThanOrEq'; | ||||||
|  | 	value: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueFollowingLessThanOrEq = { | ||||||
|  | 	type: 'followingLessThanOrEq'; | ||||||
|  | 	value: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueFollowingMoreThanOrEq = { | ||||||
|  | 	type: 'followingMoreThanOrEq'; | ||||||
|  | 	value: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export type RoleCondFormulaValue = | export type RoleCondFormulaValue = | ||||||
| 	CondFormulaValueAnd | | 	CondFormulaValueAnd | | ||||||
| 	CondFormulaValueOr | | 	CondFormulaValueOr | | ||||||
|  | @ -41,7 +61,11 @@ export type RoleCondFormulaValue = | ||||||
| 	CondFormulaValueIsLocal | | 	CondFormulaValueIsLocal | | ||||||
| 	CondFormulaValueIsRemote | | 	CondFormulaValueIsRemote | | ||||||
| 	CondFormulaValueCreatedLessThan | | 	CondFormulaValueCreatedLessThan | | ||||||
| 	CondFormulaValueCreatedMoreThan; | 	CondFormulaValueCreatedMoreThan | | ||||||
|  | 	CondFormulaValueFollowersLessThanOrEq | | ||||||
|  | 	CondFormulaValueFollowersMoreThanOrEq | | ||||||
|  | 	CondFormulaValueFollowingLessThanOrEq | | ||||||
|  | 	CondFormulaValueFollowingMoreThanOrEq; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class Role { | export class Role { | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ import type { Page } from '@/models/entities/Page.js'; | ||||||
| import type { Packed } from '@/misc/schema.js'; | import type { Packed } from '@/misc/schema.js'; | ||||||
| import type { Webhook } from '@/models/entities/Webhook.js'; | import type { Webhook } from '@/models/entities/Webhook.js'; | ||||||
| import type { Meta } from '@/models/entities/Meta.js'; | import type { Meta } from '@/models/entities/Meta.js'; | ||||||
| import { Role, RoleAssignment } from '@/models'; | import { Following, Role, RoleAssignment } from '@/models'; | ||||||
| import type Emitter from 'strict-event-emitter-types'; | import type Emitter from 'strict-event-emitter-types'; | ||||||
| import type { EventEmitter } from 'events'; | import type { EventEmitter } from 'events'; | ||||||
| 
 | 
 | ||||||
|  | @ -28,6 +28,8 @@ export interface InternalStreamTypes { | ||||||
| 	userChangeSuspendedState: Serialized<{ id: User['id']; isSuspended: User['isSuspended']; }>; | 	userChangeSuspendedState: Serialized<{ id: User['id']; isSuspended: User['isSuspended']; }>; | ||||||
| 	userTokenRegenerated: Serialized<{ id: User['id']; oldToken: User['token']; newToken: User['token']; }>; | 	userTokenRegenerated: Serialized<{ id: User['id']; oldToken: User['token']; newToken: User['token']; }>; | ||||||
| 	remoteUserUpdated: Serialized<{ id: User['id']; }>; | 	remoteUserUpdated: Serialized<{ id: User['id']; }>; | ||||||
|  | 	follow: Serialized<{ followerId: User['id']; followeeId: User['id']; }>; | ||||||
|  | 	unfollow: Serialized<{ followerId: User['id']; followeeId: User['id']; }>; | ||||||
| 	defaultRoleOverrideUpdated: Serialized<Role['options']>; | 	defaultRoleOverrideUpdated: Serialized<Role['options']>; | ||||||
| 	roleCreated: Serialized<Role>; | 	roleCreated: Serialized<Role>; | ||||||
| 	roleDeleted: Serialized<Role>; | 	roleDeleted: Serialized<Role>; | ||||||
|  |  | ||||||
|  | @ -6,6 +6,10 @@ | ||||||
| 			<option value="isRemote">{{ i18n.ts._role._condition.isRemote }}</option> | 			<option value="isRemote">{{ i18n.ts._role._condition.isRemote }}</option> | ||||||
| 			<option value="createdLessThan">{{ i18n.ts._role._condition.createdLessThan }}</option> | 			<option value="createdLessThan">{{ i18n.ts._role._condition.createdLessThan }}</option> | ||||||
| 			<option value="createdMoreThan">{{ i18n.ts._role._condition.createdMoreThan }}</option> | 			<option value="createdMoreThan">{{ i18n.ts._role._condition.createdMoreThan }}</option> | ||||||
|  | 			<option value="followersLessThanOrEq">{{ i18n.ts._role._condition.followersLessThanOrEq }}</option> | ||||||
|  | 			<option value="followersMoreThanOrEq">{{ i18n.ts._role._condition.followersMoreThanOrEq }}</option> | ||||||
|  | 			<option value="followingLessThanOrEq">{{ i18n.ts._role._condition.followingLessThanOrEq }}</option> | ||||||
|  | 			<option value="followingMoreThanOrEq">{{ i18n.ts._role._condition.followingMoreThanOrEq }}</option> | ||||||
| 			<option value="and">{{ i18n.ts._role._condition.and }}</option> | 			<option value="and">{{ i18n.ts._role._condition.and }}</option> | ||||||
| 			<option value="or">{{ i18n.ts._role._condition.or }}</option> | 			<option value="or">{{ i18n.ts._role._condition.or }}</option> | ||||||
| 			<option value="not">{{ i18n.ts._role._condition.not }}</option> | 			<option value="not">{{ i18n.ts._role._condition.not }}</option> | ||||||
|  | @ -37,6 +41,9 @@ | ||||||
| 	<MkInput v-else-if="type === 'createdLessThan' || type === 'createdMoreThan'" v-model="v.sec" type="number"> | 	<MkInput v-else-if="type === 'createdLessThan' || type === 'createdMoreThan'" v-model="v.sec" type="number"> | ||||||
| 		<template #suffix>sec</template> | 		<template #suffix>sec</template> | ||||||
| 	</MkInput> | 	</MkInput> | ||||||
|  | 
 | ||||||
|  | 	<MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq'].includes(type)" v-model="v.value" type="number"> | ||||||
|  | 	</MkInput> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | @ -85,6 +92,10 @@ const type = computed({ | ||||||
| 		if (t === 'not') v.value.value = { id: uuid(), type: 'isRemote' }; | 		if (t === 'not') v.value.value = { id: uuid(), type: 'isRemote' }; | ||||||
| 		if (t === 'createdLessThan') v.value.sec = 86400; | 		if (t === 'createdLessThan') v.value.sec = 86400; | ||||||
| 		if (t === 'createdMoreThan') v.value.sec = 86400; | 		if (t === 'createdMoreThan') v.value.sec = 86400; | ||||||
|  | 		if (t === 'followersLessThanOrEq') v.value.value = 10; | ||||||
|  | 		if (t === 'followersMoreThanOrEq') v.value.value = 10; | ||||||
|  | 		if (t === 'followingLessThanOrEq') v.value.value = 10; | ||||||
|  | 		if (t === 'followingMoreThanOrEq') v.value.value = 10; | ||||||
| 		v.value.type = t; | 		v.value.type = t; | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue