SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamSpec.kt

162 lines
5.0 KiB
Kotlin
Raw Normal View History

2020-12-21 03:13:03 +01:00
package jp.juggler.subwaytooter.streaming
import jp.juggler.subwaytooter.*
2020-12-21 21:16:33 +01:00
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.subwaytooter.streaming.StreamSpec.Companion.CHANNEL
import jp.juggler.subwaytooter.streaming.StreamSpec.Companion.PARAMS
import jp.juggler.subwaytooter.streaming.StreamSpec.Companion.STREAM
import jp.juggler.util.*
import java.io.StringWriter
private fun StringWriter.appendValue(v: Any?) {
when (v) {
is JsonArray -> {
append('[')
v.forEachIndexed { i, child ->
if (i > 0) append(',')
appendValue(child)
}
append(']')
}
is JsonObject -> {
append('{')
v.entries.sortedBy { it.key }.forEachIndexed { i, child ->
if (i > 0) append(',')
append(child.key)
append('=')
appendValue(child)
}
append('}')
}
else -> append(v.toString())
}
}
2020-12-21 03:13:03 +01:00
class StreamSpec(
2020-12-21 21:16:33 +01:00
val params: JsonObject,
val path: String,
val name: String,
val streamFilter: Column.(String?, TimelineItem) -> Boolean = { _, _ -> true }
2020-12-21 03:13:03 +01:00
) {
2020-12-21 21:16:33 +01:00
companion object {
const val STREAM = "stream"
const val CHANNEL = "channel"
const val PARAMS = "params"
}
val keyString = "$path?${params.toString(indentFactor = 0, sort = true)}"
val channelId = keyString.digestSHA256Base64Url()
override fun hashCode(): Int = keyString.hashCode()
override fun equals(other: Any?): Boolean {
if (other is StreamSpec) return keyString == other.keyString
return false
}
2020-12-26 22:03:58 +01:00
fun paramsClone() =
params.toString().decodeJsonObject()
2020-12-21 21:16:33 +01:00
}
private fun encodeStreamNameMastodon(root: JsonObject) = StringWriter()
.also { sw ->
2020-12-21 21:16:33 +01:00
sw.append(root.string(STREAM)!!)
root.entries.sortedBy { it.key }.forEach { pair ->
val (k, v) = pair
if (k != STREAM && v !is JsonArray && v !is JsonObject) {
sw.append(',').append(k).append('=').appendValue(v)
}
}
root.entries.sortedBy { it.key }.forEach { pair ->
val (k, v) = pair
if (v is JsonArray || v is JsonObject) {
sw.append(',').append(k).append('=').appendValue(v)
}
}
}.toString()
2020-12-21 21:16:33 +01:00
private fun Column.streamSpecMastodon(): StreamSpec? {
2020-12-21 21:16:33 +01:00
val root = type.streamKeyMastodon(this) ?: return null
2020-12-21 21:16:33 +01:00
return StreamSpec(
params = root,
path = "/api/v1/streaming/?${root.encodeQuery()}",
name = encodeStreamNameMastodon(root),
streamFilter = type.streamFilterMastodon
)
}
2020-12-21 21:16:33 +01:00
private fun encodeStreamNameMisskey(root: JsonObject) =
StringWriter().also { sw ->
2020-12-21 21:16:33 +01:00
sw.append(root.string(CHANNEL)!!)
val params = root.jsonObject(PARAMS)!!
params.entries.sortedBy { it.key }.forEach { pair ->
val (k, v) = pair
if (v !is JsonArray && v !is JsonObject) {
sw.append(',').append(k).append('=').appendValue(v)
}
}
params.entries.sortedBy { it.key }.forEach { pair ->
val (k, v) = pair
if (v is JsonArray || v is JsonObject) {
sw.append(',').append(k).append('=').appendValue(v)
}
}
}.toString()
2020-12-21 21:16:33 +01:00
fun Column.streamSpecMisskey(): StreamSpec? {
2020-12-21 21:16:33 +01:00
// 認証トークンがないなら認証トークン必須のカラムにはアクセスできない
if (access_info.misskeyApiToken == null &&
when (type) {
ColumnType.HOME,
ColumnType.MISSKEY_HYBRID,
ColumnType.NOTIFICATIONS,
ColumnType.LIST_TL,
ColumnType.MISSKEY_ANTENNA_TL -> true
else -> false
}
) return null
val channelName = type.streamNameMisskey ?: return null
val path = when {
misskeyVersion >= 10 -> "/streaming"
else -> type.streamPathMisskey9(this)
} ?: return null
val channelParam = type.streamParamMisskey(this) ?: JsonObject()
val root = jsonObjectOf(CHANNEL to channelName, PARAMS to channelParam)
return StreamSpec(
params = root,
path = path,
name = encodeStreamNameMisskey(root),
// no stream filter
)
}
2020-12-21 21:16:33 +01:00
val Column.streamSpec: StreamSpec?
get() = when {
// 疑似アカウントではストリーミングAPIを利用できない
// 2.1 では公開ストリームのみ利用できるらしい
(access_info.isNA || access_info.isPseudo && !isPublicStream) -> null
access_info.isMastodon -> streamSpecMastodon()
access_info.isMisskey -> streamSpecMisskey()
2020-12-21 21:16:33 +01:00
else -> null
}
fun Column.canStreaming() = when {
access_info.isNA -> false
access_info.isPseudo -> isPublicStream && streamSpec != null
else -> streamSpec != null
}
2020-12-21 03:13:03 +01:00
fun Column.canSpeech() =
canStreaming() && !isNotificationColumn