summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/components/note.vue8
-rw-r--r--src/client/components/notification.vue2
-rw-r--r--src/client/components/reaction-icon.vue6
-rw-r--r--src/client/components/reactions-viewer.reaction.vue2
-rw-r--r--src/misc/reaction-lib.ts59
-rw-r--r--src/models/entities/note-reaction.ts2
-rw-r--r--src/models/repositories/note.ts68
-rw-r--r--src/remote/activitypub/kernel/like.ts4
-rw-r--r--src/remote/activitypub/renderer/like.ts34
-rw-r--r--src/services/note/reaction/create.ts27
-rw-r--r--src/services/note/reaction/delete.ts2
11 files changed, 173 insertions, 41 deletions
diff --git a/src/client/components/note.vue b/src/client/components/note.vue
index 18d5cc34ba..a39520fb4c 100644
--- a/src/client/components/note.vue
+++ b/src/client/components/note.vue
@@ -301,6 +301,14 @@ export default Vue.extend({
case 'reacted': {
const reaction = body.reaction;
+ if (body.emoji) {
+ const emojis = this.appearNote.emojis || [];
+ if (!emojis.includes(body.emoji)) {
+ emojis.push(body.emoji);
+ Vue.set(this.appearNote, 'emojis', emojis);
+ }
+ }
+
if (this.appearNote.reactions == null) {
Vue.set(this.appearNote, 'reactions', {});
}
diff --git a/src/client/components/notification.vue b/src/client/components/notification.vue
index f415887e76..97a2fe1873 100644
--- a/src/client/components/notification.vue
+++ b/src/client/components/notification.vue
@@ -12,7 +12,7 @@
<fa :icon="faReply" v-else-if="notification.type === 'reply'"/>
<fa :icon="faAt" v-else-if="notification.type === 'mention'"/>
<fa :icon="faQuoteLeft" v-else-if="notification.type === 'quote'"/>
- <x-reaction-icon v-else-if="notification.type === 'reaction'" :reaction="notification.reaction" :no-style="true"/>
+ <x-reaction-icon v-else-if="notification.type === 'reaction'" :reaction="notification.reaction" :customEmojis="notification.note.emojis" :no-style="true"/>
</div>
</div>
<div class="tail">
diff --git a/src/client/components/reaction-icon.vue b/src/client/components/reaction-icon.vue
index 9155c59440..3c6d56b80a 100644
--- a/src/client/components/reaction-icon.vue
+++ b/src/client/components/reaction-icon.vue
@@ -1,5 +1,5 @@
<template>
-<mk-emoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :is-reaction="true" :normal="true" :no-style="noStyle"/>
+<mk-emoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :customEmojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/>
</template>
<script lang="ts">
@@ -12,6 +12,10 @@ export default Vue.extend({
type: String,
required: true
},
+ customEmojis: {
+ required: false,
+ default: () => []
+ },
noStyle: {
type: Boolean,
required: false,
diff --git a/src/client/components/reactions-viewer.reaction.vue b/src/client/components/reactions-viewer.reaction.vue
index 7494fde25f..67774cbb39 100644
--- a/src/client/components/reactions-viewer.reaction.vue
+++ b/src/client/components/reactions-viewer.reaction.vue
@@ -9,7 +9,7 @@
ref="reaction"
v-particle
>
- <x-reaction-icon :reaction="reaction" ref="icon"/>
+ <x-reaction-icon :reaction="reaction" :customEmojis="note.emojis" ref="icon"/>
<span>{{ count }}</span>
</button>
</template>
diff --git a/src/misc/reaction-lib.ts b/src/misc/reaction-lib.ts
index 43dbe1cc2c..e5da5ca4aa 100644
--- a/src/misc/reaction-lib.ts
+++ b/src/misc/reaction-lib.ts
@@ -1,6 +1,7 @@
import { emojiRegex } from './emoji-regex';
import { fetchMeta } from './fetch-meta';
import { Emojis } from '../models';
+import { toPunyNullable } from './convert-host';
const legacies: Record<string, string> = {
'like': '👍',
@@ -40,12 +41,20 @@ export function convertLegacyReactions(reactions: Record<string, number>) {
}
}
- return _reactions;
+ const _reactions2 = {} as Record<string, number>;
+
+ for (const reaction of Object.keys(_reactions)) {
+ _reactions2[decodeReaction(reaction).reaction] = _reactions[reaction];
+ }
+
+ return _reactions2;
}
-export async function toDbReaction(reaction?: string | null): Promise<string> {
+export async function toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> {
if (reaction == null) return await getFallbackReaction();
+ reacterHost = toPunyNullable(reacterHost);
+
// 文字列タイプのリアクションを絵文字に変換
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
@@ -61,18 +70,58 @@ export async function toDbReaction(reaction?: string | null): Promise<string> {
const custom = reaction.match(/^:([\w+-]+):$/);
if (custom) {
+ const name = custom[1];
const emoji = await Emojis.findOne({
- host: null,
- name: custom[1],
+ host: reacterHost || null,
+ name,
});
- if (emoji) return reaction;
+ if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`
}
return await getFallbackReaction();
}
+type DecodedReaction = {
+ /**
+ * リアクション名 (Unicode Emoji or ':name@hostname' or ':name@.')
+ */
+ reaction: string;
+
+ /**
+ * name (カスタム絵文字の場合name, Emojiクエリに使う)
+ */
+ name?: string;
+
+ /**
+ * host (カスタム絵文字の場合host, Emojiクエリに使う)
+ */
+ host?: string | null;
+};
+
+export function decodeReaction(str: string): DecodedReaction {
+ const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/);
+
+ if (custom) {
+ const name = custom[1];
+ const host = custom[2] || null;
+
+ return {
+ reaction: `:${name}@${host || '.'}:`, // ローカル分は@以降を省略するのではなく.にする
+ name,
+ host
+ };
+ }
+
+ return {
+ reaction: str,
+ name: undefined,
+ host: undefined
+ };
+}
+
export function convertLegacyReaction(reaction: string): string {
+ reaction = decodeReaction(reaction).reaction;
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
return reaction;
}
diff --git a/src/models/entities/note-reaction.ts b/src/models/entities/note-reaction.ts
index 995748760c..ed38450bb2 100644
--- a/src/models/entities/note-reaction.ts
+++ b/src/models/entities/note-reaction.ts
@@ -36,7 +36,7 @@ export class NoteReaction {
public note: Note | null;
@Column('varchar', {
- length: 130
+ length: 260
})
public reaction: string;
}
diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts
index 2aad5c0fa3..d29a48b7ec 100644
--- a/src/models/repositories/note.ts
+++ b/src/models/repositories/note.ts
@@ -5,9 +5,11 @@ import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls
import { ensure } from '../../prelude/ensure';
import { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
-import { convertLegacyReaction, convertLegacyReactions } from '../../misc/reaction-lib';
+import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '../../misc/reaction-lib';
import { toString } from '../../mfm/toString';
import { parse } from '../../mfm/parse';
+import { Emoji } from '../entities/emoji';
+import { concat } from '../../prelude/array';
export type PackedNote = SchemaType<typeof packedNoteSchema>;
@@ -129,31 +131,61 @@ export class NoteRepository extends Repository<Note> {
};
}
+ /**
+ * 添付用emojisを解決する
+ * @param emojiNames Note等に添付されたカスタム絵文字名 (:は含めない)
+ * @param noteUserHost Noteのホスト
+ * @param reactionNames Note等にリアクションされたカスタム絵文字名 (:は含めない)
+ */
async function populateEmojis(emojiNames: string[], noteUserHost: string | null, reactionNames: string[]) {
- const where = [] as {}[];
+ let all = [] as {
+ name: string,
+ url: string
+ }[];
+ // カスタム絵文字
if (emojiNames?.length > 0) {
- where.push({
- name: In(emojiNames),
- host: noteUserHost
- });
+ const tmp = await Emojis.find({
+ where: {
+ name: In(emojiNames),
+ host: noteUserHost
+ },
+ select: ['name', 'host', 'url']
+ }).then(emojis => emojis.map((emoji: Emoji) => {
+ return {
+ name: emoji.name,
+ url: emoji.url,
+ };
+ }));
+
+ all = concat([all, tmp]);
}
- reactionNames = reactionNames?.filter(x => x.match(/^:[^:]+:$/)).map(x => x.replace(/:/g, ''));
+ const customReactions = reactionNames?.map(x => decodeReaction(x)).filter(x => x.name);
- if (reactionNames?.length > 0) {
- where.push({
- name: In(reactionNames),
- host: null
- });
- }
+ if (customReactions?.length > 0) {
+ const where = [] as {}[];
- if (where.length === 0) return [];
+ for (const customReaction of customReactions) {
+ where.push({
+ name: customReaction.name,
+ host: customReaction.host
+ });
+ }
- return Emojis.find({
- where,
- select: ['name', 'host', 'url', 'aliases']
- });
+ const tmp = await Emojis.find({
+ where,
+ select: ['name', 'host', 'url']
+ }).then(emojis => emojis.map((emoji: Emoji) => {
+ return {
+ name: `${emoji.name}@${emoji.host || '.'}`, // @host付きでローカルは.
+ url: emoji.url,
+ };
+ }));
+ all = concat([all, tmp]);
+ }
+
+ return all;
}
async function populateMyReaction() {
diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts
index b25f80aedc..a877110303 100644
--- a/src/remote/activitypub/kernel/like.ts
+++ b/src/remote/activitypub/kernel/like.ts
@@ -1,7 +1,7 @@
import { IRemoteUser } from '../../../models/entities/user';
import { ILike, getApId } from '../type';
import create from '../../../services/note/reaction/create';
-import { fetchNote } from '../models/note';
+import { fetchNote, extractEmojis } from '../models/note';
export default async (actor: IRemoteUser, activity: ILike) => {
const targetUri = getApId(activity.object);
@@ -11,6 +11,8 @@ export default async (actor: IRemoteUser, activity: ILike) => {
if (actor.id === note.userId) return `skip: cannot react to my note`;
+ await extractEmojis(activity.tag || [], actor.host).catch(() => null);
+
await create(actor, note, activity._misskey_reaction || activity.content || activity.name);
return `ok`;
};
diff --git a/src/remote/activitypub/renderer/like.ts b/src/remote/activitypub/renderer/like.ts
index e36a3ab0d6..d4dd3663d4 100644
--- a/src/remote/activitypub/renderer/like.ts
+++ b/src/remote/activitypub/renderer/like.ts
@@ -1,12 +1,30 @@
import config from '../../../config';
import { NoteReaction } from '../../../models/entities/note-reaction';
import { Note } from '../../../models/entities/note';
+import { Emojis } from '../../../models';
+import renderEmoji from './emoji';
-export const renderLike = (noteReaction: NoteReaction, note: Note) => ({
- type: 'Like',
- id: `${config.url}/likes/${noteReaction.id}`,
- actor: `${config.url}/users/${noteReaction.userId}`,
- object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
- content: noteReaction.reaction,
- _misskey_reaction: noteReaction.reaction
-});
+export const renderLike = async (noteReaction: NoteReaction, note: Note) => {
+ const reaction = noteReaction.reaction;
+
+ const object = {
+ type: 'Like',
+ id: `${config.url}/likes/${noteReaction.id}`,
+ actor: `${config.url}/users/${noteReaction.userId}`,
+ object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
+ content: reaction,
+ _misskey_reaction: reaction
+ } as any;
+
+ if (reaction.startsWith(':')) {
+ const name = reaction.replace(/:/g, '');
+ const emoji = await Emojis.findOne({
+ name,
+ host: null
+ });
+
+ if (emoji) object.tag = [ renderEmoji(emoji) ];
+ }
+
+ return object;
+};
diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts
index 72750affe0..70cb1adf4b 100644
--- a/src/services/note/reaction/create.ts
+++ b/src/services/note/reaction/create.ts
@@ -4,10 +4,10 @@ import { renderLike } from '../../../remote/activitypub/renderer/like';
import DeliverManager from '../../../remote/activitypub/deliver-manager';
import { renderActivity } from '../../../remote/activitypub/renderer';
import { IdentifiableError } from '../../../misc/identifiable-error';
-import { toDbReaction } from '../../../misc/reaction-lib';
+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, UserProfiles } from '../../../models';
+import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles, Emojis } from '../../../models';
import { Not } from 'typeorm';
import { perUserReactionsChart } from '../../chart';
import { genId } from '../../../misc/gen-id';
@@ -20,7 +20,7 @@ export default async (user: User, note: Note, reaction?: string) => {
throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note');
}
- reaction = await toDbReaction(reaction);
+ reaction = await toDbReaction(reaction, user.host);
const exist = await NoteReactions.findOne({
noteId: note.id,
@@ -59,8 +59,27 @@ export default async (user: User, note: Note, reaction?: string) => {
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: reaction,
+ emoji: emoji,
userId: user.id
});
@@ -96,7 +115,7 @@ export default async (user: User, note: Note, reaction?: string) => {
//#region 配信
if (Users.isLocalUser(user) && !note.localOnly) {
- const content = renderActivity(renderLike(inserted, note));
+ const content = renderActivity(await renderLike(inserted, note));
const dm = new DeliverManager(user, content);
if (note.userHost !== null) {
const reactee = await Users.findOne(note.userId)
diff --git a/src/services/note/reaction/delete.ts b/src/services/note/reaction/delete.ts
index 09566e07ba..fd6628c71f 100644
--- a/src/services/note/reaction/delete.ts
+++ b/src/services/note/reaction/delete.ts
@@ -44,7 +44,7 @@ export default async (user: User, note: Note) => {
//#region 配信
if (Users.isLocalUser(user) && !note.localOnly) {
- const content = renderActivity(renderUndo(renderLike(exist, note), user));
+ const content = renderActivity(renderUndo(await renderLike(exist, note), user));
const dm = new DeliverManager(user, content);
if (note.userHost !== null) {
const reactee = await Users.findOne(note.userId)