diff --git a/packages/backend/migration/1731898598469-addBlockingReactionUser.js b/packages/backend/migration/1731898598469-addBlockingReactionUser.js new file mode 100644 index 0000000000..6cf9d4ca87 --- /dev/null +++ b/packages/backend/migration/1731898598469-addBlockingReactionUser.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddBlockingReactionUser1731898598469 { + name = 'AddBlockingReactionUser1731898598469' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "blocking" ADD "isReactionBlock" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`COMMENT ON COLUMN "blocking"."isReactionBlock" IS 'Whether the blockee is a reaction block.'`); + await queryRunner.query(`CREATE INDEX "IDX_7b0698c38d27a5554bed4858bd" ON "blocking" ("isReactionBlock") `); + } + + async down(queryRunner) { + await queryRunner.query(`DELETE FROM blocking WHERE "isReactionBlock" = 'true'`); // blockingテーブルのisReactionBlockカラムがtrueの行を削除する + await queryRunner.query(`DROP INDEX "IDX_7b0698c38d27a5554bed4858bd"`); + await queryRunner.query(`ALTER TABLE "blocking" DROP COLUMN "isReactionBlock"`); + } +} diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 6725ebe75b..db8c3c372d 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -24,6 +24,8 @@ export class CacheService implements OnApplicationShutdown { public userMutingsCache: RedisKVCache>; public userBlockingCache: RedisKVCache>; public userBlockedCache: RedisKVCache>; // NOTE: 「被」Blockキャッシュ + public userReactionBlockingCache: RedisKVCache>; // NOTE: リアクションBlockキャッシュ + public userReactionBlockedCache: RedisKVCache>; // NOTE: 「被」リアクションBlockキャッシュ public renoteMutingsCache: RedisKVCache>; public userFollowingsCache: RedisKVCache | undefined>>; @@ -80,7 +82,7 @@ export class CacheService implements OnApplicationShutdown { this.userBlockingCache = new RedisKVCache>(this.redisClient, 'userBlocking', { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60, // 1m - fetcher: (key) => this.blockingsRepository.find({ where: { blockerId: key }, select: ['blockeeId'] }).then(xs => new Set(xs.map(x => x.blockeeId))), + fetcher: (key) => this.blockingsRepository.find({ where: { blockerId: key, isReactionBlock: false }, select: ['blockeeId'] }).then(xs => new Set(xs.map(x => x.blockeeId))), toRedisConverter: (value) => JSON.stringify(Array.from(value)), fromRedisConverter: (value) => new Set(JSON.parse(value)), }); @@ -88,7 +90,23 @@ export class CacheService implements OnApplicationShutdown { this.userBlockedCache = new RedisKVCache>(this.redisClient, 'userBlocked', { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60, // 1m - fetcher: (key) => this.blockingsRepository.find({ where: { blockeeId: key }, select: ['blockerId'] }).then(xs => new Set(xs.map(x => x.blockerId))), + fetcher: (key) => this.blockingsRepository.find({ where: { blockeeId: key, isReactionBlock: false }, select: ['blockerId'] }).then(xs => new Set(xs.map(x => x.blockerId))), + toRedisConverter: (value) => JSON.stringify(Array.from(value)), + fromRedisConverter: (value) => new Set(JSON.parse(value)), + }); + + this.userReactionBlockingCache = new RedisKVCache>(this.redisClient, 'userReactionBlocking', { + lifetime: 1000 * 60 * 30, // 30m + memoryCacheLifetime: 1000 * 60, // 1m + fetcher: (key) => this.blockingsRepository.find({ where: { blockerId: key, isReactionBlock: true }, select: ['blockeeId'] }).then(xs => new Set(xs.map(x => x.blockeeId))), + toRedisConverter: (value) => JSON.stringify(Array.from(value)), + fromRedisConverter: (value) => new Set(JSON.parse(value)), + }); + + this.userReactionBlockedCache = new RedisKVCache>(this.redisClient, 'userReactionBlocked', { + lifetime: 1000 * 60 * 30, // 30m + memoryCacheLifetime: 1000 * 60, // 1m + fetcher: (key) => this.blockingsRepository.find({ where: { blockeeId: key, isReactionBlock: true }, select: ['blockerId'] }).then(xs => new Set(xs.map(x => x.blockerId))), toRedisConverter: (value) => JSON.stringify(Array.from(value)), fromRedisConverter: (value) => new Set(JSON.parse(value)), }); diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 734d135648..393bdf26f7 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -58,6 +58,7 @@ import { S3Service } from './S3Service.js'; import { SignupService } from './SignupService.js'; import { WebAuthnService } from './WebAuthnService.js'; import { UserBlockingService } from './UserBlockingService.js'; +import { UserReactionBlockingService } from './UserReactionBlockingService.js'; import { CacheService } from './CacheService.js'; import { UserService } from './UserService.js'; import { UserFollowingService } from './UserFollowingService.js'; @@ -202,6 +203,7 @@ const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service }; const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService }; const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService }; const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService }; +const $UserReactionBlockingService: Provider = { provide: 'UserReactionBlockingService', useExisting: UserReactionBlockingService }; const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService }; const $UserService: Provider = { provide: 'UserService', useExisting: UserService }; const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService }; @@ -353,6 +355,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SignupService, WebAuthnService, UserBlockingService, + UserReactionBlockingService, CacheService, UserService, UserFollowingService, @@ -500,6 +503,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SignupService, $WebAuthnService, $UserBlockingService, + $UserReactionBlockingService, $CacheService, $UserService, $UserFollowingService, @@ -648,6 +652,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SignupService, WebAuthnService, UserBlockingService, + UserReactionBlockingService, CacheService, UserService, UserFollowingService, @@ -794,6 +799,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SignupService, $WebAuthnService, $UserBlockingService, + $UserReactionBlockingService, $CacheService, $UserService, $UserFollowingService, diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 03646ff566..d113848c20 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -223,6 +223,8 @@ export interface InternalEventTypes { unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; }; blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; }; blockingDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; }; + blockingReactionCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; }; + blockingReactionDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; }; policiesUpdated: MiRole['policies']; roleCreated: MiRole; roleDeleted: MiRole; diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 2f1310b8ef..0234042903 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -73,6 +73,7 @@ export class UserBlockingService implements OnModuleInit { blockerId: blocker.id, blockee, blockeeId: blockee.id, + isReactionBlock: false, } as MiBlocking; await this.blockingsRepository.insert(blocking); @@ -160,6 +161,7 @@ export class UserBlockingService implements OnModuleInit { const blocking = await this.blockingsRepository.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, + isReactionBlock: false, }); if (blocking == null) { @@ -169,28 +171,23 @@ export class UserBlockingService implements OnModuleInit { // Since we already have the blocker and blockee, we do not need to fetch // them in the query above and can just manually insert them here. - blocking.blocker = blocker; - blocking.blockee = blockee; + // But we don't need to do this because we are not using them in this function. + // blocking.blocker = blocker; + // blocking.blockee = blockee; await this.blockingsRepository.delete(blocking.id); - this.cacheService.userBlockingCache.refresh(blocker.id); - this.cacheService.userBlockedCache.refresh(blockee.id); + this.cacheService.userReactionBlockedCache.refresh(blocker.id); + this.cacheService.userReactionBlockedCache.refresh(blockee.id); - this.globalEventService.publishInternalEvent('blockingDeleted', { + this.globalEventService.publishInternalEvent('blockingReactionDeleted', { blockerId: blocker.id, blockeeId: blockee.id, }); - - // deliver if remote bloking - if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderBlock(blocking), blocker)); - this.queueService.deliver(blocker, content, blockee.inbox, false); - } } @bindThis public async checkBlocked(blockerId: MiUser['id'], blockeeId: MiUser['id']): Promise { - return (await this.cacheService.userBlockingCache.fetch(blockerId)).has(blockeeId); + return (await this.cacheService.userReactionBlockingCache.fetch(blockerId)).has(blockeeId); } } diff --git a/packages/backend/src/core/UserReactionBlockingService.ts b/packages/backend/src/core/UserReactionBlockingService.ts new file mode 100644 index 0000000000..99e807bc89 --- /dev/null +++ b/packages/backend/src/core/UserReactionBlockingService.ts @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { IdService } from '@/core/IdService.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiBlocking } from '@/models/Blocking.js'; +import { QueueService } from '@/core/QueueService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; +import type { BlockingsRepository, UserListsRepository } from '@/models/_.js'; +import Logger from '@/logger.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import { UserWebhookService } from '@/core/UserWebhookService.js'; +import { bindThis } from '@/decorators.js'; +import { CacheService } from '@/core/CacheService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; + +@Injectable() +export class UserReactionBlockingService implements OnModuleInit { + private logger: Logger; + private userFollowingService: UserFollowingService; + + constructor( + private moduleRef: ModuleRef, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private cacheService: CacheService, + private userEntityService: UserEntityService, + private idService: IdService, + private queueService: QueueService, + private globalEventService: GlobalEventService, + private webhookService: UserWebhookService, + private apRendererService: ApRendererService, + private loggerService: LoggerService, + ) { + this.logger = this.loggerService.getLogger('user-block'); + } + + @bindThis + public async block(blocker: MiUser, blockee: MiUser, silent = false) { + const blocking = { + id: this.idService.gen(), + blocker, + blockerId: blocker.id, + blockee, + blockeeId: blockee.id, + isReactionBlock: true, + } as MiBlocking; + + await this.blockingsRepository.insert(blocking); + + this.cacheService.userReactionBlockingCache.refresh(blocker.id); + this.cacheService.userReactionBlockedCache.refresh(blockee.id); + + this.globalEventService.publishInternalEvent('blockingReactionCreated', { + blockerId: blocker.id, + blockeeId: blockee.id, + }); + } + + @bindThis + public async unblock(blocker: MiUser, blockee: MiUser) { + const blocking = await this.blockingsRepository.findOneBy({ + blockerId: blocker.id, + blockeeId: blockee.id, + isReactionBlock: true, + }); + + if (blocking == null) { + this.logger.warn('ブロック解除がリクエストされましたがブロックしていませんでした'); + return; + } + + // Since we already have the blocker and blockee, we do not need to fetch + // them in the query above and can just manually insert them here. + blocking.blocker = blocker; + blocking.blockee = blockee; + + await this.blockingsRepository.delete(blocking.id); + + this.cacheService.userBlockingCache.refresh(blocker.id); + this.cacheService.userBlockedCache.refresh(blockee.id); + + this.globalEventService.publishInternalEvent('blockingDeleted', { + blockerId: blocker.id, + blockeeId: blockee.id, + }); + + // deliver if remote bloking + if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) { + const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderBlock(blocking), blocker)); + this.queueService.deliver(blocker, content, blockee.inbox, false); + } + } + + @bindThis + public async checkBlocked(blockerId: MiUser['id'], blockeeId: MiUser['id']): Promise { + return (await this.cacheService.userBlockingCache.fetch(blockerId)).has(blockeeId); + } +} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index d3c087a153..a9dc7fb65b 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -76,6 +76,8 @@ export type UserRelation = { hasPendingFollowRequestToYou: boolean isBlocking: boolean isBlocked: boolean + isReactionBlocking: boolean + isReactionBlocked: boolean isMuted: boolean isRenoteMuted: boolean } @@ -169,6 +171,8 @@ export class UserEntityService implements OnModuleInit { hasPendingFollowRequestToYou, isBlocking, isBlocked, + isReactionBlocking, + isReactionBlocked, isMuted, isRenoteMuted, ] = await Promise.all([ @@ -198,12 +202,28 @@ export class UserEntityService implements OnModuleInit { where: { blockerId: me, blockeeId: target, + isReactionBlock: false, }, }), this.blockingsRepository.exists({ where: { blockerId: target, blockeeId: me, + isReactionBlock: false, + }, + }), + this.blockingsRepository.exists({ + where: { + blockerId: me, + blockeeId: target, + isReactionBlock: true, + }, + }), + this.blockingsRepository.exists({ + where: { + blockerId: target, + blockeeId: me, + isReactionBlock: true, }, }), this.mutingsRepository.exists({ @@ -229,6 +249,8 @@ export class UserEntityService implements OnModuleInit { hasPendingFollowRequestToYou, isBlocking, isBlocked, + isReactionBlocking, + isReactionBlocked, isMuted, isRenoteMuted, }; @@ -243,6 +265,8 @@ export class UserEntityService implements OnModuleInit { followeesRequests, blockers, blockees, + reactionBlockers, + reactionBlockees, muters, renoteMuters, ] = await Promise.all([ @@ -266,11 +290,25 @@ export class UserEntityService implements OnModuleInit { this.blockingsRepository.createQueryBuilder('b') .select('b.blockeeId') .where('b.blockerId = :me', { me }) + .andWhere('b.isReactionBlock = false') .getRawMany<{ b_blockeeId: string }>() .then(it => it.map(it => it.b_blockeeId)), this.blockingsRepository.createQueryBuilder('b') .select('b.blockerId') .where('b.blockeeId = :me', { me }) + .andWhere('b.isReactionBlock = false') + .getRawMany<{ b_blockerId: string }>() + .then(it => it.map(it => it.b_blockerId)), + this.blockingsRepository.createQueryBuilder('b') + .select('b.blockeeId') + .where('b.blockerId = :me', { me }) + .andWhere('b.isReactionBlock = true') + .getRawMany<{ b_blockeeId: string }>() + .then(it => it.map(it => it.b_blockeeId)), + this.blockingsRepository.createQueryBuilder('b') + .select('b.blockerId') + .where('b.blockeeId = :me', { me }) + .andWhere('b.isReactionBlock = true') .getRawMany<{ b_blockerId: string }>() .then(it => it.map(it => it.b_blockerId)), this.mutingsRepository.createQueryBuilder('m') @@ -300,6 +338,8 @@ export class UserEntityService implements OnModuleInit { hasPendingFollowRequestToYou: followeesRequests.includes(target), isBlocking: blockers.includes(target), isBlocked: blockees.includes(target), + isReactionBlocking: reactionBlockers.includes(target), + isReactionBlocked: reactionBlockees.includes(target), isMuted: muters.includes(target), isRenoteMuted: renoteMuters.includes(target), }, @@ -638,6 +678,8 @@ export class UserEntityService implements OnModuleInit { hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou, isBlocking: relation.isBlocking, isBlocked: relation.isBlocked, + isReactionBlocking: relation.isReactionBlocking, + isReactionBlocked: relation.isReactionBlocked, isMuted: relation.isMuted, isRenoteMuted: relation.isRenoteMuted, notify: relation.following?.notify ?? 'none', diff --git a/packages/backend/src/models/Blocking.ts b/packages/backend/src/models/Blocking.ts index 34a6efe5a6..8989154e7e 100644 --- a/packages/backend/src/models/Blocking.ts +++ b/packages/backend/src/models/Blocking.ts @@ -38,4 +38,11 @@ export class MiBlocking { }) @JoinColumn() public blocker: MiUser | null; + + @Index() + @Column({ + comment: 'Whether the blockee is a reaction block.', + default: false, + }) + public isReactionBlock: boolean; } diff --git a/packages/backend/src/models/json-schema/blocking.ts b/packages/backend/src/models/json-schema/blocking.ts index 2d02ba6a70..ccaf18cd8a 100644 --- a/packages/backend/src/models/json-schema/blocking.ts +++ b/packages/backend/src/models/json-schema/blocking.ts @@ -27,5 +27,9 @@ export const packedBlockingSchema = { optional: false, nullable: false, ref: 'UserDetailedNotMe', }, + isReactionBlock: { + type: 'boolean', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 38631f907d..18b80155c8 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -416,6 +416,14 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'boolean', nullable: false, optional: true, }, + isReactionBlocking: { + type: 'boolean', + nullable: false, optional: true, + }, + isReactionBlocked: { + type: 'boolean', + nullable: false, optional: true, + }, isMuted: { type: 'boolean', nullable: false, optional: true, diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 5bb194313d..512f27e6a9 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -231,6 +231,9 @@ import * as ep___i_favorites from './endpoints/i/favorites.js'; import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js'; import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js'; import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; +import * as ep___blocking_reaction_user_create from './endpoints/blocking-reaction-user/create.js'; +import * as ep___blocking_reaction_user_delete from './endpoints/blocking-reaction-user/delete.js'; +import * as ep___blocking_reaction_user_list from './endpoints/blocking-reaction-user/list.js'; import * as ep___i_importFollowing from './endpoints/i/import-following.js'; import * as ep___i_importMuting from './endpoints/i/import-muting.js'; import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js'; @@ -502,6 +505,9 @@ const $auth_session_userkey: Provider = { provide: 'ep:auth/session/userkey', us const $blocking_create: Provider = { provide: 'ep:blocking/create', useClass: ep___blocking_create.default }; const $blocking_delete: Provider = { provide: 'ep:blocking/delete', useClass: ep___blocking_delete.default }; const $blocking_list: Provider = { provide: 'ep:blocking/list', useClass: ep___blocking_list.default }; +const $blocking_reaction_user_create: Provider = { provide: 'ep:blocking-reaction-user/create', useClass: ep___blocking_reaction_user_create.default }; +const $blocking_reaction_user_delete: Provider = { provide: 'ep:blocking-reaction-user/delete', useClass: ep___blocking_reaction_user_delete.default }; +const $blocking_reaction_user_list: Provider = { provide: 'ep:blocking-reaction-user/list', useClass: ep___blocking_reaction_user_list.default }; const $channels_create: Provider = { provide: 'ep:channels/create', useClass: ep___channels_create.default }; const $channels_featured: Provider = { provide: 'ep:channels/featured', useClass: ep___channels_featured.default }; const $channels_follow: Provider = { provide: 'ep:channels/follow', useClass: ep___channels_follow.default }; @@ -894,6 +900,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $blocking_create, $blocking_delete, $blocking_list, + $blocking_reaction_user_create, + $blocking_reaction_user_delete, + $blocking_reaction_user_list, $channels_create, $channels_featured, $channels_follow, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 15809b2678..d015536610 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -120,6 +120,9 @@ import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js' import * as ep___blocking_create from './endpoints/blocking/create.js'; import * as ep___blocking_delete from './endpoints/blocking/delete.js'; import * as ep___blocking_list from './endpoints/blocking/list.js'; +import * as ep___blocking_reaction_user_create from './endpoints/blocking-reaction-user/create.js'; +import * as ep___blocking_reaction_user_delete from './endpoints/blocking-reaction-user/delete.js'; +import * as ep___blocking_reaction_user_list from './endpoints/blocking-reaction-user/list.js'; import * as ep___channels_create from './endpoints/channels/create.js'; import * as ep___channels_featured from './endpoints/channels/featured.js'; import * as ep___channels_follow from './endpoints/channels/follow.js'; @@ -506,6 +509,9 @@ const eps = [ ['blocking/create', ep___blocking_create], ['blocking/delete', ep___blocking_delete], ['blocking/list', ep___blocking_list], + ['blocking-reaction-user/create', ep___blocking_reaction_user_create], + ['blocking-reaction-user/delete', ep___blocking_reaction_user_delete], + ['blocking-reaction-user/list', ep___blocking_reaction_user_list], ['channels/create', ep___channels_create], ['channels/featured', ep___channels_featured], ['channels/follow', ep___channels_follow], diff --git a/packages/backend/src/server/api/endpoints/blocking-reaction-user/create.ts b/packages/backend/src/server/api/endpoints/blocking-reaction-user/create.ts new file mode 100644 index 0000000000..1c75de0796 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/blocking-reaction-user/create.ts @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import ms from 'ms'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; +import { UserReactionBlockingService } from '@/core/UserReactionBlockingService.js'; + +export const meta = { + tags: ['account'], + + limit: { + duration: ms('1hour'), + max: 20, + }, + + requireCredential: true, + + kind: 'write:blocks', + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '7cc4f851-e2f1-4621-9633-ec9e1d00c01e', + }, + + blockeeIsYourself: { + message: 'Blockee is yourself.', + code: 'BLOCKEE_IS_YOURSELF', + id: '88b19138-f28d-42c0-8499-6a31bbd0fdc6', + }, + + alreadyBlocking: { + message: 'You are already blocking that user.', + code: 'ALREADY_BLOCKING', + id: '787fed64-acb9-464a-82eb-afbd745b9614', + }, + }, + + res: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userReactionBlockingService: UserReactionBlockingService, + ) { + super(meta, paramDef, async (ps, me) => { + const blocker = await this.usersRepository.findOneByOrFail({ id: me.id }); + + // 自分自身 + if (me.id === ps.userId) { + throw new ApiError(meta.errors.blockeeIsYourself); + } + + // Get blockee + const blockee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check if already blocking + const exist = await this.blockingsRepository.exists({ + where: { + blockerId: blocker.id, + blockeeId: blockee.id, + isReactionBlock: true, + }, + }); + + if (exist) { + throw new ApiError(meta.errors.alreadyBlocking); + } + + await this.userReactionBlockingService.block(blocker, blockee); + + return await this.userEntityService.pack(blockee.id, blocker, { + schema: 'UserDetailedNotMe', + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/blocking-reaction-user/delete.ts b/packages/backend/src/server/api/endpoints/blocking-reaction-user/delete.ts new file mode 100644 index 0000000000..f35fc9ab9e --- /dev/null +++ b/packages/backend/src/server/api/endpoints/blocking-reaction-user/delete.ts @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import ms from 'ms'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserReactionBlockingService } from '@/core/UserReactionBlockingService.js'; +import { DI } from '@/di-symbols.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['account'], + + limit: { + duration: ms('1hour'), + max: 100, + }, + + requireCredential: true, + + kind: 'write:blocks', + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '8621d8bf-c358-4303-a066-5ea78610eb3f', + }, + + blockeeIsYourself: { + message: 'Blockee is yourself.', + code: 'BLOCKEE_IS_YOURSELF', + id: '06f6fac6-524b-473c-a354-e97a40ae6eac', + }, + + notBlocking: { + message: 'You are not blocking that user.', + code: 'NOT_BLOCKING', + id: '291b2efa-60c6-45c0-9f6a-045c8f9b02cd', + }, + }, + + res: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userReactionBlockingService: UserReactionBlockingService, + ) { + super(meta, paramDef, async (ps, me) => { + const blocker = await this.usersRepository.findOneByOrFail({ id: me.id }); + + // Check if the blockee is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.blockeeIsYourself); + } + + // Get blockee + const blockee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not blocking + const exist = await this.blockingsRepository.exists({ + where: { + blockerId: blocker.id, + blockeeId: blockee.id, + isReactionBlock: true, + }, + }); + + if (!exist) { + throw new ApiError(meta.errors.notBlocking); + } + + // Delete blocking + await this.userReactionBlockingService.unblock(blocker, blockee); + + return await this.userEntityService.pack(blockee.id, blocker, { + schema: 'UserDetailedNotMe', + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/blocking-reaction-user/list.ts b/packages/backend/src/server/api/endpoints/blocking-reaction-user/list.ts new file mode 100644 index 0000000000..385266b1e4 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/blocking-reaction-user/list.ts @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { BlockingsRepository } from '@/models/_.js'; +import { QueryService } from '@/core/QueryService.js'; +import { BlockingEntityService } from '@/core/entities/BlockingEntityService.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['account'], + + requireCredential: true, + + kind: 'read:blocks', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Blocking', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + }, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private blockingEntityService: BlockingEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) + .andWhere('blocking.blockerId = :meId', { meId: me.id }) + .andWhere('blocking.isReactionBlock = true'); + + const blockings = await query + .limit(ps.limit) + .getMany(); + + return await this.blockingEntityService.packMany(blockings, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 8431fa6b34..21f8b4815b 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -49,7 +49,8 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) - .andWhere('blocking.blockerId = :meId', { meId: me.id }); + .andWhere('blocking.blockerId = :meId', { meId: me.id }) + .andWhere('blocking.isReactionBlock = false'); const blockings = await query .limit(ps.limit) diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts index e4f42809f8..b28d386b43 100644 --- a/packages/backend/test/unit/entities/UserEntityService.ts +++ b/packages/backend/test/unit/entities/UserEntityService.ts @@ -115,6 +115,16 @@ describe('UserEntityService', () => { id: genAidx(Date.now()), blockerId: blocker.id, blockeeId: blockee.id, + isReactionBlock: false, + }); + } + + async function blockReaction(blocker: MiUser, blockee: MiUser) { + await blockingRepository.insert({ + id: genAidx(Date.now()), + blockerId: blocker.id, + blockeeId: blockee.id, + isReactionBlock: true, }); } @@ -260,6 +270,8 @@ describe('UserEntityService', () => { expect(actual.hasPendingFollowRequestToYou).toBe(false); expect(actual.isBlocking).toBe(false); expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(false); expect(actual.isMuted).toBe(false); expect(actual.isRenoteMuted).toBe(false); } @@ -275,6 +287,8 @@ describe('UserEntityService', () => { expect(actual.hasPendingFollowRequestToYou).toBe(false); expect(actual.isBlocking).toBe(false); expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(false); expect(actual.isMuted).toBe(false); expect(actual.isRenoteMuted).toBe(false); } @@ -290,6 +304,8 @@ describe('UserEntityService', () => { expect(actual.hasPendingFollowRequestToYou).toBe(false); expect(actual.isBlocking).toBe(false); expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(false); expect(actual.isMuted).toBe(false); expect(actual.isRenoteMuted).toBe(false); } @@ -305,6 +321,8 @@ describe('UserEntityService', () => { expect(actual.hasPendingFollowRequestToYou).toBe(true); expect(actual.isBlocking).toBe(false); expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(false); expect(actual.isMuted).toBe(false); expect(actual.isRenoteMuted).toBe(false); } @@ -320,6 +338,8 @@ describe('UserEntityService', () => { expect(actual.hasPendingFollowRequestToYou).toBe(false); expect(actual.isBlocking).toBe(true); expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(false); expect(actual.isMuted).toBe(false); expect(actual.isRenoteMuted).toBe(false); } @@ -339,6 +359,41 @@ describe('UserEntityService', () => { expect(actual.isRenoteMuted).toBe(false); } + + // meがリアクションをブロックしてる人たち + const reactionBlockingYou = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of reactionBlockingYou) { + await blockReaction(me, who); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(true); + expect(actual.isReactionBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + + // meのリアクションをブロックしてる人たち + const reactionBlockingMe = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of reactionBlockingMe) { + await blockReaction(who, me); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(true); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + // meがミュートしてる人たち const muters = await Promise.all(randomIntRange().map(() => createUser())); for (const who of muters) { @@ -350,6 +405,8 @@ describe('UserEntityService', () => { expect(actual.hasPendingFollowRequestToYou).toBe(false); expect(actual.isBlocking).toBe(false); expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(false); expect(actual.isMuted).toBe(true); expect(actual.isRenoteMuted).toBe(false); } @@ -365,6 +422,8 @@ describe('UserEntityService', () => { expect(actual.hasPendingFollowRequestToYou).toBe(false); expect(actual.isBlocking).toBe(false); expect(actual.isBlocked).toBe(false); + expect(actual.isReactionBlocking).toBe(false); + expect(actual.isReactionBlocked).toBe(false); expect(actual.isMuted).toBe(false); expect(actual.isRenoteMuted).toBe(true); } diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 01a3dbbb30..35cd112b6d 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -551,6 +551,24 @@ type BlockingListRequest = operations['blocking___list']['requestBody']['content // @public (undocumented) type BlockingListResponse = operations['blocking___list']['responses']['200']['content']['application/json']; +// @public (undocumented) +type BlockingReactionUserCreateRequest = operations['blocking-reaction-user___create']['requestBody']['content']['application/json']; + +// @public (undocumented) +type BlockingReactionUserCreateResponse = operations['blocking-reaction-user___create']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type BlockingReactionUserDeleteRequest = operations['blocking-reaction-user___delete']['requestBody']['content']['application/json']; + +// @public (undocumented) +type BlockingReactionUserDeleteResponse = operations['blocking-reaction-user___delete']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type BlockingReactionUserListRequest = operations['blocking-reaction-user___list']['requestBody']['content']['application/json']; + +// @public (undocumented) +type BlockingReactionUserListResponse = operations['blocking-reaction-user___list']['responses']['200']['content']['application/json']; + // @public (undocumented) type BubbleGameRankingRequest = operations['bubble-game___ranking']['requestBody']['content']['application/json']; @@ -1381,6 +1399,12 @@ declare namespace entities { BlockingDeleteResponse, BlockingListRequest, BlockingListResponse, + BlockingReactionUserCreateRequest, + BlockingReactionUserCreateResponse, + BlockingReactionUserDeleteRequest, + BlockingReactionUserDeleteResponse, + BlockingReactionUserListRequest, + BlockingReactionUserListResponse, ChannelsCreateRequest, ChannelsCreateResponse, ChannelsFeaturedResponse, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 1837f3db4f..314aa6adee 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1204,6 +1204,39 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:blocks* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:blocks* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:blocks* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index cb1f4dbe96..e9e01fbc61 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -156,6 +156,12 @@ import type { BlockingDeleteResponse, BlockingListRequest, BlockingListResponse, + BlockingReactionUserCreateRequest, + BlockingReactionUserCreateResponse, + BlockingReactionUserDeleteRequest, + BlockingReactionUserDeleteResponse, + BlockingReactionUserListRequest, + BlockingReactionUserListResponse, ChannelsCreateRequest, ChannelsCreateResponse, ChannelsFeaturedResponse, @@ -690,6 +696,9 @@ export type Endpoints = { 'blocking/create': { req: BlockingCreateRequest; res: BlockingCreateResponse }; 'blocking/delete': { req: BlockingDeleteRequest; res: BlockingDeleteResponse }; 'blocking/list': { req: BlockingListRequest; res: BlockingListResponse }; + 'blocking-reaction-user/create': { req: BlockingReactionUserCreateRequest; res: BlockingReactionUserCreateResponse }; + 'blocking-reaction-user/delete': { req: BlockingReactionUserDeleteRequest; res: BlockingReactionUserDeleteResponse }; + 'blocking-reaction-user/list': { req: BlockingReactionUserListRequest; res: BlockingReactionUserListResponse }; 'channels/create': { req: ChannelsCreateRequest; res: ChannelsCreateResponse }; 'channels/featured': { req: EmptyRequest; res: ChannelsFeaturedResponse }; 'channels/follow': { req: ChannelsFollowRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index a8f474c25c..e01c6addf5 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -159,6 +159,12 @@ export type BlockingDeleteRequest = operations['blocking___delete']['requestBody export type BlockingDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json']; export type BlockingListRequest = operations['blocking___list']['requestBody']['content']['application/json']; export type BlockingListResponse = operations['blocking___list']['responses']['200']['content']['application/json']; +export type BlockingReactionUserCreateRequest = operations['blocking-reaction-user___create']['requestBody']['content']['application/json']; +export type BlockingReactionUserCreateResponse = operations['blocking-reaction-user___create']['responses']['200']['content']['application/json']; +export type BlockingReactionUserDeleteRequest = operations['blocking-reaction-user___delete']['requestBody']['content']['application/json']; +export type BlockingReactionUserDeleteResponse = operations['blocking-reaction-user___delete']['responses']['200']['content']['application/json']; +export type BlockingReactionUserListRequest = operations['blocking-reaction-user___list']['requestBody']['content']['application/json']; +export type BlockingReactionUserListResponse = operations['blocking-reaction-user___list']['responses']['200']['content']['application/json']; export type ChannelsCreateRequest = operations['channels___create']['requestBody']['content']['application/json']; export type ChannelsCreateResponse = operations['channels___create']['responses']['200']['content']['application/json']; export type ChannelsFeaturedResponse = operations['channels___featured']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 280abba727..4a574781ab 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -997,6 +997,33 @@ export type paths = { */ post: operations['blocking___list']; }; + '/blocking-reaction-user/create': { + /** + * blocking-reaction-user/create + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:blocks* + */ + post: operations['blocking-reaction-user___create']; + }; + '/blocking-reaction-user/delete': { + /** + * blocking-reaction-user/delete + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:blocks* + */ + post: operations['blocking-reaction-user___delete']; + }; + '/blocking-reaction-user/list': { + /** + * blocking-reaction-user/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:blocks* + */ + post: operations['blocking-reaction-user___list']; + }; '/channels/create': { /** * channels/create @@ -3825,6 +3852,8 @@ export type components = { hasPendingFollowRequestToYou?: boolean; isBlocking?: boolean; isBlocked?: boolean; + isReactionBlocking?: boolean; + isReactionBlocked?: boolean; isMuted?: boolean; isRenoteMuted?: boolean; /** @enum {string} */ @@ -4486,6 +4515,7 @@ export type components = { /** Format: id */ blockeeId: string; blockee: components['schemas']['UserDetailedNotMe']; + isReactionBlock: boolean; }; Hashtag: { /** @example misskey */ @@ -11705,6 +11735,184 @@ export type operations = { }; }; }; + /** + * blocking-reaction-user/create + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:blocks* + */ + 'blocking-reaction-user___create': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserDetailedNotMe']; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description To many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * blocking-reaction-user/delete + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:blocks* + */ + 'blocking-reaction-user___delete': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserDetailedNotMe']; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description To many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * blocking-reaction-user/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:blocks* + */ + 'blocking-reaction-user___list': { + requestBody: { + content: { + 'application/json': { + /** @default 30 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Blocking'][]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * channels/create * @description No description provided.