Feat: 特定ユーザーからのリアクションをブロックする機能の追加
- BlockingReactionUserエンティティの追加 - blocking-reaction-userエンドポイントの追加 - 関連するフロントエンドの追加 Migrationがあります。
This commit is contained in:
parent
eed45c7915
commit
51bc83c3e7
|
@ -147,10 +147,14 @@ renoteMute: "Mute Renotes"
|
||||||
renoteUnmute: "Unmute Renotes"
|
renoteUnmute: "Unmute Renotes"
|
||||||
block: "Block"
|
block: "Block"
|
||||||
unblock: "Unblock"
|
unblock: "Unblock"
|
||||||
|
blockReactionUser: "Reaction Block"
|
||||||
|
unblockReactionUser: "Unblock Reaction"
|
||||||
suspend: "Suspend"
|
suspend: "Suspend"
|
||||||
unsuspend: "Unsuspend"
|
unsuspend: "Unsuspend"
|
||||||
blockConfirm: "Are you sure that you want to block this account?"
|
blockConfirm: "Are you sure that you want to block this account?"
|
||||||
unblockConfirm: "Are you sure that you want to unblock this account?"
|
unblockConfirm: "Are you sure that you want to unblock this account?"
|
||||||
|
blockReactionUserConfirm: "Are you sure that you want to block reactions from this account?"
|
||||||
|
unblockReactionUserConfirm: "Are you sure that you want to unblock reactions from this account?"
|
||||||
suspendConfirm: "Are you sure that you want to suspend this account?"
|
suspendConfirm: "Are you sure that you want to suspend this account?"
|
||||||
unsuspendConfirm: "Are you sure that you want to unsuspend this account?"
|
unsuspendConfirm: "Are you sure that you want to unsuspend this account?"
|
||||||
selectList: "Select a list"
|
selectList: "Select a list"
|
||||||
|
@ -244,6 +248,7 @@ federationAllowedHostsDescription: "Specify the hostnames of the servers you wan
|
||||||
muteAndBlock: "Mutes and Blocks"
|
muteAndBlock: "Mutes and Blocks"
|
||||||
mutedUsers: "Muted users"
|
mutedUsers: "Muted users"
|
||||||
blockedUsers: "Blocked users"
|
blockedUsers: "Blocked users"
|
||||||
|
reactionBlockedUsers: "Reaction blocked users"
|
||||||
noUsers: "There are no users"
|
noUsers: "There are no users"
|
||||||
editProfile: "Edit profile"
|
editProfile: "Edit profile"
|
||||||
noteDeleteConfirm: "Are you sure you want to delete this note?"
|
noteDeleteConfirm: "Are you sure you want to delete this note?"
|
||||||
|
|
|
@ -606,6 +606,14 @@ export interface Locale extends ILocale {
|
||||||
* ブロック解除
|
* ブロック解除
|
||||||
*/
|
*/
|
||||||
"unblock": string;
|
"unblock": string;
|
||||||
|
/**
|
||||||
|
* リアクションをブロック
|
||||||
|
*/
|
||||||
|
"blockReactionUser": string;
|
||||||
|
/**
|
||||||
|
* リアクションのブロックを解除
|
||||||
|
*/
|
||||||
|
"unblockReactionUser": string;
|
||||||
/**
|
/**
|
||||||
* 凍結
|
* 凍結
|
||||||
*/
|
*/
|
||||||
|
@ -622,6 +630,14 @@ export interface Locale extends ILocale {
|
||||||
* ブロック解除しますか?
|
* ブロック解除しますか?
|
||||||
*/
|
*/
|
||||||
"unblockConfirm": string;
|
"unblockConfirm": string;
|
||||||
|
/**
|
||||||
|
* リアクションをブロックしますか?
|
||||||
|
*/
|
||||||
|
"blockReactionUserConfirm": string;
|
||||||
|
/**
|
||||||
|
* リアクションのブロックを解除しますか?
|
||||||
|
*/
|
||||||
|
"unblockReactionUserConfirm": string;
|
||||||
/**
|
/**
|
||||||
* 凍結しますか?
|
* 凍結しますか?
|
||||||
*/
|
*/
|
||||||
|
@ -994,6 +1010,10 @@ export interface Locale extends ILocale {
|
||||||
* ブロックしたユーザー
|
* ブロックしたユーザー
|
||||||
*/
|
*/
|
||||||
"blockedUsers": string;
|
"blockedUsers": string;
|
||||||
|
/**
|
||||||
|
* リアクションをブロックしたユーザー
|
||||||
|
*/
|
||||||
|
"reactionBlockedUsers": string;
|
||||||
/**
|
/**
|
||||||
* ユーザーはいません
|
* ユーザーはいません
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -147,10 +147,14 @@ renoteMute: "リノートをミュート"
|
||||||
renoteUnmute: "リノートのミュートを解除"
|
renoteUnmute: "リノートのミュートを解除"
|
||||||
block: "ブロック"
|
block: "ブロック"
|
||||||
unblock: "ブロック解除"
|
unblock: "ブロック解除"
|
||||||
|
blockReactionUser: "リアクションをブロック"
|
||||||
|
unblockReactionUser: "リアクションのブロックを解除"
|
||||||
suspend: "凍結"
|
suspend: "凍結"
|
||||||
unsuspend: "解凍"
|
unsuspend: "解凍"
|
||||||
blockConfirm: "ブロックしますか?"
|
blockConfirm: "ブロックしますか?"
|
||||||
unblockConfirm: "ブロック解除しますか?"
|
unblockConfirm: "ブロック解除しますか?"
|
||||||
|
blockReactionUserConfirm: "リアクションをブロックしますか?"
|
||||||
|
unblockReactionUserConfirm: "リアクションのブロックを解除しますか?"
|
||||||
suspendConfirm: "凍結しますか?"
|
suspendConfirm: "凍結しますか?"
|
||||||
unsuspendConfirm: "解凍しますか?"
|
unsuspendConfirm: "解凍しますか?"
|
||||||
selectList: "リストを選択"
|
selectList: "リストを選択"
|
||||||
|
@ -244,6 +248,7 @@ federationAllowedHostsDescription: "連合を許可するサーバーのホス
|
||||||
muteAndBlock: "ミュートとブロック"
|
muteAndBlock: "ミュートとブロック"
|
||||||
mutedUsers: "ミュートしたユーザー"
|
mutedUsers: "ミュートしたユーザー"
|
||||||
blockedUsers: "ブロックしたユーザー"
|
blockedUsers: "ブロックしたユーザー"
|
||||||
|
reactionBlockedUsers: "リアクションをブロックしたユーザー"
|
||||||
noUsers: "ユーザーはいません"
|
noUsers: "ユーザーはいません"
|
||||||
editProfile: "プロフィールを編集"
|
editProfile: "プロフィールを編集"
|
||||||
noteDeleteConfirm: "このノートを削除しますか?"
|
noteDeleteConfirm: "このノートを削除しますか?"
|
||||||
|
|
|
@ -147,10 +147,14 @@ renoteMute: "リノートは見いひん"
|
||||||
renoteUnmute: "リノートもやっぱ見るわ"
|
renoteUnmute: "リノートもやっぱ見るわ"
|
||||||
block: "ブロック"
|
block: "ブロック"
|
||||||
unblock: "ブロックやめたる"
|
unblock: "ブロックやめたる"
|
||||||
|
blockReactionUser: "リアクションをブロックしたる"
|
||||||
|
unblockReactionUser: "リアクションのブロックをやめたる"
|
||||||
suspend: "凍結"
|
suspend: "凍結"
|
||||||
unsuspend: "溶かす"
|
unsuspend: "溶かす"
|
||||||
blockConfirm: "ブロックしてもええんか?"
|
blockConfirm: "ブロックしてもええんか?"
|
||||||
unblockConfirm: "ブロックやめたるってほんまか?"
|
unblockConfirm: "ブロックやめたるってほんまか?"
|
||||||
|
blockReactionUserConfirm: "リアクションをブロックしてもええんか?"
|
||||||
|
unblockReactionUserConfirm: "リアクションのブロックをやめたるってほんまか?"
|
||||||
suspendConfirm: "凍結してしもうてええか?"
|
suspendConfirm: "凍結してしもうてええか?"
|
||||||
unsuspendConfirm: "解凍するけどええか?"
|
unsuspendConfirm: "解凍するけどええか?"
|
||||||
selectList: "リストを選ぶ"
|
selectList: "リストを選ぶ"
|
||||||
|
@ -244,6 +248,7 @@ federationAllowedHostsDescription: "連合してもいいサーバーのホス
|
||||||
muteAndBlock: "ミュートとブロック"
|
muteAndBlock: "ミュートとブロック"
|
||||||
mutedUsers: "ミュートしとるユーザー"
|
mutedUsers: "ミュートしとるユーザー"
|
||||||
blockedUsers: "ブロックしとるユーザー"
|
blockedUsers: "ブロックしとるユーザー"
|
||||||
|
reactionBlockedUsers: "リアクションブロックしとるユーザー"
|
||||||
noUsers: "ユーザーはおらん"
|
noUsers: "ユーザーはおらん"
|
||||||
editProfile: "プロフィールをいじる"
|
editProfile: "プロフィールをいじる"
|
||||||
noteDeleteConfirm: "このノートをほかしてええか?"
|
noteDeleteConfirm: "このノートをほかしてええか?"
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: sakuhanight and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class MakeNotesHiddenBefore1729486255072 {
|
||||||
|
name = 'AddBlockingReactionUser1731566099974'
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE TABLE "blocking_reaction_user"(
|
||||||
|
id varchar(32) NULL,
|
||||||
|
blockeeId varchar(32) NULL,
|
||||||
|
blockerId varchar(32) NULL,
|
||||||
|
CONSTRAINT "PK_blocking_reaction_user" PRIMARY KEY (id),
|
||||||
|
CONSTRAINT "FK_blocking_reaction_user_blockeeid" FOREIGN KEY (blockeeid) REFERENCES "user" (id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT "FK_blocking_reaction_user_blockerid" FOREIGN KEY (blockerid) REFERENCES "user" (id) ON DELETE CASCADE);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_blocking_reaction_user_id" ON "blocking_reaction_user" (id);`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_blocking_reaction_user_blockeeid" ON "blocking_reaction_user" (blockeeid);`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_blocking_reaction_user_blockerid" ON "blocking_reaction_user" (blockerid);`);
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_blocking_reaction_user_blockeeid_blockerid" ON "blocking_reaction_user" (blockeeid, blockerid);`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_blocking_reaction_user_blockeeid_blockerid";`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_blocking_reaction_user_blockerid";`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_blocking_reaction_user_blockeeid";`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_blocking_reaction_user_id";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "blocking_reaction_user" DROP CONSTRAINT "FK_blocking_reaction_user_blockerid";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "blocking_reaction_user" DROP CONSTRAINT "FK_blocking_reaction_user_blockeeid";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "blocking_reaction_user" DROP CONSTRAINT "PK_blocking_reaction_user";`);
|
||||||
|
await queryRunner.query(`DROP TABLE "blocking_reaction_user";`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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 { QueueService } from '@/core/QueueService.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type {
|
||||||
|
FollowRequestsRepository,
|
||||||
|
BlockingsRepository,
|
||||||
|
UserListsRepository,
|
||||||
|
UserListMembershipsRepository,
|
||||||
|
BlockingReactionUsersRepository
|
||||||
|
} 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 {MiBlockingReactionUser} from "@/models/_.js";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BlockingReactionUserService implements OnModuleInit {
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private moduleRef: ModuleRef,
|
||||||
|
|
||||||
|
@Inject(DI.blockingReactionUsersRepository)
|
||||||
|
private blockingReactionUsersRepository: BlockingReactionUsersRepository,
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async block(blocker: MiUser, blockee: MiUser, silent = false) {
|
||||||
|
await Promise.all([
|
||||||
|
]);
|
||||||
|
|
||||||
|
const blocking = {
|
||||||
|
id: this.idService.gen(),
|
||||||
|
blocker,
|
||||||
|
blockerId: blocker.id,
|
||||||
|
blockee,
|
||||||
|
blockeeId: blockee.id,
|
||||||
|
} as MiBlockingReactionUser;
|
||||||
|
|
||||||
|
await this.blockingReactionUsersRepository.insert(blocking);
|
||||||
|
|
||||||
|
this.cacheService.blockingReactionUserCache.refresh(blocker.id);
|
||||||
|
this.cacheService.blockedReactionUserCache.refresh(blockee.id);
|
||||||
|
|
||||||
|
this.globalEventService.publishInternalEvent('blockingReactionUserCreated', {
|
||||||
|
blockerId: blocker.id,
|
||||||
|
blockeeId: blockee.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async unblock(blocker: MiUser, blockee: MiUser) {
|
||||||
|
const blocking = await this.blockingReactionUsersRepository.findOneBy({
|
||||||
|
blockerId: blocker.id,
|
||||||
|
blockeeId: blockee.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
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.blockingReactionUsersRepository.delete(blocking.id);
|
||||||
|
|
||||||
|
this.cacheService.blockingReactionUserCache.refresh(blocker.id);
|
||||||
|
this.cacheService.blockedReactionUserCache.refresh(blockee.id);
|
||||||
|
|
||||||
|
this.globalEventService.publishInternalEvent('blockingReactionUserDeleted', {
|
||||||
|
blockerId: blocker.id,
|
||||||
|
blockeeId: blockee.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async checkBlocked(blockerId: MiUser['id'], blockeeId: MiUser['id']): Promise<boolean> {
|
||||||
|
return (await this.cacheService.blockingReactionUserCache.fetch(blockerId)).has(blockeeId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,17 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
|
import type {
|
||||||
|
BlockingsRepository,
|
||||||
|
FollowingsRepository,
|
||||||
|
MutingsRepository,
|
||||||
|
RenoteMutingsRepository,
|
||||||
|
MiUserProfile,
|
||||||
|
UserProfilesRepository,
|
||||||
|
UsersRepository,
|
||||||
|
MiFollowing,
|
||||||
|
BlockingReactionUsersRepository
|
||||||
|
} from '@/models/_.js';
|
||||||
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -24,6 +34,8 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
public userMutingsCache: RedisKVCache<Set<string>>;
|
public userMutingsCache: RedisKVCache<Set<string>>;
|
||||||
public userBlockingCache: RedisKVCache<Set<string>>;
|
public userBlockingCache: RedisKVCache<Set<string>>;
|
||||||
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||||
|
public blockingReactionUserCache: RedisKVCache<Set<string>>; // NOTE: リアクションブロックするユーザーのキャッシュ
|
||||||
|
public blockedReactionUserCache: RedisKVCache<Set<string>>; // NOTE: リアクションブロックされるユーザーのキャッシュ
|
||||||
public renoteMutingsCache: RedisKVCache<Set<string>>;
|
public renoteMutingsCache: RedisKVCache<Set<string>>;
|
||||||
public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
||||||
|
|
||||||
|
@ -46,6 +58,9 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
@Inject(DI.blockingsRepository)
|
@Inject(DI.blockingsRepository)
|
||||||
private blockingsRepository: BlockingsRepository,
|
private blockingsRepository: BlockingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.blockingReactionUsersRepository)
|
||||||
|
private blockingReactionUsersRepository: BlockingReactionUsersRepository,
|
||||||
|
|
||||||
@Inject(DI.renoteMutingsRepository)
|
@Inject(DI.renoteMutingsRepository)
|
||||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
|
@ -93,6 +108,22 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.blockingReactionUserCache = new RedisKVCache<Set<string>>(this.redisClient, 'blockingReactionUser', {
|
||||||
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
fetcher: (key) => this.blockingReactionUsersRepository.find({ where: { blockerId: key }, 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.blockedReactionUserCache = new RedisKVCache<Set<string>>(this.redisClient, 'blockedReactionUser', {
|
||||||
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
fetcher: (key) => this.blockingReactionUsersRepository.find({ where: { blockeeId: key }, 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.renoteMutingsCache = new RedisKVCache<Set<string>>(this.redisClient, 'renoteMutings', {
|
this.renoteMutingsCache = new RedisKVCache<Set<string>>(this.redisClient, 'renoteMutings', {
|
||||||
lifetime: 1000 * 60 * 30, // 30m
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
memoryCacheLifetime: 1000 * 60, // 1m
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
|
|
@ -15,6 +15,8 @@ import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
import { UserSearchService } from '@/core/UserSearchService.js';
|
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||||
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
||||||
import { FlashService } from '@/core/FlashService.js';
|
import { FlashService } from '@/core/FlashService.js';
|
||||||
|
import { BlockingReactionUserEntityService } from '@/core/entities/BlockingReactionUserEntityService.js';
|
||||||
|
import { BlockingReactionUserService } from '@/core/BlockingReactionUserService.js';
|
||||||
import { AccountMoveService } from './AccountMoveService.js';
|
import { AccountMoveService } from './AccountMoveService.js';
|
||||||
import { AccountUpdateService } from './AccountUpdateService.js';
|
import { AccountUpdateService } from './AccountUpdateService.js';
|
||||||
import { AiService } from './AiService.js';
|
import { AiService } from './AiService.js';
|
||||||
|
@ -166,6 +168,7 @@ const $AntennaService: Provider = { provide: 'AntennaService', useExisting: Ante
|
||||||
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
|
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
|
||||||
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
||||||
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
|
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
|
||||||
|
const $BlockingReactionUserService: Provider = { provide: 'BlockingReactionUserService', useExisting: BlockingReactionUserService };
|
||||||
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
||||||
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
|
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
|
||||||
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
||||||
|
@ -250,6 +253,7 @@ const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useEx
|
||||||
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
|
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
|
||||||
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
||||||
const $BlockingEntityService: Provider = { provide: 'BlockingEntityService', useExisting: BlockingEntityService };
|
const $BlockingEntityService: Provider = { provide: 'BlockingEntityService', useExisting: BlockingEntityService };
|
||||||
|
const $BlockingReactionUserEntityService: Provider = { provide: 'BlockingReactionUserEntityService', useExisting: BlockingReactionUserEntityService };
|
||||||
const $ChannelEntityService: Provider = { provide: 'ChannelEntityService', useExisting: ChannelEntityService };
|
const $ChannelEntityService: Provider = { provide: 'ChannelEntityService', useExisting: ChannelEntityService };
|
||||||
const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting: ClipEntityService };
|
const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting: ClipEntityService };
|
||||||
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
||||||
|
@ -317,6 +321,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
AppLockService,
|
AppLockService,
|
||||||
AchievementService,
|
AchievementService,
|
||||||
AvatarDecorationService,
|
AvatarDecorationService,
|
||||||
|
BlockingReactionUserService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
CreateSystemUserService,
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
|
@ -401,6 +406,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
AppEntityService,
|
AppEntityService,
|
||||||
AuthSessionEntityService,
|
AuthSessionEntityService,
|
||||||
BlockingEntityService,
|
BlockingEntityService,
|
||||||
|
BlockingReactionUserEntityService,
|
||||||
ChannelEntityService,
|
ChannelEntityService,
|
||||||
ClipEntityService,
|
ClipEntityService,
|
||||||
DriveFileEntityService,
|
DriveFileEntityService,
|
||||||
|
@ -464,6 +470,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$AppLockService,
|
$AppLockService,
|
||||||
$AchievementService,
|
$AchievementService,
|
||||||
$AvatarDecorationService,
|
$AvatarDecorationService,
|
||||||
|
$BlockingReactionUserService,
|
||||||
$CaptchaService,
|
$CaptchaService,
|
||||||
$CreateSystemUserService,
|
$CreateSystemUserService,
|
||||||
$CustomEmojiService,
|
$CustomEmojiService,
|
||||||
|
@ -548,6 +555,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$AppEntityService,
|
$AppEntityService,
|
||||||
$AuthSessionEntityService,
|
$AuthSessionEntityService,
|
||||||
$BlockingEntityService,
|
$BlockingEntityService,
|
||||||
|
$BlockingReactionUserEntityService,
|
||||||
$ChannelEntityService,
|
$ChannelEntityService,
|
||||||
$ClipEntityService,
|
$ClipEntityService,
|
||||||
$DriveFileEntityService,
|
$DriveFileEntityService,
|
||||||
|
@ -612,6 +620,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
AppLockService,
|
AppLockService,
|
||||||
AchievementService,
|
AchievementService,
|
||||||
AvatarDecorationService,
|
AvatarDecorationService,
|
||||||
|
BlockingReactionUserService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
CreateSystemUserService,
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
|
@ -695,6 +704,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
AppEntityService,
|
AppEntityService,
|
||||||
AuthSessionEntityService,
|
AuthSessionEntityService,
|
||||||
BlockingEntityService,
|
BlockingEntityService,
|
||||||
|
BlockingReactionUserEntityService,
|
||||||
ChannelEntityService,
|
ChannelEntityService,
|
||||||
ClipEntityService,
|
ClipEntityService,
|
||||||
DriveFileEntityService,
|
DriveFileEntityService,
|
||||||
|
@ -840,6 +850,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$AppEntityService,
|
$AppEntityService,
|
||||||
$AuthSessionEntityService,
|
$AuthSessionEntityService,
|
||||||
$BlockingEntityService,
|
$BlockingEntityService,
|
||||||
|
$BlockingReactionUserEntityService,
|
||||||
$ChannelEntityService,
|
$ChannelEntityService,
|
||||||
$ClipEntityService,
|
$ClipEntityService,
|
||||||
$DriveFileEntityService,
|
$DriveFileEntityService,
|
||||||
|
|
|
@ -223,6 +223,8 @@ export interface InternalEventTypes {
|
||||||
unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
|
unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
|
||||||
blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
||||||
blockingDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
blockingDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
||||||
|
blockingReactionUserCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
||||||
|
blockingReactionUserDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
||||||
policiesUpdated: MiRole['policies'];
|
policiesUpdated: MiRole['policies'];
|
||||||
roleCreated: MiRole;
|
roleCreated: MiRole;
|
||||||
roleDeleted: MiRole;
|
roleDeleted: MiRole;
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
|
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
|
||||||
|
import { BlockingReactionUserService } from '@/core/BlockingReactionUserService.js';
|
||||||
|
|
||||||
const FALLBACK = '\u2764';
|
const FALLBACK = '\u2764';
|
||||||
|
|
||||||
|
@ -91,6 +92,7 @@ export class ReactionService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private userBlockingService: UserBlockingService,
|
private userBlockingService: UserBlockingService,
|
||||||
|
private blockingReactionUserService: BlockingReactionUserService,
|
||||||
private reactionsBufferingService: ReactionsBufferingService,
|
private reactionsBufferingService: ReactionsBufferingService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private featuredService: FeaturedService,
|
private featuredService: FeaturedService,
|
||||||
|
@ -107,7 +109,8 @@ export class ReactionService {
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (note.userId !== user.id) {
|
if (note.userId !== user.id) {
|
||||||
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
||||||
if (blocked) {
|
const reactionBlocked = await this.blockingReactionUserService.checkBlocked(note.userId, user.id);
|
||||||
|
if (blocked || reactionBlocked) {
|
||||||
throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7');
|
throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: sakuhanight and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { BlockingReactionUsersRepository } from '@/models/_.js';
|
||||||
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { MiBlockingReactionUser } from '@/models/BlockingReactionUser.js';
|
||||||
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BlockingReactionUserEntityService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.blockingReactionUsersRepository)
|
||||||
|
private blockingReactionUsersRepository: BlockingReactionUsersRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async pack(
|
||||||
|
src: MiBlockingReactionUser['id'] | MiBlockingReactionUser,
|
||||||
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
blockee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
|
): Promise<Packed<'BlockingReactionUser'>> {
|
||||||
|
const blocking = typeof src === 'object' ? src : await this.blockingReactionUsersRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
return await awaitAll({
|
||||||
|
id: blocking.id,
|
||||||
|
createdAt: this.idService.parse(blocking.id).date.toISOString(),
|
||||||
|
blockeeId: blocking.blockeeId,
|
||||||
|
blockee: hint?.blockee ?? this.userEntityService.pack(blocking.blockeeId, me, {
|
||||||
|
schema: 'UserDetailedNotMe',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packMany(
|
||||||
|
blockings: MiBlockingReactionUser[],
|
||||||
|
me: { id: MiUser['id'] },
|
||||||
|
) {
|
||||||
|
const _blockees = blockings.map(({ blockee, blockeeId }) => blockee ?? blockeeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_blockees, me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(blockings.map(blocking => this.pack(blocking, me, { blockee: _userMap.get(blocking.blockeeId) })));
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import {
|
||||||
} from '@/models/User.js';
|
} from '@/models/User.js';
|
||||||
import type {
|
import type {
|
||||||
BlockingsRepository,
|
BlockingsRepository,
|
||||||
|
BlockingReactionUsersRepository,
|
||||||
FollowingsRepository,
|
FollowingsRepository,
|
||||||
FollowRequestsRepository,
|
FollowRequestsRepository,
|
||||||
MiFollowing,
|
MiFollowing,
|
||||||
|
@ -76,6 +77,8 @@ export type UserRelation = {
|
||||||
hasPendingFollowRequestToYou: boolean
|
hasPendingFollowRequestToYou: boolean
|
||||||
isBlocking: boolean
|
isBlocking: boolean
|
||||||
isBlocked: boolean
|
isBlocked: boolean
|
||||||
|
isReactionBlocking: boolean
|
||||||
|
isReactionBlocked: boolean
|
||||||
isMuted: boolean
|
isMuted: boolean
|
||||||
isRenoteMuted: boolean
|
isRenoteMuted: boolean
|
||||||
}
|
}
|
||||||
|
@ -116,6 +119,9 @@ export class UserEntityService implements OnModuleInit {
|
||||||
@Inject(DI.blockingsRepository)
|
@Inject(DI.blockingsRepository)
|
||||||
private blockingsRepository: BlockingsRepository,
|
private blockingsRepository: BlockingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.blockingReactionUsersRepository)
|
||||||
|
private blockingReactionUsersRepository: BlockingReactionUsersRepository,
|
||||||
|
|
||||||
@Inject(DI.mutingsRepository)
|
@Inject(DI.mutingsRepository)
|
||||||
private mutingsRepository: MutingsRepository,
|
private mutingsRepository: MutingsRepository,
|
||||||
|
|
||||||
|
@ -169,6 +175,8 @@ export class UserEntityService implements OnModuleInit {
|
||||||
hasPendingFollowRequestToYou,
|
hasPendingFollowRequestToYou,
|
||||||
isBlocking,
|
isBlocking,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
|
isReactionBlocking,
|
||||||
|
isReactionBlocked,
|
||||||
isMuted,
|
isMuted,
|
||||||
isRenoteMuted,
|
isRenoteMuted,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
|
@ -206,6 +214,18 @@ export class UserEntityService implements OnModuleInit {
|
||||||
blockeeId: me,
|
blockeeId: me,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
this.blockingReactionUsersRepository.exists({
|
||||||
|
where: {
|
||||||
|
blockerId: me,
|
||||||
|
blockeeId: target,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
this.blockingReactionUsersRepository.exists({
|
||||||
|
where: {
|
||||||
|
blockerId: target,
|
||||||
|
blockeeId: me,
|
||||||
|
},
|
||||||
|
}),
|
||||||
this.mutingsRepository.exists({
|
this.mutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
muterId: me,
|
muterId: me,
|
||||||
|
@ -229,6 +249,8 @@ export class UserEntityService implements OnModuleInit {
|
||||||
hasPendingFollowRequestToYou,
|
hasPendingFollowRequestToYou,
|
||||||
isBlocking,
|
isBlocking,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
|
isReactionBlocking,
|
||||||
|
isReactionBlocked,
|
||||||
isMuted,
|
isMuted,
|
||||||
isRenoteMuted,
|
isRenoteMuted,
|
||||||
};
|
};
|
||||||
|
@ -243,6 +265,8 @@ export class UserEntityService implements OnModuleInit {
|
||||||
followeesRequests,
|
followeesRequests,
|
||||||
blockers,
|
blockers,
|
||||||
blockees,
|
blockees,
|
||||||
|
reactionBlockers,
|
||||||
|
reactionBlockees,
|
||||||
muters,
|
muters,
|
||||||
renoteMuters,
|
renoteMuters,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
|
@ -273,6 +297,16 @@ export class UserEntityService implements OnModuleInit {
|
||||||
.where('b.blockeeId = :me', { me })
|
.where('b.blockeeId = :me', { me })
|
||||||
.getRawMany<{ b_blockerId: string }>()
|
.getRawMany<{ b_blockerId: string }>()
|
||||||
.then(it => it.map(it => it.b_blockerId)),
|
.then(it => it.map(it => it.b_blockerId)),
|
||||||
|
this.blockingReactionUsersRepository.createQueryBuilder('bru')
|
||||||
|
.select('bru.blockeeId')
|
||||||
|
.where('bru.blockerId = :me', { me })
|
||||||
|
.getRawMany<{ bru_blockeeId: string }>()
|
||||||
|
.then(it => it.map(it => it.bru_blockeeId)),
|
||||||
|
this.blockingReactionUsersRepository.createQueryBuilder('bru')
|
||||||
|
.select('bru.blockerId')
|
||||||
|
.where('bru.blockeeId = :me', { me })
|
||||||
|
.getRawMany<{ bru_blockerId: string }>()
|
||||||
|
.then(it => it.map(it => it.bru_blockerId)),
|
||||||
this.mutingsRepository.createQueryBuilder('m')
|
this.mutingsRepository.createQueryBuilder('m')
|
||||||
.select('m.muteeId')
|
.select('m.muteeId')
|
||||||
.where('m.muterId = :me', { me })
|
.where('m.muterId = :me', { me })
|
||||||
|
@ -300,6 +334,8 @@ export class UserEntityService implements OnModuleInit {
|
||||||
hasPendingFollowRequestToYou: followeesRequests.includes(target),
|
hasPendingFollowRequestToYou: followeesRequests.includes(target),
|
||||||
isBlocking: blockers.includes(target),
|
isBlocking: blockers.includes(target),
|
||||||
isBlocked: blockees.includes(target),
|
isBlocked: blockees.includes(target),
|
||||||
|
isReactionBlocking: reactionBlockers.includes(target),
|
||||||
|
isReactionBlocked: reactionBlockees.includes(target),
|
||||||
isMuted: muters.includes(target),
|
isMuted: muters.includes(target),
|
||||||
isRenoteMuted: renoteMuters.includes(target),
|
isRenoteMuted: renoteMuters.includes(target),
|
||||||
},
|
},
|
||||||
|
@ -638,6 +674,8 @@ export class UserEntityService implements OnModuleInit {
|
||||||
hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou,
|
hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou,
|
||||||
isBlocking: relation.isBlocking,
|
isBlocking: relation.isBlocking,
|
||||||
isBlocked: relation.isBlocked,
|
isBlocked: relation.isBlocked,
|
||||||
|
isReactionBlocking: relation.isReactionBlocking,
|
||||||
|
isReactionBlocked: relation.isReactionBlocked,
|
||||||
isMuted: relation.isMuted,
|
isMuted: relation.isMuted,
|
||||||
isRenoteMuted: relation.isRenoteMuted,
|
isRenoteMuted: relation.isRenoteMuted,
|
||||||
notify: relation.following?.notify ?? 'none',
|
notify: relation.following?.notify ?? 'none',
|
||||||
|
|
|
@ -48,6 +48,7 @@ export const DI = {
|
||||||
mutingsRepository: Symbol('mutingsRepository'),
|
mutingsRepository: Symbol('mutingsRepository'),
|
||||||
renoteMutingsRepository: Symbol('renoteMutingsRepository'),
|
renoteMutingsRepository: Symbol('renoteMutingsRepository'),
|
||||||
blockingsRepository: Symbol('blockingsRepository'),
|
blockingsRepository: Symbol('blockingsRepository'),
|
||||||
|
blockingReactionUsersRepository: Symbol('blockingReactionUsersRepository'),
|
||||||
swSubscriptionsRepository: Symbol('swSubscriptionsRepository'),
|
swSubscriptionsRepository: Symbol('swSubscriptionsRepository'),
|
||||||
hashtagsRepository: Symbol('hashtagsRepository'),
|
hashtagsRepository: Symbol('hashtagsRepository'),
|
||||||
abuseUserReportsRepository: Symbol('abuseUserReportsRepository'),
|
abuseUserReportsRepository: Symbol('abuseUserReportsRepository'),
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||||
|
import { id } from './util/id.js';
|
||||||
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
|
@Entity('blocking_reaction_user')
|
||||||
|
@Index(['blockerId', 'blockeeId'], { unique: true })
|
||||||
|
export class MiBlockingReactionUser {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
comment: 'The blockee user ID.',
|
||||||
|
})
|
||||||
|
public blockeeId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public blockee: MiUser | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
comment: 'The blocker user ID.',
|
||||||
|
})
|
||||||
|
public blockerId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public blocker: MiUser | null;
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import {
|
||||||
MiAuthSession,
|
MiAuthSession,
|
||||||
MiAvatarDecoration,
|
MiAvatarDecoration,
|
||||||
MiBlocking,
|
MiBlocking,
|
||||||
|
MiBlockingReactionUser,
|
||||||
MiBubbleGameRecord,
|
MiBubbleGameRecord,
|
||||||
MiChannel,
|
MiChannel,
|
||||||
MiChannelFavorite,
|
MiChannelFavorite,
|
||||||
|
@ -279,6 +280,12 @@ const $blockingsRepository: Provider = {
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $blockingReactionUsersRepository: Provider = {
|
||||||
|
provide: DI.blockingReactionUsersRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiBlockingReactionUser),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $swSubscriptionsRepository: Provider = {
|
const $swSubscriptionsRepository: Provider = {
|
||||||
provide: DI.swSubscriptionsRepository,
|
provide: DI.swSubscriptionsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiSwSubscription).extend(miRepository as MiRepository<MiSwSubscription>),
|
useFactory: (db: DataSource) => db.getRepository(MiSwSubscription).extend(miRepository as MiRepository<MiSwSubscription>),
|
||||||
|
@ -531,6 +538,7 @@ const $reversiGamesRepository: Provider = {
|
||||||
$mutingsRepository,
|
$mutingsRepository,
|
||||||
$renoteMutingsRepository,
|
$renoteMutingsRepository,
|
||||||
$blockingsRepository,
|
$blockingsRepository,
|
||||||
|
$blockingReactionUsersRepository,
|
||||||
$swSubscriptionsRepository,
|
$swSubscriptionsRepository,
|
||||||
$hashtagsRepository,
|
$hashtagsRepository,
|
||||||
$abuseUserReportsRepository,
|
$abuseUserReportsRepository,
|
||||||
|
@ -602,6 +610,7 @@ const $reversiGamesRepository: Provider = {
|
||||||
$mutingsRepository,
|
$mutingsRepository,
|
||||||
$renoteMutingsRepository,
|
$renoteMutingsRepository,
|
||||||
$blockingsRepository,
|
$blockingsRepository,
|
||||||
|
$blockingReactionUsersRepository,
|
||||||
$swSubscriptionsRepository,
|
$swSubscriptionsRepository,
|
||||||
$hashtagsRepository,
|
$hashtagsRepository,
|
||||||
$abuseUserReportsRepository,
|
$abuseUserReportsRepository,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { MiApp } from '@/models/App.js';
|
||||||
import { MiAvatarDecoration } from '@/models/AvatarDecoration.js';
|
import { MiAvatarDecoration } from '@/models/AvatarDecoration.js';
|
||||||
import { MiAuthSession } from '@/models/AuthSession.js';
|
import { MiAuthSession } from '@/models/AuthSession.js';
|
||||||
import { MiBlocking } from '@/models/Blocking.js';
|
import { MiBlocking } from '@/models/Blocking.js';
|
||||||
|
import { MiBlockingReactionUser } from '@/models/BlockingReactionUser.js';
|
||||||
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
||||||
import { MiChannelFavorite } from '@/models/ChannelFavorite.js';
|
import { MiChannelFavorite } from '@/models/ChannelFavorite.js';
|
||||||
import { MiClip } from '@/models/Clip.js';
|
import { MiClip } from '@/models/Clip.js';
|
||||||
|
@ -136,6 +137,7 @@ export {
|
||||||
MiAvatarDecoration,
|
MiAvatarDecoration,
|
||||||
MiAuthSession,
|
MiAuthSession,
|
||||||
MiBlocking,
|
MiBlocking,
|
||||||
|
MiBlockingReactionUser,
|
||||||
MiChannelFollowing,
|
MiChannelFollowing,
|
||||||
MiChannelFavorite,
|
MiChannelFavorite,
|
||||||
MiClip,
|
MiClip,
|
||||||
|
@ -207,6 +209,7 @@ export type AppsRepository = Repository<MiApp> & MiRepository<MiApp>;
|
||||||
export type AvatarDecorationsRepository = Repository<MiAvatarDecoration> & MiRepository<MiAvatarDecoration>;
|
export type AvatarDecorationsRepository = Repository<MiAvatarDecoration> & MiRepository<MiAvatarDecoration>;
|
||||||
export type AuthSessionsRepository = Repository<MiAuthSession> & MiRepository<MiAuthSession>;
|
export type AuthSessionsRepository = Repository<MiAuthSession> & MiRepository<MiAuthSession>;
|
||||||
export type BlockingsRepository = Repository<MiBlocking> & MiRepository<MiBlocking>;
|
export type BlockingsRepository = Repository<MiBlocking> & MiRepository<MiBlocking>;
|
||||||
|
export type BlockingReactionUsersRepository = Repository<MiBlockingReactionUser> & MiRepository<MiBlockingReactionUser>;
|
||||||
export type ChannelFollowingsRepository = Repository<MiChannelFollowing> & MiRepository<MiChannelFollowing>;
|
export type ChannelFollowingsRepository = Repository<MiChannelFollowing> & MiRepository<MiChannelFollowing>;
|
||||||
export type ChannelFavoritesRepository = Repository<MiChannelFavorite> & MiRepository<MiChannelFavorite>;
|
export type ChannelFavoritesRepository = Repository<MiChannelFavorite> & MiRepository<MiChannelFavorite>;
|
||||||
export type ClipsRepository = Repository<MiClip> & MiRepository<MiClip>;
|
export type ClipsRepository = Repository<MiClip> & MiRepository<MiClip>;
|
||||||
|
|
|
@ -82,6 +82,7 @@ import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||||
import { Config } from '@/config.js';
|
import { Config } from '@/config.js';
|
||||||
import MisskeyLogger from '@/logger.js';
|
import MisskeyLogger from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import {MiBlockingReactionUser} from "@/models/_.js";
|
||||||
|
|
||||||
pg.types.setTypeParser(20, Number);
|
pg.types.setTypeParser(20, Number);
|
||||||
|
|
||||||
|
@ -152,6 +153,7 @@ export const entities = [
|
||||||
MiMuting,
|
MiMuting,
|
||||||
MiRenoteMuting,
|
MiRenoteMuting,
|
||||||
MiBlocking,
|
MiBlocking,
|
||||||
|
MiBlockingReactionUser,
|
||||||
MiNote,
|
MiNote,
|
||||||
MiNoteFavorite,
|
MiNoteFavorite,
|
||||||
MiNoteReaction,
|
MiNoteReaction,
|
||||||
|
|
|
@ -114,6 +114,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_create from './endpoints/blocking/create.js';
|
||||||
import * as ep___blocking_delete from './endpoints/blocking/delete.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_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_create from './endpoints/channels/create.js';
|
||||||
import * as ep___channels_featured from './endpoints/channels/featured.js';
|
import * as ep___channels_featured from './endpoints/channels/featured.js';
|
||||||
import * as ep___channels_follow from './endpoints/channels/follow.js';
|
import * as ep___channels_follow from './endpoints/channels/follow.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_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_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_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_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_featured: Provider = { provide: 'ep:channels/featured', useClass: ep___channels_featured.default };
|
||||||
const $channels_follow: Provider = { provide: 'ep:channels/follow', useClass: ep___channels_follow.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_create,
|
||||||
$blocking_delete,
|
$blocking_delete,
|
||||||
$blocking_list,
|
$blocking_list,
|
||||||
|
$blocking_reaction_user_create,
|
||||||
|
$blocking_reaction_user_delete,
|
||||||
|
$blocking_reaction_user_list,
|
||||||
$channels_create,
|
$channels_create,
|
||||||
$channels_featured,
|
$channels_featured,
|
||||||
$channels_follow,
|
$channels_follow,
|
||||||
|
@ -1280,6 +1289,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$blocking_create,
|
$blocking_create,
|
||||||
$blocking_delete,
|
$blocking_delete,
|
||||||
$blocking_list,
|
$blocking_list,
|
||||||
|
$blocking_reaction_user_create,
|
||||||
|
$blocking_reaction_user_delete,
|
||||||
|
$blocking_reaction_user_list,
|
||||||
$channels_create,
|
$channels_create,
|
||||||
$channels_featured,
|
$channels_featured,
|
||||||
$channels_follow,
|
$channels_follow,
|
||||||
|
|
|
@ -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_create from './endpoints/blocking/create.js';
|
||||||
import * as ep___blocking_delete from './endpoints/blocking/delete.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_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_create from './endpoints/channels/create.js';
|
||||||
import * as ep___channels_featured from './endpoints/channels/featured.js';
|
import * as ep___channels_featured from './endpoints/channels/featured.js';
|
||||||
import * as ep___channels_follow from './endpoints/channels/follow.js';
|
import * as ep___channels_follow from './endpoints/channels/follow.js';
|
||||||
|
@ -506,6 +509,9 @@ const eps = [
|
||||||
['blocking/create', ep___blocking_create],
|
['blocking/create', ep___blocking_create],
|
||||||
['blocking/delete', ep___blocking_delete],
|
['blocking/delete', ep___blocking_delete],
|
||||||
['blocking/list', ep___blocking_list],
|
['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/create', ep___channels_create],
|
||||||
['channels/featured', ep___channels_featured],
|
['channels/featured', ep___channels_featured],
|
||||||
['channels/follow', ep___channels_follow],
|
['channels/follow', ep___channels_follow],
|
||||||
|
|
|
@ -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, BlockingReactionUsersRepository } from '@/models/_.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
import {BlockingReactionUserService} from "@/core/BlockingReactionUserService.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<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@Inject(DI.blockingsRepository)
|
||||||
|
private blockingReactionUsersRepository: BlockingReactionUsersRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
private getterService: GetterService,
|
||||||
|
private blockingReactionUserService: BlockingReactionUserService,
|
||||||
|
) {
|
||||||
|
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.blockingReactionUsersRepository.exists({
|
||||||
|
where: {
|
||||||
|
blockerId: blocker.id,
|
||||||
|
blockeeId: blockee.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist) {
|
||||||
|
throw new ApiError(meta.errors.alreadyBlocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.blockingReactionUserService.block(blocker, blockee);
|
||||||
|
|
||||||
|
return await this.userEntityService.pack(blockee.id, blocker, {
|
||||||
|
schema: 'UserDetailedNotMe',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, BlockingReactionUsersRepository} from '@/models/_.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
import {BlockingReactionUserService} from "@/core/BlockingReactionUserService.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<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@Inject(DI.blockingReactionUsersRepository)
|
||||||
|
private blockingReactionUsersRepository: BlockingReactionUsersRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
private getterService: GetterService,
|
||||||
|
private blockingReactionUserService: BlockingReactionUserService,
|
||||||
|
) {
|
||||||
|
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.blockingReactionUsersRepository.exists({
|
||||||
|
where: {
|
||||||
|
blockerId: blocker.id,
|
||||||
|
blockeeId: blockee.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exist) {
|
||||||
|
throw new ApiError(meta.errors.notBlocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete blocking
|
||||||
|
await this.blockingReactionUserService.unblock(blocker, blockee);
|
||||||
|
|
||||||
|
return await this.userEntityService.pack(blockee.id, blocker, {
|
||||||
|
schema: 'UserDetailedNotMe',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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 { BlockingReactionUsersRepository } from '@/models/_.js';
|
||||||
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { BlockingReactionUserEntityService } from '@/core/entities/BlockingReactionUserEntityService.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<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.blockingReactionUsersRepository)
|
||||||
|
private blockingReactionUsersRepository: BlockingReactionUsersRepository,
|
||||||
|
|
||||||
|
private blockingReactionUserEntityService: BlockingReactionUserEntityService,
|
||||||
|
private queryService: QueryService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const query = this.queryService.makePaginationQuery(this.blockingReactionUsersRepository.createQueryBuilder('blocking_reaction_user'), ps.sinceId, ps.untilId)
|
||||||
|
.andWhere('blocking_reaction_user.blockerId = :meId', { meId: me.id });
|
||||||
|
|
||||||
|
const blockings = await query
|
||||||
|
.limit(ps.limit)
|
||||||
|
.getMany();
|
||||||
|
|
||||||
|
return await this.blockingReactionUserEntityService.packMany(blockings, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -122,6 +122,39 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-ban"></i></template>
|
||||||
|
<template #label>{{ i18n.ts.reactionBlockedUsers }}</template>
|
||||||
|
|
||||||
|
<MkPagination :pagination="blockingReactionUserPagination">
|
||||||
|
<template #empty>
|
||||||
|
<div class="_fullinfo">
|
||||||
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
|
<div>{{ i18n.ts.noUsers }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #default="{ items }">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]">
|
||||||
|
<div :class="$style.userItemMain">
|
||||||
|
<MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)">
|
||||||
|
<MkUserCardMini :user="item.blockee"/>
|
||||||
|
</MkA>
|
||||||
|
<button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||||
|
<button class="_button" :class="$style.remove" @click="unblockReactionUser(item.blockee, $event)"><i class="ti ti-x"></i></button>
|
||||||
|
</div>
|
||||||
|
<div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
|
||||||
|
<div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||||
|
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
|
||||||
|
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MkPagination>
|
||||||
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -157,6 +190,11 @@ const blockingPagination = {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const blockingReactionUserPagination = {
|
||||||
|
endpoint: 'blocking-reaction-user/list' as const,
|
||||||
|
limit: 10,
|
||||||
|
};
|
||||||
|
|
||||||
const expandedRenoteMuteItems = ref([]);
|
const expandedRenoteMuteItems = ref([]);
|
||||||
const expandedMuteItems = ref([]);
|
const expandedMuteItems = ref([]);
|
||||||
const expandedBlockItems = ref([]);
|
const expandedBlockItems = ref([]);
|
||||||
|
@ -194,6 +232,16 @@ async function unblock(user, ev) {
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function unblockReactionUser(user, ev) {
|
||||||
|
os.popupMenu([{
|
||||||
|
text: i18n.ts.unblock,
|
||||||
|
icon: 'ti ti-x',
|
||||||
|
action: async () => {
|
||||||
|
await os.apiWithDialog('blocking-reaction-user/delete', { userId: user.id });
|
||||||
|
},
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
}
|
||||||
|
|
||||||
async function toggleRenoteMuteItem(item) {
|
async function toggleRenoteMuteItem(item) {
|
||||||
if (expandedRenoteMuteItems.value.includes(item.id)) {
|
if (expandedRenoteMuteItems.value.includes(item.id)) {
|
||||||
expandedRenoteMuteItems.value = expandedRenoteMuteItems.value.filter(x => x !== item.id);
|
expandedRenoteMuteItems.value = expandedRenoteMuteItems.value.filter(x => x !== item.id);
|
||||||
|
|
|
@ -84,6 +84,16 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleReactionBlock() {
|
||||||
|
if (!await getConfirmed(user.isReactionBlocking ? i18n.ts.unblockReactionUserConfirm : i18n.ts.blockReactionUserConfirm)) return;
|
||||||
|
|
||||||
|
os.apiWithDialog(user.isReactionBlocking ? 'blocking-reaction-user/delete' : 'blocking-reaction-user/create', {
|
||||||
|
userId: user.id,
|
||||||
|
}).then(() => {
|
||||||
|
user.isReactionBlocking = !user.isReactionBlocking;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function toggleNotify() {
|
async function toggleNotify() {
|
||||||
os.apiWithDialog('following/update', {
|
os.apiWithDialog('following/update', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -373,6 +383,10 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
||||||
icon: 'ti ti-ban',
|
icon: 'ti ti-ban',
|
||||||
text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block,
|
text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block,
|
||||||
action: toggleBlock,
|
action: toggleBlock,
|
||||||
|
}, {
|
||||||
|
icon: 'ti ti-ban',
|
||||||
|
text: user.isReactionBlocking ? i18n.ts.unblockReaction : i18n.ts.blockReaction,
|
||||||
|
action: toggleReactionBlock,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user.isFollowed) {
|
if (user.isFollowed) {
|
||||||
|
|
|
@ -1204,9 +1204,42 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:blocks*
|
||||||
|
*/
|
||||||
|
request<E extends 'blocking-reaction-user/create', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:blocks*
|
||||||
|
*/
|
||||||
|
request<E extends 'blocking-reaction-user/delete', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:blocks*
|
||||||
|
*/
|
||||||
|
request<E extends 'blocking-reaction-user/list', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
* **Credential required**: *Yes* / **Permission**: *write:channels*
|
* **Credential required**: *Yes* / **Permission**: *write:channels*
|
||||||
*/
|
*/
|
||||||
request<E extends 'channels/create', P extends Endpoints[E]['req']>(
|
request<E extends 'channels/create', P extends Endpoints[E]['req']>(
|
||||||
|
|
|
@ -579,6 +579,12 @@ import type {
|
||||||
ReversiSurrenderRequest,
|
ReversiSurrenderRequest,
|
||||||
ReversiVerifyRequest,
|
ReversiVerifyRequest,
|
||||||
ReversiVerifyResponse,
|
ReversiVerifyResponse,
|
||||||
|
BlockingReactionUserCreateRequest,
|
||||||
|
BlockingReactionUserCreateResponse,
|
||||||
|
BlockingReactionUserDeleteRequest,
|
||||||
|
BlockingReactionUserDeleteResponse,
|
||||||
|
BlockingReactionUserListRequest,
|
||||||
|
BlockingReactionUserListResponse,
|
||||||
} from './entities.js';
|
} from './entities.js';
|
||||||
|
|
||||||
export type Endpoints = {
|
export type Endpoints = {
|
||||||
|
@ -690,6 +696,9 @@ export type Endpoints = {
|
||||||
'blocking/create': { req: BlockingCreateRequest; res: BlockingCreateResponse };
|
'blocking/create': { req: BlockingCreateRequest; res: BlockingCreateResponse };
|
||||||
'blocking/delete': { req: BlockingDeleteRequest; res: BlockingDeleteResponse };
|
'blocking/delete': { req: BlockingDeleteRequest; res: BlockingDeleteResponse };
|
||||||
'blocking/list': { req: BlockingListRequest; res: BlockingListResponse };
|
'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/create': { req: ChannelsCreateRequest; res: ChannelsCreateResponse };
|
||||||
'channels/featured': { req: EmptyRequest; res: ChannelsFeaturedResponse };
|
'channels/featured': { req: EmptyRequest; res: ChannelsFeaturedResponse };
|
||||||
'channels/follow': { req: ChannelsFollowRequest; res: EmptyResponse };
|
'channels/follow': { req: ChannelsFollowRequest; res: EmptyResponse };
|
||||||
|
|
|
@ -159,6 +159,12 @@ export type BlockingDeleteRequest = operations['blocking___delete']['requestBody
|
||||||
export type BlockingDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json'];
|
export type BlockingDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json'];
|
||||||
export type BlockingListRequest = operations['blocking___list']['requestBody']['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 BlockingListResponse = operations['blocking___list']['responses']['200']['content']['application/json'];
|
||||||
|
export type BlockingReactionUserCreateRequest = operations['blocking___create']['requestBody']['content']['application/json'];
|
||||||
|
export type BlockingReactionUserCreateResponse = operations['blocking___create']['responses']['200']['content']['application/json'];
|
||||||
|
export type BlockingReactionUserDeleteRequest = operations['blocking___delete']['requestBody']['content']['application/json'];
|
||||||
|
export type BlockingReactionUserDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json'];
|
||||||
|
export type BlockingReactionUserListRequest = operations['blocking___list']['requestBody']['content']['application/json'];
|
||||||
|
export type BlockingReactionUserListResponse = operations['blocking___list']['responses']['200']['content']['application/json'];
|
||||||
export type ChannelsCreateRequest = operations['channels___create']['requestBody']['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 ChannelsCreateResponse = operations['channels___create']['responses']['200']['content']['application/json'];
|
||||||
export type ChannelsFeaturedResponse = operations['channels___featured']['responses']['200']['content']['application/json'];
|
export type ChannelsFeaturedResponse = operations['channels___featured']['responses']['200']['content']['application/json'];
|
||||||
|
|
|
@ -21,6 +21,7 @@ export type Following = components['schemas']['Following'];
|
||||||
export type Muting = components['schemas']['Muting'];
|
export type Muting = components['schemas']['Muting'];
|
||||||
export type RenoteMuting = components['schemas']['RenoteMuting'];
|
export type RenoteMuting = components['schemas']['RenoteMuting'];
|
||||||
export type Blocking = components['schemas']['Blocking'];
|
export type Blocking = components['schemas']['Blocking'];
|
||||||
|
export type BlockingReactionUser = components['schemas']['Blocking'];
|
||||||
export type Hashtag = components['schemas']['Hashtag'];
|
export type Hashtag = components['schemas']['Hashtag'];
|
||||||
export type InviteCode = components['schemas']['InviteCode'];
|
export type InviteCode = components['schemas']['InviteCode'];
|
||||||
export type Page = components['schemas']['Page'];
|
export type Page = components['schemas']['Page'];
|
||||||
|
|
|
@ -3823,8 +3823,10 @@ export type components = {
|
||||||
isFollowed?: boolean;
|
isFollowed?: boolean;
|
||||||
hasPendingFollowRequestFromYou?: boolean;
|
hasPendingFollowRequestFromYou?: boolean;
|
||||||
hasPendingFollowRequestToYou?: boolean;
|
hasPendingFollowRequestToYou?: boolean;
|
||||||
isBlocking?: boolean;
|
isBlocking?: boolean;
|
||||||
isBlocked?: boolean;
|
isBlocked?: boolean;
|
||||||
|
isReactionBlocking?: boolean;
|
||||||
|
isReactionBlocked?: boolean;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
isRenoteMuted?: boolean;
|
isRenoteMuted?: boolean;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
|
|
Loading…
Reference in New Issue