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

150 lines
4.8 KiB
Kotlin

package jp.juggler.subwaytooter.streaming
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.subwaytooter.column.*
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())
}
}
class StreamSpec(
val params: JsonObject,
val path: String,
val name: String,
val streamFilter: Column.(JsonArray, TimelineItem) -> Boolean = { _, _ -> true }
) {
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
}
fun paramsClone() =
params.toString().decodeJsonObject()
}
private fun encodeStreamNameMastodon(root: JsonObject) = StringWriter()
.also { sw ->
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()
private fun Column.streamSpecMastodon(): StreamSpec? {
val root = type.streamKeyMastodon(this) ?: return null
return StreamSpec(
params = root,
path = "/api/v1/streaming/?${root.encodeQuery()}",
name = encodeStreamNameMastodon(root),
streamFilter = type.streamFilterMastodon
)
}
private fun encodeStreamNameMisskey(root: JsonObject) =
StringWriter().also { sw ->
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()
fun Column.streamSpecMisskey(): StreamSpec? {
// 認証トークンがないなら認証トークン必須のカラムにはアクセスできない
if (accessInfo.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
)
}
val Column.streamSpec: StreamSpec?
get() = when {
// 疑似アカウントではストリーミングAPIを利用できない
// 2.1 では公開ストリームのみ利用できるらしい
(accessInfo.isNA || accessInfo.isPseudo && !isPublicStream) -> null
accessInfo.isMastodon -> streamSpecMastodon()
accessInfo.isMisskey -> streamSpecMisskey()
else -> null
}