merge: Fix note create/edit validation to prevent quote loops and other invalid notes (resolves #1194) (!1230)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1230

Closes #1194

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
Marie 2025-11-03 23:17:12 +01:00
commit 02e80f6ea6
7 changed files with 158 additions and 175 deletions

View file

@ -57,6 +57,8 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
import { LatestNoteService } from '@/core/LatestNoteService.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
import { CacheService } from '@/core/CacheService.js';
import { NoteVisibilityService } from '@/core/NoteVisibilityService.js';
import { isPureRenote } from '@/misc/is-renote.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -223,6 +225,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private userBlockingService: UserBlockingService,
private cacheService: CacheService,
private latestNoteService: LatestNoteService,
private readonly noteVisibilityService: NoteVisibilityService,
) {
this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount);
}
@ -284,6 +287,10 @@ export class NoteCreateService implements OnApplicationShutdown {
}
if (data.renote) {
if (isPureRenote(data.renote)) {
throw new IdentifiableError('fd4cc33e-2a37-48dd-99cc-9b806eb2031a', 'Cannot renote a pure renote (boost)');
}
switch (data.renote.visibility) {
case 'public':
// public noteは無条件にrenote可能
@ -297,7 +304,7 @@ export class NoteCreateService implements OnApplicationShutdown {
case 'followers':
// 他人のfollowers noteはreject
if (data.renote.userId !== user.id) {
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
}
// Renote対象がfollowersならfollowersにする
@ -305,25 +312,55 @@ export class NoteCreateService implements OnApplicationShutdown {
break;
case 'specified':
// specified / direct noteはreject
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
}
if (data.renote.userId !== user.id) {
// Check local-only
if (data.renote.localOnly && user.host != null) {
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot renote a local-only note');
}
// Check visibility
const visibilityCheck = await this.noteVisibilityService.checkNoteVisibilityAsync(data.renote, user.id);
if (!visibilityCheck.accessible) {
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Cannot renote an invisible note');
}
// Check blocking
if (await this.userBlockingService.checkBlocked(data.renote.userId, user.id)) {
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked');
}
}
}
if (data.reply) {
if (isPureRenote(data.reply)) {
throw new IdentifiableError('3ac74a84-8fd5-4bb0-870f-01804f82ce15', 'Cannot reply to a pure renote (boost)');
}
if (data.reply.userId !== user.id) {
// Check local-only
if (data.reply.localOnly && user.host != null) {
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot reply to a local-only note');
}
// Check visibility
const visibilityCheck = await this.noteVisibilityService.checkNoteVisibilityAsync(data.reply, user.id);
if (!visibilityCheck.accessible) {
throw new IdentifiableError('b98980fa-3780-406c-a935-b6d0eeee10d1', 'Cannot reply to an invisible note');
}
// Check blocking
if (await this.userBlockingService.checkBlocked(data.reply.userId, user.id)) {
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Reply target is blocked');
}
}
}
// Check quote permissions
await this.checkQuotePermissions(data, user);
// Check blocking
if (this.isRenote(data) && !this.isQuote(data)) {
if (data.renote.userHost === null) {
if (data.renote.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
if (blocked) {
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked');
}
}
}
}
// 返信対象がpublicではないならhomeにする
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
data.visibility = 'home';
@ -481,14 +518,6 @@ export class NoteCreateService implements OnApplicationShutdown {
mandatoryCW: data.mandatoryCW,
});
// should really not happen, but better safe than sorry
if (data.reply?.id === insert.id) {
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t reply to itself');
}
if (data.renote?.id === insert.id) {
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t renote itself');
}
if (data.uri != null) insert.uri = data.uri;
if (data.url != null) insert.url = data.url;

View file

@ -52,6 +52,8 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
import { LatestNoteService } from '@/core/LatestNoteService.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import { NoteVisibilityService } from '@/core/NoteVisibilityService.js';
import { isPureRenote } from '@/misc/is-renote.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited';
@ -220,6 +222,7 @@ export class NoteEditService implements OnApplicationShutdown {
private cacheService: CacheService,
private latestNoteService: LatestNoteService,
private noteCreateService: NoteCreateService,
private readonly noteVisibilityService: NoteVisibilityService,
) {
this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount);
}
@ -303,8 +306,8 @@ export class NoteEditService implements OnApplicationShutdown {
}
if (this.isRenote(data)) {
if (data.renote.id === oldnote.id) {
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', `edit failed for ${oldnote.id}: cannot renote itself`);
if (isPureRenote(data.renote)) {
throw new IdentifiableError('fd4cc33e-2a37-48dd-99cc-9b806eb2031a', 'Cannot renote a pure renote (boost)');
}
switch (data.renote.visibility) {
@ -320,7 +323,7 @@ export class NoteEditService implements OnApplicationShutdown {
case 'followers':
// 他人のfollowers noteはreject
if (data.renote.userId !== user.id) {
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
}
// Renote対象がfollowersならfollowersにする
@ -328,25 +331,45 @@ export class NoteEditService implements OnApplicationShutdown {
break;
case 'specified':
// specified / direct noteはreject
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
}
if (data.renote.userId !== user.id) {
// Check local-only
if (data.renote.localOnly && user.host != null) {
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot renote a local-only note');
}
// Check visibility
const visibilityCheck = await this.noteVisibilityService.checkNoteVisibilityAsync(data.renote, user.id);
if (!visibilityCheck.accessible) {
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Cannot renote an invisible note');
}
// Check blocking
if (await this.userBlockingService.checkBlocked(data.renote.userId, user.id)) {
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked');
}
}
// Check for recursion
if (data.renote.id === oldnote.id) {
throw new IdentifiableError('33510210-8452-094c-6227-4a6c05d99f02', `edit failed for ${oldnote.id}: note cannot quote itself`);
}
for (let nextRenoteId = data.renote.renoteId; nextRenoteId != null;) {
if (nextRenoteId === oldnote.id) {
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', `edit failed for ${oldnote.id}: note cannot quote a quote of itself`);
}
// TODO create something like threadId but for quotes, that way we don't need full recursion
const next = await this.notesRepository.findOne({ where: { id: nextRenoteId }, select: { renoteId: true } });
nextRenoteId = next?.renoteId ?? null;
}
}
// Check quote permissions
await this.noteCreateService.checkQuotePermissions(data, user);
// Check blocking
if (this.isRenote(data) && !this.isQuote(data)) {
if (data.renote.userHost === null) {
if (data.renote.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
if (blocked) {
throw new Error('blocked');
}
}
}
}
// 返信対象がpublicではないならhomeにする
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
data.visibility = 'home';

View file

@ -42,6 +42,19 @@ export class ApAudienceService {
others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))),
)).filter(x => x != null);
// If no audience is specified, then assume public
if (
toGroups.public.length === 0 && toGroups.followers.length === 0 &&
ccGroups.public.length === 0 && ccGroups.followers.length === 0 &&
others.length === 0
) {
return {
visibility: 'public',
mentionedUsers: [],
visibleUsers: [],
};
}
if (toGroups.public.length > 0) {
return {
visibility: 'public',

View file

@ -239,15 +239,12 @@ export class ApNoteService {
}
const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
let visibility = noteAudience.visibility;
const visibility = noteAudience.visibility;
const visibleUsers = noteAudience.visibleUsers;
// Audience (to, cc) が指定されてなかった場合
if (visibility === 'specified' && visibleUsers.length === 0) {
if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している
// こちらから匿名GET出来たものならばpublic
visibility = 'public';
}
throw new IdentifiableError('dc2ad0d1-36bf-41f5-8e4c-a4d265a28387', `failed to create note ${entryUri}: could not resolve any recipients`);
}
const processErrors: string[] = [];
@ -283,13 +280,6 @@ export class ApNoteService {
processErrors.push('quoteUnavailable');
}
if (reply && reply.userHost == null && reply.localOnly) {
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot reply to local-only note');
}
if (quote && quote.userHost == null && quote.localOnly) {
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot quote a local-only note');
}
// vote
if (reply && reply.hasPoll) {
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
@ -430,15 +420,12 @@ export class ApNoteService {
//#endregion
const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
let visibility = noteAudience.visibility;
const visibility = noteAudience.visibility;
const visibleUsers = noteAudience.visibleUsers;
// Audience (to, cc) が指定されてなかった場合
if (visibility === 'specified' && visibleUsers.length === 0) {
if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している
// こちらから匿名GET出来たものならばpublic
visibility = 'public';
}
throw new IdentifiableError('dc2ad0d1-36bf-41f5-8e4c-a4d265a28387', `failed to create note ${entryUri}: could not resolve any recipients`);
}
const processErrors: string[] = [];
@ -472,10 +459,6 @@ export class ApNoteService {
processErrors.push('quoteUnavailable');
}
if (quote && quote.userHost == null && quote.localOnly) {
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot quote a local-only note');
}
// vote
if (reply && reply.hasPoll) {
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });

View file

@ -19,7 +19,6 @@ import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import { QueryService } from '@/core/QueryService.js';
import type { Config } from '@/config.js';
import { NoteVisibilityService } from '@/core/NoteVisibilityService.js';
import type { PopulatedNote } from '@/core/NoteVisibilityService.js';
import type { NoteVisibilityData } from '@/core/NoteVisibilityService.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CacheService } from '../CacheService.js';
@ -508,6 +507,8 @@ export class NoteEntityService implements OnModuleInit {
me?: { id: MiUser['id'] } | null | undefined,
options?: {
detail?: boolean;
recurseReply?: boolean; // Defaults to the value of detail, which defaults to true.
recurseRenote?: boolean; // Defaults to the value of detail, which defaults to true.
skipHide?: boolean;
withReactionAndUserPairCache?: boolean;
bypassSilence?: boolean;
@ -535,6 +536,8 @@ export class NoteEntityService implements OnModuleInit {
skipHide: false,
withReactionAndUserPairCache: false,
}, options);
opts.recurseRenote ??= opts.detail;
opts.recurseReply ??= opts.detail;
const meId = me ? me.id : null;
const note = typeof src === 'object' ? src : await this.noteLoader.load(src);
@ -647,27 +650,30 @@ export class NoteEntityService implements OnModuleInit {
...(opts.detail ? {
clippedCount: note.clippedCount,
processErrors: note.processErrors,
reply: note.replyId ? this.pack(note.reply ?? opts._hint_?.notes.get(note.replyId) ?? note.replyId, me, {
detail: false,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
// Don't silence target of self-reply, since the outer note will already be silenced.
bypassSilence: bypassSilence || note.userId === note.replyUserId,
}) : undefined,
renote: note.renoteId ? this.pack(note.renote ?? opts._hint_?.notes.get(note.renoteId) ?? note.renoteId, me, {
detail: true,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
// Don't silence target of self-renote, since the outer note will already be silenced.
bypassSilence: bypassSilence || note.userId === note.renoteUserId,
}) : undefined,
} : {}),
reply: opts.recurseReply && note.replyId ? this.pack(note.reply ?? opts._hint_?.notes.get(note.replyId) ?? note.replyId, me, {
detail: false,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
// Don't silence target of self-reply, since the outer note will already be silenced.
bypassSilence: bypassSilence || note.userId === note.replyUserId,
}) : undefined,
// The renote target needs to be packed with the reply, but we *must not* recurse any further.
// Pass detail=false and recurseReply=true to make sure we only include the right data.
renote: opts.recurseRenote && note.renoteId ? this.pack(note.renote ?? opts._hint_?.notes.get(note.renoteId) ?? note.renoteId, me, {
detail: false,
recurseReply: true,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
// Don't silence target of self-renote, since the outer note will already be silenced.
bypassSilence: bypassSilence || note.userId === note.renoteUserId,
}) : undefined,
});
this.noteVisibilityService.syncVisibility(packed);

View file

@ -18,7 +18,6 @@ import { NoteCreateService } from '@/core/NoteCreateService.js';
import { DI } from '@/di-symbols.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { NoteVisibilityService } from '@/core/NoteVisibilityService.js';
import { ApiError } from '../../error.js';
export const meta = {
@ -262,7 +261,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteEntityService: NoteEntityService,
private noteCreateService: NoteCreateService,
private readonly noteVisibilityService: NoteVisibilityService,
) {
super(meta, paramDef, async (ps, me) => {
if (ps.text && ps.text.length > this.config.maxNoteLength) {
@ -303,31 +301,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget);
} else if (isRenote(renote) && !isQuote(renote)) {
throw new ApiError(meta.errors.cannotReRenote);
} else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(renote, me)).accessible) {
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
}
// Check blocking
if (renote.userId !== me.id) {
const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: renote.userId,
blockeeId: me.id,
},
});
if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
if (renote.visibility === 'followers' && renote.userId !== me.id) {
// 他人のfollowers noteはreject
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
} else if (renote.visibility === 'specified') {
// specified / direct noteはreject
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
}
if (renote.channelId && renote.channelId !== ps.channelId) {
@ -351,26 +324,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
} else if (isRenote(reply) && !isQuote(reply)) {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
} else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(reply, me)).accessible) {
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
} else if (reply.visibility === 'specified' && ps.visibility !== 'specified') {
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
}
// Check blocking
if (reply.userId !== me.id) {
const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: reply.userId,
blockeeId: me.id,
},
});
if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
}
if (ps.poll) {
@ -428,6 +384,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.containsTooManyMentions);
} else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') {
throw new ApiError(meta.errors.quoteDisabledForUser);
} else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') {
throw new ApiError(meta.errors.cannotReRenote);
} else if (e.id === 'b6352a84-e5cd-4b05-a26c-63437a6b98ba') {
throw new ApiError(meta.errors.youHaveBeenBlocked);
} else if (e.id === 'be9529e9-fe72-4de0-ae43-0b363c4938af') {
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
} else if (e.id === '3ac74a84-8fd5-4bb0-870f-01804f82ce15') {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
} else if (e.id === 'b98980fa-3780-406c-a935-b6d0eeee10d1') {
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
}
}
throw e;

