This commit is contained in:
おさむのひと 2025-02-24 20:01:45 +09:00
parent 9b71319fb2
commit 776e90f28a
16 changed files with 279 additions and 174 deletions

View File

@ -3021,3 +3021,10 @@ _search:
pleaseEnterServerHost: "サーバーのホストを入力してください"
pleaseSelectUser: "ユーザーを選択してください"
serverHostPlaceholder: "例: misskey.example.com"
_noteMuting:
muteNote: "ノートをミュート"
unmuteNote: "ノートのミュートを解除"
notMutedNote: "このノートはミュートされていません"
labelSuffix: "のノート"
unmuteCaption: "ミュートを解除したノートを再表示するにはタイムラインの再読み込みが必要です。"

View File

@ -19,11 +19,15 @@ export class NoteMuting1739882320354 {
);
CREATE INDEX "IDX_note_muting_userId" ON "note_muting" ("userId");
CREATE INDEX "IDX_note_muting_noteId" ON "note_muting" ("noteId");
CREATE INDEX "IDX_note_muting_expiresAt" ON "note_muting" ("expiresAt");
CREATE UNIQUE INDEX "IDX_note_muting_userId_noteId_unique" ON note_muting ("userId", "noteId");
`);
}
async down(queryRunner) {
await queryRunner.query(`
DROP INDEX "IDX_note_muting_userId_noteId_unique";
DROP INDEX "IDX_note_muting_expiresAt";
DROP INDEX "IDX_note_muting_noteId";
DROP INDEX "IDX_note_muting_userId";
DROP TABLE "note_muting";

View File

