試験機能:misskeyのタンスを疑似アカウントで追加して、ローカルTLとグローバルTLを見れる

This commit is contained in:
tateisu 2018-08-17 04:58:30 +09:00
parent b58552c990
commit 7ceb9e4f51
14 changed files with 701 additions and 283 deletions

View File

@ -101,7 +101,8 @@ class App1 : Application() {
// 2018/5/16 v252 24=>25 SubscriptionServerKey テーブルを追加 // 2018/5/16 v252 24=>25 SubscriptionServerKey テーブルを追加
// 2018/5/16 v252 25=>26 SubscriptionServerKey テーブルを丸ごと変更 // 2018/5/16 v252 25=>26 SubscriptionServerKey テーブルを丸ごと変更
// 2018/8/5 v264 26 => 27 SavedAccountテーブルに項目追加 // 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( private val tableList = arrayOf(
LogData, LogData,

View File

@ -7,19 +7,17 @@ import android.os.AsyncTask
import android.os.SystemClock import android.os.SystemClock
import android.view.Gravity import android.view.Gravity
import jp.juggler.subwaytooter.api.* 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.api.entity.*
import jp.juggler.subwaytooter.table.* import jp.juggler.subwaytooter.table.*
import jp.juggler.subwaytooter.util.* 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.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.regex.Pattern
enum class StreamingIndicatorState { enum class StreamingIndicatorState {
NONE, NONE,
@ -309,6 +307,9 @@ class Column(
private val streamPath : String? private val streamPath : String?
get() { get() {
// misskeyの疑似アカウントはストリーミング対応していない
if(access_info.isPseudo && access_info.isMisskey) return null
return when(column_type) { return when(column_type) {
TYPE_HOME, TYPE_NOTIFICATIONS -> "/api/v1/streaming/?stream=user" TYPE_HOME, TYPE_NOTIFICATIONS -> "/api/v1/streaming/?stream=user"
TYPE_LOCAL -> "/api/v1/streaming/?stream=public:local" TYPE_LOCAL -> "/api/v1/streaming/?stream=public:local"
@ -320,7 +321,7 @@ class Column(
TYPE_HASHTAG -> when(instance_local) { TYPE_HASHTAG -> when(instance_local) {
true -> "/api/v1/streaming/?stream=" + Uri.encode("hashtag:local") + "&tag=" + hashtag.encodePercent() true -> "/api/v1/streaming/?stream=" + Uri.encode("hashtag:local") + "&tag=" + hashtag.encodePercent()
else -> "/api/v1/streaming/?stream=hashtag&tag=" + hashtag.encodePercent() else -> "/api/v1/streaming/?stream=hashtag&tag=" + hashtag.encodePercent()
// タグ先頭の#を含まない // タグ先頭の#を含まない
} }
else -> null 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) { if(column_type == columnType && targetAccount.acct == access_info.acct) {
val tmp_list = ArrayList<TimelineItem>(list_data.size) val tmp_list = ArrayList<TimelineItem>(list_data.size)
for(o in list_data) { for(o in list_data) {
@ -1502,13 +1503,36 @@ class Column(
log.d("getStatusesPinned: list size=%s", list_pinned?.size ?: - 1) 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 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 var jsonArray = result?.jsonArray
if(jsonArray != null) { if(jsonArray != null) {
saveRange(result, true, true) if(isMisskey) {
saveRangeMisskey(jsonArray, true, true)
} else {
saveRange(result, true, true)
}
// //
var src = parser.statusList(jsonArray) var src = parser.statusList(jsonArray)
@ -1540,9 +1564,16 @@ class Column(
log.d("loading-statuses: timeout.") log.d("loading-statuses: timeout.")
break break
} }
val path = path_base + delimiter + "max_id=" + max_id val result2 = if(isMisskey) {
val result2 = client.request(path) 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 jsonArray = result2?.jsonArray
if(jsonArray == null) { if(jsonArray == null) {
log.d("loading-statuses: error or cancelled.") log.d("loading-statuses: error or cancelled.")
break break
@ -1552,9 +1583,16 @@ class Column(
addWithFilterStatus(list_tmp, src) addWithFilterStatus(list_tmp, src)
if(! saveRangeEnd(result2)) { if(isMisskey) {
log.d("loading-statuses: missing range info.") if(! saveRangeEndMisskey(jsonArray)) {
break 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_DIRECT_MESSAGES -> return getStatuses(client, PATH_DIRECT_MESSAGES)
TYPE_LOCAL -> return getStatuses(client, makePublicLocalUrl()) TYPE_LOCAL -> return when(access_info.isMisskey) {
true -> getStatuses(
TYPE_FEDERATE -> return getStatuses(client, makePublicFederateUrl()) 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 -> { TYPE_PROFILE -> {
@ -1903,7 +1954,10 @@ class Column(
// //
} else { } else {
this.list_tmp = addOne(this.list_tmp, target_status) 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 { private fun saveRangeEnd(result : TootApiResult?) : Boolean {
if(result != null) { if(result != null) {
if(result.link_older == null) { if(result.link_older == null) {
@ -2147,6 +2226,23 @@ class Column(
return false 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 { private fun addRange(bBottom : Boolean, path : String) : String {
val delimiter = if(- 1 != path.indexOf('?')) '&' else '?' val delimiter = if(- 1 != path.indexOf('?')) '&' else '?'
if(bBottom) { if(bBottom) {
@ -2610,18 +2706,43 @@ class Column(
fun getStatusList( fun getStatusList(
client : TootApiClient, client : TootApiClient,
path_base : String path_base : String,
isMisskey : Boolean = false
) : TootApiResult? { ) : 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 time_start = SystemClock.elapsedRealtime()
val delimiter = if(- 1 != path_base.indexOf('?')) '&' else '?' val delimiter = if(- 1 != path_base.indexOf('?')) '&' else '?'
val last_since_id = since_id 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 var jsonArray = result?.jsonArray
if(jsonArray != null) { if(jsonArray != null) {
saveRange(result, bBottom, ! bBottom) if(isMisskey) {
saveRangeMisskey(jsonArray, bBottom, ! bBottom)
} else {
saveRange(result, bBottom, ! bBottom)
}
var src = parser.statusList(jsonArray) var src = parser.statusList(jsonArray)
list_tmp = addWithFilterStatus(null, src) list_tmp = addWithFilterStatus(null, src)
@ -2657,8 +2778,14 @@ class Column(
break break
} }
val path = path_base + delimiter + "max_id=" + max_id val result2 = if(isMisskey) {
val result2 = client.request(path) 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 jsonArray = result2?.jsonArray
if(jsonArray == null) { if(jsonArray == null) {
log.d("refresh-status-bottom: error or cancelled.") log.d("refresh-status-bottom: error or cancelled.")
@ -2669,9 +2796,16 @@ class Column(
addWithFilterStatus(list_tmp, src) addWithFilterStatus(list_tmp, src)
if(! saveRangeEnd(result2)) { if(isMisskey) {
log.d("refresh-status-bottom: saveRangeEnd failed.") if(! saveRangeEndMisskey(jsonArray)) {
break log.d("refresh-status-bottom: saveRangeEnd failed.")
break
}
} else {
if(! saveRangeEnd(result2)) {
log.d("refresh-status-bottom: saveRangeEnd failed.")
break
}
} }
} }
} else { } else {
@ -2711,6 +2845,13 @@ class Column(
break 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 = val path =
path_base + delimiter + "max_id=" + max_id + "&since_id=" + last_since_id path_base + delimiter + "max_id=" + max_id + "&since_id=" + last_since_id
val result2 = client.request(path) val result2 = client.request(path)
@ -2767,9 +2908,23 @@ class Column(
TYPE_DIRECT_MESSAGES -> getStatusList(client, PATH_DIRECT_MESSAGES) 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) TYPE_FAVOURITES -> getStatusList(client, PATH_FAVOURITES)
@ -3015,15 +3170,15 @@ class Column(
// //
val scroll_save = this@Column.scroll_save val scroll_save = this@Column.scroll_save
when { when {
// ViewHolderがある場合は増加件数分+deltaの位置にスクロールする // ViewHolderがある場合は増加件数分+deltaの位置にスクロールする
sp != null -> { sp != null -> {
sp.adapterIndex += added sp.adapterIndex += added
val delta = if(bSilent) 0f else - 20f val delta = if(bSilent) 0f else - 20f
holder?.setScrollPosition(sp, delta) holder?.setScrollPosition(sp, delta)
} }
// ViewHolderがなくて保存中の位置がある場合、増加件数分ずらす。deltaは難しいので反映しない // ViewHolderがなくて保存中の位置がある場合、増加件数分ずらす。deltaは難しいので反映しない
scroll_save != null -> scroll_save.adapterIndex += added scroll_save != null -> scroll_save.adapterIndex += added
// 保存中の位置がない場合、保存中の位置を新しく作る // 保存中の位置がない場合、保存中の位置を新しく作る
else -> this@Column.scroll_save = else -> this@Column.scroll_save =
ScrollPosition(toAdapterIndex(added), 0) ScrollPosition(toAdapterIndex(added), 0)
} }
@ -3054,6 +3209,8 @@ class Column(
return return
} }
viewHolder?.refreshLayout?.isRefreshing = true viewHolder?.refreshLayout?.isRefreshing = true
bRefreshLoading = true bRefreshLoading = true
@ -3557,9 +3714,9 @@ class Column(
log.d("onStart: column is in initial loading.") log.d("onStart: column is in initial loading.")
return return
} }
// フィルタ一覧のリロードが必要 // フィルタ一覧のリロードが必要
if( filter_reload_required ){ if(filter_reload_required) {
filter_reload_required = false filter_reload_required = false
startLoading() startLoading()
return return
@ -3659,8 +3816,11 @@ class Column(
return canStreaming() && column_type != TYPE_NOTIFICATIONS return canStreaming() && column_type != TYPE_NOTIFICATIONS
} }
internal fun canStreaming() : Boolean { internal fun canStreaming() = when {
return ! access_info.isNA && if(access_info.isPseudo) isPublicStream else streamPath != null access_info.isNA -> false
access_info.isMisskey -> false
access_info.isPseudo -> isPublicStream
else -> streamPath != null
} }
private val streamCallback = object : StreamReader.StreamCallback { private val streamCallback = object : StreamReader.StreamCallback {
@ -3918,6 +4078,7 @@ class Column(
} else { } else {
PATH_LOCAL PATH_LOCAL
} }
} }
private fun makePublicFederateUrl() : String { private fun makePublicFederateUrl() : String {
@ -3940,7 +4101,7 @@ class Column(
} }
private fun loadFilter2(client : TootApiClient) : ArrayList<TootFilter>? { private fun loadFilter2(client : TootApiClient) : ArrayList<TootFilter>? {
if( access_info.isPseudo ) return null if(access_info.isPseudo) return null
val column_context = getFilterContext() val column_context = getFilterContext()
if(column_context == 0) return null if(column_context == 0) return null
val result = client.request(PATH_FILTERS) val result = client.request(PATH_FILTERS)
@ -3955,10 +4116,12 @@ class Column(
val tree = WordTrieTree() val tree = WordTrieTree()
for(filter in filterList) { for(filter in filterList) {
if((filter.context and column_context) != 0) { if((filter.context and column_context) != 0) {
tree.add(filter.phrase,validator = when(filter.whole_word){ tree.add(
true -> WordTrieTree.WORD_VALIDATOR filter.phrase, validator = when(filter.whole_word) {
true -> WordTrieTree.WORD_VALIDATOR
else -> WordTrieTree.EMPTY_VALIDATOR else -> WordTrieTree.EMPTY_VALIDATOR
}) }
)
} }
} }
return tree return tree
@ -4023,5 +4186,4 @@ class Column(
} }
} }
} }

View File

@ -1028,7 +1028,13 @@ internal class ItemViewHolder(
} }
btnSearchTag, llTrendTag -> when(item) { 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 -> { is TootDomainBlock -> {
val domain = item.domain val domain = item.domain

View File

@ -25,7 +25,7 @@ internal fun findAccountByName(
) { ) {
TootTaskRunner(activity).run(access_info, object : TootTask { TootTaskRunner(activity).run(access_info, object : TootTask {
internal var who : TootAccount? = null var who : TootAccount? = null
override fun background(client : TootApiClient) : TootApiResult? { override fun background(client : TootApiClient) : TootApiResult? {
@ -38,10 +38,9 @@ internal fun findAccountByName(
for(i in 0 until array.length()) { for(i in 0 until array.length()) {
val a = parser.account(array.optJSONObject(i)) val a = parser.account(array.optJSONObject(i))
if(a != null) { if(a != null) {
if(a.username == user && access_info.getFullAcct(a).equals( if(a.username == user
user + "@" + host, && access_info.getFullAcct(a).equals("$user@$host", ignoreCase = true)
ignoreCase = true ) {
)) {
who = a who = a
break break
} }
@ -62,11 +61,14 @@ internal fun findAccountByName(
// 既に存在する場合は再利用する // 既に存在する場合は再利用する
// 実アカウントを返すことはない // 実アカウントを返すことはない
internal fun addPseudoAccount( internal fun addPseudoAccount(
context : Context, host : String context : Context,
host : String,
isMisskey : Boolean = false
) : SavedAccount? { ) : SavedAccount? {
try { try {
val username = "?" val username = "?"
val full_acct = username + "@" + host val full_acct = "$username@$host"
var account = SavedAccount.loadAccountByAcct(context, full_acct) var account = SavedAccount.loadAccountByAcct(context, full_acct)
if(account != null) { if(account != null) {
@ -77,7 +79,8 @@ internal fun addPseudoAccount(
account_info.put("username", username) account_info.put("username", username)
account_info.put("acct", 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) account = SavedAccount.loadAccount(context, row_id)
if(account == null) { if(account == null) {
throw RuntimeException("loadAccount returns null.") throw RuntimeException("loadAccount returns null.")
@ -132,7 +135,7 @@ internal fun loadRelation1(
client : TootApiClient, access_info : SavedAccount, who_id : Long client : TootApiClient, access_info : SavedAccount, who_id : Long
) : RelationResult { ) : RelationResult {
val rr = 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 r2 = rr.result
val jsonArray = r2?.jsonArray val jsonArray = r2?.jsonArray
if(jsonArray != null) { if(jsonArray != null) {

View File

@ -95,7 +95,7 @@ object Action_Account {
} else { } else {
// 疑似アカウントを追加 // 疑似アカウントを追加
val a = addPseudoAccount(activity, instance) val a = addPseudoAccount(activity, instance, data.optBoolean("isMisskey",false))
if(a != null) { if(a != null) {
showToast(activity, false, R.string.server_confirmed) showToast(activity, false, R.string.server_confirmed)
val pos = App1.getAppState(activity).column_list.size val pos = App1.getAppState(activity).column_list.size

View File

@ -18,6 +18,7 @@ object TootAccountMap{
ServiceType.MASTODON -> requireNotNull(parser.linkHelper.host) ServiceType.MASTODON -> requireNotNull(parser.linkHelper.host)
ServiceType.TOOTSEARCH -> "?tootsearch" ServiceType.TOOTSEARCH -> "?tootsearch"
ServiceType.MSP -> "?msp" ServiceType.MSP -> "?msp"
ServiceType.MISSKEY -> "?misskey"
} }
} }

View File

@ -445,10 +445,36 @@ class TootApiClient(
fun getInstanceInformation() : TootApiResult? { fun getInstanceInformation() : TootApiResult? {
val result = TootApiResult.makeWithCaption(instance) val result = TootApiResult.makeWithCaption(instance)
if(result.error != null) return result if(result.error != null) return result
if(! sendRequest(result) {
if(sendRequest(result) {
Request.Builder().url("https://$instance/api/v1/instance").build() 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データ以外を扱うリクエスト // JSONデータ以外を扱うリクエスト
fun http(req:Request) : TootApiResult? { fun http(req : Request) : TootApiResult? {
val result = TootApiResult.makeWithCaption(req.url().host()) val result = TootApiResult.makeWithCaption(req.url().host())
if(result.error != null) return result if(result.error != null) return result
@ -853,20 +879,19 @@ class TootApiClient(
return result return result
} }
fun requestJson(req:Request) : TootApiResult? { fun requestJson(req : Request) : TootApiResult? {
val result = TootApiResult.makeWithCaption(req.url().host()) val result = TootApiResult.makeWithCaption(req.url().host())
if(result.error != null) return result if(result.error != null) return result
if( sendRequest(result, progressPath = null) { req } ){ if(sendRequest(result, progressPath = null) { req }) {
parseJson(result) parseJson(result)
} }
return result return result
} }
// 疑似アカウントでステータスURLからステータスIDを取得するためにHTMLを取得する // 疑似アカウントでステータスURLからステータスIDを取得するためにHTMLを取得する
fun getHttp(url:String): TootApiResult? { fun getHttp(url : String) : TootApiResult? {
val result = http(Request.Builder().url(url).build()) val result = http(Request.Builder().url(url).build())
if(result !=null && result.error == null){ if(result != null && result.error == null) {
parseString(result) parseString(result)
} }
return result return result

View File

@ -4,4 +4,5 @@ enum class ServiceType {
MASTODON, MASTODON,
TOOTSEARCH, TOOTSEARCH,
MSP, MSP,
MISSKEY,
} }

View File

@ -90,106 +90,155 @@ open class TootAccount(
init { init {
var sv : String? var sv : String?
// 絵文字データは先に読んでおく if(parser.serviceType == ServiceType.MISSKEY) {
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 -> { val instance = src.parseString("host") ?: parser.linkHelper.host ?: error("missing host")
// 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.custom_emojis = null
this.id = src.parseLong("id") ?: INVALID_ID this.profile_emojis = null
// 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.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")
}
} }
} }

View File

@ -13,4 +13,9 @@ class TootApplication(
name = src.parseString("name"), name = src.parseString("name"),
website = src.parseString("website") website = src.parseString("website")
) )
constructor(src:String?):this(
name = src,
website = null
)
} }

View File

@ -18,8 +18,10 @@ import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.table.HighlightWord import jp.juggler.subwaytooter.table.HighlightWord
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.util.*
import org.json.JSONArray
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.collections.ArrayList
@Suppress("MemberVisibilityCanPrivate") @Suppress("MemberVisibilityCanPrivate")
class TootStatus(parser : TootParser, src : JSONObject) : class TootStatus(parser : TootParser, src : JSONObject) :
@ -155,133 +157,271 @@ class TootStatus(parser : TootParser, src : JSONObject) :
init { init {
this.json = src this.json = src
this.uri = src.parseString("uri") // MSPだとuriは提供されない if( parser.serviceType == ServiceType.MISSKEY) {
this.url = src.parseString("url") // 頻繁にnullになる val instance = parser.linkHelper.host
this.created_at = src.parseString("created_at") val misskeyId = src.parseString("id")
this.host_access = parser.linkHelper.host
// 絵文字マップはすぐ後で使うので、最初の方で読んでおく
this.custom_emojis = parseMapOrNull(::CustomEmoji, src.optJSONArray("emojis"), log) this.uri = "https://$instance/notes/$misskeyId"
this.profile_emojis = this.url = "https://$instance/notes/$misskeyId"
parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis"), log) this.created_at = src.parseString("createdAt")
this.time_created_at = parseTime(this.created_at)
val who = parser.account(src.optJSONObject("account")) this.id = INVALID_ID
?: throw RuntimeException("missing account")
// 絵文字マップはすぐ後で使うので、最初の方で読んでおく
this.accountRef = TootAccountRef(parser, who) this.custom_emojis = null
this.profile_emojis = null
this.reblogs_count = src.parseLong("reblogs_count")
this.favourites_count = src.parseLong("favourites_count") val who = parser.account(src.optJSONObject("user"))
this.replies_count = src.parseLong("replies_count") ?: 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) { }else{
ServiceType.MASTODON -> { this.uri = src.parseString("uri") // MSPだとuriは提供されない
this.host_access = parser.linkHelper.host 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 ServiceType.TOOTSEARCH -> {
this.host_access = null
this.reblogged = src.optBoolean("reblogged")
this.favourited = src.optBoolean("favourited") // 投稿元タンスでのIDを調べる。失敗するかもしれない
this.id = findStatusIdFromUri(uri, url)
this.time_created_at = parseTime(this.created_at)
this.media_attachments = this.time_created_at = TootStatus.parseTime(this.created_at)
parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log) this.media_attachments =
this.visibility = src.parseString("visibility") parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log)
this.sensitive = src.optBoolean("sensitive") 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.in_reply_to_id = src.parseString("in_reply_to_id")
this.host_access = null this.in_reply_to_account_id = src.parseString("in_reply_to_account_id")
this.mentions = parseListOrNull(::TootMention, src.optJSONArray("mentions"), log)
// 投稿元タンスでのIDを調べる。失敗するかもしれない this.tags = parseListOrNull(::TootTag, src.optJSONArray("tags"))
this.id = findStatusIdFromUri(uri, url) this.application = parseItem(::TootApplication, src.optJSONObject("application"), log)
this.pinned = parser.pinned || src.optBoolean("pinned")
this.time_created_at = TootStatus.parseTime(this.created_at) this.muted = src.optBoolean("muted")
this.media_attachments = this.language = src.parseString("language")
parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log) this.decoded_mentions = HTMLDecoder.decodeMentions(
this.visibility = VISIBILITY_PUBLIC parser.linkHelper,
this.sensitive = src.optBoolean("sensitive") 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 -> { // spoiler_text
this.host_access = null this.spoiler_text = reWhitespace
.matcher(src.parseString("spoiler_text") ?: "")
// MSPのデータはLTLから呼んだものなので、常に投稿元タンスでのidが得られる .replaceAll(" ")
this.id = src.parseLong("id") ?: INVALID_ID .sanitizeBDI()
this.time_created_at = parseTimeMSP(created_at) options = DecodeOptions(
this.media_attachments = parser.context,
TootAttachmentMSP.parseList(src.optJSONArray("media_attachments")) emojiMapCustom = custom_emojis,
this.visibility = VISIBILITY_PUBLIC emojiMapProfile = profile_emojis,
this.sensitive = src.optInt("sensitive", 0) != 0 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<TootAttachmentLike>? {
var rv :ArrayList<TootAttachmentLike>? = 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)
}
} }
} }
return rv
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"))
} }
/////////////////////////////////////////////////// ///////////////////////////////////////////////////

View File

@ -28,7 +28,8 @@ class SavedAccount(
val acct : String, val acct : String,
hostArg : String? = null, hostArg : String? = null,
var token_info : JSONObject? = null, var token_info : JSONObject? = null,
var loginAccount : TootAccount? = null // 疑似アカウントではnull var loginAccount : TootAccount? = null, // 疑似アカウントではnull
var isMisskey :Boolean = false // 疑似アカウントでのみtrue
) : LinkHelper { ) : LinkHelper {
val username : String val username : String
@ -156,6 +157,8 @@ class SavedAccount(
this.sound_uri = cursor.getString(cursor.getColumnIndex(COL_SOUND_URI)) this.sound_uri = cursor.getString(cursor.getColumnIndex(COL_SOUND_URI))
this.default_text = cursor.getString(cursor.getColumnIndex(COL_DEFAULT_TEXT)) ?: "" this.default_text = cursor.getString(cursor.getColumnIndex(COL_DEFAULT_TEXT)) ?: ""
this.isMisskey = cursor.getInt(cursor.getColumnIndex(COL_IS_MISSKEY)).i2b()
} }
val isNA : Boolean val isNA : Boolean
@ -405,6 +408,9 @@ class SavedAccount(
// スキーマ27から // スキーマ27から
private const val COL_DEFAULT_TEXT = "default_text" private const val COL_DEFAULT_TEXT = "default_text"
// スキーマ28から
private const val COL_IS_MISSKEY = "is_misskey"
///////////////////////////////// /////////////////////////////////
// login information // login information
const val INVALID_DB_ID = - 1L const val INVALID_DB_ID = - 1L
@ -466,6 +472,8 @@ class SavedAccount(
// 以下はDBスキーマ27で更新 // 以下はDBスキーマ27で更新
+ ",$COL_DEFAULT_TEXT text default ''" + ",$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)") 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, host : String,
acct : String, acct : String,
account : JSONObject, account : JSONObject,
token : JSONObject token : JSONObject,
isMisskey : Boolean = false
) : Long { ) : Long {
try { try {
val cv = ContentValues() val cv = ContentValues()
@ -629,6 +645,7 @@ class SavedAccount(
cv.put(COL_USER, acct) cv.put(COL_USER, acct)
cv.put(COL_ACCOUNT, account.toString()) cv.put(COL_ACCOUNT, account.toString())
cv.put(COL_TOKEN, token.toString()) cv.put(COL_TOKEN, token.toString())
cv.put(COL_IS_MISSKEY, isMisskey.b2i() )
return App1.database.insert(table, null, cv) return App1.database.insert(table, null, cv)
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)

View File

@ -20,7 +20,10 @@ import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import jp.juggler.subwaytooter.api.TootApiClient
import me.drakeet.support.toast.ToastCompat import me.drakeet.support.toast.ToastCompat
import okhttp3.Request
import okhttp3.RequestBody
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject 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 // Bundle

View File

@ -836,6 +836,8 @@ mimumedon.com
mindful.masto.host mindful.masto.host
minidon.bacardi55.org minidon.bacardi55.org
misanthropy.wang misanthropy.wang
misskey.xyz
misskey.jp
mist.so mist.so
mistermi.me mistermi.me
mn.kitetu.com mn.kitetu.com