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