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_image = reply_status.account.avatar_static
in_reply_to_url = reply_status.url
// 公開範囲
try {
// 比較する前にデフォルトの公開範囲を計算する
@ -1138,20 +1138,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
var target_status : TootStatus? = null
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 jsonObject = result?.jsonObject
if(jsonObject != null) {
val tmp = TootParser(this@ActPost, access_info).results(jsonObject)
if(tmp?.statuses?.isNotEmpty() == true) {
target_status = tmp.statuses[0]
}
val result = client.syncStatus(access_info,in_reply_to_url)
if( result?.data != null ) {
target_status = result.data as? TootStatus
if(target_status == null) {
return TootApiResult(getString(R.string.status_id_conversion_failed))
}

View File

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

View File

@ -11,6 +11,7 @@ import org.json.JSONObject
import jp.juggler.subwaytooter.table.ClientInfo
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.*
import okhttp3.*
import org.json.JSONArray
@ -68,15 +69,15 @@ class TootApiClient(
const val KEY_MISSKEY_APP_SECRET = "secret"
const val KEY_API_KEY_MISSKEY = "apiKeyMisskey"
// // APIからsecretを得られないバグがあるので定数を渡す
// 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."
// val testAppSecretMap = mapOf(
// Pair("misskey.xyz", "NGiWNZFP37WiAee3SGcVe8eSiDyLbbWf")
// , Pair("misskey.jp", "GO45N7JgeEWtlNUS4xRcOFY56JMjUTZk")
// , Pair("msky.cafe", "lvU12i7CXAB5xiqkABwzyJRzdAqhf0k3")
// , Pair("misskey.m544.net", "SLcaqff0Puymh4Fl30JCc09i6uumwJ4t")
// )
// // APIからsecretを得られないバグがあるので定数を渡す
// 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."
// val testAppSecretMap = mapOf(
// Pair("misskey.xyz", "NGiWNZFP37WiAee3SGcVe8eSiDyLbbWf")
// , Pair("misskey.jp", "GO45N7JgeEWtlNUS4xRcOFY56JMjUTZk")
// , Pair("msky.cafe", "lvU12i7CXAB5xiqkABwzyJRzdAqhf0k3")
// , Pair("misskey.m544.net", "SLcaqff0Puymh4Fl30JCc09i6uumwJ4t")
// )
private const val NO_INFORMATION = "(no information)"
@ -202,37 +203,38 @@ class TootApiClient(
fun getScopeArrayMisskey(@Suppress("UNUSED_PARAMETER") ti : TootInstance) =
JSONArray().apply {
put("account-read")
put("account-write")
put("note-read")
put("note-write")
put("reaction-read")
put("reaction-write")
put("following-read") // フォロリク申請一覧で使われていた
put("following-write")
put("drive-read")
put("drive-write")
put("notification-read")
put("notification-write")
put("favorite-read")
put("favorites-read")
put("favorite-write")
put("account/read")
put("account/write")
put("messaging-read")
put("messaging-write")
put("vote-read")
put("vote-write")
// https://github.com/syuilo/misskey/issues/2341
}
@ -317,7 +319,7 @@ class TootApiClient(
if(isApiCancelled) return null
// 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 = ""
return ""
}
@ -441,11 +443,11 @@ class TootApiClient(
var bodyString = readBodyString(result, progressPath, jsonErrorParser)
?: return if(isApiCancelled) null else result
if( bodyString.isEmpty() ){
if(bodyString.isEmpty()) {
// 204 no content は 空オブジェクトと解釈する
result.data = JSONObject()
} else if(reStartJsonArray.matcher(bodyString).find()) {
result.data = bodyString.toJsonArray()
@ -457,20 +459,19 @@ class TootApiClient(
} else {
result.data = json
}
}else {
} else {
// HTMLならタグを除去する
val ct = response.body()?.contentType()
if(ct?.subtype() == "html") {
val decoded = DecodeOptions().decodeHTML(bodyString).toString()
bodyString =decoded
bodyString = decoded
}
val sb = StringBuilder()
.append(context.getString(R.string.response_not_json))
.append(' ')
.append(bodyString)
if(sb.isNotEmpty()) sb.append(' ')
sb.append("(HTTP ").append(Integer.toString(response.code()))
@ -480,8 +481,8 @@ class TootApiClient(
}
sb.append(")")
val url = response.request()?.url()?.toString()
if(url?.isNotEmpty()==true) {
val url = response.request()?.url()?.toString()
if(url?.isNotEmpty() == true) {
sb.append(' ').append(url)
}
@ -590,7 +591,7 @@ class TootApiClient(
return result
}
private fun prepareBrowserUrlMisskey( appSecret:String) : String? {
private fun prepareBrowserUrlMisskey(appSecret : String) : String? {
val result = TootApiResult.makeWithCaption(instance)
@ -709,8 +710,8 @@ class TootApiClient(
val jsonObject = r2?.jsonObject ?: return r2
val appSecret = jsonObject.parseString(KEY_MISSKEY_APP_SECRET)
if( appSecret?.isEmpty() != false) {
showToast(context,true,context.getString(R.string.cant_get_misskey_app_secret))
if(appSecret?.isEmpty() != false) {
showToast(context, true, context.getString(R.string.cant_get_misskey_app_secret))
return null
}
// {
@ -758,10 +759,10 @@ class TootApiClient(
?: return result.setError("missing client id")
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))
}
if(! sendRequest(result) {
JSONObject().apply {
put("appSecret", appSecret)
@ -821,7 +822,6 @@ class TootApiClient(
return result
}
// クライアントをタンスに登録
fun registerClient(scope_string : String, clientName : String) : TootApiResult? {
val result = TootApiResult.makeWithCaption(this.instance)
@ -1330,35 +1330,36 @@ class TootApiClient(
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)
?: return TootApiResult(context.getString(R.string.user_id_conversion_failed))
val delm = acct.indexOf('@')
val params = accessInfo.putMisskeyApiToken(JSONObject())
if(delm!=-1){
params.put("username",acct.substring(0,delm))
params.put("host",acct.substring(delm+1))
}else{
params.put("username",acct)
if(delm != - 1) {
params.put("username", acct.substring(0, delm))
params.put("host", acct.substring(delm + 1))
} else {
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
if(jsonObject != null) {
val tmp = TootParser(this.context,accessInfo).account(jsonObject)
if(tmp != null){
val tmp = TootParser(this.context, accessInfo).account(jsonObject)
if(tmp != null) {
result.data = tmp
} else {
result.setError(context.getString(R.string.user_id_conversion_failed))
}
}
return result
}else{
} else {
val path = String.format(
Locale.JAPAN,
Column.PATH_SEARCH,
@ -1367,7 +1368,7 @@ fun TootApiClient.syncAccountByUrl(accessInfo:SavedAccount,who_url:String):TootA
val result = this.request(path)
val jsonObject = result?.jsonObject
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()) {
result.data = tmp.accounts[0].get()
} else {
@ -1378,30 +1379,30 @@ fun TootApiClient.syncAccountByUrl(accessInfo:SavedAccount,who_url:String):TootA
}
}
fun TootApiClient.syncAccountByAcct(accessInfo:SavedAccount,acct:String):TootApiResult?{
if( accessInfo.isMisskey){
fun TootApiClient.syncAccountByAcct(accessInfo : SavedAccount, acct : String) : TootApiResult? {
if(accessInfo.isMisskey) {
val delm = acct.indexOf('@')
val params = accessInfo.putMisskeyApiToken(JSONObject())
if(delm!=-1){
params.put("username",acct.substring(0,delm))
params.put("host",acct.substring(delm+1))
}else{
params.put("username",acct)
if(delm != - 1) {
params.put("username", acct.substring(0, delm))
params.put("host", acct.substring(delm + 1))
} else {
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
if(jsonObject != null) {
val tmp = TootParser(this.context,accessInfo).account(jsonObject)
if(tmp != null){
val tmp = TootParser(this.context, accessInfo).account(jsonObject)
if(tmp != null) {
result.data = tmp
} else {
result.setError(context.getString(R.string.user_id_conversion_failed))
}
}
return result
}else{
} else {
val path = String.format(
Locale.JAPAN,
Column.PATH_SEARCH,
@ -1410,7 +1411,7 @@ fun TootApiClient.syncAccountByAcct(accessInfo:SavedAccount,acct:String):TootApi
val result = this.request(path)
val jsonObject = result?.jsonObject
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()) {
result.data = tmp.accounts[0].get()
} else {
@ -1419,4 +1420,58 @@ fun TootApiClient.syncAccountByAcct(accessInfo:SavedAccount,acct:String):TootApi
}
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")
this.host_access = parser.linkHelper.host
this.uri = "https://$instance/notes/$misskeyId"
this.url = "https://$instance/notes/$misskeyId"
val uri = src.parseString("uri")
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.time_created_at = parseTime(this.created_at)
this.id = EntityIdString(src.parseString("id") ?: error("missing id"))