アカウントの追加/更新に失敗するバグの修正
This commit is contained in:
parent
e4df54479c
commit
3f3279243f
|
@ -27,6 +27,7 @@ import android.widget.CompoundButton
|
|||
import android.widget.EditText
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
@ -34,12 +35,8 @@ import java.io.FileOutputStream
|
|||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.TootTask
|
||||
import jp.juggler.subwaytooter.api.TootTaskRunner
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
|
||||
|
@ -52,6 +49,7 @@ import okhttp3.MultipartBody
|
|||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okio.BufferedSink
|
||||
import org.json.JSONObject
|
||||
|
||||
class ActAccountSetting
|
||||
: AppCompatActivity(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
|
||||
|
@ -103,6 +101,7 @@ class ActAccountSetting
|
|||
private lateinit var swNSFWOpen : Switch
|
||||
private lateinit var swDontShowTimeout : Switch
|
||||
private lateinit var btnOpenBrowser : Button
|
||||
private lateinit var btnPushTest : Button
|
||||
private lateinit var cbNotificationMention : CheckBox
|
||||
private lateinit var cbNotificationBoost : CheckBox
|
||||
private lateinit var cbNotificationFavourite : CheckBox
|
||||
|
@ -274,6 +273,7 @@ class ActAccountSetting
|
|||
swNSFWOpen = findViewById(R.id.swNSFWOpen)
|
||||
swDontShowTimeout = findViewById(R.id.swDontShowTimeout)
|
||||
btnOpenBrowser = findViewById(R.id.btnOpenBrowser)
|
||||
btnPushTest= findViewById(R.id.btnPushTest)
|
||||
cbNotificationMention = findViewById(R.id.cbNotificationMention)
|
||||
cbNotificationBoost = findViewById(R.id.cbNotificationBoost)
|
||||
cbNotificationFavourite = findViewById(R.id.cbNotificationFavourite)
|
||||
|
@ -318,6 +318,7 @@ class ActAccountSetting
|
|||
btnFields = findViewById(R.id.btnFields)
|
||||
|
||||
btnOpenBrowser.setOnClickListener(this)
|
||||
btnPushTest.setOnClickListener(this)
|
||||
btnAccessToken.setOnClickListener(this)
|
||||
btnInputAccessToken.setOnClickListener(this)
|
||||
btnAccountRemove.setOnClickListener(this)
|
||||
|
@ -488,7 +489,8 @@ class ActAccountSetting
|
|||
R.id.btnAccountRemove -> performAccountRemove()
|
||||
R.id.btnVisibility -> performVisibility()
|
||||
R.id.btnOpenBrowser -> open_browser("https://" + account.host + "/")
|
||||
|
||||
R.id.btnPushTest-> startTest()
|
||||
|
||||
R.id.btnUserCustom -> ActNickname.open(
|
||||
this,
|
||||
full_acct,
|
||||
|
@ -895,11 +897,11 @@ class ActAccountSetting
|
|||
}
|
||||
}
|
||||
|
||||
internal fun updateCredential(key : String, value : Any) {
|
||||
private fun updateCredential(key : String, value : Any) {
|
||||
updateCredential(listOf(Pair(key, value)))
|
||||
}
|
||||
|
||||
internal fun updateCredential(args : List<Pair<String, Any>>) {
|
||||
private fun updateCredential(args : List<Pair<String, Any>>) {
|
||||
|
||||
TootTaskRunner(this).run(account, object : TootTask {
|
||||
|
||||
|
@ -1324,5 +1326,75 @@ class ActAccountSetting
|
|||
task.executeOnExecutor(App1.task_executor)
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private fun startTest() {
|
||||
TootTaskRunner(this).run(account,object:TootTask{
|
||||
val sb = StringBuilder()
|
||||
|
||||
private fun addLog(s:String) {
|
||||
if(sb.isNotEmpty()) sb.append('\n')
|
||||
sb.append(s)
|
||||
}
|
||||
|
||||
override fun background(client : TootApiClient) : TootApiResult? { // TODO
|
||||
// インスタンスバージョンの確認
|
||||
var r = client.getInstanceInformation2()
|
||||
val ti = r?.data as? TootInstance ?: return r
|
||||
if(!ti.isEnoughVersion(TootInstance.VERSION_2_4)){
|
||||
addLog("Too old instance version ${ti.version} that does not support Push API.")
|
||||
return r
|
||||
}
|
||||
|
||||
// プッシュ通知の登録
|
||||
var json :JSONObject? = JSONObject().also{
|
||||
it.put("subscription",JSONObject().also {
|
||||
it.put("endpoint","${PollingWorker.APP_SERVER}/webpushcallback")
|
||||
it.put("keys",JSONObject().also {
|
||||
it.put(
|
||||
"p256dh",
|
||||
"BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8="
|
||||
)
|
||||
it.put("auth", "eH_C8rq2raXqlcBVDa1gLg==")
|
||||
})
|
||||
})
|
||||
it.put("data","<<DATA>>")
|
||||
}
|
||||
var req = Request.Builder().post(
|
||||
RequestBody.create(TootApiClient.MEDIA_TYPE_JSON,json.toString())
|
||||
)
|
||||
r = client.request("/api/v1/push/subscription",req)
|
||||
var response = r?.response
|
||||
if( response != null ){
|
||||
when(response.code()){
|
||||
404 ->{
|
||||
addLog("this instance has no API endpoint 'POST /api/v1/push/subscription'. instance version is ${ti.version}")
|
||||
return r
|
||||
}
|
||||
403 ->{
|
||||
addLog("Your access token does not contains push scope. updating access token is recommended.")
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
addLog( "${response.request()}" )
|
||||
addLog("${response.code()} ${response.message()}")
|
||||
json = r?.jsonObject
|
||||
if(json != null) {
|
||||
addLog(json.toString())
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
val e = result?.error
|
||||
if(e != null) addLog(e)
|
||||
AlertDialog.Builder(this@ActAccountSetting)
|
||||
.setMessage(sb)
|
||||
.setPositiveButton(R.string.close,null)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1609,7 +1609,10 @@ class ActMain : AppCompatActivity()
|
|||
this.host = instance
|
||||
val client_name = Pref.spClientName(this@ActMain)
|
||||
val result = client.authentication2(client_name, code)
|
||||
this.ta = TootParser(this@ActMain, object : LinkHelper {})
|
||||
this.ta = TootParser(this@ActMain, object : LinkHelper {
|
||||
override val host : String?
|
||||
get() = instance
|
||||
})
|
||||
.account(result?.jsonObject)
|
||||
return result
|
||||
}
|
||||
|
@ -1752,8 +1755,10 @@ class ActMain : AppCompatActivity()
|
|||
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
val result = client.getUserCredential(access_token)
|
||||
this.ta =
|
||||
TootParser(this@ActMain, object : LinkHelper {}).account(result?.jsonObject)
|
||||
this.ta = TootParser(this@ActMain, object : LinkHelper {
|
||||
override val host : String?
|
||||
get() = host
|
||||
}).account(result?.jsonObject)
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
@ -2152,7 +2152,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
val viewRoot = layoutInflater.inflate(R.layout.dlg_plugin_missing, null, false)
|
||||
|
||||
val tvText = viewRoot.findViewById<TextView>(R.id.tvText)
|
||||
val lcc = object : LinkHelper {}
|
||||
val lcc = object : LinkHelper {
|
||||
override val host : String?
|
||||
get() = null
|
||||
}
|
||||
val sv = DecodeOptions(this@ActPost, lcc).decodeHTML(text)
|
||||
tvText.text = sv
|
||||
tvText.movementMethod = LinkMovementMethod.getInstance()
|
||||
|
|
|
@ -11,6 +11,7 @@ import jp.juggler.subwaytooter.Pref
|
|||
import jp.juggler.subwaytooter.table.ClientInfo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.put
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import okhttp3.*
|
||||
|
@ -58,9 +59,10 @@ class TootApiClient(
|
|||
|
||||
private const val DEFAULT_CLIENT_NAME = "SubwayTooter"
|
||||
internal const val KEY_CLIENT_CREDENTIAL = "SubwayTooterClientCredential"
|
||||
internal const val KEY_CLIENT_SCOPE = "SubwayTooterClientScope"
|
||||
|
||||
private const val KEY_AUTH_VERSION = "SubwayTooterAuthVersion"
|
||||
private const val AUTH_VERSION = 1
|
||||
private const val AUTH_VERSION = 3
|
||||
private const val REDIRECT_URL = "subwaytooter://oauth/"
|
||||
|
||||
private const val NO_INFORMATION = "(no information)"
|
||||
|
@ -177,6 +179,11 @@ class TootApiClient(
|
|||
return sb.toString().replace("\n+".toRegex(), "\n")
|
||||
}
|
||||
|
||||
fun getScopeString(ti : TootInstance) = when {
|
||||
ti.isEnoughVersion(TootInstance.VERSION_2_4) -> "read+write+follow+push"
|
||||
else -> "read+write+follow"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
|
@ -278,7 +285,7 @@ class TootApiClient(
|
|||
|
||||
// レスポンスがエラーかボディがカラならエラー状態を設定する
|
||||
// 例外を出すかも
|
||||
internal fun readBodyBytes(
|
||||
private fun readBodyBytes(
|
||||
result : TootApiResult,
|
||||
progressPath : String? = null,
|
||||
jsonErrorParser : (json : JSONObject) -> String? = DEFAULT_JSON_ERROR_PARSER
|
||||
|
@ -321,7 +328,7 @@ class TootApiClient(
|
|||
}
|
||||
}
|
||||
|
||||
internal fun parseBytes(
|
||||
private fun parseBytes(
|
||||
result : TootApiResult,
|
||||
progressPath : String? = null,
|
||||
jsonErrorParser : (json : JSONObject) -> String? = DEFAULT_JSON_ERROR_PARSER
|
||||
|
@ -416,11 +423,11 @@ class TootApiClient(
|
|||
|
||||
log.d("request: $path")
|
||||
|
||||
request_builder.url("https://" + instance + path)
|
||||
request_builder.url("https://$instance$path")
|
||||
|
||||
val access_token = account.getAccessToken()
|
||||
if(access_token?.isNotEmpty() == true) {
|
||||
request_builder.header("Authorization", "Bearer " + access_token)
|
||||
request_builder.header("Authorization", "Bearer $access_token")
|
||||
}
|
||||
|
||||
request_builder.build()
|
||||
|
@ -444,12 +451,32 @@ class TootApiClient(
|
|||
return parseJson(result)
|
||||
}
|
||||
|
||||
// インスタンス情報を取得する
|
||||
internal fun getInstanceInformation2() : TootApiResult? {
|
||||
val r = getInstanceInformation()
|
||||
if(r != null) {
|
||||
val json = r.jsonObject
|
||||
if(json != null) {
|
||||
val parser = TootParser(context, object : LinkHelper {
|
||||
override val host : String?
|
||||
get() = instance
|
||||
})
|
||||
val ti = parser.instance(json)
|
||||
if(ti != null) {
|
||||
r.data = ti
|
||||
} else {
|
||||
r.setError("can't parse data in /api/v1/instance")
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// クライアントをタンスに登録
|
||||
internal fun registerClient(clientName : String) : TootApiResult? {
|
||||
internal fun registerClient(scope_string : String, clientName : String) : TootApiResult? {
|
||||
val result = TootApiResult.makeWithCaption(this.instance)
|
||||
if(result.error != null) return result
|
||||
val instance = result.caption // same to instance
|
||||
|
||||
// OAuth2 クライアント登録
|
||||
if(! sendRequest(result) {
|
||||
Request.Builder()
|
||||
|
@ -459,7 +486,7 @@ class TootApiClient(
|
|||
MEDIA_TYPE_FORM_URL_ENCODED,
|
||||
"client_name=" + clientName.encodePercent()
|
||||
+ "&redirect_uris=" + REDIRECT_URL.encodePercent()
|
||||
+ "&scopes=read write follow"
|
||||
+ "&scopes=$scope_string"
|
||||
)
|
||||
)
|
||||
.build()
|
||||
|
@ -525,8 +552,39 @@ class TootApiClient(
|
|||
return parseJson(result)
|
||||
}
|
||||
|
||||
// // client_credentialを無効にする
|
||||
internal fun revokeClientCredential(
|
||||
client_info : JSONObject,
|
||||
client_credential : String
|
||||
) : TootApiResult? {
|
||||
val result = TootApiResult.makeWithCaption(this.instance)
|
||||
if(result.error != null) return result
|
||||
|
||||
val client_id = client_info.parseString("client_id")
|
||||
?: return result.setError("missing client_id")
|
||||
|
||||
val client_secret = client_info.parseString("client_secret")
|
||||
?: return result.setError("missing client_secret")
|
||||
|
||||
if(! sendRequest(result) {
|
||||
Request.Builder()
|
||||
.url("https://$instance/oauth/revoke")
|
||||
.post(
|
||||
RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED,
|
||||
"token=" + client_credential.encodePercent()
|
||||
+ "&client_id=" + client_id.encodePercent()
|
||||
+ "&client_secret=" + client_secret.encodePercent()
|
||||
)
|
||||
)
|
||||
.build()
|
||||
}) return result
|
||||
|
||||
return parseJson(result)
|
||||
}
|
||||
|
||||
// 認証ページURLを作る
|
||||
internal fun prepareBrowserUrl(client_info : JSONObject) : String? {
|
||||
internal fun prepareBrowserUrl(scope_string : String, client_info : JSONObject) : String? {
|
||||
val account = this.account
|
||||
val client_id = client_info.parseString("client_id") ?: return null
|
||||
|
||||
|
@ -534,8 +592,8 @@ class TootApiClient(
|
|||
+ "?client_id=" + client_id.encodePercent()
|
||||
+ "&response_type=code"
|
||||
+ "&redirect_uri=" + REDIRECT_URL.encodePercent()
|
||||
+ "&scope=read+write+follow"
|
||||
+ "&scopes=read+write+follow"
|
||||
+ "&scope=$scope_string"
|
||||
+ "&scopes=$scope_string"
|
||||
+ "&state=" + (if(account != null) "db:" + account.db_id else "host:" + instance)
|
||||
+ "&grant_type=authorization_code"
|
||||
+ "&approval_prompt=force"
|
||||
|
@ -545,6 +603,12 @@ class TootApiClient(
|
|||
|
||||
// クライアントを登録してブラウザで開くURLを生成する
|
||||
fun authentication1(clientNameArg : String) : TootApiResult? {
|
||||
|
||||
// インスタンス情報の取得
|
||||
val ri = getInstanceInformation2()
|
||||
val ti = ri?.data as? TootInstance ?: return ri
|
||||
val scope_string = getScopeString(ti)
|
||||
|
||||
val result = TootApiResult.makeWithCaption(this.instance)
|
||||
if(result.error != null) return result
|
||||
val instance = result.caption // same to instance
|
||||
|
@ -555,6 +619,7 @@ class TootApiClient(
|
|||
if(client_info != null) {
|
||||
|
||||
var client_credential = client_info.parseString(KEY_CLIENT_CREDENTIAL)
|
||||
val old_scope = client_info.parseString(KEY_CLIENT_SCOPE)
|
||||
|
||||
// client_credential をまだ取得していないなら取得する
|
||||
if(client_credential?.isEmpty() != false) {
|
||||
|
@ -573,19 +638,33 @@ class TootApiClient(
|
|||
if(client_credential?.isNotEmpty() == true) {
|
||||
val resultSub = verifyClientCredential(client_credential)
|
||||
if(resultSub?.jsonObject != null) {
|
||||
result.data = prepareBrowserUrl(client_info)
|
||||
return result
|
||||
|
||||
if(old_scope != scope_string) {
|
||||
// マストドン2.4でスコープが追加された
|
||||
// 取得時のスコープ指定がマッチしない(もしくは記録されていない)ならクライアント情報を再利用してはいけない
|
||||
ClientInfo.delete(instance, client_name)
|
||||
|
||||
// client credential をタンスから消去する
|
||||
revokeClientCredential(client_info, client_credential)
|
||||
|
||||
// FIXME クライアントアプリ情報そのものはまだサーバに残っているが、明示的に消す方法は現状存在しない
|
||||
} else {
|
||||
// クライアント情報を再利用する
|
||||
result.data = prepareBrowserUrl(scope_string, client_info)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val r2 = registerClient(client_name)
|
||||
val r2 = registerClient(scope_string, client_name)
|
||||
val jsonObject = r2?.jsonObject ?: return r2
|
||||
|
||||
// {"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"******","client_secret":"******"}
|
||||
jsonObject.put(KEY_AUTH_VERSION, AUTH_VERSION)
|
||||
jsonObject.put(KEY_CLIENT_SCOPE, scope_string)
|
||||
ClientInfo.save(instance, client_name, jsonObject.toString())
|
||||
result.data = prepareBrowserUrl(jsonObject)
|
||||
result.data = prepareBrowserUrl(scope_string, jsonObject)
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -602,6 +681,8 @@ class TootApiClient(
|
|||
|
||||
if(! sendRequest(result) {
|
||||
|
||||
val scope_string = client_info.optString(KEY_CLIENT_SCOPE)
|
||||
|
||||
val client_id = client_info.parseString("client_id")
|
||||
val client_secret = client_info.parseString("client_secret")
|
||||
if(client_id == null) return result.setError("missing client_id ")
|
||||
|
@ -612,8 +693,8 @@ class TootApiClient(
|
|||
+ "&client_id=" + client_id.encodePercent()
|
||||
+ "&redirect_uri=" + REDIRECT_URL.encodePercent()
|
||||
+ "&client_secret=" + client_secret.encodePercent()
|
||||
+ "&scope=read+write+follow"
|
||||
+ "&scopes=read+write+follow")
|
||||
+ "&scope=$scope_string"
|
||||
+ "&scopes=$scope_string")
|
||||
|
||||
Request.Builder()
|
||||
.url("https://$instance/oauth/token")
|
||||
|
|
|
@ -9,6 +9,9 @@ class TootInstance(parser:TootParser,src : JSONObject) {
|
|||
|
||||
companion object {
|
||||
val rePleroma = Pattern.compile("\\bpleroma\\b",Pattern.CASE_INSENSITIVE)
|
||||
|
||||
val VERSION_2_4 = VersionString("2.4")
|
||||
|
||||
}
|
||||
|
||||
// いつ取得したか(内部利用)
|
||||
|
|
|
@ -66,9 +66,9 @@ object ClientInfo :TableCompanion {
|
|||
}
|
||||
|
||||
// 単体テスト用。インスタンス名を指定して削除する
|
||||
internal fun delete(instance : String) {
|
||||
fun delete(instance : String,client_name : String ) {
|
||||
try {
|
||||
App1.database.delete(table, "$COL_HOST=?", arrayOf(instance))
|
||||
App1.database.delete(table, "$COL_HOST=? and $COL_CLIENT_NAME=?", arrayOf(instance, client_name))
|
||||
} catch(ex : Throwable) {
|
||||
log.e(ex, "delete failed.")
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ interface LinkHelper {
|
|||
|
||||
// SavedAccountのロード時にhostを供給する必要があった
|
||||
val host : String?
|
||||
get() = null
|
||||
|
||||
fun findAcctColor(url : String?) : AcctColor? = null
|
||||
|
||||
|
|
|
@ -359,7 +359,17 @@
|
|||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPushTest"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:ellipsize="start"
|
||||
android:text="@string/push_notification_test"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
|
|
|
@ -659,8 +659,9 @@
|
|||
<string name="available_mastodon_2_4_later">(available in Mastodon 2.4 or later)</string>
|
||||
<string name="confirm_boost_private_from">Boost private status from %1$s ? It\'s shown to all followers.</string>
|
||||
<string name="boost_private_toot_not_allowed">You can\'t boost private toot by another person.</string>
|
||||
<string name="push_notification_test">Push Notification Test (Mastodon 2.4 or later)</string>
|
||||
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
|
||||
<!--<string name="abc_action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>-->
|
||||
<!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->
|
||||
|
|
|
@ -938,5 +938,6 @@
|
|||
<string name="available_mastodon_2_4_later">(マストドン2.4以降で利用可能)</string>
|
||||
<string name="confirm_boost_private_from">非公開トゥートを %1$s からブーストしますか? 全てのフォロワーに公開されます</string>
|
||||
<string name="boost_private_toot_not_allowed">非公開トゥートをブーストできるのは本人だけです</string>
|
||||
<string name="push_notification_test">Push Notification Test (Mastodon 2.4 or later)</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -645,4 +645,5 @@
|
|||
<string name="available_mastodon_2_4_later">(available in Mastodon 2.4 or later)</string>
|
||||
<string name="confirm_boost_private_from">Boost private status from %1$s ? It\'s shown to all followers.</string>
|
||||
<string name="boost_private_toot_not_allowed">You can\'t boost private toot by another person.</string>
|
||||
<string name="push_notification_test">Push Notification Test (Mastodon 2.4 or later)</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue