feat: notification muting
This commit is contained in:
parent
ab84c9afa0
commit
03c64c296b
@ -1331,6 +1331,9 @@ normalize: "Normalize"
|
||||
normalizeConfirm: "After normalization, the account will be irreversible. Are you sure you want to do this?"
|
||||
normalizeDescription: "Normalization is a feature for bulk data wiping and account suspension of users, which has a similar effect to deleting an account and closes all reports after it is run. Please note that this is an irreversible action."
|
||||
useNormalization: "Show the Normalize menu"
|
||||
alsoMuteNotification: "Also mute notifications from this user"
|
||||
muteNotification: "Mute notifications"
|
||||
unmuteNotification: "Unmute notifications"
|
||||
_bubbleGame:
|
||||
howToPlay: "How to play"
|
||||
hold: "Hold"
|
||||
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
@ -5496,6 +5496,18 @@ export interface Locale extends ILocale {
|
||||
* 選択した項目のみ許可
|
||||
*/
|
||||
"consentSelected": string;
|
||||
/**
|
||||
* このユーザーが送信する通知も一緒にミュートする
|
||||
*/
|
||||
"alsoMuteNotification": string;
|
||||
/**
|
||||
* 通知をミュートする
|
||||
*/
|
||||
"muteNotification": string;
|
||||
/**
|
||||
* 通知をミュート解除する
|
||||
*/
|
||||
"unmuteNotification": string;
|
||||
"_bubbleGame": {
|
||||
/**
|
||||
* 遊び方
|
||||
|
@ -1367,6 +1367,9 @@ pleaseConsentToTracking: "{host}は[プライバシーポリシー]({privacyPoli
|
||||
consentEssential: "必須項目のみ許可"
|
||||
consentAll: "全て許可"
|
||||
consentSelected: "選択した項目のみ許可"
|
||||
alsoMuteNotification: "このユーザーが送信する通知も一緒にミュートする"
|
||||
muteNotification: "通知をミュートする"
|
||||
unmuteNotification: "通知をミュート解除する"
|
||||
|
||||
_bubbleGame:
|
||||
howToPlay: "遊び方"
|
||||
|
@ -1356,6 +1356,9 @@ pleaseConsentToTracking: "{host}는 [개인정보 처리방침]({privacyPolicyUr
|
||||
consentEssential: "필수 항목만 허용"
|
||||
consentAll: "모두 허용"
|
||||
consentSelected: "선택한 항목만 허용"
|
||||
alsoMuteNotification: "이 유저가 보내는 알림도 같이 뮤트하기"
|
||||
muteNotification: "알림을 뮤트하기"
|
||||
unmuteNotification: "알림 뮤트를 해제하기"
|
||||
|
||||
_bubbleGame:
|
||||
howToPlay: "설명"
|
||||
|
@ -21,6 +21,8 @@ export class CacheService implements OnApplicationShutdown {
|
||||
public localUserByIdCache: MemoryKVCache<MiLocalUser>;
|
||||
public uriPersonCache: MemoryKVCache<MiUser | null>;
|
||||
public userProfileCache: RedisKVCache<MiUserProfile>;
|
||||
public userMutingsWithNotificationCache: RedisKVCache<Set<string>>;
|
||||
public userMutingsWithoutNotificationCache: RedisKVCache<Set<string>>;
|
||||
public userMutingsCache: RedisKVCache<Set<string>>;
|
||||
public userBlockingCache: RedisKVCache<Set<string>>;
|
||||
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||
@ -77,6 +79,22 @@ export class CacheService implements OnApplicationShutdown {
|
||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||
});
|
||||
|
||||
this.userMutingsWithoutNotificationCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
memoryCacheLifetime: 1000 * 60, // 1m
|
||||
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key, withNotification: false }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||
});
|
||||
|
||||
this.userMutingsWithNotificationCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
memoryCacheLifetime: 1000 * 60, // 1m
|
||||
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key, withNotification: true }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||
});
|
||||
|
||||
this.userBlockingCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocking', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
memoryCacheLifetime: 1000 * 60, // 1m
|
||||
@ -188,6 +206,8 @@ export class CacheService implements OnApplicationShutdown {
|
||||
this.uriPersonCache.dispose();
|
||||
this.userProfileCache.dispose();
|
||||
this.userMutingsCache.dispose();
|
||||
this.userMutingsWithoutNotificationCache.dispose();
|
||||
this.userMutingsWithNotificationCache.dispose();
|
||||
this.userBlockingCache.dispose();
|
||||
this.userBlockedCache.dispose();
|
||||
this.renoteMutingsCache.dispose();
|
||||
|
@ -106,7 +106,7 @@ export class NotificationService implements OnApplicationShutdown {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
|
||||
const mutings = await this.cacheService.userMutingsWithNotificationCache.fetch(notifieeId);
|
||||
if (mutings.has(notifierId)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -24,15 +24,18 @@ export class UserMutingService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
|
||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null, withNotification = true): Promise<void> {
|
||||
await this.mutingsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
expiresAt: expiresAt ?? null,
|
||||
muterId: user.id,
|
||||
muteeId: target.id,
|
||||
withNotification: withNotification,
|
||||
});
|
||||
|
||||
this.cacheService.userMutingsCache.refresh(user.id);
|
||||
this.cacheService.userMutingsWithNotificationCache.refresh(user.id);
|
||||
this.cacheService.userMutingsWithoutNotificationCache.refresh(user.id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@ -46,6 +49,17 @@ export class UserMutingService {
|
||||
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
||||
for (const muterId of muterIds) {
|
||||
this.cacheService.userMutingsCache.refresh(muterId);
|
||||
this.cacheService.userMutingsWithNotificationCache.refresh(muterId);
|
||||
this.cacheService.userMutingsWithoutNotificationCache.refresh(muterId);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async editMute(muting: MiMuting, withNotification: boolean): Promise<void> {
|
||||
await this.mutingsRepository.update(muting.id, { withNotification: withNotification });
|
||||
|
||||
this.cacheService.userMutingsCache.refresh(muting.muterId);
|
||||
this.cacheService.userMutingsWithNotificationCache.refresh(muting.muterId);
|
||||
this.cacheService.userMutingsWithoutNotificationCache.refresh(muting.muterId);
|
||||
}
|
||||
}
|
||||
|
@ -269,12 +269,12 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
*/
|
||||
#validateNotifier <T extends MiNotification | MiGroupedNotification> (
|
||||
notification: T,
|
||||
userIdsWhoMeMuting: Set<MiUser['id']>,
|
||||
userIdsWhoMeMutingWithNotification: Set<MiUser['id']>,
|
||||
userMutedInstances: Set<string>,
|
||||
notifiers: MiUser[],
|
||||
): boolean {
|
||||
if (!('notifierId' in notification)) return true;
|
||||
if (userIdsWhoMeMuting.has(notification.notifierId)) return false;
|
||||
if (userIdsWhoMeMutingWithNotification.has(notification.notifierId)) return false;
|
||||
|
||||
const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null;
|
||||
|
||||
@ -303,10 +303,10 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
meId: MiUser['id'],
|
||||
): Promise<T[]> {
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingWithNotification,
|
||||
userMutedInstances,
|
||||
] = (await Promise.allSettled([
|
||||
this.cacheService.userMutingsCache.fetch(meId),
|
||||
this.cacheService.userMutingsWithNotificationCache.fetch(meId),
|
||||
this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
|
||||
])).map(result => result.status === 'fulfilled' ? result.value : new Set<string>());
|
||||
|
||||
@ -316,7 +316,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
}) : [];
|
||||
|
||||
return ((await Promise.allSettled(notifications.map(async (notification) => {
|
||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers);
|
||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMutingWithNotification, userMutedInstances, notifiers);
|
||||
return isValid ? notification : null;
|
||||
}))).filter(result => result.status === 'fulfilled' && isNotNull(result.value))
|
||||
.map(result => (result as PromiseFulfilledResult<T>).value));
|
||||
|
@ -281,6 +281,7 @@ import * as ep___emoji from './endpoints/emoji.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||
import * as ep___mute_edit from './endpoints/mute/edit.js';
|
||||
import * as ep___mute_list from './endpoints/mute/list.js';
|
||||
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
||||
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||
@ -680,6 +681,7 @@ const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
|
||||
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
||||
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
||||
const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
|
||||
const $mute_edit: Provider = { provide: 'ep:mute/edit', useClass: ep___mute_edit.default };
|
||||
const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
|
||||
const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
|
||||
const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
|
||||
@ -1083,6 +1085,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
$mute_delete,
|
||||
$mute_edit,
|
||||
$mute_list,
|
||||
$renoteMute_create,
|
||||
$renoteMute_delete,
|
||||
@ -1480,6 +1483,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
$mute_delete,
|
||||
$mute_edit,
|
||||
$mute_list,
|
||||
$renoteMute_create,
|
||||
$renoteMute_delete,
|
||||
|
@ -281,6 +281,7 @@ import * as ep___emoji from './endpoints/emoji.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||
import * as ep___mute_edit from './endpoints/mute/edit.js';
|
||||
import * as ep___mute_list from './endpoints/mute/list.js';
|
||||
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
||||
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||
@ -678,6 +679,7 @@ const eps = [
|
||||
['miauth/gen-token', ep___miauth_genToken],
|
||||
['mute/create', ep___mute_create],
|
||||
['mute/delete', ep___mute_delete],
|
||||
['mute/edit', ep___mute_edit],
|
||||
['mute/list', ep___mute_list],
|
||||
['renote-mute/create', ep___renoteMute_create],
|
||||
['renote-mute/delete', ep___renoteMute_delete],
|
||||
|
88
packages/backend/src/server/api/endpoints/mute/edit.ts
Normal file
88
packages/backend/src/server/api/endpoints/mute/edit.ts
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 { MutingsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canUpdateContent',
|
||||
|
||||
kind: 'write:mutes',
|
||||
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: 'b851d00b-8ab1-4a56-8b1b-e24187cb48ef',
|
||||
},
|
||||
|
||||
muteeIsYourself: {
|
||||
message: 'Mutee is yourself.',
|
||||
code: 'MUTEE_IS_YOURSELF',
|
||||
id: 'f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9',
|
||||
},
|
||||
|
||||
notMuting: {
|
||||
message: 'You are not muting that user.',
|
||||
code: 'NOT_MUTING',
|
||||
id: '5467d020-daa9-4553-81e1-135c0c35a96d',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
withNotification: { type: 'boolean', nullable: false },
|
||||
},
|
||||
required: ['userId', 'withNotification'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.mutingsRepository)
|
||||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
private userMutingService: UserMutingService,
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const muter = me;
|
||||
|
||||
// Check if the mutee is yourself
|
||||
if (me.id === ps.userId) {
|
||||
throw new ApiError(meta.errors.muteeIsYourself);
|
||||
}
|
||||
|
||||
// Get mutee
|
||||
const mutee = 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 muting
|
||||
const exist = await this.mutingsRepository.findOneBy({
|
||||
muterId: muter.id,
|
||||
muteeId: mutee.id,
|
||||
});
|
||||
|
||||
if (exist == null) {
|
||||
throw new ApiError(meta.errors.notMuting);
|
||||
}
|
||||
|
||||
await this.userMutingService.editMute(exist, ps.withNotification);
|
||||
});
|
||||
}
|
||||
}
|
@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkUserCardMini :user="item.mutee"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button>
|
||||
<button class="_button" :class="$style.remove" @click="editMute(item, $event)"><i class="ti ti-dots"></i></button>
|
||||
</div>
|
||||
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
|
||||
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||
@ -216,8 +216,20 @@ async function unrenoteMute(user, ev) {
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
async function unmute(user, ev) {
|
||||
os.popupMenu([{
|
||||
async function editMute(muting, ev) {
|
||||
os.popupMenu([...(muting.withNotification === false ? [{
|
||||
icon: 'ti ti-bell',
|
||||
text: i18n.ts.muteNotification,
|
||||
action: async () => {
|
||||
await os.apiWithDialog('mute/edit', { userId: muting.mutee.id, withNotification: true });
|
||||
},
|
||||
}] : [{
|
||||
icon: 'ti ti-bell-off',
|
||||
text: i18n.ts.unmuteNotification,
|
||||
action: async () => {
|
||||
await os.apiWithDialog('mute/edit', { userId: muting.mutee.id, withNotification: false });
|
||||
},
|
||||
}]), {
|
||||
text: i18n.ts.unmute,
|
||||
icon: 'ti ti-x',
|
||||
action: async () => {
|
||||
|
@ -1623,6 +1623,7 @@ declare namespace entities {
|
||||
MiauthGenTokenResponse,
|
||||
MuteCreateRequest,
|
||||
MuteDeleteRequest,
|
||||
MuteEditRequest,
|
||||
MuteListRequest,
|
||||
MuteListResponse,
|
||||
RenoteMuteCreateRequest,
|
||||
@ -2565,6 +2566,9 @@ type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['
|
||||
// @public (undocumented)
|
||||
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
||||
|
||||
// @public (undocumented)
|
||||
type MuteEditRequest = operations['mute___edit']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
||||
|
||||
|
@ -3067,6 +3067,17 @@ declare module '../api.js' {
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||
*/
|
||||
request<E extends 'mute/edit', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
|
@ -403,6 +403,7 @@ import type {
|
||||
MiauthGenTokenResponse,
|
||||
MuteCreateRequest,
|
||||
MuteDeleteRequest,
|
||||
MuteEditRequest,
|
||||
MuteListRequest,
|
||||
MuteListResponse,
|
||||
RenoteMuteCreateRequest,
|
||||
@ -868,6 +869,7 @@ export type Endpoints = {
|
||||
'miauth/gen-token': { req: MiauthGenTokenRequest; res: MiauthGenTokenResponse };
|
||||
'mute/create': { req: MuteCreateRequest; res: EmptyResponse };
|
||||
'mute/delete': { req: MuteDeleteRequest; res: EmptyResponse };
|
||||
'mute/edit': { req: MuteEditRequest; res: EmptyResponse };
|
||||
'mute/list': { req: MuteListRequest; res: MuteListResponse };
|
||||
'renote-mute/create': { req: RenoteMuteCreateRequest; res: EmptyResponse };
|
||||
'renote-mute/delete': { req: RenoteMuteDeleteRequest; res: EmptyResponse };
|
||||
|
@ -406,6 +406,7 @@ export type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBod
|
||||
export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json'];
|
||||
export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
||||
export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json'];
|
||||
export type MuteEditRequest = operations['mute___edit']['requestBody']['content']['application/json'];
|
||||
export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
||||
export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json'];
|
||||
export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
|
||||
|
@ -2652,6 +2652,15 @@ export type paths = {
|
||||
*/
|
||||
post: operations['mute___delete'];
|
||||
};
|
||||
'/mute/edit': {
|
||||
/**
|
||||
* mute/edit
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||
*/
|
||||
post: operations['mute___edit'];
|
||||
};
|
||||
'/mute/list': {
|
||||
/**
|
||||
* mute/list
|
||||
@ -22915,6 +22924,8 @@ export type operations = {
|
||||
userId: string;
|
||||
/** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */
|
||||
expiresAt?: number | null;
|
||||
/** @default true */
|
||||
withNotification?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -23013,6 +23024,59 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* mute/edit
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||
*/
|
||||
mute___edit: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
userId: string;
|
||||
withNotification: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (without any results) */
|
||||
204: {
|
||||
content: never;
|
||||
};
|
||||
/** @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'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* mute/list
|
||||
* @description No description provided.
|
||||
|
Loading…
Reference in New Issue
Block a user