2020-12-21 03:13:03 +01:00
|
|
|
package jp.juggler.subwaytooter.streaming
|
|
|
|
|
2020-12-22 00:49:26 +01:00
|
|
|
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,
|
2020-12-22 00:49:26 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-12-22 00:49:26 +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)
|
|
|
|
}
|
|
|
|
}
|
2020-12-22 00:49:26 +01:00
|
|
|
}.toString()
|
2020-12-21 21:16:33 +01:00
|
|
|
|
|
|
|
|
2020-12-22 00:49:26 +01:00
|
|
|
private fun Column.streamSpecMastodon(): StreamSpec? {
|
2020-12-21 21:16:33 +01:00
|
|
|
|
2020-12-22 00:49:26 +01:00
|
|
|
val root = type.streamKeyMastodon(this) ?: return null
|
2020-12-21 21:16:33 +01:00
|
|
|
|
2020-12-22 00:49:26 +01:00
|
|
|
return StreamSpec(
|
2020-12-22 05:51:52 +01:00
|
|
|
params = root,
|
|
|
|
path = "/api/v1/streaming/?${root.encodeQuery()}",
|
|
|
|
name = encodeStreamNameMastodon(root),
|
|
|
|
streamFilter = type.streamFilterMastodon
|
2020-12-22 00:49:26 +01:00
|
|
|
)
|
|
|
|
}
|
2020-12-21 21:16:33 +01:00
|
|
|
|
2020-12-22 05:51:52 +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)
|
|
|
|
}
|
|
|
|
}
|
2020-12-22 00:49:26 +01:00
|
|
|
}.toString()
|
2020-12-21 21:16:33 +01:00
|
|
|
|
2020-12-22 00:49:26 +01:00
|
|
|
fun Column.streamSpecMisskey(): StreamSpec? {
|
2020-12-21 21:16:33 +01:00
|
|
|
|
2020-12-22 05:51:52 +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
|
2020-12-22 00:49:26 +01:00
|
|
|
|
2020-12-22 05:51:52 +01:00
|
|
|
val channelName = type.streamNameMisskey ?: return null
|
|
|
|
|
|
|
|
val path = when {
|
|
|
|
misskeyVersion >= 10 -> "/streaming"
|
|
|
|
else -> type.streamPathMisskey9(this)
|
2020-12-22 00:49:26 +01:00
|
|
|
} ?: return null
|
|
|
|
|
|
|
|
val channelParam = type.streamParamMisskey(this) ?: JsonObject()
|
2020-12-22 05:51:52 +01:00
|
|
|
val root = jsonObjectOf(CHANNEL to channelName, PARAMS to channelParam)
|
2020-12-22 00:49:26 +01:00
|
|
|
|
|
|
|
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
|
2020-12-22 00:49:26 +01:00
|
|
|
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
|
|
|
|
2020-12-22 00:49:26 +01:00
|
|
|
fun Column.canSpeech() =
|
|
|
|
canStreaming() && !isNotificationColumn
|