From 1d4c306450b7c20c5e39d1a15033c79ca0a22b64 Mon Sep 17 00:00:00 2001 From: Patrick Geselbracht Date: Sun, 15 Oct 2023 14:55:48 +0200 Subject: [PATCH] Deserialize JSON using KotlinX Serialization instead of Gson (#270) * Add Kotlin serialization library * Prepare all entities to be serializable by kotlinx serializer * Use Kotlin serializer instead of Gson * Ensure accounts ids are String in test JSON files as defined in API doc * Move Json base settings to internal object * Remove remnants of Gson and replace them with kotlinx.Json * Detekt/checkstyle * Remove MastodonClient#getSerializer, instead use module object Json * Replace star imports with single imports * Do not share kotlinx serialization dependency with library consumers * Change recently merged data classes to kotlinx Serializable * Remove serializer from MockClient --- bigbone-rx/build.gradle | 1 - .../src/test/assets/public_timeline.json | 80 ++++----- .../social/bigbone/rx/testtool/MockClient.kt | 5 +- bigbone/build.gradle | 4 +- .../kotlin/social/bigbone/JsonSerializer.kt | 9 ++ .../kotlin/social/bigbone/MastodonClient.kt | 48 +++--- .../kotlin/social/bigbone/MastodonRequest.kt | 40 +++-- .../social/bigbone/api/entity/Account.kt | 61 +++---- .../social/bigbone/api/entity/Application.kt | 14 +- .../social/bigbone/api/entity/Context.kt | 8 +- .../social/bigbone/api/entity/Conversation.kt | 12 +- .../social/bigbone/api/entity/CustomEmoji.kt | 14 +- .../kotlin/social/bigbone/api/entity/Error.kt | 8 +- .../social/bigbone/api/entity/FeaturedTag.kt | 14 +- .../social/bigbone/api/entity/Filter.kt | 18 ++- .../bigbone/api/entity/FilterKeyword.kt | 10 +- .../social/bigbone/api/entity/FilterResult.kt | 10 +- .../social/bigbone/api/entity/FilterStatus.kt | 8 +- .../social/bigbone/api/entity/Instance.kt | 87 +++++----- .../social/bigbone/api/entity/Marker.kt | 10 +- .../social/bigbone/api/entity/Markers.kt | 8 +- .../social/bigbone/api/entity/MastodonList.kt | 10 +- .../bigbone/api/entity/MediaAttachment.kt | 25 +-- .../social/bigbone/api/entity/Notification.kt | 16 +- .../kotlin/social/bigbone/api/entity/Poll.kt | 29 ++-- .../social/bigbone/api/entity/PreviewCard.kt | 35 ++-- .../social/bigbone/api/entity/Relationship.kt | 32 ++-- .../social/bigbone/api/entity/Report.kt | 24 +-- .../kotlin/social/bigbone/api/entity/Rule.kt | 8 +- .../bigbone/api/entity/ScheduledStatus.kt | 37 +++-- .../social/bigbone/api/entity/Search.kt | 10 +- .../social/bigbone/api/entity/Status.kt | 76 ++++----- .../social/bigbone/api/entity/StatusEdit.kt | 26 +-- .../social/bigbone/api/entity/StatusSource.kt | 10 +- .../kotlin/social/bigbone/api/entity/Tag.kt | 12 +- .../kotlin/social/bigbone/api/entity/Token.kt | 12 +- .../social/bigbone/api/entity/Translation.kt | 10 +- .../social/bigbone/api/entity/data/Focus.kt | 8 +- .../social/bigbone/api/entity/data/History.kt | 10 +- .../api/entity/data/InstanceVersion.kt | 6 +- .../bigbone/api/entity/data/PollData.kt | 12 +- .../social/bigbone/api/method/MediaMethods.kt | 11 +- .../bigbone/api/method/StreamingMethods.kt | 76 ++------- .../bigbone/extension/ResponseExtensions.kt | 25 --- .../social/bigbone/nodeinfo/NodeInfoClient.kt | 27 ++-- .../bigbone/nodeinfo/entity/NodeInfo.kt | 11 +- .../social/bigbone/nodeinfo/entity/Server.kt | 13 +- bigbone/src/test/assets/account.json | 2 +- bigbone/src/test/assets/account_search.json | 80 ++++----- bigbone/src/test/assets/blocks.json | 2 +- bigbone/src/test/assets/bookmarks.json | 4 +- bigbone/src/test/assets/context.json | 26 +-- bigbone/src/test/assets/conversations.json | 4 +- .../test/assets/conversations_after_edit.json | 2 +- bigbone/src/test/assets/favourites.json | 4 +- bigbone/src/test/assets/follow_requests.json | 2 +- bigbone/src/test/assets/mutes.json | 2 +- bigbone/src/test/assets/notifications.json | 12 +- bigbone/src/test/assets/public_timeline.json | 80 ++++----- bigbone/src/test/assets/reblogged_by.json | 2 +- bigbone/src/test/assets/relationship.json | 2 +- bigbone/src/test/assets/relationships.json | 2 +- bigbone/src/test/assets/search.json | 12 +- .../src/test/assets/search_only_accounts.json | 12 +- bigbone/src/test/assets/statuses.json | 4 +- bigbone/src/test/assets/tag_timeline.json | 152 +++++++++--------- bigbone/src/test/assets/timelines.json | 4 +- .../social/bigbone/api/entity/ErrorTest.kt | 6 +- .../bigbone/api/entity/MediaAttachmentTest.kt | 4 +- .../social/bigbone/api/entity/StatusTest.kt | 4 +- .../social/bigbone/api/entity/TokenTest.kt | 4 +- .../bigbone/nodeinfo/entity/NodeInfoTest.kt | 4 +- .../bigbone/nodeinfo/entity/ServerTest.kt | 4 +- .../social/bigbone/testtool/MockClient.kt | 4 - gradle/libs.versions.toml | 7 +- sample-java/build.gradle | 1 - .../bigbone/sample/GetInstanceInfo.java | 9 +- sample-kotlin/build.gradle | 3 - .../social/bigbone/sample/GetInstanceInfo.kt | 5 +- 79 files changed, 759 insertions(+), 746 deletions(-) create mode 100644 bigbone/src/main/kotlin/social/bigbone/JsonSerializer.kt delete mode 100644 bigbone/src/main/kotlin/social/bigbone/extension/ResponseExtensions.kt diff --git a/bigbone-rx/build.gradle b/bigbone-rx/build.gradle index 9a567e9d1..3e92bfdfe 100644 --- a/bigbone-rx/build.gradle +++ b/bigbone-rx/build.gradle @@ -11,5 +11,4 @@ dependencies { testRuntimeOnly libs.junit.platform.launcher testImplementation libs.mockk testImplementation libs.mockk.dsl - testImplementation libs.gson } diff --git a/bigbone-rx/src/test/assets/public_timeline.json b/bigbone-rx/src/test/assets/public_timeline.json index 494e05293..db7067bec 100644 --- a/bigbone-rx/src/test/assets/public_timeline.json +++ b/bigbone-rx/src/test/assets/public_timeline.json @@ -10,7 +10,7 @@ "following_count": 0, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 14476, + "id": "14476", "locked": false, "note": "フリーなエンジニア時々詩人。", "statuses_count": 10, @@ -22,7 +22,7 @@ "created_at": "2017-04-14T06:11:41.893Z", "favourited": null, "favourites_count": 0, - "id": 172429, + "id": "172429", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -48,7 +48,7 @@ "following_count": 0, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 42423, + "id": "42423", "locked": false, "note": "", "statuses_count": 2, @@ -60,7 +60,7 @@ "created_at": "2017-04-14T06:10:13.000Z", "favourited": null, "favourites_count": 0, - "id": 172427, + "id": "172427", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -86,7 +86,7 @@ "following_count": 2, "header": "https://mstdn.jp/system/accounts/headers/000/004/834/original/missing.png?1492054575", "header_static": "https://mstdn.jp/system/accounts/headers/000/004/834/original/missing.png?1492054575", - "id": 4834, + "id": "4834", "locked": false, "note": "", "statuses_count": 33, @@ -98,7 +98,7 @@ "created_at": "2017-04-14T06:10:22.000Z", "favourited": null, "favourites_count": 0, - "id": 172426, + "id": "172426", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -124,7 +124,7 @@ "following_count": 0, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 35320, + "id": "35320", "locked": false, "note": "", "statuses_count": 83, @@ -136,7 +136,7 @@ "created_at": "2017-04-14T06:11:36.000Z", "favourited": null, "favourites_count": 0, - "id": 172424, + "id": "172424", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -162,7 +162,7 @@ "following_count": 6, "header": "https://mstdn.jp/system/accounts/headers/000/016/783/original/missing.png?1492105023", "header_static": "https://mstdn.jp/system/accounts/headers/000/016/783/original/missing.png?1492105023", - "id": 16783, + "id": "16783", "locked": false, "note": "https://yukkurisinai.net", "statuses_count": 139, @@ -174,7 +174,7 @@ "created_at": "2017-04-14T06:10:24.000Z", "favourited": null, "favourites_count": 0, - "id": 172422, + "id": "172422", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -200,7 +200,7 @@ "following_count": 10, "header": "https://mstdn.jp/system/accounts/headers/000/005/632/original/babb0abc2698d65a.png?1492064426", "header_static": "https://mstdn.jp/system/accounts/headers/000/005/632/original/babb0abc2698d65a.png?1492064426", - "id": 5632, + "id": "5632", "locked": false, "note": "", "statuses_count": 41, @@ -215,7 +215,7 @@ "created_at": "2017-04-14T06:11:34.868Z", "favourited": null, "favourites_count": 0, - "id": 172421, + "id": "172421", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -241,7 +241,7 @@ "following_count": 38, "header": "https://mstdn.jp/system/accounts/headers/000/009/790/original/ebb9e786c13025fb.jpg?1492082167", "header_static": "https://mstdn.jp/system/accounts/headers/000/009/790/original/ebb9e786c13025fb.jpg?1492082167", - "id": 9790, + "id": "9790", "locked": false, "note": "(´・ω・`)っ https://mstdn.jp/@prateamsy", "statuses_count": 22, @@ -253,7 +253,7 @@ "created_at": "2017-04-14T06:09:37.000Z", "favourited": null, "favourites_count": 0, - "id": 172418, + "id": "172418", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -279,7 +279,7 @@ "following_count": 4, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 19518, + "id": "19518", "locked": false, "note": "https://twitter.com/chrolis", "statuses_count": 61, @@ -291,7 +291,7 @@ "created_at": "2017-04-14T06:11:28.000Z", "favourited": null, "favourites_count": 0, - "id": 172417, + "id": "172417", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -317,7 +317,7 @@ "following_count": 6, "header": "https://mstdn.jp/system/accounts/headers/000/016/783/original/missing.png?1492105023", "header_static": "https://mstdn.jp/system/accounts/headers/000/016/783/original/missing.png?1492105023", - "id": 16783, + "id": "16783", "locked": false, "note": "https://yukkurisinai.net", "statuses_count": 139, @@ -329,7 +329,7 @@ "created_at": "2017-04-14T06:09:28.000Z", "favourited": null, "favourites_count": 0, - "id": 172416, + "id": "172416", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -355,7 +355,7 @@ "following_count": 0, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 40084, + "id": "40084", "locked": false, "note": "", "statuses_count": 1, @@ -367,7 +367,7 @@ "created_at": "2017-04-14T06:11:32.197Z", "favourited": null, "favourites_count": 0, - "id": 172413, + "id": "172413", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -393,7 +393,7 @@ "following_count": 0, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 44964, + "id": "44964", "locked": false, "note": "", "statuses_count": 3, @@ -405,7 +405,7 @@ "created_at": "2017-04-14T05:58:23.000Z", "favourited": null, "favourites_count": 0, - "id": 172411, + "id": "172411", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -431,7 +431,7 @@ "following_count": 0, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 31042, + "id": "31042", "locked": false, "note": "自称 情報系エンジニア。愛知県民。", "statuses_count": 31, @@ -443,7 +443,7 @@ "created_at": "2017-04-14T06:11:29.123Z", "favourited": null, "favourites_count": 0, - "id": 172408, + "id": "172408", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -469,7 +469,7 @@ "following_count": 6, "header": "https://mstdn.jp/system/accounts/headers/000/008/046/original/c5c0c805bfde389d.png?1492083837", "header_static": "https://mstdn.jp/system/accounts/headers/000/008/046/original/c5c0c805bfde389d.png?1492083837", - "id": 8046, + "id": "8046", "locked": false, "note": "MBTI: ENFP765Pro,CAVE,CarSimu,OW.", "statuses_count": 121, @@ -481,7 +481,7 @@ "created_at": "2017-04-14T06:11:28.752Z", "favourited": null, "favourites_count": 0, - "id": 172407, + "id": "172407", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -507,7 +507,7 @@ "following_count": 1, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 41645, + "id": "41645", "locked": false, "note": "", "statuses_count": 4, @@ -519,7 +519,7 @@ "created_at": "2017-04-14T06:11:17.000Z", "favourited": null, "favourites_count": 0, - "id": 172405, + "id": "172405", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -545,7 +545,7 @@ "following_count": 9, "header": "https://mstdn.jp/system/accounts/headers/000/038/584/original/7eba8a66d3c0f5d0.jpg?1492139043", "header_static": "https://mstdn.jp/system/accounts/headers/000/038/584/original/7eba8a66d3c0f5d0.jpg?1492139043", - "id": 38584, + "id": "38584", "locked": false, "note": "おしょーすいと読みます。和尚で切っちゃうと全く違うものになちゃうので注意!エロい絵を描いてます。ツイッター https://twitter.com/kousuke200", "statuses_count": 15, @@ -557,7 +557,7 @@ "created_at": "2017-04-14T06:11:26.977Z", "favourited": null, "favourites_count": 0, - "id": 172404, + "id": "172404", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -583,7 +583,7 @@ "following_count": 15, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 2990, + "id": "2990", "locked": false, "note": "", "statuses_count": 17, @@ -595,7 +595,7 @@ "created_at": "2017-04-14T06:11:26.564Z", "favourited": null, "favourites_count": 0, - "id": 172403, + "id": "172403", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -621,7 +621,7 @@ "following_count": 1, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 42462, + "id": "42462", "locked": false, "note": "前髪ぱっつん至上主義な絵描きもどきデス", "statuses_count": 1, @@ -633,7 +633,7 @@ "created_at": "2017-04-14T06:11:26.581Z", "favourited": null, "favourites_count": 0, - "id": 172402, + "id": "172402", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -659,7 +659,7 @@ "following_count": 1, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 21651, + "id": "21651", "locked": false, "note": "", "statuses_count": 3, @@ -671,7 +671,7 @@ "created_at": "2017-04-14T06:11:25.776Z", "favourited": null, "favourites_count": 0, - "id": 172401, + "id": "172401", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -697,7 +697,7 @@ "following_count": 159, "header": "https://mstdn.jp/system/accounts/headers/000/017/909/original/30324680b6f9121b.jpg?1492097757", "header_static": "https://mstdn.jp/system/accounts/headers/000/017/909/original/30324680b6f9121b.jpg?1492097757", - "id": 17909, + "id": "17909", "locked": false, "note": "I'm a freelance web developer living in Japan.twitter.com/bellflower2015", "statuses_count": 144, @@ -709,7 +709,7 @@ "created_at": "2017-04-14T06:11:25.468Z", "favourited": null, "favourites_count": 0, - "id": 172400, + "id": "172400", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], @@ -735,7 +735,7 @@ "following_count": 12, "header": "https://mstdn.jp/headers/original/missing.png", "header_static": "https://mstdn.jp/headers/original/missing.png", - "id": 11485, + "id": "11485", "locked": false, "note": "", "statuses_count": 28, @@ -747,7 +747,7 @@ "created_at": "2017-04-14T06:11:23.770Z", "favourited": null, "favourites_count": 0, - "id": 172398, + "id": "172398", "in_reply_to_account_id": null, "in_reply_to_id": null, "media_attachments": [], diff --git a/bigbone-rx/src/test/kotlin/social/bigbone/rx/testtool/MockClient.kt b/bigbone-rx/src/test/kotlin/social/bigbone/rx/testtool/MockClient.kt index fb933e6d5..24258981c 100644 --- a/bigbone-rx/src/test/kotlin/social/bigbone/rx/testtool/MockClient.kt +++ b/bigbone-rx/src/test/kotlin/social/bigbone/rx/testtool/MockClient.kt @@ -1,6 +1,5 @@ package social.bigbone.rx.testtool -import com.google.gson.Gson import io.mockk.every import io.mockk.mockk import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -11,6 +10,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody import social.bigbone.MastodonClient object MockClient { + fun mock(jsonName: String, maxId: String? = null, sinceId: String? = null): MastodonClient { val client: MastodonClient = mockk() val response: Response = Response.Builder() @@ -38,9 +38,6 @@ object MockClient { .build() every { client.get(ofType(), any()) } returns response - // mocking function that is internal in MastodonClient - every { client["getSerializer"]() } returns Gson() - return client } } diff --git a/bigbone/build.gradle b/bigbone/build.gradle index 69f05b5d8..3fa31f718 100644 --- a/bigbone/build.gradle +++ b/bigbone/build.gradle @@ -1,11 +1,13 @@ plugins { id 'bigbone.library-conventions' id 'bigbone.integrationtest-conventions' + alias(libs.plugins.kotlin.serialization) } dependencies { api libs.okhttp - api libs.gson + + implementation libs.kotlinx.serialization.json testImplementation libs.junit.jupiter testRuntimeOnly libs.junit.platform.launcher diff --git a/bigbone/src/main/kotlin/social/bigbone/JsonSerializer.kt b/bigbone/src/main/kotlin/social/bigbone/JsonSerializer.kt new file mode 100644 index 000000000..85667703b --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/JsonSerializer.kt @@ -0,0 +1,9 @@ +package social.bigbone + +import kotlinx.serialization.json.Json + +internal val JSON_SERIALIZER: Json = Json { + encodeDefaults = true + ignoreUnknownKeys = true + coerceInputValues = true +} \ No newline at end of file diff --git a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt index 7c743973e..96199fdff 100644 --- a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt +++ b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt @@ -1,6 +1,5 @@ package social.bigbone -import com.google.gson.Gson import okhttp3.HttpUrl import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -53,8 +52,7 @@ import javax.net.ssl.X509TrustManager class MastodonClient private constructor( private val instanceName: String, - private val client: OkHttpClient, - private val gson: Gson + private val client: OkHttpClient ) { private var debug = false private var instanceVersion: String? = null @@ -233,9 +231,6 @@ private constructor( PUT } - @PublishedApi - internal fun getSerializer() = gson - fun getInstanceName() = instanceName fun getInstanceVersion() = instanceVersion @@ -259,7 +254,7 @@ private constructor( addIdempotencyKey: Boolean = false ): MastodonRequest { return MastodonRequest( - { + executor = { when (method) { Method.DELETE -> delete(endpoint, parameters) Method.GET -> get(endpoint, parameters) @@ -268,7 +263,7 @@ private constructor( Method.PUT -> put(endpoint, parameters) } }, - { getSerializer().fromJson(it, T::class.java) } + mapper = { JSON_SERIALIZER.decodeFromString(it) } ) } @@ -285,7 +280,7 @@ private constructor( parameters: Parameters? = null ): MastodonRequest> { return MastodonRequest>( - { + executor = { when (method) { Method.DELETE -> delete(endpoint, parameters) Method.GET -> get(endpoint, parameters) @@ -294,7 +289,7 @@ private constructor( Method.PUT -> put(endpoint, parameters) } }, - { getSerializer().fromJson(it, T::class.java) } + mapper = { JSON_SERIALIZER.decodeFromString(it) } ).toPageable() } @@ -311,7 +306,7 @@ private constructor( parameters: Parameters? = null ): MastodonRequest> { return MastodonRequest( - { + executor = { when (method) { Method.DELETE -> delete(endpoint, parameters) Method.GET -> get(endpoint, parameters) @@ -320,7 +315,7 @@ private constructor( Method.PUT -> put(endpoint, parameters) } }, - { getSerializer().fromJson(it, T::class.java) } + mapper = { JSON_SERIALIZER.decodeFromString(it) } ) } @@ -524,7 +519,6 @@ private constructor( private val instanceName: String ) { private val okHttpClientBuilder = OkHttpClient.Builder() - private val gson = Gson() private var accessToken: String? = null private var debug = false private var scheme = "https" @@ -620,12 +614,11 @@ private constructor( */ private fun getInstanceVersion(): String { try { - val server = NodeInfoClient.retrieveServerInfo(instanceName) - server.software?.let { software -> - if (software.name == "mastodon") { - return software.version - } - } + NodeInfoClient + .retrieveServerInfo(instanceName) + ?.software + ?.takeIf { it.name == "mastodon" } + ?.version } catch (_: BigBoneRequestException) { } @@ -644,8 +637,10 @@ private constructor( return try { val response = versionedInstanceRequest(apiVersion) if (response.isSuccessful) { - val instanceVersion = gson.fromJson(response.body?.string(), InstanceVersion::class.java) - instanceVersion.version + val instanceVersion: InstanceVersion? = response.body?.string()?.let { responseBody: String -> + JSON_SERIALIZER.decodeFromString(responseBody) + } + instanceVersion?.version } else { response.close() null @@ -692,14 +687,13 @@ private constructor( fun build(): MastodonClient { return MastodonClient( - instanceName, - okHttpClientBuilder + instanceName = instanceName, + client = okHttpClientBuilder .addNetworkInterceptor(AuthorizationInterceptor(accessToken)) .readTimeout(readTimeoutSeconds, TimeUnit.SECONDS) .writeTimeout(writeTimeoutSeconds, TimeUnit.SECONDS) .connectTimeout(connectTimeoutSeconds, TimeUnit.SECONDS) - .build(), - gson + .build() ).also { it.debug = debug it.instanceVersion = getInstanceVersion() @@ -710,8 +704,8 @@ private constructor( } private object TrustAllX509TrustManager : X509TrustManager { - override fun checkClientTrusted(chain: Array?, authType: String?) { } - override fun checkServerTrusted(chain: Array?, authType: String?) { } + override fun checkClientTrusted(chain: Array?, authType: String?) {} + override fun checkServerTrusted(chain: Array?, authType: String?) {} override fun getAcceptedIssuers(): Array = arrayOf() } diff --git a/bigbone/src/main/kotlin/social/bigbone/MastodonRequest.kt b/bigbone/src/main/kotlin/social/bigbone/MastodonRequest.kt index a1cddd2ac..e6875939e 100644 --- a/bigbone/src/main/kotlin/social/bigbone/MastodonRequest.kt +++ b/bigbone/src/main/kotlin/social/bigbone/MastodonRequest.kt @@ -1,10 +1,11 @@ package social.bigbone -import com.google.gson.JsonParser +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray import okhttp3.Response import social.bigbone.api.exception.BigBoneRequestException import social.bigbone.extension.toPageable -import java.lang.Exception /** * Represents an HTTP request that is sent to a Mastodon instance, once the [execute] @@ -14,6 +15,7 @@ class MastodonRequest( private val executor: () -> Response, private val mapper: (String) -> Any ) { + /** * SAM interface provided for Java interoperability related to [doOnJson] method. */ @@ -52,23 +54,27 @@ class MastodonRequest( val response = executor() if (response.isSuccessful) { try { - val body = response.body?.string() - val element = JsonParser.parseString(body) - if (element.isJsonObject) { - action(body!!) + val body: String? = response.body?.string() + requireNotNull(body) + + val element: JsonElement = JSON_SERIALIZER.parseToJsonElement(body) + if (element is JsonObject) { + action(body) + return mapper(body) as T + } + + val mappedJsonElements: List = element.jsonArray.map { jsonElement: JsonElement -> + val json = jsonElement.toString() + action(json) + + mapper(json) + } + + return if (isPageable) { + mappedJsonElements.toPageable(response) as T } else { - val list = arrayListOf() - element.asJsonArray.forEach { - val json = it.toString() - action(json) - list.add(mapper(json)) - } - return if (isPageable) { - list.toPageable(response) as T - } else { - list as T - } + mappedJsonElements as T } } catch (e: Exception) { throw BigBoneRequestException("Successful response could not be parsed", e) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt index 12aabc2dd..3f5d33b1f 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt @@ -1,184 +1,187 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a user of Mastodon and their associated profile. * @see Mastodon API Account */ +@Serializable data class Account( /** * The account id. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * The username of the account, not including domain. */ - @SerializedName("username") + @SerialName("username") val username: String = "", /** * The Webfinger account URI. Equal to username for local users, or username@domain for remote users. */ - @SerializedName("acct") + @SerialName("acct") val acct: String = "", /** * The location of the user’s profile page. */ - @SerializedName("url") + @SerialName("url") val url: String = "", /** * The profile’s display name. */ - @SerializedName("display_name") + @SerialName("display_name") val displayName: String = "", /** * The profile’s bio or description. */ - @SerializedName("note") + @SerialName("note") val note: String = "", /** * An image icon that is shown next to statuses and in the profile. */ - @SerializedName("avatar") + @SerialName("avatar") val avatar: String = "", /** * A static version of the avatar. Equal to [avatar] if its value is a static image; different if avatar is an animated GIF. */ - @SerializedName("avatar_static") + @SerialName("avatar_static") val avatarStatic: String = "", /** * An image banner that is shown above the profile and in profile cards. */ - @SerializedName("header") + @SerialName("header") val header: String = "", /** * A static version of the header. Equal to [header] if its value is a static image; different if header is an animated GIF. */ - @SerializedName("header_static") + @SerialName("header_static") val headerStatic: String = "", /** * Whether the account manually approves follow requests. */ - @SerializedName("locked") + @SerialName("locked") val isLocked: Boolean = false, /** * Additional metadata attached to a profile as name-value pairs. */ - @SerializedName("fields") + @SerialName("fields") val fields: List = emptyList(), /** * Custom emoji entities to be used when rendering the profile. */ - @SerializedName("emojis") + @SerialName("emojis") val emojis: List = emptyList(), /** * Indicates that the account may perform automated actions, may not be monitored, or identifies as a robot. */ - @SerializedName("bot") + @SerialName("bot") val isBot: Boolean = false, /** * Indicates that the account represents a Group actor. */ - @SerializedName("group") + @SerialName("group") val isGroup: Boolean = false, /** * Whether the account has opted into discovery features such as the profile directory. */ - @SerializedName("discoverable") + @SerialName("discoverable") val isDiscoverable: Boolean? = null, /** * Whether the local user has opted out of being indexed by search engines. */ - @SerializedName("noindex") + @SerialName("noindex") val isNotIndexed: Boolean? = null, /** * Indicates that the profile is currently inactive and that its user has moved to a new account. */ - @SerializedName("moved") + @SerialName("moved") val moved: Account? = null, /** * An extra attribute returned only when an account is suspended. */ - @SerializedName("suspended") + @SerialName("suspended") val isSuspended: Boolean? = null, /** * An extra attribute returned only when an account is silenced. * If true, indicates that the account should be hidden behind a warning screen. */ - @SerializedName("limited") + @SerialName("limited") val isLimited: Boolean? = null, /** * When the account was created (ISO 8601 Datetime). */ - @SerializedName("created_at") + @SerialName("created_at") val createdAt: String = "", /** * When the most recent status was posted (ISO 8601 Datetime). */ - @SerializedName("last_status_at") + @SerialName("last_status_at") val lastStatusAt: String? = null, /** * How many statuses are attached to this account. */ - @SerializedName("statuses_count") + @SerialName("statuses_count") val statusesCount: Long = 0, /** * The reported followers of this profile. */ - @SerializedName("followers_count") + @SerialName("followers_count") val followersCount: Long = 0, /** * The reported follows of this profile. */ - @SerializedName("following_count") + @SerialName("following_count") val followingCount: Long = 0 ) { /** * Specifies a name-value pair as used in [fields] of the [Account] entity. */ + @Serializable data class Field( /** * The key of a given field’s key-value pair. */ - @SerializedName("name") + @SerialName("name") val name: String = "", /** * The value associated with the [name] key (HTML string). */ - @SerializedName("value") + @SerialName("value") val value: String = "", /** * Timestamp of when the server verified a URL value for a rel=“me” link. * ISO 8601 Datetime string if [value] is a verified URL. Otherwise, null. */ - @SerializedName("verified_at") + @SerialName("verified_at") val verifiedAt: String? = null ) } diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Application.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Application.kt index 4ddbaab9e..bfbbb4e0d 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Application.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Application.kt @@ -1,39 +1,41 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents an application that interfaces with the REST API to access accounts or post statuses. * @see Mastodon API Application */ +@Serializable data class Application( /** * The name of your application. */ - @SerializedName("name") + @SerialName("name") val name: String = "", /** * The website associated with your application. */ - @SerializedName("website") + @SerialName("website") val website: String? = null, /** * Used for Push Streaming API. Returned with POST /api/v1/apps. Equivalent to [WebPushSubscription#serverKey]. */ - @SerializedName("vapid_key") + @SerialName("vapid_key") val vapidKey: String = "", /** * Client ID key, to be used for obtaining OAuth tokens. */ - @SerializedName("client_id") + @SerialName("client_id") val clientId: String? = null, /** * Client secret key, to be used for obtaining OAuth tokens. */ - @SerializedName("client_secret") + @SerialName("client_secret") val clientSecret: String? = null ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Context.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Context.kt index 16bbeb238..cfb928b64 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Context.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Context.kt @@ -1,21 +1,23 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents the tree around a given status. Used for reconstructing threads of statuses. * @see Mastodon API Context */ +@Serializable data class Context( /** * Parents in the thread. */ - @SerializedName("ancestors") + @SerialName("ancestors") val ancestors: List = emptyList(), /** * Children in the thread. */ - @SerializedName("descendants") + @SerialName("descendants") val descendants: List = emptyList() ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Conversation.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Conversation.kt index a45b14ca0..52649bed1 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Conversation.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Conversation.kt @@ -1,33 +1,35 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a conversation with "direct message" visibility. * @see Mastodon API Conversation */ +@Serializable data class Conversation( /** * The ID of the conversation in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * Is the conversation currently marked as unread? */ - @SerializedName("unread") + @SerialName("unread") val unread: Boolean = true, /** * Participants in the conversation. */ - @SerializedName("accounts") + @SerialName("accounts") val accounts: List? = emptyList(), /** * The last status in the conversation. */ - @SerializedName("last_status") + @SerialName("last_status") val lastStatus: Status? = null, ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/CustomEmoji.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/CustomEmoji.kt index 0b2398cca..6e1d3874f 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/CustomEmoji.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/CustomEmoji.kt @@ -1,36 +1,38 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a custom emoji. * @see Mastodon API CustomEmoji */ +@Serializable data class CustomEmoji( - @SerializedName("shortcode") + @SerialName("shortcode") val shortcode: String = "", /** * A link to a static copy of the custom emoji. */ - @SerializedName("static_url") + @SerialName("static_url") val staticUrl: String = "", /** * A link to the custom emoji. */ - @SerializedName("url") + @SerialName("url") val url: String = "", /** * Whether this Emoji should be visible in the picker or unlisted. */ - @SerializedName("visible_in_picker") + @SerialName("visible_in_picker") val visibleInPicker: Boolean = true, /** * Used for sorting custom emoji in the picker. */ - @SerializedName("category") + @SerialName("category") val category: String = "" ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Error.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Error.kt index 663d188fc..99b19fa83 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Error.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Error.kt @@ -1,21 +1,23 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents an error message. * @see Mastodon API Error */ +@Serializable data class Error( /** * The error message. */ - @SerializedName("error") + @SerialName("error") val error: String = "", /** * A longer description of the error, mainly provided with the OAuth API. */ - @SerializedName("error_description") + @SerialName("error_description") val errorDescription: String? = null ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/FeaturedTag.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/FeaturedTag.kt index 00f1df067..f99ae00a4 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/FeaturedTag.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/FeaturedTag.kt @@ -1,39 +1,41 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a hashtag that is featured on a profile. * @see Mastodon API FeaturedTag */ +@Serializable data class FeaturedTag( /** * The internal ID of the featured tag in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "", /** * The name of the hashtag being featured. */ - @SerializedName("name") + @SerialName("name") val name: String = "", /** * A link to all statuses by a user that contain this hashtag. */ - @SerializedName("url") + @SerialName("url") val url: String = "", /** * A link to all statuses by a user that contain this hashtag. */ - @SerializedName("statuses_count") + @SerialName("statuses_count") val statusesCount: Int = 0, /** * The timestamp of the last authored status containing this hashtag. */ - @SerializedName("last_status_at") + @SerialName("last_status_at") val lastStatusAt: String = "" ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Filter.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Filter.kt index ac401747c..b63841683 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Filter.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Filter.kt @@ -1,54 +1,56 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a user-defined filter for determining which statuses should not be shown to the user. * @see Mastodon API Filter */ +@Serializable data class Filter( /** * The ID of the Filter in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * A title given by the user to name the filter. */ - @SerializedName("title") + @SerialName("title") val title: String = "", /** * The contexts in which the filter should be applied. * @see Context */ - @SerializedName("context") + @SerialName("context") val context: List = emptyList(), /** * When the filter should no longer be applied. */ - @SerializedName("expires_at") + @SerialName("expires_at") val expiresAt: String? = null, /** * The action to be taken when a status matches this filter. * @see Action */ - @SerializedName("filter_action") + @SerialName("filter_action") val filterAction: String = Action.Warn.value, /** * The keywords grouped under this filter. */ - @SerializedName("keywords") + @SerialName("keywords") val keywords: List = emptyList(), /** * The statuses grouped under this filter. */ - @SerializedName("statuses") + @SerialName("statuses") val statuses: List = emptyList() ) { /** diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterKeyword.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterKeyword.kt index 4ca60892e..61ac6969a 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterKeyword.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterKeyword.kt @@ -1,28 +1,30 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a keyword that, if matched, should cause the filter action to be taken. * @see Mastodon API FilterKeyword */ +@Serializable data class FilterKeyword( /** * The ID of the FilterKeyword in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * The phrase to be matched against. */ - @SerializedName("keyword") + @SerialName("keyword") val keyword: String = "", /** * Should the filter consider word boundaries? * @see implementation guidelines for filters */ - @SerializedName("whole_word") + @SerialName("whole_word") val wholeWord: Boolean = false ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterResult.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterResult.kt index 0b01d2b88..16ef3aa78 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterResult.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterResult.kt @@ -1,27 +1,29 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a filter whose keywords matched a given status. * @see Mastodon API FilterResult */ +@Serializable data class FilterResult( /** * The filter that was matched. */ - @SerializedName("filter") + @SerialName("filter") val filter: Filter, /** * The keyword within the filter that was matched. */ - @SerializedName("keyword_matches") + @SerialName("keyword_matches") val keywordMatches: List? = null, /** * The status ID within the filter that was matched. */ - @SerializedName("status_matches") + @SerialName("status_matches") val statusMatches: String? = null ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterStatus.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterStatus.kt index 2f1c204ec..74014a790 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterStatus.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/FilterStatus.kt @@ -1,21 +1,23 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a status ID that, if matched, should cause the filter action to be taken. * @see Mastodon API FilterStatus */ +@Serializable data class FilterStatus( /** * The ID of the FilterStatus in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * The ID of the Status that will be filtered. */ - @SerializedName("status_id") + @SerialName("status_id") val statusId: String = "" ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt index 594ec4177..1cb68fa2e 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt @@ -1,117 +1,120 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents the software instance of Mastodon running on this domain. * @see Mastodon API V1::Instance */ +@Serializable data class Instance( /** * The domain name of the instance. */ - @SerializedName("uri") + @SerialName("uri") val uri: String = "", /** * The title of the website. */ - @SerializedName("title") + @SerialName("title") val title: String = "", /** * A short, plain-text description defined by the admin. */ - @SerializedName("short_description") + @SerialName("short_description") val shortDescription: String = "", /** * An HTML-permitted description of the Mastodon site. */ - @SerializedName("description") + @SerialName("description") val description: String = "", /** * An email that may be contacted for any inquiries. */ - @SerializedName("email") + @SerialName("email") val email: String = "", /** * The version of Mastodon installed on the instance. */ - @SerializedName("version") + @SerialName("version") val version: String = "", /** * URLs of interest for clients apps. */ - @SerializedName("urls") + @SerialName("urls") val urls: Urls? = null, /** * Statistics about how much information the instance contains. */ - @SerializedName("stats") + @SerialName("stats") val stats: Stats? = null, /** * Banner image for the website. */ - @SerializedName("thumbnail") + @SerialName("thumbnail") val thumbnail: String = "", /** * Primary languages of the website and its staff. */ - @SerializedName("languages") + @SerialName("languages") val languages: List = emptyList(), /** * Whether registrations are enabled. */ - @SerializedName("registrations") + @SerialName("registrations") val registrations: Boolean = false, /** * Whether registrations require moderator approval. */ - @SerializedName("approval_required") + @SerialName("approval_required") val approvalRequired: Boolean = false, /** * Whether invites are enabled. */ - @SerializedName("invites_enabled") + @SerialName("invites_enabled") val invitesEnabled: Boolean = false, /** * Configured values and limits for this website. */ - @SerializedName("configuration") + @SerialName("configuration") val configuration: Configuration? = null, /** * A user that can be contacted, as an alternative to email. */ - @SerializedName("contact_account") + @SerialName("contact_account") val contactAccount: Account? = null, /** * An itemized list of rules for this website. */ - @SerializedName("rules") + @SerialName("rules") val rules: List? = null ) { /** * URLs of interest for clients apps. * @see Mastodon API V1::Instance */ + @Serializable data class Urls( /** * The Websockets URL for connecting to the streaming API. */ - @SerializedName("streaming_api") + @SerialName("streaming_api") val streamingApi: String = "" ) @@ -119,23 +122,24 @@ data class Instance( * Statistics about how much information the instance contains. * @see Mastodon API V1::Instance */ + @Serializable data class Stats( /** * Total users on this instance. */ - @SerializedName("user_count") + @SerialName("user_count") val userCount: Long = 0, /** * Total statuses on this instance. */ - @SerializedName("status_count") + @SerialName("status_count") val statusCount: Long = 0, /** * Total domains discovered by this instance. */ - @SerializedName("domain_count") + @SerialName("domain_count") val domainCount: Long = 0 ) @@ -143,40 +147,42 @@ data class Instance( * Configured values and limits for this website. * @see Mastodon API V1::Instance */ + @Serializable data class Configuration( /** * Limits related to accounts. */ - @SerializedName("accounts") + @SerialName("accounts") val accounts: Accounts? = null, /** * Limits related to authoring statuses. */ - @SerializedName("statuses") + @SerialName("statuses") val statuses: Statuses? = null, /** * Hints for which attachments will be accepted. */ - @SerializedName("media_attachments") + @SerialName("media_attachments") val mediaAttachments: MediaAttachments? = null, /** * Limits related to polls. */ - @SerializedName("polls") + @SerialName("polls") val polls: Polls? = null ) { /** * Limits related to accounts. * @see Mastodon API V1::Instance */ + @Serializable data class Accounts( /** * The maximum number of featured tags allowed for each account. */ - @SerializedName("max_featured_tags") + @SerialName("max_featured_tags") val maxFeaturedTags: Int = 0 ) @@ -184,23 +190,24 @@ data class Instance( * Limits related to authoring statuses. * @see Mastodon API V1::Instance */ + @Serializable data class Statuses( /** * The maximum number of allowed characters per status. */ - @SerializedName("max_characters") + @SerialName("max_characters") val maxCharacters: Int = 0, /** * The maximum number of media attachments that can be added to a status. */ - @SerializedName("max_media_attachments") + @SerialName("max_media_attachments") val maxMediaAttachments: Int = 0, /** * Each URL in a status will be assumed to be exactly this many characters. */ - @SerializedName("characters_reserved_per_url") + @SerialName("characters_reserved_per_url") val charactersReservedPerUrl: Int = 0 ) @@ -208,41 +215,42 @@ data class Instance( * Hints for which attachments will be accepted. * @see Mastodon API V1::Instance */ + @Serializable data class MediaAttachments( /** * Contains MIME types that can be uploaded. */ - @SerializedName("supported_mime_types") + @SerialName("supported_mime_types") val supportedMimeTypes: List = emptyList(), /** * The maximum size of any uploaded image, in bytes. */ - @SerializedName("image_size_limit") + @SerialName("image_size_limit") val imageSizeLimit: Int = 0, /** * The maximum number of pixels (width times height) for image uploads. */ - @SerializedName("image_matrix_limit") + @SerialName("image_matrix_limit") val imageMatrixLimit: Int = 0, /** * The maximum size of any uploaded video, in bytes. */ - @SerializedName("video_size_limit") + @SerialName("video_size_limit") val videoSizeLimit: Int = 0, /** * The maximum frame rate for any uploaded video. */ - @SerializedName("video_frame_rate_limit") + @SerialName("video_frame_rate_limit") val videoFrameRateLimit: Int = 0, /** * The maximum number of pixels (width times height) for video uploads. */ - @SerializedName("video_matrix_limit") + @SerialName("video_matrix_limit") val videoMatrixLimit: Int = 0 ) @@ -250,29 +258,30 @@ data class Instance( * Limits related to polls. * @see Mastodon API V1::Instance */ + @Serializable data class Polls( /** * Each poll is allowed to have up to this many options. */ - @SerializedName("max_options") + @SerialName("max_options") val maxOptions: Int = 0, /** * Each poll option is allowed to have this many characters. */ - @SerializedName("max_characters_per_option") + @SerialName("max_characters_per_option") val maxCharactersPerOption: Int = 0, /** * The shortest allowed poll duration, in seconds. */ - @SerializedName("min_expiration") + @SerialName("min_expiration") val minExpiration: Int = 0, /** * The longest allowed poll duration, in seconds. */ - @SerializedName("max_expiration") + @SerialName("max_expiration") val maxExpiration: Int = 0 ) } diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Marker.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Marker.kt index 7c28a4625..aadb26e0f 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Marker.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Marker.kt @@ -1,27 +1,29 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents the last read position within a specific timeline of the user. * @see Mastodon API Marker */ +@Serializable data class Marker( /** * The ID of the most recently viewed entity. */ - @SerializedName("last_read_id") + @SerialName("last_read_id") val lastReadId: String = "0", /** * An incrementing counter, used for locking to prevent write conflicts. */ - @SerializedName("version") + @SerialName("version") val version: Int = 0, /** * The timestamp of when the marker was set (ISO 8601 Datetime). */ - @SerializedName("updated_at") + @SerialName("updated_at") val updatedAt: String = "", ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Markers.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Markers.kt index d68d447da..c54ea7fec 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Markers.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Markers.kt @@ -1,21 +1,23 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents the last read position within a user's timelines. * @see Mastodon API Marker */ +@Serializable data class Markers( /** * Represents the last read position within a user's notification timeline. */ - @SerializedName("notifications") + @SerialName("notifications") val notifications: Marker? = null, /** * Represents the last read position within a user's home timeline. */ - @SerializedName("home") + @SerialName("home") val home: Marker? = null ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/MastodonList.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/MastodonList.kt index 938b8fd46..da095e420 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/MastodonList.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/MastodonList.kt @@ -1,29 +1,31 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a list of some users that the authenticated user follows. * @see Mastodon API List */ +@Serializable data class MastodonList( /** * The internal database ID of the list. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * The user-defined title of the list. */ - @SerializedName("title") + @SerialName("title") val title: String = "", /** * Which replies should be shown in the list. * @see [RepliesPolicy] */ - @SerializedName("replies_policy") + @SerialName("replies_policy") val repliesPolicy: String = RepliesPolicy.List.value, ) { /** diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/MediaAttachment.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/MediaAttachment.kt index 4d80fa760..a7e42246d 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/MediaAttachment.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/MediaAttachment.kt @@ -1,75 +1,78 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import social.bigbone.api.entity.data.Focus /** * Represents a file or media attachment that can be added to a status. * @see Mastodon API MediaAttachment */ +@Serializable data class MediaAttachment( /** * The ID of the attachment in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * The type of the attachment. */ - @SerializedName("type") + @SerialName("type") val type: String = Type.Image.value, /** * The location of the original full-size attachment. */ - @SerializedName("url") + @SerialName("url") val url: String = "", /** * The location of the full-size original attachment on the remote website. */ - @SerializedName("remote_url") + @SerialName("remote_url") val remoteUrl: String? = null, /** * The location of a scaled-down preview of the attachment. */ - @SerializedName("preview_url") + @SerialName("preview_url") val previewUrl: String = "", /** * A shorter URL for the attachment. */ - @SerializedName("text_url") + @SerialName("text_url") val textUrl: String? = null, /** * Metadata returned by Paperclip. */ - @SerializedName("meta") + @SerialName("meta") val meta: Meta? = null, /** * Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load. */ - @SerializedName("description") + @SerialName("description") val description: String? = null, /** * A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. */ - @SerializedName("blurhash") + @SerialName("blurhash") val blurhash: String? = null ) { /** * Metadata returned by Paperclip. */ + @Serializable data class Meta( /** * Contains the coordinates to be used for smart thumbnail cropping. */ - @SerializedName("focus") + @SerialName("focus") val focus: Focus? = null, ) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Notification.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Notification.kt index 7a599a84b..a6a75da93 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Notification.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Notification.kt @@ -1,46 +1,48 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a notification of an event relevant to the user. * @see Mastodon API Notification */ +@Serializable data class Notification( /** * The id of the notification in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "0", /** * The type of event that resulted in the notification. */ - @SerializedName("type") + @SerialName("type") val type: String = Type.Mention.value, /** * The timestamp of the notification (ISO 8601 Datetime). */ - @SerializedName("created_at") + @SerialName("created_at") val createdAt: String = "", /** * The account that performed the action that generated the notification. */ - @SerializedName("account") + @SerialName("account") val account: Account? = null, /** * Status that was the object of the notification. Attached when type of the notification is favourite, reblog, status, mention, poll, or update. */ - @SerializedName("status") + @SerialName("status") val status: Status? = null, /** * Report that was the object of the notification. Attached when type of the notification is admin.report. */ - @SerializedName("report") + @SerialName("report") val report: Report? = null ) { /** diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Poll.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Poll.kt index d6bfed12f..10f7c403c 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Poll.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Poll.kt @@ -1,70 +1,72 @@ package social.bigbone.api.entity -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * Represents a poll attached to a status. * @see Mastodon API Poll */ +@Serializable data class Poll( /** * The ID of the poll in the database. */ - @SerializedName("id") + @SerialName("id") val id: String = "", /** * When the poll ends. ISO 8601 Datetime string, or null if the poll does not end. */ - @SerializedName("expires_at") + @SerialName("expires_at") val expiresAt: String? = null, /** * Is the poll currently expired? */ - @SerializedName("expired") + @SerialName("expired") val expired: Boolean = false, /** * Does the poll allow multiple-choice answers? */ - @SerializedName("multiple") + @SerialName("multiple") val multiple: Boolean = false, /** * How many votes have been received. */ - @SerializedName("votes_count") + @SerialName("votes_count") val votesCount: Long = 0, /** * How many unique accounts have voted on a multiple-choice poll; integer, or null if [multiple] is false. */ - @SerializedName("voters_count") + @SerialName("voters_count") val votersCount: Long? = null, /** * Possible answers for the poll. */ - @SerializedName("options") + @SerialName("options") val options: List