From 7ceb9e4f51e62c019964eba7d32250b935b65a22 Mon Sep 17 00:00:00 2001 From: tateisu Date: Fri, 17 Aug 2018 04:58:30 +0900 Subject: [PATCH] =?UTF-8?q?=E8=A9=A6=E9=A8=93=E6=A9=9F=E8=83=BD=EF=BC=9Ami?= =?UTF-8?q?sskey=E3=81=AE=E3=82=BF=E3=83=B3=E3=82=B9=E3=82=92=E7=96=91?= =?UTF-8?q?=E4=BC=BC=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88=E3=81=A7?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=81=97=E3=81=A6=E3=80=81=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=82=AB=E3=83=ABTL=E3=81=A8=E3=82=B0=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=90=E3=83=ABTL=E3=82=92=E8=A6=8B=E3=82=8C=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/jp/juggler/subwaytooter/App1.kt | 3 +- .../java/jp/juggler/subwaytooter/Column.kt | 250 ++++++++++-- .../jp/juggler/subwaytooter/ItemViewHolder.kt | 8 +- .../subwaytooter/action/ActionUtils.kt | 21 +- .../subwaytooter/action/Action_Account.kt | 2 +- .../subwaytooter/api/TootAccountMap.kt | 1 + .../juggler/subwaytooter/api/TootApiClient.kt | 43 +- .../subwaytooter/api/entity/ServiceType.kt | 1 + .../subwaytooter/api/entity/TootAccount.kt | 243 ++++++----- .../api/entity/TootApplication.kt | 5 + .../subwaytooter/api/entity/TootStatus.kt | 376 ++++++++++++------ .../subwaytooter/table/SavedAccount.kt | 23 +- .../jp/juggler/subwaytooter/util/Utils.kt | 6 + app/src/main/res/raw/server_list.txt | 2 + 14 files changed, 701 insertions(+), 283 deletions(-) diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.kt b/app/src/main/java/jp/juggler/subwaytooter/App1.kt index 1d0b4804..588996e2 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.kt @@ -101,7 +101,8 @@ class App1 : Application() { // 2018/5/16 v252 24=>25 SubscriptionServerKey テーブルを追加 // 2018/5/16 v252 25=>26 SubscriptionServerKey テーブルを丸ごと変更 // 2018/8/5 v264 26 => 27 SavedAccountテーブルに項目追加 - internal const val DB_VERSION = 27 + // 2018/8/17 v267 27 => 28 SavedAccountテーブルに項目追加 + internal const val DB_VERSION = 28 private val tableList = arrayOf( LogData, diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/Column.kt index c2c9fc3e..cf5fbd88 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.kt @@ -7,19 +7,17 @@ import android.os.AsyncTask import android.os.SystemClock import android.view.Gravity import jp.juggler.subwaytooter.api.* - -import org.json.JSONException -import org.json.JSONObject - -import java.lang.ref.WeakReference -import java.util.concurrent.atomic.AtomicBoolean -import java.util.regex.Pattern - import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.table.* import jp.juggler.subwaytooter.util.* +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.lang.ref.WeakReference import java.text.SimpleDateFormat import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import java.util.regex.Pattern enum class StreamingIndicatorState { NONE, @@ -309,6 +307,9 @@ class Column( private val streamPath : String? get() { + // misskeyの疑似アカウントはストリーミング対応していない + if(access_info.isPseudo && access_info.isMisskey) return null + return when(column_type) { TYPE_HOME, TYPE_NOTIFICATIONS -> "/api/v1/streaming/?stream=user" TYPE_LOCAL -> "/api/v1/streaming/?stream=public:local" @@ -320,7 +321,7 @@ class Column( TYPE_HASHTAG -> when(instance_local) { true -> "/api/v1/streaming/?stream=" + Uri.encode("hashtag:local") + "&tag=" + hashtag.encodePercent() else -> "/api/v1/streaming/?stream=hashtag&tag=" + hashtag.encodePercent() - // タグ先頭の#を含まない + // タグ先頭の#を含まない } else -> null } @@ -816,7 +817,7 @@ class Column( } } - fun removeUser(targetAccount : SavedAccount,columnType:Int,who_id:Long){ + fun removeUser(targetAccount : SavedAccount, columnType : Int, who_id : Long) { if(column_type == columnType && targetAccount.acct == access_info.acct) { val tmp_list = ArrayList(list_data.size) for(o in list_data) { @@ -1502,13 +1503,36 @@ class Column( log.d("getStatusesPinned: list size=%s", list_pinned?.size ?: - 1) } - fun getStatuses(client : TootApiClient, path_base : String) : TootApiResult? { + fun getStatuses( + client : TootApiClient, + path_base : String, + isMisskey : Boolean = false + ) : TootApiResult? { + + val params = JSONObject() + if(isMisskey) { + parser.serviceType = ServiceType.MISSKEY + params.put("limit", 100) + if(with_attachment) { + params.put("mediaOnly", true) + } + } val time_start = SystemClock.elapsedRealtime() - val result = client.request(path_base) + val result = if(isMisskey) { + client.request(path_base, params.toPostRequestBuilder()) + } else { + client.request(path_base) + } + var jsonArray = result?.jsonArray if(jsonArray != null) { - saveRange(result, true, true) + if(isMisskey) { + saveRangeMisskey(jsonArray, true, true) + } else { + saveRange(result, true, true) + } + // var src = parser.statusList(jsonArray) @@ -1540,9 +1564,16 @@ class Column( log.d("loading-statuses: timeout.") break } - val path = path_base + delimiter + "max_id=" + max_id - val result2 = client.request(path) + val result2 = if(isMisskey) { + params.put("untilId", max_id) + client.request(path_base, params.toPostRequestBuilder()) + } else { + val path = path_base + delimiter + "max_id=" + max_id + client.request(path) + } + jsonArray = result2?.jsonArray + if(jsonArray == null) { log.d("loading-statuses: error or cancelled.") break @@ -1552,9 +1583,16 @@ class Column( addWithFilterStatus(list_tmp, src) - if(! saveRangeEnd(result2)) { - log.d("loading-statuses: missing range info.") - break + if(isMisskey) { + if(! saveRangeEndMisskey(jsonArray)) { + log.d("loading-statuses: missing range info.") + break + } + } else { + if(! saveRangeEnd(result2)) { + log.d("loading-statuses: missing range info.") + break + } } } } @@ -1748,9 +1786,22 @@ class Column( TYPE_DIRECT_MESSAGES -> return getStatuses(client, PATH_DIRECT_MESSAGES) - TYPE_LOCAL -> return getStatuses(client, makePublicLocalUrl()) - - TYPE_FEDERATE -> return getStatuses(client, makePublicFederateUrl()) + TYPE_LOCAL -> return when(access_info.isMisskey) { + true -> getStatuses( + client, + "/api/notes/local-timeline", + isMisskey = true + ) + else -> getStatuses(client, makePublicLocalUrl()) + } + TYPE_FEDERATE -> return when(access_info.isMisskey) { + true -> getStatuses( + client, + "/api/notes/global-timeline", + isMisskey = true + ) + else -> return getStatuses(client, makePublicFederateUrl()) + } TYPE_PROFILE -> { @@ -1903,7 +1954,10 @@ class Column( // } else { this.list_tmp = addOne(this.list_tmp, target_status) - this.list_tmp = addOne(this.list_tmp, TootMessageHolder(context.getString(R.string.toot_context_parse_failed))) + this.list_tmp = addOne( + this.list_tmp, + TootMessageHolder(context.getString(R.string.toot_context_parse_failed)) + ) } // カードを取得する @@ -2132,6 +2186,31 @@ class Column( } } + private fun saveRangeMisskey(src : JSONArray?, bBottom : Boolean, bTop : Boolean) { + src ?: return + var id_min : String? = null + var id_max : String? = null + for(i in 0 until src.length()) { + val id = src.optJSONObject(i)?.optString("id", null) ?: continue + if(id_min == null || id < id_min) id_min = id + if(id_max == null || id > id_max) id_max = id + } + if(bBottom) { + when { + id_min == null -> max_id = "" + max_id.isEmpty() || id_min < max_id -> max_id = id_min + } + } + if(bTop) { + when { + id_max == null -> { + } + + since_id.isEmpty() || id_max > since_id -> since_id = id_max + } + } + } + private fun saveRangeEnd(result : TootApiResult?) : Boolean { if(result != null) { if(result.link_older == null) { @@ -2147,6 +2226,23 @@ class Column( return false } + private fun saveRangeEndMisskey(src : JSONArray?) : Boolean { + if(src != null) { + var id_min : String? = null + for(i in 0 until src.length()) { + val id = src.optJSONObject(i)?.optString("id", null) ?: continue + if(id_min == null || id < id_min) id_min = id + } + if(id_min?.isEmpty() != false) { + max_id = "" + } else { + max_id = id_min + return true + } + } + return false + } + private fun addRange(bBottom : Boolean, path : String) : String { val delimiter = if(- 1 != path.indexOf('?')) '&' else '?' if(bBottom) { @@ -2610,18 +2706,43 @@ class Column( fun getStatusList( client : TootApiClient, - path_base : String + path_base : String, + isMisskey : Boolean = false ) : TootApiResult? { + val params = JSONObject() + if(isMisskey) { + parser.serviceType = ServiceType.MISSKEY + params.put("limit", 100) + if(with_attachment) { + params.put("mediaOnly", true) + } + } + val time_start = SystemClock.elapsedRealtime() val delimiter = if(- 1 != path_base.indexOf('?')) '&' else '?' val last_since_id = since_id - val result = client.request(addRange(bBottom, path_base)) + val result = if(isMisskey) { + if(bBottom) { + if(max_id.isNotEmpty()) params.put("untilId", max_id) + } else { + if(since_id.isNotEmpty()) params.put("sinceId", since_id) + } + client.request(path_base, params.toPostRequestBuilder()) + } else { + client.request(addRange(bBottom, path_base)) + } + var jsonArray = result?.jsonArray if(jsonArray != null) { - saveRange(result, bBottom, ! bBottom) + if(isMisskey) { + saveRangeMisskey(jsonArray, bBottom, ! bBottom) + } else { + saveRange(result, bBottom, ! bBottom) + } + var src = parser.statusList(jsonArray) list_tmp = addWithFilterStatus(null, src) @@ -2657,8 +2778,14 @@ class Column( break } - val path = path_base + delimiter + "max_id=" + max_id - val result2 = client.request(path) + val result2 = if(isMisskey) { + params.put("untilId", max_id) + client.request(path_base, params.toPostRequestBuilder()) + } else { + val path = path_base + delimiter + "max_id=" + max_id + client.request(path) + } + jsonArray = result2?.jsonArray if(jsonArray == null) { log.d("refresh-status-bottom: error or cancelled.") @@ -2669,9 +2796,16 @@ class Column( addWithFilterStatus(list_tmp, src) - if(! saveRangeEnd(result2)) { - log.d("refresh-status-bottom: saveRangeEnd failed.") - break + if(isMisskey) { + if(! saveRangeEndMisskey(jsonArray)) { + log.d("refresh-status-bottom: saveRangeEnd failed.") + break + } + } else { + if(! saveRangeEnd(result2)) { + log.d("refresh-status-bottom: saveRangeEnd failed.") + break + } } } } else { @@ -2711,6 +2845,13 @@ class Column( break } + if(isMisskey) { + log.d("refresh-status-offset: misskey does not allows gap reading.") + addOne(list_tmp, TootGap(max_id, last_since_id)) + bGapAdded = true + break + } + val path = path_base + delimiter + "max_id=" + max_id + "&since_id=" + last_since_id val result2 = client.request(path) @@ -2767,9 +2908,23 @@ class Column( TYPE_DIRECT_MESSAGES -> getStatusList(client, PATH_DIRECT_MESSAGES) - TYPE_LOCAL -> getStatusList(client, makePublicLocalUrl()) + TYPE_LOCAL -> when(access_info.isMisskey) { + true -> getStatusList( + client, + "/api/notes/local-timeline", + isMisskey = true + ) + else -> getStatusList(client, makePublicLocalUrl()) + } - TYPE_FEDERATE -> getStatusList(client, makePublicFederateUrl()) + TYPE_FEDERATE -> when(access_info.isMisskey) { + true -> getStatusList( + client, + "/api/notes/global-timeline", + isMisskey = true + ) + else -> getStatusList(client, makePublicFederateUrl()) + } TYPE_FAVOURITES -> getStatusList(client, PATH_FAVOURITES) @@ -3015,15 +3170,15 @@ class Column( // val scroll_save = this@Column.scroll_save when { - // ViewHolderがある場合は増加件数分+deltaの位置にスクロールする + // ViewHolderがある場合は増加件数分+deltaの位置にスクロールする sp != null -> { sp.adapterIndex += added val delta = if(bSilent) 0f else - 20f holder?.setScrollPosition(sp, delta) } - // ViewHolderがなくて保存中の位置がある場合、増加件数分ずらす。deltaは難しいので反映しない + // ViewHolderがなくて保存中の位置がある場合、増加件数分ずらす。deltaは難しいので反映しない scroll_save != null -> scroll_save.adapterIndex += added - // 保存中の位置がない場合、保存中の位置を新しく作る + // 保存中の位置がない場合、保存中の位置を新しく作る else -> this@Column.scroll_save = ScrollPosition(toAdapterIndex(added), 0) } @@ -3054,6 +3209,8 @@ class Column( return } + + viewHolder?.refreshLayout?.isRefreshing = true bRefreshLoading = true @@ -3557,9 +3714,9 @@ class Column( log.d("onStart: column is in initial loading.") return } - + // フィルタ一覧のリロードが必要 - if( filter_reload_required ){ + if(filter_reload_required) { filter_reload_required = false startLoading() return @@ -3659,8 +3816,11 @@ class Column( return canStreaming() && column_type != TYPE_NOTIFICATIONS } - internal fun canStreaming() : Boolean { - return ! access_info.isNA && if(access_info.isPseudo) isPublicStream else streamPath != null + internal fun canStreaming() = when { + access_info.isNA -> false + access_info.isMisskey -> false + access_info.isPseudo -> isPublicStream + else -> streamPath != null } private val streamCallback = object : StreamReader.StreamCallback { @@ -3918,6 +4078,7 @@ class Column( } else { PATH_LOCAL } + } private fun makePublicFederateUrl() : String { @@ -3940,7 +4101,7 @@ class Column( } private fun loadFilter2(client : TootApiClient) : ArrayList? { - if( access_info.isPseudo ) return null + if(access_info.isPseudo) return null val column_context = getFilterContext() if(column_context == 0) return null val result = client.request(PATH_FILTERS) @@ -3955,10 +4116,12 @@ class Column( val tree = WordTrieTree() for(filter in filterList) { if((filter.context and column_context) != 0) { - tree.add(filter.phrase,validator = when(filter.whole_word){ - true -> WordTrieTree.WORD_VALIDATOR + tree.add( + filter.phrase, validator = when(filter.whole_word) { + true -> WordTrieTree.WORD_VALIDATOR else -> WordTrieTree.EMPTY_VALIDATOR - }) + } + ) } } return tree @@ -4023,5 +4186,4 @@ class Column( } } - } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt index 077ece4f..2922b6fc 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt @@ -1028,7 +1028,13 @@ internal class ItemViewHolder( } btnSearchTag, llTrendTag -> when(item) { - is TootGap -> column.startGap(item) + is TootGap -> { + if( access_info.isMisskey){ + showToast(activity,false, "Misskey does not allows gap reading.") + }else { + column.startGap(item) + } + } is TootDomainBlock -> { val domain = item.domain diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt b/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt index 9543bd1a..c70685d5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt @@ -25,7 +25,7 @@ internal fun findAccountByName( ) { TootTaskRunner(activity).run(access_info, object : TootTask { - internal var who : TootAccount? = null + var who : TootAccount? = null override fun background(client : TootApiClient) : TootApiResult? { @@ -38,10 +38,9 @@ internal fun findAccountByName( for(i in 0 until array.length()) { val a = parser.account(array.optJSONObject(i)) if(a != null) { - if(a.username == user && access_info.getFullAcct(a).equals( - user + "@" + host, - ignoreCase = true - )) { + if(a.username == user + && access_info.getFullAcct(a).equals("$user@$host", ignoreCase = true) + ) { who = a break } @@ -62,11 +61,14 @@ internal fun findAccountByName( // 既に存在する場合は再利用する // 実アカウントを返すことはない internal fun addPseudoAccount( - context : Context, host : String + context : Context, + host : String, + isMisskey : Boolean = false ) : SavedAccount? { + try { val username = "?" - val full_acct = username + "@" + host + val full_acct = "$username@$host" var account = SavedAccount.loadAccountByAcct(context, full_acct) if(account != null) { @@ -77,7 +79,8 @@ internal fun addPseudoAccount( account_info.put("username", username) account_info.put("acct", username) - val row_id = SavedAccount.insert(host, full_acct, account_info, JSONObject()) + val row_id = + SavedAccount.insert(host, full_acct, account_info, JSONObject(), isMisskey = isMisskey) account = SavedAccount.loadAccount(context, row_id) if(account == null) { throw RuntimeException("loadAccount returns null.") @@ -132,7 +135,7 @@ internal fun loadRelation1( client : TootApiClient, access_info : SavedAccount, who_id : Long ) : RelationResult { val rr = RelationResult() - rr.result = client.request("/api/v1/accounts/relationships?id=" + who_id) + rr.result = client.request("/api/v1/accounts/relationships?id=$who_id") val r2 = rr.result val jsonArray = r2?.jsonArray if(jsonArray != null) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt index 0d8600af..e69536e7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt @@ -95,7 +95,7 @@ object Action_Account { } else { // 疑似アカウントを追加 - val a = addPseudoAccount(activity, instance) + val a = addPseudoAccount(activity, instance, data.optBoolean("isMisskey",false)) if(a != null) { showToast(activity, false, R.string.server_confirmed) val pos = App1.getAppState(activity).column_list.size diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt index 7d8c8733..3d7bce3d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt @@ -18,6 +18,7 @@ object TootAccountMap{ ServiceType.MASTODON -> requireNotNull(parser.linkHelper.host) ServiceType.TOOTSEARCH -> "?tootsearch" ServiceType.MSP -> "?msp" + ServiceType.MISSKEY -> "?misskey" } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt index 2e645072..35cfffd4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt @@ -445,10 +445,36 @@ class TootApiClient( fun getInstanceInformation() : TootApiResult? { val result = TootApiResult.makeWithCaption(instance) if(result.error != null) return result - if(! sendRequest(result) { + + if(sendRequest(result) { Request.Builder().url("https://$instance/api/v1/instance").build() - }) return result - return parseJson(result) + } + && parseJson(result) != null + && result.jsonObject != null + ) { + // インスタンス情報のjsonを読めたらマストドンのインスタンス + return result + } + + // misskeyか試してみる + val r2 = TootApiResult.makeWithCaption(instance) + if(sendRequest(r2) { + Request.Builder().post(RequestBody.create(MEDIA_TYPE_JSON,JSONObject().apply{ + put("dummy",1) + }.toString())) + .url("https://$instance/api/notes/local-timeline").build() + } + ) { + if(parseJson(r2) != null && r2.jsonArray != null) { + r2.data = JSONObject().apply{ + put("isMisskey", true) + } + return r2 + } + } + + // misskeyの事は忘れて本来のエラー情報を返す + return result } // インスタンス情報を取得する @@ -845,7 +871,7 @@ class TootApiClient( //////////////////////////////////////////////////////////////////////// // JSONデータ以外を扱うリクエスト - fun http(req:Request) : TootApiResult? { + fun http(req : Request) : TootApiResult? { val result = TootApiResult.makeWithCaption(req.url().host()) if(result.error != null) return result @@ -853,20 +879,19 @@ class TootApiClient( return result } - fun requestJson(req:Request) : TootApiResult? { + fun requestJson(req : Request) : TootApiResult? { val result = TootApiResult.makeWithCaption(req.url().host()) if(result.error != null) return result - if( sendRequest(result, progressPath = null) { req } ){ + if(sendRequest(result, progressPath = null) { req }) { parseJson(result) } return result } - // 疑似アカウントでステータスURLからステータスIDを取得するためにHTMLを取得する - fun getHttp(url:String): TootApiResult? { + fun getHttp(url : String) : TootApiResult? { val result = http(Request.Builder().url(url).build()) - if(result !=null && result.error == null){ + if(result != null && result.error == null) { parseString(result) } return result diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/ServiceType.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/ServiceType.kt index cda6b0e0..d080d6e3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/ServiceType.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/ServiceType.kt @@ -4,4 +4,5 @@ enum class ServiceType { MASTODON, TOOTSEARCH, MSP, + MISSKEY, } 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 91d00c81..187f9503 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 @@ -90,106 +90,155 @@ open class TootAccount( init { var sv : String? - // 絵文字データは先に読んでおく - this.custom_emojis = parseMapOrNull(::CustomEmoji, src.optJSONArray("emojis")) - this.profile_emojis = parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis")) - - // 疑似アカウントにacctとusernameだけ - this.url = src.parseString("url") - this.username = src.notEmptyOrThrow("username") - - // - sv = src.parseString("display_name") - this.display_name = if(sv?.isNotEmpty() == true) sv.sanitizeBDI() else username - - // - this.note = src.parseString("note") - - this.source = parseSource(src.optJSONObject("source")) - this.movedRef = TootAccountRef.mayNull( - parser, - src.optJSONObject("moved")?.let { - TootAccount(parser, it) - } - ) - this.locked = src.optBoolean("locked") - - this.fields = parseFields(src.optJSONArray("fields")) - - this.bot = src.optBoolean("bot", false) - - // this.user_hides_network = src.optBoolean("user_hides_network") - - when(parser.serviceType) { - ServiceType.MASTODON -> { - - val hostAccess = parser.linkHelper.host - - this.id = src.parseLong("id") ?: INVALID_ID - - this.acct = src.notEmptyOrThrow("acct") - this.host = findHostFromUrl(acct, hostAccess, url) - ?: throw RuntimeException("can't get host from acct or url") - - this.followers_count = src.parseLong("followers_count") - this.following_count = src.parseLong("following_count") - this.statuses_count = src.parseLong("statuses_count") - - this.created_at = src.parseString("created_at") - this.time_created_at = TootStatus.parseTime(this.created_at) - - this.avatar = src.parseString("avatar") - this.avatar_static = src.parseString("avatar_static") - this.header = src.parseString("header") - this.header_static = src.parseString("header_static") - - } + if(parser.serviceType == ServiceType.MISSKEY) { - ServiceType.TOOTSEARCH -> { - // tootsearch のアカウントのIDはどのタンス上のものか分からないので役に立たない - this.id = INVALID_ID // src.parseLong( "id", INVALID_ID) - - sv = src.notEmptyOrThrow("acct") - this.host = findHostFromUrl(sv, null, url) - ?: throw RuntimeException("can't get host from acct or url") - this.acct = this.username + "@" + this.host - - this.followers_count = src.parseLong("followers_count") - this.following_count = src.parseLong("following_count") - this.statuses_count = src.parseLong("statuses_count") - - this.created_at = src.parseString("created_at") - this.time_created_at = TootStatus.parseTime(this.created_at) - - this.avatar = src.parseString("avatar") - this.avatar_static = src.parseString("avatar_static") - this.header = src.parseString("header") - this.header_static = src.parseString("header_static") - } + val instance = src.parseString("host") ?: parser.linkHelper.host ?: error("missing host") - ServiceType.MSP -> { - this.id = src.parseLong("id") ?: INVALID_ID - - // MSPはLTLの情報しか持ってないのでacctは常にホスト名部分を持たない - this.host = findHostFromUrl(null, null, url) - ?: throw RuntimeException("can't get host from url") - this.acct = this.username + "@" + host - - this.followers_count = null - this.following_count = null - this.statuses_count = null - - this.created_at = null - this.time_created_at = 0L - - val avatar = src.parseString("avatar") - this.avatar = avatar - this.avatar_static = avatar - this.header = null - this.header_static = null - - } + this.custom_emojis = null + this.profile_emojis = null + this.username = src.notEmptyOrThrow("username") + this.url = "https://$instance/@$username" + + // + sv = src.parseString("name") + this.display_name = if(sv?.isNotEmpty() == true) sv.sanitizeBDI() else username + + // + this.note = src.parseString("description") + + this.source = null + this.movedRef = null + this.locked = src.optBoolean("isLocked") + + this.fields = null + + this.bot = src.optBoolean("isBot", false) + + // this.user_hides_network = src.optBoolean("user_hides_network") + + this.id = INVALID_ID + + this.acct = "$username@$instance" + this.host = instance + + this.followers_count = src.parseLong("followersCount") ?: -1L + this.following_count = src.parseLong("followingCount") ?: -1L + this.statuses_count = src.parseLong("notesCount") ?: -1L + + this.created_at = src.parseString("createdAt") + this.time_created_at = TootStatus.parseTime(this.created_at) + + this.avatar = src.parseString("avatarUrl") + this.avatar_static = src.parseString("avatarUrl") + this.header =src.parseString("bannerUrl") + this.header_static = src.parseString("bannerUrl") + + } else { + + // 絵文字データは先に読んでおく + this.custom_emojis = parseMapOrNull(::CustomEmoji, src.optJSONArray("emojis")) + this.profile_emojis = + parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis")) + + // 疑似アカウントにacctとusernameだけ + this.url = src.parseString("url") + this.username = src.notEmptyOrThrow("username") + + // + sv = src.parseString("display_name") + this.display_name = if(sv?.isNotEmpty() == true) sv.sanitizeBDI() else username + + // + this.note = src.parseString("note") + + this.source = parseSource(src.optJSONObject("source")) + this.movedRef = TootAccountRef.mayNull( + parser, + src.optJSONObject("moved")?.let { + TootAccount(parser, it) + } + ) + this.locked = src.optBoolean("locked") + + this.fields = parseFields(src.optJSONArray("fields")) + + this.bot = src.optBoolean("bot", false) + + // this.user_hides_network = src.optBoolean("user_hides_network") + + when(parser.serviceType) { + ServiceType.MASTODON -> { + + val hostAccess = parser.linkHelper.host + + this.id = src.parseLong("id") ?: INVALID_ID + + this.acct = src.notEmptyOrThrow("acct") + this.host = findHostFromUrl(acct, hostAccess, url) + ?: throw RuntimeException("can't get host from acct or url") + + this.followers_count = src.parseLong("followers_count") + this.following_count = src.parseLong("following_count") + this.statuses_count = src.parseLong("statuses_count") + + this.created_at = src.parseString("created_at") + this.time_created_at = TootStatus.parseTime(this.created_at) + + this.avatar = src.parseString("avatar") + this.avatar_static = src.parseString("avatar_static") + this.header = src.parseString("header") + this.header_static = src.parseString("header_static") + + } + + ServiceType.TOOTSEARCH -> { + // tootsearch のアカウントのIDはどのタンス上のものか分からないので役に立たない + this.id = INVALID_ID // src.parseLong( "id", INVALID_ID) + + sv = src.notEmptyOrThrow("acct") + this.host = findHostFromUrl(sv, null, url) + ?: throw RuntimeException("can't get host from acct or url") + this.acct = this.username + "@" + this.host + + this.followers_count = src.parseLong("followers_count") + this.following_count = src.parseLong("following_count") + this.statuses_count = src.parseLong("statuses_count") + + this.created_at = src.parseString("created_at") + this.time_created_at = TootStatus.parseTime(this.created_at) + + this.avatar = src.parseString("avatar") + this.avatar_static = src.parseString("avatar_static") + this.header = src.parseString("header") + this.header_static = src.parseString("header_static") + } + + ServiceType.MSP -> { + this.id = src.parseLong("id") ?: INVALID_ID + + // MSPはLTLの情報しか持ってないのでacctは常にホスト名部分を持たない + this.host = findHostFromUrl(null, null, url) + ?: throw RuntimeException("can't get host from url") + this.acct = this.username + "@" + host + + this.followers_count = null + this.following_count = null + this.statuses_count = null + + this.created_at = null + this.time_created_at = 0L + + val avatar = src.parseString("avatar") + this.avatar = avatar + this.avatar_static = avatar + this.header = null + this.header_static = null + + } + + else -> error("will not happen") + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootApplication.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootApplication.kt index e08e43d1..f0316a1c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootApplication.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootApplication.kt @@ -13,4 +13,9 @@ class TootApplication( name = src.parseString("name"), website = src.parseString("website") ) + + constructor(src:String?):this( + name = src, + website = null + ) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt index 1d7de830..a21f0a77 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt @@ -18,8 +18,10 @@ import jp.juggler.subwaytooter.api.TootParser import jp.juggler.subwaytooter.table.HighlightWord import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.util.* +import org.json.JSONArray import java.text.SimpleDateFormat import java.util.* +import kotlin.collections.ArrayList @Suppress("MemberVisibilityCanPrivate") class TootStatus(parser : TootParser, src : JSONObject) : @@ -155,133 +157,271 @@ class TootStatus(parser : TootParser, src : JSONObject) : init { this.json = src - this.uri = src.parseString("uri") // MSPだとuriは提供されない - this.url = src.parseString("url") // 頻繁にnullになる - this.created_at = src.parseString("created_at") - - // 絵文字マップはすぐ後で使うので、最初の方で読んでおく - this.custom_emojis = parseMapOrNull(::CustomEmoji, src.optJSONArray("emojis"), log) - this.profile_emojis = - parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis"), log) - - val who = parser.account(src.optJSONObject("account")) - ?: throw RuntimeException("missing account") - - this.accountRef = TootAccountRef(parser, who) - - this.reblogs_count = src.parseLong("reblogs_count") - this.favourites_count = src.parseLong("favourites_count") - this.replies_count = src.parseLong("replies_count") + if( parser.serviceType == ServiceType.MISSKEY) { + val instance = parser.linkHelper.host + val misskeyId = src.parseString("id") + this.host_access = parser.linkHelper.host + + this.uri = "https://$instance/notes/$misskeyId" + this.url = "https://$instance/notes/$misskeyId" + this.created_at = src.parseString("createdAt") + this.time_created_at = parseTime(this.created_at) + this.id = INVALID_ID + + // 絵文字マップはすぐ後で使うので、最初の方で読んでおく + this.custom_emojis = null + this.profile_emojis = null + + val who = parser.account(src.optJSONObject("user")) + ?: throw RuntimeException("missing account") + + this.accountRef = TootAccountRef(parser, who) + + this.reblogs_count = 0L + this.favourites_count = 0L + this.replies_count = 0L + + this.reblogged = false + this.favourited = false + + this.media_attachments = parseMediaAttachmentMisskey(src.optJSONArray("media")) + this.visibility = src.parseString("visibility") + this.sensitive = src.optBoolean("sensitive") + + + this.in_reply_to_id = null + this.in_reply_to_account_id = null + this.mentions = null + this.tags = null + this.application = parseItem(::TootApplication, src.optJSONObject("appId"), log) + this.pinned = parser.pinned + this.muted = false + this.language = null + + this.decoded_mentions = HTMLDecoder.decodeMentions( + parser.linkHelper, + this.mentions, + this + ) ?: EMPTY_SPANNABLE + + // this.decoded_tags = HTMLDecoder.decodeTags( account,status.tags ); + + // content + this.content = src.parseString("text") + + var options = DecodeOptions( + parser.context, + parser.linkHelper, + short = true, + decodeEmoji = true, + emojiMapCustom = custom_emojis, + emojiMapProfile = profile_emojis, + attachmentList = media_attachments, + highlightTrie = parser.highlightTrie + ) + + this.decoded_content = options.decodeHTML(content) + this.hasHighlight = this.hasHighlight || options.hasHighlight + if(options.highlight_sound != null && this.highlight_sound == null) { + this.highlight_sound = options.highlight_sound + } + + // spoiler_text + this.spoiler_text = reWhitespace + .matcher(src.parseString("cw") ?: "") + .replaceAll(" ") + .sanitizeBDI() + + options = DecodeOptions( + parser.context, + emojiMapCustom = custom_emojis, + emojiMapProfile = profile_emojis, + highlightTrie = parser.highlightTrie + ) + + this.decoded_spoiler_text = options.decodeEmoji(spoiler_text) + + this.hasHighlight = this.hasHighlight || options.hasHighlight + if(options.highlight_sound != null && this.highlight_sound == null) { + this.highlight_sound = options.highlight_sound + } + + this.enquete = null + + this.reblog = parser.status(src.optJSONObject("renote")) - when(parser.serviceType) { - ServiceType.MASTODON -> { - this.host_access = parser.linkHelper.host + }else{ + this.uri = src.parseString("uri") // MSPだとuriは提供されない + this.url = src.parseString("url") // 頻繁にnullになる + this.created_at = src.parseString("created_at") + + // 絵文字マップはすぐ後で使うので、最初の方で読んでおく + this.custom_emojis = parseMapOrNull(::CustomEmoji, src.optJSONArray("emojis"), log) + this.profile_emojis = + parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis"), log) + + val who = parser.account(src.optJSONObject("account")) + ?: throw RuntimeException("missing account") + + this.accountRef = TootAccountRef(parser, who) + + this.reblogs_count = src.parseLong("reblogs_count") + this.favourites_count = src.parseLong("favourites_count") + this.replies_count = src.parseLong("replies_count") + + when(parser.serviceType) { + ServiceType.MASTODON -> { + this.host_access = parser.linkHelper.host + + this.id = src.parseLong("id") ?: INVALID_ID + + this.reblogged = src.optBoolean("reblogged") + this.favourited = src.optBoolean("favourited") + + this.time_created_at = parseTime(this.created_at) + this.media_attachments = + parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log) + this.visibility = src.parseString("visibility") + this.sensitive = src.optBoolean("sensitive") + + } - this.id = src.parseLong("id") ?: INVALID_ID - - this.reblogged = src.optBoolean("reblogged") - this.favourited = src.optBoolean("favourited") - - this.time_created_at = parseTime(this.created_at) - this.media_attachments = - parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log) - this.visibility = src.parseString("visibility") - this.sensitive = src.optBoolean("sensitive") + ServiceType.TOOTSEARCH -> { + this.host_access = null + + // 投稿元タンスでのIDを調べる。失敗するかもしれない + this.id = findStatusIdFromUri(uri, url) + + this.time_created_at = TootStatus.parseTime(this.created_at) + this.media_attachments = + parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log) + this.visibility = VISIBILITY_PUBLIC + this.sensitive = src.optBoolean("sensitive") + + } + ServiceType.MSP -> { + this.host_access = null + + // MSPのデータはLTLから呼んだものなので、常に投稿元タンスでのidが得られる + this.id = src.parseLong("id") ?: INVALID_ID + + this.time_created_at = parseTimeMSP(created_at) + this.media_attachments = + TootAttachmentMSP.parseList(src.optJSONArray("media_attachments")) + this.visibility = VISIBILITY_PUBLIC + this.sensitive = src.optInt("sensitive", 0) != 0 + } + else-> error("will not happen") } - ServiceType.TOOTSEARCH -> { - this.host_access = null - - // 投稿元タンスでのIDを調べる。失敗するかもしれない - this.id = findStatusIdFromUri(uri, url) - - this.time_created_at = TootStatus.parseTime(this.created_at) - this.media_attachments = - parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log) - this.visibility = VISIBILITY_PUBLIC - this.sensitive = src.optBoolean("sensitive") - + this.in_reply_to_id = src.parseString("in_reply_to_id") + this.in_reply_to_account_id = src.parseString("in_reply_to_account_id") + this.mentions = parseListOrNull(::TootMention, src.optJSONArray("mentions"), log) + this.tags = parseListOrNull(::TootTag, src.optJSONArray("tags")) + this.application = parseItem(::TootApplication, src.optJSONObject("application"), log) + this.pinned = parser.pinned || src.optBoolean("pinned") + this.muted = src.optBoolean("muted") + this.language = src.parseString("language") + this.decoded_mentions = HTMLDecoder.decodeMentions( + parser.linkHelper, + this.mentions, + this + ) ?: EMPTY_SPANNABLE + // this.decoded_tags = HTMLDecoder.decodeTags( account,status.tags ); + + // content + this.content = src.parseString("content") + + var options = DecodeOptions( + parser.context, + parser.linkHelper, + short = true, + decodeEmoji = true, + emojiMapCustom = custom_emojis, + emojiMapProfile = profile_emojis, + attachmentList = media_attachments, + highlightTrie = parser.highlightTrie + ) + + this.decoded_content = options.decodeHTML(content) + this.hasHighlight = this.hasHighlight || options.hasHighlight + if(options.highlight_sound != null && this.highlight_sound == null) { + this.highlight_sound = options.highlight_sound } - ServiceType.MSP -> { - this.host_access = null - - // MSPのデータはLTLから呼んだものなので、常に投稿元タンスでのidが得られる - this.id = src.parseLong("id") ?: INVALID_ID - - this.time_created_at = parseTimeMSP(created_at) - this.media_attachments = - TootAttachmentMSP.parseList(src.optJSONArray("media_attachments")) - this.visibility = VISIBILITY_PUBLIC - this.sensitive = src.optInt("sensitive", 0) != 0 + // spoiler_text + this.spoiler_text = reWhitespace + .matcher(src.parseString("spoiler_text") ?: "") + .replaceAll(" ") + .sanitizeBDI() + + options = DecodeOptions( + parser.context, + emojiMapCustom = custom_emojis, + emojiMapProfile = profile_emojis, + highlightTrie = parser.highlightTrie + ) + + this.decoded_spoiler_text = options.decodeEmoji(spoiler_text) + + this.hasHighlight = this.hasHighlight || options.hasHighlight + if(options.highlight_sound != null && this.highlight_sound == null) { + this.highlight_sound = options.highlight_sound + } + + this.enquete = NicoEnquete.parse( + parser, + this, + media_attachments, + src.parseString("enquete") + ) + + // Pinned TL を取得した時にreblogが登場することはないので、reblogについてpinned 状態を気にする必要はない + this.reblog = parser.status(src.optJSONObject("reblog")) + + } + } + + private fun parseMediaAttachmentMisskey2(src : JSONObject?) : TootAttachment? { + src?: return null + + val mimeType = src.parseString("type") + val url = src.parseString("url") + val thumbnailUrl = src.parseString("thumbnailUrl") + val dst = JSONObject() + dst.put("id",-1L) + dst.put("type", when{ + mimeType?.startsWith("image/") ==true -> TootAttachmentLike.TYPE_IMAGE + mimeType?.startsWith("video/") ==true -> TootAttachmentLike.TYPE_VIDEO + else-> TootAttachmentLike.TYPE_UNKNOWN + }) + dst.put("url",url) + dst.put("remote_url",url) + dst.put("text_url",url) + dst.put("preview_url",thumbnailUrl) + dst.put("description",src.parseString("comment")) + + return parseItem(::TootAttachment,dst) + } + + private fun parseMediaAttachmentMisskey(src : JSONArray?) : ArrayList? { + var rv :ArrayList? = null + if(src!=null){ + for(i in 0 until src.length() ){ + val item = try{ + parseMediaAttachmentMisskey2(src.optJSONObject(i)) + }catch(ex:Throwable){ + log.e(ex,"parseMediaAttachmentMisskey") + null + } + if( item != null ){ + if(rv==null) rv = ArrayList() + rv.add(item) + } } } - - this.in_reply_to_id = src.parseString("in_reply_to_id") - this.in_reply_to_account_id = src.parseString("in_reply_to_account_id") - this.mentions = parseListOrNull(::TootMention, src.optJSONArray("mentions"), log) - this.tags = parseListOrNull(::TootTag, src.optJSONArray("tags")) - this.application = parseItem(::TootApplication, src.optJSONObject("application"), log) - this.pinned = parser.pinned || src.optBoolean("pinned") - this.muted = src.optBoolean("muted") - this.language = src.parseString("language") - this.decoded_mentions = HTMLDecoder.decodeMentions( - parser.linkHelper, - this.mentions, - this - ) ?: EMPTY_SPANNABLE - // this.decoded_tags = HTMLDecoder.decodeTags( account,status.tags ); - - // content - this.content = src.parseString("content") - - var options = DecodeOptions( - parser.context, - parser.linkHelper, - short = true, - decodeEmoji = true, - emojiMapCustom = custom_emojis, - emojiMapProfile = profile_emojis, - attachmentList = media_attachments, - highlightTrie = parser.highlightTrie - ) - - this.decoded_content = options.decodeHTML(content) - this.hasHighlight = this.hasHighlight || options.hasHighlight - if(options.highlight_sound != null && this.highlight_sound == null) { - this.highlight_sound = options.highlight_sound - } - - // spoiler_text - this.spoiler_text = reWhitespace - .matcher(src.parseString("spoiler_text") ?: "") - .replaceAll(" ") - .sanitizeBDI() - - options = DecodeOptions( - parser.context, - emojiMapCustom = custom_emojis, - emojiMapProfile = profile_emojis, - highlightTrie = parser.highlightTrie - ) - - this.decoded_spoiler_text = options.decodeEmoji(spoiler_text) - - this.hasHighlight = this.hasHighlight || options.hasHighlight - if(options.highlight_sound != null && this.highlight_sound == null) { - this.highlight_sound = options.highlight_sound - } - - this.enquete = NicoEnquete.parse( - parser, - this, - media_attachments, - src.parseString("enquete") - ) - - // Pinned TL を取得した時にreblogが登場することはないので、reblogについてpinned 状態を気にする必要はない - this.reblog = parser.status(src.optJSONObject("reblog")) + return rv } /////////////////////////////////////////////////// diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt index 814ff79e..90ccbc2c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt @@ -28,7 +28,8 @@ class SavedAccount( val acct : String, hostArg : String? = null, var token_info : JSONObject? = null, - var loginAccount : TootAccount? = null // 疑似アカウントではnull + var loginAccount : TootAccount? = null, // 疑似アカウントではnull + var isMisskey :Boolean = false // 疑似アカウントでのみtrue ) : LinkHelper { val username : String @@ -156,6 +157,8 @@ class SavedAccount( this.sound_uri = cursor.getString(cursor.getColumnIndex(COL_SOUND_URI)) this.default_text = cursor.getString(cursor.getColumnIndex(COL_DEFAULT_TEXT)) ?: "" + + this.isMisskey = cursor.getInt(cursor.getColumnIndex(COL_IS_MISSKEY)).i2b() } val isNA : Boolean @@ -405,6 +408,9 @@ class SavedAccount( // スキーマ27から private const val COL_DEFAULT_TEXT = "default_text" + // スキーマ28から + private const val COL_IS_MISSKEY = "is_misskey" + ///////////////////////////////// // login information const val INVALID_DB_ID = - 1L @@ -466,6 +472,8 @@ class SavedAccount( // 以下はDBスキーマ27で更新 + ",$COL_DEFAULT_TEXT text default ''" + // 以下はDBスキーマ28で更新 + + ",$COL_IS_MISSKEY integer default 0" + ")" ) db.execSQL("create index if not exists ${table}_user on ${table}(u)") @@ -592,7 +600,14 @@ class SavedAccount( } } - + if(oldVersion < 28 && newVersion >= 28) { + try { + db.execSQL("alter table $table add column $COL_IS_MISSKEY integer default 0") + } catch(ex : Throwable) { + log.trace(ex) + } + + } } // 横断検索用の、何とも紐ついていないアカウント @@ -621,7 +636,8 @@ class SavedAccount( host : String, acct : String, account : JSONObject, - token : JSONObject + token : JSONObject, + isMisskey : Boolean = false ) : Long { try { val cv = ContentValues() @@ -629,6 +645,7 @@ class SavedAccount( cv.put(COL_USER, acct) cv.put(COL_ACCOUNT, account.toString()) cv.put(COL_TOKEN, token.toString()) + cv.put(COL_IS_MISSKEY, isMisskey.b2i() ) return App1.database.insert(table, null, cv) } catch(ex : Throwable) { log.trace(ex) diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt index 9c85f080..f8d7c33f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt @@ -20,7 +20,10 @@ import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.webkit.MimeTypeMap import android.widget.Toast +import jp.juggler.subwaytooter.api.TootApiClient import me.drakeet.support.toast.ToastCompat +import okhttp3.Request +import okhttp3.RequestBody import org.apache.commons.io.IOUtils import org.json.JSONArray import org.json.JSONObject @@ -651,6 +654,9 @@ fun JSONObject.parseInt(key : String) : Int? { } } +fun JSONObject.toPostRequestBuilder()= + Request.Builder().post(RequestBody.create(TootApiClient.MEDIA_TYPE_JSON,this.toString())) + //////////////////////////////////////////////////////////////////// // Bundle diff --git a/app/src/main/res/raw/server_list.txt b/app/src/main/res/raw/server_list.txt index 437e13bf..cdc4f4fa 100644 --- a/app/src/main/res/raw/server_list.txt +++ b/app/src/main/res/raw/server_list.txt @@ -836,6 +836,8 @@ mimumedon.com mindful.masto.host minidon.bacardi55.org misanthropy.wang +misskey.xyz +misskey.jp mist.so mistermi.me mn.kitetu.com