fix accesstoken update issue
This commit is contained in:
parent
7e7f726e6a
commit
03352bb180
|
@ -32,6 +32,7 @@ import jp.juggler.util.data.notEmpty
|
|||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -256,12 +257,16 @@ fun ActMain.launchDialogs() {
|
|||
}
|
||||
}
|
||||
|
||||
fun ActMain.afterNotificationGranted() {
|
||||
suspend fun ActMain.afterNotificationGranted() {
|
||||
sideMenuAdapter.filterListItems()
|
||||
|
||||
// Workの掃除
|
||||
WorkManager.getInstance(applicationContext).pruneWork()
|
||||
|
||||
// 定期的にendpointを再登録したい
|
||||
PushWorker.enqueueRegisterEndpoint(applicationContext, keepAliveMode = true)
|
||||
// 認証やアクセストークン更新から戻ってきた時に処理を重ねたくない
|
||||
delay(2000L)
|
||||
if (!accountVerifyMutex.isLocked) {
|
||||
// 定期的にendpointを再登録したい
|
||||
PushWorker.enqueueRegisterEndpoint(applicationContext, keepAliveMode = true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
|||
import jp.juggler.subwaytooter.notification.recycleClickedNotification
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.push.PushWorker
|
||||
import jp.juggler.subwaytooter.push.fcmHandler
|
||||
import jp.juggler.subwaytooter.push.pushRepo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
|
@ -44,7 +43,8 @@ import jp.juggler.util.data.groupEx
|
|||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.queryIntentActivitiesCompat
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
|
@ -229,32 +229,36 @@ private fun ActMain.handleOAuth2Callback(uri: Uri) {
|
|||
}
|
||||
}
|
||||
|
||||
val accountVerifyMutex = Mutex()
|
||||
|
||||
/**
|
||||
* アカウントを確認した後に呼ばれる
|
||||
* @return 何かデータを更新したら真
|
||||
*/
|
||||
fun ActMain.afterAccountVerify(auth2Result: Auth2Result): Boolean = auth2Result.run {
|
||||
suspend fun ActMain.afterAccountVerify(auth2Result: Auth2Result): Boolean = auth2Result.run {
|
||||
accountVerifyMutex.withLock {
|
||||
// ユーザ情報中のacctはfull acct ではないので、組み立てる
|
||||
val newAcct = Acct.parse(tootAccount.username, apDomain)
|
||||
|
||||
// ユーザ情報中のacctはfull acct ではないので、組み立てる
|
||||
val newAcct = Acct.parse(tootAccount.username, apDomain)
|
||||
// full acctだよな?
|
||||
"""\A[^@]+@[^@]+\z""".toRegex().find(newAcct.ascii)
|
||||
?: error("afterAccountAdd: incorrect userAcct. ${newAcct.ascii}")
|
||||
|
||||
// full acctだよな?
|
||||
"""\A[^@]+@[^@]+\z""".toRegex().find(newAcct.ascii)
|
||||
?: error("afterAccountAdd: incorrect userAcct. ${newAcct.ascii}")
|
||||
|
||||
// 「アカウント追加のハズが既存アカウントで認証していた」
|
||||
// 「アクセストークン更新のハズが別アカウントで認証していた」
|
||||
// などを防止するため、full acctでアプリ内DBを検索
|
||||
when (val sa = daoSavedAccount.loadAccountByAcct(newAcct)) {
|
||||
null -> afterAccountAdd(newAcct, auth2Result)
|
||||
else -> afterAccessTokenUpdate(auth2Result, sa)
|
||||
// 「アカウント追加のハズが既存アカウントで認証していた」
|
||||
// 「アクセストークン更新のハズが別アカウントで認証していた」
|
||||
// などを防止するため、full acctでアプリ内DBを検索
|
||||
when (val sa = daoSavedAccount.loadAccountByAcct(newAcct)) {
|
||||
null -> afterAccountAdd(newAcct, auth2Result)
|
||||
else -> afterAccessTokenUpdate(auth2Result, sa)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ActMain.afterAccessTokenUpdate(
|
||||
private suspend fun ActMain.afterAccessTokenUpdate(
|
||||
auth2Result: Auth2Result,
|
||||
sa: SavedAccount,
|
||||
): Boolean {
|
||||
log.i("afterAccessTokenUpdate token ${sa.bearerAccessToken ?: sa.misskeyApiToken} =>${auth2Result.tokenJson}")
|
||||
// DBの情報を更新する
|
||||
authRepo.updateTokenInfo(sa, auth2Result)
|
||||
|
||||
|
@ -275,7 +279,7 @@ private fun ActMain.afterAccessTokenUpdate(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun ActMain.afterAccountAdd(
|
||||
private suspend fun ActMain.afterAccountAdd(
|
||||
newAcct: Acct,
|
||||
auth2Result: Auth2Result,
|
||||
): Boolean {
|
||||
|
@ -350,17 +354,22 @@ fun ActMain.handleSharedIntent(intent: Intent) {
|
|||
}
|
||||
|
||||
// アカウントを追加/更新したらappServerHashの取得をやりなおす
|
||||
fun ActMain.updatePushDistributer() {
|
||||
suspend fun ActMain.updatePushDistributer() {
|
||||
when {
|
||||
fcmHandler.noFcm && prefDevice.pushDistributor.isNullOrEmpty() -> {
|
||||
try {
|
||||
selectPushDistributor()
|
||||
// 選択したら
|
||||
} catch (_: CancellationException) {
|
||||
// 選択しなかった場合は購読の更新を行わない
|
||||
selectPushDistributor()
|
||||
// 選択しなかった場合は購読の更新を行わない
|
||||
}
|
||||
else -> {
|
||||
runInProgress(cancellable = false) { reporter ->
|
||||
withContext(AppDispatchers.DEFAULT) {
|
||||
pushRepo.switchDistributor(
|
||||
prefDevice.pushDistributor,
|
||||
reporter = reporter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> PushWorker.enqueueRegisterEndpoint(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ class AuthMastodon(override val client: TootApiClient) : AuthBase() {
|
|||
companion object {
|
||||
private val log = LogCategory("MastodonAuth")
|
||||
|
||||
@Suppress("MayBeConstant")
|
||||
val DEBUG_AUTH = false
|
||||
|
||||
const val callbackUrl = "${BuildConfig.customScheme}://oauth/"
|
||||
|
||||
fun mastodonScope(ti: TootInstance?) = when {
|
||||
|
@ -153,6 +156,7 @@ class AuthMastodon(override val client: TootApiClient) : AuthBase() {
|
|||
put(KEY_AUTH_VERSION, AUTH_VERSION)
|
||||
put(KEY_CLIENT_SCOPE, scopeString)
|
||||
}
|
||||
if(DEBUG_AUTH) log.i("DEBUG_AUTH client_id=${clientInfo.string("client_id")}")
|
||||
// client credentialを取得して保存する
|
||||
// この時点ではまだ client credential がないので、必ず更新と保存が行われる
|
||||
prepareClientCredential(apiHost, clientInfo, clientName)
|
||||
|
@ -291,6 +295,7 @@ class AuthMastodon(override val client: TootApiClient) : AuthBase() {
|
|||
|
||||
val accessToken = tokenInfo.string("access_token")
|
||||
?.notEmpty() ?: error("can't parse access token.")
|
||||
if(DEBUG_AUTH) log.i("DEBUG_AUTH accessToken=${accessToken}")
|
||||
|
||||
val accountJson = verifyAccount(
|
||||
accessToken = accessToken,
|
||||
|
|
|
@ -115,7 +115,7 @@ object AppDataExporter {
|
|||
writer.name(jsonKey)
|
||||
writer.beginArray()
|
||||
|
||||
appDatabase.rawQuery("select from $table", emptyArray()).use { cursor ->
|
||||
appDatabase.rawQuery("select * from $table", emptyArray()).use { cursor ->
|
||||
val names = ArrayList<String>()
|
||||
val column_count = cursor.columnCount
|
||||
for (i in 0 until column_count) {
|
||||
|
|
|
@ -68,7 +68,7 @@ class Column(
|
|||
val account_db_id = src.long(ColumnEncoder.KEY_ACCOUNT_ROW_ID) ?: -1L
|
||||
return if (account_db_id > 0) {
|
||||
daoSavedAccount.loadAccount(account_db_id)
|
||||
?: error("missing account")
|
||||
?: error("missing account for db_id $account_db_id")
|
||||
} else {
|
||||
SavedAccount.na
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import jp.juggler.subwaytooter.api.entity.TootAccountRef
|
|||
import jp.juggler.subwaytooter.column.Column
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.withCaption
|
||||
import jp.juggler.util.ui.scan
|
||||
|
||||
abstract class ViewHolderHeaderBase(viewRoot: View) : RecyclerView.ViewHolder(viewRoot) {
|
||||
|
@ -28,15 +29,18 @@ abstract class ViewHolderHeaderBase(viewRoot: View) : RecyclerView.ViewHolder(vi
|
|||
if (v is Button) {
|
||||
// ボタンは太字なので触らない
|
||||
} else if (v is TextView) {
|
||||
|
||||
v.typeface = ActMain.timelineFont
|
||||
try {
|
||||
activity.timelineFontSizeSp
|
||||
.takeIf { it.isFinite() }
|
||||
?.let { v.textSize = it }
|
||||
|
||||
activity.timelineFontSizeSp
|
||||
.takeIf { it.isFinite() }
|
||||
?.let { v.textSize = it }
|
||||
|
||||
activity.timelineSpacing
|
||||
?.let { v.setLineSpacing(0f, it) }
|
||||
activity.timelineSpacing
|
||||
?.let { v.setLineSpacing(0f, it) }
|
||||
} catch (ex: NullPointerException) {
|
||||
// 非null型なのになぜかnull例外が出る
|
||||
log.w(ex.withCaption("can't read timelineFontSizeSp, timelineSpacing"))
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "can't initialize text styles.")
|
||||
|
|
|
@ -91,6 +91,7 @@ internal class ViewHolderHeaderInstance(
|
|||
btnExplore.isEnabledAlpha = false
|
||||
tvConfiguration.text = ""
|
||||
tvFedibirdCapacities.text = ""
|
||||
tvPlelomaFeatures.text = ""
|
||||
} else {
|
||||
val domain = instance.apDomain
|
||||
btnInstance.text = when {
|
||||
|
@ -163,6 +164,8 @@ internal class ViewHolderHeaderInstance(
|
|||
instance.configuration?.toString(1, sort = true) ?: ""
|
||||
tvFedibirdCapacities.text =
|
||||
instance.fedibirdCapabilities?.sorted()?.joinToString("\n") ?: ""
|
||||
tvPlelomaFeatures.text=
|
||||
instance.pleromaFeatures?.sorted()?.joinToString("\n") ?: ""
|
||||
}
|
||||
|
||||
tvHandshake.text = when (val handshake = column.handshake) {
|
||||
|
|
|
@ -33,12 +33,13 @@ abstract class PushBase {
|
|||
protected abstract val daoStatus: AccountNotificationStatus.Access
|
||||
|
||||
// 購読の確認と更新
|
||||
// 失敗したらエラーメッセージを返す。成功したらnull
|
||||
abstract suspend fun updateSubscription(
|
||||
subLog: SubscriptionLogger,
|
||||
account: SavedAccount,
|
||||
willRemoveSubscription: Boolean,
|
||||
forceUpdate:Boolean,
|
||||
)
|
||||
):String?
|
||||
|
||||
// プッシュメッセージのJSONデータを通知用に整形
|
||||
abstract suspend fun formatPushMessage(
|
||||
|
|
|
@ -8,12 +8,16 @@ import jp.juggler.subwaytooter.R
|
|||
import jp.juggler.subwaytooter.api.ApiError
|
||||
import jp.juggler.subwaytooter.api.TootApiCallback
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.auth.AuthMastodon
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.push.ApiPushMastodon
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.pref.lazyContext
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.subwaytooter.table.AccountNotificationStatus
|
||||
import jp.juggler.subwaytooter.table.PushMessage
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.appDatabase
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.time.parseTimeIso8601
|
||||
|
@ -41,26 +45,25 @@ class PushMastodon(
|
|||
account: SavedAccount,
|
||||
willRemoveSubscription: Boolean,
|
||||
forceUpdate: Boolean,
|
||||
) {
|
||||
): String? {
|
||||
val deviceHash = deviceHash(account)
|
||||
val newUrl = snsCallbackUrl(account) // appServerHashを参照する
|
||||
if (newUrl.isNullOrEmpty()) {
|
||||
if (willRemoveSubscription) {
|
||||
val msg =
|
||||
lazyContext.getString(R.string.push_subscription_app_server_hash_missing_but_ok)
|
||||
subLog.i(msg)
|
||||
} else {
|
||||
val msg =
|
||||
lazyContext.getString(R.string.push_subscription_app_server_hash_missing_error)
|
||||
subLog.e(msg)
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
msg
|
||||
return when {
|
||||
willRemoveSubscription -> {
|
||||
val msg = lazyContext.getString(
|
||||
R.string.push_subscription_app_server_hash_missing_but_ok
|
||||
)
|
||||
subLog.i(msg)
|
||||
null
|
||||
}
|
||||
else -> lazyContext.getString(
|
||||
R.string.push_subscription_app_server_hash_missing_error
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (AuthMastodon.DEBUG_AUTH) log.i("DEBUG_AUTH bearerAccessToken=${account.bearerAccessToken} ${account.acct}")
|
||||
val oldSubscription = try {
|
||||
api.getPushSubscription(account)
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -90,16 +93,12 @@ class PushMastodon(
|
|||
}
|
||||
}
|
||||
if (params["dh"] != deviceHash && !isOldSubscription(account, oldEndpointUrl)) {
|
||||
|
||||
// この端末で作成した購読ではない。
|
||||
// TODO: 古い形式のURLを移行できないか?
|
||||
log.w("deviceHash not match. keep it for other devices. ${account.acct} $oldEndpointUrl")
|
||||
subLog.e(R.string.push_subscription_exists_but_not_created_by_this_device)
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
context.getString(R.string.push_subscription_exists_but_not_created_by_this_device)
|
||||
return context.getString(
|
||||
R.string.push_subscription_exists_but_not_created_by_this_device
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,14 +113,22 @@ class PushMastodon(
|
|||
api.deletePushSubscription(account)
|
||||
}
|
||||
}
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
val newAlerts = account.alerts()
|
||||
// サーバのバージョンを見て、サーバの知らないalertを無視して比較する
|
||||
val client = TootApiClient(context, callback = object : TootApiCallback {
|
||||
override suspend fun isApiCancelled(): Boolean = !coroutineContext.isActive
|
||||
})
|
||||
client.account = account
|
||||
val ti = TootInstance.getExOrThrow(client)
|
||||
|
||||
val newAlerts = account.alerts(ti)
|
||||
|
||||
val isSameAlert = isSameAlerts(
|
||||
subLog = subLog,
|
||||
account = account,
|
||||
ti = ti,
|
||||
oldSubscriptionJson = oldSubscription,
|
||||
newAlerts = newAlerts,
|
||||
)
|
||||
|
@ -135,7 +142,7 @@ class PushMastodon(
|
|||
) {
|
||||
// 現在の更新を使い続ける
|
||||
subLog.i(R.string.push_subscription_keep_using)
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
if (newUrl == oldEndpointUrl) {
|
||||
|
@ -179,6 +186,7 @@ class PushMastodon(
|
|||
)
|
||||
subLog.i(R.string.push_subscription_completed)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isOldSubscription(account: SavedAccount, url: String): Boolean {
|
||||
|
@ -189,7 +197,7 @@ class PushMastodon(
|
|||
// /{flags }
|
||||
// /{ client identifier}
|
||||
|
||||
val clientIdentifierOld = url.toHttpUrlOrNull()?.pathSegments?.elementAtOrNull(4)
|
||||
val clientIdentifierOld = url.toHttpUrlOrNull()?.pathSegments?.elementAtOrNull(4)
|
||||
?: return false
|
||||
val installId = prefDevice.installIdV1?.notEmpty() ?: return false
|
||||
val accessToken = account.bearerAccessToken?.notEmpty() ?: return false
|
||||
|
@ -197,9 +205,10 @@ class PushMastodon(
|
|||
return clientIdentifier == clientIdentifierOld
|
||||
}
|
||||
|
||||
private suspend fun isSameAlerts(
|
||||
private fun isSameAlerts(
|
||||
subLog: SubscriptionLogger,
|
||||
account: SavedAccount,
|
||||
ti: TootInstance,
|
||||
oldSubscriptionJson: JsonObject?,
|
||||
newAlerts: JsonObject,
|
||||
): Boolean {
|
||||
|
@ -210,7 +219,7 @@ class PushMastodon(
|
|||
// flagsの値が変わりendpoint URLも変わった状態で購読を自動更新してしまう
|
||||
// しかしそのタイミングではサーバは古いのでサーバ側の購読内容は変化しなかった。
|
||||
|
||||
// サーバ上の購読アラートのリスト
|
||||
// 既存の購読のアラートのリスト
|
||||
var alertsOld = oldSubscription.alerts.entries
|
||||
.mapNotNull { if (it.value) it.key else null }
|
||||
.sorted()
|
||||
|
@ -221,26 +230,13 @@ class PushMastodon(
|
|||
.sorted()
|
||||
|
||||
// 両方に共通するアラートは除去する
|
||||
// サーバが知らないアラートは除去する
|
||||
val bothHave = alertsOld.filter { alertsNew.contains(it) }
|
||||
alertsOld = alertsOld.filter { !bothHave.contains(it) }
|
||||
alertsNew = alertsNew.filter { !bothHave.contains(it) }
|
||||
|
||||
// サーバのバージョンを調べる前に、この時点でalertsが一致するか確認する
|
||||
if (alertsOld.joinToString(",") == alertsNew.joinToString(",")) {
|
||||
return true
|
||||
}
|
||||
|
||||
// サーバのバージョンを見て、サーバの知らないalertを無視して比較する
|
||||
val client = TootApiClient(context, callback = object : TootApiCallback {
|
||||
override suspend fun isApiCancelled(): Boolean = !coroutineContext.isActive
|
||||
})
|
||||
client.account = account
|
||||
val ti = TootInstance.getExOrThrow(client)
|
||||
|
||||
alertsOld = alertsOld.knownOnly(account, ti)
|
||||
alertsNew = alertsNew.knownOnly(account, ti)
|
||||
alertsOld =
|
||||
alertsOld.filter { !bothHave.contains(it) }.knownOnly(account, ti)
|
||||
alertsNew =
|
||||
alertsNew.filter { !bothHave.contains(it) }.knownOnly(account, ti)
|
||||
return if (alertsOld.joinToString(",") == alertsNew.joinToString(",")) {
|
||||
log.i("${account.acct}: same alerts(2)")
|
||||
true
|
||||
} else {
|
||||
log.i("${account.acct}: changed. old=${alertsOld.sorted()}, new=${alertsNew.sorted()}")
|
||||
|
@ -249,32 +245,41 @@ class PushMastodon(
|
|||
}
|
||||
}
|
||||
|
||||
private fun SavedAccount.alerts() = JsonObject().apply {
|
||||
private fun SavedAccount.alerts(ti: TootInstance) = JsonObject().also { dst ->
|
||||
// Mastodon's Notification::TYPES in
|
||||
// in https://github.com/mastodon/mastodon/blob/main/app/models/notification.rb#L30
|
||||
put(TootNotification.TYPE_ADMIN_REPORT, notificationFollow)
|
||||
put(TootNotification.TYPE_ADMIN_SIGNUP, notificationFollow) // 設定項目不足
|
||||
put(TootNotification.TYPE_FAVOURITE, notificationFavourite)
|
||||
put(TootNotification.TYPE_FOLLOW, notificationFollow)
|
||||
put(TootNotification.TYPE_FOLLOW_REQUEST, notificationFollowRequest)
|
||||
put(TootNotification.TYPE_MENTION, notificationMention)
|
||||
put(TootNotification.TYPE_POLL, notificationVote)
|
||||
put(TootNotification.TYPE_REBLOG, notificationBoost)
|
||||
put(TootNotification.TYPE_STATUS, notificationPost)
|
||||
put(TootNotification.TYPE_UPDATE, notificationUpdate)
|
||||
dst[TootNotification.TYPE_ADMIN_REPORT] = notificationFollow
|
||||
dst[TootNotification.TYPE_ADMIN_SIGNUP] = notificationFollow // 設定項目不足
|
||||
dst[TootNotification.TYPE_FAVOURITE] = notificationFavourite
|
||||
dst[TootNotification.TYPE_FOLLOW] = notificationFollow
|
||||
dst[TootNotification.TYPE_FOLLOW_REQUEST] = notificationFollowRequest
|
||||
dst[TootNotification.TYPE_MENTION] = notificationMention
|
||||
dst[TootNotification.TYPE_POLL] = notificationVote
|
||||
dst[TootNotification.TYPE_REBLOG] = notificationBoost
|
||||
dst[TootNotification.TYPE_STATUS] = notificationPost
|
||||
dst[TootNotification.TYPE_UPDATE] = notificationUpdate
|
||||
// fedibird拡張
|
||||
// https://github.com/fedibird/mastodon/blob/fedibird/app/controllers/api/v1/push/subscriptions_controller.rb#L55
|
||||
// https://github.com/fedibird/mastodon/blob/fedibird/app/models/notification.rb
|
||||
put(TootNotification.TYPE_EMOJI_REACTION, notificationReaction)
|
||||
put(TootNotification.TYPE_SCHEDULED_STATUS, notificationPost) // 設定項目不足
|
||||
put(TootNotification.TYPE_STATUS_REFERENCE, notificationStatusReference)
|
||||
if (!ti.pleromaFeatures.isNullOrEmpty()) {
|
||||
dst[TootNotification.TYPE_EMOJI_REACTION_PLEROMA] = notificationReaction
|
||||
} else if (!ti.fedibirdCapabilities.isNullOrEmpty()) {
|
||||
dst[TootNotification.TYPE_EMOJI_REACTION] = notificationReaction
|
||||
}
|
||||
dst[TootNotification.TYPE_SCHEDULED_STATUS] = notificationPost // 設定項目不足
|
||||
dst[TootNotification.TYPE_STATUS_REFERENCE] = notificationStatusReference
|
||||
}
|
||||
|
||||
// サーバが知らないアラート種別は比較対象から除去する
|
||||
private fun Iterable<String>.knownOnly(account: SavedAccount, ti: TootInstance) = filter {
|
||||
private fun Iterable<String>.knownOnly(
|
||||
account: SavedAccount,
|
||||
ti: TootInstance,
|
||||
) = filter {
|
||||
when (it) {
|
||||
// TYPE_ADMIN_SIGNUP, TYPE_UPDATE はalertから読めない時期があった。4.0.0以降なら大丈夫だろう
|
||||
|
||||
TootNotification.TYPE_ADMIN_REPORT -> ti.versionGE(TootInstance.VERSION_4_0_0)
|
||||
TootNotification.TYPE_ADMIN_SIGNUP -> ti.versionGE(TootInstance.VERSION_3_5_0_rc1)
|
||||
TootNotification.TYPE_ADMIN_SIGNUP -> ti.versionGE(TootInstance.VERSION_4_0_0)
|
||||
TootNotification.TYPE_FAVOURITE -> true
|
||||
TootNotification.TYPE_FOLLOW -> true
|
||||
TootNotification.TYPE_FOLLOW_REQUEST -> ti.versionGE(TootInstance.VERSION_3_1_0_rc1)
|
||||
|
@ -282,12 +287,15 @@ class PushMastodon(
|
|||
TootNotification.TYPE_POLL -> ti.versionGE(TootInstance.VERSION_2_8_0_rc1)
|
||||
TootNotification.TYPE_REBLOG -> true
|
||||
TootNotification.TYPE_STATUS -> ti.versionGE(TootInstance.VERSION_3_3_0_rc1)
|
||||
TootNotification.TYPE_UPDATE -> ti.versionGE(TootInstance.VERSION_3_5_0_rc1)
|
||||
TootNotification.TYPE_UPDATE -> ti.versionGE(TootInstance.VERSION_4_0_0)
|
||||
|
||||
//////////////////////
|
||||
// Fedibird拡張
|
||||
|
||||
TootNotification.TYPE_EMOJI_REACTION,
|
||||
-> InstanceCapability.emojiReaction(account, ti)
|
||||
|
||||
// pleromaの絵文字リアクションはalertに指定できない
|
||||
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
|
||||
-> InstanceCapability.emojiReaction(account, ti)
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class PushMisskey(
|
|||
account: SavedAccount,
|
||||
willRemoveSubscription: Boolean,
|
||||
forceUpdate: Boolean,
|
||||
) {
|
||||
): String? {
|
||||
val newUrl = snsCallbackUrl(account)
|
||||
|
||||
val lastEndpointUrl = daoStatus.lastEndpointUrl(account.acct)
|
||||
|
@ -83,29 +83,28 @@ class PushMisskey(
|
|||
subLog.i(R.string.push_subscription_off_unread_notification)
|
||||
}
|
||||
}
|
||||
return
|
||||
return null
|
||||
} else {
|
||||
// 古い購読はあったが、削除したい
|
||||
api.deletePushSubscription(account, lastEndpointUrl)
|
||||
daoStatus.deleteLastEndpointUrl(account.acct)
|
||||
if (willRemoveSubscription) {
|
||||
subLog.i(R.string.push_subscription_delete_current)
|
||||
return
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newUrl == null) {
|
||||
if (willRemoveSubscription) {
|
||||
subLog.i(R.string.push_subscription_app_server_hash_missing_but_ok)
|
||||
} else {
|
||||
subLog.e(R.string.push_subscription_app_server_hash_missing_error)
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
context.getString(R.string.push_subscription_app_server_hash_missing_error)
|
||||
return when {
|
||||
willRemoveSubscription -> {
|
||||
subLog.i(R.string.push_subscription_app_server_hash_missing_but_ok)
|
||||
null
|
||||
}
|
||||
else -> context.getString(
|
||||
R.string.push_subscription_app_server_hash_missing_error
|
||||
)
|
||||
}
|
||||
return
|
||||
} else if (willRemoveSubscription) {
|
||||
// 購読を解除したい。
|
||||
// hasEmptySubscription が真なら購読はないが、
|
||||
|
@ -117,7 +116,7 @@ class PushMisskey(
|
|||
subLog.i(R.string.push_subscription_is_not_required_delete_secret_keys)
|
||||
}
|
||||
}
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
// 鍵がなければ作る
|
||||
|
@ -163,6 +162,7 @@ class PushMisskey(
|
|||
subLog.i(R.string.push_subscription_server_key_saved)
|
||||
}
|
||||
subLog.i(R.string.push_subscription_completed)
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isOldSubscription(account: SavedAccount, url: String): Boolean {
|
||||
|
@ -173,7 +173,7 @@ class PushMisskey(
|
|||
// /{flags }
|
||||
// /{ client identifier}
|
||||
|
||||
val clientIdentifierOld = url.toHttpUrlOrNull()?.pathSegments?.elementAtOrNull(4)
|
||||
val clientIdentifierOld = url.toHttpUrlOrNull()?.pathSegments?.elementAtOrNull(4)
|
||||
?: return false
|
||||
val installId = prefDevice.installIdV1?.notEmpty() ?: return false
|
||||
val accessToken = account.misskeyApiToken?.notEmpty() ?: return false
|
||||
|
|
|
@ -36,12 +36,15 @@ import jp.juggler.util.os.applicationContextSafe
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
import java.lang.ref.WeakReference
|
||||
import java.security.Provider
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.min
|
||||
|
||||
private val log = LogCategory("PushRepo")
|
||||
|
||||
|
@ -90,6 +93,8 @@ class PushRepo(
|
|||
private val ncPushMessage = NotificationChannels.PushMessage
|
||||
|
||||
var refReporter: WeakReference<SuspendProgress.Reporter>? = null
|
||||
|
||||
var subscriptionMutex = Mutex()
|
||||
}
|
||||
|
||||
private val pushMisskey by lazy {
|
||||
|
@ -116,7 +121,7 @@ class PushRepo(
|
|||
* UPでプッシュサービスを選ぶと呼ばれる
|
||||
*/
|
||||
suspend fun switchDistributor(
|
||||
pushDistributor: String,
|
||||
pushDistributor: String?,
|
||||
reporter: SuspendProgress.Reporter,
|
||||
) {
|
||||
val timeSwitchStart = System.currentTimeMillis()
|
||||
|
@ -139,6 +144,10 @@ class PushRepo(
|
|||
fcmHandler.deleteFcmToken()
|
||||
|
||||
when (pushDistributor) {
|
||||
null, "" -> {
|
||||
// 特に変更しない(アクセストークン更新時に呼ばれる
|
||||
enqueueRegisterEndpoint(context)
|
||||
}
|
||||
PrefDevice.PUSH_DISTRIBUTOR_NONE -> {
|
||||
// 購読解除
|
||||
reporter.setMessage("SubscriptionUpdateService.launch")
|
||||
|
@ -161,20 +170,19 @@ class PushRepo(
|
|||
}
|
||||
val timeout = timeSwitchStart + TimeUnit.SECONDS.toMillis(20)
|
||||
while (true) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now >= timeout) {
|
||||
reporter.setMessage("timeout")
|
||||
delay(888L)
|
||||
break
|
||||
}
|
||||
if (PushWorker.timeEndRegisterEndpoint.get() >= timeSwitchStart ||
|
||||
PushWorker.timeEndUpEndpoint.get() >= timeSwitchStart
|
||||
) {
|
||||
reporter.setMessage("complete")
|
||||
delay(888L)
|
||||
// complete
|
||||
break
|
||||
}
|
||||
delay(1000L)
|
||||
val remain = min(1000L, timeout - System.currentTimeMillis())
|
||||
if (remain <= 0) {
|
||||
reporter.setMessage("timeout")
|
||||
delay(666L)
|
||||
break
|
||||
}
|
||||
delay(remain)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,155 +223,158 @@ class PushRepo(
|
|||
suspend fun registerEndpoint(
|
||||
keepAliveMode: Boolean,
|
||||
) {
|
||||
log.i("registerEndpoint: keepAliveMode=$keepAliveMode")
|
||||
subscriptionMutex.withLock {
|
||||
log.i("registerEndpoint: keepAliveMode=$keepAliveMode")
|
||||
|
||||
// 古いFCMトークンの情報はアプリサーバ側で勝手に消えるはず
|
||||
try {
|
||||
// 期限切れのUPエンドポイントがあればそれ経由の中継を解除する
|
||||
prefDevice.fcmTokenExpired.notEmpty()?.let {
|
||||
refReporter?.get()?.setMessage("期限切れのFCMデバイストークンをアプリサーバから削除しています")
|
||||
log.i("remove fcmTokenExpired")
|
||||
apiAppServer.endpointRemove(fcmToken = it)
|
||||
prefDevice.fcmTokenExpired = null
|
||||
// 古いFCMトークンの情報はアプリサーバ側で勝手に消えるはず
|
||||
try {
|
||||
// 期限切れのUPエンドポイントがあればそれ経由の中継を解除する
|
||||
prefDevice.fcmTokenExpired.notEmpty()?.let {
|
||||
refReporter?.get()?.setMessage("期限切れのFCMデバイストークンをアプリサーバから削除しています")
|
||||
log.i("remove fcmTokenExpired")
|
||||
apiAppServer.endpointRemove(fcmToken = it)
|
||||
prefDevice.fcmTokenExpired = null
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.w(ex, "can't forgot fcmTokenExpired")
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.w(ex, "can't forgot fcmTokenExpired")
|
||||
}
|
||||
|
||||
try {
|
||||
// 期限切れのUPエンドポイントがあればそれ経由の中継を解除する
|
||||
prefDevice.upEndpointExpired.notEmpty()?.let {
|
||||
refReporter?.get()?.setMessage("期限切れのUnifiedPushエンドポイントをアプリサーバから削除しています")
|
||||
log.i("remove upEndpointExpired")
|
||||
apiAppServer.endpointRemove(upUrl = it)
|
||||
prefDevice.upEndpointExpired = null
|
||||
try {
|
||||
// 期限切れのUPエンドポイントがあればそれ経由の中継を解除する
|
||||
prefDevice.upEndpointExpired.notEmpty()?.let {
|
||||
refReporter?.get()?.setMessage("期限切れのUnifiedPushエンドポイントをアプリサーバから削除しています")
|
||||
log.i("remove upEndpointExpired")
|
||||
apiAppServer.endpointRemove(upUrl = it)
|
||||
prefDevice.upEndpointExpired = null
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.w(ex, "can't forgot upEndpointExpired")
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.w(ex, "can't forgot upEndpointExpired")
|
||||
}
|
||||
|
||||
val realAccounts = daoSavedAccount.loadRealAccounts()
|
||||
.filter { !it.isPseudo && it.isConfirmed }
|
||||
val realAccounts = daoSavedAccount.loadRealAccounts()
|
||||
.filter { !it.isPseudo && it.isConfirmed }
|
||||
|
||||
val accts = realAccounts.map { it.acct }
|
||||
val accts = realAccounts.map { it.acct }
|
||||
|
||||
// map of acctHash to account
|
||||
val acctHashMap = daoStatus.updateAcctHash(accts)
|
||||
if (acctHashMap.isEmpty()) {
|
||||
log.w("acctHashMap is empty. no need to update register endpoint")
|
||||
return
|
||||
}
|
||||
|
||||
if (keepAliveMode) {
|
||||
val lastUpdated = prefDevice.timeLastEndpointRegister
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastUpdated < TimeUnit.DAYS.toMillis(3)) {
|
||||
log.i("lazeMode: skip re-registration.")
|
||||
// map of acctHash to account
|
||||
val acctHashMap = daoStatus.updateAcctHash(accts)
|
||||
if (acctHashMap.isEmpty()) {
|
||||
log.w("acctHashMap is empty. no need to update register endpoint")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var willRemoveSubscription = false
|
||||
if (keepAliveMode) {
|
||||
val lastUpdated = prefDevice.timeLastEndpointRegister
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastUpdated < TimeUnit.DAYS.toMillis(3)) {
|
||||
log.i("lazeMode: skip re-registration.")
|
||||
}
|
||||
}
|
||||
|
||||
// アプリサーバにendpointを登録する
|
||||
refReporter?.get()?.setMessage("アプリサーバにプッシュサービスの情報を送信しています")
|
||||
var willRemoveSubscription = false
|
||||
|
||||
if (!fcmHandler.hasFcm && prefDevice.pushDistributor == PrefDevice.PUSH_DISTRIBUTOR_FCM) {
|
||||
log.w("fmc selected, but this is noFcm build. unset distributer.")
|
||||
prefDevice.pushDistributor = null
|
||||
}
|
||||
// アプリサーバにendpointを登録する
|
||||
refReporter?.get()?.setMessage("アプリサーバにプッシュサービスの情報を送信しています")
|
||||
|
||||
val acctHashList = acctHashMap.keys.toList()
|
||||
if (!fcmHandler.hasFcm && prefDevice.pushDistributor == PrefDevice.PUSH_DISTRIBUTOR_FCM) {
|
||||
log.w("fmc selected, but this is noFcm build. unset distributer.")
|
||||
prefDevice.pushDistributor = null
|
||||
}
|
||||
|
||||
val json = when (prefDevice.pushDistributor) {
|
||||
null, "" -> when {
|
||||
fcmHandler.hasFcm -> {
|
||||
log.i("registerEndpoint dist=FCM(default), acctHashList=${acctHashList.size}")
|
||||
val acctHashList = acctHashMap.keys.toList()
|
||||
|
||||
val json = when (prefDevice.pushDistributor) {
|
||||
null, "" -> when {
|
||||
fcmHandler.hasFcm -> {
|
||||
log.i("registerEndpoint dist=FCM(default), acctHashList=${acctHashList.size}")
|
||||
registerEndpointFcm(acctHashList)
|
||||
}
|
||||
else -> {
|
||||
log.w("pushDistributor not selected. but can't select default distributor from background service.")
|
||||
return
|
||||
}
|
||||
}
|
||||
PrefDevice.PUSH_DISTRIBUTOR_NONE -> {
|
||||
log.i("push distrobuter 'none' is selected. it will remove subscription.")
|
||||
willRemoveSubscription = true
|
||||
null
|
||||
}
|
||||
PrefDevice.PUSH_DISTRIBUTOR_FCM -> {
|
||||
log.i("registerEndpoint dist=FCM, acctHashList=${acctHashList.size}")
|
||||
registerEndpointFcm(acctHashList)
|
||||
}
|
||||
else -> {
|
||||
log.w("pushDistributor not selected. but can't select default distributor from background service.")
|
||||
return
|
||||
log.i("registerEndpoint dist=${prefDevice.pushDistributor}, acctHashList=${acctHashList.size}")
|
||||
registerEndpointUnifiedPush(acctHashList)
|
||||
}
|
||||
}
|
||||
PrefDevice.PUSH_DISTRIBUTOR_NONE -> {
|
||||
log.i("push distrobuter 'none' is selected. it will remove subscription.")
|
||||
willRemoveSubscription = true
|
||||
null
|
||||
}
|
||||
PrefDevice.PUSH_DISTRIBUTOR_FCM -> {
|
||||
log.i("registerEndpoint dist=FCM, acctHashList=${acctHashList.size}")
|
||||
registerEndpointFcm(acctHashList)
|
||||
}
|
||||
else -> {
|
||||
log.i("registerEndpoint dist=${prefDevice.pushDistributor}, acctHashList=${acctHashList.size}")
|
||||
registerEndpointUnifiedPush(acctHashList)
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
json.isNullOrEmpty() ->
|
||||
log.i("no information of appServerHash.")
|
||||
when {
|
||||
json.isNullOrEmpty() ->
|
||||
log.i("no information of appServerHash.")
|
||||
|
||||
else -> {
|
||||
// acctHash => appServerHash のマップが返ってくる
|
||||
// ステータスに覚える
|
||||
var saveCount = 0
|
||||
for (acctHash in json.keys) {
|
||||
val acct = acctHashMap[acctHash] ?: continue
|
||||
val appServerHash = json.string(acctHash) ?: continue
|
||||
++saveCount
|
||||
val status = daoStatus.loadOrCreate(acct)
|
||||
if (status.appServerHash == appServerHash) continue
|
||||
daoStatus.saveAppServerHash(status.id, appServerHash)
|
||||
else -> {
|
||||
// acctHash => appServerHash のマップが返ってくる
|
||||
// ステータスに覚える
|
||||
var saveCount = 0
|
||||
for (acctHash in json.keys) {
|
||||
val acct = acctHashMap[acctHash] ?: continue
|
||||
val appServerHash = json.string(acctHash) ?: continue
|
||||
++saveCount
|
||||
val status = daoStatus.loadOrCreate(acct)
|
||||
if (status.appServerHash == appServerHash) continue
|
||||
daoStatus.saveAppServerHash(status.id, appServerHash)
|
||||
}
|
||||
log.i("appServerHash updated. saveCount=$saveCount")
|
||||
}
|
||||
log.i("appServerHash updated. saveCount=$saveCount")
|
||||
}
|
||||
}
|
||||
|
||||
realAccounts.forEach { account ->
|
||||
val subLog = object : PushBase.SubscriptionLogger {
|
||||
override val context = this@PushRepo.context
|
||||
override fun i(msg: String) {
|
||||
log.i("[${account.acct}]$msg")
|
||||
realAccounts.forEach { account ->
|
||||
val subLog = object : PushBase.SubscriptionLogger {
|
||||
override val context = this@PushRepo.context
|
||||
override fun i(msg: String) {
|
||||
log.i("[${account.acct}]$msg")
|
||||
refReporter?.get()?.setMessage("[${account.acct}]$msg")
|
||||
}
|
||||
|
||||
override fun e(msg: String) {
|
||||
log.e("[${account.acct}]$msg")
|
||||
refReporter?.get()?.setMessage("[${account.acct}]$msg")
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
override fun e(ex: Throwable, msg: String) {
|
||||
log.e(ex, "[${account.acct}]$msg")
|
||||
refReporter?.get()?.setMessage("[${account.acct}]$msg")
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
ex.withCaption(msg)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun e(msg: String) {
|
||||
log.e("[${account.acct}]$msg")
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
override fun e(ex: Throwable, msg: String) {
|
||||
log.e(ex, "[${account.acct}]$msg")
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
ex.withCaption(msg)
|
||||
try {
|
||||
val errMsg = pushBase(account).updateSubscription(
|
||||
subLog = subLog,
|
||||
account = account,
|
||||
willRemoveSubscription = willRemoveSubscription || !account.isRequiredPushSubscription(),
|
||||
forceUpdate = false,
|
||||
)
|
||||
daoAccountNotificationStatus.updateSubscriptionError(account.acct, errMsg)
|
||||
if (errMsg != null) {
|
||||
subLog.e(errMsg)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "updateSubscription failed.")
|
||||
val errMsg = ex.withCaption()
|
||||
subLog.e(errMsg)
|
||||
daoAccountNotificationStatus.updateSubscriptionError(account.acct, errMsg)
|
||||
}
|
||||
}
|
||||
try {
|
||||
refReporter?.get()?.setMessage("${account.acct.pretty} のWebPush購読を更新しています")
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
null
|
||||
)
|
||||
pushBase(account).updateSubscription(
|
||||
subLog = subLog,
|
||||
account = account,
|
||||
willRemoveSubscription = willRemoveSubscription || !account.isRequiredPushSubscription(),
|
||||
forceUpdate = false,
|
||||
)
|
||||
} catch (ex: Throwable) {
|
||||
subLog.e(ex, "updateSubscription failed.")
|
||||
daoAccountNotificationStatus.updateSubscriptionError(
|
||||
account.acct,
|
||||
ex.withCaption()
|
||||
)
|
||||
}
|
||||
prefDevice.timeLastEndpointRegister = System.currentTimeMillis()
|
||||
}
|
||||
prefDevice.timeLastEndpointRegister = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
private suspend fun registerEndpointUnifiedPush(acctHashList: List<String>) =
|
||||
|
@ -408,12 +419,25 @@ class PushRepo(
|
|||
willRemoveSubscription: Boolean,
|
||||
forceUpdate: Boolean = false,
|
||||
) {
|
||||
pushBase(account).updateSubscription(
|
||||
subLog = subLog,
|
||||
account = account,
|
||||
willRemoveSubscription = willRemoveSubscription || !account.isRequiredPushSubscription(),
|
||||
forceUpdate = forceUpdate,
|
||||
)
|
||||
subscriptionMutex.withLock {
|
||||
try {
|
||||
val errMsg = pushBase(account).updateSubscription(
|
||||
subLog = subLog,
|
||||
account = account,
|
||||
willRemoveSubscription = willRemoveSubscription || !account.isRequiredPushSubscription(),
|
||||
forceUpdate = forceUpdate,
|
||||
)
|
||||
daoAccountNotificationStatus.updateSubscriptionError(account.acct, errMsg)
|
||||
if (errMsg != null) {
|
||||
subLog.e(errMsg)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "updateSubscription failed.")
|
||||
val errMsg = ex.withCaption()
|
||||
subLog.e(errMsg)
|
||||
daoAccountNotificationStatus.updateSubscriptionError(account.acct, errMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun pushBase(account: SavedAccount) = when {
|
||||
|
|
|
@ -38,6 +38,7 @@ class SavedAccount(
|
|||
apiHostArg: String? = null,
|
||||
apDomainArg: String? = null,
|
||||
|
||||
var accountJson:JsonObject? = null,
|
||||
var confirmBoost: Boolean = false,
|
||||
var confirmFavourite: Boolean = false,
|
||||
var confirmFollow: Boolean = false,
|
||||
|
@ -119,7 +120,7 @@ class SavedAccount(
|
|||
var notificationAccentColor by jsonDelegates.int
|
||||
|
||||
init {
|
||||
log.i("ctor acctArg $acctArg")
|
||||
// log.i("ctor acctArg $acctArg")
|
||||
|
||||
// acctArg はMastodonの生のやつで、ドメイン部分がない場合がある
|
||||
// Acct.parse はHost部分がnullのacctになるかもしれない
|
||||
|
@ -142,7 +143,8 @@ class SavedAccount(
|
|||
acctArg = cursor.getString(COL_USER), // acct
|
||||
apiHostArg = cursor.getStringOrNull(COL_HOST), // host
|
||||
apDomainArg = cursor.getStringOrNull(COL_DOMAIN), // host
|
||||
misskeyVersion = cursor.getInt(COL_MISSKEY_VERSION),
|
||||
|
||||
accountJson = cursor.getStringOrNull(COL_ACCOUNT)?.decodeJsonObject(),
|
||||
confirmBoost = cursor.getBoolean(COL_CONFIRM_BOOST),
|
||||
confirmFavourite = cursor.getBoolean(COL_CONFIRM_FAVOURITE),
|
||||
confirmFollow = cursor.getBoolean(COL_CONFIRM_FOLLOW),
|
||||
|
@ -177,6 +179,9 @@ class SavedAccount(
|
|||
tokenJson = cursor.getString(COL_TOKEN).decodeJsonObject(),
|
||||
visibility = TootVisibility.parseSavedVisibility(cursor.getStringOrNull(COL_VISIBILITY))
|
||||
?: TootVisibility.Public,
|
||||
|
||||
misskeyVersion = cursor.getInt(COL_MISSKEY_VERSION),
|
||||
|
||||
// lastNotificationError = cursor.getStringOrNull(COL_LAST_NOTIFICATION_ERROR)
|
||||
// last_push_endpoint = cursor.getStringOrNull(COL_LAST_PUSH_ENDPOINT)
|
||||
// last_subscription_error = cursor.getStringOrNull(COL_LAST_SUBSCRIPTION_ERROR)
|
||||
|
@ -185,9 +190,7 @@ class SavedAccount(
|
|||
// register_time = cursor.getLong(COL_REGISTER_TIME)
|
||||
|
||||
) {
|
||||
val strAccount = cursor.getString(COL_ACCOUNT)
|
||||
val jsonAccount = strAccount.decodeJsonObject()
|
||||
loginAccount = if (jsonAccount["id"] == null) {
|
||||
loginAccount = if (accountJson?.get("id") == null) {
|
||||
null // 疑似アカウント
|
||||
} else {
|
||||
TootParser(
|
||||
|
@ -197,8 +200,8 @@ class SavedAccount(
|
|||
apDomainArg = this@SavedAccount.apDomain,
|
||||
misskeyVersion = misskeyVersion
|
||||
)
|
||||
).account(jsonAccount)
|
||||
?: error("missing loginAccount for $strAccount")
|
||||
).account(accountJson)
|
||||
?: error("missing loginAccount for $accountJson")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,6 +471,7 @@ class SavedAccount(
|
|||
if (isInvalidId) error("saveSetting: missing db_id")
|
||||
|
||||
ContentValues().apply {
|
||||
put(COL_ACCOUNT,accountJson?.toString())
|
||||
put(COL_CONFIRM_BOOST, confirmBoost)
|
||||
put(COL_CONFIRM_FAVOURITE, confirmFavourite)
|
||||
put(COL_CONFIRM_FOLLOW, confirmFollow)
|
||||
|
@ -499,7 +503,9 @@ class SavedAccount(
|
|||
put(COL_NOTIFICATION_VOTE, notificationVote)
|
||||
put(COL_PUSH_POLICY, pushPolicy)
|
||||
// put(COL_SOUND_URI, soundUri)
|
||||
put(COL_TOKEN,tokenJson?.toString())
|
||||
put(COL_VISIBILITY, visibility.id.toString())
|
||||
put(COL_MISSKEY_VERSION,misskeyVersion)
|
||||
}.let { db.update(table, it, "$COL_ID=?", arrayOf(db_id.toString())) }
|
||||
}
|
||||
}
|
||||
|
@ -512,7 +518,7 @@ class SavedAccount(
|
|||
|
||||
// DBから削除されてるかもしれない
|
||||
val b = newData ?: loadAccount(db_id) ?: return
|
||||
|
||||
this.accountJson = b.accountJson
|
||||
this.confirmBoost = b.confirmBoost
|
||||
this.confirmFavourite = b.confirmFavourite
|
||||
this.confirmPost = b.confirmPost
|
||||
|
@ -525,31 +531,32 @@ class SavedAccount(
|
|||
this.dontHideNsfw = b.dontHideNsfw
|
||||
this.dontShowTimeout = b.dontShowTimeout
|
||||
this.expandCw = b.expandCw
|
||||
this.extraJson = b.extraJson
|
||||
this.imageMaxMegabytes = b.imageMaxMegabytes
|
||||
this.imageResize = b.imageResize
|
||||
this.lang = b.lang
|
||||
this.movieMaxMegabytes = b.movieMaxMegabytes
|
||||
this.movieTranscodeBitrate = b.movieTranscodeBitrate
|
||||
this.movieTranscodeFramerate = b.movieTranscodeFramerate
|
||||
this.movieTranscodeMode = b.movieTranscodeMode
|
||||
this.movieTranscodeSquarePixels = b.movieTranscodeSquarePixels
|
||||
this.movieMaxMegabytes = b.movieMaxMegabytes
|
||||
this.notificationBoost = b.notificationBoost
|
||||
this.notificationFavourite = b.notificationFavourite
|
||||
this.notificationFollow = b.notificationFollow
|
||||
this.notificationFollowRequest = b.notificationFollowRequest
|
||||
this.notificationMention = b.notificationMention
|
||||
this.notificationPost = b.notificationPost
|
||||
this.notificationPullEnable = b.notificationPullEnable
|
||||
this.notificationPushEnable = b.notificationPushEnable
|
||||
this.notificationReaction = b.notificationReaction
|
||||
this.notificationStatusReference = b.notificationStatusReference
|
||||
this.notificationUpdate = b.notificationUpdate
|
||||
this.notificationVote = b.notificationVote
|
||||
this.pushPolicy = b.pushPolicy
|
||||
// this.soundUri = b.soundUri
|
||||
this.tokenJson = b.tokenJson
|
||||
this.visibility = b.visibility
|
||||
this.notificationPushEnable = b.notificationPushEnable
|
||||
this.notificationPullEnable = b.notificationPullEnable
|
||||
|
||||
// this.soundUri = b.soundUri
|
||||
this.misskeyVersion = b.misskeyVersion
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,87 +5,76 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="128dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="128dp"
|
||||
|
||||
>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/instance"
|
||||
/>
|
||||
android:text="@string/instance" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnInstance"
|
||||
style="@style/setting_row_button"
|
||||
/>
|
||||
style="@style/setting_row_button" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/version"
|
||||
/>
|
||||
android:text="@string/version" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvVersion"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
style="@style/setting_horizontal_stretch" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/title"
|
||||
/>
|
||||
android:text="@string/title" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
style="@style/setting_horizontal_stretch" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/email"
|
||||
/>
|
||||
android:text="@string/email" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEmail"
|
||||
style="@style/setting_row_button"
|
||||
/>
|
||||
style="@style/setting_row_button" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/contact"
|
||||
/>
|
||||
android:text="@string/contact" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnContact"
|
||||
style="@style/setting_row_button"
|
||||
/>
|
||||
style="@style/setting_row_button" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/languages"
|
||||
/>
|
||||
android:text="@string/languages" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -94,16 +83,14 @@
|
|||
style="@style/setting_horizontal_stretch"
|
||||
android:background="@drawable/btn_bg_transparent_round6dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/invites_enabled"
|
||||
/>
|
||||
android:text="@string/invites_enabled" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -112,60 +99,52 @@
|
|||
style="@style/setting_horizontal_stretch"
|
||||
android:background="@drawable/btn_bg_transparent_round6dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
android:textAllCaps="false" />
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/user_count"
|
||||
/>
|
||||
android:text="@string/user_count" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvUserCount"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
style="@style/setting_horizontal_stretch" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/toot_count"
|
||||
/>
|
||||
android:text="@string/toot_count" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTootCount"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
style="@style/setting_horizontal_stretch" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/domain_count"
|
||||
/>
|
||||
android:text="@string/domain_count" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDomainCount"
|
||||
style="@style/setting_row_form"
|
||||
/>
|
||||
style="@style/setting_row_form" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/thumbnail"
|
||||
/>
|
||||
android:text="@string/thumbnail" />
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
|
@ -173,94 +152,90 @@
|
|||
android:id="@+id/ivThumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/short_description"
|
||||
/>
|
||||
android:text="@string/short_description" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyTextView
|
||||
android:id="@+id/tvShortDescription"
|
||||
style="@style/setting_row_form"
|
||||
/>
|
||||
style="@style/setting_row_form" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/description"
|
||||
/>
|
||||
android:text="@string/description" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyTextView
|
||||
android:id="@+id/tvDescription"
|
||||
style="@style/setting_row_form"
|
||||
/>
|
||||
style="@style/setting_row_form" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/links"
|
||||
/>
|
||||
android:text="@string/links" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAbout"
|
||||
style="@style/setting_row_button"
|
||||
android:text="@string/top_page"
|
||||
/>
|
||||
android:text="@string/top_page" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAboutMore"
|
||||
style="@style/setting_row_button"
|
||||
android:text="@string/about_this_instance"
|
||||
/>
|
||||
android:text="@string/about_this_instance" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnExplore"
|
||||
style="@style/setting_row_button"
|
||||
android:text="@string/profile_directory"
|
||||
/>
|
||||
<View style="@style/setting_divider"/>
|
||||
android:text="@string/profile_directory" />
|
||||
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/server_configuration"
|
||||
/>
|
||||
android:text="@string/server_configuration" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyTextView
|
||||
android:id="@+id/tvConfiguration"
|
||||
style="@style/setting_row_form"
|
||||
/>
|
||||
<View style="@style/setting_divider"/>
|
||||
style="@style/setting_row_form" />
|
||||
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/fedibird_capacities"
|
||||
/>
|
||||
android:text="@string/fedibird_capacities" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyTextView
|
||||
android:id="@+id/tvFedibirdCapacities"
|
||||
style="@style/setting_row_form"
|
||||
/>
|
||||
style="@style/setting_row_form" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/tls_handshake"
|
||||
/>
|
||||
android:text="@string/pleroma_features" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyTextView
|
||||
android:id="@+id/tvPlelomaFeatures"
|
||||
style="@style/setting_row_form" />
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/tls_handshake" />
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyTextView
|
||||
android:id="@+id/tvHandshake"
|
||||
style="@style/setting_row_form"
|
||||
/>
|
||||
style="@style/setting_row_form" />
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
<View style="@style/setting_divider" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1239,4 +1239,5 @@
|
|||
<string name="manually_update">手動Manually update</string>
|
||||
<string name="notification_push_distributor_disabled">通知のプッシュ配送サービスが選択されていません</string>
|
||||
<string name="notification_accent_color">通知のアクセント色</string>
|
||||
<string name="pleroma_features">Pleroma機能</string>
|
||||
</resources>
|
||||
|
|
|
@ -1255,4 +1255,5 @@
|
|||
<string name="manually_update">Manually update</string>
|
||||
<string name="notification_push_distributor_disabled">Notification push distributor not selected.</string>
|
||||
<string name="notification_accent_color">Notification accent color</string>
|
||||
<string name="pleroma_features">Pleroma features</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue