diff options
Diffstat (limited to 'packages/backend/src/services/note/reaction/create.ts')
| -rw-r--r-- | packages/backend/src/services/note/reaction/create.ts | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts new file mode 100644 index 0000000000..308bd4dff7 --- /dev/null +++ b/packages/backend/src/services/note/reaction/create.ts @@ -0,0 +1,135 @@ +import { publishNoteStream } from '@/services/stream'; +import { renderLike } from '@/remote/activitypub/renderer/like'; +import DeliverManager from '@/remote/activitypub/deliver-manager'; +import { renderActivity } from '@/remote/activitypub/renderer/index'; +import { toDbReaction, decodeReaction } from '@/misc/reaction-lib'; +import { User, IRemoteUser } from '@/models/entities/user'; +import { Note } from '@/models/entities/note'; +import { NoteReactions, Users, NoteWatchings, Notes, Emojis, Blockings } from '@/models/index'; +import { Not } from 'typeorm'; +import { perUserReactionsChart } from '@/services/chart/index'; +import { genId } from '@/misc/gen-id'; +import { createNotification } from '../../create-notification'; +import deleteReaction from './delete'; +import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; +import { NoteReaction } from '@/models/entities/note-reaction'; +import { IdentifiableError } from '@/misc/identifiable-error'; + +export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { + // Check blocking + if (note.userId !== user.id) { + const block = await Blockings.findOne({ + blockerId: note.userId, + blockeeId: user.id, + }); + if (block) { + throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7'); + } + } + + // TODO: cache + reaction = await toDbReaction(reaction, user.host); + + const record: NoteReaction = { + id: genId(), + createdAt: new Date(), + noteId: note.id, + userId: user.id, + reaction + }; + + // Create reaction + try { + await NoteReactions.insert(record); + } catch (e) { + if (isDuplicateKeyValueError(e)) { + const exists = await NoteReactions.findOneOrFail({ + noteId: note.id, + userId: user.id, + }); + + if (exists.reaction !== reaction) { + // 別のリアクションがすでにされていたら置き換える + await deleteReaction(user, note); + await NoteReactions.insert(record); + } else { + // 同じリアクションがすでにされていたらエラー + throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298'); + } + } else { + throw e; + } + } + + // Increment reactions count + const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`; + await Notes.createQueryBuilder().update() + .set({ + reactions: () => sql, + score: () => '"score" + 1' + }) + .where('id = :id', { id: note.id }) + .execute(); + + perUserReactionsChart.update(user, note); + + // カスタム絵文字リアクションだったら絵文字情報も送る + const decodedReaction = decodeReaction(reaction); + + let emoji = await Emojis.findOne({ + where: { + name: decodedReaction.name, + host: decodedReaction.host + }, + select: ['name', 'host', 'url'] + }); + + if (emoji) { + emoji = { + name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, + url: emoji.url + } as any; + } + + publishNoteStream(note.id, 'reacted', { + reaction: decodedReaction.reaction, + emoji: emoji, + userId: user.id + }); + + // リアクションされたユーザーがローカルユーザーなら通知を作成 + if (note.userHost === null) { + createNotification(note.userId, 'reaction', { + notifierId: user.id, + noteId: note.id, + reaction: reaction + }); + } + + // Fetch watchers + NoteWatchings.find({ + noteId: note.id, + userId: Not(user.id) + }).then(watchers => { + for (const watcher of watchers) { + createNotification(watcher.userId, 'reaction', { + notifierId: user.id, + noteId: note.id, + reaction: reaction + }); + } + }); + + //#region 配信 + if (Users.isLocalUser(user) && !note.localOnly) { + const content = renderActivity(await renderLike(record, note)); + const dm = new DeliverManager(user, content); + if (note.userHost !== null) { + const reactee = await Users.findOne(note.userId); + dm.addDirectRecipe(reactee as IRemoteUser); + } + dm.addFollowersRecipe(); + dm.execute(); + } + //#endregion +}; |