misskeyのカラムに出たリモートの投稿のuriを解釈する

This commit is contained in:
tateisu 2018-08-30 21:33:22 +09:00
parent 8be857c8f1
commit 4f29ec0676
4 changed files with 165 additions and 188 deletions

View File

@ -590,7 +590,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
in_reply_to_text = reply_status.content in_reply_to_text = reply_status.content
in_reply_to_image = reply_status.account.avatar_static in_reply_to_image = reply_status.account.avatar_static
in_reply_to_url = reply_status.url in_reply_to_url = reply_status.url
// 公開範囲 // 公開範囲
try { try {
// 比較する前にデフォルトの公開範囲を計算する // 比較する前にデフォルトの公開範囲を計算する
@ -1138,20 +1138,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
var target_status : TootStatus? = null var target_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? { override fun background(client : TootApiClient) : TootApiResult? {
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる
val path = String.format(
Locale.JAPAN,
Column.PATH_SEARCH,
in_reply_to_url.encodePercent()
) + "&resolve=1"
val result = client.request(path) val result = client.syncStatus(access_info,in_reply_to_url)
val jsonObject = result?.jsonObject if( result?.data != null ) {
if(jsonObject != null) { target_status = result.data as? TootStatus
val tmp = TootParser(this@ActPost, access_info).results(jsonObject)
if(tmp?.statuses?.isNotEmpty() == true) {
target_status = tmp.statuses[0]
}
if(target_status == null) { if(target_status == null) {
return TootApiResult(getString(R.string.status_id_conversion_failed)) return TootApiResult(getString(R.string.status_id_conversion_failed))
} }

View File

@ -1,31 +1,26 @@
package jp.juggler.subwaytooter.action package jp.juggler.subwaytooter.action
import android.net.Uri import android.net.Uri
import jp.juggler.subwaytooter.*
import java.util.ArrayList import jp.juggler.subwaytooter.api.*
import java.util.Locale import jp.juggler.subwaytooter.api.entity.EntityId
import java.util.regex.Pattern import jp.juggler.subwaytooter.api.entity.EntityIdLong
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.ActMain import jp.juggler.subwaytooter.api.entity.TootVisibility
import jp.juggler.subwaytooter.ActPost
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Column
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootTask
import jp.juggler.subwaytooter.api.TootTaskRunner
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.AccountPicker import jp.juggler.subwaytooter.dialog.AccountPicker
import jp.juggler.subwaytooter.dialog.ActionsDialog import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgConfirm import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.table.AcctColor import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.util.EmptyCallback
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.showToast
import jp.juggler.subwaytooter.util.toPostRequestBuilder
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import org.json.JSONObject import org.json.JSONObject
import java.util.*
import java.util.regex.Pattern
object Action_Toot { object Action_Toot {
@ -130,38 +125,17 @@ object Action_Toot {
var result : TootApiResult? var result : TootApiResult?
var target_status : TootStatus? val target_status : TootStatus
if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) { if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) {
if(access_info.isMisskey) { result = client.syncStatus( access_info,arg_status)
return TootApiResult("Misskey has no API to sync note from remote to local.") if( result?.data == null) return result
} target_status = result.data as? TootStatus
?: return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる if(target_status.favourited) {
val status_url = arg_status.url
if(status_url?.isEmpty() != false) return TootApiResult("missing status URL")
val path = String.format(
Locale.JAPAN,
Column.PATH_SEARCH,
status_url.encodePercent()
) + "&resolve=1"
result = client.request(path)
val jsonObject = result?.jsonObject ?: return result
target_status = null
val tmp = TootParser(activity, access_info).results(jsonObject)
if(tmp != null) {
if(tmp.statuses.isNotEmpty()) {
target_status = tmp.statuses[0]
log.d("status id conversion %s => %s", arg_status.id, target_status.id)
}
}
if(target_status == null) {
return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
} else if(target_status.favourited) {
return TootApiResult(activity.getString(R.string.already_favourited)) return TootApiResult(activity.getString(R.string.already_favourited))
} }
} else { } else {
target_status = arg_status target_status = arg_status
} }
@ -400,34 +374,14 @@ object Action_Toot {
var result : TootApiResult? var result : TootApiResult?
var target_status : TootStatus? val target_status : TootStatus
if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) { if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) {
if(access_info.isMisskey) { result = client.syncStatus(access_info,arg_status)
// XXX Misskey対応はトゥートの同期方法が分からないので保留 if( result?.data == null) return result
return TootApiResult("Misskey has no API to sync the note from remote to local.") target_status = result.data as? TootStatus
} ?: return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
if(target_status.reblogged) {
val status_url = arg_status.url
if(status_url?.isEmpty() != false) return TootApiResult("missing status URL")
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる
val path = String.format(
Locale.JAPAN,
Column.PATH_SEARCH,
status_url.encodePercent()
) + "&resolve=1"
result = client.request(path)
val jsonObject = result?.jsonObject ?: return result
target_status = null
val tmp = parser.results(jsonObject)
if(tmp?.statuses?.isNotEmpty() == true) {
target_status = tmp.statuses[0]
}
if(target_status == null) {
return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
} else if(target_status.reblogged) {
return TootApiResult(activity.getString(R.string.already_boosted)) return TootApiResult(activity.getString(R.string.already_boosted))
} }
} else { } else {
@ -799,26 +753,12 @@ object Action_Toot {
} }
} }
} else { } else {
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる result = client.syncStatus(access_info,remote_status_url)
val path = String.format( if( result?.data == null ) return result
Locale.JAPAN, val status = result.data as? TootStatus
Column.PATH_SEARCH, ?: return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
remote_status_url.encodePercent() local_status_id = status.id
) + "&resolve=1" log.d("status id conversion %s => %s", remote_status_url, status.id)
result = client.request(path)
val jsonObject = result?.jsonObject
if(jsonObject != null) {
val tmp = TootParser(activity, access_info).results(jsonObject)
if(tmp?.statuses?.isNotEmpty() == true) {
val status = tmp.statuses[0]
local_status_id = status.id
log.d("status id conversion %s => %s", remote_status_url, status.id)
}
if(local_status_id == null) {
result =
TootApiResult(activity.getString(R.string.status_id_conversion_failed))
}
}
} }
return result return result
} }
@ -949,25 +889,10 @@ object Action_Toot {
var local_status : TootStatus? = null var local_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? { override fun background(client : TootApiClient) : TootApiResult? {
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる val result = client.syncStatus(access_info,remote_status_url)
val path = String.format( if( result?.data == null) return result
Locale.JAPAN, local_status = result.data as? TootStatus
Column.PATH_SEARCH, ?: return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
remote_status_url.encodePercent()
) + "&resolve=1"
val result = client.request(path)
val jsonObject = result?.jsonObject
if(jsonObject != null) {
val tmp = TootParser(activity, access_info).results(jsonObject)
if(tmp?.statuses?.isNotEmpty() == true) {
val ls = tmp.statuses[0]
local_status = ls
log.d("status id conversion %s => %s", remote_status_url, ls.id)
}
local_status
?: return TootApiResult(activity.getString(R.string.status_id_conversion_failed))
}
return result return result
} }

View File

@ -11,6 +11,7 @@ import org.json.JSONObject
import jp.juggler.subwaytooter.table.ClientInfo import jp.juggler.subwaytooter.table.ClientInfo
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.api.entity.TootInstance import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.util.*
import okhttp3.* import okhttp3.*
import org.json.JSONArray import org.json.JSONArray
@ -68,15 +69,15 @@ class TootApiClient(
const val KEY_MISSKEY_APP_SECRET = "secret" const val KEY_MISSKEY_APP_SECRET = "secret"
const val KEY_API_KEY_MISSKEY = "apiKeyMisskey" const val KEY_API_KEY_MISSKEY = "apiKeyMisskey"
// // APIからsecretを得られないバグがあるので定数を渡す // // APIからsecretを得られないバグがあるので定数を渡す
// const val appSecretError = // const val appSecretError =
// "This Misskey instance Currently Misskey does not allow client registration from API, please tell me notify instance name that you want login via Subway Tooter." // "This Misskey instance Currently Misskey does not allow client registration from API, please tell me notify instance name that you want login via Subway Tooter."
// val testAppSecretMap = mapOf( // val testAppSecretMap = mapOf(
// Pair("misskey.xyz", "NGiWNZFP37WiAee3SGcVe8eSiDyLbbWf") // Pair("misskey.xyz", "NGiWNZFP37WiAee3SGcVe8eSiDyLbbWf")
// , Pair("misskey.jp", "GO45N7JgeEWtlNUS4xRcOFY56JMjUTZk") // , Pair("misskey.jp", "GO45N7JgeEWtlNUS4xRcOFY56JMjUTZk")
// , Pair("msky.cafe", "lvU12i7CXAB5xiqkABwzyJRzdAqhf0k3") // , Pair("msky.cafe", "lvU12i7CXAB5xiqkABwzyJRzdAqhf0k3")
// , Pair("misskey.m544.net", "SLcaqff0Puymh4Fl30JCc09i6uumwJ4t") // , Pair("misskey.m544.net", "SLcaqff0Puymh4Fl30JCc09i6uumwJ4t")
// ) // )
private const val NO_INFORMATION = "(no information)" private const val NO_INFORMATION = "(no information)"
@ -202,37 +203,38 @@ class TootApiClient(
fun getScopeArrayMisskey(@Suppress("UNUSED_PARAMETER") ti : TootInstance) = fun getScopeArrayMisskey(@Suppress("UNUSED_PARAMETER") ti : TootInstance) =
JSONArray().apply { JSONArray().apply {
put("account-read") put("account-read")
put("account-write") put("account-write")
put("note-read") put("note-read")
put("note-write") put("note-write")
put("reaction-read") put("reaction-read")
put("reaction-write") put("reaction-write")
put("following-read") // フォロリク申請一覧で使われていた put("following-read") // フォロリク申請一覧で使われていた
put("following-write") put("following-write")
put("drive-read") put("drive-read")
put("drive-write") put("drive-write")
put("notification-read") put("notification-read")
put("notification-write") put("notification-write")
put("favorite-read") put("favorite-read")
put("favorites-read")
put("favorite-write") put("favorite-write")
put("account/read") put("account/read")
put("account/write") put("account/write")
put("messaging-read") put("messaging-read")
put("messaging-write") put("messaging-write")
put("vote-read") put("vote-read")
put("vote-write") put("vote-write")
// https://github.com/syuilo/misskey/issues/2341 // https://github.com/syuilo/misskey/issues/2341
} }
@ -317,7 +319,7 @@ class TootApiClient(
if(isApiCancelled) return null if(isApiCancelled) return null
// Misskey の /api/notes/favorites/create は 204(no content)を返す。ボディはカラになる。 // Misskey の /api/notes/favorites/create は 204(no content)を返す。ボディはカラになる。
if( bodyString?.isEmpty() != false && response.code() in 200 until 300 ){ if(bodyString?.isEmpty() != false && response.code() in 200 until 300) {
result.bodyString = "" result.bodyString = ""
return "" return ""
} }
@ -441,11 +443,11 @@ class TootApiClient(
var bodyString = readBodyString(result, progressPath, jsonErrorParser) var bodyString = readBodyString(result, progressPath, jsonErrorParser)
?: return if(isApiCancelled) null else result ?: return if(isApiCancelled) null else result
if( bodyString.isEmpty() ){ if(bodyString.isEmpty()) {
// 204 no content は 空オブジェクトと解釈する // 204 no content は 空オブジェクトと解釈する
result.data = JSONObject() result.data = JSONObject()
} else if(reStartJsonArray.matcher(bodyString).find()) { } else if(reStartJsonArray.matcher(bodyString).find()) {
result.data = bodyString.toJsonArray() result.data = bodyString.toJsonArray()
@ -457,20 +459,19 @@ class TootApiClient(
} else { } else {
result.data = json result.data = json
} }
}else { } else {
// HTMLならタグを除去する // HTMLならタグを除去する
val ct = response.body()?.contentType() val ct = response.body()?.contentType()
if(ct?.subtype() == "html") { if(ct?.subtype() == "html") {
val decoded = DecodeOptions().decodeHTML(bodyString).toString() val decoded = DecodeOptions().decodeHTML(bodyString).toString()
bodyString =decoded bodyString = decoded
} }
val sb = StringBuilder() val sb = StringBuilder()
.append(context.getString(R.string.response_not_json)) .append(context.getString(R.string.response_not_json))
.append(' ') .append(' ')
.append(bodyString) .append(bodyString)
if(sb.isNotEmpty()) sb.append(' ') if(sb.isNotEmpty()) sb.append(' ')
sb.append("(HTTP ").append(Integer.toString(response.code())) sb.append("(HTTP ").append(Integer.toString(response.code()))
@ -480,8 +481,8 @@ class TootApiClient(
} }
sb.append(")") sb.append(")")
val url = response.request()?.url()?.toString() val url = response.request()?.url()?.toString()
if(url?.isNotEmpty()==true) { if(url?.isNotEmpty() == true) {
sb.append(' ').append(url) sb.append(' ').append(url)
} }
@ -590,7 +591,7 @@ class TootApiClient(
return result return result
} }
private fun prepareBrowserUrlMisskey( appSecret:String) : String? { private fun prepareBrowserUrlMisskey(appSecret : String) : String? {
val result = TootApiResult.makeWithCaption(instance) val result = TootApiResult.makeWithCaption(instance)
@ -709,8 +710,8 @@ class TootApiClient(
val jsonObject = r2?.jsonObject ?: return r2 val jsonObject = r2?.jsonObject ?: return r2
val appSecret = jsonObject.parseString(KEY_MISSKEY_APP_SECRET) val appSecret = jsonObject.parseString(KEY_MISSKEY_APP_SECRET)
if( appSecret?.isEmpty() != false) { if(appSecret?.isEmpty() != false) {
showToast(context,true,context.getString(R.string.cant_get_misskey_app_secret)) showToast(context, true, context.getString(R.string.cant_get_misskey_app_secret))
return null return null
} }
// { // {
@ -758,10 +759,10 @@ class TootApiClient(
?: return result.setError("missing client id") ?: return result.setError("missing client id")
val appSecret = client_info.parseString(KEY_MISSKEY_APP_SECRET) val appSecret = client_info.parseString(KEY_MISSKEY_APP_SECRET)
if( appSecret?.isEmpty() != false) { if(appSecret?.isEmpty() != false) {
return result.setError(context.getString(R.string.cant_get_misskey_app_secret)) return result.setError(context.getString(R.string.cant_get_misskey_app_secret))
} }
if(! sendRequest(result) { if(! sendRequest(result) {
JSONObject().apply { JSONObject().apply {
put("appSecret", appSecret) put("appSecret", appSecret)
@ -821,7 +822,6 @@ class TootApiClient(
return result return result
} }
// クライアントをタンスに登録 // クライアントをタンスに登録
fun registerClient(scope_string : String, clientName : String) : TootApiResult? { fun registerClient(scope_string : String, clientName : String) : TootApiResult? {
val result = TootApiResult.makeWithCaption(this.instance) val result = TootApiResult.makeWithCaption(this.instance)
@ -1330,35 +1330,36 @@ class TootApiClient(
return result return result
} }
}
fun TootApiClient.syncAccountByUrl(accessInfo:SavedAccount,who_url:String):TootApiResult?{ }
if( accessInfo.isMisskey){
fun TootApiClient.syncAccountByUrl(accessInfo : SavedAccount, who_url : String) : TootApiResult? {
if(accessInfo.isMisskey) {
val acct = TootAccount.getAcctFromUrl(who_url) val acct = TootAccount.getAcctFromUrl(who_url)
?: return TootApiResult(context.getString(R.string.user_id_conversion_failed)) ?: return TootApiResult(context.getString(R.string.user_id_conversion_failed))
val delm = acct.indexOf('@') val delm = acct.indexOf('@')
val params = accessInfo.putMisskeyApiToken(JSONObject()) val params = accessInfo.putMisskeyApiToken(JSONObject())
if(delm!=-1){ if(delm != - 1) {
params.put("username",acct.substring(0,delm)) params.put("username", acct.substring(0, delm))
params.put("host",acct.substring(delm+1)) params.put("host", acct.substring(delm + 1))
}else{ } else {
params.put("username",acct) params.put("username", acct)
} }
val result = this.request("/api/users/show",params.toPostRequestBuilder()) val result = this.request("/api/users/show", params.toPostRequestBuilder())
val jsonObject = result?.jsonObject val jsonObject = result?.jsonObject
if(jsonObject != null) { if(jsonObject != null) {
val tmp = TootParser(this.context,accessInfo).account(jsonObject) val tmp = TootParser(this.context, accessInfo).account(jsonObject)
if(tmp != null){ if(tmp != null) {
result.data = tmp result.data = tmp
} else { } else {
result.setError(context.getString(R.string.user_id_conversion_failed)) result.setError(context.getString(R.string.user_id_conversion_failed))
} }
} }
return result return result
}else{ } else {
val path = String.format( val path = String.format(
Locale.JAPAN, Locale.JAPAN,
Column.PATH_SEARCH, Column.PATH_SEARCH,
@ -1367,7 +1368,7 @@ fun TootApiClient.syncAccountByUrl(accessInfo:SavedAccount,who_url:String):TootA
val result = this.request(path) val result = this.request(path)
val jsonObject = result?.jsonObject val jsonObject = result?.jsonObject
if(jsonObject != null) { if(jsonObject != null) {
val tmp = TootParser(this.context,accessInfo).results(jsonObject) val tmp = TootParser(this.context, accessInfo).results(jsonObject)
if(tmp != null && tmp.accounts.isNotEmpty()) { if(tmp != null && tmp.accounts.isNotEmpty()) {
result.data = tmp.accounts[0].get() result.data = tmp.accounts[0].get()
} else { } else {
@ -1378,30 +1379,30 @@ fun TootApiClient.syncAccountByUrl(accessInfo:SavedAccount,who_url:String):TootA
} }
} }
fun TootApiClient.syncAccountByAcct(accessInfo:SavedAccount,acct:String):TootApiResult?{ fun TootApiClient.syncAccountByAcct(accessInfo : SavedAccount, acct : String) : TootApiResult? {
if( accessInfo.isMisskey){ if(accessInfo.isMisskey) {
val delm = acct.indexOf('@') val delm = acct.indexOf('@')
val params = accessInfo.putMisskeyApiToken(JSONObject()) val params = accessInfo.putMisskeyApiToken(JSONObject())
if(delm!=-1){ if(delm != - 1) {
params.put("username",acct.substring(0,delm)) params.put("username", acct.substring(0, delm))
params.put("host",acct.substring(delm+1)) params.put("host", acct.substring(delm + 1))
}else{ } else {
params.put("username",acct) params.put("username", acct)
} }
val result = this.request("/api/users/show",params.toPostRequestBuilder()) val result = this.request("/api/users/show", params.toPostRequestBuilder())
val jsonObject = result?.jsonObject val jsonObject = result?.jsonObject
if(jsonObject != null) { if(jsonObject != null) {
val tmp = TootParser(this.context,accessInfo).account(jsonObject) val tmp = TootParser(this.context, accessInfo).account(jsonObject)
if(tmp != null){ if(tmp != null) {
result.data = tmp result.data = tmp
} else { } else {
result.setError(context.getString(R.string.user_id_conversion_failed)) result.setError(context.getString(R.string.user_id_conversion_failed))
} }
} }
return result return result
}else{ } else {
val path = String.format( val path = String.format(
Locale.JAPAN, Locale.JAPAN,
Column.PATH_SEARCH, Column.PATH_SEARCH,
@ -1410,7 +1411,7 @@ fun TootApiClient.syncAccountByAcct(accessInfo:SavedAccount,acct:String):TootApi
val result = this.request(path) val result = this.request(path)
val jsonObject = result?.jsonObject val jsonObject = result?.jsonObject
if(jsonObject != null) { if(jsonObject != null) {
val tmp = TootParser(this.context,accessInfo).results(jsonObject) val tmp = TootParser(this.context, accessInfo).results(jsonObject)
if(tmp != null && tmp.accounts.isNotEmpty()) { if(tmp != null && tmp.accounts.isNotEmpty()) {
result.data = tmp.accounts[0].get() result.data = tmp.accounts[0].get()
} else { } else {
@ -1419,4 +1420,58 @@ fun TootApiClient.syncAccountByAcct(accessInfo:SavedAccount,acct:String):TootApi
} }
return result return result
} }
}
fun TootApiClient.syncStatus(accessInfo : SavedAccount, url : String) : TootApiResult? {
if(accessInfo.isMisskey) {
return TootApiResult("Misskey has no API to sync note from remote to local.")
}
val path = String.format(
Locale.JAPAN,
Column.PATH_SEARCH,
url.encodePercent()
) + "&resolve=1"
val result = request(path)
if(result != null) {
val tmp = TootParser(context, accessInfo).results(result.jsonObject)
if(tmp != null && tmp.statuses.isNotEmpty()) {
result.data = tmp.statuses[0]
}
}
return result
}
fun TootApiClient.syncStatus(
accessInfo : SavedAccount,
statusRemote : TootStatus
) : TootApiResult? {
if(accessInfo.isMisskey) {
return TootApiResult("Misskey has no API to sync note from remote to local.")
}
var result : TootApiResult? = TootApiResult("missing url or uri")
var sv = statusRemote.url
when {
sv?.isEmpty() != false -> {
}
sv.contains("/notes/") -> {
// Misskeyタンスから読んだマストドンの投稿はurlがmisskeyタンス上のものになる
// uri で試す
}
else -> {
result = syncStatus(accessInfo, sv)
if(result == null || result.data is TootStatus) {
return result
}
}
}
sv = statusRemote.uri
if(sv?.isNotEmpty() == true) {
result = syncStatus(accessInfo, sv)
}
return result
} }

View File

@ -180,8 +180,15 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
val misskeyId = src.parseString("id") val misskeyId = src.parseString("id")
this.host_access = parser.linkHelper.host this.host_access = parser.linkHelper.host
this.uri = "https://$instance/notes/$misskeyId" val uri = src.parseString("uri")
this.url = "https://$instance/notes/$misskeyId" if( uri != null ){
this.uri = uri
this.url = uri
}else {
this.uri = "https://$instance/notes/$misskeyId"
this.url = "https://$instance/notes/$misskeyId"
}
this.created_at = src.parseString("createdAt") this.created_at = src.parseString("createdAt")
this.time_created_at = parseTime(this.created_at) this.time_created_at = parseTime(this.created_at)
this.id = EntityIdString(src.parseString("id") ?: error("missing id")) this.id = EntityIdString(src.parseString("id") ?: error("missing id"))