2018-01-04 19:52:25 +01:00
|
|
|
|
package jp.juggler.subwaytooter.util
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.text.SpannableStringBuilder
|
|
|
|
|
import android.text.Spanned
|
2018-01-18 19:09:36 +01:00
|
|
|
|
import android.util.SparseBooleanArray
|
2019-09-14 22:09:52 +02:00
|
|
|
|
import androidx.annotation.DrawableRes
|
2018-01-20 17:59:38 +01:00
|
|
|
|
import jp.juggler.subwaytooter.R
|
2021-05-19 22:14:30 +02:00
|
|
|
|
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
|
|
|
|
import jp.juggler.subwaytooter.emoji.EmojiMap
|
|
|
|
|
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
2021-11-08 10:29:11 +01:00
|
|
|
|
import jp.juggler.subwaytooter.pref.PrefB
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import jp.juggler.subwaytooter.span.EmojiImageSpan
|
|
|
|
|
import jp.juggler.subwaytooter.span.HighlightSpan
|
|
|
|
|
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
2019-09-14 22:09:52 +02:00
|
|
|
|
import jp.juggler.subwaytooter.span.createSpan
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import jp.juggler.subwaytooter.table.HighlightWord
|
2023-02-04 21:52:26 +01:00
|
|
|
|
import jp.juggler.subwaytooter.table.daoHighlightWord
|
2023-01-13 13:22:25 +01:00
|
|
|
|
import jp.juggler.util.data.asciiPattern
|
|
|
|
|
import jp.juggler.util.data.codePointBefore
|
|
|
|
|
import jp.juggler.util.log.LogCategory
|
2018-01-20 17:59:38 +01:00
|
|
|
|
import java.util.regex.Pattern
|
2021-02-19 02:18:58 +01:00
|
|
|
|
import kotlin.math.min
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
object EmojiDecoder {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
2023-01-11 04:28:01 +01:00
|
|
|
|
private val log = LogCategory("EmojiDecoder")
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
2021-05-08 07:56:34 +02:00
|
|
|
|
private const val cpColon = ':'.code
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
2021-05-08 07:56:34 +02:00
|
|
|
|
private const val cpZwsp = '\u200B'.code
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
2021-11-08 10:29:11 +01:00
|
|
|
|
var useTwemoji = true
|
2021-02-20 12:22:22 +01:00
|
|
|
|
|
2021-11-21 07:38:25 +01:00
|
|
|
|
fun customEmojiSeparator() =
|
2023-02-04 21:52:26 +01:00
|
|
|
|
if (PrefB.bpCustomEmojiSeparatorZwsp.value) {
|
2021-11-08 10:29:11 +01:00
|
|
|
|
'\u200B'
|
|
|
|
|
} else {
|
|
|
|
|
' '
|
|
|
|
|
}
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
// タンス側が落ち着いたら [^[:almun:]_] から [:space:]に切り替える
|
|
|
|
|
// private fun isHeadOrAfterWhitespace( s:CharSequence,index:Int):Boolean {
|
|
|
|
|
// val cp = s.codePointBefore(index)
|
|
|
|
|
// return cp == -1 || CharacterGroup.isWhitespace(cp)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
fun canStartShortCode(s: CharSequence, index: Int): Boolean {
|
|
|
|
|
return when (val cp = s.codePointBefore(index)) {
|
2021-02-22 22:33:54 +01:00
|
|
|
|
-1 -> true
|
|
|
|
|
cpColon -> false
|
|
|
|
|
cpZwsp -> true
|
2021-02-20 00:40:02 +01:00
|
|
|
|
// rubyの (Letter | Mark | Decimal_Number) はNG
|
|
|
|
|
// ftp://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values
|
|
|
|
|
else -> when (Character.getType(cp).toByte()) {
|
|
|
|
|
// Letter
|
|
|
|
|
// LCはエイリアスなので文字から得られることはないはず
|
2021-02-22 22:33:54 +01:00
|
|
|
|
Character.UPPERCASE_LETTER,
|
|
|
|
|
Character.LOWERCASE_LETTER,
|
|
|
|
|
Character.TITLECASE_LETTER,
|
|
|
|
|
Character.MODIFIER_LETTER,
|
2022-03-11 00:11:49 +01:00
|
|
|
|
Character.OTHER_LETTER,
|
|
|
|
|
-> false
|
2021-02-20 00:40:02 +01:00
|
|
|
|
// Mark
|
2021-02-22 22:33:54 +01:00
|
|
|
|
Character.NON_SPACING_MARK,
|
|
|
|
|
Character.COMBINING_SPACING_MARK,
|
2022-03-11 00:11:49 +01:00
|
|
|
|
Character.ENCLOSING_MARK,
|
|
|
|
|
-> false
|
2021-02-20 00:40:02 +01:00
|
|
|
|
// Decimal_Number
|
2021-02-22 22:33:54 +01:00
|
|
|
|
Character.DECIMAL_DIGIT_NUMBER -> false
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
else -> true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// https://mastodon.juggler.jp/@tateisu/99727683089280157
|
|
|
|
|
// https://github.com/tootsuite/mastodon/pull/5570 がマージされたらこっちに切り替える
|
|
|
|
|
// return cp == -1 || CharacterGroup.isWhitespace(cp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun canStartHashtag(s: CharSequence, index: Int): Boolean {
|
|
|
|
|
val cp = s.codePointBefore(index)
|
|
|
|
|
// HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
|
|
|
|
|
return if (cp >= 0x80) {
|
|
|
|
|
true
|
|
|
|
|
} else when (cp.toChar()) {
|
2021-02-22 22:33:54 +01:00
|
|
|
|
'/' -> false
|
|
|
|
|
')' -> false
|
|
|
|
|
'_' -> false
|
|
|
|
|
in 'a'..'z' -> false
|
|
|
|
|
in 'A'..'Z' -> false
|
|
|
|
|
in '0'..'9' -> false
|
2021-02-20 00:40:02 +01:00
|
|
|
|
else -> true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class EmojiStringBuilder(val options: DecodeOptions) {
|
|
|
|
|
|
|
|
|
|
val sb = SpannableStringBuilder()
|
|
|
|
|
var normal_char_start = -1
|
|
|
|
|
|
|
|
|
|
private fun openNormalText() {
|
|
|
|
|
if (normal_char_start == -1) {
|
|
|
|
|
normal_char_start = sb.length
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun closeNormalText() {
|
|
|
|
|
if (normal_char_start != -1) {
|
|
|
|
|
val end = sb.length
|
|
|
|
|
applyHighlight(normal_char_start, end)
|
|
|
|
|
normal_char_start = -1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun applyHighlight(start: Int, end: Int) {
|
|
|
|
|
val list = options.highlightTrie?.matchList(sb, start, end) ?: return
|
|
|
|
|
for (range in list) {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
val word = daoHighlightWord.load(range.word)
|
|
|
|
|
?: continue
|
2021-02-20 00:40:02 +01:00
|
|
|
|
sb.setSpan(
|
2021-02-22 22:33:54 +01:00
|
|
|
|
HighlightSpan(word.color_fg, word.color_bg),
|
|
|
|
|
range.start,
|
|
|
|
|
range.end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
if (word.sound_type != HighlightWord.SOUND_TYPE_NONE) {
|
|
|
|
|
if (options.highlightSound == null) options.highlightSound = word
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (word.speech != 0) {
|
|
|
|
|
if (options.highlightSpeech == null) options.highlightSpeech = word
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.highlightAny == null) options.highlightAny = word
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-22 16:22:28 +01:00
|
|
|
|
fun addNetworkEmojiSpan(text: String, url: String, initialAspect: Float?) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
closeNormalText()
|
|
|
|
|
val start = sb.length
|
|
|
|
|
sb.append(text)
|
|
|
|
|
val end = sb.length
|
|
|
|
|
sb.setSpan(
|
2023-02-21 16:17:29 +01:00
|
|
|
|
NetworkEmojiSpan(
|
|
|
|
|
url,
|
|
|
|
|
scale = options.enlargeCustomEmoji,
|
2023-02-22 16:22:28 +01:00
|
|
|
|
sizeMode = options.emojiSizeMode,
|
|
|
|
|
initialAspect = initialAspect,
|
2023-02-21 16:17:29 +01:00
|
|
|
|
),
|
2021-02-22 22:33:54 +01:00
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
2021-02-20 00:40:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
fun addImageSpan(text: String, @DrawableRes resId: Int) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
val context = options.context
|
|
|
|
|
if (context == null) {
|
|
|
|
|
openNormalText()
|
|
|
|
|
sb.append(text)
|
|
|
|
|
} else {
|
|
|
|
|
closeNormalText()
|
|
|
|
|
val start = sb.length
|
|
|
|
|
sb.append(text)
|
|
|
|
|
val end = sb.length
|
|
|
|
|
sb.setSpan(
|
2021-06-20 15:12:25 +02:00
|
|
|
|
EmojiImageSpan(context, resId, scale = options.enlargeEmoji),
|
2021-02-22 22:33:54 +01:00
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
2021-02-20 00:40:02 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 22:33:54 +01:00
|
|
|
|
fun addImageSpan(text: String, emoji: UnicodeEmoji) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
val context = options.context
|
2021-11-08 10:29:11 +01:00
|
|
|
|
when {
|
|
|
|
|
context == null -> {
|
|
|
|
|
openNormalText()
|
|
|
|
|
sb.append(text)
|
|
|
|
|
}
|
2023-02-04 21:52:26 +01:00
|
|
|
|
PrefB.bpUseTwemoji.value -> {
|
2021-11-08 10:29:11 +01:00
|
|
|
|
closeNormalText()
|
|
|
|
|
val start = sb.length
|
|
|
|
|
sb.append(text)
|
|
|
|
|
val end = sb.length
|
|
|
|
|
sb.setSpan(
|
|
|
|
|
emoji.createSpan(context, scale = options.enlargeEmoji),
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
|
|
|
|
openNormalText()
|
|
|
|
|
sb.append(emoji.unifiedCode)
|
|
|
|
|
}
|
2021-02-20 00:40:02 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-19 02:18:58 +01:00
|
|
|
|
|
2021-02-20 00:40:02 +01:00
|
|
|
|
fun addUnicodeString(s: String) {
|
2021-02-20 12:22:22 +01:00
|
|
|
|
|
2021-11-08 10:29:11 +01:00
|
|
|
|
if (!useTwemoji) {
|
2021-02-20 12:22:22 +01:00
|
|
|
|
openNormalText()
|
|
|
|
|
sb.append(s)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-20 00:40:02 +01:00
|
|
|
|
var i = 0
|
|
|
|
|
val end = s.length
|
|
|
|
|
|
|
|
|
|
// 絵文字ではない部分をコピーする
|
|
|
|
|
fun normalCopy(initialJ: Int): Boolean {
|
|
|
|
|
var j = initialJ
|
2021-02-20 12:22:22 +01:00
|
|
|
|
while (j < end && !EmojiMap.isStartChar(s[j])) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
j += min(end - j, Character.charCount(s.codePointAt(j)))
|
|
|
|
|
}
|
|
|
|
|
if (j <= i) return false
|
|
|
|
|
// https://github.com/tateisu/SubwayTooter/issues/69
|
|
|
|
|
val text = s.substring(i, j).replace('\u00AD', '-')
|
|
|
|
|
openNormalText()
|
|
|
|
|
sb.append(text)
|
|
|
|
|
i = j
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (i < end) {
|
|
|
|
|
// 絵文字ではない部分をコピーする
|
|
|
|
|
if (normalCopy(i) && i >= end) break
|
|
|
|
|
|
|
|
|
|
// 絵文字コードを探索
|
2021-02-22 22:33:54 +01:00
|
|
|
|
val result = EmojiMap.unicodeTrie.get(s, i, end)
|
2021-02-20 00:40:02 +01:00
|
|
|
|
if (result == null) {
|
|
|
|
|
// 見つからなかったら、通常テキストを1文字以上コピーする
|
2021-02-22 22:33:54 +01:00
|
|
|
|
normalCopy(i + min(end - i, Character.charCount(s.codePointAt(i))))
|
2021-02-20 00:40:02 +01:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-08 07:56:34 +02:00
|
|
|
|
val nextChar = if (result.endPos >= end) null else s[result.endPos].code
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
// 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合
|
|
|
|
|
// その文字を絵文字化しない
|
|
|
|
|
if (nextChar == 0xFE0E) {
|
2021-02-22 22:33:54 +01:00
|
|
|
|
normalCopy(result.endPos + 1)
|
2021-02-20 00:40:02 +01:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-08 07:56:34 +02:00
|
|
|
|
val emoji = if (nextChar == 0xFE0F && s[result.endPos - 1].code != 0xFE0F) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
// 絵文字の最後が 0xFE0F でない
|
|
|
|
|
// 直後にU+0xFE0F (絵文字バリエーション・シーケンスEVSのVS-16)がある
|
|
|
|
|
// 直後のそれまで含めて絵文字として表示する
|
|
|
|
|
s.substring(i, result.endPos + 1)
|
|
|
|
|
} else {
|
|
|
|
|
s.substring(i, result.endPos)
|
|
|
|
|
}
|
|
|
|
|
addImageSpan(emoji, result.data)
|
|
|
|
|
i += emoji.length
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-08 07:56:34 +02:00
|
|
|
|
private const val codepointColon = ':'.code
|
2021-02-20 00:40:02 +01:00
|
|
|
|
// private const val codepointAtmark = '@'.toInt()
|
|
|
|
|
|
|
|
|
|
private val shortCodeCharacterSet =
|
|
|
|
|
SparseBooleanArray().apply {
|
2021-05-08 07:56:34 +02:00
|
|
|
|
for (c in 'A'..'Z') put(c.code, true)
|
|
|
|
|
for (c in 'a'..'z') put(c.code, true)
|
|
|
|
|
for (c in '0'..'9') put(c.code, true)
|
|
|
|
|
for (c in "+-_@:") put(c.code, true)
|
|
|
|
|
for (c in ".") put(c.code, true)
|
2021-02-20 00:40:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private interface ShortCodeSplitterCallback {
|
|
|
|
|
fun onString(part: String) // shortcode以外の文字列
|
|
|
|
|
fun onShortCode(
|
2021-02-22 22:33:54 +01:00
|
|
|
|
prevCodePoint: Int,
|
|
|
|
|
part: String,
|
2022-03-11 00:11:49 +01:00
|
|
|
|
name: String,
|
2021-02-22 22:33:54 +01:00
|
|
|
|
) // part : ":shortcode:", name : "shortcode"
|
2021-02-20 00:40:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val reUrl = """https?://[\w/:%#@${'$'}&?!()\[\]~.=+\-]+"""
|
|
|
|
|
.asciiPattern()
|
|
|
|
|
|
|
|
|
|
private fun splitShortCode(
|
2021-02-22 22:33:54 +01:00
|
|
|
|
s: String,
|
2022-03-11 00:11:49 +01:00
|
|
|
|
callback: ShortCodeSplitterCallback,
|
2021-02-22 22:33:54 +01:00
|
|
|
|
) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
val urlList = ArrayList<IntRange>().apply {
|
|
|
|
|
val m = reUrl.matcher(s)
|
|
|
|
|
while (m.find()) {
|
|
|
|
|
add(m.start()..m.end())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val end = s.length
|
|
|
|
|
var i = 0
|
|
|
|
|
while (i < end) {
|
|
|
|
|
|
|
|
|
|
// ":"以外を読み飛ばす
|
|
|
|
|
// URL中のコロンも読み飛ばす
|
|
|
|
|
var start = i
|
|
|
|
|
loop@ while (i < end) {
|
|
|
|
|
val c = s.codePointAt(i)
|
2022-07-20 06:27:19 +02:00
|
|
|
|
if (c == codepointColon && urlList.none { i in it }) break@loop
|
2021-02-20 00:40:02 +01:00
|
|
|
|
i += Character.charCount(c)
|
|
|
|
|
}
|
|
|
|
|
if (i > start) callback.onString(s.substring(start, i))
|
|
|
|
|
|
|
|
|
|
if (i >= end) break
|
|
|
|
|
|
|
|
|
|
start = i++ // start=コロンの位置 i=その次の位置
|
|
|
|
|
|
|
|
|
|
// 閉じるコロンを探す
|
|
|
|
|
var posEndColon = -1
|
2021-06-20 15:12:25 +02:00
|
|
|
|
loop@ while (i < end) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
val cp = s.codePointAt(i)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
when {
|
|
|
|
|
cp == codepointColon -> {
|
|
|
|
|
posEndColon = i
|
|
|
|
|
break@loop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
!shortCodeCharacterSet.get(cp, false) ->
|
|
|
|
|
break@loop
|
|
|
|
|
}
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
i += Character.charCount(cp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 閉じるコロンが見つからないか、shortcodeが短すぎるなら
|
|
|
|
|
// startの位置のコロンだけを処理して残りは次のループで処理する
|
|
|
|
|
if (posEndColon == -1 || posEndColon - start < 2) {
|
|
|
|
|
callback.onString(":")
|
|
|
|
|
i = start + 1
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val prevCodePoint = when {
|
|
|
|
|
start <= 0 -> 0x20
|
|
|
|
|
else -> s.codePointBefore(start)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback.onShortCode(
|
2021-02-22 22:33:54 +01:00
|
|
|
|
prevCodePoint,
|
|
|
|
|
s.substring(start, posEndColon + 1), // ":shortcode:"
|
|
|
|
|
s.substring(start + 1, posEndColon) // "shortcode"
|
|
|
|
|
)
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
i = posEndColon + 1 // コロンの次の位置
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val reNicoru = """\Anicoru\d*\z""".asciiPattern(Pattern.CASE_INSENSITIVE)
|
|
|
|
|
private val reHohoemi = """\Ahohoemi\d*\z""".asciiPattern(Pattern.CASE_INSENSITIVE)
|
|
|
|
|
|
|
|
|
|
fun decodeEmoji(options: DecodeOptions, s: String): SpannableStringBuilder {
|
|
|
|
|
|
|
|
|
|
val builder = EmojiStringBuilder(options)
|
|
|
|
|
|
|
|
|
|
val emojiMapCustom = options.emojiMapCustom
|
|
|
|
|
val emojiMapProfile = options.emojiMapProfile
|
|
|
|
|
|
2023-02-04 21:52:26 +01:00
|
|
|
|
val useEmojioneShortcode = PrefB.bpEmojioneShortcode.value
|
|
|
|
|
val disableEmojiAnimation = PrefB.bpDisableEmojiAnimation.value
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
2023-02-08 10:55:49 +01:00
|
|
|
|
// カスタム絵文字のアニメーション切り替え
|
|
|
|
|
fun CustomEmoji.customEmojiToUrl(): String = when {
|
|
|
|
|
disableEmojiAnimation && staticUrl?.isNotEmpty() == true -> staticUrl
|
|
|
|
|
else -> this.url
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun findEmojiMisskey13(name: String): String? {
|
|
|
|
|
val cols = name.split("@", limit = 2)
|
|
|
|
|
val apiHostAscii = options.linkHelper?.apiHost?.ascii
|
|
|
|
|
|
|
|
|
|
// @以降にあるホスト名か、投稿者のホスト名か、閲覧先サーバのホスト名
|
|
|
|
|
val userHost = cols.elementAtOrNull(1)
|
|
|
|
|
?: options.authorDomain?.apiHost?.ascii
|
|
|
|
|
?: apiHostAscii
|
2023-02-26 00:15:57 +01:00
|
|
|
|
// log.i(
|
|
|
|
|
// "decodeEmoji Misskey13 c0=${cols.elementAtOrNull(0)} c1=${
|
|
|
|
|
// cols.elementAtOrNull(1)
|
|
|
|
|
// } apiHostAscii=$apiHostAscii, userHost=$userHost"
|
|
|
|
|
// )
|
2023-02-08 10:55:49 +01:00
|
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
// 絵文字プロクシを利用できない
|
|
|
|
|
apiHostAscii == null -> {
|
|
|
|
|
log.w("decodeEmoji Misskey13 missing apiHostAscii")
|
|
|
|
|
}
|
|
|
|
|
userHost != null && userHost != "." && userHost != apiHostAscii -> {
|
|
|
|
|
// 投稿者のホスト名を使う
|
|
|
|
|
return "https://$apiHostAscii/emoji/${
|
|
|
|
|
cols.elementAtOrNull(0)
|
|
|
|
|
}@$userHost.webp"
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
|
|
|
|
// 存在確認せずに絵文字プロキシのURLを返す
|
|
|
|
|
// 閲覧先サーバの絵文字を探す
|
2023-02-21 16:17:29 +01:00
|
|
|
|
return "https://${apiHostAscii}/emoji/${cols.elementAtOrNull(0)}.webp"
|
2023-02-08 10:55:49 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
2023-02-16 01:28:07 +01:00
|
|
|
|
|
2023-02-22 16:22:28 +01:00
|
|
|
|
|
2021-02-20 00:40:02 +01:00
|
|
|
|
splitShortCode(s, callback = object : ShortCodeSplitterCallback {
|
2021-02-22 22:33:54 +01:00
|
|
|
|
override fun onString(part: String) {
|
|
|
|
|
builder.addUnicodeString(part)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onShortCode(prevCodePoint: Int, part: String, name: String) {
|
|
|
|
|
// フレニコのプロフ絵文字
|
|
|
|
|
if (emojiMapProfile != null && name.length >= 2 && name[0] == '@') {
|
|
|
|
|
val emojiProfile = emojiMapProfile[name] ?: emojiMapProfile[name.substring(1)]
|
|
|
|
|
if (emojiProfile != null) {
|
|
|
|
|
val url = emojiProfile.url
|
|
|
|
|
if (url.isNotEmpty()) {
|
2023-02-22 16:22:28 +01:00
|
|
|
|
builder.addNetworkEmojiSpan(part, url, initialAspect = null)
|
2021-02-22 22:33:54 +01:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-22 16:22:28 +01:00
|
|
|
|
emojiMapCustom?.get(name)?.let {
|
|
|
|
|
val url = it.customEmojiToUrl()
|
|
|
|
|
builder.addNetworkEmojiSpan(part, url, initialAspect = it.aspect)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
val url = if (options.linkHelper?.isMisskey == true) {
|
|
|
|
|
findEmojiMisskey13(name = name)
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
2023-01-11 04:28:01 +01:00
|
|
|
|
if (url != null) {
|
2023-02-22 16:22:28 +01:00
|
|
|
|
builder.addNetworkEmojiSpan(part, url, initialAspect = null)
|
2021-02-22 22:33:54 +01:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通常の絵文字
|
|
|
|
|
when {
|
2023-01-18 05:59:35 +01:00
|
|
|
|
reHohoemi.matcher(name).find() ->
|
|
|
|
|
builder.addImageSpan(part, R.drawable.emoji_hohoemi)
|
|
|
|
|
reNicoru.matcher(name).find() ->
|
|
|
|
|
builder.addImageSpan(part, R.drawable.emoji_nicoru)
|
2021-02-22 22:33:54 +01:00
|
|
|
|
else -> {
|
|
|
|
|
// EmojiOneのショートコード
|
2023-01-18 05:59:35 +01:00
|
|
|
|
val emoji = when {
|
|
|
|
|
useEmojioneShortcode ->
|
|
|
|
|
EmojiMap.shortNameMap[name.lowercase().replace('-', '_')]
|
|
|
|
|
else -> null
|
2021-02-22 22:33:54 +01:00
|
|
|
|
}
|
2023-01-18 05:59:35 +01:00
|
|
|
|
when (emoji) {
|
|
|
|
|
null -> builder.addUnicodeString(part)
|
|
|
|
|
else -> builder.addImageSpan(part, emoji)
|
2021-02-22 22:33:54 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
builder.closeNormalText()
|
|
|
|
|
|
|
|
|
|
return builder.sb
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 投稿などの際、表示は不要だがショートコード=>Unicodeの解決を行いたい場合がある
|
|
|
|
|
// カスタム絵文字の変換も行わない
|
|
|
|
|
fun decodeShortCode(
|
2021-02-22 22:33:54 +01:00
|
|
|
|
s: String,
|
2022-03-11 00:11:49 +01:00
|
|
|
|
emojiMapCustom: HashMap<String, CustomEmoji>? = null,
|
2021-02-22 22:33:54 +01:00
|
|
|
|
): String {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
val decodeEmojioneShortcode = PrefB.bpEmojioneShortcode.value
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
val sb = StringBuilder()
|
|
|
|
|
|
|
|
|
|
splitShortCode(s, callback = object : ShortCodeSplitterCallback {
|
2021-02-22 22:33:54 +01:00
|
|
|
|
override fun onString(part: String) {
|
|
|
|
|
sb.append(part)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onShortCode(prevCodePoint: Int, part: String, name: String) {
|
|
|
|
|
// カスタム絵文字にマッチするなら変換しない
|
|
|
|
|
// カスタム絵文字ではなく通常の絵文字のショートコードなら絵文字に変換する
|
2023-01-18 05:59:35 +01:00
|
|
|
|
val emoji = when {
|
|
|
|
|
decodeEmojioneShortcode &&
|
|
|
|
|
emojiMapCustom?.get(name) == null ->
|
|
|
|
|
EmojiMap.shortNameMap[name.lowercase().replace('-', '_')]
|
|
|
|
|
else -> null
|
2021-02-22 22:33:54 +01:00
|
|
|
|
}
|
|
|
|
|
sb.append(emoji?.unifiedCode ?: part)
|
|
|
|
|
}
|
|
|
|
|
})
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
return sb.toString()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 入力補完用。絵文字ショートコード一覧を部分一致で絞り込む
|
|
|
|
|
internal fun searchShortCode(
|
2021-02-22 22:33:54 +01:00
|
|
|
|
context: Context,
|
|
|
|
|
prefix: String,
|
2022-03-11 00:11:49 +01:00
|
|
|
|
limit: Int,
|
2021-02-22 22:33:54 +01:00
|
|
|
|
): ArrayList<CharSequence> {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
val dst = ArrayList<CharSequence>()
|
2021-02-20 12:22:22 +01:00
|
|
|
|
for (shortCode in EmojiMap.shortNameList) {
|
2021-02-20 00:40:02 +01:00
|
|
|
|
if (dst.size >= limit) break
|
|
|
|
|
if (!shortCode.contains(prefix)) continue
|
|
|
|
|
|
2021-02-22 22:33:54 +01:00
|
|
|
|
val emoji = EmojiMap.shortNameMap[shortCode] ?: continue
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
val sb = SpannableStringBuilder()
|
|
|
|
|
|
2023-02-04 21:52:26 +01:00
|
|
|
|
if (PrefB.bpUseTwemoji.value) {
|
2021-11-08 10:29:11 +01:00
|
|
|
|
val start = 0
|
|
|
|
|
sb.append(' ')
|
|
|
|
|
val end = sb.length
|
|
|
|
|
|
|
|
|
|
sb.setSpan(
|
|
|
|
|
emoji.createSpan(context),
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
2021-11-08 12:05:03 +01:00
|
|
|
|
} else {
|
2021-11-08 10:29:11 +01:00
|
|
|
|
sb.append(emoji.unifiedCode)
|
|
|
|
|
}
|
2021-02-20 00:40:02 +01:00
|
|
|
|
|
|
|
|
|
sb.append(' ')
|
|
|
|
|
.append(':')
|
|
|
|
|
.append(shortCode)
|
|
|
|
|
.append(':')
|
|
|
|
|
|
|
|
|
|
dst.add(sb)
|
|
|
|
|
}
|
|
|
|
|
return dst
|
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|