投票結果通知に対応。(サーバ側の問題によりプッシュ通知は届きません)
エクスポートを介してクライアントアプリ登録を端末間で再利用するとアクセストークンを更新しても同じ値が返ってきてプッシュ購読がうまくない問題の対応。
This commit is contained in:
parent
2485e53287
commit
d9487d0e78
|
@ -1224,10 +1224,9 @@ class TestTootApiClient {
|
|||
callback = callback
|
||||
)
|
||||
client.account = accessInfo
|
||||
val result = client.webSocket("/api/v1/streaming/?stream=public:local",
|
||||
val(_,ws) = client.webSocket("/api/v1/streaming/?stream=public:local",
|
||||
object : WebSocketListener() {
|
||||
})
|
||||
val ws = result?.data as? WebSocket
|
||||
assertNotNull(ws)
|
||||
ws?.cancel()
|
||||
}
|
||||
|
|
|
@ -669,7 +669,10 @@ class ActAccountSetting
|
|||
|
||||
TootTaskRunner(this@ActAccountSetting).run(account, object : TootTask {
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
return client.authentication1(Pref.spClientName(this@ActAccountSetting))
|
||||
return client.authentication1(
|
||||
Pref.spClientName(this@ActAccountSetting),
|
||||
forceUpdateClient = false
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
|
|
|
@ -509,9 +509,9 @@ class ActMain : AppCompatActivity()
|
|||
MyClickableSpan.showLinkUnderline = Pref.bpShowLinkUnderline(pref)
|
||||
MyClickableSpan.defaultLinkColor = Pref.ipLinkColor(pref).notZero()
|
||||
?: getAttributeColor(this, R.attr.colorLink)
|
||||
|
||||
|
||||
// 背景画像を表示しない設定が変更された時にカラムの背景を設定しなおす
|
||||
for( column in app_state.column_list){
|
||||
for(column in app_state.column_list) {
|
||||
column.fireColumnColor()
|
||||
}
|
||||
|
||||
|
@ -1439,9 +1439,13 @@ class ActMain : AppCompatActivity()
|
|||
|
||||
env.tablet_pager.adapter = env.tablet_pager_adapter
|
||||
env.tablet_pager.layoutManager = env.tablet_layout_manager
|
||||
env.tablet_pager.addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
|
||||
env.tablet_pager.addOnScrollListener(object :
|
||||
androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
|
||||
|
||||
override fun onScrollStateChanged(recyclerView : androidx.recyclerview.widget.RecyclerView, newState : Int) {
|
||||
override fun onScrollStateChanged(
|
||||
recyclerView : androidx.recyclerview.widget.RecyclerView,
|
||||
newState : Int
|
||||
) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
|
||||
val vs = env.tablet_layout_manager.findFirstVisibleItemPosition()
|
||||
|
@ -1456,7 +1460,11 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
}
|
||||
|
||||
override fun onScrolled(recyclerView : androidx.recyclerview.widget.RecyclerView, dx : Int, dy : Int) {
|
||||
override fun onScrolled(
|
||||
recyclerView : androidx.recyclerview.widget.RecyclerView,
|
||||
dx : Int,
|
||||
dy : Int
|
||||
) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
updateColumnStripSelection(- 1, - 1f)
|
||||
}
|
||||
|
@ -1839,7 +1847,7 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
|
||||
// OAuth2 認証コールバック
|
||||
// subwaytooter://oauth/?...
|
||||
// subwaytooter://oauth(\d*)/?...
|
||||
TootTaskRunner(this@ActMain).run(object : TootTask {
|
||||
|
||||
var ta : TootAccount? = null
|
||||
|
@ -1892,7 +1900,7 @@ class ActMain : AppCompatActivity()
|
|||
// Mastodon 認証コールバック
|
||||
|
||||
// エラー時
|
||||
// subwaytooter://oauth
|
||||
// subwaytooter://oauth(\d*)/
|
||||
// ?error=access_denied
|
||||
// &error_description=%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AE%E6%89%80%E6%9C%89%E8%80%85%E3%81%BE%E3%81%9F%E3%81%AF%E8%AA%8D%E8%A8%BC%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%8C%E8%A6%81%E6%B1%82%E3%82%92%E6%8B%92%E5%90%A6%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82
|
||||
// &state=db%3A3
|
||||
|
@ -1901,7 +1909,7 @@ class ActMain : AppCompatActivity()
|
|||
return TootApiResult(error)
|
||||
}
|
||||
|
||||
// subwaytooter://oauth
|
||||
// subwaytooter://oauth(\d*)/
|
||||
// ?code=113cc036e078ac500d3d0d3ad345cd8181456ab087abc67270d40f40a4e9e3c2
|
||||
// &state=host%3Amastodon.juggler.jp
|
||||
|
||||
|
@ -1915,25 +1923,33 @@ class ActMain : AppCompatActivity()
|
|||
return TootApiResult("missing state in callback url.")
|
||||
}
|
||||
|
||||
if(sv.startsWith("db:")) {
|
||||
try {
|
||||
val dataId = sv.substring(3).toLong(10)
|
||||
val sa = SavedAccount.loadAccount(this@ActMain, dataId)
|
||||
?: return TootApiResult("missing account db_id=$dataId")
|
||||
this.sa = sa
|
||||
client.account = sa
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
return TootApiResult(ex.withCaption("invalid state"))
|
||||
for( param in sv.split(",")){
|
||||
when {
|
||||
|
||||
param.startsWith("db:") -> try {
|
||||
val dataId = param.substring(3).toLong(10)
|
||||
val sa = SavedAccount.loadAccount(this@ActMain, dataId)
|
||||
?: return TootApiResult("missing account db_id=$dataId")
|
||||
this.sa = sa
|
||||
client.account = sa
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
return TootApiResult(ex.withCaption("invalid state"))
|
||||
}
|
||||
|
||||
param.startsWith("host:") -> {
|
||||
val host = param.substring(5)
|
||||
client.instance = host
|
||||
}
|
||||
|
||||
else -> {
|
||||
// ignore other parameter
|
||||
}
|
||||
}
|
||||
|
||||
} else if(sv.startsWith("host:")) {
|
||||
val host = sv.substring(5)
|
||||
client.instance = host
|
||||
}
|
||||
|
||||
val instance = client.instance
|
||||
?: return TootApiResult("missing instance in callback url.")
|
||||
?: return TootApiResult("missing instance in callback url.")
|
||||
|
||||
this.host = instance
|
||||
val client_name = Pref.spClientName(this@ActMain)
|
||||
|
|
|
@ -388,9 +388,11 @@ object AppDataExporter {
|
|||
writeFromTable(writer, KEY_MUTED_APP, MutedApp.table)
|
||||
writeFromTable(writer, KEY_MUTED_WORD, MutedWord.table)
|
||||
writeFromTable(writer, KEY_FAV_MUTE, FavMute.table)
|
||||
writeFromTable(writer, KEY_CLIENT_INFO, ClientInfo.table)
|
||||
writeFromTable(writer, KEY_HIGHLIGHT_WORD, HighlightWord.table)
|
||||
|
||||
// 端末間でクライアントIDを再利用することはできなくなった
|
||||
//writeFromTable(writer, KEY_CLIENT_INFO, ClientInfo.table)
|
||||
|
||||
//////////////////////////////////////
|
||||
run {
|
||||
writer.name(KEY_COLUMN)
|
||||
|
@ -428,8 +430,12 @@ object AppDataExporter {
|
|||
KEY_MUTED_WORD -> importTable(reader, MutedWord.table, null)
|
||||
KEY_FAV_MUTE -> importTable(reader, FavMute.table, null)
|
||||
KEY_HIGHLIGHT_WORD -> importTable(reader, HighlightWord.table, null)
|
||||
KEY_CLIENT_INFO -> importTable(reader, ClientInfo.table, null)
|
||||
KEY_COLUMN -> result = readColumn(app_state, reader, account_id_map)
|
||||
|
||||
// 端末間でクライアントIDを再利用することはできなくなった
|
||||
// KEY_CLIENT_INFO -> importTable(reader, ClientInfo.table, null)
|
||||
|
||||
else-> reader.skipValue()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1101,14 +1101,14 @@ class Column(
|
|||
if(n ++ > 0) sb.append(", ")
|
||||
sb.append(context.getString(R.string.notification_type_reaction))
|
||||
}
|
||||
if(isMisskey && ! dont_show_vote) {
|
||||
if(! dont_show_vote) {
|
||||
if(n ++ > 0) sb.append(", ")
|
||||
sb.append(context.getString(R.string.notification_type_vote))
|
||||
}
|
||||
val n_max = if(isMisskey) {
|
||||
6
|
||||
} else {
|
||||
4
|
||||
5
|
||||
}
|
||||
if(n == 0 || n == n_max) return "" // 全部か皆無なら部分表記は要らない
|
||||
}
|
||||
|
@ -1638,79 +1638,44 @@ class Column(
|
|||
|
||||
private fun isFiltered(item : TootNotification) : Boolean {
|
||||
|
||||
when(quick_filter) {
|
||||
QUICK_FILTER_ALL -> {
|
||||
when(item.type) {
|
||||
TootNotification.TYPE_FAVOURITE -> if(dont_show_favourite) {
|
||||
log.d("isFiltered: favourite notification filtered.")
|
||||
return true
|
||||
}
|
||||
if(when(quick_filter) {
|
||||
QUICK_FILTER_ALL -> when(item.type) {
|
||||
TootNotification.TYPE_FAVOURITE -> dont_show_favourite
|
||||
|
||||
TootNotification.TYPE_REBLOG,
|
||||
TootNotification.TYPE_RENOTE,
|
||||
TootNotification.TYPE_QUOTE -> if(dont_show_boost) {
|
||||
log.d("isFiltered: reblog notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_QUOTE -> dont_show_boost
|
||||
|
||||
TootNotification.TYPE_FOLLOW_REQUEST,
|
||||
TootNotification.TYPE_FOLLOW -> if(dont_show_follow) {
|
||||
log.d("isFiltered: follow notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_FOLLOW -> dont_show_follow
|
||||
|
||||
TootNotification.TYPE_MENTION,
|
||||
TootNotification.TYPE_REPLY -> if(dont_show_reply) {
|
||||
log.d("isFiltered: mention notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_REACTION -> if(dont_show_reaction) {
|
||||
log.d("isFiltered: reaction notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_VOTE -> if(dont_show_vote) {
|
||||
log.d("isFiltered: vote notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_REPLY -> dont_show_reply
|
||||
|
||||
TootNotification.TYPE_REACTION -> dont_show_reaction
|
||||
|
||||
TootNotification.TYPE_VOTE,
|
||||
TootNotification.TYPE_POLL -> dont_show_vote
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
when(item.type) {
|
||||
TootNotification.TYPE_FAVOURITE -> if(quick_filter != QUICK_FILTER_FAVOURITE) {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
|
||||
else -> when(item.type) {
|
||||
TootNotification.TYPE_FAVOURITE -> quick_filter != QUICK_FILTER_FAVOURITE
|
||||
TootNotification.TYPE_REBLOG,
|
||||
TootNotification.TYPE_RENOTE,
|
||||
TootNotification.TYPE_QUOTE -> if(quick_filter != QUICK_FILTER_BOOST) {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_QUOTE -> quick_filter != QUICK_FILTER_BOOST
|
||||
TootNotification.TYPE_FOLLOW_REQUEST,
|
||||
TootNotification.TYPE_FOLLOW -> if(quick_filter != QUICK_FILTER_FOLLOW) {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_FOLLOW -> quick_filter != QUICK_FILTER_FOLLOW
|
||||
TootNotification.TYPE_MENTION,
|
||||
TootNotification.TYPE_REPLY -> if(quick_filter != QUICK_FILTER_MENTION) {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_REACTION -> if(quick_filter != QUICK_FILTER_REACTION) {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_VOTE -> if(quick_filter != QUICK_FILTER_VOTE) {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
|
||||
else -> {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
TootNotification.TYPE_REPLY -> quick_filter != QUICK_FILTER_MENTION
|
||||
TootNotification.TYPE_REACTION -> quick_filter != QUICK_FILTER_REACTION
|
||||
TootNotification.TYPE_VOTE,
|
||||
TootNotification.TYPE_POLL -> quick_filter != QUICK_FILTER_VOTE
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}) {
|
||||
log.d("isFiltered: ${item.type} notification filtered.")
|
||||
return true
|
||||
}
|
||||
|
||||
val status = item.status
|
||||
|
@ -6985,6 +6950,7 @@ class Column(
|
|||
if(dont_show_boost) sb.append("&exclude_types[]=reblog")
|
||||
if(dont_show_follow) sb.append("&exclude_types[]=follow")
|
||||
if(dont_show_reply) sb.append("&exclude_types[]=mention")
|
||||
if(dont_show_vote) sb.append("&exclude_types[]=poll")
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
|
|
@ -658,7 +658,7 @@ class ColumnViewHolder(
|
|||
vg(cbDontShowReply, column.canFilterReply())
|
||||
vg(cbDontShowNormalToot, column.canFilterNormalToot())
|
||||
vg(cbDontShowReaction, isNotificationColumn && column.isMisskey)
|
||||
vg(cbDontShowVote, isNotificationColumn && column.isMisskey)
|
||||
vg(cbDontShowVote, isNotificationColumn )
|
||||
vg(cbDontShowFavourite, isNotificationColumn && ! column.isMisskey)
|
||||
vg(cbDontShowFollow, isNotificationColumn)
|
||||
|
||||
|
@ -1525,8 +1525,6 @@ class ColumnViewHolder(
|
|||
if(! isNotificationColumn) return
|
||||
|
||||
vg(btnQuickFilterReaction, column.isMisskey)
|
||||
vg(btnQuickFilterVote, column.isMisskey)
|
||||
|
||||
vg(btnQuickFilterFavourite, ! column.isMisskey)
|
||||
|
||||
val insideColumnSetting = Pref.bpMoveNotificationsQuickFilter(activity.pref)
|
||||
|
|
|
@ -930,6 +930,19 @@ internal class ItemViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
TootNotification.TYPE_POLL -> {
|
||||
val colorBg = 0
|
||||
if(n_account != null) showBoost(
|
||||
n_accountRef,
|
||||
n.time_created_at,
|
||||
R.drawable.ic_vote,
|
||||
R.string.end_of_polling_from
|
||||
)
|
||||
if(n_status != null) {
|
||||
showNotificationStatus(n_status, colorBg)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val colorBg = 0
|
||||
if(n_account != null) showBoost(
|
||||
|
|
|
@ -1303,6 +1303,9 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
name
|
||||
)
|
||||
|
||||
TootNotification.TYPE_POLL ->
|
||||
"- " + context.getString(R.string.end_of_polling_from, name)
|
||||
|
||||
else -> "- " + "?"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import jp.juggler.subwaytooter.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.table.ClientInfo
|
||||
|
@ -54,7 +55,9 @@ class TootApiClient(
|
|||
private const val REDIRECT_URL = "subwaytooter://oauth/"
|
||||
|
||||
// 20181225 3=>4 client credentialの取得時にもscopeの取得が必要になった
|
||||
private const val AUTH_VERSION = 4
|
||||
// 20190147 4=>5 client id とユーザIDが同じだと同じアクセストークンが返ってくるので複数端末の利用で困る。
|
||||
// AUTH_VERSIONが古いclient情報は使わない。また、インポートの対象にしない。
|
||||
private const val AUTH_VERSION = 5
|
||||
|
||||
internal const val KEY_CLIENT_CREDENTIAL = "SubwayTooterClientCredential"
|
||||
internal const val KEY_CLIENT_SCOPE = "SubwayTooterClientScope"
|
||||
|
@ -212,11 +215,11 @@ class TootApiClient(
|
|||
"reaction-write",
|
||||
"vote-read",
|
||||
"vote-write"
|
||||
|
||||
|
||||
)
|
||||
// APIのエラーを回避するため、重複を排除する
|
||||
.toMutableSet()
|
||||
.forEach {put(it) }
|
||||
.forEach { put(it) }
|
||||
}
|
||||
|
||||
private fun encodeScopeArray(scope_array : JSONArray?) : String? {
|
||||
|
@ -539,8 +542,8 @@ class TootApiClient(
|
|||
}
|
||||
|
||||
// インスタンス情報を取得する
|
||||
internal fun parseInstanceInformation(result : TootApiResult?) : Pair<TootApiResult?,TootInstance?> {
|
||||
var ti: TootInstance? = null
|
||||
internal fun parseInstanceInformation(result : TootApiResult?) : Pair<TootApiResult?, TootInstance?> {
|
||||
var ti : TootInstance? = null
|
||||
val json = result?.jsonObject
|
||||
if(json != null) {
|
||||
val parser = TootParser(
|
||||
|
@ -550,7 +553,7 @@ class TootApiClient(
|
|||
ti = parser.instance(json)
|
||||
if(ti == null) result.setError("can't parse data in instance information.")
|
||||
}
|
||||
return Pair(result,ti)
|
||||
return Pair(result, ti)
|
||||
}
|
||||
|
||||
private fun getAppInfoMisskey(appId : String?) : TootApiResult? {
|
||||
|
@ -912,13 +915,19 @@ class TootApiClient(
|
|||
val account = this.account
|
||||
val client_id = client_info.parseString("client_id") ?: return null
|
||||
|
||||
val state = StringBuilder()
|
||||
.append((if(account != null) "db:${account.db_id}" else "host:$instance"))
|
||||
.append(',')
|
||||
.append("random:${System.currentTimeMillis()}")
|
||||
.toString()
|
||||
|
||||
return ("https://" + instance + "/oauth/authorize"
|
||||
+ "?client_id=" + client_id.encodePercent()
|
||||
+ "&response_type=code"
|
||||
+ "&redirect_uri=" + REDIRECT_URL.encodePercent()
|
||||
+ "&scope=$scope_string"
|
||||
+ "&scopes=$scope_string"
|
||||
+ "&state=" + (if(account != null) "db:${account.db_id}" else "host:$instance")
|
||||
+ "&state=" + state.encodePercent()
|
||||
+ "&grant_type=authorization_code"
|
||||
+ "&approval_prompt=force"
|
||||
+ "&force_login=true"
|
||||
|
@ -926,7 +935,11 @@ class TootApiClient(
|
|||
)
|
||||
}
|
||||
|
||||
private fun prepareClientMastodon(clientNameArg : String, ti : TootInstance) : TootApiResult? {
|
||||
private fun prepareClientMastodon(
|
||||
clientNameArg : String,
|
||||
ti : TootInstance,
|
||||
forceUpdateClient : Boolean = false
|
||||
) : TootApiResult? {
|
||||
// 前準備
|
||||
val result = TootApiResult.makeWithCaption(this.instance)
|
||||
if(result.error != null) return result
|
||||
|
@ -937,56 +950,53 @@ class TootApiClient(
|
|||
var client_info = ClientInfo.load(instance, client_name)
|
||||
|
||||
// スコープ一覧を取得する
|
||||
|
||||
val scope_string = getScopeString(ti)
|
||||
|
||||
if(client_info != null
|
||||
&& AUTH_VERSION == client_info.optInt(KEY_AUTH_VERSION)
|
||||
&& ! client_info.optBoolean(KEY_IS_MISSKEY)
|
||||
) {
|
||||
|
||||
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) {
|
||||
val resultSub = getClientCredential(client_info)
|
||||
client_credential = resultSub?.string
|
||||
if(client_credential?.isNotEmpty() == true) {
|
||||
try {
|
||||
client_info.put(KEY_CLIENT_CREDENTIAL, client_credential)
|
||||
ClientInfo.save(instance, client_name, client_info.toString())
|
||||
} catch(ignored : JSONException) {
|
||||
}
|
||||
}
|
||||
when {
|
||||
AUTH_VERSION != client_info?.optInt(KEY_AUTH_VERSION) -> {
|
||||
// 古いクライアント情報は使わない。削除もしない。
|
||||
}
|
||||
|
||||
// client_credential があるならcredentialがまだ使えるか確認する
|
||||
if(client_credential?.isNotEmpty() == true) {
|
||||
val resultSub = verifyClientCredential(client_credential)
|
||||
val currentCC = resultSub?.jsonObject
|
||||
if(currentCC != null) {
|
||||
|
||||
var allowReuseCC = true
|
||||
|
||||
|
||||
if(old_scope != scope_string) {
|
||||
// マストドン2.4でスコープが追加された
|
||||
// 取得時のスコープ指定がマッチしない(もしくは記録されていない)ならクライアント情報を再利用してはいけない
|
||||
ClientInfo.delete(instance, client_name)
|
||||
|
||||
// client credential をタンスから消去する
|
||||
revokeClientCredential(client_info, client_credential)
|
||||
|
||||
// XXX クライアントアプリ情報そのものはまだサーバに残っているが、明示的に消す方法は現状存在しない
|
||||
|
||||
allowReuseCC = false
|
||||
client_info.optBoolean(KEY_IS_MISSKEY) -> {
|
||||
// Misskeyにはclient情報をまだ利用できるかどうか調べる手段がないので、再利用しない
|
||||
}
|
||||
|
||||
else -> {
|
||||
val old_scope = client_info.parseString(KEY_CLIENT_SCOPE)
|
||||
|
||||
// client_credential をまだ取得していないなら取得する
|
||||
var client_credential = client_info.parseString(KEY_CLIENT_CREDENTIAL)
|
||||
if(client_credential?.isEmpty() != false) {
|
||||
val resultSub = getClientCredential(client_info)
|
||||
client_credential = resultSub?.string
|
||||
if(client_credential?.isNotEmpty() == true) {
|
||||
try {
|
||||
client_info.put(KEY_CLIENT_CREDENTIAL, client_credential)
|
||||
ClientInfo.save(instance, client_name, client_info.toString())
|
||||
} catch(ignored : JSONException) {
|
||||
}
|
||||
}
|
||||
|
||||
if(allowReuseCC) {
|
||||
// クライアント情報を再利用する
|
||||
result.data = client_info
|
||||
return result
|
||||
}
|
||||
|
||||
// client_credential があるならcredentialがまだ使えるか確認する
|
||||
if(client_credential?.isNotEmpty() == true) {
|
||||
val resultSub = verifyClientCredential(client_credential)
|
||||
val currentCC = resultSub?.jsonObject
|
||||
if(currentCC != null) {
|
||||
if(old_scope != scope_string || forceUpdateClient) {
|
||||
// マストドン2.4でスコープが追加された
|
||||
// 取得時のスコープ指定がマッチしない(もしくは記録されていない)ならクライアント情報を再利用してはいけない
|
||||
ClientInfo.delete(instance, client_name)
|
||||
|
||||
// client credential をタンスから消去する
|
||||
revokeClientCredential(client_info, client_credential)
|
||||
|
||||
// XXX クライアントアプリ情報そのものはまだサーバに残っているが、明示的に消す方法は現状存在しない
|
||||
} else {
|
||||
// クライアント情報を再利用する
|
||||
result.data = client_info
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1021,9 +1031,10 @@ class TootApiClient(
|
|||
|
||||
private fun authentication1Mastodon(
|
||||
clientNameArg : String,
|
||||
ti : TootInstance
|
||||
ti : TootInstance,
|
||||
forceUpdateClient : Boolean = false
|
||||
) : TootApiResult? =
|
||||
prepareClientMastodon(clientNameArg, ti)?.also { result ->
|
||||
prepareClientMastodon(clientNameArg, ti, forceUpdateClient)?.also { result ->
|
||||
val client_info = result.jsonObject
|
||||
if(client_info != null) {
|
||||
result.data = prepareBrowserUrl(getScopeString(ti), client_info)
|
||||
|
@ -1044,13 +1055,16 @@ class TootApiClient(
|
|||
}
|
||||
|
||||
// クライアントを登録してブラウザで開くURLを生成する
|
||||
fun authentication1(clientNameArg : String) : TootApiResult? {
|
||||
fun authentication1(
|
||||
clientNameArg : String,
|
||||
forceUpdateClient : Boolean = false
|
||||
) : TootApiResult? {
|
||||
|
||||
var lastRi : TootApiResult?
|
||||
|
||||
|
||||
// misskeyのインスタンス情報
|
||||
run{
|
||||
val (ri ,ti) = parseInstanceInformation(getInstanceInformationMisskey())
|
||||
run {
|
||||
val (ri, ti) = parseInstanceInformation(getInstanceInformationMisskey())
|
||||
lastRi = ri
|
||||
if(ti != null && (ri?.response?.code() ?: 0) in 200 until 300) {
|
||||
return authentication1Misskey(clientNameArg, ti)
|
||||
|
@ -1058,11 +1072,11 @@ class TootApiClient(
|
|||
}
|
||||
|
||||
// マストドンのインスタンス情報
|
||||
run{
|
||||
val (ri,ti) = parseInstanceInformation(getInstanceInformationMastodon())
|
||||
run {
|
||||
val (ri, ti) = parseInstanceInformation(getInstanceInformationMastodon())
|
||||
lastRi = ri
|
||||
if(ti != null && (ri?.response?.code() ?: 0) in 200 until 300) {
|
||||
return authentication1Mastodon(clientNameArg, ti)
|
||||
return authentication1Mastodon(clientNameArg, ti, forceUpdateClient)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1170,9 +1184,9 @@ class TootApiClient(
|
|||
}
|
||||
|
||||
fun createUser1(clientNameArg : String) : TootApiResult? {
|
||||
|
||||
|
||||
var lastRi : TootApiResult?
|
||||
|
||||
|
||||
// misskeyのインスタンス情報
|
||||
run {
|
||||
val (ri, ti) = parseInstanceInformation(getInstanceInformationMisskey())
|
||||
|
@ -1195,7 +1209,7 @@ class TootApiClient(
|
|||
return prepareClientMastodon(clientNameArg, ti)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return lastRi
|
||||
}
|
||||
|
||||
|
@ -1360,24 +1374,27 @@ class TootApiClient(
|
|||
return result
|
||||
}
|
||||
|
||||
fun getHttpBytes(url : String) :Pair<TootApiResult?,ByteArray?> {
|
||||
fun getHttpBytes(url : String) : Pair<TootApiResult?, ByteArray?> {
|
||||
val result = TootApiResult.makeWithCaption(url)
|
||||
if(result.error != null) return Pair(result,null)
|
||||
if(result.error != null) return Pair(result, null)
|
||||
|
||||
if( !sendRequest(result, progressPath = url) {
|
||||
if(! sendRequest(result, progressPath = url) {
|
||||
Request.Builder().url(url).build()
|
||||
}) {
|
||||
return Pair(result, null)
|
||||
}
|
||||
val r2 = parseBytes(result)
|
||||
return Pair(r2,r2?.data as? ByteArray)
|
||||
return Pair(r2, r2?.data as? ByteArray)
|
||||
}
|
||||
|
||||
fun webSocket(path : String, ws_listener : WebSocketListener) : Pair<TootApiResult?,WebSocket?> {
|
||||
var ws:WebSocket? = null
|
||||
fun webSocket(
|
||||
path : String,
|
||||
ws_listener : WebSocketListener
|
||||
) : Pair<TootApiResult?, WebSocket?> {
|
||||
var ws : WebSocket? = null
|
||||
val result = TootApiResult.makeWithCaption(instance)
|
||||
if(result.error != null) return Pair(result,null)
|
||||
val account = this.account ?: return Pair(TootApiResult("account is null"),null)
|
||||
if(result.error != null) return Pair(result, null)
|
||||
val account = this.account ?: return Pair(TootApiResult("account is null"), null)
|
||||
try {
|
||||
var url = "wss://$instance$path"
|
||||
|
||||
|
@ -1394,13 +1411,14 @@ class TootApiClient(
|
|||
ws = httpClient.getWebSocket(request, ws_listener)
|
||||
if(isApiCancelled) {
|
||||
ws.cancel()
|
||||
return Pair(null,null)
|
||||
return Pair(null, null)
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
result.error = "${result.caption}: ${ex.withCaption(context.resources, R.string.network_error)}"
|
||||
result.error =
|
||||
"${result.caption}: ${ex.withCaption(context.resources, R.string.network_error)}"
|
||||
}
|
||||
return Pair(result,ws)
|
||||
return Pair(result, ws)
|
||||
|
||||
}
|
||||
|
||||
|
@ -1504,7 +1522,10 @@ fun TootApiClient.syncAccountByAcct(
|
|||
return Pair(result, ar)
|
||||
}
|
||||
|
||||
fun TootApiClient.syncStatus(accessInfo : SavedAccount, urlArg : String) : Pair<TootApiResult?,TootStatus?> {
|
||||
fun TootApiClient.syncStatus(
|
||||
accessInfo : SavedAccount,
|
||||
urlArg : String
|
||||
) : Pair<TootApiResult?, TootStatus?> {
|
||||
|
||||
var url = urlArg
|
||||
|
||||
|
@ -1532,18 +1553,18 @@ fun TootApiClient.syncStatus(accessInfo : SavedAccount, urlArg : String) : Pair<
|
|||
.status(result.jsonObject)
|
||||
?.apply {
|
||||
if(host.equals(accessInfo.host, ignoreCase = true)) {
|
||||
return Pair(result,this)
|
||||
return Pair(result, this)
|
||||
}
|
||||
uri.letNotEmpty { url = it }
|
||||
}
|
||||
|
||||
}
|
||||
?: return Pair(null,null) // cancelled.
|
||||
?: return Pair(null, null) // cancelled.
|
||||
}
|
||||
|
||||
// 使いたいタンス上の投稿IDを取得する
|
||||
val parser = TootParser(context, accessInfo)
|
||||
var targetStatus :TootStatus? = null
|
||||
var targetStatus : TootStatus? = null
|
||||
val result = if(accessInfo.isMisskey) {
|
||||
request(
|
||||
"/api/ap/show",
|
||||
|
@ -1566,13 +1587,13 @@ fun TootApiClient.syncStatus(accessInfo : SavedAccount, urlArg : String) : Pair<
|
|||
}
|
||||
}
|
||||
}
|
||||
return Pair(result,targetStatus)
|
||||
return Pair(result, targetStatus)
|
||||
}
|
||||
|
||||
fun TootApiClient.syncStatus(
|
||||
accessInfo : SavedAccount,
|
||||
statusRemote : TootStatus
|
||||
) : Pair<TootApiResult?,TootStatus?> {
|
||||
) : Pair<TootApiResult?, TootStatus?> {
|
||||
|
||||
// URL->URIの順に試す
|
||||
|
||||
|
@ -1600,10 +1621,10 @@ fun TootApiClient.syncStatus(
|
|||
|
||||
for(uri in uriList) {
|
||||
val pair = syncStatus(accessInfo, uri)
|
||||
if( pair.second != null || pair.first == null ) {
|
||||
if(pair.second != null || pair.first == null) {
|
||||
return pair
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(TootApiResult("can't resolve status URL/URI."),null)
|
||||
return Pair(TootApiResult("can't resolve status URL/URI."), null)
|
||||
}
|
||||
|
|
|
@ -128,23 +128,25 @@ class NicoEnquete(
|
|||
this.votes_count = src.parseInt("votes_count")
|
||||
this.myVoted = if(src.optBoolean("voted", false)) 1 else null
|
||||
|
||||
if(this.items == null) {
|
||||
maxVotesCount = null
|
||||
} else if(this.multiple){
|
||||
var max :Int? = null
|
||||
for( item in items){
|
||||
val v = item.votes
|
||||
if( v != null && (max == null || v > max) ) max =v
|
||||
|
||||
when {
|
||||
this.items == null -> maxVotesCount = null
|
||||
this.multiple -> {
|
||||
var max :Int? = null
|
||||
for( item in items){
|
||||
val v = item.votes
|
||||
if( v != null && (max == null || v > max) ) max =v
|
||||
|
||||
}
|
||||
maxVotesCount = max
|
||||
}
|
||||
maxVotesCount = max
|
||||
} else {
|
||||
var sum :Int?= null
|
||||
for( item in items){
|
||||
val v = item.votes
|
||||
if( v != null ) sum = (sum?:0) + v
|
||||
else -> {
|
||||
var sum :Int?= null
|
||||
for( item in items){
|
||||
val v = item.votes
|
||||
if( v != null ) sum = (sum?:0) + v
|
||||
}
|
||||
maxVotesCount = sum
|
||||
}
|
||||
maxVotesCount = sum
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
|
@ -29,7 +29,9 @@ class TootNotification(parser : TootParser, src : JSONObject) : TimelineItem() {
|
|||
// 投票
|
||||
const val TYPE_VOTE = "poll_vote"
|
||||
const val TYPE_FOLLOW_REQUEST = "receiveFollowRequest"
|
||||
|
||||
|
||||
// (Mastodon 2.8)投票完了
|
||||
const val TYPE_POLL = "poll"
|
||||
}
|
||||
|
||||
val json : JSONObject
|
||||
|
|
|
@ -918,7 +918,8 @@ class SavedAccount(
|
|||
|
||||
TootNotification.TYPE_REACTION -> notification_reaction
|
||||
|
||||
TootNotification.TYPE_VOTE -> notification_vote
|
||||
TootNotification.TYPE_VOTE,TootNotification.TYPE_POLL -> notification_vote
|
||||
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class PushSubscriptionHelper(
|
|||
if(account.notification_follow) n += 4
|
||||
if(account.notification_mention) n += 8
|
||||
if(account.isMisskey && account.notification_reaction) n += 16
|
||||
if(account.isMisskey && account.notification_vote) n += 32
|
||||
if(account.notification_vote) n += 32
|
||||
this.flags = n
|
||||
}
|
||||
|
||||
|
@ -365,6 +365,7 @@ class PushSubscriptionHelper(
|
|||
put("favourite", account.notification_favourite)
|
||||
put("reblog", account.notification_boost)
|
||||
put("mention", account.notification_mention)
|
||||
put("poll", account.notification_vote)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -610,7 +610,7 @@
|
|||
<CheckBox
|
||||
android:id="@+id/cbNotificationVote"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:text="@string/vote_misskey"
|
||||
android:text="@string/vote_polls"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
|
|
@ -415,7 +415,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/setting_row_label_indent1"
|
||||
android:text="@string/vote_misskey"
|
||||
android:text="@string/vote_polls"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
|
|
@ -565,7 +565,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_margin="0dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/vote_misskey"
|
||||
android:contentDescription="@string/vote_polls"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
|
|
@ -748,7 +748,6 @@
|
|||
<string name="follow_request_cancelled">Follow request was cancelled.</string>
|
||||
<string name="confirm_cancel_follow_request_who_from">La demande de suivi de %2$s à %1$s sera rejetée. Vous êtes sûr ?</string>
|
||||
<string name="reaction">Réaction (Misskey)</string>
|
||||
<string name="vote_misskey">Vote (Misskey)</string>
|
||||
<string name="timeout_for_embed_media_viewer">Timeout for embed media viewer (unit:seconds, app restart(delete from app history) required)</string>
|
||||
<string name="media_attachment_max_byte_size_movie">Taille maximale en octets des médias vidéo (unité : méga octets. par défaut : 40)</string>
|
||||
<string name="link_color">Couleur des liens (redémarrage requis)</string>
|
||||
|
|
|
@ -749,7 +749,7 @@
|
|||
<string name="visibility_local_followers">フォロワー (ローカル)</string>
|
||||
<string name="visibility_local_unlisted">未収載 (ローカル)</string>
|
||||
|
||||
<string name="vote_misskey">投票 (Misskey)</string>
|
||||
<string name="vote_polls">投票やその結果</string>
|
||||
<string name="vote_count_text">%1$d票</string>
|
||||
<string name="wait_previous_operation">直前の操作が完了するまでお待ちください</string>
|
||||
<string name="with_attachment">添付データあり</string>
|
||||
|
@ -880,9 +880,9 @@
|
|||
<string name="poll_expire_hours">時間</string>
|
||||
<string name="poll_expire_minutes">分</string>
|
||||
<string name="vote_1">1 vote</string>
|
||||
<string name="vote_2">%1$d votes</string>
|
||||
<string name="vote_2">%1$d 人。</string>
|
||||
<string name="vote_expire_at">投票期限 %1$s</string>
|
||||
<string name="vote_count_unavailable">\?\?\?票</string>
|
||||
<string name="vote_button">投票</string>
|
||||
|
||||
<string name="end_of_polling_from">%1$sの調査の終了</string>
|
||||
</resources>
|
||||
|
|
|
@ -769,7 +769,6 @@
|
|||
<string name="follow_request_cancelled">팔로우 요청 취소됨.</string>
|
||||
<string name="confirm_cancel_follow_request_who_from">%2$s로부터 %1$s로의 팔로우 요청을 취소할까요\?</string>
|
||||
<string name="reaction">반응 (Misskey)</string>
|
||||
<string name="vote_misskey">투표 (Misskey)</string>
|
||||
<string name="timeout_for_embed_media_viewer">내장 미디어 뷰어 시간제한 (단위:초, 앱 재시작(앱 사용이력에서 삭제) 필요)</string>
|
||||
<string name="link_color">링크 색 (앱 재시작 필요)</string>
|
||||
<string name="missing_closeable_column">닫을 칼럼이 표시 범위에 없음.</string>
|
||||
|
|
|
@ -719,7 +719,6 @@
|
|||
<string name="follow_request_cancelled">Følgingsforespørsel forkastet.</string>
|
||||
<string name="confirm_cancel_follow_request_who_from">Følgingsforespørsel fra %1$s til %2$s vil forkastes. Er du sikker\?</string>
|
||||
<string name="reaction">Reaksjon (Misskey)</string>
|
||||
<string name="vote_misskey">Stem (Misskey)</string>
|
||||
<string name="timeout_for_embed_media_viewer">Tidsavbrudd for innebygd mediaviser (enhet:sekunder, programomstart (sletting fra programhistorikk) kreves)</string>
|
||||
<string name="link_color">Lenkefarge (programomstart kreves)</string>
|
||||
<string name="missing_closeable_column">mangler lukkbar kolonne i synlig område.</string>
|
||||
|
|
|
@ -756,7 +756,7 @@
|
|||
<string name="follow_request_cancelled">Follow request cancelled.</string>
|
||||
<string name="confirm_cancel_follow_request_who_from">Cancel follow request from %2$s to %1$s?</string>
|
||||
<string name="reaction">Reaction (Misskey)</string>
|
||||
<string name="vote_misskey">Vote (Misskey)</string>
|
||||
<string name="vote_polls">voting or its result</string>
|
||||
<string name="timeout_for_embed_media_viewer">Timeout for embedded media viewer (Unit:seconds, app restart(delete from app history) required)</string>
|
||||
<string name="link_color">Link color (app restart required)</string>
|
||||
<string name="missing_closeable_column">Missing closeable column in visible range.</string>
|
||||
|
@ -875,8 +875,10 @@
|
|||
<string name="poll_expire_hours">hours</string>
|
||||
<string name="poll_expire_minutes">minutes</string>
|
||||
<string name="vote_1">1 vote</string>
|
||||
<string name="vote_2">%1$d votes</string>
|
||||
<string name="vote_2">%1$d votes.</string>
|
||||
<string name="vote_expire_at">time limit: %1$s</string>
|
||||
<string name="vote_count_unavailable">\?\?\? votes</string>
|
||||
<string name="vote_button">Vote</string>
|
||||
<string name="end_of_polling_from">End of polling from %1$s</string>
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue