Custom reaction (#4517)
* Custom reaction * increase limit of reactions/delete * リアクションの場合は OS標準の絵文字を使用 を迂回する * カスタムリアクションを無効にする設定 * fix * disableCustomReaction --> enableEmojiReaction * Avoid MFM rendering * 🎨 * 🎨 * Auto accept * custom emoji reaction * Improve usability * Extract emojiRegex * Fix * Clean up * 🎨 * 🎨 * toDbReaction で reaction は必須に あとフォールバックは like に * Clean up * Make required * https://github.com/syuilo/misskey/pull/4517/commits/3eb08748feeaab9ee5c5b505c870f97d7edbeb0d#r266241728 * Refactor * Allow null
This commit is contained in:
		
							parent
							
								
									a5b12bac54
								
							
						
					
					
						commit
						2684541693
					
				
					 19 changed files with 278 additions and 44 deletions
				
			
		| 
						 | 
				
			
			@ -25,6 +25,7 @@
 | 
			
		|||
			<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
 | 
			
		||||
			<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
 | 
			
		||||
			<ui-info>{{ $t('disabling-timelines-info') }}</ui-info>
 | 
			
		||||
			<ui-switch v-model="enableEmojiReaction">{{ $t('enable-emoji-reaction') }}</ui-switch>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section class="fit-bottom">
 | 
			
		||||
			<header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
 | 
			
		||||
| 
						 | 
				
			
			@ -155,6 +156,7 @@ export default Vue.extend({
 | 
			
		|||
			disableRegistration: false,
 | 
			
		||||
			disableLocalTimeline: false,
 | 
			
		||||
			disableGlobalTimeline: false,
 | 
			
		||||
			enableEmojiReaction: true,
 | 
			
		||||
			mascotImageUrl: null,
 | 
			
		||||
			bannerUrl: null,
 | 
			
		||||
			errorImageUrl: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -206,6 +208,7 @@ export default Vue.extend({
 | 
			
		|||
			this.disableRegistration = meta.disableRegistration;
 | 
			
		||||
			this.disableLocalTimeline = meta.disableLocalTimeline;
 | 
			
		||||
			this.disableGlobalTimeline = meta.disableGlobalTimeline;
 | 
			
		||||
			this.enableEmojiReaction = meta.enableEmojiReaction;
 | 
			
		||||
			this.mascotImageUrl = meta.mascotImageUrl;
 | 
			
		||||
			this.bannerUrl = meta.bannerUrl;
 | 
			
		||||
			this.errorImageUrl = meta.errorImageUrl;
 | 
			
		||||
| 
						 | 
				
			
			@ -267,6 +270,7 @@ export default Vue.extend({
 | 
			
		|||
				disableRegistration: this.disableRegistration,
 | 
			
		||||
				disableLocalTimeline: this.disableLocalTimeline,
 | 
			
		||||
				disableGlobalTimeline: this.disableGlobalTimeline,
 | 
			
		||||
				enableEmojiReaction: this.enableEmojiReaction,
 | 
			
		||||
				mascotImageUrl: this.mascotImageUrl,
 | 
			
		||||
				bannerUrl: this.bannerUrl,
 | 
			
		||||
				errorImageUrl: this.errorImageUrl,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,11 @@ export default Vue.extend({
 | 
			
		|||
		customEmojis: {
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: () => []
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
		isReaction: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +50,7 @@ export default Vue.extend({
 | 
			
		|||
		},
 | 
			
		||||
 | 
			
		||||
		useOsDefaultEmojis(): boolean {
 | 
			
		||||
			return this.$store.state.device.useOsDefaultEmojis;
 | 
			
		||||
			return this.$store.state.device.useOsDefaultEmojis && !this.isReaction;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,19 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
<span class="mk-reaction-icon">
 | 
			
		||||
	<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" :alt="$t('@.reactions.like')">
 | 
			
		||||
	<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" :alt="$t('@.reactions.love')">
 | 
			
		||||
	<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" :alt="$t('@.reactions.laugh')">
 | 
			
		||||
	<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" :alt="$t('@.reactions.hmm')">
 | 
			
		||||
	<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" :alt="$t('@.reactions.surprise')">
 | 
			
		||||
	<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" :alt="$t('@.reactions.congrats')">
 | 
			
		||||
	<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" :alt="$t('@.reactions.angry')">
 | 
			
		||||
	<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" :alt="$t('@.reactions.confused')">
 | 
			
		||||
	<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" :alt="$t('@.reactions.rip')">
 | 
			
		||||
	<template v-if="reaction == 'pudding'">
 | 
			
		||||
		<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" :alt="$t('@.reactions.pudding')">
 | 
			
		||||
		<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" :alt="$t('@.reactions.pudding')">
 | 
			
		||||
	</template>
 | 
			
		||||
</span>
 | 
			
		||||
<mk-emoji :emoji="str.startsWith(':') ? null : str" :name="str.startsWith(':') ? str.substr(1, str.length - 2) : null" :is-reaction="true" :custom-emojis="customEmojis" :normal="true"/>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +7,34 @@ import Vue from 'vue';
 | 
			
		|||
import i18n from '../../../i18n';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
	props: ['reaction']
 | 
			
		||||
	props: {
 | 
			
		||||
		reaction: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			customEmojis: (this.$root.getMetaSync() || { emojis: [] }).emojis || []
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		str(): any {
 | 
			
		||||
			switch (this.reaction) {
 | 
			
		||||
				case 'like': return '👍';
 | 
			
		||||
				case 'love': return '❤';
 | 
			
		||||
				case 'laugh': return '😆';
 | 
			
		||||
				case 'hmm': return '🤔';
 | 
			
		||||
				case 'surprise': return '😮';
 | 
			
		||||
				case 'congrats': return '🎉';
 | 
			
		||||
				case 'angry': return '💢';
 | 
			
		||||
				case 'confused': return '😥';
 | 
			
		||||
				case 'rip': return '😇';
 | 
			
		||||
				case 'pudding': return (this.$store.getters.isSignedIn && this.$store.state.settings.iLikeSushi) ? '🍣' : '🍮';
 | 
			
		||||
				default: return this.reaction;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
	<div class="backdrop" ref="backdrop" @click="close"></div>
 | 
			
		||||
	<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
 | 
			
		||||
		<p v-if="!$root.isMobile">{{ title }}</p>
 | 
			
		||||
		<div ref="buttons" :class="{ showFocus }">
 | 
			
		||||
		<div class="buttons" ref="buttons" :class="{ showFocus }">
 | 
			
		||||
			<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button>
 | 
			
		||||
			<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
 | 
			
		||||
			<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +15,9 @@
 | 
			
		|||
			<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
 | 
			
		||||
			<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-if="enableEmojiReaction" class="text">
 | 
			
		||||
			<input v-model="text" placeholder="または絵文字を入力" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +26,7 @@
 | 
			
		|||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import anime from 'animejs';
 | 
			
		||||
import { emojiRegex } from '../../../../../misc/emoji-regex';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('common/views/components/reaction-picker.vue'),
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +60,8 @@ export default Vue.extend({
 | 
			
		|||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			title: this.$t('choose-reaction'),
 | 
			
		||||
			text: null,
 | 
			
		||||
			enableEmojiReaction: false,
 | 
			
		||||
			focus: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +100,10 @@ export default Vue.extend({
 | 
			
		|||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.$root.getMeta().then(meta => {
 | 
			
		||||
			this.enableEmojiReaction = meta.enableEmojiReaction;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			this.focus = 0;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +153,17 @@ export default Vue.extend({
 | 
			
		|||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		reactText() {
 | 
			
		||||
			if (!this.text) return;
 | 
			
		||||
			this.react(this.text);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		tryReactText() {
 | 
			
		||||
			if (!this.text) return;
 | 
			
		||||
			if (!this.text.match(emojiRegex)) return;
 | 
			
		||||
			this.reactText();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onMouseover(e) {
 | 
			
		||||
			this.title = e.target.title;
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			@ -256,9 +277,9 @@ export default Vue.extend({
 | 
			
		|||
			color var(--popupFg)
 | 
			
		||||
			border-bottom solid var(--lineWidth) var(--faceDivider)
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
		> .buttons
 | 
			
		||||
			padding 4px
 | 
			
		||||
			width 240px
 | 
			
		||||
			width 216px
 | 
			
		||||
			text-align center
 | 
			
		||||
 | 
			
		||||
			&.showFocus
 | 
			
		||||
| 
						 | 
				
			
			@ -283,6 +304,9 @@ export default Vue.extend({
 | 
			
		|||
				font-size 24px
 | 
			
		||||
				border-radius 2px
 | 
			
		||||
 | 
			
		||||
				> *
 | 
			
		||||
					height 1em
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					background var(--reactionPickerButtonHoverBg)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -290,4 +314,29 @@ export default Vue.extend({
 | 
			
		|||
					background var(--primary)
 | 
			
		||||
					box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
 | 
			
		||||
 | 
			
		||||
		> .text
 | 
			
		||||
			width 216px
 | 
			
		||||
			padding 4px 8px 8px 8px
 | 
			
		||||
 | 
			
		||||
			> input
 | 
			
		||||
				width 100%
 | 
			
		||||
				padding 10px
 | 
			
		||||
				margin 0
 | 
			
		||||
				text-align center
 | 
			
		||||
				font-size 16px
 | 
			
		||||
				color var(--desktopPostFormTextareaFg)
 | 
			
		||||
				background var(--desktopPostFormTextareaBg)
 | 
			
		||||
				outline none
 | 
			
		||||
				border solid 1px var(--primaryAlpha01)
 | 
			
		||||
				border-radius 4px
 | 
			
		||||
				transition border-color .2s ease
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					border-color var(--primaryAlpha02)
 | 
			
		||||
					transition border-color .1s ease
 | 
			
		||||
 | 
			
		||||
				&:focus
 | 
			
		||||
					border-color var(--primaryAlpha05)
 | 
			
		||||
					transition border-color 0s ease
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -136,12 +136,8 @@ export default Vue.extend({
 | 
			
		|||
		&:hover
 | 
			
		||||
			background var(--reactionViewerButtonHoverBg)
 | 
			
		||||
 | 
			
		||||
	> .mk-reaction-icon
 | 
			
		||||
		font-size 1.4em
 | 
			
		||||
 | 
			
		||||
	> span
 | 
			
		||||
		font-size 1.1em
 | 
			
		||||
		line-height 32px
 | 
			
		||||
		vertical-align middle
 | 
			
		||||
		color var(--text)
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								src/misc/emoji-regex.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/misc/emoji-regex.ts
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -13,6 +13,7 @@ const defaultMeta: any = {
 | 
			
		|||
		originalUsersCount: 0
 | 
			
		||||
	},
 | 
			
		||||
	maxNoteTextLength: 1000,
 | 
			
		||||
	enableEmojiReaction: true,
 | 
			
		||||
	enableTwitterIntegration: false,
 | 
			
		||||
	enableGithubIntegration: false,
 | 
			
		||||
	enableDiscordIntegration: false,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										59
									
								
								src/misc/reaction-lib.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/misc/reaction-lib.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
import Emoji from '../models/emoji';
 | 
			
		||||
import { emojiRegex } from './emoji-regex';
 | 
			
		||||
 | 
			
		||||
const basic10: Record<string, string> = {
 | 
			
		||||
	'👍': 'like',
 | 
			
		||||
	'❤': 'love',	// ここに記述する場合は異体字セレクタを入れない
 | 
			
		||||
	'😆': 'laugh',
 | 
			
		||||
	'🤔': 'hmm',
 | 
			
		||||
	'😮': 'surprise',
 | 
			
		||||
	'🎉': 'congrats',
 | 
			
		||||
	'💢': 'angry',
 | 
			
		||||
	'😥': 'confused',
 | 
			
		||||
	'😇': 'rip',
 | 
			
		||||
	'🍮': 'pudding',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function getFallbackReaction(): Promise<string> {
 | 
			
		||||
	return 'like';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function toDbReaction(reaction: string, enableEmoji = true): Promise<string> {
 | 
			
		||||
	if (reaction == null) return await getFallbackReaction();
 | 
			
		||||
 | 
			
		||||
	// 既存の文字列リアクションはそのまま
 | 
			
		||||
	if (Object.values(basic10).includes(reaction)) return reaction;
 | 
			
		||||
 | 
			
		||||
	if (!enableEmoji) return await getFallbackReaction();
 | 
			
		||||
 | 
			
		||||
	// Unicode絵文字
 | 
			
		||||
	const match = emojiRegex.exec(reaction);
 | 
			
		||||
	if (match) {
 | 
			
		||||
		// 合字を含む1つの絵文字
 | 
			
		||||
		const unicode = match[0];
 | 
			
		||||
 | 
			
		||||
		// 異体字セレクタ除去後の絵文字
 | 
			
		||||
		const normalized = unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
 | 
			
		||||
 | 
			
		||||
		// Unicodeプリンは寿司化不能とするため文字列化しない
 | 
			
		||||
		if (normalized === '🍮') return normalized;
 | 
			
		||||
 | 
			
		||||
		// プリン以外の既存のリアクションは文字列化する
 | 
			
		||||
		if (basic10[normalized]) return basic10[normalized];
 | 
			
		||||
 | 
			
		||||
		// それ以外はUnicodeのまま
 | 
			
		||||
		return normalized;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const custom = reaction.match(/:([\w+-]+):/);
 | 
			
		||||
	if (custom) {
 | 
			
		||||
		const emoji = await Emoji.findOne({
 | 
			
		||||
			host: null,
 | 
			
		||||
			name: custom[1],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (emoji) return reaction;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return await getFallbackReaction();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -194,6 +194,7 @@ export type IMeta = {
 | 
			
		|||
	disableRegistration?: boolean;
 | 
			
		||||
	disableLocalTimeline?: boolean;
 | 
			
		||||
	disableGlobalTimeline?: boolean;
 | 
			
		||||
	enableEmojiReaction?: boolean;
 | 
			
		||||
	hidedTags?: string[];
 | 
			
		||||
	mascotImageUrl?: string;
 | 
			
		||||
	bannerUrl?: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ import { packMany as packFileMany, IDriveFile } from './drive-file';
 | 
			
		|||
import Following from './following';
 | 
			
		||||
import Emoji from './emoji';
 | 
			
		||||
import { dbLogger } from '../db/logger';
 | 
			
		||||
import { unique, concat } from '../prelude/array';
 | 
			
		||||
 | 
			
		||||
const Note = db.get<INote>('notes');
 | 
			
		||||
Note.createIndex('uri', { sparse: true, unique: true });
 | 
			
		||||
| 
						 | 
				
			
			@ -242,6 +243,11 @@ export const pack = async (
 | 
			
		|||
 | 
			
		||||
	const id = _note._id;
 | 
			
		||||
 | 
			
		||||
	// Some counts
 | 
			
		||||
	_note.renoteCount = _note.renoteCount || 0;
 | 
			
		||||
	_note.repliesCount = _note.repliesCount || 0;
 | 
			
		||||
	_note.reactionCounts = _note.reactionCounts || {};
 | 
			
		||||
 | 
			
		||||
	// _note._userを消す前か、_note.userを解決した後でないとホストがわからない
 | 
			
		||||
	if (_note._user) {
 | 
			
		||||
		const host = _note._user.host;
 | 
			
		||||
| 
						 | 
				
			
			@ -253,6 +259,8 @@ export const pack = async (
 | 
			
		|||
				fields: { _id: false }
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			_note.emojis = unique(concat([_note.emojis, Object.keys(_note.reactionCounts)]));
 | 
			
		||||
 | 
			
		||||
			_note.emojis = Emoji.find({
 | 
			
		||||
				name: { $in: _note.emojis },
 | 
			
		||||
				host: host
 | 
			
		||||
| 
						 | 
				
			
			@ -290,11 +298,6 @@ export const pack = async (
 | 
			
		|||
	// Populate files
 | 
			
		||||
	_note.files = packFileMany(_note.fileIds || []);
 | 
			
		||||
 | 
			
		||||
	// Some counts
 | 
			
		||||
	_note.renoteCount = _note.renoteCount || 0;
 | 
			
		||||
	_note.repliesCount = _note.repliesCount || 0;
 | 
			
		||||
	_note.reactionCounts = _note.reactionCounts || {};
 | 
			
		||||
 | 
			
		||||
	// 後方互換性のため
 | 
			
		||||
	_note.mediaIds = _note.fileIds;
 | 
			
		||||
	_note.media = _note.files;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ import Note from '../../../models/note';
 | 
			
		|||
import { IRemoteUser } from '../../../models/user';
 | 
			
		||||
import { ILike } from '../type';
 | 
			
		||||
import create from '../../../services/note/reaction/create';
 | 
			
		||||
import { validateReaction } from '../../../models/note-reaction';
 | 
			
		||||
 | 
			
		||||
export default async (actor: IRemoteUser, activity: ILike) => {
 | 
			
		||||
	const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
 | 
			
		||||
| 
						 | 
				
			
			@ -18,12 +17,5 @@ export default async (actor: IRemoteUser, activity: ILike) => {
 | 
			
		|||
		throw new Error();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let reaction = 'like';
 | 
			
		||||
 | 
			
		||||
	// 他のMisskeyインスタンスからのリアクション
 | 
			
		||||
	if (activity._misskey_reaction && validateReaction.ok(activity._misskey_reaction)) {
 | 
			
		||||
		reaction = activity._misskey_reaction;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	await create(actor, note, reaction);
 | 
			
		||||
	await create(actor, note, activity._misskey_reaction);
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,13 @@ export const meta = {
 | 
			
		|||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		enableEmojiReaction: {
 | 
			
		||||
			validator: $.optional.nullable.bool,
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '絵文字リアクションを有効にするか否か'
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		hidedTags: {
 | 
			
		||||
			validator: $.optional.nullable.arr($.str),
 | 
			
		||||
			desc: {
 | 
			
		||||
| 
						 | 
				
			
			@ -351,6 +358,10 @@ export default define(meta, async (ps) => {
 | 
			
		|||
		set.disableGlobalTimeline = ps.disableGlobalTimeline;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (typeof ps.enableEmojiReaction === 'boolean') {
 | 
			
		||||
		set.enableEmojiReaction = ps.enableEmojiReaction;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (Array.isArray(ps.hidedTags)) {
 | 
			
		||||
		set.hidedTags = ps.hidedTags;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,6 +70,10 @@ export const meta = {
 | 
			
		|||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether disabled GTL.',
 | 
			
		||||
			},
 | 
			
		||||
			enableEmojiReaction: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether enabled emoji reaction.',
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +111,7 @@ export default define(meta, async (ps, me) => {
 | 
			
		|||
		disableRegistration: instance.disableRegistration,
 | 
			
		||||
		disableLocalTimeline: instance.disableLocalTimeline,
 | 
			
		||||
		disableGlobalTimeline: instance.disableGlobalTimeline,
 | 
			
		||||
		enableEmojiReaction: instance.enableEmojiReaction,
 | 
			
		||||
		driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
 | 
			
		||||
		driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
 | 
			
		||||
		cacheRemoteFiles: instance.cacheRemoteFiles,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
import $ from 'cafy';
 | 
			
		||||
import ID, { transform } from '../../../../../misc/cafy-id';
 | 
			
		||||
import createReaction from '../../../../../services/note/reaction/create';
 | 
			
		||||
import { validateReaction } from '../../../../../models/note-reaction';
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import { getNote } from '../../../common/getters';
 | 
			
		||||
import { ApiError } from '../../../error';
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +29,7 @@ export const meta = {
 | 
			
		|||
		},
 | 
			
		||||
 | 
			
		||||
		reaction: {
 | 
			
		||||
			validator: $.str.pipe(validateReaction.ok),
 | 
			
		||||
			validator: $.str,
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': 'リアクションの種類'
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ export const meta = {
 | 
			
		|||
 | 
			
		||||
	limit: {
 | 
			
		||||
		duration: ms('1hour'),
 | 
			
		||||
		max: 5,
 | 
			
		||||
		max: 60,
 | 
			
		||||
		minInterval: ms('3sec')
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,8 @@ import { deliver } from '../../../queue';
 | 
			
		|||
import { renderActivity } from '../../../remote/activitypub/renderer';
 | 
			
		||||
import perUserReactionsChart from '../../../services/chart/per-user-reactions';
 | 
			
		||||
import { IdentifiableError } from '../../../misc/identifiable-error';
 | 
			
		||||
import { toDbReaction } from '../../../misc/reaction-lib';
 | 
			
		||||
import fetchMeta from '../../../misc/fetch-meta';
 | 
			
		||||
 | 
			
		||||
export default async (user: IUser, note: INote, reaction: string) => {
 | 
			
		||||
	// Myself
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +19,9 @@ export default async (user: IUser, note: INote, reaction: string) => {
 | 
			
		|||
		throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const meta = await fetchMeta();
 | 
			
		||||
	reaction = await toDbReaction(reaction, meta.enableEmojiReaction);
 | 
			
		||||
 | 
			
		||||
	// Create reaction
 | 
			
		||||
	await NoteReaction.insert({
 | 
			
		||||
		createdAt: new Date(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue