From 9c12e5f85d10fe5645967de233a2d3b2fefb436d Mon Sep 17 00:00:00 2001 From: tateisu Date: Tue, 6 Nov 2018 10:29:33 +0900 Subject: [PATCH] =?UTF-8?q?Misskey=E3=81=AE=E3=82=AB=E3=82=B9=E3=82=BF?= =?UTF-8?q?=E3=83=A0=E7=B5=B5=E6=96=87=E5=AD=97=E3=81=AE=E5=85=A5=E5=8A=9B?= =?UTF-8?q?=E8=A3=9C=E5=AE=8C=E3=81=A7aliases=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=80=82Misskey=E3=81=AE=E3=83=97=E3=83=AD=E3=83=95=E3=82=AB?= =?UTF-8?q?=E3=83=A9=E3=83=A0=E3=81=A7=E6=83=85=E5=A0=B1=E3=81=8C=E8=B6=B3?= =?UTF-8?q?=E3=82=8A=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=81=AB=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subwaytooter/ViewHolderHeaderProfile.kt | 43 ++-- .../api/MisskeyAccountDetailMap.kt | 41 ++++ .../jp/juggler/subwaytooter/api/TootParser.kt | 1 + .../subwaytooter/api/entity/CustomEmoji.kt | 50 +++-- .../subwaytooter/api/entity/TootAccount.kt | 8 +- .../subwaytooter/util/CustomEmojiLister.kt | 95 +++++++-- .../juggler/subwaytooter/util/EmojiDecoder.kt | 16 +- .../util/PopupAutoCompleteAcct.kt | 14 +- .../juggler/subwaytooter/util/PostHelper.kt | 189 +++++++++++------- 9 files changed, 324 insertions(+), 133 deletions(-) create mode 100644 app/src/main/java/jp/juggler/subwaytooter/api/MisskeyAccountDetailMap.kt diff --git a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt index 6316ea75..2a3efac3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt @@ -12,6 +12,7 @@ import jp.juggler.emoji.EmojiMap201709 import jp.juggler.subwaytooter.action.Action_Follow import jp.juggler.subwaytooter.action.Action_User +import jp.juggler.subwaytooter.api.MisskeyAccountDetailMap import jp.juggler.subwaytooter.api.entity.TootAccount import jp.juggler.subwaytooter.api.entity.TootAccountRef import jp.juggler.subwaytooter.api.entity.TootStatus @@ -129,6 +130,7 @@ internal class ViewHolderHeaderProfile( override fun bindData(column : Column) { super.bindData(column) + if(! activity.timeline_font_size_sp.isNaN()) { tvMovedName.textSize = activity.timeline_font_size_sp tvMoved.textSize = activity.timeline_font_size_sp @@ -142,6 +144,13 @@ internal class ViewHolderHeaderProfile( val whoRef = column.who_account this.whoRef = whoRef val who = whoRef?.get() + + // Misskeyの場合はNote中のUserエンティティと /api/users/show の情報量がかなり異なる + val whoDetail = if(who == null) { + null + } else { + MisskeyAccountDetailMap.get(access_info, who.id) + } showColor() @@ -184,7 +193,7 @@ internal class ViewHolderHeaderProfile( access_info.supplyBaseUrl(who.avatar) ) - val name = whoRef.decoded_display_name + val name = whoDetail?.decodeDisplayName(activity) ?: whoRef.decoded_display_name tvDisplayName.text = name name_invalidator.register(name) @@ -193,7 +202,7 @@ internal class ViewHolderHeaderProfile( val sb = SpannableStringBuilder() sb.append("@").append(access_info.getFullAcct(who)) - if(who.locked) { + if(whoDetail?.locked ?: who.locked) { sb.append(" ") val start = sb.length sb.append("locked") @@ -208,7 +217,7 @@ internal class ViewHolderHeaderProfile( ) } } - if(who.bot){ + if(who.bot) { sb.append(" ") val start = sb.length sb.append("bot") @@ -229,9 +238,12 @@ internal class ViewHolderHeaderProfile( tvNote.text = note note_invalidator.register(note) - btnStatusCount.text = activity.getString(R.string.statuses) + "\n" + who.statuses_count - btnFollowing.text = activity.getString(R.string.following) + "\n" + who.following_count - btnFollowers.text = activity.getString(R.string.followers) + "\n" + who.followers_count + btnStatusCount.text = activity.getString(R.string.statuses) + "\n" + + (whoDetail?.statuses_count ?: who.statuses_count) + btnFollowing.text = activity.getString(R.string.following) + "\n" + + (whoDetail?.following_count ?: who.following_count) + btnFollowers.text = activity.getString(R.string.followers) + "\n" + + (whoDetail?.followers_count ?: who.followers_count) val relation = UserRelation.load(access_info.db_id, who.id) Styler.setFollowIcon(activity, btnFollow, ivFollowedBy, relation, who) @@ -259,8 +271,7 @@ internal class ViewHolderHeaderProfile( short = true, emojiMapProfile = who.profile_emojis ) - - + val content_color = column.content_color val c = if(content_color != 0) content_color else default_color @@ -295,18 +306,18 @@ internal class ViewHolderHeaderProfile( ) val valueText = decodeOptions.decodeHTML(item.value) - if(item.verified_at > 0L){ + if(item.verified_at > 0L) { valueText.append('\n') - + val start = valueText.length - valueText.append( activity.getString(R.string.verified_at)) - valueText.append( ": ") - valueText.append(TootStatus.formatTime(activity,item.verified_at,false)) + valueText.append(activity.getString(R.string.verified_at)) + valueText.append(": ") + valueText.append(TootStatus.formatTime(activity, item.verified_at, false)) val end = valueText.length - + valueText.setSpan( ForegroundColorSpan(Color.BLACK or 0x7fbc99) - ,start,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + , start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE ) } @@ -320,7 +331,7 @@ internal class ViewHolderHeaderProfile( valueView.typeface = valueTypeface valueView.movementMethod = MyLinkMovementMethod - if(item.verified_at > 0L){ + if(item.verified_at > 0L) { valueView.setBackgroundColor(0x337fbc99) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/MisskeyAccountDetailMap.kt b/app/src/main/java/jp/juggler/subwaytooter/api/MisskeyAccountDetailMap.kt new file mode 100644 index 00000000..2bfed7fe --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/api/MisskeyAccountDetailMap.kt @@ -0,0 +1,41 @@ +package jp.juggler.subwaytooter.api + +import jp.juggler.subwaytooter.api.entity.* +import jp.juggler.subwaytooter.table.SavedAccount +import java.util.concurrent.ConcurrentHashMap + +object MisskeyAccountDetailMap { + + private class AccountKey( + val db_id : Long, + val id : EntityId + ) { + + override fun hashCode() : Int { + val h1 = (db_id xor db_id.ushr(32)).toInt() + val h2 = id.hashCode() + return (h1 xor h2) + } + + override fun equals(other : Any?) : Boolean { + return other is AccountKey && other.db_id == db_id && other.id == id + } + } + + private val accountMap = ConcurrentHashMap() + + fun fromAccount(parser : TootParser, src : TootAccount, id : EntityId) { + // SavedAccountが不明なら何もしない + val access_info = parser.linkHelper as? SavedAccount ?: return + + // アカウントのjsonがフォロー数を含まないなら何もしない + if((src.followers_count ?: - 1) < 0L) return + + val key = AccountKey(access_info.db_id, id) + accountMap[key] = src + } + + fun get(accessInfo : SavedAccount, id : EntityId) : TootAccount? { + return accountMap[AccountKey(accessInfo.db_id, id)] + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt index 0a208cd0..b91b70b3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt @@ -19,6 +19,7 @@ class TootParser( var misskeyDecodeProfilePin :Boolean = false ) { val misskeyUserRelationMap = HashMap() + val misskeyAccountDetailMap = HashMap() init{ if(linkHelper.isMisskey) serviceType = ServiceType.MISSKEY diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/CustomEmoji.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/CustomEmoji.kt index b4951841..183b8460 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/CustomEmoji.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/CustomEmoji.kt @@ -2,15 +2,25 @@ package jp.juggler.subwaytooter.api.entity import jp.juggler.subwaytooter.util.notEmptyOrThrow import jp.juggler.subwaytooter.util.parseString +import org.json.JSONArray import org.json.JSONObject class CustomEmoji( val shortcode : String, // shortcode (コロンを含まない) val url : String, // 画像URL - val static_url : String? // アニメーションなしの画像URL + val static_url : String?, // アニメーションなしの画像URL + val aliases : ArrayList? = null, + val alias:String? =null ) : Mappable { + fun makeAlias(alias : String) = CustomEmoji ( + shortcode= this.shortcode, + url = this.url, + static_url = this.static_url, + alias = alias + ) + override val mapKey : String get() = shortcode @@ -24,31 +34,31 @@ class CustomEmoji( } val decodeMisskey : (JSONObject) -> CustomEmoji = { src -> val url = src.parseString("url") ?: error("missing url") - val name = src.parseString("name") ?: error("missing name") - - // 使い方が分からない val aliases = parseAliases(src.optJSONArray("aliases")) CustomEmoji( - shortcode = name, + shortcode = src.parseString("name") ?: error("missing name"), url = url, - static_url = url + static_url = url, + aliases = parseAliases(src.optJSONArray("aliases")) ) } -// private fun parseAliases(src : JSONArray?) : ArrayList? { -// var dst = null as ArrayList? -// if(src != null) { -// val size = src.length() -// for(i in 0 until size) { -// val str = src.parseString(i) ?: continue -// if(str.isNotEmpty()) { -// if(dst == null) dst = ArrayList(size) -// dst.add(str) -// } -// } -// } -// return dst -// } + private fun parseAliases(src : JSONArray?) : ArrayList? { + var dst = null as ArrayList? + if(src != null) { + val size = src.length() + if( size > 0){ + dst = ArrayList(size) + for(i in 0 until size) { + val str = src.parseString(i) ?: continue + if(str.isNotEmpty()) { + dst.add(str) + } + } + } + } + return if(dst?.isNotEmpty() == true ) dst else null + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt index 2eef5772..cc3d7474 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt @@ -3,15 +3,13 @@ package jp.juggler.subwaytooter.api.entity import android.content.Context import android.net.Uri import android.text.Spannable +import jp.juggler.subwaytooter.api.MisskeyAccountDetailMap import jp.juggler.subwaytooter.api.TootParser -import jp.juggler.subwaytooter.table.UserRelation import jp.juggler.subwaytooter.table.UserRelationMisskey import jp.juggler.subwaytooter.util.* - import org.json.JSONArray import org.json.JSONObject - -import java.util.ArrayList +import java.util.* import java.util.regex.Pattern open class TootAccount(parser : TootParser, src : JSONObject) { @@ -252,7 +250,7 @@ open class TootAccount(parser : TootParser, src : JSONObject) { } UserRelationMisskey.fromAccount(parser,src,id) - + MisskeyAccountDetailMap.fromAccount(parser,this,id) } else { diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/CustomEmojiLister.kt b/app/src/main/java/jp/juggler/subwaytooter/util/CustomEmojiLister.kt index 62b28c8a..c9f2df81 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/CustomEmojiLister.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/CustomEmojiLister.kt @@ -27,7 +27,11 @@ class CustomEmojiLister(internal val context : Context) { get() = SystemClock.elapsedRealtime() } - internal class CacheItem(val instance : String, var list : ArrayList?) { + internal class CacheItem( + val instance : String, + var list : ArrayList? = null, + var listWithAliases : ArrayList? = null + ) { // 参照された時刻 var time_used : Long = 0 @@ -44,6 +48,7 @@ class CustomEmojiLister(internal val context : Context) { internal class Request( val instance : String, val isMisskey : Boolean, + val reportWithAliases : Boolean = false, val onListLoaded : (list : ArrayList) -> Unit? ) @@ -53,7 +58,7 @@ class CustomEmojiLister(internal val context : Context) { // エラーキャッシュ internal val cache_error = ConcurrentHashMap() - private val cache_error_item = CacheItem("error", null) + private val cache_error_item = CacheItem("error") // ロード要求 internal val queue = ConcurrentLinkedQueue() @@ -100,7 +105,36 @@ class CustomEmojiLister(internal val context : Context) { if(item != null) return item.list } - queue.add(Request(instance, isMisskey, onListLoaded)) + queue.add(Request(instance, isMisskey, onListLoaded = onListLoaded)) + worker.notifyEx() + } catch(ex : Throwable) { + log.trace(ex) + } + return null + } + + fun getListWithAliases( + _instance : String, + isMisskey : Boolean, + onListLoaded : (list : ArrayList) -> Unit + ) : ArrayList? { + try { + if(_instance.isEmpty()) return null + val instance = _instance.toLowerCase() + + synchronized(cache) { + val item = getCached(elapsedTime, instance) + if(item != null) return item.listWithAliases + } + + queue.add( + Request( + instance, + isMisskey, + reportWithAliases = true, + onListLoaded = onListLoaded + ) + ) worker.notifyEx() } catch(ex : Throwable) { log.trace(ex) @@ -141,8 +175,9 @@ class CustomEmojiLister(internal val context : Context) { val item = getCached(elapsedTime, request.instance) return@synchronized if(item != null) { val list = item.list - if(list != null) { - fireCallback(request, list) + val listWithAliases = item.listWithAliases + if(list != null && listWithAliases != null) { + fireCallback(request, list, listWithAliases) } true } else { @@ -154,6 +189,7 @@ class CustomEmojiLister(internal val context : Context) { if(cached) continue var list : ArrayList? = null + var listWithAlias : ArrayList? = null try { val data = if(request.isMisskey) { App1.getHttpCachedString("https://" + request.instance + "/api/meta") { builder -> @@ -165,29 +201,32 @@ class CustomEmojiLister(internal val context : Context) { } else { App1.getHttpCachedString("https://" + request.instance + "/api/v1/custom_emojis") } - + if(data != null) { - list = decodeEmojiList(data, request.instance, request.isMisskey) + val a = decodeEmojiList(data, request.instance, request.isMisskey) + list = a + listWithAlias = makeListWithAlias(a) } - + } catch(ex : Throwable) { log.trace(ex) } synchronized(cache) { val now = elapsedTime - if(list == null) { + if(list == null || listWithAlias == null) { cache_error.put(request.instance, now) } else { var item : CacheItem? = cache[request.instance] if(item == null) { - item = CacheItem(request.instance, list) + item = CacheItem(request.instance, list, listWithAlias) cache[request.instance] = item } else { item.list = list + item.listWithAliases = listWithAlias item.time_update = now } - fireCallback(request, list) + fireCallback(request, list, listWithAlias) } } } catch(ex : Throwable) { @@ -197,8 +236,21 @@ class CustomEmojiLister(internal val context : Context) { } } - private fun fireCallback(request : Request, list : ArrayList) { - handler.post { request.onListLoaded(list) } + + private fun fireCallback( + request : Request, + list : ArrayList, + listWithAliases : ArrayList + ) { + handler.post { + request.onListLoaded( + if(request.reportWithAliases) { + listWithAliases + } else { + list + } + ) + } } // キャッシュの掃除 @@ -243,6 +295,23 @@ class CustomEmojiLister(internal val context : Context) { null } } + + + private fun makeListWithAlias(list : ArrayList?) : ArrayList { + val dst = ArrayList() + if( list != null) { + dst.addAll(list) + for(item in list) { + val aliases = item.aliases ?: continue + for(alias in aliases) { + if( alias.equals(item.shortcode,ignoreCase = true)) continue + dst.add(item.makeAlias(alias)) + } + } + dst.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.alias ?: it.shortcode }) + } + return dst + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt index cbb030cd..6958532f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt @@ -36,21 +36,21 @@ object EmojiDecoder { return when(cp) { - 1 -> true cpColon -> false - // rubyの (Letter | Mark | Decimal_Number) はNG - // ftp://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values + // rubyの (Letter | Mark | Decimal_Number) はNG + // ftp://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values else -> when(java.lang.Character.getType(cp).toByte()) { - // Letter - // LCはエイリアスなので文字から得られることはないはず + // Letter + // LCはエイリアスなので文字から得られることはないはず Character.UPPERCASE_LETTER, Character.LOWERCASE_LETTER, Character.TITLECASE_LETTER, Character.MODIFIER_LETTER, Character.OTHER_LETTER -> false - // Mark + // Mark Character.NON_SPACING_MARK, Character.COMBINING_SPACING_MARK, Character.ENCLOSING_MARK -> false - // Decimal_Number + // Decimal_Number Character.DECIMAL_DIGIT_NUMBER -> false else -> true @@ -106,7 +106,7 @@ object EmojiDecoder { sb.append(text) val end = sb.length sb.setSpan( - NetworkEmojiSpan(url,scale = options.enlargeCustomEmoji), + NetworkEmojiSpan(url, scale = options.enlargeCustomEmoji), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE @@ -177,7 +177,7 @@ object EmojiDecoder { val c = s[i ++] sb.append( when(c) { - // https://github.com/tateisu/SubwayTooter/issues/69 + // https://github.com/tateisu/SubwayTooter/issues/69 '\u00AD' -> '-' else -> c } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt index d87ea016..5c2d43aa 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt @@ -18,6 +18,7 @@ import java.util.ArrayList import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.Styler import jp.juggler.subwaytooter.view.MyEditText +import java.util.regex.Pattern @SuppressLint("InflateParams") internal class PopupAutoCompleteAcct( @@ -30,6 +31,9 @@ internal class PopupAutoCompleteAcct( companion object { internal val log = LogCategory("PopupAutoCompleteAcct") + + // 絵文字ショートコードにマッチするとても雑な正規表現 + private val reLastShortCode = Pattern.compile(""":([^\s:]+):\z""") } private val acct_popup : PopupWindow @@ -135,7 +139,7 @@ internal class PopupAutoCompleteAcct( if(acct[0] == ' ') { // 絵文字ショートコード if(! EmojiDecoder.canStartShortCode(sb, start)) sb.append(' ') - sb.append(acct.subSequence(2, acct.length)) + sb.append( findShortCode(acct.toString())) } else { // @user@host, #hashtag // 直後に空白を付与する @@ -159,6 +163,14 @@ internal class PopupAutoCompleteAcct( updatePosition() } + + + private fun findShortCode(acct : String) : String { + val m = reLastShortCode.matcher(acct) + if(m.find()) return m.group(0) + return acct + } + fun updatePosition() { val location = IntArray(2) diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt index 71082961..ab73def1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt @@ -6,6 +6,7 @@ import android.os.SystemClock import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity import android.text.* +import android.text.style.ForegroundColorSpan import android.view.View import jp.juggler.emoji.EmojiMap201709 @@ -19,6 +20,7 @@ import java.util.regex.Pattern import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.Pref import jp.juggler.subwaytooter.R +import jp.juggler.subwaytooter.Styler import jp.juggler.subwaytooter.api.TootApiClient import jp.juggler.subwaytooter.api.TootApiResult import jp.juggler.subwaytooter.api.TootTask @@ -159,7 +161,7 @@ class PostHelper( if(! bConfirmTag) { - if( !account.isMisskey + if(! account.isMisskey && visibility != TootVisibility.Public && reTag.matcher(content).find() ) { @@ -228,18 +230,18 @@ class PostHelper( var credential_tmp : TootAccount? = null - val parser = TootParser(activity, account ) + val parser = TootParser(activity, account) fun getInstanceInformation(client : TootApiClient) : TootApiResult? { - val result = if( account.isMisskey){ - val params = JSONObject().apply{ - put("dummy",1) + val result = if(account.isMisskey) { + val params = JSONObject().apply { + put("dummy", 1) } - client.request("/api/meta",params.toPostRequestBuilder()) - }else{ + client.request("/api/meta", params.toPostRequestBuilder()) + } else { client.request("/api/v1/instance") } - instance_tmp = parseItem(::TootInstance,parser , result?.jsonObject) + instance_tmp = parseItem(::TootInstance, parser, result?.jsonObject) return result } @@ -255,15 +257,15 @@ class PostHelper( // 元の投稿を削除する if(redraft_status_id != null) { - result = if( isMisskey ){ - val params = account.putMisskeyApiToken(JSONObject()).apply{ - put("noteId",redraft_status_id) + result = if(isMisskey) { + val params = account.putMisskeyApiToken(JSONObject()).apply { + put("noteId", redraft_status_id) } client.request( "/api/notes/delete", params.toPostRequestBuilder() ) - }else{ + } else { client.request( "/api/v1/statuses/$redraft_status_id", Request.Builder().delete() @@ -283,16 +285,17 @@ class PostHelper( account.instance = instance } - if( visibility == TootVisibility.WebSetting) { - visibility_checked = if(account.isMisskey || instance.versionGE(TootInstance.VERSION_1_6)) { - null - } else { - val r2 = getCredential(client) - val credential_tmp = this.credential_tmp ?: return r2 - val privacy = credential_tmp.source?.privacy - ?: return TootApiResult(activity.getString(R.string.cant_get_web_setting_visibility)) - TootVisibility.parseMastodon(privacy) - } + if(visibility == TootVisibility.WebSetting) { + visibility_checked = + if(account.isMisskey || instance.versionGE(TootInstance.VERSION_1_6)) { + null + } else { + val r2 = getCredential(client) + val credential_tmp = this.credential_tmp ?: return r2 + val privacy = credential_tmp.source?.privacy + ?: return TootApiResult(activity.getString(R.string.cant_get_web_setting_visibility)) + TootVisibility.parseMastodon(privacy) + } } val json = JSONObject() @@ -308,34 +311,43 @@ class PostHelper( ) if(visibility_checked != null) { - if( visibility_checked == TootVisibility.DirectSpecified ){ + if(visibility_checked == TootVisibility.DirectSpecified) { val userIds = JSONArray() - val reMention = Pattern.compile("(?:\\A|\\s)@([a-zA-Z0-9_]{1,20})(?:@([\\w.:-]+))?(?:\\z|\\s)") + val reMention = + Pattern.compile("(?:\\A|\\s)@([a-zA-Z0-9_]{1,20})(?:@([\\w.:-]+))?(?:\\z|\\s)") val m = reMention.matcher(content) - while(m.find()){ + while(m.find()) { val username = m.group(1) val host = m.group(2) val queryParams = account.putMisskeyApiToken(JSONObject()) - if(username?.isNotEmpty()==true) queryParams.put("username",username) - if(host?.isNotEmpty()==true) queryParams.put("host",host) - result = client.request("/api/users/show",queryParams.toPostRequestBuilder()) + if(username?.isNotEmpty() == true) queryParams.put( + "username", + username + ) + if(host?.isNotEmpty() == true) queryParams.put("host", host) + result = client.request( + "/api/users/show", + queryParams.toPostRequestBuilder() + ) val id = result?.jsonObject?.parseString("id") - if( id?.isNotEmpty() == true ){ - userIds.put( id) + if(id?.isNotEmpty() == true) { + userIds.put(id) } } - json.put("visibility",if( userIds.length() == 0 ){ - "private" - }else{ - json.put("visibleUserIds",userIds) - "specified" - }) - }else { - json.put("visibility",visibility_checked.strMisskey) + json.put( + "visibility", if(userIds.length() == 0) { + "private" + } else { + json.put("visibleUserIds", userIds) + "specified" + } + ) + } else { + json.put("visibility", visibility_checked.strMisskey) } } - if(spoiler_text?.isNotEmpty() == true ) { + if(spoiler_text?.isNotEmpty() == true) { json.put( "cw", EmojiDecoder.decodeShortCode( @@ -349,7 +361,7 @@ class PostHelper( json.put("replyId", in_reply_to_id.toString()) } - json.put("viaMobile",true) + json.put("viaMobile", true) if(attachment_list != null) { val array = JSONArray() @@ -359,31 +371,34 @@ class PostHelper( array.put(a.id.toString()) // Misskeyの場合、NSFWするにはアップロード済みの画像を drive/files/update で更新する - if( bNSFW){ + if(bNSFW) { val params = account.putMisskeyApiToken(JSONObject()) - .put("fileId",a.id.toString()) - .put("isSensitive",true) - val r = client.request("/api/drive/files/update",params.toPostRequestBuilder()) - if(r==null|| r.error!=null ) return r + .put("fileId", a.id.toString()) + .put("isSensitive", true) + val r = client.request( + "/api/drive/files/update", + params.toPostRequestBuilder() + ) + if(r == null || r.error != null) return r } } - if( array.length() > 0) json.put("mediaIds", array) + if(array.length() > 0) json.put("mediaIds", array) } if(enquete_items?.isNotEmpty() == true) { val choices = JSONArray().apply { for(item in enquete_items) { - val text =EmojiDecoder.decodeShortCode( + val text = EmojiDecoder.decodeShortCode( item, emojiMapCustom = emojiMapCustom ) - if( text.isEmpty() ) continue + if(text.isEmpty()) continue put(text) } } - if( choices.length() > 0 ) { - json.put("poll",JSONObject().apply { - put("choices",choices) + if(choices.length() > 0) { + json.put("poll", JSONObject().apply { + put("choices", choices) }) } } @@ -453,19 +468,21 @@ class PostHelper( val digest = (body_string + account.acct).digestSHA256Hex() request_builder.header("Idempotency-Key", digest) } - - result = if(isMisskey){ + + result = if(isMisskey) { client.request("/api/notes/create", request_builder) // TODO {"error":{}} が返ってきた時にどう扱えばいい? - }else{ + } else { client.request("/api/v1/statuses", request_builder) } - val status = parser.status(if(isMisskey) { - result?.jsonObject?.optJSONObject("createdNote") ?: result?.jsonObject - }else{ - result?.jsonObject - }) + val status = parser.status( + if(isMisskey) { + result?.jsonObject?.optJSONObject("createdNote") ?: result?.jsonObject + } else { + result?.jsonObject + } + ) this.status = status if(status != null) { @@ -664,18 +681,21 @@ class PostHelper( et, last_colon, end, null, picker_caption_emoji, open_picker_emoji ) } else { - // 絵文字を部分一致で検索 + + val code_list = ArrayList() val limit = 100 - val s = src.substring(last_colon + 1, end).toLowerCase().replace('-', '_') - val code_list = EmojiDecoder.searchShortCode(activity, s, limit) - log.d("checkEmoji: search for %s, result=%d", s, code_list.size) // カスタム絵文字を検索 val instance = this@PostHelper.instance - if(instance != null && instance.isNotEmpty() ) { - val custom_list = App1.custom_emoji_lister.getList(instance,isMisskey, onEmojiListLoad) + if(instance != null && instance.isNotEmpty()) { + val custom_list = App1.custom_emoji_lister.getListWithAliases( + instance, + isMisskey, + onEmojiListLoad + ) if(custom_list != null) { val needle = src.substring(last_colon + 1, end) + for(item in custom_list) { if(code_list.size >= limit) break if(! item.shortcode.contains(needle)) continue @@ -689,14 +709,43 @@ class PostHelper( Spanned.SPAN_EXCLUSIVE_EXCLUSIVE ) sb.append(' ') + if(item.alias != null) { + val start = sb.length + sb.append(":") + sb.append(item.alias) + sb.append(": → ") + sb.setSpan( + ForegroundColorSpan( + Styler.getAttributeColor( + activity, + R.attr.colorTimeSmall + ) + ), + start, + sb.length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + sb.append(':') sb.append(item.shortcode) sb.append(':') + code_list.add(sb) } } } + // 通常の絵文字を部分一致で検索 + val remain = limit - code_list.size + if(remain > 0) { + val s = src.substring(last_colon + 1, end).toLowerCase().replace('-', '_') + val src = EmojiDecoder.searchShortCode(activity, s, remain) + log.d("checkEmoji: search for %s, result=%d", s, src.size) + code_list.addAll(src) + + } + openPopup()?.setList( et, last_colon, @@ -731,8 +780,8 @@ class PostHelper( this.instance = instance this.isMisskey = isMisskey - if(instance != null ) { - App1.custom_emoji_lister.getList(instance, isMisskey,onEmojiListLoad) + if(instance != null) { + App1.custom_emoji_lister.getList(instance, isMisskey, onEmojiListLoad) } val popup = this.popup @@ -841,7 +890,7 @@ class PostHelper( .appendEmoji(name, instance, bInstanceHasCustomEmoji) val newSelection = sb.length - if(end < src_length) sb.append(src.subSequence(end, src_length) ) + if(end < src_length) sb.append(src.subSequence(end, src_length)) et.text = sb et.setSelection(newSelection) @@ -864,11 +913,11 @@ class PostHelper( val end = Math.min(src_length, et.selectionEnd) val sb = SpannableStringBuilder() - .append(src.subSequence(0, start) ) + .append(src.subSequence(0, start)) .appendEmoji(name, instance, bInstanceHasCustomEmoji) val newSelection = sb.length - if(end < src_length) sb.append(src.subSequence(end, src_length) ) + if(end < src_length) sb.append(src.subSequence(end, src_length)) et.text = sb et.setSelection(newSelection)