diff --git a/app/src/main/java/app/pachli/components/search/SearchActivity.kt b/app/src/main/java/app/pachli/components/search/SearchActivity.kt index 26bed02cd..c9e07aa82 100644 --- a/app/src/main/java/app/pachli/components/search/SearchActivity.kt +++ b/app/src/main/java/app/pachli/components/search/SearchActivity.kt @@ -74,21 +74,21 @@ import app.pachli.core.common.extensions.show import app.pachli.core.common.extensions.toggleVisibility import app.pachli.core.common.extensions.viewBinding import app.pachli.core.common.extensions.visible +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE import app.pachli.core.network.Server -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE import app.pachli.core.preferences.PrefKeys import app.pachli.core.ui.extensions.await import app.pachli.core.ui.extensions.awaitSingleChoiceItem diff --git a/app/src/main/java/app/pachli/components/search/SearchViewModel.kt b/app/src/main/java/app/pachli/components/search/SearchViewModel.kt index 1779e9297..ad98e2ddd 100644 --- a/app/src/main/java/app/pachli/components/search/SearchViewModel.kt +++ b/app/src/main/java/app/pachli/components/search/SearchViewModel.kt @@ -36,20 +36,20 @@ import app.pachli.components.search.adapter.SearchPagingSourceFactory import app.pachli.core.accounts.AccountManager import app.pachli.core.data.repository.ServerRepository import app.pachli.core.database.model.AccountEntity -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE import app.pachli.core.network.model.DeletedStatus import app.pachli.core.network.model.Poll import app.pachli.core.network.model.Status diff --git a/app/src/main/java/app/pachli/fragment/SFragment.kt b/app/src/main/java/app/pachli/fragment/SFragment.kt index 5bf82984b..2fcc77f1d 100644 --- a/app/src/main/java/app/pachli/fragment/SFragment.kt +++ b/app/src/main/java/app/pachli/fragment/SFragment.kt @@ -49,13 +49,13 @@ import app.pachli.core.data.repository.ServerRepository import app.pachli.core.database.model.AccountEntity import app.pachli.core.database.model.TranslationState import app.pachli.core.domain.DownloadUrlUseCase +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE import app.pachli.core.navigation.AttachmentViewData import app.pachli.core.navigation.ComposeActivityIntent import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions import app.pachli.core.navigation.ReportActivityIntent import app.pachli.core.navigation.TimelineActivityIntent import app.pachli.core.navigation.ViewMediaActivityIntent -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE import app.pachli.core.network.model.Attachment import app.pachli.core.network.model.Status import app.pachli.core.network.parseAsMastodonHtml diff --git a/core/data/src/main/kotlin/app/pachli/core/data/repository/ContentFiltersRepository.kt b/core/data/src/main/kotlin/app/pachli/core/data/repository/ContentFiltersRepository.kt index 9e6b872f8..bf2659e38 100644 --- a/core/data/src/main/kotlin/app/pachli/core/data/repository/ContentFiltersRepository.kt +++ b/core/data/src/main/kotlin/app/pachli/core/data/repository/ContentFiltersRepository.kt @@ -30,9 +30,9 @@ import app.pachli.core.data.repository.ContentFiltersError.GetContentFiltersErro import app.pachli.core.data.repository.ContentFiltersError.ServerDoesNotFilter import app.pachli.core.data.repository.ContentFiltersError.ServerRepositoryError import app.pachli.core.data.repository.ContentFiltersError.UpdateContentFilterError +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_FILTERS_CLIENT +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_FILTERS_SERVER import app.pachli.core.network.Server -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_FILTERS_CLIENT -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_FILTERS_SERVER import app.pachli.core.network.model.FilterAction import app.pachli.core.network.model.FilterContext import app.pachli.core.network.model.FilterKeyword diff --git a/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt b/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt index e7dd8b1bb..3878218bc 100644 --- a/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt +++ b/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt @@ -29,7 +29,7 @@ import app.pachli.core.data.repository.ServerRepository.Error.GetWellKnownNodeIn import app.pachli.core.data.repository.ServerRepository.Error.UnsupportedSchema import app.pachli.core.data.repository.ServerRepository.Error.ValidateNodeInfo import app.pachli.core.network.Server -import app.pachli.core.network.model.nodeinfo.NodeInfo +import app.pachli.core.network.model.nodeinfo.UnvalidatedNodeInfo import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.network.retrofit.NodeInfoApi import at.connyduck.calladapter.networkresult.fold @@ -100,7 +100,7 @@ class ServerRepository @Inject constructor( Timber.d("Loading node info from %s", nodeInfoUrl) val nodeInfo = nodeInfoApi.nodeInfo(nodeInfoUrl).fold( - { NodeInfo.from(it).mapError { ValidateNodeInfo(nodeInfoUrl, it) } }, + { it.validate().mapError { ValidateNodeInfo(nodeInfoUrl, it) } }, { Err(GetNodeInfo(nodeInfoUrl, it)) }, ).bind() @@ -136,7 +136,7 @@ class ServerRepository @Inject constructor( arrayOf(url, throwable.localizedMessage ?: ""), ) - data class ValidateNodeInfo(val url: String, val error: NodeInfo.Error) : Error( + data class ValidateNodeInfo(val url: String, val error: UnvalidatedNodeInfo.Error) : Error( R.string.server_repository_error_validate_node_info, arrayOf(url), cause = error, diff --git a/core/data/src/main/kotlin/app/pachli/core/data/repository/StatusDisplayOptionsRepository.kt b/core/data/src/main/kotlin/app/pachli/core/data/repository/StatusDisplayOptionsRepository.kt index fadf0e753..2a3286eba 100644 --- a/core/data/src/main/kotlin/app/pachli/core/data/repository/StatusDisplayOptionsRepository.kt +++ b/core/data/src/main/kotlin/app/pachli/core/data/repository/StatusDisplayOptionsRepository.kt @@ -23,7 +23,7 @@ import app.pachli.core.accounts.AccountManager import app.pachli.core.common.di.ApplicationScope import app.pachli.core.data.model.StatusDisplayOptions import app.pachli.core.database.model.AccountEntity -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE import app.pachli.core.preferences.CardViewMode import app.pachli.core.preferences.PrefKeys import app.pachli.core.preferences.SharedPreferencesRepository diff --git a/core/data/src/test/kotlin/app/pachli/core/data/repository/filtersRepository/BaseContentFiltersRepositoryTest.kt b/core/data/src/test/kotlin/app/pachli/core/data/repository/filtersRepository/BaseContentFiltersRepositoryTest.kt index 53e94c26e..2a0fb61e4 100644 --- a/core/data/src/test/kotlin/app/pachli/core/data/repository/filtersRepository/BaseContentFiltersRepositoryTest.kt +++ b/core/data/src/test/kotlin/app/pachli/core/data/repository/filtersRepository/BaseContentFiltersRepositoryTest.kt @@ -22,9 +22,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import app.pachli.core.data.repository.ContentFiltersRepository import app.pachli.core.data.repository.HiltTestApplication_Application import app.pachli.core.data.repository.ServerRepository +import app.pachli.core.model.ServerKind +import app.pachli.core.model.ServerOperation import app.pachli.core.network.Server -import app.pachli.core.network.ServerKind -import app.pachli.core.network.ServerOperation import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.testing.rules.MainCoroutineRule import com.github.michaelbull.result.Ok diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 6a6c62f41..a733bfb4f 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -36,4 +36,7 @@ dependencies { implementation(libs.moshix.sealed.runtime) ksp(libs.moshix.sealed.codegen) + + implementation(libs.semver) + ?.because("ServerOperation uses Version") } diff --git a/core/model/src/main/kotlin/app/pachli/core/model/NodeInfo.kt b/core/model/src/main/kotlin/app/pachli/core/model/NodeInfo.kt new file mode 100644 index 000000000..ab5e2f311 --- /dev/null +++ b/core/model/src/main/kotlin/app/pachli/core/model/NodeInfo.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.core.model + +/** + * A validated NodeInfo. + * + * See https://nodeinfo.diaspora.software/protocol.html and + * https://nodeinfo.diaspora.software/schema.html. + */ +data class NodeInfo(val software: Software) { + data class Software( + /** Software name, won't be null, empty, or blank */ + val name: String, + /** Software version, won't be null, empty, or blank */ + val version: String, + ) +} diff --git a/core/model/src/main/kotlin/app/pachli/core/model/ServerKind.kt b/core/model/src/main/kotlin/app/pachli/core/model/ServerKind.kt new file mode 100644 index 000000000..5f2d0b30e --- /dev/null +++ b/core/model/src/main/kotlin/app/pachli/core/model/ServerKind.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.core.model + +/** + * Servers that are known to implement the Mastodon client API + */ +enum class ServerKind { + AKKOMA, + FEDIBIRD, + FIREFISH, + FRIENDICA, + GLITCH, + GOTOSOCIAL, + HOMETOWN, + ICESHRIMP, + MASTODON, + PLEROMA, + PIXELFED, + SHARKEY, + + /** + * Catch-all for servers we don't recognise but that responded to either + * /api/v1/instance or /api/v2/instance + */ + UNKNOWN, + ; + + companion object { + fun from(s: NodeInfo.Software) = when (s.name.lowercase()) { + "akkoma" -> AKKOMA + "fedibird" -> FEDIBIRD + "firefish" -> FIREFISH + "friendica" -> FRIENDICA + "gotosocial" -> GOTOSOCIAL + "hometown" -> HOMETOWN + "iceshrimp" -> ICESHRIMP + "mastodon" -> { + // Glitch doesn't report a different software name it stuffs it + // in the version (https://github.com/glitch-soc/mastodon/issues/2582). + if (s.version.contains("+glitch")) GLITCH else MASTODON + } + "pixelfed" -> PIXELFED + "pleroma" -> PLEROMA + "sharkey" -> SHARKEY + else -> UNKNOWN + } + } +} diff --git a/core/model/src/main/kotlin/app/pachli/core/model/ServerOperation.kt b/core/model/src/main/kotlin/app/pachli/core/model/ServerOperation.kt new file mode 100644 index 000000000..16dfbf779 --- /dev/null +++ b/core/model/src/main/kotlin/app/pachli/core/model/ServerOperation.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.core.model + +import io.github.z4kn4fein.semver.Version + +/** + * Identifiers for operations that the server may or may not support. + */ +enum class ServerOperation(id: String, versions: List) { + /** Client-side filters */ + ORG_JOINMASTODON_FILTERS_CLIENT( + "org.joinmastodon.filters.client", + listOf( + // Initial introduction in Mastodon 2.4.3 + Version(major = 1), + // "account" context available in filter views in Mastodon 3.1.0 + Version(major = 1, minor = 1), + ), + ), + + /** Server-side filters */ + ORG_JOINMASTODON_FILTERS_SERVER( + "org.joinmastodon.filters.server", + listOf( + // Intitial introduction in Mastodon 4.0.0 + Version(major = 1), + ), + ), + + /** Translate a status */ + ORG_JOINMASTODON_STATUSES_TRANSLATE( + "org.joinmastodon.statuses.translate", + listOf( + // Initial introduction in Mastodon 4.0.0 + Version(major = 1), + // Spoiler warnings, polls, and media descriptions are also translated in Mastodon 4.2.0 + Version(major = 1, minor = 1), + ), + ), + + /** Search for posts from a particular account */ + ORG_JOINMASTODON_SEARCH_QUERY_FROM( + "org.joinmastodon.search.query:from", + listOf( + // Initial introduction in Mastodon 3.5.0 + Version(major = 1), + // Support for `from:me` in Mastodon 4.2.0 + Version(major = 1, minor = 1), + ), + ), + ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE("org.joinmastodon.search.query:language", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA("org.joinmastodon.search.query:has:media", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE("org.joinmastodon.search.query:has:image", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO("org.joinmastodon.search.query:has:video", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO("org.joinmastodon.search.query:has:audio", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL("org.joinmastodon.search.query:has:poll", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK("org.joinmastodon.search.query:has:link", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED("org.joinmastodon.search.query:has:embed", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY("org.joinmastodon.search.query:is:reply", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE("org.joinmastodon.search.query:is:sensitive", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY("org.joinmastodon.search.query:in:library", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC("org.joinmastodon.search.query:in:public", listOf(Version(major = 1))), + ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE("org.joinmastodon.search.query:in:public", listOf(Version(major = 1))), +} diff --git a/core/network/src/main/kotlin/app/pachli/core/network/Server.kt b/core/network/src/main/kotlin/app/pachli/core/network/Server.kt index 8bab7012d..19820326d 100644 --- a/core/network/src/main/kotlin/app/pachli/core/network/Server.kt +++ b/core/network/src/main/kotlin/app/pachli/core/network/Server.kt @@ -20,40 +20,42 @@ package app.pachli.core.network import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.Companion.PRIVATE import app.pachli.core.common.PachliError +import app.pachli.core.model.NodeInfo +import app.pachli.core.model.ServerKind +import app.pachli.core.model.ServerKind.AKKOMA +import app.pachli.core.model.ServerKind.FEDIBIRD +import app.pachli.core.model.ServerKind.FIREFISH +import app.pachli.core.model.ServerKind.FRIENDICA +import app.pachli.core.model.ServerKind.GLITCH +import app.pachli.core.model.ServerKind.GOTOSOCIAL +import app.pachli.core.model.ServerKind.HOMETOWN +import app.pachli.core.model.ServerKind.ICESHRIMP +import app.pachli.core.model.ServerKind.MASTODON +import app.pachli.core.model.ServerKind.PIXELFED +import app.pachli.core.model.ServerKind.PLEROMA +import app.pachli.core.model.ServerKind.SHARKEY +import app.pachli.core.model.ServerKind.UNKNOWN +import app.pachli.core.model.ServerOperation +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_FILTERS_CLIENT +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_FILTERS_SERVER +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE import app.pachli.core.network.Server.Error.UnparseableVersion -import app.pachli.core.network.ServerKind.AKKOMA -import app.pachli.core.network.ServerKind.FEDIBIRD -import app.pachli.core.network.ServerKind.FIREFISH -import app.pachli.core.network.ServerKind.FRIENDICA -import app.pachli.core.network.ServerKind.GLITCH -import app.pachli.core.network.ServerKind.GOTOSOCIAL -import app.pachli.core.network.ServerKind.HOMETOWN -import app.pachli.core.network.ServerKind.ICESHRIMP -import app.pachli.core.network.ServerKind.MASTODON -import app.pachli.core.network.ServerKind.PIXELFED -import app.pachli.core.network.ServerKind.PLEROMA -import app.pachli.core.network.ServerKind.SHARKEY -import app.pachli.core.network.ServerKind.UNKNOWN -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_FILTERS_CLIENT -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_FILTERS_SERVER -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE import app.pachli.core.network.model.InstanceV1 import app.pachli.core.network.model.InstanceV2 -import app.pachli.core.network.model.nodeinfo.NodeInfo import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result import com.github.michaelbull.result.andThen @@ -69,66 +71,6 @@ import io.github.z4kn4fein.semver.toVersion import java.text.ParseException import kotlin.collections.set -/** - * Identifiers for operations that the server may or may not support. - */ -enum class ServerOperation(id: String, versions: List) { - /** Client-side filters */ - ORG_JOINMASTODON_FILTERS_CLIENT( - "org.joinmastodon.filters.client", - listOf( - // Initial introduction in Mastodon 2.4.3 - Version(major = 1), - // "account" context available in filter views in Mastodon 3.1.0 - Version(major = 1, minor = 1), - ), - ), - - /** Server-side filters */ - ORG_JOINMASTODON_FILTERS_SERVER( - "org.joinmastodon.filters.server", - listOf( - // Intitial introduction in Mastodon 4.0.0 - Version(major = 1), - ), - ), - - /** Translate a status */ - ORG_JOINMASTODON_STATUSES_TRANSLATE( - "org.joinmastodon.statuses.translate", - listOf( - // Initial introduction in Mastodon 4.0.0 - Version(major = 1), - // Spoiler warnings, polls, and media descriptions are also translated in Mastodon 4.2.0 - Version(major = 1, minor = 1), - ), - ), - - /** Search for posts from a particular account */ - ORG_JOINMASTODON_SEARCH_QUERY_FROM( - "org.joinmastodon.search.query:from", - listOf( - // Initial introduction in Mastodon 3.5.0 - Version(major = 1), - // Support for `from:me` in Mastodon 4.2.0 - Version(major = 1, minor = 1), - ), - ), - ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE("org.joinmastodon.search.query:language", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA("org.joinmastodon.search.query:has:media", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE("org.joinmastodon.search.query:has:image", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO("org.joinmastodon.search.query:has:video", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO("org.joinmastodon.search.query:has:audio", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL("org.joinmastodon.search.query:has:poll", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK("org.joinmastodon.search.query:has:link", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED("org.joinmastodon.search.query:has:embed", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY("org.joinmastodon.search.query:is:reply", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE("org.joinmastodon.search.query:is:sensitive", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY("org.joinmastodon.search.query:in:library", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC("org.joinmastodon.search.query:in:public", listOf(Version(major = 1))), - ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE("org.joinmastodon.search.query:in:public", listOf(Version(major = 1))), -} - data class Server( val kind: ServerKind, val version: Version, @@ -138,10 +80,10 @@ data class Server( * @return true if the server supports the given operation at the given minimum version * level, false otherwise. */ - fun can(operation: ServerOperation, constraint: Constraint) = capabilities[operation]?.let { - version -> - version satisfies constraint - } ?: false + fun can(operation: ServerOperation, constraint: Constraint) = + capabilities[operation]?.let { version -> + version satisfies constraint + } ?: false companion object { /** @@ -401,49 +343,3 @@ data class Server( } } } - -/** - * Servers that are known to implement the Mastodon client API - */ -enum class ServerKind { - AKKOMA, - FEDIBIRD, - FIREFISH, - FRIENDICA, - GLITCH, - GOTOSOCIAL, - HOMETOWN, - ICESHRIMP, - MASTODON, - PLEROMA, - PIXELFED, - SHARKEY, - - /** - * Catch-all for servers we don't recognise but that responded to either - * /api/v1/instance or /api/v2/instance - */ - UNKNOWN, - ; - - companion object { - fun from(s: NodeInfo.Software) = when (s.name.lowercase()) { - "akkoma" -> AKKOMA - "fedibird" -> FEDIBIRD - "firefish" -> FIREFISH - "friendica" -> FRIENDICA - "gotosocial" -> GOTOSOCIAL - "hometown" -> HOMETOWN - "iceshrimp" -> ICESHRIMP - "mastodon" -> { - // Glitch doesn't report a different software name it stuffs it - // in the version (https://github.com/glitch-soc/mastodon/issues/2582). - if (s.version.contains("+glitch")) GLITCH else MASTODON - } - "pixelfed" -> PIXELFED - "pleroma" -> PLEROMA - "sharkey" -> SHARKEY - else -> UNKNOWN - } - } -} diff --git a/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt b/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt index 4395114da..dc3be6c97 100644 --- a/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt +++ b/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt @@ -19,10 +19,9 @@ package app.pachli.core.network.model.nodeinfo import androidx.annotation.StringRes import app.pachli.core.common.PachliError +import app.pachli.core.model.NodeInfo +import app.pachli.core.model.NodeInfo.Software import app.pachli.core.network.R -import app.pachli.core.network.model.nodeinfo.NodeInfo.Error.NoSoftwareBlock -import app.pachli.core.network.model.nodeinfo.NodeInfo.Error.NoSoftwareName -import app.pachli.core.network.model.nodeinfo.NodeInfo.Error.NoSoftwareVersion import com.github.michaelbull.result.Err import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result @@ -49,30 +48,20 @@ data class UnvalidatedJrd(val links: List) { data class UnvalidatedNodeInfo(val software: Software?) { @JsonClass(generateAdapter = true) data class Software(val name: String?, val version: String?) -} -/** - * A validated NodeInfo. - * - * See https://nodeinfo.diaspora.software/protocol.html and - * https://nodeinfo.diaspora.software/schema.html. - */ -data class NodeInfo(val software: Software) { - data class Software( - /** Software name, won't be null, empty, or blank */ - val name: String, - /** Software version, won't be null, empty, or blank */ - val version: String, - ) - - companion object { - fun from(nodeInfo: UnvalidatedNodeInfo): Result { - return when { - nodeInfo.software == null -> Err(NoSoftwareBlock) - nodeInfo.software.name.isNullOrBlank() -> Err(NoSoftwareName) - nodeInfo.software.version.isNullOrBlank() -> Err(NoSoftwareVersion) - else -> Ok(NodeInfo(Software(nodeInfo.software.name, nodeInfo.software.version))) - } + fun validate(): Result { + return when { + software == null -> Err(Error.NoSoftwareBlock) + software.name.isNullOrBlank() -> Err(Error.NoSoftwareName) + software.version.isNullOrBlank() -> Err(Error.NoSoftwareVersion) + else -> Ok( + NodeInfo( + NodeInfo.Software( + software.name, + software.version, + ), + ), + ) } } diff --git a/core/network/src/test/kotlin/app/pachli/core/network/ServerTest.kt b/core/network/src/test/kotlin/app/pachli/core/network/ServerTest.kt index 759adcd7e..d6718800a 100644 --- a/core/network/src/test/kotlin/app/pachli/core/network/ServerTest.kt +++ b/core/network/src/test/kotlin/app/pachli/core/network/ServerTest.kt @@ -17,29 +17,31 @@ package app.pachli.core.network -import app.pachli.core.network.ServerKind.AKKOMA -import app.pachli.core.network.ServerKind.FIREFISH -import app.pachli.core.network.ServerKind.FRIENDICA -import app.pachli.core.network.ServerKind.GOTOSOCIAL -import app.pachli.core.network.ServerKind.MASTODON -import app.pachli.core.network.ServerKind.PLEROMA -import app.pachli.core.network.ServerKind.UNKNOWN -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_FILTERS_CLIENT -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_FILTERS_SERVER -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE -import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE +import app.pachli.core.model.NodeInfo +import app.pachli.core.model.ServerKind +import app.pachli.core.model.ServerKind.AKKOMA +import app.pachli.core.model.ServerKind.FIREFISH +import app.pachli.core.model.ServerKind.FRIENDICA +import app.pachli.core.model.ServerKind.GOTOSOCIAL +import app.pachli.core.model.ServerKind.MASTODON +import app.pachli.core.model.ServerKind.PLEROMA +import app.pachli.core.model.ServerKind.UNKNOWN +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_FILTERS_CLIENT +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_FILTERS_SERVER +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE +import app.pachli.core.model.ServerOperation.ORG_JOINMASTODON_STATUSES_TRANSLATE import app.pachli.core.network.model.Account import app.pachli.core.network.model.Configuration import app.pachli.core.network.model.Contact @@ -54,7 +56,6 @@ import app.pachli.core.network.model.Registrations import app.pachli.core.network.model.Thumbnail import app.pachli.core.network.model.Usage import app.pachli.core.network.model.Users -import app.pachli.core.network.model.nodeinfo.NodeInfo import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result import com.google.common.truth.Truth.assertWithMessage