View file

@ -17,7 +17,6 @@ import { NoteEditService } from '@/core/NoteEditService.js';
import { DI } from '@/di-symbols.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { NoteVisibilityService } from '@/core/NoteVisibilityService.js';
import { ApiError } from '../../error.js';
export const meta = {
@ -312,7 +311,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteEntityService: NoteEntityService,
private noteEditService: NoteEditService,
private readonly noteVisibilityService: NoteVisibilityService,
) {
super(meta, paramDef, async (ps, me) => {
if (ps.text && ps.text.length > this.config.maxNoteLength) {
@ -347,44 +345,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
let renote: MiNote | null = null;
if (ps.renoteId === ps.editId) {
throw new ApiError(meta.errors.cannotQuoteCurrentPost);
}
if (ps.renoteId != null) {
// Fetch renote to note
renote = await this.notesRepository.findOneBy({ id: ps.renoteId });
if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget);
} else if (isRenote(renote) && !isQuote(renote)) {
throw new ApiError(meta.errors.cannotReRenote);
}
if (renote.renoteId === ps.editId) {
throw new ApiError(meta.errors.cannotQuoteaQuoteOfCurrentPost);
}
// Check blocking
if (renote.userId !== me.id) {
const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: renote.userId,
blockeeId: me.id,
},
});
if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
if (renote.visibility === 'followers' && renote.userId !== me.id) {
// 他人のfollowers noteはreject
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
} else if (renote.visibility === 'specified') {
// specified / direct noteはreject
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
}
if (renote.channelId && renote.channelId !== ps.channelId) {
@ -408,26 +374,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
} else if (isRenote(reply) && !isQuote(reply)) {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
} else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(reply, me)).accessible) {
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
} else if (reply.visibility === 'specified' && ps.visibility !== 'specified') {
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
}
// Check blocking
if (reply.userId !== me.id) {
const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: reply.userId,
blockeeId: me.id,
},
});
if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
}
if (ps.poll) {
@ -483,6 +432,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.containsTooManyMentions);
} else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') {
throw new ApiError(meta.errors.quoteDisabledForUser);
} else if (e.id === '33510210-8452-094c-6227-4a6c05d99f02') {
throw new ApiError(meta.errors.cannotQuoteCurrentPost);
} else if (e.id === 'ea93b7c2-3d6c-4e10-946b-00d50b1a75cb') {
throw new ApiError(meta.errors.cannotQuoteaQuoteOfCurrentPost);
} else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') {
throw new ApiError(meta.errors.cannotReRenote);
} else if (e.id === 'b6352a84-e5cd-4b05-a26c-63437a6b98ba') {
throw new ApiError(meta.errors.youHaveBeenBlocked);
} else if (e.id === 'be9529e9-fe72-4de0-ae43-0b363c4938af') {
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
} else if (e.id === '3ac74a84-8fd5-4bb0-870f-01804f82ce15') {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
} else if (e.id === 'b98980fa-3780-406c-a935-b6d0eeee10d1') {
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
}
}
throw e;