backend
This commit is contained in:
parent
b37622fa64
commit
1cb2b38dcb
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { NoteMutingService } from '@/core/note/NoteMutingService.js';
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { AbuseReportService } from '@/core/AbuseReportService.js';
|
import { AbuseReportService } from '@/core/AbuseReportService.js';
|
||||||
import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
|
import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
|
||||||
|
@ -185,6 +186,7 @@ const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting
|
||||||
const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
|
const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
|
||||||
const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
|
const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
|
||||||
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
||||||
|
const $NoteMutingService: Provider = { provide: 'NoteMutingService', useExisting: NoteMutingService };
|
||||||
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
|
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
|
||||||
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
|
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
|
||||||
const $SystemAccountService: Provider = { provide: 'SystemAccountService', useExisting: SystemAccountService };
|
const $SystemAccountService: Provider = { provide: 'SystemAccountService', useExisting: SystemAccountService };
|
||||||
|
@ -334,6 +336,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
NoteDeleteService,
|
NoteDeleteService,
|
||||||
NotePiningService,
|
NotePiningService,
|
||||||
NoteReadService,
|
NoteReadService,
|
||||||
|
NoteMutingService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
PollService,
|
PollService,
|
||||||
SystemAccountService,
|
SystemAccountService,
|
||||||
|
@ -479,6 +482,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$NoteDeleteService,
|
$NoteDeleteService,
|
||||||
$NotePiningService,
|
$NotePiningService,
|
||||||
$NoteReadService,
|
$NoteReadService,
|
||||||
|
$NoteMutingService,
|
||||||
$NotificationService,
|
$NotificationService,
|
||||||
$PollService,
|
$PollService,
|
||||||
$SystemAccountService,
|
$SystemAccountService,
|
||||||
|
@ -625,6 +629,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
NoteDeleteService,
|
NoteDeleteService,
|
||||||
NotePiningService,
|
NotePiningService,
|
||||||
NoteReadService,
|
NoteReadService,
|
||||||
|
NoteMutingService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
PollService,
|
PollService,
|
||||||
SystemAccountService,
|
SystemAccountService,
|
||||||
|
@ -769,6 +774,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$NoteDeleteService,
|
$NoteDeleteService,
|
||||||
$NotePiningService,
|
$NotePiningService,
|
||||||
$NoteReadService,
|
$NoteReadService,
|
||||||
|
$NoteMutingService,
|
||||||
$NotificationService,
|
$NotificationService,
|
||||||
$PollService,
|
$PollService,
|
||||||
$SystemAccountService,
|
$SystemAccountService,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { isReply } from '@/misc/is-reply.js';
|
import { isReply } from '@/misc/is-reply.js';
|
||||||
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||||
|
import { NoteMutingService } from './note/NoteMutingService.js';
|
||||||
|
|
||||||
type TimelineOptions = {
|
type TimelineOptions = {
|
||||||
untilId: string | null,
|
untilId: string | null,
|
||||||
|
@ -45,6 +46,7 @@ export class FanoutTimelineEndpointService {
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private fanoutTimelineService: FanoutTimelineService,
|
private fanoutTimelineService: FanoutTimelineService,
|
||||||
|
private noteMutingService: NoteMutingService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,11 +103,13 @@ export class FanoutTimelineEndpointService {
|
||||||
userIdsWhoMeMutingRenotes,
|
userIdsWhoMeMutingRenotes,
|
||||||
userIdsWhoBlockingMe,
|
userIdsWhoBlockingMe,
|
||||||
userMutedInstances,
|
userMutedInstances,
|
||||||
|
noteMutings,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.cacheService.userMutingsCache.fetch(ps.me.id),
|
this.cacheService.userMutingsCache.fetch(ps.me.id),
|
||||||
this.cacheService.renoteMutingsCache.fetch(ps.me.id),
|
this.cacheService.renoteMutingsCache.fetch(ps.me.id),
|
||||||
this.cacheService.userBlockedCache.fetch(ps.me.id),
|
this.cacheService.userBlockedCache.fetch(ps.me.id),
|
||||||
this.cacheService.userProfileCache.fetch(me.id).then(p => new Set(p.mutedInstances)),
|
this.cacheService.userProfileCache.fetch(me.id).then(p => new Set(p.mutedInstances)),
|
||||||
|
this.noteMutingService.getMutingNoteIdsSet(me.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const parentFilter = filter;
|
const parentFilter = filter;
|
||||||
|
@ -114,6 +118,7 @@ export class FanoutTimelineEndpointService {
|
||||||
if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
|
if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
|
||||||
if (!ps.ignoreAuthorFromMute && isRenote(note) && !isQuote(note) && userIdsWhoMeMutingRenotes.has(note.userId)) return false;
|
if (!ps.ignoreAuthorFromMute && isRenote(note) && !isQuote(note) && userIdsWhoMeMutingRenotes.has(note.userId)) return false;
|
||||||
if (isInstanceMuted(note, userMutedInstances)) return false;
|
if (isInstanceMuted(note, userMutedInstances)) return false;
|
||||||
|
if (noteMutings.has(note.id)) return false;
|
||||||
|
|
||||||
return parentFilter(note);
|
return parentFilter(note);
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ import type { MiPage } from '@/models/Page.js';
|
||||||
import type { MiWebhook } from '@/models/Webhook.js';
|
import type { MiWebhook } from '@/models/Webhook.js';
|
||||||
import type { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
import type { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||||
import type { MiMeta } from '@/models/Meta.js';
|
import type { MiMeta } from '@/models/Meta.js';
|
||||||
import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
|
import { MiAvatarDecoration, MiNoteMuting, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
@ -249,6 +249,8 @@ export interface InternalEventTypes {
|
||||||
unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
|
unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
|
||||||
userListMemberAdded: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
userListMemberAdded: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
||||||
userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
||||||
|
noteMuteCreated: MiNoteMuting;
|
||||||
|
noteMuteDeleted: MiNoteMuting;
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventTypesToEventPayload<T> = EventUnionFromDictionary<UndefinedAsNullAll<SerializedAll<T>>>;
|
type EventTypesToEventPayload<T> = EventUnionFromDictionary<UndefinedAsNullAll<SerializedAll<T>>>;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Brackets, ObjectLiteral } from 'typeorm';
|
import { Brackets, ObjectLiteral } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js';
|
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository, NoteMutingsRepository } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { SelectQueryBuilder } from 'typeorm';
|
import type { SelectQueryBuilder } from 'typeorm';
|
||||||
|
@ -21,12 +21,12 @@ export class QueryService {
|
||||||
@Inject(DI.followingsRepository)
|
@Inject(DI.followingsRepository)
|
||||||
private followingsRepository: FollowingsRepository,
|
private followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
@Inject(DI.channelFollowingsRepository)
|
|
||||||
private channelFollowingsRepository: ChannelFollowingsRepository,
|
|
||||||
|
|
||||||
@Inject(DI.blockingsRepository)
|
@Inject(DI.blockingsRepository)
|
||||||
private blockingsRepository: BlockingsRepository,
|
private blockingsRepository: BlockingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.noteMutingsRepository)
|
||||||
|
private noteMutingsRepository: NoteMutingsRepository,
|
||||||
|
|
||||||
@Inject(DI.noteThreadMutingsRepository)
|
@Inject(DI.noteThreadMutingsRepository)
|
||||||
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
|
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
|
||||||
|
|
||||||
|
@ -110,6 +110,16 @@ export class QueryService {
|
||||||
q.setParameters(blockedQuery.getParameters());
|
q.setParameters(blockedQuery.getParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public generateMutedNoteQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
||||||
|
const query = this.noteMutingsRepository.createQueryBuilder('noteMuting')
|
||||||
|
.select('noteMuting.noteId')
|
||||||
|
.where('noteMuting.userId = :userId', { userId: me.id });
|
||||||
|
|
||||||
|
q.andWhere(`note.id NOT IN (${ query.getQuery() })`);
|
||||||
|
q.setParameters(query.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateMutedNoteThreadQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
public generateMutedNoteThreadQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
||||||
const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted')
|
const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted')
|
||||||
|
|
|
@ -234,8 +234,11 @@ export class SearchService {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
if (me) {
|
||||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
}
|
||||||
|
|
||||||
return query.limit(pagination.limit).getMany();
|
return query.limit(pagination.limit).getMany();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
|
import { RedisKVCache } from '@/misc/cache.js';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NoteMutingService implements OnApplicationShutdown {
|
||||||
|
public static NoSuchItemError = class extends Error {
|
||||||
|
};
|
||||||
|
|
||||||
|
private cache: RedisKVCache<Set<string>>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis,
|
||||||
|
@Inject(DI.redisForSub)
|
||||||
|
private redisForSub: Redis.Redis,
|
||||||
|
@Inject(DI.noteMutingsRepository)
|
||||||
|
private noteMutingsRepository: NoteMutingsRepository,
|
||||||
|
private idService: IdService,
|
||||||
|
private globalEventService: GlobalEventService,
|
||||||
|
) {
|
||||||
|
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))),
|
||||||
|
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||||
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async onMessage(_: string, data: string): Promise<void> {
|
||||||
|
const obj = JSON.parse(data);
|
||||||
|
if (obj.channel !== 'internal') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||||
|
switch (type) {
|
||||||
|
case 'noteMuteCreated': {
|
||||||
|
const noteIds = await this.cache.get(body.userId);
|
||||||
|
if (noteIds) {
|
||||||
|
noteIds.add(body.noteId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'noteMuteDeleted': {
|
||||||
|
const noteIds = await this.cache.get(body.userId);
|
||||||
|
if (noteIds) {
|
||||||
|
noteIds.delete(body.noteId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async listByUserId(
|
||||||
|
userId: MiNoteMuting['userId'],
|
||||||
|
opts?: {
|
||||||
|
joinUser?: boolean;
|
||||||
|
joinNote?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<MiNoteMuting[]> {
|
||||||
|
const q = this.noteMutingsRepository.createQueryBuilder('noteMuting');
|
||||||
|
|
||||||
|
q.where('noteMuting.userId = :userId', { userId });
|
||||||
|
if (opts?.joinUser) {
|
||||||
|
q.leftJoinAndSelect('noteMuting.user', 'user');
|
||||||
|
}
|
||||||
|
if (opts?.joinNote) {
|
||||||
|
q.leftJoinAndSelect('noteMuting.note', 'note');
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getMutingNoteIdsSet(userId: MiNoteMuting['userId']): Promise<Set<string>> {
|
||||||
|
return this.cache.fetch(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async create(
|
||||||
|
params: Pick<MiNoteMuting, 'userId' | 'noteId' | 'expiresAt'>,
|
||||||
|
): Promise<void> {
|
||||||
|
const id = this.idService.gen();
|
||||||
|
const result = await this.noteMutingsRepository.insertOne({
|
||||||
|
id,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.globalEventService.publishInternalEvent('noteMuteCreated', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async update(
|
||||||
|
id: MiNoteMuting['id'],
|
||||||
|
params: Partial<Pick<MiNoteMuting, 'expiresAt'>>,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.noteMutingsRepository.update(id, params);
|
||||||
|
|
||||||
|
// 現状、ミュート設定の有無しかキャッシュしていないので更新時はイベントを発行しない。
|
||||||
|
// 他に細かい設定が登場した場合はキャッシュの型をSetからMapに変えつつ、イベントを発行するようにする。
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async delete(id: MiNoteMuting['id']): Promise<void> {
|
||||||
|
const value = await this.noteMutingsRepository.findOne({ where: { id } });
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.noteMutingsRepository.delete(id);
|
||||||
|
this.globalEventService.publishInternalEvent('noteMuteDeleted', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
|
this.redisForSub.off('message', this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ export const DI = {
|
||||||
appsRepository: Symbol('appsRepository'),
|
appsRepository: Symbol('appsRepository'),
|
||||||
avatarDecorationsRepository: Symbol('avatarDecorationsRepository'),
|
avatarDecorationsRepository: Symbol('avatarDecorationsRepository'),
|
||||||
noteFavoritesRepository: Symbol('noteFavoritesRepository'),
|
noteFavoritesRepository: Symbol('noteFavoritesRepository'),
|
||||||
|
noteMutingsRepository: Symbol('noteMutingsRepository'),
|
||||||
noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
|
noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
|
||||||
noteReactionsRepository: Symbol('noteReactionsRepository'),
|
noteReactionsRepository: Symbol('noteReactionsRepository'),
|
||||||
noteUnreadsRepository: Symbol('noteUnreadsRepository'),
|
noteUnreadsRepository: Symbol('noteUnreadsRepository'),
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
type NoteCompat = {
|
||||||
|
id: string;
|
||||||
|
reply?: NoteCompat | null;
|
||||||
|
renote?: NoteCompat | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMutingNoteRelated(note: NoteCompat, noteIds: Set<string>) {
|
||||||
|
if (noteIds.has(note.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.reply != null && noteIds.has(note.reply.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.renote != null && noteIds.has(note.renote.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
import { MiNote } from './Note.js';
|
||||||
|
|
||||||
|
@Entity('note_muting')
|
||||||
|
export class MiNoteMuting {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
})
|
||||||
|
public userId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: MiUser | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('varchar', {
|
||||||
|
...id(),
|
||||||
|
})
|
||||||
|
public noteId: string;
|
||||||
|
|
||||||
|
@ManyToOne(type => MiNote, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public note: MiNote | null;
|
||||||
|
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public expiresAt: Date | null;
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import {
|
||||||
MiMuting,
|
MiMuting,
|
||||||
MiNote,
|
MiNote,
|
||||||
MiNoteFavorite,
|
MiNoteFavorite,
|
||||||
|
MiNoteMuting,
|
||||||
MiNoteReaction,
|
MiNoteReaction,
|
||||||
MiNoteThreadMuting,
|
MiNoteThreadMuting,
|
||||||
MiNoteUnread,
|
MiNoteUnread,
|
||||||
|
@ -124,6 +125,12 @@ const $noteFavoritesRepository: Provider = {
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $noteMutingsRepository: Provider = {
|
||||||
|
provide: DI.noteMutingsRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiNoteMuting).extend(miRepository as MiRepository<MiNoteMuting>),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $noteThreadMutingsRepository: Provider = {
|
const $noteThreadMutingsRepository: Provider = {
|
||||||
provide: DI.noteThreadMutingsRepository,
|
provide: DI.noteThreadMutingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiNoteThreadMuting).extend(miRepository as MiRepository<MiNoteThreadMuting>),
|
useFactory: (db: DataSource) => db.getRepository(MiNoteThreadMuting).extend(miRepository as MiRepository<MiNoteThreadMuting>),
|
||||||
|
@ -512,6 +519,7 @@ const $reversiGamesRepository: Provider = {
|
||||||
$appsRepository,
|
$appsRepository,
|
||||||
$avatarDecorationsRepository,
|
$avatarDecorationsRepository,
|
||||||
$noteFavoritesRepository,
|
$noteFavoritesRepository,
|
||||||
|
$noteMutingsRepository,
|
||||||
$noteThreadMutingsRepository,
|
$noteThreadMutingsRepository,
|
||||||
$noteReactionsRepository,
|
$noteReactionsRepository,
|
||||||
$noteUnreadsRepository,
|
$noteUnreadsRepository,
|
||||||
|
@ -584,6 +592,7 @@ const $reversiGamesRepository: Provider = {
|
||||||
$appsRepository,
|
$appsRepository,
|
||||||
$avatarDecorationsRepository,
|
$avatarDecorationsRepository,
|
||||||
$noteFavoritesRepository,
|
$noteFavoritesRepository,
|
||||||
|
$noteMutingsRepository,
|
||||||
$noteThreadMutingsRepository,
|
$noteThreadMutingsRepository,
|
||||||
$noteReactionsRepository,
|
$noteReactionsRepository,
|
||||||
$noteUnreadsRepository,
|
$noteUnreadsRepository,
|
||||||
|
|
|
@ -80,6 +80,7 @@ import { MiFlashLike } from '@/models/FlashLike.js';
|
||||||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||||
|
import { MiNoteMuting } from './NoteMuting.js';
|
||||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||||
|
|
||||||
export interface MiRepository<T extends ObjectLiteral> {
|
export interface MiRepository<T extends ObjectLiteral> {
|
||||||
|
@ -158,6 +159,7 @@ export {
|
||||||
MiNote,
|
MiNote,
|
||||||
MiNoteFavorite,
|
MiNoteFavorite,
|
||||||
MiNoteReaction,
|
MiNoteReaction,
|
||||||
|
MiNoteMuting,
|
||||||
MiNoteThreadMuting,
|
MiNoteThreadMuting,
|
||||||
MiNoteUnread,
|
MiNoteUnread,
|
||||||
MiPage,
|
MiPage,
|
||||||
|
@ -230,6 +232,7 @@ export type RenoteMutingsRepository = Repository<MiRenoteMuting> & MiRepository<
|
||||||
export type NotesRepository = Repository<MiNote> & MiRepository<MiNote>;
|
export type NotesRepository = Repository<MiNote> & MiRepository<MiNote>;
|
||||||
export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<MiNoteFavorite>;
|
export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<MiNoteFavorite>;
|
||||||
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
|
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
|
||||||
|
export type NoteMutingsRepository = Repository<MiNoteMuting> & MiRepository<MiNoteMuting>;
|
||||||
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
|
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
|
||||||
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
|
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
|
||||||
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
|
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
|
||||||
|
|
|
@ -325,6 +325,10 @@ export * as 'notes/timeline' from './endpoints/notes/timeline.js';
|
||||||
export * as 'notes/translate' from './endpoints/notes/translate.js';
|
export * as 'notes/translate' from './endpoints/notes/translate.js';
|
||||||
export * as 'notes/unrenote' from './endpoints/notes/unrenote.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/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';
|
export * as 'notifications/create' from './endpoints/notifications/create.js';
|
||||||
export * as 'notifications/flush' from './endpoints/notifications/flush.js';
|
export * as 'notifications/flush' from './endpoints/notifications/flush.js';
|
||||||
export * as 'notifications/mark-all-as-read' from './endpoints/notifications/mark-all-as-read.js';
|
export * as 'notifications/mark-all-as-read' from './endpoints/notifications/mark-all-as-read.js';
|
||||||
|
|
|
@ -116,6 +116,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|
||||||
const notes = await query.getMany();
|
const notes = await query.getMany();
|
||||||
if (sinceId != null && untilId == null) {
|
if (sinceId != null && untilId == null) {
|
||||||
|
|
|
@ -124,6 +124,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (me) {
|
if (me) {
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = await query
|
const notes = await query
|
||||||
|
|
|
@ -73,6 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (me) {
|
if (me) {
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = await query.limit(ps.limit).getMany();
|
const notes = await query.limit(ps.limit).getMany();
|
||||||
|
|
|
@ -82,6 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.withFiles) {
|
if (ps.withFiles) {
|
||||||
|
|
|
@ -46,7 +46,7 @@ export const meta = {
|
||||||
bothWithRepliesAndWithFiles: {
|
bothWithRepliesAndWithFiles: {
|
||||||
message: 'Specifying both withReplies and withFiles is not supported',
|
message: 'Specifying both withReplies and withFiles is not supported',
|
||||||
code: 'BOTH_WITH_REPLIES_AND_WITH_FILES',
|
code: 'BOTH_WITH_REPLIES_AND_WITH_FILES',
|
||||||
id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f'
|
id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -246,6 +246,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|
||||||
if (ps.includeMyRenotes === false) {
|
if (ps.includeMyRenotes === false) {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
|
|
|
@ -156,9 +156,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
if (me) {
|
||||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.withFiles) {
|
if (ps.withFiles) {
|
||||||
query.andWhere('note.fileIds != \'{}\'');
|
query.andWhere('note.fileIds != \'{}\'');
|
||||||
|
|
|
@ -77,6 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateMutedNoteThreadQuery(query, me);
|
this.queryService.generateMutedNoteThreadQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|
||||||
if (ps.visibility) {
|
if (ps.visibility) {
|
||||||
query.andWhere('note.visibility = :visibility', { visibility: ps.visibility });
|
query.andWhere('note.visibility = :visibility', { visibility: ps.visibility });
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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 { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: 'write:account',
|
||||||
|
|
||||||
|
limit: {
|
||||||
|
duration: ms('1hour'),
|
||||||
|
max: 10,
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchNote: {
|
||||||
|
message: 'No such note.',
|
||||||
|
code: 'NO_SUCH_NOTE',
|
||||||
|
id: 'a58e7999-f6d3-1780-a688-f43661719662',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
noteId: { type: 'string', format: 'misskey:id' },
|
||||||
|
expiresAt: { type: 'integer', nullable: true },
|
||||||
|
},
|
||||||
|
required: ['noteId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
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',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchItem: {
|
||||||
|
message: 'No such item.',
|
||||||
|
code: 'NO_SUCH_ITEM',
|
||||||
|
id: '6ad3b6c9-f173-60f7-b558-5eea13896254',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
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) => {
|
||||||
|
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.delete(ps.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { NoteMutingService } from '@/core/note/NoteMutingService.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
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' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private readonly noteMutingService: NoteMutingService,
|
||||||
|
private readonly noteEntityService: NoteEntityService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const mutings = await this.noteMutingService.listByUserId(me.id, { joinNote: true });
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const packedNotes = await this.noteEntityService.packMany(mutings.map(m => m.note!))
|
||||||
|
.then(res => new Map(res.map(it => [it.id, it])));
|
||||||
|
|
||||||
|
return mutings.map(m => ({
|
||||||
|
id: m.id,
|
||||||
|
expiresAt: m.expiresAt,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
note: packedNotes.get(m.noteId)!,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,8 +72,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
if (me) {
|
||||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
}
|
||||||
|
|
||||||
const renotes = await query.limit(ps.limit).getMany();
|
const renotes = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
if (me) {
|
||||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
}
|
||||||
|
|
||||||
const timeline = await query.limit(ps.limit).getMany();
|
const timeline = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
|
|
|
@ -81,8 +81,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
if (me) {
|
||||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ps.tag) {
|
if (ps.tag) {
|
||||||
|
|
|
@ -202,6 +202,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|
||||||
if (ps.includeMyRenotes === false) {
|
if (ps.includeMyRenotes === false) {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
|
|
|
@ -187,6 +187,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|
||||||
if (ps.includeMyRenotes === false) {
|
if (ps.includeMyRenotes === false) {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
|
|
|
@ -104,6 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|
||||||
const notes = await query.getMany();
|
const notes = await query.getMany();
|
||||||
notes.sort((a, b) => a.id > b.id ? -1 : 1);
|
notes.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||||
|
|
|
@ -187,6 +187,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (me) {
|
if (me) {
|
||||||
this.queryService.generateMutedUserQuery(query, me, { id: ps.userId });
|
this.queryService.generateMutedUserQuery(query, me, { id: ps.userId });
|
||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.withFiles) {
|
if (ps.withFiles) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService
|
||||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
import { isJsonObject } from '@/misc/json-value.js';
|
import { isJsonObject } from '@/misc/json-value.js';
|
||||||
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||||
|
import { NoteMutingService } from '@/core/note/NoteMutingService.js';
|
||||||
import type { ChannelsService } from './ChannelsService.js';
|
import type { ChannelsService } from './ChannelsService.js';
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
import type Channel from './channel.js';
|
import type Channel from './channel.js';
|
||||||
|
@ -41,6 +42,7 @@ export default class Connection {
|
||||||
public userIdsWhoBlockingMe: Set<string> = new Set();
|
public userIdsWhoBlockingMe: Set<string> = new Set();
|
||||||
public userIdsWhoMeMutingRenotes: Set<string> = new Set();
|
public userIdsWhoMeMutingRenotes: Set<string> = new Set();
|
||||||
public userMutedInstances: Set<string> = new Set();
|
public userMutedInstances: Set<string> = new Set();
|
||||||
|
public noteMuting: Set<string> = new Set();
|
||||||
private fetchIntervalId: NodeJS.Timeout | null = null;
|
private fetchIntervalId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -49,6 +51,7 @@ export default class Connection {
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private channelFollowingService: ChannelFollowingService,
|
private channelFollowingService: ChannelFollowingService,
|
||||||
|
private noteMutingService: NoteMutingService,
|
||||||
|
|
||||||
user: MiUser | null | undefined,
|
user: MiUser | null | undefined,
|
||||||
token: MiAccessToken | null | undefined,
|
token: MiAccessToken | null | undefined,
|
||||||
|
@ -60,13 +63,14 @@ export default class Connection {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async fetch() {
|
public async fetch() {
|
||||||
if (this.user == null) return;
|
if (this.user == null) return;
|
||||||
const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes] = await Promise.all([
|
const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes, noteMuting] = await Promise.all([
|
||||||
this.cacheService.userProfileCache.fetch(this.user.id),
|
this.cacheService.userProfileCache.fetch(this.user.id),
|
||||||
this.cacheService.userFollowingsCache.fetch(this.user.id),
|
this.cacheService.userFollowingsCache.fetch(this.user.id),
|
||||||
this.channelFollowingService.userFollowingChannelsCache.fetch(this.user.id),
|
this.channelFollowingService.userFollowingChannelsCache.fetch(this.user.id),
|
||||||
this.cacheService.userMutingsCache.fetch(this.user.id),
|
this.cacheService.userMutingsCache.fetch(this.user.id),
|
||||||
this.cacheService.userBlockedCache.fetch(this.user.id),
|
this.cacheService.userBlockedCache.fetch(this.user.id),
|
||||||
this.cacheService.renoteMutingsCache.fetch(this.user.id),
|
this.cacheService.renoteMutingsCache.fetch(this.user.id),
|
||||||
|
this.noteMutingService.getMutingNoteIdsSet(this.user.id),
|
||||||
]);
|
]);
|
||||||
this.userProfile = userProfile;
|
this.userProfile = userProfile;
|
||||||
this.following = following;
|
this.following = following;
|
||||||
|
@ -75,6 +79,7 @@ export default class Connection {
|
||||||
this.userIdsWhoBlockingMe = userIdsWhoBlockingMe;
|
this.userIdsWhoBlockingMe = userIdsWhoBlockingMe;
|
||||||
this.userIdsWhoMeMutingRenotes = userIdsWhoMeMutingRenotes;
|
this.userIdsWhoMeMutingRenotes = userIdsWhoMeMutingRenotes;
|
||||||
this.userMutedInstances = new Set(userProfile.mutedInstances);
|
this.userMutedInstances = new Set(userProfile.mutedInstances);
|
||||||
|
this.noteMuting = noteMuting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||||
|
import { isMutingNoteRelated } from '@/misc/is-muting-note-related.js';
|
||||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
@ -51,6 +52,10 @@ export default abstract class Channel {
|
||||||
return this.connection.userMutedInstances;
|
return this.connection.userMutedInstances;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected get noteMuting() {
|
||||||
|
return this.connection.noteMuting;
|
||||||
|
}
|
||||||
|
|
||||||
protected get followingChannels() {
|
protected get followingChannels() {
|
||||||
return this.connection.followingChannels;
|
return this.connection.followingChannels;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +79,9 @@ export default abstract class Channel {
|
||||||
// 流れてきたNoteがリノートをミュートしてるユーザが行ったもの
|
// 流れてきたNoteがリノートをミュートしてるユーザが行ったもの
|
||||||
if (isRenotePacked(note) && !isQuotePacked(note) && this.userIdsWhoMeMutingRenotes.has(note.user.id)) return true;
|
if (isRenotePacked(note) && !isQuotePacked(note) && this.userIdsWhoMeMutingRenotes.has(note.user.id)) return true;
|
||||||
|
|
||||||
|
// 流れてきたNoteがミュートしているNoteに関わる(ミュートしたノートがリノートされた or リプライがついた時)
|
||||||
|
if (isMutingNoteRelated(note, this.noteMuting)) return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1690,6 +1690,10 @@ declare namespace entities {
|
||||||
NotesLocalTimelineResponse,
|
NotesLocalTimelineResponse,
|
||||||
NotesMentionsRequest,
|
NotesMentionsRequest,
|
||||||
NotesMentionsResponse,
|
NotesMentionsResponse,
|
||||||
|
NotesMutingCreateRequest,
|
||||||
|
NotesMutingDeleteRequest,
|
||||||
|
NotesMutingListResponse,
|
||||||
|
NotesMutingUpdateRequest,
|
||||||
NotesPollsRecommendationRequest,
|
NotesPollsRecommendationRequest,
|
||||||
NotesPollsRecommendationResponse,
|
NotesPollsRecommendationResponse,
|
||||||
NotesPollsVoteRequest,
|
NotesPollsVoteRequest,
|
||||||
|
@ -2740,6 +2744,18 @@ type NotesMentionsRequest = operations['notes___mentions']['requestBody']['conte
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type NotesMentionsResponse = operations['notes___mentions']['responses']['200']['content']['application/json'];
|
type NotesMentionsResponse = operations['notes___mentions']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type NotesMutingCreateRequest = operations['notes___muting___create']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type NotesMutingDeleteRequest = operations['notes___muting___delete']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type NotesMutingListResponse = operations['notes___muting___list']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type NotesMutingUpdateRequest = operations['notes___muting___update']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json'];
|
type NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
|
|
@ -3332,6 +3332,50 @@ declare module '../api.js' {
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:account*
|
||||||
|
*/
|
||||||
|
request<E extends 'notes/muting/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:account*
|
||||||
|
*/
|
||||||
|
request<E extends 'notes/muting/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:account*
|
||||||
|
*/
|
||||||
|
request<E extends 'notes/muting/list', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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*
|
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||||
*/
|
*/
|
||||||
request<E extends 'notes/polls/recommendation', P extends Endpoints[E]['req']>(
|
request<E extends 'notes/polls/recommendation', P extends Endpoints[E]['req']>(
|
||||||
|
|
|
@ -449,6 +449,10 @@ import type {
|
||||||
NotesLocalTimelineResponse,
|
NotesLocalTimelineResponse,
|
||||||
NotesMentionsRequest,
|
NotesMentionsRequest,
|
||||||
NotesMentionsResponse,
|
NotesMentionsResponse,
|
||||||
|
NotesMutingCreateRequest,
|
||||||
|
NotesMutingDeleteRequest,
|
||||||
|
NotesMutingListResponse,
|
||||||
|
NotesMutingUpdateRequest,
|
||||||
NotesPollsRecommendationRequest,
|
NotesPollsRecommendationRequest,
|
||||||
NotesPollsRecommendationResponse,
|
NotesPollsRecommendationResponse,
|
||||||
NotesPollsVoteRequest,
|
NotesPollsVoteRequest,
|
||||||
|
@ -886,6 +890,10 @@ export type Endpoints = {
|
||||||
'notes/hybrid-timeline': { req: NotesHybridTimelineRequest; res: NotesHybridTimelineResponse };
|
'notes/hybrid-timeline': { req: NotesHybridTimelineRequest; res: NotesHybridTimelineResponse };
|
||||||
'notes/local-timeline': { req: NotesLocalTimelineRequest; res: NotesLocalTimelineResponse };
|
'notes/local-timeline': { req: NotesLocalTimelineRequest; res: NotesLocalTimelineResponse };
|
||||||
'notes/mentions': { req: NotesMentionsRequest; res: NotesMentionsResponse };
|
'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/polls/recommendation': { req: NotesPollsRecommendationRequest; res: NotesPollsRecommendationResponse };
|
'notes/polls/recommendation': { req: NotesPollsRecommendationRequest; res: NotesPollsRecommendationResponse };
|
||||||
'notes/polls/vote': { req: NotesPollsVoteRequest; res: EmptyResponse };
|
'notes/polls/vote': { req: NotesPollsVoteRequest; res: EmptyResponse };
|
||||||
'notes/reactions': { req: NotesReactionsRequest; res: NotesReactionsResponse };
|
'notes/reactions': { req: NotesReactionsRequest; res: NotesReactionsResponse };
|
||||||
|
|
|
@ -452,6 +452,10 @@ export type NotesLocalTimelineRequest = operations['notes___local-timeline']['re
|
||||||
export type NotesLocalTimelineResponse = operations['notes___local-timeline']['responses']['200']['content']['application/json'];
|
export type NotesLocalTimelineResponse = operations['notes___local-timeline']['responses']['200']['content']['application/json'];
|
||||||
export type NotesMentionsRequest = operations['notes___mentions']['requestBody']['content']['application/json'];
|
export type NotesMentionsRequest = operations['notes___mentions']['requestBody']['content']['application/json'];
|
||||||
export type NotesMentionsResponse = operations['notes___mentions']['responses']['200']['content']['application/json'];
|
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 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 NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json'];
|
||||||
export type NotesPollsRecommendationResponse = operations['notes___polls___recommendation']['responses']['200']['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'];
|
export type NotesPollsVoteRequest = operations['notes___polls___vote']['requestBody']['content']['application/json'];
|
||||||
|
|
Loading…
Reference in New Issue