M husky/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt => husky/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +265 -220
@@ 1,21 1,53 @@
-/* Copyright 2017 Andrew Dawson
+/*
+ * Husky -- A Pleroma client for Android
*
- * This file is a part of Tusky.
+ * Copyright (C) 2021 The Husky Developers
+ * Copyright (C) 2017 Alibek "a1batross" Omarov
*
- * 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.
+ * 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.
*
- * Tusky 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.
+ * This program 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 Tusky; if not,
- * see <http://www.gnu.org/licenses>. */
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
package com.keylesspalace.tusky.network
-import com.keylesspalace.tusky.entity.*
+import com.keylesspalace.tusky.entity.AccessToken
+import com.keylesspalace.tusky.entity.Account
+import com.keylesspalace.tusky.entity.Announcement
+import com.keylesspalace.tusky.entity.AppCredentials
+import com.keylesspalace.tusky.entity.Attachment
+import com.keylesspalace.tusky.entity.Chat
+import com.keylesspalace.tusky.entity.ChatMessage
+import com.keylesspalace.tusky.entity.Conversation
+import com.keylesspalace.tusky.entity.DeletedStatus
+import com.keylesspalace.tusky.entity.Emoji
+import com.keylesspalace.tusky.entity.EmojiReaction
+import com.keylesspalace.tusky.entity.Filter
+import com.keylesspalace.tusky.entity.IdentityProof
+import com.keylesspalace.tusky.entity.Instance
+import com.keylesspalace.tusky.entity.Marker
+import com.keylesspalace.tusky.entity.MastoList
+import com.keylesspalace.tusky.entity.NewChatMessage
+import com.keylesspalace.tusky.entity.NewStatus
+import com.keylesspalace.tusky.entity.NodeInfo
+import com.keylesspalace.tusky.entity.NodeInfoLinks
+import com.keylesspalace.tusky.entity.Notification
+import com.keylesspalace.tusky.entity.Poll
+import com.keylesspalace.tusky.entity.Relationship
+import com.keylesspalace.tusky.entity.ScheduledStatus
+import com.keylesspalace.tusky.entity.SearchResult
+import com.keylesspalace.tusky.entity.Status
+import com.keylesspalace.tusky.entity.StatusContext
+import com.keylesspalace.tusky.entity.StickerPack
import io.reactivex.Completable
import io.reactivex.Single
import okhttp3.MultipartBody
@@ 23,8 55,21 @@ import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.Response
-import retrofit2.http.*
+import retrofit2.http.Body
+import retrofit2.http.DELETE
import retrofit2.http.Field
+import retrofit2.http.FormUrlEncoded
+import retrofit2.http.GET
+import retrofit2.http.HTTP
+import retrofit2.http.Header
+import retrofit2.http.Multipart
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.PUT
+import retrofit2.http.Part
+import retrofit2.http.Path
+import retrofit2.http.Query
+import retrofit2.http.Url
/**
* for documentation of the Mastodon REST API see https://docs.joinmastodon.org/api/
@@ 53,66 98,66 @@ interface MastodonApi {
@GET("api/v1/timelines/home?with_muted=true")
fun homeTimeline(
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?
): Call<List<Status>>
@GET("api/v1/timelines/home?with_muted=true")
fun homeTimelineSingle(
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?
): Single<List<Status>>
@GET("api/v1/timelines/public?with_muted=true")
fun publicTimeline(
- @Query("local") local: Boolean?,
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?
+ @Query("local") local: Boolean?,
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?
): Call<List<Status>>
@GET("api/v1/timelines/tag/{hashtag}?with_muted=true")
fun hashtagTimeline(
- @Path("hashtag") hashtag: String,
- @Query("any[]") any: List<String>?,
- @Query("local") local: Boolean?,
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?
+ @Path("hashtag") hashtag: String,
+ @Query("any[]") any: List<String>?,
+ @Query("local") local: Boolean?,
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?
): Call<List<Status>>
@GET("api/v1/timelines/list/{listId}?with_muted=true")
fun listTimeline(
- @Path("listId") listId: String,
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?
+ @Path("listId") listId: String,
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?
): Call<List<Status>>
@GET("api/v1/notifications")
fun notifications(
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?,
- @Query("exclude_types[]") excludes: Set<Notification.Type>?,
- @Query("with_muted") withMuted: Boolean?
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?,
+ @Query("exclude_types[]") excludes: Set<Notification.Type>?,
+ @Query("with_muted") withMuted: Boolean?
): Call<List<Notification>>
@GET("api/v1/markers")
fun markersWithAuth(
- @Header("Authorization") auth: String,
- @Header(DOMAIN_HEADER) domain: String,
- @Query("timeline[]") timelines: List<String>
+ @Header("Authorization") auth: String,
+ @Header(DOMAIN_HEADER) domain: String,
+ @Query("timeline[]") timelines: List<String>
): Single<Map<String, Marker>>
@GET("api/v1/notifications?with_muted=true")
fun notificationsWithAuth(
- @Header("Authorization") auth: String,
- @Header(DOMAIN_HEADER) domain: String,
- @Query("since_id") sinceId: String?,
- @Query("include_types[]") includeTypes: List<String>?
+ @Header("Authorization") auth: String,
+ @Header(DOMAIN_HEADER) domain: String,
+ @Query("since_id") sinceId: String?,
+ @Query("include_types[]") includeTypes: List<String>?
): Single<List<Notification>>
@POST("api/v1/notifications/clear")
@@ 120,122 165,122 @@ interface MastodonApi {
@GET("api/v1/notifications/{id}")
fun notification(
- @Path("id") notificationId: String
+ @Path("id") notificationId: String
): Call<Notification>
@Multipart
@POST("api/v1/media")
fun uploadMedia(
- @Part file: MultipartBody.Part,
- @Part description: MultipartBody.Part? = null
+ @Part file: MultipartBody.Part,
+ @Part description: MultipartBody.Part? = null
): Single<Attachment>
@FormUrlEncoded
@PUT("api/v1/media/{mediaId}")
fun updateMedia(
- @Path("mediaId") mediaId: String,
- @Field("description") description: String
+ @Path("mediaId") mediaId: String,
+ @Field("description") description: String
): Single<Attachment>
@POST("api/v1/statuses")
fun createStatus(
- @Header("Authorization") auth: String,
- @Header(DOMAIN_HEADER) domain: String,
- @Header("Idempotency-Key") idempotencyKey: String,
- @Body status: NewStatus
+ @Header("Authorization") auth: String,
+ @Header(DOMAIN_HEADER) domain: String,
+ @Header("Idempotency-Key") idempotencyKey: String,
+ @Body status: NewStatus
): Call<Status>
@GET("api/v1/statuses/{id}")
fun status(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Call<Status>
@GET("api/v1/statuses/{id}")
fun statusSingle(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@GET("api/v1/statuses/{id}/context")
fun statusContext(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Call<StatusContext>
@GET("api/v1/statuses/{id}/reblogged_by")
fun statusRebloggedBy(
- @Path("id") statusId: String,
- @Query("max_id") maxId: String?
+ @Path("id") statusId: String,
+ @Query("max_id") maxId: String?
): Single<Response<List<Account>>>
@GET("api/v1/statuses/{id}/favourited_by")
fun statusFavouritedBy(
- @Path("id") statusId: String,
- @Query("max_id") maxId: String?
+ @Path("id") statusId: String,
+ @Query("max_id") maxId: String?
): Single<Response<List<Account>>>
@DELETE("api/v1/statuses/{id}")
fun deleteStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<DeletedStatus>
@POST("api/v1/statuses/{id}/reblog")
fun reblogStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@POST("api/v1/statuses/{id}/unreblog")
fun unreblogStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@POST("api/v1/statuses/{id}/favourite")
fun favouriteStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@POST("api/v1/statuses/{id}/unfavourite")
fun unfavouriteStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@POST("api/v1/statuses/{id}/bookmark")
fun bookmarkStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@POST("api/v1/statuses/{id}/unbookmark")
fun unbookmarkStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@POST("api/v1/statuses/{id}/pin")
fun pinStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@POST("api/v1/statuses/{id}/unpin")
fun unpinStatus(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
-
+
@POST("api/v1/statuses/{id}/mute")
fun muteConversation(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
-
+
@POST("api/v1/statuses/{id}/unmute")
fun unmuteConversation(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
-
+
@GET("api/v1/scheduled_statuses")
fun scheduledStatuses(
- @Query("limit") limit: Int? = null,
- @Query("max_id") maxId: String? = null
+ @Query("limit") limit: Int? = null,
+ @Query("max_id") maxId: String? = null
): Single<List<ScheduledStatus>>
@DELETE("api/v1/scheduled_statuses/{id}")
fun deleteScheduledStatus(
- @Path("id") scheduledStatusId: String
+ @Path("id") scheduledStatusId: String
): Single<ResponseBody>
@GET("api/v1/accounts/verify_credentials")
@@ 244,39 289,39 @@ interface MastodonApi {
@FormUrlEncoded
@PATCH("api/v1/accounts/update_credentials")
fun accountUpdateSource(
- @Field("source[privacy]") privacy: String?,
- @Field("source[sensitive]") sensitive: Boolean?
+ @Field("source[privacy]") privacy: String?,
+ @Field("source[sensitive]") sensitive: Boolean?
): Call<Account>
@Multipart
@PATCH("api/v1/accounts/update_credentials")
fun accountUpdateCredentials(
- @Part(value = "display_name") displayName: RequestBody?,
- @Part(value = "note") note: RequestBody?,
- @Part(value = "locked") locked: RequestBody?,
- @Part avatar: MultipartBody.Part?,
- @Part header: MultipartBody.Part?,
- @Part(value = "fields_attributes[0][name]") fieldName0: RequestBody?,
- @Part(value = "fields_attributes[0][value]") fieldValue0: RequestBody?,
- @Part(value = "fields_attributes[1][name]") fieldName1: RequestBody?,
- @Part(value = "fields_attributes[1][value]") fieldValue1: RequestBody?,
- @Part(value = "fields_attributes[2][name]") fieldName2: RequestBody?,
- @Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?,
- @Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?,
- @Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody?
+ @Part(value = "display_name") displayName: RequestBody?,
+ @Part(value = "note") note: RequestBody?,
+ @Part(value = "locked") locked: RequestBody?,
+ @Part avatar: MultipartBody.Part?,
+ @Part header: MultipartBody.Part?,
+ @Part(value = "fields_attributes[0][name]") fieldName0: RequestBody?,
+ @Part(value = "fields_attributes[0][value]") fieldValue0: RequestBody?,
+ @Part(value = "fields_attributes[1][name]") fieldName1: RequestBody?,
+ @Part(value = "fields_attributes[1][value]") fieldValue1: RequestBody?,
+ @Part(value = "fields_attributes[2][name]") fieldName2: RequestBody?,
+ @Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?,
+ @Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?,
+ @Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody?
): Call<Account>
@GET("api/v1/accounts/search")
fun searchAccounts(
- @Query("q") query: String,
- @Query("resolve") resolve: Boolean? = null,
- @Query("limit") limit: Int? = null,
- @Query("following") following: Boolean? = null
+ @Query("q") query: String,
+ @Query("resolve") resolve: Boolean? = null,
+ @Query("limit") limit: Int? = null,
+ @Query("following") following: Boolean? = null
): Single<List<Account>>
@GET("api/v1/accounts/{id}")
fun account(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Account>
/**
@@ 290,78 335,78 @@ interface MastodonApi {
*/
@GET("api/v1/accounts/{id}/statuses?with_muted=true")
fun accountStatuses(
- @Path("id") accountId: String,
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?,
- @Query("exclude_replies") excludeReplies: Boolean?,
- @Query("only_media") onlyMedia: Boolean?,
- @Query("pinned") pinned: Boolean?
+ @Path("id") accountId: String,
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?,
+ @Query("exclude_replies") excludeReplies: Boolean?,
+ @Query("only_media") onlyMedia: Boolean?,
+ @Query("pinned") pinned: Boolean?
): Call<List<Status>>
@GET("api/v1/accounts/{id}/followers")
fun accountFollowers(
- @Path("id") accountId: String,
- @Query("max_id") maxId: String?
+ @Path("id") accountId: String,
+ @Query("max_id") maxId: String?
): Single<Response<List<Account>>>
@GET("api/v1/accounts/{id}/following")
fun accountFollowing(
- @Path("id") accountId: String,
- @Query("max_id") maxId: String?
+ @Path("id") accountId: String,
+ @Query("max_id") maxId: String?
): Single<Response<List<Account>>>
@FormUrlEncoded
@POST("api/v1/accounts/{id}/follow")
fun followAccount(
- @Path("id") accountId: String,
- @Field("reblogs") showReblogs: Boolean? = null,
- @Field("notify") notify: Boolean? = null
+ @Path("id") accountId: String,
+ @Field("reblogs") showReblogs: Boolean? = null,
+ @Field("notify") notify: Boolean? = null
): Single<Relationship>
@POST("api/v1/accounts/{id}/unfollow")
fun unfollowAccount(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Relationship>
@POST("api/v1/accounts/{id}/block")
fun blockAccount(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Relationship>
@POST("api/v1/accounts/{id}/unblock")
fun unblockAccount(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Relationship>
@FormUrlEncoded
@POST("api/v1/accounts/{id}/mute")
fun muteAccount(
- @Path("id") accountId: String,
- @Field("notifications") notifications: Boolean? = null,
- @Field("duration") duration: Int? = null
+ @Path("id") accountId: String,
+ @Field("notifications") notifications: Boolean? = null,
+ @Field("duration") duration: Int? = null
): Single<Relationship>
@POST("api/v1/accounts/{id}/unmute")
fun unmuteAccount(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Relationship>
@GET("api/v1/accounts/relationships")
fun relationships(
- @Query("id[]") accountIds: List<String>
+ @Query("id[]") accountIds: List<String>
): Single<List<Relationship>>
@GET("api/v1/accounts/{id}/identity_proofs")
fun identityProofs(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<List<IdentityProof>>
-
+
@POST("api/v1/pleroma/accounts/{id}/subscribe")
fun subscribeAccount(
@Path("id") accountId: String
): Single<Relationship>
-
+
@POST("api/v1/pleroma/accounts/{id}/unsubscribe")
fun unsubscribeAccount(
@Path("id") accountId: String
@@ 369,25 414,25 @@ interface MastodonApi {
@GET("api/v1/blocks")
fun blocks(
- @Query("max_id") maxId: String?
+ @Query("max_id") maxId: String?
): Single<Response<List<Account>>>
@GET("api/v1/mutes")
fun mutes(
- @Query("max_id") maxId: String?
+ @Query("max_id") maxId: String?
): Single<Response<List<Account>>>
@GET("api/v1/domain_blocks")
fun domainBlocks(
- @Query("max_id") maxId: String? = null,
- @Query("since_id") sinceId: String? = null,
- @Query("limit") limit: Int? = null
+ @Query("max_id") maxId: String? = null,
+ @Query("since_id") sinceId: String? = null,
+ @Query("limit") limit: Int? = null
): Single<Response<List<String>>>
@FormUrlEncoded
@POST("api/v1/domain_blocks")
fun blockDomain(
- @Field("domain") domain: String
+ @Field("domain") domain: String
): Call<Any>
@FormUrlEncoded
@@ 397,107 442,107 @@ interface MastodonApi {
@GET("api/v1/favourites?with_muted=true")
fun favourites(
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?
): Call<List<Status>>
@GET("api/v1/bookmarks?with_muted=true")
fun bookmarks(
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?
): Call<List<Status>>
@GET("api/v1/follow_requests")
fun followRequests(
- @Query("max_id") maxId: String?
+ @Query("max_id") maxId: String?
): Single<Response<List<Account>>>
@POST("api/v1/follow_requests/{id}/authorize")
fun authorizeFollowRequest(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Call<Relationship>
@POST("api/v1/follow_requests/{id}/reject")
fun rejectFollowRequest(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Call<Relationship>
@POST("api/v1/follow_requests/{id}/authorize")
fun authorizeFollowRequestObservable(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Relationship>
@POST("api/v1/follow_requests/{id}/reject")
fun rejectFollowRequestObservable(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Relationship>
@FormUrlEncoded
@POST("api/v1/apps")
fun authenticateApp(
- @Header(DOMAIN_HEADER) domain: String,
- @Field("client_name") clientName: String,
- @Field("redirect_uris") redirectUris: String,
- @Field("scopes") scopes: String,
- @Field("website") website: String
+ @Header(DOMAIN_HEADER) domain: String,
+ @Field("client_name") clientName: String,
+ @Field("redirect_uris") redirectUris: String,
+ @Field("scopes") scopes: String,
+ @Field("website") website: String
): Call<AppCredentials>
@FormUrlEncoded
@POST("oauth/token")
fun fetchOAuthToken(
- @Header(DOMAIN_HEADER) domain: String,
- @Field("client_id") clientId: String,
- @Field("client_secret") clientSecret: String,
- @Field("redirect_uri") redirectUri: String,
- @Field("code") code: String,
- @Field("grant_type") grantType: String
+ @Header(DOMAIN_HEADER) domain: String,
+ @Field("client_id") clientId: String,
+ @Field("client_secret") clientSecret: String,
+ @Field("redirect_uri") redirectUri: String,
+ @Field("code") code: String,
+ @Field("grant_type") grantType: String
): Call<AccessToken>
@FormUrlEncoded
@POST("api/v1/lists")
fun createList(
- @Field("title") title: String
+ @Field("title") title: String
): Single<MastoList>
@FormUrlEncoded
@PUT("api/v1/lists/{listId}")
fun updateList(
- @Path("listId") listId: String,
- @Field("title") title: String
+ @Path("listId") listId: String,
+ @Field("title") title: String
): Single<MastoList>
@DELETE("api/v1/lists/{listId}")
fun deleteList(
- @Path("listId") listId: String
+ @Path("listId") listId: String
): Completable
@GET("api/v1/lists/{listId}/accounts")
fun getAccountsInList(
- @Path("listId") listId: String,
- @Query("limit") limit: Int
+ @Path("listId") listId: String,
+ @Query("limit") limit: Int
): Single<List<Account>>
@FormUrlEncoded
// @DELETE doesn't support fields
@HTTP(method = "DELETE", path = "api/v1/lists/{listId}/accounts", hasBody = true)
fun deleteAccountFromList(
- @Path("listId") listId: String,
- @Field("account_ids[]") accountIds: List<String>
+ @Path("listId") listId: String,
+ @Field("account_ids[]") accountIds: List<String>
): Completable
@FormUrlEncoded
@POST("api/v1/lists/{listId}/accounts")
fun addCountToList(
- @Path("listId") listId: String,
- @Field("account_ids[]") accountIds: List<String>
+ @Path("listId") listId: String,
+ @Field("account_ids[]") accountIds: List<String>
): Completable
@GET("/api/v1/conversations")
fun getConversations(
- @Query("max_id") maxId: String? = null,
- @Query("limit") limit: Int
+ @Query("max_id") maxId: String? = null,
+ @Query("limit") limit: Int
): Call<List<Conversation>>
data class PostFilter(
@@ 513,83 558,83 @@ interface MastodonApi {
@PUT("api/v1/filters/{id}")
fun updateFilter(
- @Path("id") id: String,
- @Body body: PostFilter
+ @Path("id") id: String,
+ @Body body: PostFilter
): Call<Filter>
@DELETE("api/v1/filters/{id}")
fun deleteFilter(
- @Path("id") id: String
+ @Path("id") id: String
): Call<ResponseBody>
@FormUrlEncoded
@POST("api/v1/polls/{id}/votes")
fun voteInPoll(
- @Path("id") id: String,
- @Field("choices[]") choices: List<Int>
+ @Path("id") id: String,
+ @Field("choices[]") choices: List<Int>
): Single<Poll>
@GET("api/v1/announcements")
fun listAnnouncements(
- @Query("with_dismissed") withDismissed: Boolean = true
+ @Query("with_dismissed") withDismissed: Boolean = true
): Single<List<Announcement>>
@POST("api/v1/announcements/{id}/dismiss")
fun dismissAnnouncement(
- @Path("id") announcementId: String
+ @Path("id") announcementId: String
): Single<ResponseBody>
@PUT("api/v1/announcements/{id}/reactions/{name}")
fun addAnnouncementReaction(
- @Path("id") announcementId: String,
- @Path("name") name: String
+ @Path("id") announcementId: String,
+ @Path("name") name: String
): Single<ResponseBody>
@DELETE("api/v1/announcements/{id}/reactions/{name}")
fun removeAnnouncementReaction(
- @Path("id") announcementId: String,
- @Path("name") name: String
+ @Path("id") announcementId: String,
+ @Path("name") name: String
): Single<ResponseBody>
@FormUrlEncoded
@POST("api/v1/reports")
fun reportObservable(
- @Field("account_id") accountId: String,
- @Field("status_ids[]") statusIds: List<String>,
- @Field("comment") comment: String,
- @Field("forward") isNotifyRemote: Boolean?
+ @Field("account_id") accountId: String,
+ @Field("status_ids[]") statusIds: List<String>,
+ @Field("comment") comment: String,
+ @Field("forward") isNotifyRemote: Boolean?
): Single<ResponseBody>
@GET("api/v1/accounts/{id}/statuses?with_muted=true")
fun accountStatusesObservable(
- @Path("id") accountId: String,
- @Query("max_id") maxId: String?,
- @Query("since_id") sinceId: String?,
- @Query("limit") limit: Int?,
- @Query("exclude_reblogs") excludeReblogs: Boolean?
+ @Path("id") accountId: String,
+ @Query("max_id") maxId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("limit") limit: Int?,
+ @Query("exclude_reblogs") excludeReblogs: Boolean?
): Single<List<Status>>
@GET("api/v1/statuses/{id}")
fun statusObservable(
- @Path("id") statusId: String
+ @Path("id") statusId: String
): Single<Status>
@GET("api/v2/search")
fun searchObservable(
- @Query("q") query: String?,
- @Query("type") type: String? = null,
- @Query("resolve") resolve: Boolean? = null,
- @Query("limit") limit: Int? = null,
- @Query("offset") offset: Int? = null,
- @Query("following") following: Boolean? = null
+ @Query("q") query: String?,
+ @Query("type") type: String? = null,
+ @Query("resolve") resolve: Boolean? = null,
+ @Query("limit") limit: Int? = null,
+ @Query("offset") offset: Int? = null,
+ @Query("following") following: Boolean? = null
): Single<SearchResult>
-
+
@GET(".well-known/nodeinfo")
- fun getNodeinfoLinks() : Single<NodeInfoLinks>
-
+ fun getNodeinfoLinks(): Single<NodeInfoLinks>
+
@GET
- fun getNodeinfo(@Url url: String) : Single<NodeInfo>
-
+ fun getNodeinfo(@Url url: String): Single<NodeInfo>
+
@PUT("api/v1/pleroma/statuses/{id}/reactions/{emoji}")
fun reactWithEmoji(
@Path("id") statusId: String,
@@ 611,7 656,7 @@ interface MastodonApi {
// NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS
// just for testing and because puniko asked me
@GET("static/stickers.json")
- fun getStickers() : Single<Map<String, String>>
+ fun getStickers(): Single<Map<String, String>>
@GET
fun getStickerPack(
@@ 621,64 666,64 @@ interface MastodonApi {
@POST("api/v1/pleroma/chats/{id}/messages/{message_id}/read")
fun markChatMessageAsRead(
- @Path("id") chatId: String,
- @Path("message_id") messageId: String
+ @Path("id") chatId: String,
+ @Path("message_id") messageId: String
): Single<ChatMessage>
@DELETE("api/v1/pleroma/chats/{id}/messages/{message_id}")
fun deleteChatMessage(
- @Path("id") chatId: String,
- @Path("message_id") messageId: String
+ @Path("id") chatId: String,
+ @Path("message_id") messageId: String
): Single<ChatMessage>
@GET("api/v2/pleroma/chats")
fun getChats(
- @Query("max_id") maxId: String?,
- @Query("min_id") minId: String?,
- @Query("since_id") sinceId: String?,
- @Query("offset") offset: Int?,
- @Query("limit") limit: Int?
+ @Query("max_id") maxId: String?,
+ @Query("min_id") minId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("offset") offset: Int?,
+ @Query("limit") limit: Int?
): Single<List<Chat>>
@GET("api/v1/pleroma/chats/{id}/messages")
fun getChatMessages(
- @Path("id") chatId: String,
- @Query("max_id") maxId: String?,
- @Query("min_id") minId: String?,
- @Query("since_id") sinceId: String?,
- @Query("offset") offset: Int?,
- @Query("limit") limit: Int?
+ @Path("id") chatId: String,
+ @Query("max_id") maxId: String?,
+ @Query("min_id") minId: String?,
+ @Query("since_id") sinceId: String?,
+ @Query("offset") offset: Int?,
+ @Query("limit") limit: Int?
): Single<List<ChatMessage>>
@POST("api/v1/pleroma/chats/{id}/messages")
fun createChatMessage(
- @Header("Authorization") auth: String,
- @Header(DOMAIN_HEADER) domain: String,
- @Path("id") chatId: String,
- @Body chatMessage: NewChatMessage
+ @Header("Authorization") auth: String,
+ @Header(DOMAIN_HEADER) domain: String,
+ @Path("id") chatId: String,
+ @Body chatMessage: NewChatMessage
): Call<ChatMessage>
@FormUrlEncoded
@POST("api/v1/pleroma/chats/{id}/read")
fun markChatAsRead(
- @Path("id") chatId: String,
- @Field("last_read_id") lastReadId: String? = null
+ @Path("id") chatId: String,
+ @Field("last_read_id") lastReadId: String? = null
): Single<Chat>
@POST("api/v1/pleroma/chats/by-account-id/{id}")
fun createChat(
- @Path("id") accountId: String
+ @Path("id") accountId: String
): Single<Chat>
@GET("api/v1/pleroma/chats/{id}")
fun getChat(
- @Path("id") chatId: String
+ @Path("id") chatId: String
): Single<Chat>
@FormUrlEncoded
@POST("api/v1/accounts/{id}/note")
fun updateAccountNote(
- @Path("id") accountId: String,
- @Field("comment") note: String
+ @Path("id") accountId: String,
+ @Field("comment") note: String
): Single<Relationship>
}
M husky/app/src/main/java/com/keylesspalace/tusky/repository/ChatRepository.kt => husky/app/src/main/java/com/keylesspalace/tusky/repository/ChatRepository.kt +162 -97
@@ 1,3 1,23 @@
+/*
+ * Husky -- A Pleroma client for Android
+ *
+ * Copyright (C) 2021 The Husky Developers
+ * Copyright (C) 2021 Andrew Dawson
+ *
+ * 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.
+ *
+ * This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
package com.keylesspalace.tusky.repository
import android.text.SpannedString
@@ 5,8 25,16 @@ import androidx.core.text.parseAsHtml
import androidx.core.text.toHtml
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
-import com.keylesspalace.tusky.db.*
-import com.keylesspalace.tusky.entity.*
+import com.keylesspalace.tusky.db.AccountManager
+import com.keylesspalace.tusky.db.ChatEntity
+import com.keylesspalace.tusky.db.ChatEntityWithAccount
+import com.keylesspalace.tusky.db.ChatMessageEntity
+import com.keylesspalace.tusky.db.ChatsDao
+import com.keylesspalace.tusky.entity.Account
+import com.keylesspalace.tusky.entity.Attachment
+import com.keylesspalace.tusky.entity.Chat
+import com.keylesspalace.tusky.entity.ChatMessage
+import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK
import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK
@@ 17,39 45,56 @@ import com.keylesspalace.tusky.util.trimTrailingWhitespace
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import java.io.IOException
-import java.util.*
+import java.util.Date
typealias ChatStatus = Either<Placeholder, Chat>
typealias ChatMesssageOrPlaceholder = Either<Placeholder, ChatMessage>
interface ChatRepository {
- fun getChats(maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int,
- requestMode: TimelineRequestMode): Single<out List<ChatStatus>>
-
- fun getChatMessages(chatId: String, maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, requestMode: TimelineRequestMode) : Single<out List<ChatMesssageOrPlaceholder>>
+ fun getChats(
+ maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int,
+ requestMode: TimelineRequestMode
+ ): Single<out List<ChatStatus>>
+
+ fun getChatMessages(
+ chatId: String,
+ maxId: String?,
+ sinceId: String?,
+ sincedIdMinusOne: String?,
+ limit: Int,
+ requestMode: TimelineRequestMode
+ ): Single<out List<ChatMesssageOrPlaceholder>>
}
class ChatRepositoryImpl(
- private val chatsDao: ChatsDao,
- private val mastodonApi: MastodonApi,
- private val accountManager: AccountManager,
- private val gson: Gson
+ private val chatsDao: ChatsDao,
+ private val mastodonApi: MastodonApi,
+ private val accountManager: AccountManager,
+ private val gson: Gson
) : ChatRepository {
- override fun getChats(maxId: String?, sinceId: String?, sincedIdMinusOne: String?,
- limit: Int, requestMode: TimelineRequestMode
+ override fun getChats(
+ maxId: String?, sinceId: String?, sincedIdMinusOne: String?,
+ limit: Int, requestMode: TimelineRequestMode
): Single<out List<ChatStatus>> {
val acc = accountManager.activeAccount ?: throw IllegalStateException()
val accountId = acc.id
- return if (requestMode == DISK) {
+ return if(requestMode == DISK) {
this.getChatsFromDb(accountId, maxId, sinceId, limit)
} else {
getChatsFromNetwork(maxId, sinceId, sincedIdMinusOne, limit, accountId, requestMode)
}
}
- override fun getChatMessages(chatId: String, maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, requestMode: TimelineRequestMode) : Single<out List<ChatMesssageOrPlaceholder>> {
+ override fun getChatMessages(
+ chatId: String,
+ maxId: String?,
+ sinceId: String?,
+ sincedIdMinusOne: String?,
+ limit: Int,
+ requestMode: TimelineRequestMode
+ ): Single<out List<ChatMesssageOrPlaceholder>> {
val acc = accountManager.activeAccount ?: throw IllegalStateException()
val accountId = acc.id
@@ 62,9 107,10 @@ class ChatRepositoryImpl(
return getChatMessagesFromNetwork(chatId, maxId, null, null, limit, accountId, requestMode)
}
- private fun getChatsFromNetwork(maxId: String?, sinceId: String?,
- sinceIdMinusOne: String?, limit: Int,
- accountId: Long, requestMode: TimelineRequestMode
+ private fun getChatsFromNetwork(
+ maxId: String?, sinceId: String?,
+ sinceIdMinusOne: String?, limit: Int,
+ accountId: Long, requestMode: TimelineRequestMode
): Single<out List<ChatStatus>> {
return mastodonApi.getChats(null, null, sinceIdMinusOne, 0, limit + 1)
.map { chats ->
@@ 74,17 120,18 @@ class ChatRepositoryImpl(
this.addFromDbIfNeeded(accountId, chats, maxId, sinceId, limit, requestMode)
}
.onErrorResumeNext { error ->
- if (error is IOException && requestMode != NETWORK) {
+ if(error is IOException && requestMode != NETWORK) {
this.getChatsFromDb(accountId, maxId, sinceId, limit)
} else {
- Single.error(error)
- }
+ Single.error(error)
}
+ }
}
- private fun getChatMessagesFromNetwork(chatId: String, maxId: String?, sinceId: String?,
- sinceIdMinusOne: String?, limit: Int,
- accountId: Long, requestMode: TimelineRequestMode
+ private fun getChatMessagesFromNetwork(
+ chatId: String, maxId: String?, sinceId: String?,
+ sinceIdMinusOne: String?, limit: Int,
+ accountId: Long, requestMode: TimelineRequestMode
): Single<out List<ChatMesssageOrPlaceholder>> {
return mastodonApi.getChatMessages(chatId, maxId, null, sinceIdMinusOne, 0, limit + 1).map {
it.mapTo(mutableListOf(), ChatMessage::lift)
@@ 92,62 139,66 @@ class ChatRepositoryImpl(
}
- private fun addFromDbIfNeeded(accountId: Long, chats: List<ChatStatus>,
- maxId: String?, sinceId: String?, limit: Int,
- requestMode: TimelineRequestMode
+ private fun addFromDbIfNeeded(
+ accountId: Long, chats: List<ChatStatus>,
+ maxId: String?, sinceId: String?, limit: Int,
+ requestMode: TimelineRequestMode
): Single<List<ChatStatus>> {
- return if (requestMode != NETWORK && chats.size < 2) {
- val newMaxID = if (chats.isEmpty()) {
+ return if(requestMode != NETWORK && chats.size < 2) {
+ val newMaxID = if(chats.isEmpty()) {
maxId
} else {
chats.last { it.isRight() }.asRight().id
}
this.getChatsFromDb(accountId, newMaxID, sinceId, limit)
- .map { fromDb ->
- // If it's just placeholders and less than limit (so we exhausted both
- // db and server at this point)
- if (fromDb.size < limit && fromDb.all { !it.isRight() }) {
- chats
- } else {
- chats + fromDb
- }
+ .map { fromDb ->
+ // If it's just placeholders and less than limit (so we exhausted both
+ // db and server at this point)
+ if(fromDb.size < limit && fromDb.all { !it.isRight() }) {
+ chats
+ } else {
+ chats + fromDb
}
+ }
} else {
Single.just(chats)
}
}
- private fun getChatsFromDb(accountId: Long, maxId: String?, sinceId: String?,
- limit: Int): Single<out List<ChatStatus>> {
+ private fun getChatsFromDb(
+ accountId: Long, maxId: String?, sinceId: String?,
+ limit: Int
+ ): Single<out List<ChatStatus>> {
return chatsDao.getChatsForAccount(accountId, maxId, sinceId, limit)
- .subscribeOn(Schedulers.io())
- .map { chats ->
- chats.map { it.toChat(gson) }
- }
+ .subscribeOn(Schedulers.io())
+ .map { chats ->
+ chats.map { it.toChat(gson) }
+ }
}
- private fun saveChatsToDb(accountId: Long, chats: List<Chat>,
- maxId: String?, sinceId: String?
+ private fun saveChatsToDb(
+ accountId: Long, chats: List<Chat>,
+ maxId: String?, sinceId: String?
): List<ChatStatus> {
var placeholderToInsert: Placeholder? = null
// Look for overlap
- val resultChats = if (chats.isNotEmpty() && sinceId != null) {
+ val resultChats = if(chats.isNotEmpty() && sinceId != null) {
val indexOfSince = chats.indexOfLast { it.id == sinceId }
- if (indexOfSince == -1) {
+ if(indexOfSince == -1) {
// We didn't find the status which must be there. Add a placeholder
placeholderToInsert = Placeholder(sinceId.inc())
chats.mapTo(mutableListOf(), Chat::lift)
- .apply {
- add(Either.Left(placeholderToInsert))
- }
+ .apply {
+ add(Either.Left(placeholderToInsert))
+ }
} else {
// There was an overlap. Remove all overlapped statuses. No need for a placeholder.
chats.mapTo(mutableListOf(), Chat::lift)
- .apply {
- subList(indexOfSince, size).clear()
- }
+ .apply {
+ subList(indexOfSince, size).clear()
+ }
}
} else {
// Just a normal case.
@@ 160,13 211,13 @@ class ChatRepositoryImpl(
chatsDao.deleteRange(accountId, chats.last().id, chats.first().id)
}
- for (chat in chats) {
+ for(chat in chats) {
val pair = chat.toEntity(accountId, gson)
chatsDao.insertInTransaction(
- pair.first,
- pair.second,
- chat.account.toEntity(accountId, gson)
+ pair.first,
+ pair.second,
+ chat.account.toEntity(accountId, gson)
)
}
@@ 176,21 227,24 @@ class ChatRepositoryImpl(
// If we're loading in the bottom insert placeholder after every load
// (for requests on next launches) but not return it.
- if (sinceId == null && chats.isNotEmpty()) {
+ if(sinceId == null && chats.isNotEmpty()) {
chatsDao.insertChatIfNotThere(
- Placeholder(chats.last().id.dec()).toChatEntity(accountId))
+ Placeholder(chats.last().id.dec()).toChatEntity(accountId)
+ )
}
// There may be placeholders which we thought could be from our TL but they are not
- if (chats.size > 2) {
- chatsDao.removeAllPlaceholdersBetween(accountId, chats.first().id,
- chats.last().id)
- } else if (placeholderToInsert == null && maxId != null && sinceId != null) {
+ if(chats.size > 2) {
+ chatsDao.removeAllPlaceholdersBetween(
+ accountId, chats.first().id,
+ chats.last().id
+ )
+ } else if(placeholderToInsert == null && maxId != null && sinceId != null) {
chatsDao.removeAllPlaceholdersBetween(accountId, maxId, sinceId)
}
}
- .subscribeOn(Schedulers.io())
- .subscribe()
+ .subscribeOn(Schedulers.io())
+ .subscribe()
return resultChats
}
@@ 200,62 254,73 @@ private val emojisListTypeToken = object : TypeToken<List<Emoji>>() {}
fun Placeholder.toChatEntity(timelineUserId: Long): ChatEntity {
return ChatEntity(
- localId = timelineUserId,
- chatId = this.id,
- accountId = "",
- unread = 0L,
- updatedAt = 0L,
- lastMessageId = null
+ localId = timelineUserId,
+ chatId = this.id,
+ accountId = "",
+ unread = 0L,
+ updatedAt = 0L,
+ lastMessageId = null
)
}
-fun ChatMessage.toEntity(timelineUserId: Long, gson: Gson) : ChatMessageEntity {
+fun ChatMessage.toEntity(timelineUserId: Long, gson: Gson): ChatMessageEntity {
return ChatMessageEntity(
- localId = timelineUserId,
- messageId = this.id,
- content = this.content?.toHtml(),
- chatId = this.chatId,
- accountId = this.accountId,
- createdAt = this.createdAt.time,
- attachment = this.attachment?.let { gson.toJson(it, Attachment::class.java) },
- emojis = gson.toJson(this.emojis)
+ localId = timelineUserId,
+ messageId = this.id,
+ content = this.content?.toHtml(),
+ chatId = this.chatId,
+ accountId = this.accountId,
+ createdAt = this.createdAt.time,
+ attachment = this.attachment?.let { gson.toJson(it, Attachment::class.java) },
+ emojis = gson.toJson(this.emojis)
)
}
fun Chat.toEntity(timelineUserId: Long, gson: Gson): Pair<ChatEntity, ChatMessageEntity?> {
- return Pair(ChatEntity(
+ return Pair(
+ ChatEntity(
localId = timelineUserId,
chatId = this.id,
accountId = this.account.id,
unread = this.unread,
updatedAt = this.updatedAt.time,
lastMessageId = this.lastMessage?.id
- ), this.lastMessage?.toEntity(timelineUserId, gson))
+ ), this.lastMessage?.toEntity(timelineUserId, gson)
+ )
}
-fun ChatMessageEntity.toChatMessage(gson: Gson) : ChatMessage {
+fun ChatMessageEntity.toChatMessage(gson: Gson): ChatMessage {
return ChatMessage(
- id = this.messageId,
- content = this.content?.let { it.parseAsHtml().trimTrailingWhitespace() },
- chatId = this.chatId,
- accountId = this.accountId,
- createdAt = Date(this.createdAt),
- attachment = this.attachment?.let { gson.fromJson(it, Attachment::class.java) },
- emojis = gson.fromJson(this.emojis, object : TypeToken<List<Emoji>>() {}.type ),
- card = null /* don't care about card */
+ id = this.messageId,
+ content = this.content?.let { it.parseAsHtml().trimTrailingWhitespace() },
+ chatId = this.chatId,
+ accountId = this.accountId,
+ createdAt = Date(this.createdAt),
+ attachment = this.attachment?.let { gson.fromJson(it, Attachment::class.java) },
+ emojis = gson.fromJson(this.emojis, object : TypeToken<List<Emoji>>() {}.type),
+ card = null /* don't care about card */
)
}
-fun ChatEntityWithAccount.toChat(gson: Gson) : ChatStatus {
+fun ChatEntityWithAccount.toChat(gson: Gson): ChatStatus {
if(account == null || chat.accountId.isEmpty() || chat.updatedAt == 0L)
return Either.Left(Placeholder(chat.chatId))
return Chat(
- account = this.account?.toAccount(gson) ?: Account("", "", "", "", SpannedString(""), "", "", "" ),
- id = this.chat.chatId,
- unread = this.chat.unread,
- updatedAt = Date(this.chat.updatedAt),
- lastMessage = this.lastMessage?.toChatMessage(gson)
+ account = this.account?.toAccount(gson) ?: Account(
+ "",
+ "",
+ "",
+ "",
+ SpannedString(""),
+ "",
+ "",
+ ""
+ ),
+ id = this.chat.chatId,
+ unread = this.chat.unread,
+ updatedAt = Date(this.chat.updatedAt),
+ lastMessage = this.lastMessage?.toChatMessage(gson)
).lift()
}