fix(backend): fix creating reactions bugs (#13901)
* fix(backend): add fallback for empty string when creating reaction * fix(backend): prohibit reactions to Renote * test(backend): add some tests for `notes/reactions/create` endpoint * Update CHANGELOG.md * lint * Update CHANGELOG.md --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
00b213373b
commit
961cb6c5ee
4 changed files with 77 additions and 1 deletions
|
@ -25,6 +25,8 @@
|
||||||
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
||||||
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
||||||
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
||||||
|
- Fix: 空文字列のリアクションはフォールバックされるように
|
||||||
|
- Fix: リノートにリアクションできないように
|
||||||
|
|
||||||
## 2024.5.0
|
## 2024.5.0
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
|
|
||||||
const FALLBACK = '\u2764';
|
const FALLBACK = '\u2764';
|
||||||
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||||
|
@ -117,11 +118,16 @@ export class ReactionService {
|
||||||
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if note is Renote
|
||||||
|
if (isRenote(note) && !isQuote(note)) {
|
||||||
|
throw new IdentifiableError('12c35529-3c79-4327-b1cc-e2cf63a71925', 'You cannot react to Renote.');
|
||||||
|
}
|
||||||
|
|
||||||
let reaction = _reaction ?? FALLBACK;
|
let reaction = _reaction ?? FALLBACK;
|
||||||
|
|
||||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
||||||
reaction = '\u2764';
|
reaction = '\u2764';
|
||||||
} else if (_reaction) {
|
} else if (_reaction != null) {
|
||||||
const custom = reaction.match(isCustomEmojiRegexp);
|
const custom = reaction.match(isCustomEmojiRegexp);
|
||||||
if (custom) {
|
if (custom) {
|
||||||
const reacterHost = this.utilityService.toPunyNullable(user.host);
|
const reacterHost = this.utilityService.toPunyNullable(user.host);
|
||||||
|
|
|
@ -36,6 +36,12 @@ export const meta = {
|
||||||
code: 'YOU_HAVE_BEEN_BLOCKED',
|
code: 'YOU_HAVE_BEEN_BLOCKED',
|
||||||
id: '20ef5475-9f38-4e4c-bd33-de6d979498ec',
|
id: '20ef5475-9f38-4e4c-bd33-de6d979498ec',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cannotReactToRenote: {
|
||||||
|
message: 'You cannot react to Renote.',
|
||||||
|
code: 'CANNOT_REACT_TO_RENOTE',
|
||||||
|
id: 'eaccdc08-ddef-43fe-908f-d108faad57f5',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -62,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
await this.reactionService.create(me, note, ps.reaction).catch(err => {
|
await this.reactionService.create(me, note, ps.reaction).catch(err => {
|
||||||
if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
|
if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
|
||||||
if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked);
|
if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||||
|
if (err.id === '12c35529-3c79-4327-b1cc-e2cf63a71925') throw new ApiError(meta.errors.cannotReactToRenote);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -266,6 +266,67 @@ describe('Endpoints', () => {
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('リノートにリアクションできない', async () => {
|
||||||
|
const bobNote = await post(bob, { text: 'hi' });
|
||||||
|
const bobRenote = await post(bob, { renoteId: bobNote.id });
|
||||||
|
|
||||||
|
const res = await api('notes/reactions/create', {
|
||||||
|
noteId: bobRenote.id,
|
||||||
|
reaction: '🚀',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.status, 400);
|
||||||
|
assert.strictEqual(res.body.error.code, 'CANNOT_REACT_TO_RENOTE');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('引用にリアクションできる', async () => {
|
||||||
|
const bobNote = await post(bob, { text: 'hi' });
|
||||||
|
const bobRenote = await post(bob, { text: 'hi again', renoteId: bobNote.id });
|
||||||
|
|
||||||
|
const res = await api('notes/reactions/create', {
|
||||||
|
noteId: bobRenote.id,
|
||||||
|
reaction: '🚀',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.status, 204);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('空文字列のリアクションは\u2764にフォールバックされる', async () => {
|
||||||
|
const bobNote = await post(bob, { text: 'hi' });
|
||||||
|
|
||||||
|
const res = await api('notes/reactions/create', {
|
||||||
|
noteId: bobNote.id,
|
||||||
|
reaction: '',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.status, 204);
|
||||||
|
|
||||||
|
const reaction = await api('notes/reactions', {
|
||||||
|
noteId: bobNote.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(reaction.body.length, 1);
|
||||||
|
assert.strictEqual(reaction.body[0].type, '\u2764');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('絵文字ではない文字列のリアクションは\u2764にフォールバックされる', async () => {
|
||||||
|
const bobNote = await post(bob, { text: 'hi' });
|
||||||
|
|
||||||
|
const res = await api('notes/reactions/create', {
|
||||||
|
noteId: bobNote.id,
|
||||||
|
reaction: 'Hello!',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.status, 204);
|
||||||
|
|
||||||
|
const reaction = await api('notes/reactions', {
|
||||||
|
noteId: bobNote.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(reaction.body.length, 1);
|
||||||
|
assert.strictEqual(reaction.body[0].type, '\u2764');
|
||||||
|
});
|
||||||
|
|
||||||
test('空のパラメータで怒られる', async () => {
|
test('空のパラメータで怒られる', async () => {
|
||||||
// @ts-expect-error param must not be empty
|
// @ts-expect-error param must not be empty
|
||||||
const res = await api('notes/reactions/create', {}, alice);
|
const res = await api('notes/reactions/create', {}, alice);
|
||||||
|
|
Loading…
Reference in a new issue