@ -5,11 +5,14 @@ import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { MiNoteMuting, NoteMutingsRepository } from '@/models/_.js';
import type { MiNoteMuting, NoteMutingsRepository, NotesRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
@Injectable()
export class NoteMutingService implements OnApplicationShutdown {
public static NoSuchItemError = class extends Error {
public static NoSuchNoteError = class extends Error {
};
public static NotMutedError = class extends Error {
};
private cache: RedisKVCache<Set<string>>;
@ -19,16 +22,27 @@ export class NoteMutingService implements OnApplicationShutdown {
private redisClient: Redis.Redis,
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.noteMutingsRepository)
private noteMutingsRepository: NoteMutingsRepository,
private idService: IdService,
private globalEventService: GlobalEventService,
private queryService: QueryService,
) {
this.redisForSub.on('message', this.onMessage);
this.cache = new RedisKVCache<Set<string>>(this.redisClient, 'noteMutings', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
fetcher: (userId) => this.listByUserId(userId).then(xs => new Set(xs.map(x => x.noteId))),
this.cache = new RedisKVCache<Set<MiNoteMuting['noteId']>>(this.redisClient, 'noteMutings', {
// 使用頻度が高く使用される期間も長いためキャッシュの有効期限切れ→再取得が頻発すると思われる。
// よって、有効期限を長めに設定して再取得の頻度を抑えるキャッシュの鮮度はRedisイベント経由で保たれているので問題ないはず
lifetime: 1000 * 60 * 60 * 24, // 1d
memoryCacheLifetime: 1000 * 60 * 60 * 24, // 1d
fetcher: async (userId) => {
return this.noteMutingsRepository.createQueryBuilder('noteMuting')
.select('noteMuting.noteId')
.where('noteMuting.userId = :userId', { userId })
.getRawMany<{ noteMuting_noteId: string }>()
.then((results) => new Set(results.map(x => x.noteMuting_noteId)));
},
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
@ -62,15 +76,21 @@ export class NoteMutingService implements OnApplicationShutdown {
@bindThis
public async listByUserId(
userId: MiNoteMuting['userId'],
params: {
userId: MiNoteMuting['userId'],
sinceId?: MiNoteMuting['id'] | null,
untilId?: MiNoteMuting['id'] | null,
},
opts?: {
limit?: number;
offset?: number;
joinUser?: boolean;
joinNote?: boolean;
},
): Promise<MiNoteMuting[]> {
const q = this.noteMutingsRepository.createQueryBuilder('noteMuting');
const q = this.queryService.makePaginationQuery(this.noteMutingsRepository.createQueryBuilder('noteMuting'), params.sinceId, params.untilId);
q.where('noteMuting.userId = :userId', { userId });
q.where('noteMuting.userId = :userId', { userId: params.userId });
if (opts?.joinUser) {
q.leftJoinAndSelect('noteMuting.user', 'user');
}
@ -78,6 +98,15 @@ export class NoteMutingService implements OnApplicationShutdown {
q.leftJoinAndSelect('noteMuting.note', 'note');
}
q.orderBy('noteMuting.id', 'DESC');
const limit = opts?.limit ?? 10;
q.limit(limit);
if (opts?.offset) {
q.offset(opts.offset);
}
return q.getMany();
}
@ -87,19 +116,18 @@ export class NoteMutingService implements OnApplicationShutdown {
}
@bindThis
public async get(id: MiNoteMuting['id']): Promise<MiNoteMuting> {
const result = await this.noteMutingsRepository.findOne({ where: { id } });
if (!result) {
throw new NoteMutingService.NoSuchItemError();
}
return result;
public async isMuting(userId: MiNoteMuting['userId'], noteId: MiNoteMuting['noteId']): Promise<boolean> {
return this.cache.fetch(userId).then(noteIds => noteIds.has(noteId));
}
@bindThis
public async create(
params: Pick<MiNoteMuting, 'userId' | 'noteId' | 'expiresAt'>,
): Promise<void> {
if (!await this.notesRepository.existsBy({ id: params.noteId })) {
throw new NoteMutingService.NoSuchNoteError();
}
const id = this.idService.gen();
const result = await this.noteMutingsRepository.insertOne({
id,
@ -110,25 +138,31 @@ export class NoteMutingService implements OnApplicationShutdown {
}
@bindThis
public async update(
id: MiNoteMuting['id'],
params: Partial<Pick<MiNoteMuting, 'expiresAt'>>,
): Promise<void> {
await this.noteMutingsRepository.update(id, params);
public async delete(userId: MiNoteMuting['userId'], noteId: MiNoteMuting['noteId']): Promise<void> {
const value = await this.noteMutingsRepository.findOne({ where: { userId, noteId } });
if (!value) {
throw new NoteMutingService.NotMutedError();
}
// 現状、ミュート設定の有無しかキャッシュしていないので更新時はイベントを発行しない。
// 他に細かい設定が登場した場合はキャッシュの型をSetからMapに変えつつ、イベントを発行するようにする。
await this.noteMutingsRepository.delete(value.id);
this.globalEventService.publishInternalEvent('noteMuteDeleted', value);
}
@bindThis
public async delete(id: MiNoteMuting['id']): Promise<void> {
const value = await this.noteMutingsRepository.findOne({ where: { id } });
if (!value) {
return;
}
public async cleanupExpiredMutes(): Promise<void> {
const now = new Date();
const noteMutings = await this.noteMutingsRepository.createQueryBuilder('noteMuting')
.select(['noteMuting.id', 'noteMuting.userId'])
.where('noteMuting.expiresAt < :now', { now })
.andWhere('noteMuting.expiresAt IS NOT NULL')
.getRawMany<{ noteMuting_id: MiNoteMuting['id'], noteMuting_userId: MiNoteMuting['id'] }>();
await this.noteMutingsRepository.delete(id);
this.globalEventService.publishInternalEvent('noteMuteDeleted', value);
await this.noteMutingsRepository.delete(noteMutings.map(x => x.noteMuting_id));
for (const id of [...new Set(noteMutings.map(x => x.noteMuting_userId))]) {
// 同時多発的なDBアクセスが発生することを避けるため1回ごとにawaitする
await this.cache.refresh(id);
}
}
@bindThis

View File

@ -10,6 +10,7 @@ import type { MutingsRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { UserMutingService } from '@/core/UserMutingService.js';
import { NoteMutingService } from '@/core/note/NoteMutingService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@ -22,6 +23,7 @@ export class CheckExpiredMutingsProcessorService {
private mutingsRepository: MutingsRepository,
private userMutingService: UserMutingService,
private noteMutingService: NoteMutingService,
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings');
@ -41,6 +43,8 @@ export class CheckExpiredMutingsProcessorService {
await this.userMutingService.unmute(expired);
}
await this.noteMutingService.cleanupExpiredMutes();
this.logger.succ('All expired mutings checked.');
}
}

View File

@ -326,7 +326,6 @@ export * as 'notes/translate' from './endpoints/notes/translate.js';
export * as 'notes/unrenote' from './endpoints/notes/unrenote.js';
export * as 'notes/user-list-timeline' from './endpoints/notes/user-list-timeline.js';
export * as 'notes/muting/create' from './endpoints/notes/muting/create.js';
export * as 'notes/muting/update' from './endpoints/notes/muting/update.js';
export * as 'notes/muting/delete' from './endpoints/notes/muting/delete.js';
export * as 'notes/muting/list' from './endpoints/notes/muting/list.js';
export * as 'notifications/create' from './endpoints/notifications/create.js';

View File

@ -44,19 +44,21 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private readonly noteMutingService: NoteMutingService,
private readonly getterService: GetterService,
) {
super(meta, paramDef, async (ps, me) => {
const note = await this.getterService.getNote(ps.noteId).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw err;
});
await this.noteMutingService.create({
userId: me.id,
noteId: note.id,
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
});
try {
await this.noteMutingService.create({
userId: me.id,
noteId: ps.noteId,
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
});
} catch (e) {
if (e instanceof NoteMutingService.NoSuchNoteError) {
throw new ApiError(meta.errors.noSuchNote);
} else {
throw e;
}
}
});
}
}

View File

@ -16,10 +16,11 @@ export const meta = {
kind: 'write:account',
errors: {
noSuchItem: {
message: 'No such item.',
code: 'NO_SUCH_ITEM',
notMuted: {
message: 'Not muted.',
code: 'NOT_MUTED',
id: '6ad3b6c9-f173-60f7-b558-5eea13896254',
httpStatusCode: 400,
},
},
} as const;
@ -27,9 +28,9 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },
noteId: { type: 'string', format: 'misskey:id' },
},
required: ['id'],
required: ['noteId'],
} as const;
@Injectable()
@ -37,19 +38,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private readonly noteMutingService: NoteMutingService,
) {
super(meta, paramDef, async (ps) => {
super(meta, paramDef, async (ps, me) => {
try {
// Existence check
await this.noteMutingService.get(ps.id);
await this.noteMutingService.delete(me.id, ps.noteId);
} catch (e) {
if (e instanceof NoteMutingService.NoSuchItemError) {
throw new ApiError(meta.errors.noSuchItem);
if (e instanceof NoteMutingService.NotMutedError) {
throw new ApiError(meta.errors.notMuted);
} else {
throw e;
}
throw e;
}
await this.noteMutingService.delete(ps.id);
});
}
}

View File

@ -16,18 +16,13 @@ export const meta = {
kind: 'read:account',
res: {
type: 'object',
properties: {
notes: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
expiresAt: { type: 'string', format: 'date-time', nullable: true },
note: { type: 'object', ref: 'Note' },
},
},
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
expiresAt: { type: 'string', format: 'date-time', nullable: true },
note: { type: 'object', ref: 'Note' },
},
},
},
@ -35,7 +30,12 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {},
properties: {
sinceId: { type: 'string', format: 'misskey:id', nullable: true },
untilId: { type: 'string', format: 'misskey:id', nullable: true },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer' },
},
required: [],
} as const;
@ -46,7 +46,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private readonly noteEntityService: NoteEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const mutings = await this.noteMutingService.listByUserId(me.id, { joinNote: true });
const mutings = await this.noteMutingService.listByUserId(
{ userId: me.id },
{
joinNote: true,
limit: ps.limit,
offset: ps.offset,
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const packedNotes = await this.noteEntityService.packMany(mutings.map(m => m.note!))
@ -54,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return mutings.map(m => ({
id: m.id,
expiresAt: m.expiresAt,
expiresAt: m.expiresAt?.toISOString(),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
note: packedNotes.get(m.noteId)!,
}));

View File

@ -1,64 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import ms from 'ms';
import { NoteMutingService } from '@/core/note/NoteMutingService.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['notes'],
requireCredential: true,
kind: 'write:account',
limit: {
duration: ms('1hour'),
max: 10,
},
errors: {
noSuchItem: {
message: 'No such item.',
code: 'NO_SUCH_ITEM',
id: '502ce7a1-d8b0-7094-78e2-ff5b8190efc9',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },
expiresAt: { type: 'integer', nullable: true },
},
required: ['id'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private readonly noteMutingService: NoteMutingService,
) {
super(meta, paramDef, async (ps, me) => {
try {
// Existence check
await this.noteMutingService.get(ps.id);
} catch (e) {
if (e instanceof NoteMutingService.NoSuchItemError) {
throw new ApiError(meta.errors.noSuchItem);
}
throw e;
}
await this.noteMutingService.update(ps.id, {
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
});
});
}
}

View File

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { NoteMutingService } from '@/core/note/NoteMutingService.js';
export const meta = {
tags: ['notes'],
@ -26,6 +27,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
isMutedNote: {
type: 'boolean',
optional: false, nullable: false,
},
},
},
} as const;
@ -49,11 +54,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.noteFavoritesRepository)
private noteFavoritesRepository: NoteFavoritesRepository,
private noteMutingService: NoteMutingService,
) {
super(meta, paramDef, async (ps, me) => {
const note = await this.notesRepository.findOneByOrFail({ id: ps.noteId });
const [favorite, threadMuting] = await Promise.all([
const [favorite, threadMuting, isMutedNote] = await Promise.all([
this.noteFavoritesRepository.count({
where: {
userId: me.id,
@ -68,11 +75,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
},
take: 1,
}),
this.noteMutingService.isMuting(me.id, note.id),
]);
return {
isFavorited: favorite !== 0,
isMutedThread: threadMuting !== 0,
isMutedNote,
};
});
}

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkPagination :pagination="blockingPagination">
<MkPagination ref="pagingComponent" :pagination="noteMutingPagination">
<template #empty>
<div class="_fullinfo">
<img :src="infoImageUrl" class="_ghost"/>
@ -13,36 +13,80 @@ SPDX-License-Identifier: AGPL-3.0-only
</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="unblock(item.blockee, $event)"><i class="ti ti-x"></i></button>
<MkFolder v-for="item in (items as entities.NotesMutingListResponse)" :key="item.id" style="margin-bottom: 1rem;">
<template #label>
<div>
<span>[{{ i18n.ts.expiration }}: </span>
<MkTime v-if="item.expiresAt" :time="item.expiresAt" mode="absolute"/>
<span v-else>{{ i18n.ts.none }}</span>
<span>] </span>
<span>
{{ ((item.note.user.name) ? item.note.user.name + ` (@${item.note.user.username})` : `@${item.note.user.username}`) }}
</span>
<span>
{{ i18n.ts._noteMuting.labelSuffix }}
</span>
</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>
</template>
<template #default>
<MkNoteSub :note="item.note"/>
</template>
<template #footer>
<div style="display: flex; flex-direction: column" class="_gaps">
<MkButton :danger="true" @click="onClickUnmuteNote(item.note.id)">{{ i18n.ts._noteMuting.unmuteNote }}</MkButton>
<span :class="$style.caption">{{ i18n.ts._noteMuting.unmuteCaption }}</span>
</div>
</div>
</div>
</template>
</MkFolder>
</template>
</MkPagination>
</template>
<script lang="ts" setup>
import { entities } from 'misskey-js';
import { shallowRef } from 'vue';
import type { Paging } from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkNoteSub from '@/components/MkNoteSub.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { userPage } from '@/filters/user';
import { i18n } from '@/i18n';
import { infoImageUrl } from '@/instance';
import * as os from '@/os';
const noteMutingPagination = {
endpoint: 'notes/muting/list' as const,
const noteMutingPagination: Paging = {
endpoint: 'notes/muting/list',
limit: 10,
};
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
async function onClickUnmuteNote(noteId: string) {
await os.apiWithDialog(
'notes/muting/delete',
{
noteId,
},
undefined,
{
'6ad3b6c9-f173-60f7-b558-5eea13896254': {
title: i18n.ts.error,
text: i18n.ts._noteMuting.notMutedNote,
},
},
);
pagingComponent.value?.reload();
}
</script>
<style lang="scss" module>
.caption {
font-size: 0.85em;
padding: 8px 0 0 0;
color: var(--MI_THEME-fgTransparentWeak);
}
</style>

View File

@ -234,6 +234,70 @@ export function getNoteMenu(props: {
});
}
async function toggleNoteMute(mute: boolean) {
if (!mute) {
await os.apiWithDialog(
'notes/muting/delete',
{
noteId: appearNote.id,
},
undefined,
{
'6ad3b6c9-f173-60f7-b558-5eea13896254': {
title: i18n.ts.error,
text: i18n.ts._noteMuting.notMutedNote,
},
},
);
} else {
const { canceled, result: period } = await os.select({
title: i18n.ts.mutePeriod,
items: [{
value: 'indefinitely', text: i18n.ts.indefinitely,
}, {
value: 'tenMinutes', text: i18n.ts.tenMinutes,
}, {
value: 'oneHour', text: i18n.ts.oneHour,
}, {
value: 'oneDay', text: i18n.ts.oneDay,
}, {
value: 'oneWeek', text: i18n.ts.oneWeek,
}],
default: 'indefinitely',
});
if (canceled) return;
const expiresAt = period === 'indefinitely'
? null
: period === 'tenMinutes'
? Date.now() + (1000 * 60 * 10)
: period === 'oneHour'
? Date.now() + (1000 * 60 * 60)
: period === 'oneDay'
? Date.now() + (1000 * 60 * 60 * 24)
: period === 'oneWeek'
? Date.now() + (1000 * 60 * 60 * 24 * 7)
: null;
await os.apiWithDialog(
'notes/muting/create',
{
noteId: appearNote.id,
expiresAt,
},
undefined,
{
'a58e7999-f6d3-1780-a688-f43661719662': {
title: i18n.ts.error,
text: i18n.ts._noteMuting.noNotes,
},
},
).then(() => {
props.isDeleted.value = true;
});
}
}
function copyContent(): void {
copyToClipboard(appearNote.text);
}
@ -379,6 +443,16 @@ export function getNoteMenu(props: {
action: () => toggleThreadMute(true),
}));
menuItems.push(statePromise.then(state => state.isMutedNote ? {
icon: 'ti ti-message',
text: i18n.ts._noteMuting.unmuteNote,
action: () => toggleNoteMute(false),
} : {
icon: 'ti ti-message-off',
text: i18n.ts._noteMuting.muteNote,
action: () => toggleNoteMute(true),
}));
if (appearNote.userId === $i.id) {
if (($i.pinnedNoteIds ?? []).includes(appearNote.id)) {
menuItems.push({

View File

@ -1692,8 +1692,8 @@ declare namespace entities {
NotesMentionsResponse,
NotesMutingCreateRequest,
NotesMutingDeleteRequest,
NotesMutingListRequest,
NotesMutingListResponse,
NotesMutingUpdateRequest,
NotesPollsRecommendationRequest,
NotesPollsRecommendationResponse,
NotesPollsVoteRequest,
@ -2751,10 +2751,10 @@ type NotesMutingCreateRequest = operations['notes___muting___create']['requestBo
type NotesMutingDeleteRequest = operations['notes___muting___delete']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesMutingListResponse = operations['notes___muting___list']['responses']['200']['content']['application/json'];
type NotesMutingListRequest = operations['notes___muting___list']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesMutingUpdateRequest = operations['notes___muting___update']['requestBody']['content']['application/json'];
type NotesMutingListResponse = operations['notes___muting___list']['responses']['200']['content']['application/json'];
// @public (undocumented)
type NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json'];

View File

@ -3365,17 +3365,6 @@ declare module '../api.js' {
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'notes/muting/update', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/polls/recommendation', P extends Endpoints[E]['req']>(

View File

@ -451,8 +451,8 @@ import type {
NotesMentionsResponse,
NotesMutingCreateRequest,
NotesMutingDeleteRequest,
NotesMutingListRequest,
NotesMutingListResponse,
NotesMutingUpdateRequest,
NotesPollsRecommendationRequest,
NotesPollsRecommendationResponse,
NotesPollsVoteRequest,
@ -892,8 +892,7 @@ export type Endpoints = {
'notes/mentions': { req: NotesMentionsRequest; res: NotesMentionsResponse };
'notes/muting/create': { req: NotesMutingCreateRequest; res: EmptyResponse };
'notes/muting/delete': { req: NotesMutingDeleteRequest; res: EmptyResponse };
'notes/muting/list': { req: EmptyRequest; res: NotesMutingListResponse };
'notes/muting/update': { req: NotesMutingUpdateRequest; res: EmptyResponse };
'notes/muting/list': { req: NotesMutingListRequest; res: NotesMutingListResponse };
'notes/polls/recommendation': { req: NotesPollsRecommendationRequest; res: NotesPollsRecommendationResponse };
'notes/polls/vote': { req: NotesPollsVoteRequest; res: EmptyResponse };
'notes/reactions': { req: NotesReactionsRequest; res: NotesReactionsResponse };

View File

@ -454,8 +454,8 @@ export type NotesMentionsRequest = operations['notes___mentions']['requestBody']
export type NotesMentionsResponse = operations['notes___mentions']['responses']['200']['content']['application/json'];
export type NotesMutingCreateRequest = operations['notes___muting___create']['requestBody']['content']['application/json'];
export type NotesMutingDeleteRequest = operations['notes___muting___delete']['requestBody']['content']['application/json'];
export type NotesMutingListRequest = operations['notes___muting___list']['requestBody']['content']['application/json'];
export type NotesMutingListResponse = operations['notes___muting___list']['responses']['200']['content']['application/json'];
export type NotesMutingUpdateRequest = operations['notes___muting___update']['requestBody']['content']['application/json'];
export type NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json'];
export type NotesPollsRecommendationResponse = operations['notes___polls___recommendation']['responses']['200']['content']['application/json'];
export type NotesPollsVoteRequest = operations['notes___polls___vote']['requestBody']['content']['application/json'];