From e771207d58f804b99975ed2bd6c90fe54860f8b2 Mon Sep 17 00:00:00 2001 From: Artem Umerov Date: Fri, 30 Dec 2022 19:54:50 +0300 Subject: [PATCH] 3. First My Rest 3. First My Rest --- app_fenrir/build.gradle | 1 - .../main/kotlin/dev/ragnarok/fenrir/App.kt | 1 + .../fenrir/api/IOtherVkRestProvider.kt | 11 + .../fenrir/api/IOtherVkRetrofitProvider.kt | 10 - .../ragnarok/fenrir/api/IServiceProvider.kt | 5 +- .../fenrir/api/IUploadRestProvider.kt | 8 + .../fenrir/api/IUploadRetrofitProvider.kt | 7 - .../fenrir/api/IVkMethodHttpClientFactory.kt | 11 +- .../ragnarok/fenrir/api/IVkRestProvider.kt | 14 + .../fenrir/api/IVkRetrofitProvider.kt | 13 - ...ofitProvider.kt => OtherVkRestProvider.kt} | 110 +-- .../ragnarok/fenrir/api/RetrofitWrapper.kt | 40 - ...rofitProvider.kt => UploadRestProvider.kt} | 50 +- .../fenrir/api/VkMethodHttpClientFactory.kt | 14 +- .../dev/ragnarok/fenrir/api/VkRestProvider.kt | 107 +++ .../ragnarok/fenrir/api/VkRetrofitProvider.kt | 121 --- .../dev/ragnarok/fenrir/api/impl/AbsApi.kt | 44 +- .../ragnarok/fenrir/api/impl/AccountApi.kt | 32 +- .../dev/ragnarok/fenrir/api/impl/AudioApi.kt | 68 +- .../dev/ragnarok/fenrir/api/impl/AuthApi.kt | 82 +- .../dev/ragnarok/fenrir/api/impl/BoardApi.kt | 12 +- .../ragnarok/fenrir/api/impl/CommentsApi.kt | 2 +- .../ragnarok/fenrir/api/impl/DatabaseApi.kt | 16 +- .../dev/ragnarok/fenrir/api/impl/DocsApi.kt | 18 +- .../dev/ragnarok/fenrir/api/impl/FaveApi.kt | 42 +- .../ragnarok/fenrir/api/impl/FriendsApi.kt | 18 +- .../dev/ragnarok/fenrir/api/impl/GroupsApi.kt | 38 +- .../dev/ragnarok/fenrir/api/impl/LikesApi.kt | 10 +- .../ragnarok/fenrir/api/impl/LongpollApi.kt | 30 +- .../ragnarok/fenrir/api/impl/MessagesApi.kt | 2 +- .../dev/ragnarok/fenrir/api/impl/Networker.kt | 54 +- .../ragnarok/fenrir/api/impl/NewsfeedApi.kt | 28 +- .../fenrir/api/impl/NotificationsApi.kt | 10 +- .../dev/ragnarok/fenrir/api/impl/OtherApi.kt | 12 +- .../dev/ragnarok/fenrir/api/impl/PagesApi.kt | 2 +- .../dev/ragnarok/fenrir/api/impl/PhotosApi.kt | 58 +- .../dev/ragnarok/fenrir/api/impl/PollsApi.kt | 10 +- .../dev/ragnarok/fenrir/api/impl/StatusApi.kt | 2 +- .../dev/ragnarok/fenrir/api/impl/StoreApi.kt | 4 +- .../dev/ragnarok/fenrir/api/impl/UploadApi.kt | 52 +- .../dev/ragnarok/fenrir/api/impl/UsersApi.kt | 30 +- .../dev/ragnarok/fenrir/api/impl/UtilsApi.kt | 18 +- .../dev/ragnarok/fenrir/api/impl/VideoApi.kt | 24 +- .../dev/ragnarok/fenrir/api/impl/VkApies.kt | 80 +- .../dev/ragnarok/fenrir/api/impl/WallApi.kt | 38 +- .../fenrir/api/interfaces/ILongpollApi.kt | 4 +- .../fenrir/api/interfaces/INetworker.kt | 4 +- .../fenrir/api/interfaces/IUploadApi.kt | 20 +- .../dev/ragnarok/fenrir/api/model/Error.kt | 8 +- .../rest/HttpException.kt} | 6 +- .../ragnarok/fenrir/api/rest/IServiceRest.kt | 77 ++ .../fenrir/api/rest/SimplePostHttp.kt | 137 +++ .../fenrir/api/services/IAccountService.kt | 194 ++-- .../fenrir/api/services/IAudioService.kt | 602 +++++++----- .../fenrir/api/services/IAuthService.kt | 84 +- .../fenrir/api/services/IBoardService.kt | 168 ++-- .../fenrir/api/services/ICommentsService.kt | 49 +- .../fenrir/api/services/IDatabaseService.kt | 148 +-- .../fenrir/api/services/IDocsService.kt | 145 ++- .../fenrir/api/services/IFaveService.kt | 380 +++++--- .../fenrir/api/services/IFriendsService.kt | 159 ++-- .../fenrir/api/services/IGroupsService.kt | 428 ++++++--- .../fenrir/api/services/ILikesService.kt | 132 ++- .../api/services/ILocalServerService.kt | 216 +++-- .../api/services/ILongpollUpdatesService.kt | 55 +- .../fenrir/api/services/IMessageService.kt | 622 ++++++++----- .../fenrir/api/services/INewsfeedService.kt | 324 ++++--- .../api/services/INotificationsService.kt | 67 +- .../fenrir/api/services/IPagesService.kt | 36 +- .../fenrir/api/services/IPhotosService.kt | 642 ++++++++----- .../fenrir/api/services/IPollsService.kt | 123 ++- .../fenrir/api/services/IStatusService.kt | 21 +- .../fenrir/api/services/IStoreService.kt | 26 +- .../fenrir/api/services/IUploadService.kt | 95 +- .../fenrir/api/services/IUsersService.kt | 364 +++++--- .../fenrir/api/services/IUtilsService.kt | 103 +- .../fenrir/api/services/IVideoService.kt | 297 ++++-- .../fenrir/api/services/IWallService.kt | 453 +++++---- .../fenrir/db/impl/AttachmentsStorage.kt | 2 +- .../fenrir/db/impl/CommentsStorage.kt | 2 +- .../ragnarok/fenrir/db/impl/ContactsUtils.kt | 4 +- .../fenrir/db/impl/KeysPersistStorage.kt | 2 +- .../fenrir/db/impl/MessagesStorage.kt | 4 +- .../audioduplicate/AudioDuplicatePresenter.kt | 10 +- .../fenrir/domain/impl/MessagesRepository.kt | 9 +- .../fragment/accounts/AccountsFragment.kt | 10 +- .../audioslocal/AudioLocalRecyclerAdapter.kt | 14 +- .../AudioLocalServerRecyclerAdapter.kt | 4 +- .../fragment/messages/chat/ChatPresenter.kt | 2 +- .../fragment/userwall/UserWallPresenter.kt | 8 +- .../ragnarok/fenrir/longpoll/GroupLongpoll.kt | 2 +- .../ragnarok/fenrir/service/ErrorLocalizer.kt | 12 +- .../upload/impl/AudioToMessageUploadable.kt | 7 +- .../fenrir/upload/impl/AudioUploadable.kt | 7 +- .../fenrir/upload/impl/ChatPhotoUploadable.kt | 6 +- .../fenrir/upload/impl/DocumentUploadable.kt | 7 +- .../upload/impl/OwnerPhotoUploadable.kt | 6 +- .../upload/impl/Photo2AlbumUploadable.kt | 6 +- .../upload/impl/Photo2MessageUploadable.kt | 4 +- .../upload/impl/Photo2WallUploadable.kt | 6 +- .../fenrir/upload/impl/StoryUploadable.kt | 2 +- .../upload/impl/Video2WallUploadable.kt | 7 +- .../upload/impl/VideoToMessageUploadable.kt | 7 +- .../fenrir/upload/impl/VideoUploadable.kt | 7 +- .../dev/ragnarok/fenrir/util/Mp3InfoHelper.kt | 4 +- .../DeserializationStrategyConverter.kt | 24 - .../retrofit/kotlinx/serialization/Factory.kt | 100 -- .../SerializationStrategyConverter.kt | 14 - .../kotlinx/serialization/Serializer.kt | 122 --- .../retrofit/rxjava3/BodyObservable.kt | 98 -- .../retrofit/rxjava3/CallEnqueueObservable.kt | 75 -- .../retrofit/rxjava3/CallExecuteObservable.kt | 59 -- .../serializeble/retrofit/rxjava3/Result.kt | 52 -- .../retrofit/rxjava3/ResultObservable.kt | 46 - .../retrofit/rxjava3/RxJava3CallAdapter.kt | 55 -- .../rxjava3/RxJava3CallAdapterFactory.kt | 103 -- app_fenrir/src/main/res/values-be/strings.xml | 1 + app_fenrir/src/main/res/values-ru/strings.xml | 1 + app_fenrir/src/main/res/values/strings.xml | 1 + app_filegallery/build.gradle | 1 - .../kotlin/dev/ragnarok/filegallery/App.kt | 17 + .../filegallery/api/IOtherRestProvider.kt | 8 + .../filegallery/api/IOtherRetrofitProvider.kt | 7 - ...trofitProvider.kt => OtherRestProvider.kt} | 49 +- .../filegallery/api/RetrofitWrapper.kt | 40 - .../filegallery/api/impl/LocalServerApi.kt | 2 +- .../filegallery/api/impl/Networker.kt | 19 +- .../api/interfaces/ILocalServerApi.kt | 2 +- .../filegallery/api/rest/HttpException.kt | 13 + .../filegallery/api/rest/IServiceRest.kt | 77 ++ .../filegallery/api/rest/SimplePostHttp.kt | 107 +++ .../api/services/ILocalServerService.kt | 233 +++-- .../db/impl/SearchRequestHelperStorage.kt | 6 +- .../filemanager/FileManagerAdapter.kt | 4 +- .../filemanager/FileManagerPresenter.kt | 4 +- .../AudioLocalServerRecyclerAdapter.kt | 4 +- .../filegallery/util/ErrorLocalizer.kt | 22 +- .../DeserializationStrategyConverter.kt | 14 - .../retrofit/kotlinx/serialization/Factory.kt | 100 -- .../SerializationStrategyConverter.kt | 14 - .../kotlinx/serialization/Serializer.kt | 122 --- .../retrofit/rxjava3/BodyObservable.kt | 65 -- .../retrofit/rxjava3/CallEnqueueObservable.kt | 75 -- .../retrofit/rxjava3/CallExecuteObservable.kt | 59 -- .../serializeble/retrofit/rxjava3/Result.kt | 52 -- .../retrofit/rxjava3/ResultObservable.kt | 46 - .../retrofit/rxjava3/RxJava3CallAdapter.kt | 55 -- .../rxjava3/RxJava3CallAdapterFactory.kt | 103 -- .../src/main/res/values-be/strings.xml | 2 + .../src/main/res/values-ru/strings.xml | 2 + .../src/main/res/values/strings.xml | 2 + build.gradle | 2 +- compiled_native/libfenrir-release.aar | Bin 9467732 -> 9471297 bytes .../github/luben/zstd/ZstdCompressCtx.java | 12 + ...ectBufferCompressingStreamNoFinalizer.java | 4 +- .../luben/zstd/ZstdFrameProgression.java | 65 ++ .../jni/compress/zstd/common/error_private.c | 2 + .../compress/zstd/compress/zstd_compress.c | 258 +++++- .../zstd/compress/zstd_compress_internal.h | 53 ++ .../jni/compress/zstd/compress/zstd_opt.c | 76 +- .../main/jni/compress/zstd/jni_fast_zstd.c | 16 + libfenrir/src/main/jni/compress/zstd/zdict.h | 2 +- libfenrir/src/main/jni/compress/zstd/zstd.h | 173 +++- .../src/main/jni/compress/zstd/zstd_errors.h | 2 + .../rlottie/src/lottie/rapidjson/document.h | 2 +- .../rlottie/src/lottie/rapidjson/error/en.h | 15 +- .../src/lottie/rapidjson/error/error.h | 19 +- .../jni/rlottie/src/lottie/rapidjson/schema.h | 471 ++++++++-- .../src/main/jni/thorvg/src/lib/tvgMath.h | 6 + .../src/main/jni/thorvg/src/lib/tvgPaint.cpp | 6 +- retrofit/build.gradle | 35 - .../java/retrofit2/BuiltInConverters.java | 124 --- retrofit/src/main/java/retrofit2/Call.java | 89 -- .../src/main/java/retrofit2/CallAdapter.java | 89 -- .../src/main/java/retrofit2/Callback.java | 46 - .../CompletableFutureCallAdapterFactory.java | 154 --- .../src/main/java/retrofit2/Converter.java | 106 --- .../retrofit2/DefaultCallAdapterFactory.java | 143 --- .../main/java/retrofit2/HttpException.java | 63 -- .../java/retrofit2/HttpServiceMethod.java | 258 ------ .../src/main/java/retrofit2/Invocation.java | 85 -- .../main/java/retrofit2/KotlinExtensions.kt | 127 --- .../src/main/java/retrofit2/OkHttpCall.java | 361 ------- .../retrofit2/OptionalConverterFactory.java | 59 -- .../main/java/retrofit2/ParameterHandler.java | 457 --------- .../src/main/java/retrofit2/Platform.java | 160 ---- .../main/java/retrofit2/RequestBuilder.java | 316 ------- .../main/java/retrofit2/RequestFactory.java | 877 ------------------ .../src/main/java/retrofit2/Response.java | 192 ---- .../src/main/java/retrofit2/Retrofit.java | 674 -------------- .../main/java/retrofit2/ServiceMethod.java | 45 - .../java/retrofit2/SkipCallbackExecutor.java | 48 - .../retrofit2/SkipCallbackExecutorImpl.java | 58 -- retrofit/src/main/java/retrofit2/Utils.java | 572 ------------ .../src/main/java/retrofit2/http/Body.java | 40 - .../src/main/java/retrofit2/http/DELETE.java | 42 - .../src/main/java/retrofit2/http/Field.java | 74 -- .../main/java/retrofit2/http/FieldMap.java | 52 -- .../java/retrofit2/http/FormUrlEncoded.java | 37 - .../src/main/java/retrofit2/http/GET.java | 42 - .../src/main/java/retrofit2/http/HEAD.java | 42 - .../src/main/java/retrofit2/http/HTTP.java | 62 -- .../src/main/java/retrofit2/http/Header.java | 64 -- .../main/java/retrofit2/http/HeaderMap.java | 67 -- .../src/main/java/retrofit2/http/Headers.java | 66 -- .../main/java/retrofit2/http/Multipart.java | 33 - .../src/main/java/retrofit2/http/OPTIONS.java | 42 - .../src/main/java/retrofit2/http/PATCH.java | 42 - .../src/main/java/retrofit2/http/POST.java | 42 - .../src/main/java/retrofit2/http/PUT.java | 42 - .../src/main/java/retrofit2/http/Part.java | 71 -- .../src/main/java/retrofit2/http/PartMap.java | 62 -- .../src/main/java/retrofit2/http/Path.java | 68 -- .../src/main/java/retrofit2/http/Query.java | 91 -- .../main/java/retrofit2/http/QueryMap.java | 69 -- .../main/java/retrofit2/http/QueryName.java | 71 -- .../main/java/retrofit2/http/Streaming.java | 35 - .../src/main/java/retrofit2/http/Tag.java | 41 - .../src/main/java/retrofit2/http/Url.java | 43 - settings.gradle | 2 +- 220 files changed, 6241 insertions(+), 11209 deletions(-) create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRestProvider.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRetrofitProvider.kt create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRestProvider.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRetrofitProvider.kt create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRestProvider.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRetrofitProvider.kt rename app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/{OtherVkRetrofitProvider.kt => OtherVkRestProvider.kt} (61%) delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/RetrofitWrapper.kt rename app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/{UploadRetrofitProvider.kt => UploadRestProvider.kt} (50%) create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRestProvider.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRetrofitProvider.kt rename app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/{util/serializeble/retrofit/HttpCodeException.kt => api/rest/HttpException.kt} (58%) create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/IServiceRest.kt create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/SimplePostHttp.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Factory.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/BodyObservable.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/Result.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/ResultObservable.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt create mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRestProvider.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRetrofitProvider.kt rename app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/{OtherRetrofitProvider.kt => OtherRestProvider.kt} (61%) delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/RetrofitWrapper.kt create mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/HttpException.kt create mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/IServiceRest.kt create mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/SimplePostHttp.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Factory.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/BodyObservable.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/Result.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/ResultObservable.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt create mode 100644 libfenrir/src/main/java/com/github/luben/zstd/ZstdFrameProgression.java delete mode 100644 retrofit/build.gradle delete mode 100644 retrofit/src/main/java/retrofit2/BuiltInConverters.java delete mode 100644 retrofit/src/main/java/retrofit2/Call.java delete mode 100644 retrofit/src/main/java/retrofit2/CallAdapter.java delete mode 100644 retrofit/src/main/java/retrofit2/Callback.java delete mode 100644 retrofit/src/main/java/retrofit2/CompletableFutureCallAdapterFactory.java delete mode 100644 retrofit/src/main/java/retrofit2/Converter.java delete mode 100644 retrofit/src/main/java/retrofit2/DefaultCallAdapterFactory.java delete mode 100644 retrofit/src/main/java/retrofit2/HttpException.java delete mode 100644 retrofit/src/main/java/retrofit2/HttpServiceMethod.java delete mode 100644 retrofit/src/main/java/retrofit2/Invocation.java delete mode 100644 retrofit/src/main/java/retrofit2/KotlinExtensions.kt delete mode 100644 retrofit/src/main/java/retrofit2/OkHttpCall.java delete mode 100644 retrofit/src/main/java/retrofit2/OptionalConverterFactory.java delete mode 100644 retrofit/src/main/java/retrofit2/ParameterHandler.java delete mode 100644 retrofit/src/main/java/retrofit2/Platform.java delete mode 100644 retrofit/src/main/java/retrofit2/RequestBuilder.java delete mode 100644 retrofit/src/main/java/retrofit2/RequestFactory.java delete mode 100644 retrofit/src/main/java/retrofit2/Response.java delete mode 100644 retrofit/src/main/java/retrofit2/Retrofit.java delete mode 100644 retrofit/src/main/java/retrofit2/ServiceMethod.java delete mode 100644 retrofit/src/main/java/retrofit2/SkipCallbackExecutor.java delete mode 100644 retrofit/src/main/java/retrofit2/SkipCallbackExecutorImpl.java delete mode 100644 retrofit/src/main/java/retrofit2/Utils.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Body.java delete mode 100644 retrofit/src/main/java/retrofit2/http/DELETE.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Field.java delete mode 100644 retrofit/src/main/java/retrofit2/http/FieldMap.java delete mode 100644 retrofit/src/main/java/retrofit2/http/FormUrlEncoded.java delete mode 100644 retrofit/src/main/java/retrofit2/http/GET.java delete mode 100644 retrofit/src/main/java/retrofit2/http/HEAD.java delete mode 100644 retrofit/src/main/java/retrofit2/http/HTTP.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Header.java delete mode 100644 retrofit/src/main/java/retrofit2/http/HeaderMap.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Headers.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Multipart.java delete mode 100644 retrofit/src/main/java/retrofit2/http/OPTIONS.java delete mode 100644 retrofit/src/main/java/retrofit2/http/PATCH.java delete mode 100644 retrofit/src/main/java/retrofit2/http/POST.java delete mode 100644 retrofit/src/main/java/retrofit2/http/PUT.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Part.java delete mode 100644 retrofit/src/main/java/retrofit2/http/PartMap.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Path.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Query.java delete mode 100644 retrofit/src/main/java/retrofit2/http/QueryMap.java delete mode 100644 retrofit/src/main/java/retrofit2/http/QueryName.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Streaming.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Tag.java delete mode 100644 retrofit/src/main/java/retrofit2/http/Url.java diff --git a/app_fenrir/build.gradle b/app_fenrir/build.gradle index 0ced149a0..826ecce54 100644 --- a/app_fenrir/build.gradle +++ b/app_fenrir/build.gradle @@ -139,7 +139,6 @@ dependencies { implementation project(path: ":viewpager2") implementation project(path: ":material") implementation project(path: ":preference") - implementation project(path: ":retrofit") implementation project(path: ":camera2") implementation("com.squareup.okhttp3:okhttp-android:$okhttpLibraryVersion") //implementation("com.squareup.okhttp3:logging-interceptor:$okhttpLibraryVersion") diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/App.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/App.kt index a3c8dd63a..6454a144a 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/App.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/App.kt @@ -101,6 +101,7 @@ class App : Application() { }, RxUtils.ignore()) ) RxJavaPlugins.setErrorHandler { + it.printStackTrace() Handler(mainLooper).post { if (Settings.get().other().isDeveloper_mode) { createCustomToast(this).showToastError( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRestProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRestProvider.kt new file mode 100644 index 000000000..e3b608dd7 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRestProvider.kt @@ -0,0 +1,11 @@ +package dev.ragnarok.fenrir.api + +import dev.ragnarok.fenrir.api.rest.SimplePostHttp +import io.reactivex.rxjava3.core.Single + +interface IOtherVkRestProvider { + fun provideAuthRest(): Single + fun provideAuthServiceRest(): Single + fun provideLongpollRest(): Single + fun provideLocalServerRest(): Single +} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRetrofitProvider.kt deleted file mode 100644 index 2f78ae166..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IOtherVkRetrofitProvider.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.ragnarok.fenrir.api - -import io.reactivex.rxjava3.core.Single - -interface IOtherVkRetrofitProvider { - fun provideAuthRetrofit(): Single - fun provideAuthServiceRetrofit(): Single - fun provideLongpollRetrofit(): Single - fun provideLocalServerRetrofit(): Single -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IServiceProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IServiceProvider.kt index 688aefa29..5820f8234 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IServiceProvider.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IServiceProvider.kt @@ -1,11 +1,12 @@ package dev.ragnarok.fenrir.api +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single interface IServiceProvider { - fun provideService( + fun provideService( accountId: Int, - serviceClass: Class, + serviceClass: T, vararg tokenTypes: Int ): Single } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRestProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRestProvider.kt new file mode 100644 index 000000000..4224f7646 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRestProvider.kt @@ -0,0 +1,8 @@ +package dev.ragnarok.fenrir.api + +import dev.ragnarok.fenrir.api.rest.SimplePostHttp +import io.reactivex.rxjava3.core.Single + +interface IUploadRestProvider { + fun provideUploadRest(): Single +} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRetrofitProvider.kt deleted file mode 100644 index 33ec832e4..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IUploadRetrofitProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.ragnarok.fenrir.api - -import io.reactivex.rxjava3.core.Single - -interface IUploadRetrofitProvider { - fun provideUploadRetrofit(): Single -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkMethodHttpClientFactory.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkMethodHttpClientFactory.kt index d23647f73..309dcfb8c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkMethodHttpClientFactory.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkMethodHttpClientFactory.kt @@ -5,13 +5,16 @@ import dev.ragnarok.fenrir.model.ProxyConfig import okhttp3.OkHttpClient interface IVkMethodHttpClientFactory { - fun createDefaultVkHttpClient(accountId: Int, config: ProxyConfig?): OkHttpClient + fun createDefaultVkHttpClient(accountId: Int, config: ProxyConfig?): OkHttpClient.Builder fun createCustomVkHttpClient( accountId: Int, token: String, config: ProxyConfig? - ): OkHttpClient + ): OkHttpClient.Builder - fun createServiceVkHttpClient(config: ProxyConfig?): OkHttpClient - fun createRawVkApiOkHttpClient(@AccountType type: Int, config: ProxyConfig?): OkHttpClient + fun createServiceVkHttpClient(config: ProxyConfig?): OkHttpClient.Builder + fun createRawVkApiOkHttpClient( + @AccountType type: Int, + config: ProxyConfig? + ): OkHttpClient.Builder } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRestProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRestProvider.kt new file mode 100644 index 000000000..85aed4d31 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRestProvider.kt @@ -0,0 +1,14 @@ +package dev.ragnarok.fenrir.api + +import dev.ragnarok.fenrir.AccountType +import dev.ragnarok.fenrir.api.rest.SimplePostHttp +import io.reactivex.rxjava3.core.Single +import okhttp3.OkHttpClient + +interface IVkRestProvider { + fun provideNormalRest(accountId: Int): Single + fun provideCustomRest(accountId: Int, token: String): Single + fun provideServiceRest(): Single + fun provideNormalHttpClient(accountId: Int): Single + fun provideRawHttpClient(@AccountType type: Int): Single +} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRetrofitProvider.kt deleted file mode 100644 index 68a73e3a7..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/IVkRetrofitProvider.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.ragnarok.fenrir.api - -import dev.ragnarok.fenrir.AccountType -import io.reactivex.rxjava3.core.Single -import okhttp3.OkHttpClient - -interface IVkRetrofitProvider { - fun provideNormalRetrofit(accountId: Int): Single - fun provideCustomRetrofit(accountId: Int, token: String): Single - fun provideServiceRetrofit(): Single - fun provideNormalHttpClient(accountId: Int): Single - fun provideRawHttpClient(@AccountType type: Int): Single -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRestProvider.kt similarity index 61% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRetrofitProvider.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRestProvider.kt index 1ccdfba7d..f8c516128 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRetrofitProvider.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRestProvider.kt @@ -6,45 +6,40 @@ import dev.ragnarok.fenrir.Constants import dev.ragnarok.fenrir.Constants.USER_AGENT import dev.ragnarok.fenrir.api.HttpLoggerAndParser.toRequestBuilder import dev.ragnarok.fenrir.api.HttpLoggerAndParser.vkHeader -import dev.ragnarok.fenrir.api.RetrofitWrapper.Companion.wrap -import dev.ragnarok.fenrir.kJson +import dev.ragnarok.fenrir.api.rest.SimplePostHttp import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.settings.IProxySettings import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.UncompressDefaultInterceptor import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack -import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.jsonMsgPackConverterFactory -import dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3.RxJava3CallAdapterFactory import io.reactivex.rxjava3.core.Single import okhttp3.FormBody import okhttp3.Interceptor import okhttp3.OkHttpClient -import retrofit2.Retrofit import java.util.concurrent.TimeUnit -class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private val proxySettings: IProxySettings) : - IOtherVkRetrofitProvider { - private val longpollRetrofitLock = Any() - private val localServerRetrofitLock = Any() - private var longpollRetrofitInstance: RetrofitWrapper? = null - private var localServerRetrofitInstance: RetrofitWrapper? = null +class OtherVkRestProvider @SuppressLint("CheckResult") constructor(private val proxySettings: IProxySettings) : + IOtherVkRestProvider { + private val longpollRestLock = Any() + private val localServerRestLock = Any() + private var longpollRestInstance: SimplePostHttp? = null + private var localServerRestInstance: SimplePostHttp? = null private fun onProxySettingsChanged() { - synchronized(longpollRetrofitLock) { - if (longpollRetrofitInstance != null) { - longpollRetrofitInstance?.cleanup() - longpollRetrofitInstance = null + synchronized(longpollRestLock) { + if (longpollRestInstance != null) { + longpollRestInstance?.stop() + longpollRestInstance = null } } - synchronized(localServerRetrofitLock) { - if (localServerRetrofitInstance != null) { - localServerRetrofitInstance?.cleanup() - localServerRetrofitInstance = null + synchronized(localServerRestLock) { + if (localServerRestInstance != null) { + localServerRestInstance?.stop() + localServerRestInstance = null } } } - override fun provideAuthRetrofit(): Single { + override fun provideAuthRest(): Single { return Single.fromCallable { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .readTimeout(15, TimeUnit.SECONDS) @@ -65,17 +60,11 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v ProxyUtil.applyProxyConfig(builder, proxySettings.activeProxy) HttpLoggerAndParser.adjust(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) - val retrofit = Retrofit.Builder() - .baseUrl("https://" + Settings.get().other().get_Auth_Domain() + "/") - .addConverterFactory(KCONVERTER_FACTORY) - .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) - .client(builder.build()) - .build() - wrap(retrofit, false) + SimplePostHttp("https://" + Settings.get().other().get_Auth_Domain(), builder) } } - override fun provideAuthServiceRetrofit(): Single { + override fun provideAuthServiceRest(): Single { return Single.fromCallable { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .readTimeout(15, TimeUnit.SECONDS) @@ -96,17 +85,14 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v ProxyUtil.applyProxyConfig(builder, proxySettings.activeProxy) HttpLoggerAndParser.adjust(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) - val retrofit = Retrofit.Builder() - .baseUrl("https://" + Settings.get().other().get_Api_Domain() + "/method/") - .addConverterFactory(KCONVERTER_FACTORY) - .addCallAdapterFactory(RX_ADAPTER_FACTORY) - .client(builder.build()) - .build() - wrap(retrofit, false) + SimplePostHttp( + "https://" + Settings.get().other().get_Api_Domain() + "/method", + builder + ) } } - private fun createLocalServerRetrofit(): Retrofit { + private fun createLocalServerRest(): SimplePostHttp { val localSettings = Settings.get().other().localServer val builder: OkHttpClient.Builder = OkHttpClient.Builder() .readTimeout(15, TimeUnit.SECONDS) @@ -141,17 +127,10 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v HttpLoggerAndParser.adjust(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) val url = Utils.firstNonEmptyString(localSettings.url, "https://debug.dev")!! - return Retrofit.Builder() - .baseUrl("$url/method/") - .addConverterFactory( - KCONVERTER_FACTORY - ) - .addCallAdapterFactory(RX_ADAPTER_FACTORY) - .client(builder.build()) - .build() + return SimplePostHttp("$url/method", builder) } - private fun createLongpollRetrofitInstance(): Retrofit { + private fun createLongpollRestInstance(): SimplePostHttp { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .readTimeout(25, TimeUnit.SECONDS) .connectTimeout(25, TimeUnit.SECONDS) @@ -170,45 +149,38 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v ProxyUtil.applyProxyConfig(builder, proxySettings.activeProxy) HttpLoggerAndParser.adjust(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) - return Retrofit.Builder() - .baseUrl("https://" + Settings.get().other().get_Api_Domain() + "/method/") // dummy - .addConverterFactory(KCONVERTER_FACTORY) - .addCallAdapterFactory(RX_ADAPTER_FACTORY) - .client(builder.build()) - .build() + return SimplePostHttp( + "https://" + Settings.get().other().get_Api_Domain() + "/method", + builder + ) } - override fun provideLocalServerRetrofit(): Single { + override fun provideLocalServerRest(): Single { return Single.fromCallable { - if (localServerRetrofitInstance == null) { - synchronized(localServerRetrofitLock) { - if (localServerRetrofitInstance == null) { - localServerRetrofitInstance = wrap(createLocalServerRetrofit()) + if (localServerRestInstance == null) { + synchronized(localServerRestLock) { + if (localServerRestInstance == null) { + localServerRestInstance = createLocalServerRest() } } } - localServerRetrofitInstance!! + localServerRestInstance!! } } - override fun provideLongpollRetrofit(): Single { + override fun provideLongpollRest(): Single { return Single.fromCallable { - if (longpollRetrofitInstance == null) { - synchronized(longpollRetrofitLock) { - if (longpollRetrofitInstance == null) { - longpollRetrofitInstance = wrap(createLongpollRetrofitInstance()) + if (longpollRestInstance == null) { + synchronized(longpollRestLock) { + if (longpollRestInstance == null) { + longpollRestInstance = createLongpollRestInstance() } } } - longpollRetrofitInstance!! + longpollRestInstance!! } } - companion object { - private val KCONVERTER_FACTORY = jsonMsgPackConverterFactory(kJson, MsgPack()) - private val RX_ADAPTER_FACTORY = RxJava3CallAdapterFactory.create() - } - init { proxySettings.observeActive() .subscribe { onProxySettingsChanged() } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/RetrofitWrapper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/RetrofitWrapper.kt deleted file mode 100644 index b1e7721ae..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/RetrofitWrapper.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.ragnarok.fenrir.api - -import retrofit2.Retrofit -import java.util.* - -class RetrofitWrapper private constructor( - private val retrofit: Retrofit, - private val withCaching: Boolean -) { - private val servicesCache: MutableMap, Any?>? = - if (withCaching) Collections.synchronizedMap(HashMap(4)) else null - - @Suppress("UNCHECKED_CAST") - fun create(serviceClass: Class): T { - if (!withCaching || servicesCache == null) { - return retrofit.create(serviceClass) - } - if (servicesCache.containsKey(serviceClass)) { - return servicesCache[serviceClass] as T - } - val service = retrofit.create(serviceClass) - servicesCache[serviceClass] = service - return service - } - - fun cleanup() { - servicesCache?.clear() - } - - companion object { - fun wrap(retrofit: Retrofit): RetrofitWrapper { - return RetrofitWrapper(retrofit, true) - } - - fun wrap(retrofit: Retrofit, withCaching: Boolean): RetrofitWrapper { - return RetrofitWrapper(retrofit, withCaching) - } - } - -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRestProvider.kt similarity index 50% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRetrofitProvider.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRestProvider.kt index 429884c43..70ea5049a 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRetrofitProvider.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRestProvider.kt @@ -5,48 +5,43 @@ import dev.ragnarok.fenrir.AccountType import dev.ragnarok.fenrir.Constants.USER_AGENT import dev.ragnarok.fenrir.api.HttpLoggerAndParser.toRequestBuilder import dev.ragnarok.fenrir.api.HttpLoggerAndParser.vkHeader -import dev.ragnarok.fenrir.api.RetrofitWrapper.Companion.wrap -import dev.ragnarok.fenrir.kJson +import dev.ragnarok.fenrir.api.rest.SimplePostHttp import dev.ragnarok.fenrir.settings.IProxySettings import dev.ragnarok.fenrir.settings.Settings -import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack -import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.jsonMsgPackConverterFactory -import dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3.RxJava3CallAdapterFactory import io.reactivex.rxjava3.core.Single import okhttp3.Interceptor import okhttp3.OkHttpClient -import retrofit2.Retrofit import java.util.concurrent.TimeUnit @SuppressLint("CheckResult") -class UploadRetrofitProvider(private val proxySettings: IProxySettings) : IUploadRetrofitProvider { - private val uploadRetrofitLock = Any() +class UploadRestProvider(private val proxySettings: IProxySettings) : IUploadRestProvider { + private val uploadRestLock = Any() @Volatile - private var uploadRetrofitInstance: RetrofitWrapper? = null + private var uploadRestInstance: SimplePostHttp? = null private fun onProxySettingsChanged() { - synchronized(uploadRetrofitLock) { - if (uploadRetrofitInstance != null) { - uploadRetrofitInstance?.cleanup() - uploadRetrofitInstance = null + synchronized(uploadRestLock) { + if (uploadRestInstance != null) { + uploadRestInstance?.stop() + uploadRestInstance = null } } } - override fun provideUploadRetrofit(): Single { + override fun provideUploadRest(): Single { return Single.fromCallable { - if (uploadRetrofitInstance == null) { - synchronized(uploadRetrofitLock) { - if (uploadRetrofitInstance == null) { - uploadRetrofitInstance = wrap(createUploadRetrofit(), true) + if (uploadRestInstance == null) { + synchronized(uploadRestLock) { + if (uploadRestInstance == null) { + uploadRestInstance = createUploadRest() } } } - uploadRetrofitInstance!! + uploadRestInstance!! } } - private fun createUploadRetrofit(): Retrofit { + private fun createUploadRest(): SimplePostHttp { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .readTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS) @@ -64,17 +59,10 @@ class UploadRetrofitProvider(private val proxySettings: IProxySettings) : IUploa ProxyUtil.applyProxyConfig(builder, proxySettings.activeProxy) HttpLoggerAndParser.adjustUpload(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) - return Retrofit.Builder() - .baseUrl("https://" + Settings.get().other().get_Api_Domain() + "/method/") // dummy - .addConverterFactory(KCONVERTER_FACTORY) - .addCallAdapterFactory(RX_ADAPTER_FACTORY) - .client(builder.build()) - .build() - } - - companion object { - private val KCONVERTER_FACTORY = jsonMsgPackConverterFactory(kJson, MsgPack()) - private val RX_ADAPTER_FACTORY = RxJava3CallAdapterFactory.create() + return SimplePostHttp( + "https://" + Settings.get().other().get_Api_Domain() + "/method", + builder + ) } init { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt index 00f1c7b69..7c425a56e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt @@ -15,7 +15,7 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { override fun createDefaultVkHttpClient( accountId: Int, config: ProxyConfig? - ): OkHttpClient { + ): OkHttpClient.Builder { return createDefaultVkApiOkHttpClient( DefaultVkApiInterceptor( accountId, @@ -28,7 +28,7 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { accountId: Int, token: String, config: ProxyConfig? - ): OkHttpClient { + ): OkHttpClient.Builder { return createDefaultVkApiOkHttpClient( CustomTokenVkApiInterceptor( token, @@ -39,7 +39,7 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { ) } - override fun createServiceVkHttpClient(config: ProxyConfig?): OkHttpClient { + override fun createServiceVkHttpClient(config: ProxyConfig?): OkHttpClient.Builder { return createDefaultVkApiOkHttpClient( CustomTokenVkApiInterceptor( BuildConfig.SERVICE_TOKEN, @@ -53,7 +53,7 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { private fun createDefaultVkApiOkHttpClient( interceptor: AbsVkApiInterceptor, config: ProxyConfig? - ): OkHttpClient { + ): OkHttpClient.Builder { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .addInterceptor(interceptor) .readTimeout(15, TimeUnit.SECONDS) @@ -68,13 +68,13 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { ProxyUtil.applyProxyConfig(builder, config) HttpLoggerAndParser.adjust(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) - return builder.build() + return builder } override fun createRawVkApiOkHttpClient( @AccountType type: Int, config: ProxyConfig? - ): OkHttpClient { + ): OkHttpClient.Builder { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .readTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS) @@ -88,6 +88,6 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { ProxyUtil.applyProxyConfig(builder, config) HttpLoggerAndParser.adjust(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) - return builder.build() + return builder } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRestProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRestProvider.kt new file mode 100644 index 000000000..59f4b8fab --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRestProvider.kt @@ -0,0 +1,107 @@ +package dev.ragnarok.fenrir.api + +import android.annotation.SuppressLint +import dev.ragnarok.fenrir.AccountType +import dev.ragnarok.fenrir.api.rest.SimplePostHttp +import dev.ragnarok.fenrir.settings.IProxySettings +import dev.ragnarok.fenrir.settings.Settings +import io.reactivex.rxjava3.core.Single +import okhttp3.OkHttpClient +import java.util.* + +@SuppressLint("CheckResult") +class VkRestProvider( + private val proxyManager: IProxySettings, + private val clientFactory: IVkMethodHttpClientFactory +) : IVkRestProvider { + private val restCacheLock = Any() + private val serviceRestLock = Any() + + private val restCache = Collections.synchronizedMap(HashMap(1)) + + @Volatile + private var serviceRest: SimplePostHttp? = null + private fun onProxySettingsChanged() { + synchronized(restCacheLock) { + for ((_, value) in restCache) { + value?.stop() + } + restCache.clear() + } + } + + override fun provideNormalRest(accountId: Int): Single { + return Single.fromCallable { + var rest: SimplePostHttp? + synchronized(restCacheLock) { + restCache[accountId]?.let { + return@fromCallable it + } + val client = clientFactory.createDefaultVkHttpClient( + accountId, + proxyManager.activeProxy + ) + rest = createDefaultVkApiRest(client) + restCache.put(accountId, rest) + } + rest!! + } + } + + override fun provideCustomRest(accountId: Int, token: String): Single { + return Single.fromCallable { + val client = clientFactory.createCustomVkHttpClient( + accountId, + token, + proxyManager.activeProxy + ) + createDefaultVkApiRest(client) + } + } + + override fun provideServiceRest(): Single { + return Single.fromCallable { + if (serviceRest == null) { + synchronized(serviceRestLock) { + if (serviceRest == null) { + val client = clientFactory.createServiceVkHttpClient( + proxyManager.activeProxy + ) + serviceRest = createDefaultVkApiRest(client) + } + } + } + serviceRest!! + } + } + + override fun provideNormalHttpClient(accountId: Int): Single { + return Single.fromCallable { + clientFactory.createDefaultVkHttpClient( + accountId, + proxyManager.activeProxy + ) + } + } + + override fun provideRawHttpClient(@AccountType type: Int): Single { + return Single.fromCallable { + clientFactory.createRawVkApiOkHttpClient( + type, + proxyManager.activeProxy + ) + } + } + + private fun createDefaultVkApiRest(okHttpClient: OkHttpClient.Builder): SimplePostHttp { + return SimplePostHttp( + "https://" + Settings.get().other().get_Api_Domain() + "/method", + okHttpClient + ) + } + + init { + proxyManager.observeActive() + .subscribe { onProxySettingsChanged() } + } +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRetrofitProvider.kt deleted file mode 100644 index 8dc362027..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkRetrofitProvider.kt +++ /dev/null @@ -1,121 +0,0 @@ -package dev.ragnarok.fenrir.api - -import android.annotation.SuppressLint -import dev.ragnarok.fenrir.AccountType -import dev.ragnarok.fenrir.api.RetrofitWrapper.Companion.wrap -import dev.ragnarok.fenrir.kJson -import dev.ragnarok.fenrir.settings.IProxySettings -import dev.ragnarok.fenrir.settings.Settings -import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack -import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.jsonMsgPackConverterFactory -import dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3.RxJava3CallAdapterFactory -import io.reactivex.rxjava3.core.Single -import okhttp3.OkHttpClient -import retrofit2.Retrofit -import java.util.* - -@SuppressLint("CheckResult") -class VkRetrofitProvider( - private val proxyManager: IProxySettings, - private val clientFactory: IVkMethodHttpClientFactory -) : IVkRetrofitProvider { - private val retrofitCacheLock = Any() - private val serviceRetrofitLock = Any() - - private val retrofitCache = Collections.synchronizedMap(HashMap(1)) - - @Volatile - private var serviceRetrofit: RetrofitWrapper? = null - private fun onProxySettingsChanged() { - synchronized(retrofitCacheLock) { - for ((_, value) in retrofitCache) { - value?.cleanup() - } - retrofitCache.clear() - } - } - - override fun provideNormalRetrofit(accountId: Int): Single { - return Single.fromCallable { - var retrofit: RetrofitWrapper? - synchronized(retrofitCacheLock) { - retrofitCache[accountId]?.let { - return@fromCallable it - } - val client = clientFactory.createDefaultVkHttpClient( - accountId, - proxyManager.activeProxy - ) - retrofit = createDefaultVkApiRetrofit(client) - retrofitCache.put(accountId, retrofit) - } - retrofit!! - } - } - - override fun provideCustomRetrofit(accountId: Int, token: String): Single { - return Single.fromCallable { - val client = clientFactory.createCustomVkHttpClient( - accountId, - token, - proxyManager.activeProxy - ) - createDefaultVkApiRetrofit(client) - } - } - - override fun provideServiceRetrofit(): Single { - return Single.fromCallable { - if (serviceRetrofit == null) { - synchronized(serviceRetrofitLock) { - if (serviceRetrofit == null) { - val client = clientFactory.createServiceVkHttpClient( - proxyManager.activeProxy - ) - serviceRetrofit = createDefaultVkApiRetrofit(client) - } - } - } - serviceRetrofit!! - } - } - - override fun provideNormalHttpClient(accountId: Int): Single { - return Single.fromCallable { - clientFactory.createDefaultVkHttpClient( - accountId, - proxyManager.activeProxy - ) - } - } - - override fun provideRawHttpClient(@AccountType type: Int): Single { - return Single.fromCallable { - clientFactory.createRawVkApiOkHttpClient( - type, - proxyManager.activeProxy - ) - } - } - - private fun createDefaultVkApiRetrofit(okHttpClient: OkHttpClient): RetrofitWrapper { - return wrap( - Retrofit.Builder() - .baseUrl("https://" + Settings.get().other().get_Api_Domain() + "/method/") - .addConverterFactory(KCONVERTER_FACTORY) - .addCallAdapterFactory(RX_ADAPTER_FACTORY) - .client(okHttpClient) - .build() - ) - } - - companion object { - private val KCONVERTER_FACTORY = jsonMsgPackConverterFactory(kJson, MsgPack()) - private val RX_ADAPTER_FACTORY = RxJava3CallAdapterFactory.create() - } - - init { - proxyManager.observeActive() - .subscribe { onProxySettingsChanged() } - } -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt index adfce6b27..0b7471ef9 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt @@ -9,42 +9,42 @@ import dev.ragnarok.fenrir.api.model.IAttachmentToken import dev.ragnarok.fenrir.api.model.Params import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.VkResponse +import dev.ragnarok.fenrir.api.rest.HttpException +import dev.ragnarok.fenrir.api.rest.IServiceRest import dev.ragnarok.fenrir.service.ApiErrorCodes import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.refresh.RefreshToken import dev.ragnarok.fenrir.util.serializeble.json.decodeFromStream import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack -import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException -import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.Serializer import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.SingleEmitter import io.reactivex.rxjava3.exceptions.Exceptions import io.reactivex.rxjava3.functions.Function +import kotlinx.serialization.KSerializer import okhttp3.* -import java.lang.reflect.Type import java.util.* -internal open class AbsApi(val accountId: Int, private val retrofitProvider: IServiceProvider) { - fun provideService(serviceClass: Class, vararg tokenTypes: Int): Single { +internal open class AbsApi(val accountId: Int, private val restProvider: IServiceProvider) { + fun provideService(serviceClass: T, vararg tokenTypes: Int): Single { var pTokenTypes: IntArray = tokenTypes if (pTokenTypes.nullOrEmpty()) { pTokenTypes = intArrayOf(TokenType.USER) // user by default } - return retrofitProvider.provideService(accountId, serviceClass, *pTokenTypes) + return restProvider.provideService(accountId, serviceClass, *pTokenTypes) } @Suppress("unchecked_cast") private fun rawVKRequest( method: String, postParams: Map, - javaClass: Type, serializerType: Serializer + serializerType: KSerializer<*> ): Single> { val bodyBuilder = FormBody.Builder() for ((key, value) in postParams) { bodyBuilder.add(key, value) } - return Includes.networkInterfaces.getVkRetrofitProvider().provideNormalHttpClient(accountId) + return Includes.networkInterfaces.getVkRestProvider().provideNormalHttpClient(accountId) .flatMap { client -> Single.create { emitter: SingleEmitter -> val request: Request = Request.Builder() @@ -53,35 +53,29 @@ internal open class AbsApi(val accountId: Int, private val retrofitProvider: ISe ) .post(bodyBuilder.build()) .build() - val call = client.newCall(request) + val call = client.build().newCall(request) emitter.setCancellable { call.cancel() } try { val response = call.execute() if (!response.isSuccessful) { - emitter.onError(HttpCodeException(response.code)) + emitter.tryOnError(HttpException(response.code)) } else { emitter.onSuccess(response) } response.close() } catch (e: Exception) { - emitter.onError(e) + emitter.tryOnError(e) } } } .map { response -> val k = if (response.body.isMsgPack()) MsgPack().decodeFromOkioStream( - serializerType.serializer( - javaClass - ), response.body.source() + serializerType, response.body.source() ) as BaseResponse else kJson.decodeFromStream( - serializerType.serializer( - javaClass - ), response.body.byteStream() + serializerType, response.body.byteStream() ) as BaseResponse k.error?.let { - it.type = javaClass it.serializer = serializerType - val o = ArrayList() for ((key, value) in postParams) { val tmp = Params() @@ -107,7 +101,7 @@ internal open class AbsApi(val accountId: Int, private val retrofitProvider: ISe for ((key, value) in postParams) { bodyBuilder.add(key, value) } - return Includes.networkInterfaces.getVkRetrofitProvider().provideNormalHttpClient(accountId) + return Includes.networkInterfaces.getVkRestProvider().provideNormalHttpClient(accountId) .flatMap { client -> Single.create { emitter: SingleEmitter -> val request: Request = Request.Builder() @@ -116,18 +110,18 @@ internal open class AbsApi(val accountId: Int, private val retrofitProvider: ISe ) .post(bodyBuilder.build()) .build() - val call = client.newCall(request) + val call = client.build().newCall(request) emitter.setCancellable { call.cancel() } try { val response = call.execute() if (!response.isSuccessful) { - emitter.onError(HttpCodeException(response.code)) + emitter.tryOnError(HttpException(response.code)) } else { emitter.onSuccess(response) } response.close() } catch (e: Exception) { - emitter.onError(e) + emitter.tryOnError(e) } } } @@ -229,7 +223,6 @@ internal open class AbsApi(val accountId: Int, private val retrofitProvider: ISe return@Function rawVKRequest( method, params, - it.type ?: throw UnsupportedOperationException(), it.serializer ?: throw UnsupportedOperationException() ) .map(extractResponseWithErrorHandling()) @@ -301,19 +294,16 @@ internal open class AbsApi(val accountId: Int, private val retrofitProvider: ISe return sb.toString() } - fun formatAttachmentToken(token: IAttachmentToken): String { return token.format() } - fun toQuotes(word: String?): String? { return if (word == null) { null } else "\"" + word + "\"" } - fun integerFromBoolean(value: Boolean?): Int? { return if (value == null) null else if (value) 1 else 0 } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt index eca885dff..ad51da362 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt @@ -14,7 +14,7 @@ import io.reactivex.rxjava3.core.Single internal class AccountApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IAccountApi { override fun ban(ownerId: Int): Single { - return provideService(IAccountService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IAccountService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .ban(ownerId) @@ -23,7 +23,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : } override fun unban(ownerId: Int): Single { - return provideService(IAccountService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IAccountService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .unban(ownerId) @@ -36,7 +36,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : offset: Int?, fields: String? ): Single { - return provideService(IAccountService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IAccountService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .getBanned(count, offset, fields) @@ -45,7 +45,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : } override fun unregisterDevice(deviceId: String?): Single { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMap { service -> service.unregisterDevice(deviceId) .map(extractResponseWithErrorHandling()) @@ -65,7 +65,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : systemVersion: String?, settings: String? ): Single { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMap { service -> service .registerDevice( @@ -86,24 +86,24 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : } override fun setOffline(): Single { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMap { service -> service - .setOffline() + .setOffline .map(extractResponseWithErrorHandling()) .map { it == 1 } } } override val profileInfo: Single - get() = provideService(IAccountService::class.java, TokenType.USER) + get() = provideService(IAccountService(), TokenType.USER) .flatMap { service -> service .profileInfo .map(extractResponseWithErrorHandling()) } override val pushSettings: Single - get() = provideService(IAccountService::class.java, TokenType.USER) + get() = provideService(IAccountService(), TokenType.USER) .flatMap { service -> service .pushSettings @@ -119,7 +119,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : home_town: String?, sex: Int? ): Single { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMap { service -> service .saveProfileInfo( @@ -136,7 +136,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : } override fun getCounters(filter: String?): Single { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMap { service -> service .getCounters(filter) @@ -150,7 +150,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : nonce: String?, timestamp: Long? ): Single { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMap { service -> service .refreshToken(receipt, receipt2, nonce, timestamp) @@ -159,7 +159,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : } override fun importMessagesContacts(contacts: String?): Completable { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMapCompletable { service -> service .importMessagesContacts(contacts) @@ -168,7 +168,7 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : } override fun getContactList(offset: Int?, count: Int?): Single { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMap { service -> service .getContactList(offset, count, 1, VKApiUser.ALL_FIELDS) @@ -177,10 +177,10 @@ internal class AccountApi(accountId: Int, provider: IServiceProvider) : } override fun resetMessagesContacts(): Completable { - return provideService(IAccountService::class.java, TokenType.USER) + return provideService(IAccountService(), TokenType.USER) .flatMapCompletable { service -> service - .resetMessagesContacts() + .resetMessagesContacts .flatMapCompletable(checkResponseWithErrorHandling()) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AudioApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AudioApi.kt index b0995e9c1..cf11d5ee1 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AudioApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AudioApi.kt @@ -20,7 +20,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : override fun setBroadcast(audio: AccessIdPair, targetIds: Collection): Single> { val f = join(setOf(audio), ",") { AccessIdPair.format(it) } val s = join(targetIds, ",") - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .setBroadcast(f, s) @@ -38,7 +38,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .search( @@ -60,7 +60,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .searchArtists(query, offset, count) @@ -73,7 +73,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .searchPlaylists(query, offset, count) @@ -82,7 +82,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun restore(audioId: Int, ownerId: Int?): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .restore(audioId, ownerId) @@ -91,7 +91,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun delete(audioId: Int, ownerId: Int): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .delete(audioId, ownerId) @@ -113,7 +113,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : title: String?, text: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .edit(ownerId, audioId, artist, title, text) @@ -122,7 +122,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun add(audioId: Int, ownerId: Int, groupId: Int?, accessKey: String?): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .add(audioId, ownerId, groupId, accessKey) @@ -135,7 +135,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : title: String?, description: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .createPlaylist(ownerId, title, description) @@ -149,7 +149,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : title: String?, description: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .editPlaylist(ownerId, playlist_id, title, description) @@ -162,7 +162,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : playlist_id: Int, audio_ids: Collection ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .removeFromPlaylist( @@ -178,7 +178,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : playlist_id: Int, audio_ids: Collection ): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .addToPlaylist( @@ -190,7 +190,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun reorder(ownerId: Int, audio_id: Int, before: Int?, after: Int?): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .reorder(ownerId, audio_id, before, after) @@ -199,7 +199,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun trackEvents(events: String?): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .trackEvents(events) @@ -210,7 +210,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : override fun getCatalogV2Sections( owner_id: Int, artist_id: String?, url: String?, query: String?, context: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> (if (artist_id.nonNullNoEmpty()) service.getCatalogV2Artist( artist_id, @@ -229,7 +229,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : override fun getCatalogV2BlockItems( block_id: String, start_from: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getCatalogV2BlockItems(block_id, start_from) @@ -241,7 +241,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : section_id: String, start_from: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service.getCatalogV2Section(section_id, start_from) .map(extractResponseWithErrorHandling()) @@ -249,7 +249,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun deletePlaylist(playlist_id: Int, ownerId: Int): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .deletePlaylist(playlist_id, ownerId) @@ -262,7 +262,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : ownerId: Int, accessKey: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .followPlaylist(playlist_id, ownerId, accessKey) @@ -271,7 +271,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun clonePlaylist(playlist_id: Int, ownerId: Int): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .clonePlaylist(playlist_id, ownerId) @@ -284,7 +284,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : ownerId: Int, accessKey: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getPlaylistById(playlist_id, ownerId, accessKey) @@ -299,7 +299,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : count: Int?, accessKey: String? ): Single> { - return provideService(IAudioService::class.java).flatMap { service -> + return provideService(IAudioService()).flatMap { service -> service[playlist_id, ownerId, offset, count, accessKey].map( extractResponseWithErrorHandling() ) @@ -311,7 +311,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IAudioService::class.java).flatMap { service -> + return provideService(IAudioService()).flatMap { service -> service.getAudiosByArtist(artist_id, offset, count).map( extractResponseWithErrorHandling() ) @@ -322,7 +322,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : foreign: Int?, genre: Int?, count: Int? ): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getPopular(foreign, genre, count) @@ -331,7 +331,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun getRecommendations(audioOwnerId: Int?, count: Int?): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getRecommendations(audioOwnerId, count) @@ -340,7 +340,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun getRecommendationsByAudio(audio: String?, count: Int?): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getRecommendationsByAudio(audio, count) @@ -353,7 +353,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : offset: Int, count: Int ): Single> { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getPlaylists(owner_id, offset, count) @@ -362,7 +362,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun getPlaylistsCustom(code: String?): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getPlaylistsCustom(code) @@ -375,7 +375,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : ids.add(AccessIdPair(i.id, i.ownerId, i.accessKey)) } val audio_string = join(ids, ",") { AccessIdPair.format(it) } - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getById(audio_string) @@ -389,7 +389,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : ids.add(AccessIdPair(i.id, i.ownerId, i.accessKey)) } val audio_string = join(ids, ",") { AccessIdPair.format(it) } - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getByIdVersioned(audio_string, "5.90") @@ -398,7 +398,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override fun getLyrics(lyrics_id: Int): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getLyrics(lyrics_id) @@ -407,7 +407,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : } override val uploadServer: Single - get() = provideService(IAudioService::class.java) + get() = provideService(IAudioService()) .flatMap { service -> service.uploadServer .map(extractResponseWithErrorHandling()) @@ -420,7 +420,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : artist: String?, title: String? ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service.save(server, audio, hash, artist, title) .map(extractResponseWithErrorHandling()) @@ -430,7 +430,7 @@ internal class AudioApi(accountId: Int, provider: IServiceProvider) : override fun getArtistById( artist_id: String ): Single { - return provideService(IAudioService::class.java) + return provideService(IAudioService()) .flatMap { service -> service .getArtistById(artist_id) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt index 43e89ffe2..e8e7705d8 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt @@ -6,15 +6,11 @@ import dev.ragnarok.fenrir.api.interfaces.IAuthApi import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse import dev.ragnarok.fenrir.api.model.response.BaseResponse -import dev.ragnarok.fenrir.kJson import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.util.Utils.getDeviceId -import dev.ragnarok.fenrir.util.serializeble.json.decodeFromStream import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.core.SingleTransformer import io.reactivex.rxjava3.exceptions.Exceptions import io.reactivex.rxjava3.functions.Function -import retrofit2.HttpException class AuthApi(private val service: IDirectLoginSeviceProvider) : IAuthApi { override fun directLogin( @@ -43,7 +39,36 @@ class AuthApi(private val service: IDirectLoginSeviceProvider) : IAuthApi { ), 1 ) - .compose(withHttpErrorHandling()) + .flatMap { response -> + when { + "need_captcha".equals(response.error, ignoreCase = true) -> { + Single.error( + CaptchaNeedException( + response.captchaSid, + response.captchaImg + ) + ) + } + "need_validation".equals(response.error, ignoreCase = true) -> { + Single.error( + NeedValidationException( + response.validationType, + response.redirect_uri, + response.validation_sid + ) + ) + } + response.error.nonNullNoEmpty() -> { + Single.error( + AuthException( + response.error.orEmpty(), + response.errorDescription + ) + ) + } + else -> Single.just(response) + } + } } } @@ -63,7 +88,6 @@ class AuthApi(private val service: IDirectLoginSeviceProvider) : IAuthApi { } companion object { - private val BASE_RESPONSE_KSERIALIZER = kJson fun extractResponseWithErrorHandling(): Function, T> { return Function { response: BaseResponse -> response.error?.let { throw Exceptions.propagate(ApiException(it)) } @@ -71,51 +95,5 @@ class AuthApi(private val service: IDirectLoginSeviceProvider) : IAuthApi { ?: throw NullPointerException("VK return null response")) } } - - internal fun withHttpErrorHandling(): SingleTransformer { - return SingleTransformer { single: Single -> - single.onErrorResumeNext { throwable -> - if (throwable is HttpException) { - try { - val body = throwable.response()?.errorBody() - val response: LoginResponse = - BASE_RESPONSE_KSERIALIZER.decodeFromStream( - body?.byteStream()!! - ) - - //{"error":"need_captcha","captcha_sid":"846773809328","captcha_img":"https:\/\/api.vk.com\/captcha.php?sid=846773809328"} - if ("need_captcha".equals(response.error, ignoreCase = true)) { - return@onErrorResumeNext Single.error( - CaptchaNeedException( - response.captchaSid, - response.captchaImg - ) - ) - } - if ("need_validation".equals(response.error, ignoreCase = true)) { - return@onErrorResumeNext Single.error( - NeedValidationException( - response.validationType, - response.redirect_uri, - response.validation_sid - ) - ) - } - response.error.nonNullNoEmpty { - return@onErrorResumeNext Single.error( - AuthException( - it, - response.errorDescription - ) - ) - } - } catch (e: Exception) { - e.printStackTrace() - } - } - Single.error(throwable) - } - } - } } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/BoardApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/BoardApi.kt index a1da0e24d..14245aead 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/BoardApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/BoardApi.kt @@ -23,7 +23,7 @@ internal class BoardApi(accountId: Int, provider: IServiceProvider) : sort: String?, fields: String? ): Single { - return provideService(IBoardService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IBoardService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getComments( @@ -42,7 +42,7 @@ internal class BoardApi(accountId: Int, provider: IServiceProvider) : } override fun restoreComment(groupId: Int, topicId: Int, commentId: Int): Single { - return provideService(IBoardService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IBoardService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.restoreComment(groupId, topicId, commentId) .map(extractResponseWithErrorHandling()) @@ -51,7 +51,7 @@ internal class BoardApi(accountId: Int, provider: IServiceProvider) : } override fun deleteComment(groupId: Int, topicId: Int, commentId: Int): Single { - return provideService(IBoardService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IBoardService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.deleteComment(groupId, topicId, commentId) .map(extractResponseWithErrorHandling()) @@ -64,7 +64,7 @@ internal class BoardApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int?, extended: Boolean?, preview: Int?, previewLength: Int?, fields: String? ): Single { - return provideService(IBoardService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IBoardService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getTopics( @@ -95,7 +95,7 @@ internal class BoardApi(accountId: Int, provider: IServiceProvider) : groupId: Int, topicId: Int, commentId: Int, message: String?, attachments: Collection? ): Single { - return provideService(IBoardService::class.java, TokenType.USER) + return provideService(IBoardService(), TokenType.USER) .flatMap { service -> service.editComment( groupId, @@ -120,7 +120,7 @@ internal class BoardApi(accountId: Int, provider: IServiceProvider) : stickerId: Int?, generatedUniqueId: Int? ): Single { - return provideService(IBoardService::class.java, TokenType.USER) + return provideService(IBoardService(), TokenType.USER) .flatMap { service -> service .addComment( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/CommentsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/CommentsApi.kt index 5ad1a36d6..8954c4908 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/CommentsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/CommentsApi.kt @@ -23,7 +23,7 @@ internal class CommentsApi(accountId: Int, provider: IServiceProvider) : fields: String? ): Single { val thread_id = threadComment ?: 0 - return provideService(ICommentsService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(ICommentsService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service["var comment_id = Args.comment_id;\n" + "var owner_id = Args.owner_id;\n" + diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DatabaseApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DatabaseApi.kt index ec586e113..9a97b620d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DatabaseApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DatabaseApi.kt @@ -13,7 +13,7 @@ import io.reactivex.rxjava3.core.Single internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IDatabaseApi { override fun getCitiesById(cityIds: Collection): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getCitiesById(join(cityIds, ",") { obj: Any -> obj.toString() }) @@ -27,7 +27,7 @@ internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service.getCountries(integerFromBoolean(needAll), code, offset, count) .map(extractResponseWithErrorHandling()) @@ -35,7 +35,7 @@ internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : } override fun getSchoolClasses(countryId: Int?): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getSchoolClasses(countryId) @@ -44,7 +44,7 @@ internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : } override fun getChairs(facultyId: Int, offset: Int?, count: Int?): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getChairs(facultyId, offset, count) @@ -57,7 +57,7 @@ internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getFaculties(universityId, offset, count) @@ -72,7 +72,7 @@ internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getUniversities(query, countryId, cityId, offset, count) @@ -86,7 +86,7 @@ internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getSchools(query, cityId, offset, count) @@ -102,7 +102,7 @@ internal class DatabaseApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IDatabaseService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IDatabaseService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getCities( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DocsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DocsApi.kt index 096d7017b..53d66ffc8 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DocsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/DocsApi.kt @@ -14,7 +14,7 @@ import io.reactivex.rxjava3.core.Single internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IDocsApi { override fun delete(ownerId: Int?, docId: Int): Single { - return provideService(IDocsService::class.java, TokenType.USER) + return provideService(IDocsService(), TokenType.USER) .flatMap { service -> service.delete(ownerId, docId) .map(extractResponseWithErrorHandling()) @@ -23,7 +23,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun add(ownerId: Int, docId: Int, accessKey: String?): Single { - return provideService(IDocsService::class.java, TokenType.USER) + return provideService(IDocsService(), TokenType.USER) .flatMap { service -> service.add(ownerId, docId, accessKey) .map(extractResponseWithErrorHandling()) @@ -33,7 +33,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco override fun getById(pairs: Collection): Single> { val ids = join(pairs, ",") { AccessIdPair.format(it) } - return provideService(IDocsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IDocsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getById(ids) .map(extractResponseWithErrorHandling()) @@ -41,7 +41,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun search(query: String?, count: Int?, offset: Int?): Single> { - return provideService(IDocsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IDocsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.search(query, count, offset) .map(extractResponseWithErrorHandling()) @@ -49,7 +49,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun save(file: String?, title: String?, tags: String?): Single { - return provideService(IDocsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IDocsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.save(file, title, tags) .map(extractResponseWithErrorHandling()) @@ -60,7 +60,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco peerId: Int?, type: String? ): Single { - return provideService(IDocsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IDocsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getMessagesUploadServer(peerId, type) .map(extractResponseWithErrorHandling()) @@ -68,7 +68,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun getUploadServer(groupId: Int?): Single { - return provideService(IDocsService::class.java, TokenType.USER) + return provideService(IDocsService(), TokenType.USER) .flatMap { service -> service.getUploadServer(groupId) .map(extractResponseWithErrorHandling()) @@ -80,7 +80,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco group_id: Int?, name: String? ): Single { - return provideService(IDocsService::class.java, TokenType.USER) + return provideService(IDocsService(), TokenType.USER) .flatMap { service -> service.getVideoServer(isPrivate, group_id, name) .map(extractResponseWithErrorHandling()) @@ -93,7 +93,7 @@ internal class DocsApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco offset: Int?, type: Int? ): Single> { - return provideService(IDocsService::class.java, TokenType.USER) + return provideService(IDocsService(), TokenType.USER) .flatMap { service -> service[ownerId, count, offset, type] .map(extractResponseWithErrorHandling()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FaveApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FaveApi.kt index a809132c0..0a5fff83c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FaveApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FaveApi.kt @@ -20,7 +20,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco fields: String?, type: String? ): Single> { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.getPages(offset, count, type, fields) .map(extractResponseWithErrorHandling()) @@ -28,7 +28,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun getPhotos(offset: Int?, count: Int?): Single> { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.getPhotos(offset, count) .map(extractResponseWithErrorHandling()) @@ -36,7 +36,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun getVideos(offset: Int?, count: Int?): Single> { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.getVideos(offset, count, "video", 1, UserColumns.API_FIELDS) .map(extractResponseWithErrorHandling()) @@ -52,7 +52,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun getArticles(offset: Int?, count: Int?): Single> { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.getArticles(offset, count, "article", 1, UserColumns.API_FIELDS) .map(extractResponseWithErrorHandling()) @@ -72,7 +72,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco offset: Int?, count: Int? ): Single> { - return provideService(IFaveService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IFaveService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getOwnerPublishedArticles( owner_id, @@ -87,7 +87,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun getPosts(offset: Int?, count: Int?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.getPosts(offset, count, "post", 1, UserColumns.API_FIELDS) .map(extractResponseWithErrorHandling()) @@ -95,7 +95,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun getLinks(offset: Int?, count: Int?): Single> { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.getLinks(offset, count, "link", 1, UserColumns.API_FIELDS) .map(extractResponseWithErrorHandling()) @@ -103,7 +103,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun getProducts(offset: Int?, count: Int?): Single> { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.getProducts(offset, count, "product", 1, UserColumns.API_FIELDS) .map(extractResponseWithErrorHandling()) @@ -119,7 +119,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun addPage(userId: Int?, groupId: Int?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.addPage(userId, groupId) .map(extractResponseWithErrorHandling()) @@ -128,7 +128,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun addLink(link: String?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.addLink(link) .map(extractResponseWithErrorHandling()) @@ -137,7 +137,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun addVideo(owner_id: Int?, id: Int?, access_key: String?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.addVideo(owner_id, id, access_key) .map(extractResponseWithErrorHandling()) @@ -146,7 +146,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun addArticle(url: String?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.addArticle(url) .map(extractResponseWithErrorHandling()) @@ -155,7 +155,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun addProduct(id: Int, owner_id: Int, access_key: String?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.addProduct(id, owner_id, access_key) .map(extractResponseWithErrorHandling()) @@ -164,7 +164,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun addPost(owner_id: Int?, id: Int?, access_key: String?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.addPost(owner_id, id, access_key) .map(extractResponseWithErrorHandling()) @@ -173,7 +173,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun removePage(userId: Int?, groupId: Int?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.removePage(userId, groupId) .map(extractResponseWithErrorHandling()) @@ -182,7 +182,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun removeLink(linkId: String?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.removeLink(linkId) .map(extractResponseWithErrorHandling()) @@ -191,7 +191,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun removeArticle(owner_id: Int?, article_id: Int?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.removeArticle(owner_id, article_id) .map(extractResponseWithErrorHandling()) @@ -200,7 +200,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun removeProduct(id: Int?, owner_id: Int?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.removeProduct(id, owner_id) .map(extractResponseWithErrorHandling()) @@ -209,7 +209,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun removePost(owner_id: Int?, id: Int?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.removePost(owner_id, id) .map(extractResponseWithErrorHandling()) @@ -218,7 +218,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun removeVideo(owner_id: Int?, id: Int?): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.removeVideo(owner_id, id) .map(extractResponseWithErrorHandling()) @@ -227,7 +227,7 @@ internal class FaveApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun pushFirst(owner_id: Int): Single { - return provideService(IFaveService::class.java, TokenType.USER) + return provideService(IFaveService(), TokenType.USER) .flatMap { service -> service.pushFirst( "var owner_id = Args.owner_id;\n" + diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FriendsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FriendsApi.kt index 779cddfd0..525e8afe0 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FriendsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/FriendsApi.kt @@ -39,7 +39,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : "\n" + "return {\"uids\":uids, \"profiles\":profiles};" val formattedCode = String.format(code, userId, count, offset, targetFields, targetOrder) - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service .getOnline(formattedCode) @@ -73,7 +73,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : userId: Int?, order: String?, listId: Int?, count: Int?, offset: Int?, fields: String?, nameCase: String? ): Single> { - return provideService(IFriendsService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IFriendsService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service[userId, order, listId, count, offset, fields, nameCase] .map(extractResponseWithErrorHandling()) @@ -81,7 +81,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : } override fun getByPhones(phones: String?, fields: String?): Single> { - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service.getByPhones(phones, fields) .map(extractResponseWithErrorHandling()) @@ -93,7 +93,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : fields: String?, nameCase: String? ): Single> { - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service.getRecommendations(count, fields, nameCase) .map(extractResponseWithErrorHandling()) @@ -101,7 +101,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : } override fun getLists(userId: Int?, returnSystem: Boolean?): Single> { - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service.getLists(userId, integerFromBoolean(returnSystem)) .map(extractResponseWithErrorHandling()) @@ -109,7 +109,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : } override fun delete(userId: Int): Single { - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service.delete(userId) .map(extractResponseWithErrorHandling()) @@ -117,7 +117,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : } override fun add(userId: Int, text: String?, follow: Boolean?): Single { - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service.add(userId, text, integerFromBoolean(follow)) .map(extractResponseWithErrorHandling()) @@ -132,7 +132,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service.search(userId, query, fields, nameCase, offset, count) .map(extractResponseWithErrorHandling()) @@ -171,7 +171,7 @@ internal class FriendsApi(accountId: Int, provider: IServiceProvider) : // MutualFriendsResponse data = convertJsonResponse(response.get(), MutualFriendsResponse.class); // return data.profiles; // }); - return provideService(IFriendsService::class.java, TokenType.USER) + return provideService(IFriendsService(), TokenType.USER) .flatMap { service -> service.getMutual(formattedCode) .map(extractResponseWithErrorHandling()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/GroupsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/GroupsApi.kt index e8c633222..5db1ba6bb 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/GroupsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/GroupsApi.kt @@ -25,7 +25,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : contactEmail: String?, contactPhone: String?, ): Completable { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMapCompletable { service -> service .editManager( @@ -56,7 +56,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : obscene_stopwords: Int?, obscene_words: String? ): Completable { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMapCompletable { service -> service .edit( @@ -74,7 +74,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : } override fun unban(groupId: Int, ownerId: Int): Completable { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMapCompletable { service -> service .unban(groupId, ownerId) @@ -91,7 +91,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : comment: String?, commentVisible: Boolean? ): Completable { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMapCompletable { service -> service .ban( @@ -108,7 +108,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : } override fun getSettings(groupId: Int): Single { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .getSettings(groupId) @@ -121,7 +121,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : offset: Int, count: Int ): Single> { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getMarketAlbums(owner_id, offset, count) .map(extractResponseWithErrorHandling()) @@ -135,7 +135,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : count: Int, extended: Int? ): Single> { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getMarket(owner_id, album_id, offset, count, extended) .map(extractResponseWithErrorHandling()) @@ -148,7 +148,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : count: Int, extended: Int? ): Single> { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getMarketServices(owner_id, offset, count, extended) .map(extractResponseWithErrorHandling()) @@ -158,7 +158,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : override fun getMarketById(ids: Collection): Single> { val markets = join(ids, ",") { AccessIdPair.format(it) } - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getMarketById(markets, 1) .map(extractResponseWithErrorHandling()) @@ -172,7 +172,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : fields: String?, userId: Int? ): Single> { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getBanned(groupId, offset, count, fields, userId) .map(extractResponseWithErrorHandling()) @@ -181,7 +181,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : override fun getWallInfo(groupId: String?, fields: String?): Single { return provideService( - IGroupsService::class.java, + IGroupsService(), TokenType.USER, TokenType.SERVICE, TokenType.COMMUNITY @@ -211,7 +211,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : filter: String? ): Single> { return provideService( - IGroupsService::class.java, + IGroupsService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE @@ -234,7 +234,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IGroupsService::class.java, TokenType.USER) + return provideService(IGroupsService(), TokenType.USER) .flatMap { service -> service .search( @@ -246,7 +246,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : } override fun leave(groupId: Int): Single { - return provideService(IGroupsService::class.java, TokenType.USER) + return provideService(IGroupsService(), TokenType.USER) .flatMap { service -> service.leave(groupId) .map(extractResponseWithErrorHandling()) @@ -255,7 +255,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : } override fun join(groupId: Int, notSure: Int?): Single { - return provideService(IGroupsService::class.java, TokenType.USER) + return provideService(IGroupsService(), TokenType.USER) .flatMap { service -> service.join(groupId, notSure) .map(extractResponseWithErrorHandling()) @@ -271,7 +271,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IGroupsService::class.java, TokenType.USER) + return provideService(IGroupsService(), TokenType.USER) .flatMap { service -> service[userId, integerFromBoolean(extended), filter, fields, offset, count] .map(extractResponseWithErrorHandling()) @@ -279,7 +279,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : } override fun getLongPollServer(groupId: Int): Single { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .getLongPollServer(groupId) @@ -297,7 +297,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : join(ids, ",")?.let { pds.add(it) } join(domains, ",")?.let { pds.add(it) } return provideService( - IGroupsService::class.java, + IGroupsService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE @@ -310,7 +310,7 @@ internal class GroupsApi(accountId: Int, provider: IServiceProvider) : } override fun getChats(groupId: Int, offset: Int?, count: Int?): Single> { - return provideService(IGroupsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IGroupsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .getChats(groupId, offset, count) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LikesApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LikesApi.kt index 7aaf9ed04..c9769e91e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LikesApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LikesApi.kt @@ -15,7 +15,7 @@ internal class LikesApi(accountId: Int, provider: IServiceProvider) : filter: String?, friendsOnly: Boolean?, offset: Int?, count: Int?, skipOwn: Boolean?, fields: String? ): Single { - return provideService(ILikesService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(ILikesService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getList( @@ -32,7 +32,7 @@ internal class LikesApi(accountId: Int, provider: IServiceProvider) : itemId: Int, accessKey: String? ): Single { - return provideService(ILikesService::class.java, TokenType.USER) + return provideService(ILikesService(), TokenType.USER) .flatMap { service -> service.delete(type, ownerId, itemId, accessKey) .map(extractResponseWithErrorHandling()) @@ -41,7 +41,7 @@ internal class LikesApi(accountId: Int, provider: IServiceProvider) : } override fun add(type: String?, ownerId: Int?, itemId: Int, accessKey: String?): Single { - return provideService(ILikesService::class.java, TokenType.USER) + return provideService(ILikesService(), TokenType.USER) .flatMap { service -> service.add(type, ownerId, itemId, accessKey) .map(extractResponseWithErrorHandling()) @@ -50,7 +50,7 @@ internal class LikesApi(accountId: Int, provider: IServiceProvider) : } override fun isLiked(type: String?, ownerId: Int?, itemId: Int): Single { - return provideService(ILikesService::class.java, TokenType.USER) + return provideService(ILikesService(), TokenType.USER) .flatMap { service -> service.isLiked(type, ownerId, itemId) .map(extractResponseWithErrorHandling()) @@ -64,7 +64,7 @@ internal class LikesApi(accountId: Int, provider: IServiceProvider) : itemId: Int, accessKey: String? ): Single { - return provideService(ILikesService::class.java, TokenType.USER) + return provideService(ILikesService(), TokenType.USER) .flatMap { service -> service.checkAndAddLike( "var type = Args.type; var owner_id = Args.owner_id; var item_id = Args.item_id; var access_key = Args.access_key; if(API.likes.isLiked({\"v\":\"" + Constants.API_VERSION + "\", \"type\": type, \"owner_id\": owner_id, \"item_id\": item_id}).liked == 0) {return API.likes.add({\"v\":\"" + Constants.API_VERSION + "\", \"type\": type, \"owner_id\": owner_id, \"item_id\": item_id, \"access_key\": access_key}).likes;} return 0;", diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LongpollApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LongpollApi.kt index 08e1dd0e2..d856fdbf1 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LongpollApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/LongpollApi.kt @@ -1,43 +1,41 @@ package dev.ragnarok.fenrir.api.impl -import dev.ragnarok.fenrir.api.IOtherVkRetrofitProvider +import dev.ragnarok.fenrir.api.IOtherVkRestProvider import dev.ragnarok.fenrir.api.interfaces.ILongpollApi import dev.ragnarok.fenrir.api.model.longpoll.VkApiGroupLongpollUpdates import dev.ragnarok.fenrir.api.model.longpoll.VkApiLongpollUpdates import dev.ragnarok.fenrir.api.services.ILongpollUpdatesService import io.reactivex.rxjava3.core.Single -class LongpollApi internal constructor(private val provider: IOtherVkRetrofitProvider) : +class LongpollApi internal constructor(private val provider: IOtherVkRestProvider) : ILongpollApi { override fun getUpdates( - server: String?, + server: String, key: String?, ts: Long, wait: Int, mode: Int, version: Int ): Single { - return provider.provideLongpollRetrofit() - .flatMap { wrapper -> - wrapper.create( - ILongpollUpdatesService::class.java - ) - .getUpdates(server, "a_check", key, ts, wait, mode, version) + return provider.provideLongpollRest() + .flatMap { + val ret = ILongpollUpdatesService() + ret.addon(it) + ret.getUpdates(server, "a_check", key, ts, wait, mode, version) } } override fun getGroupUpdates( - server: String?, + server: String, key: String?, ts: String?, wait: Int ): Single { - return provider.provideLongpollRetrofit() - .flatMap { wrapper -> - wrapper.create( - ILongpollUpdatesService::class.java - ) - .getGroupUpdates(server, "a_check", key, ts, wait) + return provider.provideLongpollRest() + .flatMap { + val ret = ILongpollUpdatesService() + ret.addon(it) + ret.getGroupUpdates(server, "a_check", key, ts, wait) } } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/MessagesApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/MessagesApi.kt index 7d92cc66d..e2ad1565d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/MessagesApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/MessagesApi.kt @@ -13,7 +13,7 @@ import io.reactivex.rxjava3.core.Single internal class MessagesApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IMessagesApi { private fun serviceRx(vararg tokenTypes: Int): Single { - return provideService(IMessageService::class.java, *tokenTypes) + return provideService(IMessageService(), *tokenTypes) } override fun edit( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/Networker.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/Networker.kt index b169a2de6..7be7e8b20 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/Networker.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/Networker.kt @@ -8,29 +8,29 @@ import dev.ragnarok.fenrir.settings.IProxySettings import io.reactivex.rxjava3.core.Single class Networker(settings: IProxySettings) : INetworker { - private val otherVkRetrofitProvider: IOtherVkRetrofitProvider - private val vkRetrofitProvider: IVkRetrofitProvider - private val uploadRetrofitProvider: IUploadRetrofitProvider + private val otherVkRestProvider: IOtherVkRestProvider + private val vkRestProvider: IVkRestProvider + private val uploadRestProvider: IUploadRestProvider override fun vkDefault(accountId: Int): IAccountApis { - return VkApies[accountId, vkRetrofitProvider] + return VkApies[accountId, vkRestProvider] } override fun vkManual(accountId: Int, accessToken: String): IAccountApis { - return VkApies.create(accountId, accessToken, vkRetrofitProvider) + return VkApies.create(accountId, accessToken, vkRestProvider) } - override fun getVkRetrofitProvider(): IVkRetrofitProvider { - return vkRetrofitProvider + override fun getVkRestProvider(): IVkRestProvider { + return vkRestProvider } override fun vkDirectAuth(): IAuthApi { return AuthApi(object : IDirectLoginSeviceProvider { override fun provideAuthService(): Single { - return otherVkRetrofitProvider.provideAuthRetrofit() - .map { wrapper -> - wrapper.create( - IAuthService::class.java - ) + return otherVkRestProvider.provideAuthRest() + .map { + val ret = IAuthService() + ret.addon(it) + ret } } }) @@ -39,11 +39,11 @@ class Networker(settings: IProxySettings) : INetworker { override fun vkAuth(): IAuthApi { return AuthApi(object : IDirectLoginSeviceProvider { override fun provideAuthService(): Single { - return otherVkRetrofitProvider.provideAuthServiceRetrofit() - .map { wrapper -> - wrapper.create( - IAuthService::class.java - ) + return otherVkRestProvider.provideAuthServiceRest() + .map { + val ret = IAuthService() + ret.addon(it) + ret } } }) @@ -52,27 +52,27 @@ class Networker(settings: IProxySettings) : INetworker { override fun localServerApi(): ILocalServerApi { return LocalServerApi(object : ILocalServerServiceProvider { override fun provideLocalServerService(): Single { - return otherVkRetrofitProvider.provideLocalServerRetrofit() - .map { wrapper -> - wrapper.create( - ILocalServerService::class.java - ) + return otherVkRestProvider.provideLocalServerRest() + .map { + val ret = ILocalServerService() + ret.addon(it) + ret } } }) } override fun longpoll(): ILongpollApi { - return LongpollApi(otherVkRetrofitProvider) + return LongpollApi(otherVkRestProvider) } override fun uploads(): IUploadApi { - return UploadApi(uploadRetrofitProvider) + return UploadApi(uploadRestProvider) } init { - otherVkRetrofitProvider = OtherVkRetrofitProvider(settings) - vkRetrofitProvider = VkRetrofitProvider(settings, VkMethodHttpClientFactory()) - uploadRetrofitProvider = UploadRetrofitProvider(settings) + otherVkRestProvider = OtherVkRestProvider(settings) + vkRestProvider = VkRestProvider(settings, VkMethodHttpClientFactory()) + uploadRestProvider = UploadRestProvider(settings) } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NewsfeedApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NewsfeedApi.kt index ece9abd64..60b745f9e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NewsfeedApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NewsfeedApi.kt @@ -17,7 +17,7 @@ import kotlin.math.abs internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), INewsfeedApi { override fun getLists(listIds: Collection?): Single> { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service.getLists(join(listIds, ","), 1) .map(extractResponseWithErrorHandling()) @@ -25,7 +25,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : } override fun saveList(title: String?, listIds: Collection?): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service.saveList(title, join(listIds, ",")) .map(extractResponseWithErrorHandling()) @@ -42,7 +42,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : users.add(i) } } - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service.addBan(join(users, ","), join(groups, ",")) .map(extractResponseWithErrorHandling()) @@ -50,7 +50,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : } override fun deleteList(list_id: Int?): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service.deleteList(list_id) .map(extractResponseWithErrorHandling()) @@ -58,7 +58,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : } override fun ignoreItem(type: String?, owner_id: Int?, item_id: Int?): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service.ignoreItem(type, owner_id, item_id) .map(extractResponseWithErrorHandling()) @@ -76,7 +76,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : startFrom: String?, fields: String? ): Single { - return provideService(INewsfeedService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(INewsfeedService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .search( @@ -97,7 +97,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : startFrom: String?, fields: String? ): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service .getComments( @@ -122,7 +122,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : startTime: Long?, endTime: Long? ): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service .getMentions(owner_id, count, offset, startTime, endTime) @@ -135,7 +135,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : endTime: Long?, maxPhotoCount: Int?, sourceIds: String?, startFrom: String?, count: Int?, fields: String? ): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service[filters, integerFromBoolean(returnBanned), startTime, endTime, maxPhotoCount, sourceIds, startFrom, count, fields] .map(extractResponseWithErrorHandling()) @@ -147,7 +147,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : endTime: Long?, maxPhotoCount: Int?, sourceIds: String?, startFrom: String?, count: Int?, fields: String? ): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service.getByType( "top", @@ -169,7 +169,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : startTime: Long?, endTime: Long?, maxPhotoCount: Int?, startFrom: String?, count: Int?, fields: String? ): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service .getRecommended( @@ -186,7 +186,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : count: Int?, fields: String? ): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service .getFeedLikes(maxPhotoCount, startFrom, count, fields) @@ -204,7 +204,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : users.add(i) } } - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service.deleteBan(join(users, ","), join(groups, ",")) .map(extractResponseWithErrorHandling()) @@ -212,7 +212,7 @@ internal class NewsfeedApi(accountId: Int, provider: IServiceProvider) : } override fun getBanned(): Single { - return provideService(INewsfeedService::class.java, TokenType.USER) + return provideService(INewsfeedService(), TokenType.USER) .flatMap { service -> service .getBanned( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NotificationsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NotificationsApi.kt index d597bcebb..a816b3d0c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NotificationsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/NotificationsApi.kt @@ -14,9 +14,9 @@ import io.reactivex.rxjava3.core.Single internal class NotificationsApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), INotificationsApi { override fun markAsViewed(): Single { - return provideService(INotificationsService::class.java, TokenType.USER) + return provideService(INotificationsService(), TokenType.USER) .flatMap { service -> - service.markAsViewed() + service.markAsViewed .map(extractResponseWithErrorHandling()) } } @@ -28,7 +28,7 @@ internal class NotificationsApi(accountId: Int, provider: IServiceProvider) : startTime: Long?, endTime: Long? ): Single { - return provideService(INotificationsService::class.java, TokenType.USER) + return provideService(INotificationsService(), TokenType.USER) .flatMap { service -> service[count, startFrom, filters, startTime, endTime] .map(extractResponseWithErrorHandling()) @@ -57,7 +57,7 @@ internal class NotificationsApi(accountId: Int, provider: IServiceProvider) : startTime: Long?, endTime: Long? ): Single { - return provideService(INotificationsService::class.java, TokenType.USER) + return provideService(INotificationsService(), TokenType.USER) .flatMap { service -> service.getOfficial( count, @@ -72,7 +72,7 @@ internal class NotificationsApi(accountId: Int, provider: IServiceProvider) : } override fun hide(query: String?): Single { - return provideService(INotificationsService::class.java, TokenType.USER) + return provideService(INotificationsService(), TokenType.USER) .flatMap { service -> service.hide(query) .map(extractResponseWithErrorHandling()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt index 940bf3472..b36332bd3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt @@ -1,11 +1,11 @@ package dev.ragnarok.fenrir.api.impl -import dev.ragnarok.fenrir.api.IVkRetrofitProvider +import dev.ragnarok.fenrir.api.IVkRestProvider import dev.ragnarok.fenrir.api.interfaces.IOtherApi +import dev.ragnarok.fenrir.api.rest.HttpException import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.Optional import dev.ragnarok.fenrir.util.Optional.Companion.wrap -import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.SingleEmitter import okhttp3.FormBody @@ -13,7 +13,7 @@ import okhttp3.Request import okhttp3.Response import okhttp3.ResponseBody -class OtherApi(private val accountId: Int, private val provider: IVkRetrofitProvider) : IOtherApi { +class OtherApi(private val accountId: Int, private val provider: IVkRestProvider) : IOtherApi { override fun rawRequest( method: String, postParams: Map @@ -33,18 +33,18 @@ class OtherApi(private val accountId: Int, private val provider: IVkRetrofitProv ) .post(bodyBuilder.build()) .build() - val call = client.newCall(request) + val call = client.build().newCall(request) emitter.setCancellable { call.cancel() } try { val response = call.execute() if (!response.isSuccessful) { - emitter.onError(HttpCodeException(response.code)) + emitter.tryOnError(HttpException(response.code)) } else { emitter.onSuccess(response) } response.close() } catch (e: Exception) { - emitter.onError(e) + emitter.tryOnError(e) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PagesApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PagesApi.kt index d188f38e1..c792ae766 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PagesApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PagesApi.kt @@ -18,7 +18,7 @@ internal class PagesApi(accountId: Int, provider: IServiceProvider) : needSource: Boolean?, needHtml: Boolean? ): Single { - return provideService(IPagesService::class.java, TokenType.USER) + return provideService(IPagesService(), TokenType.USER) .flatMap { service -> service[ownerId, pageId, integerFromBoolean(global), integerFromBoolean(sitePreview), title, integerFromBoolean( needSource diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PhotosApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PhotosApi.kt index 55381ab21..d2500a772 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PhotosApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PhotosApi.kt @@ -15,7 +15,7 @@ import io.reactivex.rxjava3.core.Single internal class PhotosApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IPhotosApi { override fun deleteAlbum(albumId: Int, groupId: Int?): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.deleteAlbum(albumId, groupId) .map(extractResponseWithErrorHandling()) @@ -24,7 +24,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun restore(ownerId: Int?, photoId: Int): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.restore(ownerId, photoId) .map(extractResponseWithErrorHandling()) @@ -33,7 +33,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun delete(ownerId: Int?, photoId: Int): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.delete(ownerId, photoId) .map(extractResponseWithErrorHandling()) @@ -42,7 +42,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun deleteComment(ownerId: Int?, commentId: Int): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.deleteComment(ownerId, commentId) .map(extractResponseWithErrorHandling()) @@ -51,7 +51,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun restoreComment(ownerId: Int?, commentId: Int): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.restoreComment(ownerId, commentId) .map(extractResponseWithErrorHandling()) @@ -63,7 +63,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : ownerId: Int?, commentId: Int, message: String?, attachments: Collection? ): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.editComment( ownerId, @@ -89,7 +89,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : ): Single { val privacyViewTxt = privacyView?.buildJsonArray() val privacyCommentTxt = privacyComment?.buildJsonArray() - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .createAlbum( @@ -112,7 +112,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : ): Single { val privacyViewTxt = privacyView?.buildJsonArray() val privacyCommentTxt = privacyComment?.buildJsonArray() - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .editAlbum( @@ -125,7 +125,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun copy(ownerId: Int, photoId: Int, accessKey: String?): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.copy(ownerId, photoId, accessKey) .map(extractResponseWithErrorHandling()) @@ -137,7 +137,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : replyToComment: Int?, attachments: Collection?, stickerId: Int?, accessKey: String?, generatedUniqueId: Int? ): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .createComment( @@ -170,7 +170,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : extended: Boolean?, fields: String? ): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .getComments( @@ -186,7 +186,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : ids, "," ) { pair -> pair.ownerId.toString() + "_" + pair.id + if (pair.accessKey == null) "" else "_" + pair.accessKey } - return provideService(IPhotosService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IPhotosService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service.getById(line, 1, 1) .map(extractResponseWithErrorHandling()) @@ -204,7 +204,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun getUploadServer(albumId: Int, groupId: Int?): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .getUploadServer(albumId, groupId) @@ -217,7 +217,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : hash: String?, photo: String? ): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .saveOwnerPhoto(server, hash, photo) @@ -226,7 +226,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun getOwnerPhotoUploadServer(ownerId: Int?): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .getOwnerPhotoUploadServer(ownerId) @@ -235,7 +235,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun getChatUploadServer(chat_id: Int?): Single { - return provideService(IPhotosService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IPhotosService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .getChatUploadServer(chat_id) @@ -244,7 +244,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun setChatPhoto(file: String?): Single { - return provideService(IPhotosService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IPhotosService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .setChatPhoto(file) @@ -257,7 +257,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : server: Int, hash: String?, latitude: Double?, longitude: Double?, caption: String? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .saveWallPhoto( @@ -275,7 +275,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override fun getWallUploadServer(groupId: Int?): Single { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .getWallUploadServer(groupId) @@ -287,7 +287,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : albumId: Int, groupId: Int?, server: Int, photosList: String?, hash: String?, latitude: Double?, longitude: Double?, caption: String? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service .save(albumId, groupId, server, photosList, hash, latitude, longitude, caption) @@ -300,7 +300,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : rev: Boolean?, offset: Int?, count: Int? ): Single> { val photos = join(photoIds, ",") - return provideService(IPhotosService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IPhotosService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service[ownerId, albumId, photos, integerFromBoolean(rev), 1, 1, offset, count] .map(extractResponseWithErrorHandling()) @@ -314,7 +314,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.getUserPhotos(ownerId, extended, sort, offset, count) .map(extractResponseWithErrorHandling()) @@ -328,7 +328,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.getAll(ownerId, extended, photo_sizes, offset, count, 0, 1, 0) .map(extractResponseWithErrorHandling()) @@ -336,7 +336,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : } override val messagesUploadServer: Single - get() = provideService(IPhotosService::class.java, TokenType.USER, TokenType.COMMUNITY) + get() = provideService(IPhotosService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.messagesUploadServer .map(extractResponseWithErrorHandling()) @@ -347,7 +347,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : photo: String?, hash: String? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IPhotosService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.saveMessagesPhoto(server, photo, hash) .map(extractResponseWithErrorHandling()) @@ -360,7 +360,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : needCovers: Boolean? ): Single> { val ids = join(albumIds, ",") - return provideService(IPhotosService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IPhotosService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service.getAlbums( ownerId, @@ -380,7 +380,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : photo_id: Int?, access_key: String? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.getTags(ownerId, photo_id, access_key).map( extractResponseWithErrorHandling() @@ -395,7 +395,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER) + return provideService(IPhotosService(), TokenType.USER) .flatMap { service -> service.getAllComments(ownerId, album_id, need_likes, offset, count).map( extractResponseWithErrorHandling() @@ -414,7 +414,7 @@ internal class PhotosApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IPhotosService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IPhotosService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service.search( q, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PollsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PollsApi.kt index 2e023eb8d..476caf779 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PollsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/PollsApi.kt @@ -22,7 +22,7 @@ internal class PollsApi(accountId: Int, provider: IServiceProvider) : ownerId: Int, addAnswers: List ): Single { - return provideService(IPollsService::class.java, TokenType.USER) + return provideService(IPollsService(), TokenType.USER) .flatMap { service -> service .create( @@ -42,7 +42,7 @@ internal class PollsApi(accountId: Int, provider: IServiceProvider) : answerId: Long, isBoard: Boolean? ): Single { - return provideService(IPollsService::class.java, TokenType.USER) + return provideService(IPollsService(), TokenType.USER) .flatMap { service -> service.deleteVote(ownerId, pollId, answerId, integerFromBoolean(isBoard)) .map(extractResponseWithErrorHandling()) @@ -56,7 +56,7 @@ internal class PollsApi(accountId: Int, provider: IServiceProvider) : answerIds: Set, isBoard: Boolean? ): Single { - return provideService(IPollsService::class.java, TokenType.USER) + return provideService(IPollsService(), TokenType.USER) .flatMap { service -> service.addVote(ownerId, pollId, join(answerIds, ","), integerFromBoolean(isBoard)) .map(extractResponseWithErrorHandling()) @@ -65,7 +65,7 @@ internal class PollsApi(accountId: Int, provider: IServiceProvider) : } override fun getById(ownerId: Int, isBoard: Boolean?, pollId: Int): Single { - return provideService(IPollsService::class.java, TokenType.USER) + return provideService(IPollsService(), TokenType.USER) .flatMap { service -> service.getById(ownerId, integerFromBoolean(isBoard), pollId) .map(extractResponseWithErrorHandling()) @@ -81,7 +81,7 @@ internal class PollsApi(accountId: Int, provider: IServiceProvider) : val ids = join(answer_ids, ",") { obj: Any -> obj.toString() } ?: return Single.just( emptyList() ) - return provideService(IPollsService::class.java, TokenType.USER) + return provideService(IPollsService(), TokenType.USER) .flatMap { service -> service.getVoters( ownerId, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StatusApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StatusApi.kt index be1b68755..9102a6255 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StatusApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StatusApi.kt @@ -9,7 +9,7 @@ import io.reactivex.rxjava3.core.Single internal class StatusApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IStatusApi { override fun set(text: String?, groupId: Int?): Single { - return provideService(IStatusService::class.java, TokenType.USER) + return provideService(IStatusService(), TokenType.USER) .flatMap { service -> service.set(text, groupId) .map(extractResponseWithErrorHandling()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoreApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoreApi.kt index 5b13c3823..1452225bd 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoreApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoreApi.kt @@ -12,14 +12,14 @@ import io.reactivex.rxjava3.core.Single internal class StoreApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IStoreApi { override val stickerKeywords: Single - get() = provideService(IStoreService::class.java, TokenType.USER) + get() = provideService(IStoreService(), TokenType.USER) .flatMap { service -> service .getStickersKeywords("var dic=API.store.getStickersKeywords({'v':'" + Constants.API_VERSION + "','aliases':1,'all_products':1}).dictionary;return {'keywords': dic@.words, 'words_stickers': dic@.user_stickers};") .map(extractResponseWithErrorHandling()) } override val stickers: Single - get() = provideService(IStoreService::class.java, TokenType.USER) + get() = provideService(IStoreService(), TokenType.USER) .flatMap { service -> service .getStickers("var pack = API.store.getProducts({'v':'" + Constants.API_VERSION + "','extended':1,'filters':'active','type':'stickers'}); var recent = API.messages.getRecentStickers(); return {'sticker_pack': pack, 'recent': recent};") diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UploadApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UploadApi.kt index d0a4ebf5d..0908f768b 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UploadApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UploadApi.kt @@ -1,6 +1,6 @@ package dev.ragnarok.fenrir.api.impl -import dev.ragnarok.fenrir.api.IUploadRetrofitProvider +import dev.ragnarok.fenrir.api.IUploadRestProvider import dev.ragnarok.fenrir.api.PercentagePublisher import dev.ragnarok.fenrir.api.interfaces.IUploadApi import dev.ragnarok.fenrir.api.model.response.BaseResponse @@ -13,13 +13,17 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import java.io.InputStream -class UploadApi internal constructor(private val provider: IUploadRetrofitProvider) : IUploadApi { - private fun service(): IUploadService { - return provider.provideUploadRetrofit().blockingGet().create(IUploadService::class.java) +class UploadApi internal constructor(private val provider: IUploadRestProvider) : IUploadApi { + private fun service(): Single { + return provider.provideUploadRest().map { + val ret = IUploadService() + ret.addon(it) + ret + } } override fun uploadDocumentRx( - server: String?, + server: String, filename: String?, doc: InputStream, listener: PercentagePublisher? @@ -29,11 +33,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "*/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("file", filename, body) - return service().uploadDocumentRx(server, part) + return service().flatMap { service -> service.uploadDocumentRx(server, part) } } override fun uploadAudioRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher? @@ -43,11 +47,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "*/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("file", filename, body) - return service().uploadAudioRx(server, part) + return service().flatMap { service -> service.uploadAudioRx(server, part) } } override fun remotePlayAudioRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher? @@ -57,11 +61,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "*/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("audio", filename, body) - return service().remotePlayAudioRx(server, part) + return service().flatMap { service -> service.remotePlayAudioRx(server, part) } } override fun uploadStoryRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher?, @@ -77,11 +81,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid filename, body ) - return service().uploadStoryRx(server, part) + return service().flatMap { service -> service.uploadStoryRx(server, part) } } override fun uploadVideoRx( - server: String?, + server: String, filename: String?, video: InputStream, listener: PercentagePublisher? @@ -91,11 +95,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "*/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("file", filename, body) - return service().uploadVideoRx(server, part) + return service().flatMap { service -> service.uploadVideoRx(server, part) } } override fun uploadOwnerPhotoRx( - server: String?, + server: String, photo: InputStream, listener: PercentagePublisher? ): Single { @@ -105,11 +109,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "image/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("photo", "photo.jpg", body) - return service().uploadOwnerPhotoRx(server, part) + return service().flatMap { service -> service.uploadOwnerPhotoRx(server, part) } } override fun uploadChatPhotoRx( - server: String?, + server: String, photo: InputStream, listener: PercentagePublisher? ): Single { @@ -119,11 +123,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "image/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("photo", "photo.jpg", body) - return service().uploadChatPhotoRx(server, part) + return service().flatMap { service -> service.uploadChatPhotoRx(server, part) } } override fun uploadPhotoToWallRx( - server: String?, + server: String, photo: InputStream, listener: PercentagePublisher? ): Single { @@ -133,11 +137,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "image/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("photo", "photo.jpg", body) - return service().uploadPhotoToWallRx(server, part) + return service().flatMap { service -> service.uploadPhotoToWallRx(server, part) } } override fun uploadPhotoToMessageRx( - server: String?, + server: String, `is`: InputStream, listener: PercentagePublisher? ): Single { @@ -147,11 +151,11 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "image/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("photo", "photo.jpg", body) - return service().uploadPhotoToMessageRx(server, part) + return service().flatMap { service -> service.uploadPhotoToMessageRx(server, part) } } override fun uploadPhotoToAlbumRx( - server: String?, + server: String, file1: InputStream, listener: PercentagePublisher? ): Single { @@ -161,7 +165,7 @@ class UploadApi internal constructor(private val provider: IUploadRetrofitProvid "image/*".toMediaTypeOrNull() ) val part: MultipartBody.Part = MultipartBody.Part.createFormData("file1", "photo.jpg", body) - return service().uploadPhotoToAlbumRx(server, part) + return service().flatMap { service -> service.uploadPhotoToAlbumRx(server, part) } } companion object { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UsersApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UsersApi.kt index cebce9b31..54fb22f5d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UsersApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UsersApi.kt @@ -22,7 +22,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : fields: String?, nameCase: String? ): Single { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IUsersService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getUserWallInfo( @@ -65,7 +65,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : fields: String?, nameCase: String? ): Single> { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IUsersService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service.getFollowers(userId, offset, count, fields, nameCase) .map(extractResponseWithErrorHandling()) @@ -79,7 +79,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : out: Int?, fields: String? ): Single> { - return provideService(IUsersService::class.java, TokenType.USER) + return provideService(IUsersService(), TokenType.USER) .flatMap { service -> service.getRequests(offset, count, extended, out, fields) .map(extractResponseWithErrorHandling()) @@ -121,7 +121,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : groupId: Int?, fromList: String? ): Single> { - return provideService(IUsersService::class.java, TokenType.USER) + return provideService(IUsersService(), TokenType.USER) .flatMap { service -> service .search( @@ -164,7 +164,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : } override fun report(userId: Int?, type: String?, comment: String?): Single { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .report(userId, type, comment) @@ -173,7 +173,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : } override fun checkAndAddFriend(userId: Int?): Single { - return provideService(IUsersService::class.java, TokenType.USER) + return provideService(IUsersService(), TokenType.USER) .flatMap { service -> service .checkAndAddFriend( @@ -185,7 +185,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : } override fun getStory(owner_id: Int?, extended: Int?, fields: String?): Single { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getStory(owner_id, extended, fields) .map(extractResponseWithErrorHandling()) @@ -197,7 +197,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : offset: Int?, count: Int? ): Single> { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getNarratives(owner_id, offset, count) .map(extractResponseWithErrorHandling()) @@ -210,7 +210,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : fields: String? ): Single { val storyString = join(stories, ",") { AccessIdPair.format(it) } - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getStoryById(storyString, extended, fields) .map(extractResponseWithErrorHandling()) @@ -218,7 +218,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : } override fun getGifts(user_id: Int?, count: Int?, offset: Int?): Single> { - return provideService(IUsersService::class.java, TokenType.USER) + return provideService(IUsersService(), TokenType.USER) .flatMap { service -> service.getGifts(user_id, count, offset) .map(extractResponseWithErrorHandling()) @@ -232,7 +232,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : extended: Int?, fields: String? ): Single { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.searchStory(q, mentioned_id, count, extended, fields) .map(extractResponseWithErrorHandling()) @@ -253,7 +253,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : ids.add(join(domains, ",")) } return provideService( - IUsersService::class.java, + IUsersService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE @@ -265,7 +265,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : } override fun stories_getPhotoUploadServer(): Single { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.stories_getPhotoUploadServer(1) .map(extractResponseWithErrorHandling()) @@ -273,7 +273,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : } override fun stories_getVideoUploadServer(): Single { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.stories_getVideoUploadServer(1) .map(extractResponseWithErrorHandling()) @@ -281,7 +281,7 @@ internal class UsersApi(accountId: Int, provider: IServiceProvider) : } override fun stories_save(upload_results: String?): Single> { - return provideService(IUsersService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUsersService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.stories_save(upload_results) .map(extractResponseWithErrorHandling()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt index 2172fe948..87db88bd6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt @@ -16,7 +16,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : AbsApi(accountId, provider), IUtilsApi { override fun resolveScreenName(screenName: String?): Single { return provideService( - IUtilsService::class.java, + IUtilsService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE @@ -29,7 +29,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : override fun getShortLink(url: String?, t_private: Int?): Single { return provideService( - IUtilsService::class.java, + IUtilsService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE @@ -41,7 +41,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : } override fun getLastShortenedLinks(count: Int?, offset: Int?): Single> { - return provideService(IUtilsService::class.java, TokenType.USER) + return provideService(IUtilsService(), TokenType.USER) .flatMap { service -> service.getLastShortenedLinks(count, offset) .map(extractResponseWithErrorHandling()) @@ -49,7 +49,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : } override fun deleteFromLastShortened(key: String?): Single { - return provideService(IUtilsService::class.java, TokenType.USER) + return provideService(IUtilsService(), TokenType.USER) .flatMap { service -> service.deleteFromLastShortened(key) .map(extractResponseWithErrorHandling()) @@ -58,7 +58,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : override fun checkLink(url: String?): Single { return provideService( - IUtilsService::class.java, + IUtilsService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE @@ -70,7 +70,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : } override fun joinChatByInviteLink(link: String?): Single { - return provideService(IUtilsService::class.java, TokenType.USER) + return provideService(IUtilsService(), TokenType.USER) .flatMap { service -> service.joinChatByInviteLink(link) .map(extractResponseWithErrorHandling()) @@ -78,7 +78,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : } override fun getInviteLink(peer_id: Int?, reset: Int?): Single { - return provideService(IUtilsService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IUtilsService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service.getInviteLink(peer_id, reset) .map(extractResponseWithErrorHandling()) @@ -87,7 +87,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : override fun customScript(code: String?): Single { return provideService( - IUtilsService::class.java, + IUtilsService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE @@ -100,7 +100,7 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : override fun getServerTime(): Single { return provideService( - IUtilsService::class.java, + IUtilsService(), TokenType.USER, TokenType.COMMUNITY, TokenType.SERVICE diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VideoApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VideoApi.kt index 956c794dd..37ad8218c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VideoApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VideoApi.kt @@ -15,7 +15,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : ownerId: Int?, videoId: Int, needLikes: Boolean?, startCommentId: Int?, offset: Int?, count: Int?, sort: String?, extended: Boolean?, fields: String? ): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service .getComments( @@ -34,7 +34,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : } override fun addVideo(targetId: Int?, videoId: Int?, ownerId: Int?): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.addVideo(targetId, videoId, ownerId) .map(extractResponseWithErrorHandling()) @@ -42,7 +42,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : } override fun deleteVideo(videoId: Int?, ownerId: Int?, targetId: Int?): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.deleteVideo(videoId, ownerId, targetId) .map(extractResponseWithErrorHandling()) @@ -55,7 +55,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : count: Int?, needSystem: Boolean? ): Single> { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.getAlbums(ownerId, offset, count, 1, integerFromBoolean(needSystem)) .map(extractResponseWithErrorHandling()) @@ -67,7 +67,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : owner_id: Int?, video_id: Int? ): Single> { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.getAlbumsByVideo(target_id, owner_id, video_id, 1) .map(extractResponseWithErrorHandling()) @@ -79,7 +79,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : filters: String?, searchOwn: Boolean?, offset: Int?, longer: Int?, shorter: Int?, count: Int?, extended: Boolean? ): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service .search( @@ -100,7 +100,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : } override fun restoreComment(ownerId: Int?, commentId: Int): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.restoreComment(ownerId, commentId) .map(extractResponseWithErrorHandling()) @@ -109,7 +109,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : } override fun deleteComment(ownerId: Int?, commentId: Int): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.deleteComment(ownerId, commentId) .map(extractResponseWithErrorHandling()) @@ -124,7 +124,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : ): Single> { val videos = join(ids, ",") { AccessIdPair.format(it) } - return provideService(IVideoService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IVideoService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service[ownerId, videos, albumId, count, offset, integerFromBoolean(extended)] .map(extractResponseWithErrorHandling()) @@ -139,7 +139,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : val atts = join(attachments, ",") { formatAttachmentToken(it) } - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service .createComment( @@ -155,7 +155,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : ownerId: Int, commentId: Int, message: String?, attachments: Collection? ): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.editComment( ownerId, @@ -172,7 +172,7 @@ internal class VideoApi(accountId: Int, provider: IServiceProvider) : override fun edit(ownerId: Int, video_id: Int, name: String?, desc: String?): Single { - return provideService(IVideoService::class.java, TokenType.USER) + return provideService(IVideoService(), TokenType.USER) .flatMap { service -> service.edit(ownerId, video_id, name, desc) .map(extractResponseWithErrorHandling()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VkApies.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VkApies.kt index 8a04344a2..caab4f28b 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VkApies.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VkApies.kt @@ -2,10 +2,11 @@ package dev.ragnarok.fenrir.api.impl import android.annotation.SuppressLint import dev.ragnarok.fenrir.api.IServiceProvider -import dev.ragnarok.fenrir.api.IVkRetrofitProvider -import dev.ragnarok.fenrir.api.RetrofitWrapper +import dev.ragnarok.fenrir.api.IVkRestProvider import dev.ragnarok.fenrir.api.TokenType import dev.ragnarok.fenrir.api.interfaces.* +import dev.ragnarok.fenrir.api.rest.IServiceRest +import dev.ragnarok.fenrir.api.rest.SimplePostHttp import dev.ragnarok.fenrir.util.Utils import io.reactivex.rxjava3.core.Single @@ -13,7 +14,7 @@ internal class VkApies private constructor( accountId: Int, useCustomToken: Boolean, customAccessToken: String?, - provider: IVkRetrofitProvider + provider: IVkRestProvider ) : IAccountApis { private val messagesApi: IMessagesApi private val photosApi: IPhotosApi @@ -133,12 +134,12 @@ internal class VkApies private constructor( companion object { @SuppressLint("UseSparseArrays") private val APIS: MutableMap = HashMap(1) - fun create(accountId: Int, accessToken: String?, provider: IVkRetrofitProvider): VkApies { + fun create(accountId: Int, accessToken: String?, provider: IVkRestProvider): VkApies { return VkApies(accountId, true, accessToken, provider) } @Synchronized - operator fun get(accountId: Int, provider: IVkRetrofitProvider): VkApies { + operator fun get(accountId: Int, provider: IVkRestProvider): VkApies { var apies = APIS[accountId] if (apies == null) { apies = VkApies(accountId, false, null, provider) @@ -149,30 +150,33 @@ internal class VkApies private constructor( } init { - val retrofitProvider: IServiceProvider = object : IServiceProvider { - override fun provideService( + val restProvider: IServiceProvider = object : IServiceProvider { + override fun provideService( accountId: Int, - serviceClass: Class, + serviceClass: T, vararg tokenTypes: Int ): Single { - return provideRetrofit( + return provideRest( accountId, *tokenTypes - ).map { retrofit -> retrofit.create(serviceClass) } + ).map { + serviceClass.addon(it) + serviceClass + } } - fun provideRetrofit(aid: Int, vararg tokenPolicy: Int): Single { + fun provideRest(aid: Int, vararg tokenPolicy: Int): Single { if (useCustomToken) { - return provider.provideCustomRetrofit(aid, customAccessToken!!) + return provider.provideCustomRest(aid, customAccessToken!!) } val isCommunity = aid < 0 return if (isCommunity) { when { Utils.intValueIn(TokenType.COMMUNITY, *tokenPolicy) -> { - provider.provideNormalRetrofit(aid) + provider.provideNormalRest(aid) } Utils.intValueIn(TokenType.SERVICE, *tokenPolicy) -> { - provider.provideServiceRetrofit() + provider.provideServiceRest() } else -> { Single.error( @@ -185,10 +189,10 @@ internal class VkApies private constructor( } else { when { Utils.intValueIn(TokenType.USER, *tokenPolicy) -> { - provider.provideNormalRetrofit(aid) + provider.provideNormalRest(aid) } Utils.intValueIn(TokenType.SERVICE, *tokenPolicy) -> { - provider.provideServiceRetrofit() + provider.provideServiceRest() } else -> { Single.error( @@ -201,28 +205,28 @@ internal class VkApies private constructor( } } } - accountApi = AccountApi(accountId, retrofitProvider) - audioApi = AudioApi(accountId, retrofitProvider) - boardApi = BoardApi(accountId, retrofitProvider) - commentsApi = CommentsApi(accountId, retrofitProvider) - databaseApi = DatabaseApi(accountId, retrofitProvider) - docsApi = DocsApi(accountId, retrofitProvider) - faveApi = FaveApi(accountId, retrofitProvider) - friendsApi = FriendsApi(accountId, retrofitProvider) - groupsApi = GroupsApi(accountId, retrofitProvider) - likesApi = LikesApi(accountId, retrofitProvider) - messagesApi = MessagesApi(accountId, retrofitProvider) - newsfeedApi = NewsfeedApi(accountId, retrofitProvider) - notificationsApi = NotificationsApi(accountId, retrofitProvider) - pagesApi = PagesApi(accountId, retrofitProvider) - photosApi = PhotosApi(accountId, retrofitProvider) - pollsApi = PollsApi(accountId, retrofitProvider) - statusApi = StatusApi(accountId, retrofitProvider) - storeApi = StoreApi(accountId, retrofitProvider) - usersApi = UsersApi(accountId, retrofitProvider) - utilsApi = UtilsApi(accountId, retrofitProvider) - videoApi = VideoApi(accountId, retrofitProvider) - wallApi = WallApi(accountId, retrofitProvider) + accountApi = AccountApi(accountId, restProvider) + audioApi = AudioApi(accountId, restProvider) + boardApi = BoardApi(accountId, restProvider) + commentsApi = CommentsApi(accountId, restProvider) + databaseApi = DatabaseApi(accountId, restProvider) + docsApi = DocsApi(accountId, restProvider) + faveApi = FaveApi(accountId, restProvider) + friendsApi = FriendsApi(accountId, restProvider) + groupsApi = GroupsApi(accountId, restProvider) + likesApi = LikesApi(accountId, restProvider) + messagesApi = MessagesApi(accountId, restProvider) + newsfeedApi = NewsfeedApi(accountId, restProvider) + notificationsApi = NotificationsApi(accountId, restProvider) + pagesApi = PagesApi(accountId, restProvider) + photosApi = PhotosApi(accountId, restProvider) + pollsApi = PollsApi(accountId, restProvider) + statusApi = StatusApi(accountId, restProvider) + storeApi = StoreApi(accountId, restProvider) + usersApi = UsersApi(accountId, restProvider) + utilsApi = UtilsApi(accountId, restProvider) + videoApi = VideoApi(accountId, restProvider) + wallApi = WallApi(accountId, restProvider) otherApi = OtherApi(accountId, provider) } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/WallApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/WallApi.kt index cd9c750d4..7e4720b4d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/WallApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/WallApi.kt @@ -20,7 +20,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco extended: Boolean?, fields: String? ): Single { - return provideService(IWallService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IWallService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .search( @@ -45,7 +45,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco placeId: Int?, markAsAds: Boolean? ): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service .edit( @@ -71,7 +71,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun pin(ownerId: Int?, postId: Int): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.pin(ownerId, postId) .map(extractResponseWithErrorHandling()) @@ -80,7 +80,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun unpin(ownerId: Int?, postId: Int): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.unpin(ownerId, postId) .map(extractResponseWithErrorHandling()) @@ -96,7 +96,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco markAsAds: Boolean? ): Single { val `object` = "wall" + postOwnerId + "_" + postId - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.repost(`object`, message, groupId, integerFromBoolean(markAsAds)) .map(extractResponseWithErrorHandling()) @@ -120,7 +120,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco markAsAds: Boolean?, adsPromotedStealth: Boolean? ): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service .post( @@ -149,7 +149,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun delete(ownerId: Int?, postId: Int): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.delete(ownerId, postId) .map(extractResponseWithErrorHandling()) @@ -158,7 +158,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun restoreComment(ownerId: Int?, commentId: Int): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.restoreComment(ownerId, commentId) .map(extractResponseWithErrorHandling()) @@ -167,7 +167,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun deleteComment(ownerId: Int?, commentId: Int): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.deleteComment(ownerId, commentId) .map(extractResponseWithErrorHandling()) @@ -176,7 +176,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun restore(ownerId: Int?, postId: Int): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.restore(ownerId, postId) .map(extractResponseWithErrorHandling()) @@ -188,7 +188,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco ownerId: Int?, commentId: Int, message: String?, attachments: Collection? ): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service.editComment( ownerId, @@ -208,7 +208,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco replyToComment: Int?, attachments: Collection?, stickerId: Int?, generatedUniqueId: Int? ): Single { - return provideService(IWallService::class.java, TokenType.USER, TokenType.COMMUNITY) + return provideService(IWallService(), TokenType.USER, TokenType.COMMUNITY) .flatMap { service -> service .createComment( @@ -238,7 +238,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco extended: Boolean?, fields: String? ): Single { - return provideService(IWallService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IWallService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service[ownerId, domain, offset, count, filter, if (extended != null) if (extended) 1 else 0 else null, fields] .map(extractResponseWithErrorHandling()) @@ -252,7 +252,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco fields: String? ): Single { val line = join(ids, ",") { orig -> orig.ownerId.toString() + "_" + orig.id } - return provideService(IWallService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IWallService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getById( @@ -266,7 +266,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun reportPost(owner_id: Int?, post_id: Int?, reason: Int?): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service .reportPost(owner_id, post_id, reason) @@ -275,7 +275,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun subscribe(owner_id: Int?): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service .subscribe(owner_id) @@ -284,7 +284,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun unsubscribe(owner_id: Int?): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service .unsubscribe(owner_id) @@ -293,7 +293,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco } override fun reportComment(owner_id: Int?, post_id: Int?, reason: Int?): Single { - return provideService(IWallService::class.java, TokenType.USER) + return provideService(IWallService(), TokenType.USER) .flatMap { service -> service .reportComment(owner_id, post_id, reason) @@ -306,7 +306,7 @@ internal class WallApi(accountId: Int, provider: IServiceProvider) : AbsApi(acco startCommentId: Int?, offset: Int?, count: Int?, sort: String?, extended: Boolean?, fields: String? ): Single { - return provideService(IWallService::class.java, TokenType.USER, TokenType.SERVICE) + return provideService(IWallService(), TokenType.USER, TokenType.SERVICE) .flatMap { service -> service .getComments( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/ILongpollApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/ILongpollApi.kt index 05890709f..153ea07ff 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/ILongpollApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/ILongpollApi.kt @@ -6,7 +6,7 @@ import io.reactivex.rxjava3.core.Single interface ILongpollApi { fun getUpdates( - server: String?, + server: String, key: String?, ts: Long, wait: Int, @@ -15,7 +15,7 @@ interface ILongpollApi { ): Single fun getGroupUpdates( - server: String?, + server: String, key: String?, ts: String?, wait: Int diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/INetworker.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/INetworker.kt index 8ec434786..ca29330ad 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/INetworker.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/INetworker.kt @@ -1,9 +1,9 @@ package dev.ragnarok.fenrir.api.interfaces -import dev.ragnarok.fenrir.api.IVkRetrofitProvider +import dev.ragnarok.fenrir.api.IVkRestProvider interface INetworker { - fun getVkRetrofitProvider(): IVkRetrofitProvider + fun getVkRestProvider(): IVkRestProvider fun vkDefault(accountId: Int): IAccountApis fun vkManual(accountId: Int, accessToken: String): IAccountApis fun vkDirectAuth(): IAuthApi diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUploadApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUploadApi.kt index 14a70f542..92d00ed6f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUploadApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUploadApi.kt @@ -8,28 +8,28 @@ import java.io.InputStream interface IUploadApi { fun uploadDocumentRx( - server: String?, + server: String, filename: String?, doc: InputStream, listener: PercentagePublisher? ): Single fun uploadAudioRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher? ): Single fun remotePlayAudioRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher? ): Single> fun uploadStoryRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher?, @@ -37,38 +37,38 @@ interface IUploadApi { ): Single> fun uploadVideoRx( - server: String?, + server: String, filename: String?, video: InputStream, listener: PercentagePublisher? ): Single fun uploadOwnerPhotoRx( - server: String?, + server: String, photo: InputStream, listener: PercentagePublisher? ): Single fun uploadChatPhotoRx( - server: String?, + server: String, photo: InputStream, listener: PercentagePublisher? ): Single fun uploadPhotoToWallRx( - server: String?, + server: String, photo: InputStream, listener: PercentagePublisher? ): Single fun uploadPhotoToMessageRx( - server: String?, + server: String, `is`: InputStream, listener: PercentagePublisher? ): Single fun uploadPhotoToAlbumRx( - server: String?, + server: String, file1: InputStream, listener: PercentagePublisher? ): Single diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/Error.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/Error.kt index b6c70722d..e546c0c5b 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/Error.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/Error.kt @@ -1,11 +1,10 @@ package dev.ragnarok.fenrir.api.model import dev.ragnarok.fenrir.orZero -import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.Serializer +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import java.lang.reflect.Type @Serializable class Error { @@ -28,10 +27,7 @@ class Error { var requestParams: List? = null @Transient - var type: Type? = null - - @Transient - var serializer: Serializer? = null + var serializer: KSerializer<*>? = null fun requests(): HashMap { val params: HashMap = HashMap(requestParams?.size.orZero()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/HttpCodeException.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/HttpException.kt similarity index 58% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/HttpCodeException.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/HttpException.kt index 605d6a60f..1b15e1079 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/HttpCodeException.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/HttpException.kt @@ -1,6 +1,6 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit +package dev.ragnarok.fenrir.api.rest -class HttpCodeException(val code: Int) : RuntimeException( +class HttpException(val code: Int) : RuntimeException( getMessage( code ) @@ -10,4 +10,4 @@ class HttpCodeException(val code: Int) : RuntimeException( return "HTTP $code" } } -} \ No newline at end of file +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/IServiceRest.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/IServiceRest.kt new file mode 100644 index 000000000..322d1f80c --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/IServiceRest.kt @@ -0,0 +1,77 @@ +package dev.ragnarok.fenrir.api.rest + +import dev.ragnarok.fenrir.api.model.Items +import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.kJson +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import okhttp3.FormBody +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.lang.ref.WeakReference + +abstract class IServiceRest { + private var restClient: WeakReference? = null + + val rest: SimplePostHttp + get() = restClient?.get() ?: throw HttpException(-1) + + fun addon(client: SimplePostHttp?) { + restClient = WeakReference(client) + } + + companion object { + val baseInt: KSerializer> + get() = BaseResponse.serializer(Int.serializer()) + + val baseLong: KSerializer> + get() = BaseResponse.serializer(Long.serializer()) + + val baseString: KSerializer> + get() = BaseResponse.serializer(String.serializer()) + + inline fun base(serial: KSerializer): KSerializer> { + return BaseResponse.serializer(serial) + } + + inline fun baseList(serial: KSerializer): KSerializer>> { + return BaseResponse.serializer(ListSerializer(serial)) + } + + inline fun items(serial: KSerializer): KSerializer>> { + return BaseResponse.serializer(Items.serializer(serial)) + } + + private fun toSerialStr(obj: Any?): String? { + return when (obj) { + is String -> { + obj + } + is Byte, is Short, is Int, is Long, is Float, is Double -> { + obj.toString() + } + is Boolean -> { + if (obj) "1" else "0" + } + else -> null + } + } + + inline fun jsonForm(obj: T, serial: KSerializer): RequestBody { + return kJson.encodeToString(serial, obj) + .toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) + } + + fun form(vararg pairs: Pair): FormBody { + val formBuilder = FormBody.Builder() + for ((first, second) in pairs) { + toSerialStr(second)?.let { + formBuilder.add(first, it) + } + } + return formBuilder.build() + } + } +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/SimplePostHttp.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/SimplePostHttp.kt new file mode 100644 index 000000000..239a8e2bd --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/rest/SimplePostHttp.kt @@ -0,0 +1,137 @@ +package dev.ragnarok.fenrir.api.rest + +import dev.ragnarok.fenrir.api.HttpLoggerAndParser +import dev.ragnarok.fenrir.api.model.Params +import dev.ragnarok.fenrir.api.model.response.VkResponse +import dev.ragnarok.fenrir.ifNonNull +import dev.ragnarok.fenrir.isMsgPack +import dev.ragnarok.fenrir.kJson +import dev.ragnarok.fenrir.util.serializeble.json.decodeFromStream +import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack +import io.reactivex.rxjava3.core.Single +import kotlinx.serialization.KSerializer +import okhttp3.* + +class SimplePostHttp( + private val baseUrl: String?, + okHttpClient: OkHttpClient.Builder +) { + private val client = okHttpClient.build() + + fun stop() { + client.dispatcher.cancelAll() + } + + fun requestFullUrl( + url: String, + body: RequestBody?, + serial: KSerializer, + onlySuccessful: Boolean = true + ): Single { + return requestInternal( + url, + body, + serial, onlySuccessful + ) + } + + fun request( + methodOrFullUrl: String, + body: RequestBody?, + serial: KSerializer, + onlySuccessful: Boolean = true + ): Single { + return requestInternal( + if (baseUrl.isNullOrEmpty()) methodOrFullUrl else "$baseUrl/$methodOrFullUrl", + body, + serial, onlySuccessful + ) + } + + private fun requestInternal( + url: String, + body: RequestBody?, + serial: KSerializer, + onlySuccessful: Boolean + ): Single { + return Single.create { emitter -> + val request = Request.Builder() + .url( + url + ) + body.ifNonNull( + { request.post(it) }, { request.get() } + ) + val call = client.newCall(request.build()) + emitter.setCancellable { call.cancel() } + try { + val response = call.execute() + if (!response.isSuccessful && onlySuccessful) { + emitter.tryOnError(HttpException(response.code)) + } else { + val ret = if (response.body.isMsgPack()) MsgPack().decodeFromOkioStream( + serial, response.body.source() + ) else kJson.decodeFromStream( + serial, response.body.byteStream() + ) + if (ret is VkResponse) { + ret.error?.let { + it.serializer = serial + val o: ArrayList = when (val stmp = response.request.body) { + is FormBody -> { + val f = ArrayList(stmp.size) + for (i in 0 until stmp.size) { + val tmp = Params() + tmp.key = stmp.name(i) + tmp.value = stmp.value(i) + f.add(tmp) + } + f + } + is HttpLoggerAndParser.GzipFormBody -> { + val f = ArrayList(stmp.original.size) + f.addAll(stmp.original) + f + } + else -> { + ArrayList() + } + } + val tmp = Params() + tmp.key = "post_url" + tmp.value = response.request.url.toString() + o.add(tmp) + it.requestParams = o + } + } + emitter.onSuccess( + ret + ) + } + response.close() + } catch (e: Exception) { + emitter.tryOnError(e) + } + } + } + + fun doMultipartForm( + methodOrFullUrl: String, + part: MultipartBody.Part, + serial: KSerializer, onlySuccessful: Boolean = true + ): Single { + val requestBodyMultipart: RequestBody = + MultipartBody.Builder().setType(MultipartBody.FORM).addPart(part).build() + return request(methodOrFullUrl, requestBodyMultipart, serial, onlySuccessful) + } + + fun doMultipartFormFullUrl( + url: String, + part: MultipartBody.Part, + serial: KSerializer, onlySuccessful: Boolean = true + ): Single { + val requestBodyMultipart: RequestBody = + MultipartBody.Builder().setType(MultipartBody.FORM).addPart(part).build() + return requestFullUrl(url, requestBodyMultipart, serial, onlySuccessful) + } +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt index fbe44595a..a3c132d4e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt @@ -5,28 +5,30 @@ import dev.ragnarok.fenrir.api.model.RefreshToken import dev.ragnarok.fenrir.api.model.VKApiProfileInfo import dev.ragnarok.fenrir.api.model.VKApiProfileInfoResponse import dev.ragnarok.fenrir.api.model.response.* +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.GET -import retrofit2.http.POST -interface IAccountService { - @POST("account.ban") - @FormUrlEncoded - fun ban(@Field("owner_id") owner_id: Int): Single> +class IAccountService : IServiceRest() { + fun ban(owner_id: Int): Single> { + return rest.request("account.ban", form("owner_id" to owner_id), baseInt) + } - @POST("account.unban") - @FormUrlEncoded - fun unban(@Field("owner_id") owner_id: Int): Single> + fun unban(owner_id: Int): Single> { + return rest.request("account.unban", form("owner_id" to owner_id), baseInt) + } - @POST("account.getBanned") - @FormUrlEncoded fun getBanned( - @Field("count") count: Int?, - @Field("offset") offset: Int?, - @Field("fields") fields: String? - ): Single> + count: Int?, + offset: Int?, + fields: String? + ): Single> { + return rest.request( + "account.getBanned", + form("count" to count, "offset" to offset, "fields" to fields), + base(AccountsBannedResponse.serializer()) + ) + } + //https://vk.com/dev/account.getCounters /** * @param filter friends — новые заявки в друзья; @@ -41,81 +43,131 @@ interface IAccountService { * sdk — запросы в мобильных играх; * app_requests — уведомления от приложений. */ - @POST("account.getCounters") - @FormUrlEncoded - fun getCounters(@Field("filter") filter: String?): Single> + fun getCounters(filter: String?): Single> { + return rest.request( + "account.getCounters", + form("filter" to filter), + base(CountersDto.serializer()) + ) + } //https://vk.com/dev/account.unregisterDevice - @FormUrlEncoded - @POST("account.unregisterDevice") - fun unregisterDevice(@Field("device_id") deviceId: String?): Single> + fun unregisterDevice(deviceId: String?): Single> { + return rest.request("account.unregisterDevice", form("device_id" to deviceId), baseInt) + } //https://vk.com/dev/account.registerDevice - @FormUrlEncoded - @POST("account.registerDevice") fun registerDevice( - @Field("token") token: String?, - @Field("pushes_granted") pushes_granted: Int?, - @Field("app_version") app_version: String?, - @Field("push_provider") push_provider: String?, - @Field("companion_apps") companion_apps: String?, - @Field("type") type: Int?, - @Field("device_model") deviceModel: String?, - @Field("device_id") deviceId: String?, - @Field("system_version") systemVersion: String?, - @Field("settings") settings: String? - ): Single> + token: String?, + pushes_granted: Int?, + app_version: String?, + push_provider: String?, + companion_apps: String?, + type: Int?, + deviceModel: String?, + deviceId: String?, + systemVersion: String?, + settings: String? + ): Single> { + return rest.request( + "account.registerDevice", + form( + "token" to token, + "pushes_granted" to pushes_granted, + "app_version" to app_version, + "push_provider" to push_provider, + "companion_apps" to companion_apps, + "type" to type, + "device_model" to deviceModel, + "device_id" to deviceId, + "system_version" to systemVersion, + "settings" to settings + ), baseInt + ) + } /** * Marks a current user as offline. * * @return In case of success returns 1. */ - @GET("account.setOffline") - fun setOffline(): Single> + val setOffline: Single> + get() = rest.request("account.setOffline", null, baseInt) - @get:GET("account.getProfileInfo") val profileInfo: Single> + get() = rest.request("account.getProfileInfo", null, base(VKApiProfileInfo.serializer())) - @get:GET("account.getPushSettings") val pushSettings: Single> + get() = rest.request( + "account.getPushSettings", + null, + base(PushSettingsResponse.serializer()) + ) - @FormUrlEncoded - @POST("account.saveProfileInfo") fun saveProfileInfo( - @Field("first_name") first_name: String?, - @Field("last_name") last_name: String?, - @Field("maiden_name") maiden_name: String?, - @Field("screen_name") screen_name: String?, - @Field("bdate") bdate: String?, - @Field("home_town") home_town: String?, - @Field("sex") sex: Int? - ): Single> + first_name: String?, + last_name: String?, + maiden_name: String?, + screen_name: String?, + bdate: String?, + home_town: String?, + sex: Int? + ): Single> { + return rest.request( + "account.saveProfileInfo", + form( + "first_name" to first_name, + "last_name" to last_name, + "maiden_name" to maiden_name, + "screen_name" to screen_name, + "bdate" to bdate, + "home_town" to home_town, + "sex" to sex + ), base(VKApiProfileInfoResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("auth.refreshToken") fun refreshToken( - @Field("receipt") receipt: String?, - @Field("receipt2") receipt2: String?, - @Field("nonce") nonce: String?, - @Field("timestamp") timestamp: Long? - ): Single> + receipt: String?, + receipt2: String?, + nonce: String?, + timestamp: Long? + ): Single> { + return rest.request( + "auth.refreshToken", + form( + "receipt" to receipt, + "receipt2" to receipt2, + "nonce" to nonce, + "timestamp" to timestamp + ), + base(RefreshToken.serializer()) + ) + } - @FormUrlEncoded - @POST("account.importMessagesContacts") - fun importMessagesContacts( - @Field("contacts") contacts: String? - ): Single + val resetMessagesContacts: Single> + get() = rest.request("account.resetMessagesContacts", null, baseInt) - @GET("account.resetMessagesContacts") - fun resetMessagesContacts(): Single> + fun importMessagesContacts( + contacts: String? + ): Single { + return rest.request( + "account.importMessagesContacts", + form("contacts" to contacts), + VkResponse.serializer() + ) + } - @FormUrlEncoded - @POST("account.getContactList") fun getContactList( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + offset: Int?, + count: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "account.getContactList", + form("offset" to offset, "count" to count, "extended" to extended, "fields" to fields), + base(ContactsResponse.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAudioService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAudioService.kt index e7dfb889a..72eeb3c78 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAudioService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAudioService.kt @@ -4,78 +4,120 @@ import dev.ragnarok.fenrir.api.model.* import dev.ragnarok.fenrir.api.model.catalog_v2_audio.VKApiCatalogV2BlockResponse import dev.ragnarok.fenrir.api.model.catalog_v2_audio.VKApiCatalogV2ListResponse import dev.ragnarok.fenrir.api.model.catalog_v2_audio.VKApiCatalogV2SectionResponse -import dev.ragnarok.fenrir.api.model.response.* +import dev.ragnarok.fenrir.api.model.response.AddToPlaylistResponse +import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.model.response.ServicePlaylistResponse import dev.ragnarok.fenrir.api.model.server.VKApiAudioUploadServer +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST +import kotlinx.serialization.builtins.serializer -interface IAudioService { - @FormUrlEncoded - @POST("audio.setBroadcast") +class IAudioService : IServiceRest() { fun setBroadcast( - @Field("audio") audio: String?, - @Field("target_ids") targetIds: String? - ): Single>> + audio: String?, + targetIds: String? + ): Single>> { + return rest.request( + "audio.setBroadcast", form("audio" to audio, "target_ids" to targetIds), baseList( + Int.serializer() + ) + ) + } //https://vk.com/dev/audio.search - @FormUrlEncoded - @POST("audio.search") fun search( - @Field("q") query: String?, - @Field("auto_complete") autoComplete: Int?, - @Field("lyrics") lyrics: Int?, - @Field("performer_only") performerOnly: Int?, - @Field("sort") sort: Int?, - @Field("search_own") searchOwn: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + query: String?, + autoComplete: Int?, + lyrics: Int?, + performerOnly: Int?, + sort: Int?, + searchOwn: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "audio.search", + form( + "q" to query, + "auto_complete" to autoComplete, + "lyrics" to lyrics, + "performer_only" to performerOnly, + "sort" to sort, + "search_own" to searchOwn, + "offset" to offset, + "count" to count + ), items(VKApiAudio.serializer()) + ) + } //https://vk.com/dev/audio.searchArtists - @FormUrlEncoded - @POST("audio.searchArtists") fun searchArtists( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + query: String?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "audio.searchArtists", + form("q" to query, "offset" to offset, "count" to count), + items(VKApiArtist.serializer()) + ) + } //https://vk.com/dev/audio.searchPlaylists - @FormUrlEncoded - @POST("audio.searchPlaylists") fun searchPlaylists( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + query: String?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "audio.searchPlaylists", + form("q" to query, "offset" to offset, "count" to count), + items(VKApiAudioPlaylist.serializer()) + ) + } //https://vk.com/dev/audio.restore - @FormUrlEncoded - @POST("audio.restore") fun restore( - @Field("audio_id") audioId: Int, - @Field("owner_id") ownerId: Int? - ): Single> + audioId: Int, + ownerId: Int? + ): Single> { + return rest.request( + "audio.restore", + form("audio_id" to audioId, "owner_id" to ownerId), + base(VKApiAudio.serializer()) + ) + } //https://vk.com/dev/audio.delete - @FormUrlEncoded - @POST("audio.delete") fun delete( - @Field("audio_id") audioId: Int, - @Field("owner_id") ownerId: Int - ): Single> + audioId: Int, + ownerId: Int + ): Single> { + return rest.request( + "audio.delete", + form("audio_id" to audioId, "owner_id" to ownerId), + baseInt + ) + } //https://vk.com/dev/audio.add - @FormUrlEncoded - @POST("audio.add") fun add( - @Field("audio_id") audioId: Int, - @Field("owner_id") ownerId: Int, - @Field("group_id") groupId: Int?, - @Field("access_key") accessKey: String? - ): Single> + audioId: Int, + ownerId: Int, + groupId: Int?, + accessKey: String? + ): Single> { + return rest.request( + "audio.add", + form( + "audio_id" to audioId, + "owner_id" to ownerId, + "group_id" to groupId, + "access_key" to accessKey + ), + baseInt + ) + } /** * Returns a list of audio files of a user or community. @@ -88,213 +130,347 @@ interface IAudioService { * @return Returns the total results number in count field and an array of objects describing audio in items field. */ //https://vk.com/dev/audio.get - @FormUrlEncoded - @POST("audio.get") operator fun get( - @Field("playlist_id") playlist_id: Int?, - @Field("owner_id") ownerId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("access_key") accessKey: String? - ): Single>> + playlist_id: Int?, + ownerId: Int?, + offset: Int?, + count: Int?, + accessKey: String? + ): Single>> { + return rest.request( + "audio.get", + form( + "playlist_id" to playlist_id, + "owner_id" to ownerId, + "offset" to offset, + "count" to count, + "access_key" to accessKey + ), items(VKApiAudio.serializer()) + ) + } //https://vk.com/dev/audio.getAudiosByArtist - @FormUrlEncoded - @POST("audio.getAudiosByArtist") fun getAudiosByArtist( - @Field("artist_id") artist_id: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + artist_id: String?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "audio.getAudiosByArtist", + form("artist_id" to artist_id, "offset" to offset, "count" to count), + items(VKApiAudio.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.getPopular") fun getPopular( - @Field("only_eng") foreign: Int?, - @Field("genre_id") genre: Int?, - @Field("count") count: Int? - ): Single>> + only_eng: Int?, + genre: Int?, + count: Int? + ): Single>> { + return rest.request( + "audio.getPopular", + form("only_eng" to only_eng, "genre_id" to genre, "count" to count), + baseList(VKApiAudio.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.getRecommendations") fun getRecommendations( - @Field("user_id") user_id: Int?, - @Field("count") count: Int? - ): Single>> + user_id: Int?, + count: Int? + ): Single>> { + return rest.request( + "audio.getRecommendations", + form("user_id" to user_id, "count" to count), + items(VKApiAudio.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.getRecommendations") fun getRecommendationsByAudio( - @Field("target_audio") audio: String?, - @Field("count") count: Int? - ): Single>> + audio: String?, + count: Int? + ): Single>> { + return rest.request( + "audio.getRecommendations", + form("target_audio" to audio, "count" to count), + items(VKApiAudio.serializer()) + ) + } + + fun getById(audios: String?): Single>> { + return rest.request( + "audio.getById", + form("audios" to audios), + baseList(VKApiAudio.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.getById") - fun getById(@Field("audios") audios: String?): Single>> - - @FormUrlEncoded - @POST("audio.getById") fun getByIdVersioned( - @Field("audios") audios: String?, - @Field("v") version: String? - ): Single>> - - @FormUrlEncoded - @POST("audio.getLyrics") - fun getLyrics(@Field("lyrics_id") lyrics_id: Int): Single> + audios: String?, + version: String? + ): Single>> { + return rest.request( + "audio.getById", + form("audios" to audios, "v" to version), + baseList(VKApiAudio.serializer()) + ) + } + + fun getLyrics(lyrics_id: Int): Single> { + return rest.request( + "audio.getLyrics", + form("lyrics_id" to lyrics_id), + base(VKApiLyrics.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.getPlaylists") fun getPlaylists( - @Field("owner_id") owner_id: Int, - @Field("offset") offset: Int, - @Field("count") count: Int - ): Single>> + owner_id: Int, + offset: Int, + count: Int + ): Single>> { + return rest.request( + "audio.getPlaylists", + form("owner_id" to owner_id, "offset" to offset, "count" to count), + items(VKApiAudioPlaylist.serializer()) + ) + } + + fun getPlaylistsCustom(code: String?): Single { + return rest.request("execute", form("code" to code), ServicePlaylistResponse.serializer()) + } - @FormUrlEncoded - @POST("execute") - fun getPlaylistsCustom(@Field("code") code: String?): Single - - @FormUrlEncoded - @POST("audio.deletePlaylist") fun deletePlaylist( - @Field("playlist_id") playlist_id: Int, - @Field("owner_id") ownerId: Int - ): Single> + playlist_id: Int, + ownerId: Int + ): Single> { + return rest.request( + "audio.deletePlaylist", + form("playlist_id" to playlist_id, "owner_id" to ownerId), + baseInt + ) + } - @FormUrlEncoded - @POST("audio.followPlaylist") fun followPlaylist( - @Field("playlist_id") playlist_id: Int, - @Field("owner_id") ownerId: Int, - @Field("access_key") accessKey: String? - ): Single> + playlist_id: Int, + ownerId: Int, + accessKey: String? + ): Single> { + return rest.request( + "audio.followPlaylist", + form("playlist_id" to playlist_id, "owner_id" to ownerId, "access_key" to accessKey), + base(VKApiAudioPlaylist.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.savePlaylistAsCopy") fun clonePlaylist( - @Field("playlist_id") playlist_id: Int, - @Field("owner_id") ownerId: Int - ): Single> + playlist_id: Int, + ownerId: Int + ): Single> { + return rest.request( + "audio.savePlaylistAsCopy", + form("playlist_id" to playlist_id, "owner_id" to ownerId), + base(VKApiAudioPlaylist.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.getPlaylistById") fun getPlaylistById( - @Field("playlist_id") playlist_id: Int, - @Field("owner_id") ownerId: Int, - @Field("access_key") accessKey: String? - ): Single> + playlist_id: Int, + ownerId: Int, + accessKey: String? + ): Single> { + return rest.request( + "audio.getPlaylistById", + form("playlist_id" to playlist_id, "owner_id" to ownerId, "access_key" to accessKey), + base(VKApiAudioPlaylist.serializer()) + ) + } - @get:POST("audio.getUploadServer") val uploadServer: Single> + get() = rest.request( + "audio.getUploadServer", + null, + base(VKApiAudioUploadServer.serializer()) + ) - @FormUrlEncoded - @POST("audio.save") fun save( - @Field("server") server: String?, - @Field("audio") audio: String?, - @Field("hash") hash: String?, - @Field("artist") artist: String?, - @Field("title") title: String? - ): Single> - - @FormUrlEncoded - @POST("audio.edit") + server: String?, + audio: String?, + hash: String?, + artist: String?, + title: String? + ): Single> { + return rest.request( + "audio.save", + form( + "server" to server, + "audio" to audio, + "hash" to hash, + "artist" to artist, + "title" to title + ), + base(VKApiAudio.serializer()) + ) + } + fun edit( - @Field("owner_id") ownerId: Int, - @Field("audio_id") audioId: Int, - @Field("artist") artist: String?, - @Field("title") title: String?, - @Field("text") text: String? - ): Single> - - @FormUrlEncoded - @POST("audio.createPlaylist") + ownerId: Int, + audioId: Int, + artist: String?, + title: String?, + text: String? + ): Single> { + return rest.request( + "audio.edit", + form( + "owner_id" to ownerId, + "audio_id" to audioId, + "artist" to artist, + "title" to title, + "text" to text + ), + baseInt + ) + } + fun createPlaylist( - @Field("owner_id") ownerId: Int, - @Field("title") title: String?, - @Field("description") description: String? - ): Single> + ownerId: Int, + title: String?, + description: String? + ): Single> { + return rest.request( + "audio.createPlaylist", + form("owner_id" to ownerId, "title" to title, "description" to description), + base(VKApiAudioPlaylist.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.editPlaylist") fun editPlaylist( - @Field("owner_id") ownerId: Int, - @Field("playlist_id") playlist_id: Int, - @Field("title") title: String?, - @Field("description") description: String? - ): Single> - - @FormUrlEncoded - @POST("audio.removeFromPlaylist") + ownerId: Int, + playlist_id: Int, + title: String?, + description: String? + ): Single> { + return rest.request( + "audio.editPlaylist", + form( + "owner_id" to ownerId, + "playlist_id" to playlist_id, + "title" to title, + "description" to description + ), baseInt + ) + } + fun removeFromPlaylist( - @Field("owner_id") ownerId: Int, - @Field("playlist_id") playlist_id: Int, - @Field("audio_ids") audio_ids: String? - ): Single> + ownerId: Int, + playlist_id: Int, + audio_ids: String? + ): Single> { + return rest.request( + "audio.removeFromPlaylist", + form("owner_id" to ownerId, "playlist_id" to playlist_id, "audio_ids" to audio_ids), + baseInt + ) + } - @FormUrlEncoded - @POST("audio.addToPlaylist") fun addToPlaylist( - @Field("owner_id") ownerId: Int, - @Field("playlist_id") playlist_id: Int, - @Field("audio_ids") audio_ids: String? - ): Single>> + ownerId: Int, + playlist_id: Int, + audio_ids: String? + ): Single>> { + return rest.request( + "audio.addToPlaylist", + form("owner_id" to ownerId, "playlist_id" to playlist_id, "audio_ids" to audio_ids), + baseList(AddToPlaylistResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.reorder") fun reorder( - @Field("owner_id") ownerId: Int, - @Field("audio_id") audio_id: Int, - @Field("before") before: Int?, - @Field("after") after: Int? - ): Single> - - @FormUrlEncoded - @POST("stats.trackEvents") - fun trackEvents(@Field("events") events: String?): Single> - - @FormUrlEncoded - @POST("catalog.getAudio") + ownerId: Int, + audio_id: Int, + before: Int?, + after: Int? + ): Single> { + return rest.request( + "audio.reorder", + form( + "owner_id" to ownerId, + "audio_id" to audio_id, + "before" to before, + "after" to after + ), + baseInt + ) + } + + fun trackEvents(events: String?): Single> { + return rest.request("stats.trackEvents", form("events" to events), baseInt) + } + fun getCatalogV2Sections( - @Field("owner_id") owner_id: Int, - @Field("need_blocks") need_blocks: Int, - @Field("url") url: String? - ): Single> + owner_id: Int, + need_blocks: Int, + url: String? + ): Single> { + return rest.request( + "catalog.getAudio", + form("owner_id" to owner_id, "need_blocks" to need_blocks, "url" to url), + base(VKApiCatalogV2ListResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("catalog.getAudioArtist") fun getCatalogV2Artist( - @Field("artist_id") artist_id: String, - @Field("need_blocks") need_blocks: Int - ): Single> + artist_id: String, + need_blocks: Int + ): Single> { + return rest.request( + "catalog.getAudioArtist", + form("artist_id" to artist_id, "need_blocks" to need_blocks), + base(VKApiCatalogV2ListResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("catalog.getSection") fun getCatalogV2Section( - @Field("section_id") section_id: String, - @Field("start_from") start_from: String? - ): Single> + section_id: String, + start_from: String? + ): Single> { + return rest.request( + "catalog.getSection", + form("section_id" to section_id, "start_from" to start_from), + base(VKApiCatalogV2SectionResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("catalog.getBlockItems") fun getCatalogV2BlockItems( - @Field("block_id") block_id: String, - @Field("start_from") start_from: String? - ): Single> + block_id: String, + start_from: String? + ): Single> { + return rest.request( + "catalog.getBlockItems", + form("block_id" to block_id, "start_from" to start_from), + base(VKApiCatalogV2BlockResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("catalog.getAudioSearch") fun getCatalogV2AudioSearch( - @Field("query") query: String?, - @Field("context") context: String?, - @Field("need_blocks") need_blocks: Int - ): Single> + query: String?, + context: String?, + need_blocks: Int + ): Single> { + return rest.request( + "catalog.getAudioSearch", + form("query" to query, "context" to context, "need_blocks" to need_blocks), + base(VKApiCatalogV2ListResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.getArtistById") fun getArtistById( - @Field("artist_id") artist_id: String - ): Single> + artist_id: String + ): Single> { + return rest.request( + "audio.getArtistById", + form("artist_id" to artist_id), + base(ArtistInfo.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt index 90b271418..b0fb3b49f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt @@ -3,38 +3,64 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IAuthService { - @FormUrlEncoded - @POST("token") +class IAuthService : IServiceRest() { fun directLogin( - @Field("grant_type") grantType: String?, - @Field("client_id") clientId: Int, - @Field("client_secret") clientSecret: String?, - @Field("username") username: String?, - @Field("password") password: String?, - @Field("v") v: String?, - @Field("2fa_supported") twoFaSupported: Int?, - @Field("scope") scope: String?, - @Field("code") smscode: String?, - @Field("captcha_sid") captchaSid: String?, - @Field("captcha_key") captchaKey: String?, - @Field("force_sms") forceSms: Int?, - @Field("device_id") device_id: String?, - @Field("libverify_support") libverify_support: Int? - ): Single + grantType: String?, + clientId: Int, + clientSecret: String?, + username: String?, + password: String?, + v: String?, + twoFaSupported: Int?, + scope: String?, + smscode: String?, + captchaSid: String?, + captchaKey: String?, + forceSms: Int?, + device_id: String?, + libverify_support: Int? + ): Single { + return rest.request( + "token", + form( + "grant_type" to grantType, + "client_id" to clientId, + "client_secret" to clientSecret, + "username" to username, + "password" to password, + "v" to v, + "2fa_supported" to twoFaSupported, + "scope" to scope, + "code" to smscode, + "captcha_sid" to captchaSid, + "captcha_key" to captchaKey, + "force_sms" to forceSms, + "device_id" to device_id, + "libverify_support" to libverify_support + ), LoginResponse.serializer(), false + ) + } - @FormUrlEncoded - @POST("auth.validatePhone") fun validatePhone( - @Field("api_id") apiId: Int, - @Field("client_id") clientId: Int, - @Field("client_secret") clientSecret: String?, - @Field("sid") sid: String?, - @Field("v") v: String? - ): Single> + apiId: Int, + clientId: Int, + clientSecret: String?, + sid: String?, + v: String? + ): Single> { + return rest.request( + "auth.validatePhone", + form( + "api_id" to apiId, + "client_id" to clientId, + "client_secret" to clientSecret, + "sid" to sid, + "v" to v + ), + base(VKApiValidationResponse.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IBoardService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IBoardService.kt index 7755291e0..11b215f31 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IBoardService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IBoardService.kt @@ -3,44 +3,63 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.DefaultCommentsResponse import dev.ragnarok.fenrir.api.model.response.TopicsResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IBoardService { +class IBoardService : IServiceRest() { //https://vk.com/dev/board.getComments - @FormUrlEncoded - @POST("board.getComments") fun getComments( - @Field("group_id") groupId: Int, - @Field("topic_id") topicId: Int, - @Field("need_likes") needLikes: Int?, - @Field("start_comment_id") startCommentId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("extended") extended: Int?, - @Field("sort") sort: String?, - @Field("fields") fields: String? - ): Single> + groupId: Int, + topicId: Int, + needLikes: Int?, + startCommentId: Int?, + offset: Int?, + count: Int?, + extended: Int?, + sort: String?, + fields: String? + ): Single> { + return rest.request( + "board.getComments", + form( + "group_id" to groupId, + "topic_id" to topicId, + "need_likes" to needLikes, + "start_comment_id" to startCommentId, + "offset" to offset, + "count" to count, + "extended" to extended, + "sort" to sort, + "fields" to fields + ), base(DefaultCommentsResponse.serializer()) + ) + } //https://vk.com/dev/board.restoreComment - @FormUrlEncoded - @POST("board.restoreComment") fun restoreComment( - @Field("group_id") groupId: Int, - @Field("topic_id") topicId: Int, - @Field("comment_id") commentId: Int - ): Single> + groupId: Int, + topicId: Int, + commentId: Int + ): Single> { + return rest.request( + "board.restoreComment", + form("group_id" to groupId, "topic_id" to topicId, "comment_id" to commentId), + baseInt + ) + } //https://vk.com/dev/board.deleteComment - @FormUrlEncoded - @POST("board.deleteComment") fun deleteComment( - @Field("group_id") groupId: Int, - @Field("topic_id") topicId: Int, - @Field("comment_id") commentId: Int - ): Single> + groupId: Int, + topicId: Int, + commentId: Int + ): Single> { + return rest.request( + "board.deleteComment", + form("group_id" to groupId, "topic_id" to topicId, "comment_id" to commentId), + baseInt + ) + } /** * Returns a list of topics on a community's discussion board. @@ -68,19 +87,32 @@ interface IBoardService { * To preview the full comment, specify 0. Default 90 * @return array of objects describing topics. */ - @FormUrlEncoded - @POST("board.getTopics") fun getTopics( - @Field("group_id") groupId: Int, - @Field("topic_ids") topicIds: String?, - @Field("order") order: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("extended") extended: Int?, - @Field("preview") preview: Int?, - @Field("preview_length") previewLength: Int?, - @Field("fields") fields: String? - ): Single> // not doccumented + groupId: Int, + topicIds: String?, + order: Int?, + offset: Int?, + count: Int?, + extended: Int?, + preview: Int?, + previewLength: Int?, + fields: String? + ): Single> { + return rest.request( + "board.getTopics", + form( + "group_id" to groupId, + "topic_ids" to topicIds, + "order" to order, + "offset" to offset, + "count" to count, + "extended" to extended, + "preview" to preview, + "preview_length" to previewLength, + "fields" to fields + ), base(TopicsResponse.serializer()) + ) + } /** * Edits a comment on a topic on a community's discussion board. @@ -103,25 +135,45 @@ interface IBoardService { * List of comma-separated words * @return 1 */ - @FormUrlEncoded - @POST("board.editComment") fun editComment( - @Field("group_id") groupId: Int, - @Field("topic_id") topicId: Int, - @Field("comment_id") commentId: Int, - @Field("message") message: String?, - @Field("attachments") attachments: String? - ): Single> + groupId: Int, + topicId: Int, + commentId: Int, + message: String?, + attachments: String? + ): Single> { + return rest.request( + "board.editComment", + form( + "group_id" to groupId, + "topic_id" to topicId, + "comment_id" to commentId, + "message" to message, + "attachments" to attachments + ), baseInt + ) + } - @FormUrlEncoded - @POST("board.addComment") fun addComment( - @Field("group_id") groupId: Int?, - @Field("topic_id") topicId: Int, - @Field("message") message: String?, - @Field("attachments") attachments: String?, - @Field("from_group") fromGroup: Int?, - @Field("sticker_id") stickerId: Int?, - @Field("guid") generatedUniqueId: Int? - ): Single> + groupId: Int?, + topicId: Int, + message: String?, + attachments: String?, + fromGroup: Int?, + stickerId: Int?, + generatedUniqueId: Int? + ): Single> { + return rest.request( + "board.addComment", + form( + "group_id" to groupId, + "topic_id" to topicId, + "message" to message, + "attachments" to attachments, + "from_group" to fromGroup, + "sticker_id" to stickerId, + "guid" to generatedUniqueId + ), baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ICommentsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ICommentsService.kt index e9acf97e9..3658b9ff4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ICommentsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ICommentsService.kt @@ -2,25 +2,38 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.CustomCommentsResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface ICommentsService { - @FormUrlEncoded - @POST("execute") +class ICommentsService : IServiceRest() { operator fun get( - @Field("code") code: String?, - @Field("source_type") sourceType: String?, - @Field("owner_id") ownerId: Int, - @Field("source_id") sourceId: Int, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("sort") sort: String?, - @Field("start_comment_id") startCommentId: Int?, - @Field("comment_id") thread_id: Int, - @Field("access_key") accessKey: String?, - @Field("fields") fields: String? - ): Single> + code: String?, + sourceType: String?, + ownerId: Int, + sourceId: Int, + offset: Int?, + count: Int?, + sort: String?, + startCommentId: Int?, + comment_id: Int, + accessKey: String?, + fields: String? + ): Single> { + return rest.request( + "execute", + form( + "code" to code, + "source_type" to sourceType, + "owner_id" to ownerId, + "source_id" to sourceId, + "offset" to offset, + "count" to count, + "sort" to sort, + "start_comment_id" to startCommentId, + "comment_id" to comment_id, + "access_key" to accessKey, + "fields" to fields + ), base(CustomCommentsResponse.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDatabaseService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDatabaseService.kt index 54d011954..55431f4ff 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDatabaseService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDatabaseService.kt @@ -5,16 +5,18 @@ import dev.ragnarok.fenrir.api.model.VKApiCity import dev.ragnarok.fenrir.api.model.VKApiCountry import dev.ragnarok.fenrir.api.model.database.* import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IDatabaseService { +class IDatabaseService : IServiceRest() { //https://vk.com/dev/database.getCitiesById - @FormUrlEncoded - @POST("database.getCities") - fun getCitiesById(@Field("city_ids") cityIds: String?): Single>> + fun getCitiesById(cityIds: String?): Single>> { + return rest.request( + "database.getCities", + form("city_ids" to cityIds), + baseList(VKApiCity.serializer()) + ) + } /** * Returns a list of countries. @@ -26,14 +28,18 @@ interface IDatabaseService { * @param count Number of countries to return. Default 100, maximum value 1000 * @return Returns the total results number in count field and an array of objects describing countries in items field */ - @FormUrlEncoded - @POST("database.getCountries") fun getCountries( - @Field("need_all") needAll: Int?, - @Field("code") code: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + needAll: Int?, + code: String?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "database.getCountries", + form("need_all" to needAll, "code" to code, "offset" to offset, "count" to count), + items(VKApiCountry.serializer()) + ) + } /** * Returns a list of school classes specified for the country. @@ -41,9 +47,13 @@ interface IDatabaseService { * @param countryId Country ID. * @return Returns an array of objects, each of them is a pair of class ID and definition. */ - @FormUrlEncoded - @POST("database.getSchoolClasses") - fun getSchoolClasses(@Field("country_id") countryId: Int?): Single>> + fun getSchoolClasses(countryId: Int?): Single>> { + return rest.request( + "database.getSchoolClasses", + form("country_id" to countryId), + baseList(SchoolClazzDto.serializer()) + ) + } /** * Returns list of chairs on a specified faculty. @@ -53,13 +63,17 @@ interface IDatabaseService { * @param count amount of chairs to get. Default 100, maximum value 10000 * @return the total results number in count field and an array of objects describing chairs in items field */ - @FormUrlEncoded - @POST("database.getChairs") fun getChairs( - @Field("faculty_id") facultyId: Int, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + facultyId: Int, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "database.getChairs", + form("faculty_id" to facultyId, "offset" to offset, "count" to count), + items(ChairDto.serializer()) + ) + } /** * Returns a list of faculties (i.e., university departments). @@ -70,13 +84,17 @@ interface IDatabaseService { * @return the total results number in count field and an array * of objects describing faculties in items field */ - @FormUrlEncoded - @POST("database.getFaculties") fun getFaculties( - @Field("university_id") universityId: Int, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + universityId: Int, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "database.getFaculties", + form("university_id" to universityId, "offset" to offset, "count" to count), + items(FacultyDto.serializer()) + ) + } /** * Returns a list of higher education institutions. @@ -88,15 +106,23 @@ interface IDatabaseService { * @param count Number of universities to return. Default 100, maximum value 10000 * @return an array of objects describing universities */ - @FormUrlEncoded - @POST("database.getUniversities") fun getUniversities( - @Field("q") query: String?, - @Field("country_id") countryId: Int?, - @Field("city_id") cityId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + query: String?, + countryId: Int?, + cityId: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "database.getUniversities", form( + "q" to query, + "country_id" to countryId, + "city_id" to cityId, + "offset" to offset, + "count" to count + ), items(UniversityDto.serializer()) + ) + } /** * Returns a list of schools. @@ -107,14 +133,21 @@ interface IDatabaseService { * @param count Number of schools to return. Default 100, maximum value 10000 * @return an array of objects describing schools */ - @FormUrlEncoded - @POST("database.getSchools") fun getSchools( - @Field("q") query: String?, - @Field("city_id") cityId: Int, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + query: String?, + cityId: Int, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "database.getSchools", form( + "q" to query, + "city_id" to cityId, + "offset" to offset, + "count" to count + ), items(SchoolDto.serializer()) + ) + } /** * Returns a list of cities. @@ -128,14 +161,23 @@ interface IDatabaseService { * @param count Number of cities to return. Default 100, maximum value 1000 * @return the total results number in count field and an array of objects describing cities in items field */ - @FormUrlEncoded - @POST("database.getCities") fun getCities( - @Field("country_id") countryId: Int, - @Field("region_id") regionId: Int?, - @Field("q") query: String?, - @Field("need_all") needAll: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + countryId: Int, + regionId: Int?, + query: String?, + needAll: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "database.getCities", form( + "country_id" to countryId, + "region_id" to regionId, + "q" to query, + "need_all" to needAll, + "offset" to offset, + "count" to count + ), items(VKApiCity.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDocsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDocsService.kt index 2ca37ffa3..9b9921eed 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDocsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IDocsService.kt @@ -5,19 +5,22 @@ import dev.ragnarok.fenrir.api.model.VKApiDoc import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.server.VKApiDocsUploadServer import dev.ragnarok.fenrir.api.model.server.VKApiVideosUploadServer +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IDocsService { +class IDocsService : IServiceRest() { //https://vk.com/dev/docs.delete - @FormUrlEncoded - @POST("docs.delete") fun delete( - @Field("owner_id") ownerId: Int?, - @Field("doc_id") docId: Int - ): Single> + ownerId: Int?, + docId: Int + ): Single> { + return rest.request( + "docs.delete", form( + "owner_id" to ownerId, + "doc_id" to docId + ), baseInt + ) + } /** * Copies a document to a user's or community's document list. @@ -27,13 +30,19 @@ interface IDocsService { * @param accessKey Access key. This parameter is required if access_key was returned with the document's data. * @return the ID of the created document. */ - @FormUrlEncoded - @POST("docs.add") fun add( - @Field("owner_id") ownerId: Int, - @Field("doc_id") docId: Int, - @Field("access_key") accessKey: String? - ): Single> + ownerId: Int, + docId: Int, + accessKey: String? + ): Single> { + return rest.request( + "docs.add", form( + "owner_id" to ownerId, + "doc_id" to docId, + "access_key" to accessKey + ), baseInt + ) + } /** * Returns information about documents by their IDs. @@ -42,9 +51,9 @@ interface IDocsService { * List of comma-separated words, required parameter * @return an array of objects describing documents */ - @FormUrlEncoded - @POST("docs.getById") - fun getById(@Field("docs") ids: String?): Single>> + fun getById(ids: String?): Single>> { + return rest.request("docs.getById", form("docs" to ids), baseList(VKApiDoc.serializer())) + } /** * Returns a list of documents matching the search criteria. @@ -54,13 +63,19 @@ interface IDocsService { * @param offset Offset needed to return a specific subset of results. * @return Returns the total results number in count field and an array of objects describing documents in items field */ - @FormUrlEncoded - @POST("docs.search") fun search( - @Field("q") query: String?, - @Field("count") count: Int?, - @Field("offset") offset: Int? - ): Single>> + query: String?, + count: Int?, + offset: Int? + ): Single>> { + return rest.request( + "docs.search", form( + "q" to query, + "count" to count, + "offset" to offset + ), items(VKApiDoc.serializer()) + ) + } /** * Saves a document after uploading it to a server. @@ -70,13 +85,19 @@ interface IDocsService { * @param tags Document tags. * @return Returns an array of uploaded document objects. */ - @FormUrlEncoded - @POST("docs.save") fun save( - @Field("file") file: String?, - @Field("title") title: String?, - @Field("tags") tags: String? - ): Single> + file: String?, + title: String?, + tags: String? + ): Single> { + return rest.request( + "docs.save", form( + "file" to file, + "title" to title, + "tags" to tags + ), base(VKApiDoc.Entry.serializer()) + ) + } /** * Returns the server address for document upload. @@ -85,24 +106,39 @@ interface IDocsService { * @param type type of document, null or "audio_message" (undocumented option) * @return an object with an upload_url field. After the document is uploaded, use the [.save] method. */ - @FormUrlEncoded - @POST("docs.getMessagesUploadServer") fun getMessagesUploadServer( - @Field("peer_id") peer_id: Int?, - @Field("type") type: String? - ): Single> + peer_id: Int?, + type: String? + ): Single> { + return rest.request( + "docs.getMessagesUploadServer", form( + "peer_id" to peer_id, + "type" to type + ), base(VKApiDocsUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("docs.getUploadServer") - fun getUploadServer(@Field("group_id") groupId: Int?): Single> + fun getUploadServer(groupId: Int?): Single> { + return rest.request( + "docs.getUploadServer", + form("group_id" to groupId), + base(VKApiDocsUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("video.save") fun getVideoServer( - @Field("is_private") is_private: Int?, - @Field("group_id") group_id: Int?, - @Field("name") name: String? - ): Single> + is_private: Int?, + group_id: Int?, + name: String? + ): Single> { + return rest.request( + "video.save", form( + "is_private" to is_private, + "group_id" to group_id, + "name" to name + ), base(VKApiVideosUploadServer.serializer()) + ) + } /** * Returns detailed information about user or community documents. @@ -123,12 +159,19 @@ interface IDocsService { * 8 — unknown. * @return Returns the total results number in count field and an array of objects describing documents in items field */ - @FormUrlEncoded - @POST("docs.get") operator fun get( - @Field("owner_id") ownerId: Int?, - @Field("count") count: Int?, - @Field("offset") offset: Int?, - @Field("type") type: Int? - ): Single>> + ownerId: Int?, + count: Int?, + offset: Int?, + type: Int? + ): Single>> { + return rest.request( + "docs.get", form( + "owner_id" to ownerId, + "count" to count, + "offset" to offset, + "type" to type + ), items(VKApiDoc.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFaveService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFaveService.kt index 844ec6cec..e638b794d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFaveService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFaveService.kt @@ -4,172 +4,284 @@ import dev.ragnarok.fenrir.api.model.* import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.FavePageResponse import dev.ragnarok.fenrir.api.model.response.FavePostsResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IFaveService { - @FormUrlEncoded - @POST("fave.getPages") +class IFaveService : IServiceRest() { fun getPages( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("type") type: String?, - @Field("fields") fields: String? - ): Single>> - - @FormUrlEncoded - @POST("fave.get") + offset: Int?, + count: Int?, + type: String?, + fields: String? + ): Single>> { + return rest.request( + "fave.getPages", form( + "offset" to offset, + "count" to count, + "type" to type, + "fields" to fields + ), items(FavePageResponse.serializer()) + ) + } + fun getVideos( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("item_type") item_type: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single>> - - @FormUrlEncoded - @POST("fave.get") + offset: Int?, + count: Int?, + item_type: String?, + extended: Int?, + fields: String? + ): Single>> { + return rest.request( + "fave.get", form( + "offset" to offset, + "count" to count, + "item_type" to item_type, + "extended" to extended, + "fields" to fields + ), items(VKApiAttachments.Entry.serializer()) + ) + } + fun getArticles( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("item_type") item_type: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single>> - - @FormUrlEncoded - @POST("articles.getOwnerPublished") + offset: Int?, + count: Int?, + item_type: String?, + extended: Int?, + fields: String? + ): Single>> { + return rest.request( + "fave.get", form( + "offset" to offset, + "count" to count, + "item_type" to item_type, + "extended" to extended, + "fields" to fields + ), items(VKApiAttachments.Entry.serializer()) + ) + } + fun getOwnerPublishedArticles( - @Field("owner_id") owner_id: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("sort_by") sort_by: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single>> - - @FormUrlEncoded - @POST("fave.get") + owner_id: Int?, + offset: Int?, + count: Int?, + sort_by: String?, + extended: Int?, + fields: String? + ): Single>> { + return rest.request( + "articles.getOwnerPublished", form( + "owner_id" to owner_id, + "offset" to offset, + "count" to count, + "sort_by" to sort_by, + "extended" to extended, + "fields" to fields + ), items(VKApiArticle.serializer()) + ) + } + fun getPosts( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("item_type") item_type: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> - - @FormUrlEncoded - @POST("fave.get") + offset: Int?, + count: Int?, + item_type: String?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "fave.get", form( + "offset" to offset, + "count" to count, + "item_type" to item_type, + "extended" to extended, + "fields" to fields + ), base(FavePostsResponse.serializer()) + ) + } + fun getLinks( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("item_type") item_type: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single>> - - @FormUrlEncoded - @POST("fave.get") + offset: Int?, + count: Int?, + item_type: String?, + extended: Int?, + fields: String? + ): Single>> { + return rest.request( + "fave.get", form( + "offset" to offset, + "count" to count, + "item_type" to item_type, + "extended" to extended, + "fields" to fields + ), items(FaveLinkDto.serializer()) + ) + } + fun getProducts( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("item_type") item_type: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single>> - - @FormUrlEncoded - @POST("fave.getPhotos") + offset: Int?, + count: Int?, + item_type: String?, + extended: Int?, + fields: String? + ): Single>> { + return rest.request( + "fave.get", form( + "offset" to offset, + "count" to count, + "item_type" to item_type, + "extended" to extended, + "fields" to fields + ), items(VKApiAttachments.Entry.serializer()) + ) + } + fun getPhotos( - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "fave.getPhotos", form( + "offset" to offset, + "count" to count + ), items(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("fave.addLink") - fun addLink(@Field("link") link: String?): Single> + fun addLink(link: String?): Single> { + return rest.request("fave.addLink", form("link" to link), baseInt) + } - @FormUrlEncoded - @POST("fave.addPage") fun addPage( - @Field("user_id") userId: Int?, - @Field("group_id") groupId: Int? - ): Single> + userId: Int?, + groupId: Int? + ): Single> { + return rest.request( + "fave.addPage", form( + "user_id" to userId, + "group_id" to groupId + ), baseInt + ) + } - @FormUrlEncoded - @POST("fave.addVideo") fun addVideo( - @Field("owner_id") owner_id: Int?, - @Field("id") id: Int?, - @Field("access_key") access_key: String? - ): Single> + owner_id: Int?, + id: Int?, + access_key: String? + ): Single> { + return rest.request( + "fave.addVideo", form( + "owner_id" to owner_id, + "id" to id, + "access_key" to access_key + ), baseInt + ) + } - @FormUrlEncoded - @POST("fave.addArticle") - fun addArticle(@Field("url") url: String?): Single> + fun addArticle(url: String?): Single> { + return rest.request("fave.addArticle", form("url" to url), baseInt) + } - @FormUrlEncoded - @POST("fave.addProduct") fun addProduct( - @Field("id") id: Int, - @Field("owner_id") owner_id: Int, - @Field("access_key") access_key: String? - ): Single> + id: Int, + owner_id: Int, + access_key: String? + ): Single> { + return rest.request( + "fave.addProduct", form( + "id" to id, + "owner_id" to owner_id, + "access_key" to access_key + ), baseInt + ) + } - @FormUrlEncoded - @POST("fave.addPost") fun addPost( - @Field("owner_id") owner_id: Int?, - @Field("id") id: Int?, - @Field("access_key") access_key: String? - ): Single> + owner_id: Int?, + id: Int?, + access_key: String? + ): Single> { + return rest.request( + "fave.addPost", form( + "owner_id" to owner_id, + "id" to id, + "access_key" to access_key + ), baseInt + ) + } //https://vk.com/dev/fave.removePage - @FormUrlEncoded - @POST("fave.removePage") fun removePage( - @Field("user_id") userId: Int?, - @Field("group_id") groupId: Int? - ): Single> + userId: Int?, + groupId: Int? + ): Single> { + return rest.request( + "fave.removePage", form( + "user_id" to userId, + "group_id" to groupId + ), baseInt + ) + } - @FormUrlEncoded - @POST("fave.removeLink") - fun removeLink(@Field("link_id") linkId: String?): Single> + fun removeLink(linkId: String?): Single> { + return rest.request("fave.removeLink", form("link_id" to linkId), baseInt) + } - @FormUrlEncoded - @POST("fave.removeArticle") fun removeArticle( - @Field("owner_id") owner_id: Int?, - @Field("article_id") article_id: Int? - ): Single> + owner_id: Int?, + article_id: Int? + ): Single> { + return rest.request( + "fave.removeArticle", form( + "owner_id" to owner_id, + "article_id" to article_id + ), baseInt + ) + } - @FormUrlEncoded - @POST("fave.removeProduct") fun removeProduct( - @Field("id") id: Int?, - @Field("owner_id") owner_id: Int? - ): Single> + id: Int?, + owner_id: Int? + ): Single> { + return rest.request( + "fave.removeProduct", form( + "id" to id, + "owner_id" to owner_id + ), baseInt + ) + } - @FormUrlEncoded - @POST("fave.removePost") fun removePost( - @Field("owner_id") owner_id: Int?, - @Field("id") id: Int? - ): Single> + owner_id: Int?, + id: Int? + ): Single> { + return rest.request( + "fave.removePost", form( + "owner_id" to owner_id, + "id" to id + ), baseInt + ) + } - @FormUrlEncoded - @POST("fave.removeVideo") fun removeVideo( - @Field("owner_id") owner_id: Int?, - @Field("id") id: Int? - ): Single> + owner_id: Int?, + id: Int? + ): Single> { + return rest.request( + "fave.removeVideo", form( + "owner_id" to owner_id, + "id" to id + ), baseInt + ) + } - @FormUrlEncoded - @POST("execute") fun pushFirst( - @Field("code") code: String?, - @Field("owner_id") ownerId: Int - ): Single> + code: String?, + ownerId: Int + ): Single> { + return rest.request( + "execute", form( + "code" to code, + "owner_id" to ownerId + ), baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFriendsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFriendsService.kt index b5a38c70d..542bdd03e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFriendsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IFriendsService.kt @@ -7,82 +7,133 @@ import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.DeleteFriendResponse import dev.ragnarok.fenrir.api.model.response.MutualFriendsResponse import dev.ragnarok.fenrir.api.model.response.OnlineFriendsResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IFriendsService { - @FormUrlEncoded - @POST("execute") - fun getOnline(@Field("code") code: String?): Single> +class IFriendsService : IServiceRest() { + fun getOnline(code: String?): Single> { + return rest.request( + "execute", + form("code" to code), + base(OnlineFriendsResponse.serializer()) + ) + } /*@FormUrlEncoded @POST("execute") Single> getWithMyCounters(@Field("code") String code);*/ - @FormUrlEncoded - @POST("friends.get") operator fun get( - @Field("user_id") userId: Int?, - @Field("order") order: String?, - @Field("list_id") listId: Int?, - @Field("count") count: Int?, - @Field("offset") offset: Int?, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String? - ): Single>> + userId: Int?, + order: String?, + listId: Int?, + count: Int?, + offset: Int?, + fields: String?, + nameCase: String? + ): Single>> { + return rest.request( + "friends.get", form( + "user_id" to userId, + "order" to order, + "list_id" to listId, + "count" to count, + "offset" to offset, + "fields" to fields, + "name_case" to nameCase + ), items(VKApiUser.serializer()) + ) + } - @FormUrlEncoded - @POST("friends.getRecommendations") fun getRecommendations( - @Field("count") count: Int?, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String? - ): Single>> + count: Int?, + fields: String?, + nameCase: String? + ): Single>> { + return rest.request( + "friends.getRecommendations", form( + "count" to count, + "fields" to fields, + "name_case" to nameCase + ), items(VKApiUser.serializer()) + ) + } //https://vk.com/dev/friends.getLists - @FormUrlEncoded - @POST("friends.getLists") fun getLists( - @Field("user_id") userId: Int?, - @Field("return_system") returnSystem: Int? - ): Single>> + userId: Int?, + returnSystem: Int? + ): Single>> { + return rest.request( + "friends.getLists", form( + "user_id" to userId, + "return_system" to returnSystem + ), items(VKApiFriendList.serializer()) + ) + } //https://vk.com/dev/friends.delete - @FormUrlEncoded - @POST("friends.delete") - fun delete(@Field("user_id") userId: Int): Single> + fun delete(userId: Int): Single> { + return rest.request( + "friends.delete", + form("user_id" to userId), + base(DeleteFriendResponse.serializer()) + ) + } //https://vk.com/dev/friends.add - @FormUrlEncoded - @POST("friends.add") fun add( - @Field("user_id") userId: Int, - @Field("text") text: String?, - @Field("follow") follow: Int? - ): Single> + userId: Int, + text: String?, + follow: Int? + ): Single> { + return rest.request( + "friends.add", form( + "user_id" to userId, + "text" to text, + "follow" to follow + ), baseInt + ) + } //https://vk.com/dev/friends.search - @FormUrlEncoded - @POST("friends.search") fun search( - @Field("user_id") userId: Int, - @Field("q") query: String?, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + userId: Int, + query: String?, + fields: String?, + nameCase: String?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "friends.search", form( + "user_id" to userId, + "q" to query, + "fields" to fields, + "name_case" to nameCase, + "offset" to offset, + "count" to count + ), items(VKApiUser.serializer()) + ) + } - @FormUrlEncoded - @POST("execute") - fun getMutual(@Field("code") code: String?): Single> + fun getMutual(code: String?): Single> { + return rest.request( + "execute", + form("code" to code), + base(MutualFriendsResponse.serializer()) + ) + } //https://vk.com/dev/friends.getByPhones - @FormUrlEncoded - @POST("friends.getByPhones") fun getByPhones( - @Field("phones") phones: String?, - @Field("fields") fields: String? - ): Single>> + phones: String?, + fields: String? + ): Single>> { + return rest.request( + "friends.getByPhones", form( + "phones" to phones, + "fields" to fields + ), baseList(VKApiUser.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IGroupsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IGroupsService.kt index 0d7cb74d8..58acd88b7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IGroupsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IGroupsService.kt @@ -4,172 +4,292 @@ import dev.ragnarok.fenrir.api.model.* import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.GroupLongpollServer import dev.ragnarok.fenrir.api.model.response.GroupWallInfoResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IGroupsService { - @FormUrlEncoded - @POST("groups.editManager") +class IGroupsService : IServiceRest() { fun editManager( - @Field("group_id") groupId: Int, - @Field("user_id") userId: Int, - @Field("role") role: String?, - @Field("is_contact") isContact: Int?, - @Field("contact_position") contactPosition: String?, - @Field("contact_email") contactEmail: String?, - @Field("contact_phone") contactPhone: String? - ): Single> - - @FormUrlEncoded - @POST("groups.edit") + groupId: Int, + userId: Int, + role: String?, + isContact: Int?, + contactPosition: String?, + contactEmail: String?, + contactPhone: String? + ): Single> { + return rest.request( + "groups.editManager", form( + "group_id" to groupId, + "user_id" to userId, + "role" to role, + "is_contact" to isContact, + "contact_position" to contactPosition, + "contact_email" to contactEmail, + "contact_phone" to contactPhone + ), baseInt + ) + } + fun edit( - @Field("group_id") groupId: Int, - @Field("title") title: String?, - @Field("description") description: String?, - @Field("screen_name") screen_name: String?, - @Field("access") access: Int?, - @Field("website") website: String?, - //@Field("public_category") public_category: Int?, - //@Field("public_subcategory") public_subcategory: Int?, - @Field("public_date") public_date: String?, - @Field("age_limits") age_limits: Int?, - @Field("obscene_filter") obscene_filter: Int?, - @Field("obscene_stopwords") obscene_stopwords: Int?, - @Field("obscene_words") obscene_words: String? - ): Single> - - @FormUrlEncoded - @POST("groups.unban") + groupId: Int, + title: String?, + description: String?, + screen_name: String?, + access: Int?, + website: String?, + //public_category: Int?, + //public_subcategory: Int?, + public_date: String?, + age_limits: Int?, + obscene_filter: Int?, + obscene_stopwords: Int?, + obscene_words: String? + ): Single> { + return rest.request( + "groups.edit", form( + "group_id" to groupId, + "title" to title, + "description" to description, + "screen_name" to screen_name, + "access" to access, + "website" to website, + "public_date" to public_date, + "age_limits" to age_limits, + "obscene_filter" to obscene_filter, + "obscene_stopwords" to obscene_stopwords, + "obscene_words" to obscene_words + ), baseInt + ) + } + fun unban( - @Field("group_id") groupId: Int, - @Field("owner_id") ownerId: Int - ): Single> + groupId: Int, + ownerId: Int + ): Single> { + return rest.request( + "groups.unban", form( + "group_id" to groupId, + "owner_id" to ownerId + ), baseInt + ) + } - @FormUrlEncoded - @POST("market.getAlbums") fun getMarketAlbums( - @Field("owner_id") owner_id: Int, - @Field("offset") offset: Int, - @Field("count") count: Int - ): Single>> + owner_id: Int, + offset: Int, + count: Int + ): Single>> { + return rest.request( + "market.getAlbums", form( + "owner_id" to owner_id, + "offset" to offset, + "count" to count + ), items(VKApiMarketAlbum.serializer()) + ) + } - @FormUrlEncoded - @POST("market.get") fun getMarket( - @Field("owner_id") owner_id: Int, - @Field("album_id") album_id: Int?, - @Field("offset") offset: Int, - @Field("count") count: Int, - @Field("extended") extended: Int? - ): Single>> - - @FormUrlEncoded - @POST("market.getServices") + owner_id: Int, + album_id: Int?, + offset: Int, + count: Int, + extended: Int? + ): Single>> { + return rest.request( + "market.get", form( + "owner_id" to owner_id, + "album_id" to album_id, + "offset" to offset, + "count" to count, + "extended" to extended + ), items(VKApiMarket.serializer()) + ) + } + fun getMarketServices( - @Field("owner_id") owner_id: Int, - @Field("offset") offset: Int, - @Field("count") count: Int, - @Field("extended") extended: Int? - ): Single>> - - @FormUrlEncoded - @POST("market.getById") + owner_id: Int, + offset: Int, + count: Int, + extended: Int? + ): Single>> { + return rest.request( + "market.getServices", form( + "owner_id" to owner_id, + "offset" to offset, + "count" to count, + "extended" to extended + ), items(VKApiMarket.serializer()) + ) + } + fun getMarketById( - @Field("item_ids") item_ids: String?, - @Field("extended") extended: Int? - ): Single>> + item_ids: String?, + extended: Int? + ): Single>> { + return rest.request( + "market.getById", form( + "item_ids" to item_ids, + "extended" to extended + ), items(VKApiMarket.serializer()) + ) + } - @POST("groups.ban") - @FormUrlEncoded fun ban( - @Field("group_id") groupId: Int, - @Field("owner_id") ownerId: Int, - @Field("end_date") endDate: Long?, - @Field("reason") reason: Int?, - @Field("comment") comment: String?, - @Field("comment_visible") commentVisible: Int? - ): Single> - - @FormUrlEncoded - @POST("groups.getSettings") - fun getSettings(@Field("group_id") groupId: Int): Single> + groupId: Int, + ownerId: Int, + endDate: Long?, + reason: Int?, + comment: String?, + commentVisible: Int? + ): Single> { + return rest.request( + "groups.ban", form( + "group_id" to groupId, + "owner_id" to ownerId, + "end_date" to endDate, + "reason" to reason, + "comment" to comment, + "comment_visible" to commentVisible + ), baseInt + ) + } + + fun getSettings(groupId: Int): Single> { + return rest.request( + "groups.getSettings", + form("group_id" to groupId), + base(GroupSettingsDto.serializer()) + ) + } //https://vk.com/dev/groups.getBanned - @FormUrlEncoded - @POST("groups.getBanned") fun getBanned( - @Field("group_id") groupId: Int, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("fields") fields: String?, - @Field("user_id") userId: Int? - ): Single>> - - @FormUrlEncoded - @POST("execute") + groupId: Int, + offset: Int?, + count: Int?, + fields: String?, + userId: Int? + ): Single>> { + return rest.request( + "groups.getBanned", form( + "group_id" to groupId, + "offset" to offset, + "count" to count, + "fields" to fields, + "user_id" to userId + ), items(VKApiBanned.serializer()) + ) + } + fun getGroupWallInfo( - @Field("code") code: String?, - @Field("group_id") groupId: String?, - @Field("fields") fields: String? - ): Single> + code: String?, + groupId: String?, + fields: String? + ): Single> { + return rest.request( + "execute", form( + "code" to code, + "group_id" to groupId, + "fields" to fields + ), base(GroupWallInfoResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("groups.getMembers") fun getMembers( - @Field("group_id") groupId: String?, - @Field("sort") sort: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("fields") fields: String?, - @Field("filter") filter: String? - ): Single>> + groupId: String?, + sort: Int?, + offset: Int?, + count: Int?, + fields: String?, + filter: String? + ): Single>> { + return rest.request( + "groups.getMembers", form( + "group_id" to groupId, + "sort" to sort, + "offset" to offset, + "count" to count, + "fields" to fields, + "filter" to filter + ), items(VKApiUser.serializer()) + ) + } //https://vk.com/dev/groups.search - @FormUrlEncoded - @POST("groups.search") fun search( - @Field("q") query: String?, - @Field("type") type: String?, - @Field("fields") fields: String?, - @Field("country_id") countryId: Int?, - @Field("city_id") cityId: Int?, - @Field("future") future: Int?, - @Field("market") market: Int?, - @Field("sort") sort: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> - - @FormUrlEncoded - @POST("groups.getLongPollServer") - fun getLongPollServer(@Field("group_id") groupId: Int): Single> + query: String?, + type: String?, + fields: String?, + countryId: Int?, + cityId: Int?, + future: Int?, + market: Int?, + sort: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "groups.search", form( + "q" to query, + "type" to type, + "fields" to fields, + "country_id" to countryId, + "city_id" to cityId, + "future" to future, + "market" to market, + "sort" to sort, + "offset" to offset, + "count" to count + ), items(VKApiCommunity.serializer()) + ) + } + + fun getLongPollServer(groupId: Int): Single> { + return rest.request( + "groups.getLongPollServer", + form("group_id" to groupId), + base(GroupLongpollServer.serializer()) + ) + } //https://vk.com/dev/groups.leave - @FormUrlEncoded - @POST("groups.leave") - fun leave(@Field("group_id") groupId: Int): Single> + fun leave(groupId: Int): Single> { + return rest.request("groups.leave", form("group_id" to groupId), baseInt) + } //https://vk.com/dev/groups.join - @FormUrlEncoded - @POST("groups.join") fun join( - @Field("group_id") groupId: Int, - @Field("not_sure") notSure: Int? - ): Single> + groupId: Int, + notSure: Int? + ): Single> { + return rest.request( + "groups.join", form( + "group_id" to groupId, + "not_sure" to notSure + ), baseInt + ) + } //https://vk.com/dev/groups.get - @FormUrlEncoded - @POST("groups.get") operator fun get( - @Field("user_id") userId: Int?, - @Field("extended") extended: Int?, - @Field("filter") filter: String?, - @Field("fields") fields: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + userId: Int?, + extended: Int?, + filter: String?, + fields: String?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "groups.get", form( + "user_id" to userId, + "extended" to extended, + "filter" to filter, + "fields" to fields, + "offset" to offset, + "count" to count + ), items(VKApiCommunity.serializer()) + ) + } /** * Returns information about communities by their IDs. @@ -180,19 +300,31 @@ interface IGroupsService { * @param fields Group fields to return. List of comma-separated words * @return an array of objects describing communities */ - @FormUrlEncoded - @POST("groups.getById") fun getById( - @Field("group_ids") groupIds: String?, - @Field("group_id") groupId: String?, - @Field("fields") fields: String? - ): Single>> + groupIds: String?, + groupId: String?, + fields: String? + ): Single>> { + return rest.request( + "groups.getById", form( + "group_ids" to groupIds, + "group_id" to groupId, + "fields" to fields + ), baseList(VKApiCommunity.serializer()) + ) + } - @FormUrlEncoded - @POST("groups.getChats") fun getChats( - @Field("group_id") groupId: Int, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + groupId: Int, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "groups.getChats", form( + "group_id" to groupId, + "offset" to offset, + "count" to count + ), items(VKApiGroupChats.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILikesService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILikesService.kt index 8c7e13c4f..58acb929d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILikesService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILikesService.kt @@ -4,65 +4,105 @@ import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.LikeResponse import dev.ragnarok.fenrir.api.model.response.LikesListResponse import dev.ragnarok.fenrir.api.model.response.isLikeResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface ILikesService { +class ILikesService : IServiceRest() { //https://vk.com/dev/likes.getList - @FormUrlEncoded - @POST("likes.getList") fun getList( - @Field("type") type: String?, - @Field("owner_id") ownerId: Int?, - @Field("item_id") itemId: Int?, - @Field("page_url") pageUrl: String?, - @Field("filter") filter: String?, - @Field("friends_only") friendsOnly: Int?, - @Field("extended") extended: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("skip_own") skipOwn: Int?, - @Field("fields") fields: String? - ): Single> + type: String?, + ownerId: Int?, + itemId: Int?, + pageUrl: String?, + filter: String?, + friendsOnly: Int?, + extended: Int?, + offset: Int?, + count: Int?, + skipOwn: Int?, + fields: String? + ): Single> { + return rest.request( + "likes.getList", form( + "type" to type, + "owner_id" to ownerId, + "item_id" to itemId, + "page_url" to pageUrl, + "filter" to filter, + "friends_only" to friendsOnly, + "extended" to extended, + "offset" to offset, + "count" to count, + "skip_own" to skipOwn, + "fields" to fields + ), base(LikesListResponse.serializer()) + ) + } //https://vk.com/dev/likes.delete - @FormUrlEncoded - @POST("likes.delete") fun delete( - @Field("type") type: String?, - @Field("owner_id") ownerId: Int?, - @Field("item_id") itemId: Int, - @Field("access_key") accessKey: String? - ): Single> + type: String?, + ownerId: Int?, + itemId: Int, + accessKey: String? + ): Single> { + return rest.request( + "likes.delete", form( + "type" to type, + "owner_id" to ownerId, + "item_id" to itemId, + "access_key" to accessKey + ), base(LikeResponse.serializer()) + ) + } //https://vk.com/dev/likes.add - @FormUrlEncoded - @POST("likes.add") fun add( - @Field("type") type: String?, - @Field("owner_id") ownerId: Int?, - @Field("item_id") itemId: Int, - @Field("access_key") accessKey: String? - ): Single> + type: String?, + ownerId: Int?, + itemId: Int, + accessKey: String? + ): Single> { + return rest.request( + "likes.add", form( + "type" to type, + "owner_id" to ownerId, + "item_id" to itemId, + "access_key" to accessKey + ), base(LikeResponse.serializer()) + ) + } //https://vk.com/dev/likes.isLiked - @FormUrlEncoded - @POST("likes.isLiked") fun isLiked( - @Field("type") type: String?, - @Field("owner_id") ownerId: Int?, - @Field("item_id") itemId: Int - ): Single> + type: String?, + ownerId: Int?, + itemId: Int + ): Single> { + return rest.request( + "likes.isLiked", form( + "type" to type, + "owner_id" to ownerId, + "item_id" to itemId + ), base(isLikeResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("execute") fun checkAndAddLike( - @Field("code") code: String?, - @Field("type") type: String?, - @Field("owner_id") ownerId: Int?, - @Field("item_id") itemId: Int, - @Field("access_key") accessKey: String? - ): Single> + code: String?, + type: String?, + ownerId: Int?, + itemId: Int, + accessKey: String? + ): Single> { + return rest.request( + "execute", form( + "code" to code, + "type" to type, + "owner_id" to ownerId, + "item_id" to itemId, + "access_key" to accessKey + ), baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILocalServerService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILocalServerService.kt index ba817c015..8209d0422 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILocalServerService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILocalServerService.kt @@ -5,117 +5,151 @@ import dev.ragnarok.fenrir.api.model.VKApiAudio import dev.ragnarok.fenrir.api.model.VKApiPhoto import dev.ragnarok.fenrir.api.model.VKApiVideo import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import dev.ragnarok.fenrir.model.FileRemote import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface ILocalServerService { - @FormUrlEncoded - @POST("audio.get") +class ILocalServerService : IServiceRest() { fun getAudios( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "audio.get", + form("offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiAudio.serializer()) + ) + } - @FormUrlEncoded - @POST("discography.get") fun getDiscography( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "discography.get", + form("offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiAudio.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.get") fun getPhotos( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "photos.get", + form("offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("video.get") fun getVideos( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "video.get", + form("offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiVideo.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.search") fun searchAudios( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("discography.search") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "audio.search", + form("q" to query, "offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiAudio.serializer()) + ) + } + fun searchDiscography( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("video.search") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "discography.search", + form("q" to query, "offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiAudio.serializer()) + ) + } + fun searchVideos( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("photos.search") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "video.search", + form("q" to query, "offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiVideo.serializer()) + ) + } + fun searchPhotos( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("update_time") - fun update_time(@Field("hash") hash: String?): Single> - - @FormUrlEncoded - @POST("delete_media") - fun delete_media(@Field("hash") hash: String?): Single> - - @FormUrlEncoded - @POST("get_file_name") - fun get_file_name(@Field("hash") hash: String?): Single> - - @FormUrlEncoded - @POST("update_file_name") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "photos.search", + form("q" to query, "offset" to offset, "count" to count, "reverse" to reverse), + items(VKApiPhoto.serializer()) + ) + } + + fun update_time(hash: String?): Single> { + return rest.request("update_time", form("hash" to hash), baseInt) + } + + fun delete_media(hash: String?): Single> { + return rest.request("delete_media", form("hash" to hash), baseInt) + } + + fun get_file_name(hash: String?): Single> { + return rest.request("get_file_name", form("hash" to hash), baseString) + } + fun update_file_name( - @Field("hash") hash: String?, - @Field("name") name: String? - ): Single> + hash: String?, + name: String? + ): Single> { + return rest.request("update_file_name", form("hash" to hash, "name" to name), baseInt) + } - @FormUrlEncoded - @POST("fs.get") fun fsGet( - @Field("dir") dir: String? - ): Single>> + dir: String? + ): Single>> { + return rest.request("fs.get", form("dir" to dir), items(FileRemote.serializer())) + } - @FormUrlEncoded - @POST("rebootPC") fun rebootPC( - @Field("type") type: String? - ): Single> + type: String? + ): Single> { + return rest.request("rebootPC", form("type" to type), baseInt) + } - @FormUrlEncoded - @POST("audio.upload") fun uploadAudio( - @Field("hash") hash: String?, - @Field("access_token") access_token: String?, - @Field("user_agent") user_agent: String? - ): Single> + hash: String?, + access_token: String?, + user_agent: String? + ): Single> { + return rest.request( + "audio.upload", + form("hash" to hash, "access_token" to access_token, "user_agent" to user_agent), + baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILongpollUpdatesService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILongpollUpdatesService.kt index deffdf537..3527af3af 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILongpollUpdatesService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/ILongpollUpdatesService.kt @@ -2,29 +2,44 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.longpoll.VkApiGroupLongpollUpdates import dev.ragnarok.fenrir.api.model.longpoll.VkApiLongpollUpdates +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.GET -import retrofit2.http.Query -import retrofit2.http.Url -interface ILongpollUpdatesService { - @GET +class ILongpollUpdatesService : IServiceRest() { fun getUpdates( - @Url server: String?, - @Query("act") act: String?, - @Query("key") key: String?, - @Query("ts") ts: Long, - @Query("wait") wait: Int, - @Query("mode") mode: Int, - @Query("version") version: Int - ): Single + server: String, + act: String?, + key: String?, + ts: Long, + wait: Int, + mode: Int, + version: Int + ): Single { + return rest.requestFullUrl( + server, + form( + "act" to act, + "key" to key, + "ts" to ts, + "wait" to wait, + "mode" to mode, + "version" to version + ), + VkApiLongpollUpdates.serializer() + ) + } - @GET fun getGroupUpdates( - @Url server: String?, - @Query("act") act: String?, - @Query("key") key: String?, - @Query("ts") ts: String?, - @Query("wait") wait: Int - ): Single + server: String, + act: String?, + key: String?, + ts: String?, + wait: Int + ): Single { + return rest.requestFullUrl( + server, + form("act" to act, "key" to key, "ts" to ts, "wait" to wait), + VkApiGroupLongpollUpdates.serializer() + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IMessageService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IMessageService.kt index af6b5af8a..516d79e0c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IMessageService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IMessageService.kt @@ -2,45 +2,59 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.* import dev.ragnarok.fenrir.api.model.response.* +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer -interface IMessageService { - @FormUrlEncoded - @POST("messages.edit") +class IMessageService : IServiceRest() { fun editMessage( - @Field("peer_id") peedId: Int, - @Field("message_id") messageId: Int, - @Field("message") message: String?, - @Field("attachment") attachment: String?, - @Field("keep_forward_messages") keepForwardMessages: Int?, - @Field("keep_snippets") keepSnippets: Int? - ): Single> - - @FormUrlEncoded - @POST("messages.pin") - fun pin( - @Field("peer_id") peerId: Int, - @Field("message_id") messageId: Int - ): Single> - - @FormUrlEncoded - @POST("messages.pinConversation") - fun pinConversation(@Field("peer_id") peerId: Int): Single> - - @FormUrlEncoded - @POST("messages.unpinConversation") - fun unpinConversation(@Field("peer_id") peerId: Int): Single> + peedId: Int, + messageId: Int, + message: String?, + attachment: String?, + keepForwardMessages: Int?, + keepSnippets: Int? + ): Single> { + return rest.request( + "messages.edit", form( + "peer_id" to peedId, + "message_id" to messageId, + "message" to message, + "attachment" to attachment, + "keep_forward_messages" to keepForwardMessages, + "keep_snippets" to keepSnippets + ), baseInt + ) + } - @FormUrlEncoded - @POST("messages.markAsListened") - fun markAsListened(@Field("message_id") message_id: Int): Single> - - @FormUrlEncoded - @POST("messages.unpin") - fun unpin(@Field("peer_id") peerId: Int): Single> + fun pin( + peerId: Int, + messageId: Int + ): Single> { + return rest.request( + "messages.pin", form( + "peer_id" to peerId, + "message_id" to messageId + ), base(VKApiMessage.serializer()) + ) + } + + fun pinConversation(peerId: Int): Single> { + return rest.request("messages.pinConversation", form("peer_id" to peerId), baseInt) + } + + fun unpinConversation(peerId: Int): Single> { + return rest.request("messages.unpinConversation", form("peer_id" to peerId), baseInt) + } + + fun markAsListened(message_id: Int): Single> { + return rest.request("messages.markAsListened", form("message_id" to message_id), baseInt) + } + + fun unpin(peerId: Int): Single> { + return rest.request("messages.unpin", form("peer_id" to peerId), baseInt) + } /** * Allows the current user to leave a chat or, if the current user started the chat, @@ -50,16 +64,25 @@ interface IMessageService { * @param memberId ID of the member to be removed from the chat * @return 1 */ - @FormUrlEncoded - @POST("messages.removeChatUser") fun removeChatUser( - @Field("chat_id") chatId: Int, - @Field("member_id") memberId: Int - ): Single> - - @FormUrlEncoded - @POST("messages.deleteChatPhoto") - fun deleteChatPhoto(@Field("chat_id") chatId: Int): Single> + chatId: Int, + memberId: Int + ): Single> { + return rest.request( + "messages.removeChatUser", form( + "chat_id" to chatId, + "member_id" to memberId + ), baseInt + ) + } + + fun deleteChatPhoto(chatId: Int): Single> { + return rest.request( + "messages.deleteChatPhoto", + form("chat_id" to chatId), + base(UploadChatPhotoResponse.serializer()) + ) + } /** * Adds a new user to a chat. @@ -68,12 +91,17 @@ interface IMessageService { * @param userId ID of the user to be added to the chat. * @return 1 */ - @FormUrlEncoded - @POST("messages.addChatUser") fun addChatUser( - @Field("chat_id") chatId: Int, - @Field("user_id") userId: Int - ): Single> + chatId: Int, + userId: Int + ): Single> { + return rest.request( + "messages.addChatUser", form( + "chat_id" to chatId, + "user_id" to userId + ), baseInt + ) + } /** * Returns information about a chat. @@ -90,21 +118,35 @@ interface IMessageService { * abl — prepositional * @return Returns a list of chat objects. */ - @FormUrlEncoded - @POST("messages.getChat") fun getChat( - @Field("chat_id") chatId: Int?, - @Field("chat_ids") chatIds: String?, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String? - ): Single> - - @FormUrlEncoded - @POST("messages.getConversationMembers") + chatId: Int?, + chatIds: String?, + fields: String?, + nameCase: String? + ): Single> { + return rest.request( + "messages.getChat", form( + "chat_id" to chatId, + "chat_ids" to chatIds, + "fields" to fields, + "name_case" to nameCase + ), base(ChatsInfoResponse.serializer()) + ) + } + fun getConversationMembers( - @Field("peer_id") peer_id: Int?, - @Field("fields") fields: String? - ): Single> + peer_id: Int?, + fields: String? + ): Single> { + return rest.request( + "messages.getConversationMembers", + form( + "peer_id" to peer_id, + "fields" to fields + ), + base(ConversationMembersResponse.serializer()) + ) + } /** * Edits the title of a chat. @@ -113,12 +155,17 @@ interface IMessageService { * @param title New title of the chat. * @return 1 */ - @FormUrlEncoded - @POST("messages.editChat") fun editChat( - @Field("chat_id") chatId: Int, - @Field("title") title: String? - ): Single> + chatId: Int, + title: String? + ): Single> { + return rest.request( + "messages.editChat", form( + "chat_id" to chatId, + "title" to title + ), baseInt + ) + } /** * Creates a chat with several participants. @@ -127,12 +174,17 @@ interface IMessageService { * @param title Chat title * @return the ID of the created chat (chat_id). */ - @FormUrlEncoded - @POST("messages.createChat") fun createChat( - @Field("user_ids") userIds: String?, - @Field("title") title: String? - ): Single> + userIds: String?, + title: String? + ): Single> { + return rest.request( + "messages.createChat", form( + "user_ids" to userIds, + "title" to title + ), baseInt + ) + } /** * Deletes all private messages in a conversation. @@ -140,9 +192,13 @@ interface IMessageService { * @param peerId Destination ID. * @return 1 */ - @FormUrlEncoded - @POST("messages.deleteConversation") - fun deleteDialog(@Field("peer_id") peerId: Int): Single> + fun deleteDialog(peerId: Int): Single> { + return rest.request( + "messages.deleteConversation", + form("peer_id" to peerId), + base(ConversationDeleteResult.serializer()) + ) + } /** * Restores a deleted message. @@ -150,9 +206,9 @@ interface IMessageService { * @param messageId ID of a previously-deleted message to restore * @return 1 */ - @FormUrlEncoded - @POST("messages.restore") - fun restore(@Field("message_id") messageId: Int): Single> + fun restore(messageId: Int): Single> { + return rest.request("messages.restore", form("message_id" to messageId), baseInt) + } /** * Deletes one or more messages. @@ -161,13 +217,21 @@ interface IMessageService { * @param spam 1 — to mark message as spam. * @return 1 */ - @FormUrlEncoded - @POST("messages.delete") fun delete( - @Field("message_ids") messageIds: String?, - @Field("delete_for_all") deleteForAll: Int?, - @Field("spam") spam: Int? - ): Single>> + messageIds: String?, + deleteForAll: Int?, + spam: Int? + ): Single>> { + return rest.request( + "messages.delete", + form( + "message_ids" to messageIds, + "delete_for_all" to deleteForAll, + "spam" to spam + ), + base(MapSerializer(String.serializer(), Int.serializer())) + ) + } /** * Marks messages as read. @@ -176,19 +240,29 @@ interface IMessageService { * @param startMessageId Message ID to start from * @return 1 */ - @FormUrlEncoded - @POST("messages.markAsRead") fun markAsRead( - @Field("peer_id") peerId: Int?, - @Field("start_message_id") startMessageId: Int? - ): Single> + peerId: Int?, + startMessageId: Int? + ): Single> { + return rest.request( + "messages.markAsRead", form( + "peer_id" to peerId, + "start_message_id" to startMessageId + ), baseInt + ) + } - @FormUrlEncoded - @POST("messages.markAsImportant") fun markAsImportant( - @Field("message_ids") messageIds: String?, - @Field("important") important: Int? - ): Single>> + messageIds: String?, + important: Int? + ): Single>> { + return rest.request( + "messages.markAsImportant", form( + "message_ids" to messageIds, + "important" to important + ), baseList(Int.serializer()) + ) + } /** * Changes the status of a user as typing in a conversation. @@ -197,12 +271,17 @@ interface IMessageService { * @param type typing — user has started to type. * @return 1 */ - @FormUrlEncoded - @POST("messages.setActivity") fun setActivity( - @Field("peer_id") peerId: Int, - @Field("type") type: String? - ): Single> + peerId: Int, + type: String? + ): Single> { + return rest.request( + "messages.setActivity", form( + "peer_id" to peerId, + "type" to type + ), baseInt + ) + } /** * Returns a list of the current user's private messages that match search criteria. @@ -217,16 +296,25 @@ interface IMessageService { * @param count Number of messages to return * @return list of the current user's private messages */ - @FormUrlEncoded - @POST("messages.search") fun search( - @Field("q") query: String?, - @Field("peer_id") peerId: Int?, - @Field("date") date: Long?, - @Field("preview_length") previewLength: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + query: String?, + peerId: Int?, + date: Long?, + previewLength: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "messages.search", form( + "q" to query, + "peer_id" to peerId, + "date" to date, + "preview_length" to previewLength, + "offset" to offset, + "count" to count + ), items(VKApiMessage.serializer()) + ) + } /** * Returns updates in user's private messages. @@ -254,18 +342,31 @@ interface IMessageService { * (addition of a new message) from the history field. Each object of message contains a set * of fields described here. The first array element is the total number of messages. */ - @FormUrlEncoded - @POST("messages.getLongPollHistory") fun getLongPollHistory( - @Field("ts") ts: Long?, - @Field("pts") pts: Long?, - @Field("preview_length") previewLength: Int?, - @Field("onlines") onlines: Int?, - @Field("fields") fields: String?, - @Field("events_limit") eventsLimit: Int?, - @Field("msgs_limit") msgsLimit: Int?, - @Field("max_msg_id") maxMsgId: Int? - ): Single> + ts: Long?, + pts: Long?, + previewLength: Int?, + onlines: Int?, + fields: String?, + eventsLimit: Int?, + msgsLimit: Int?, + maxMsgId: Int? + ): Single> { + return rest.request( + "messages.getLongPollHistory", + form( + "ts" to ts, + "pts" to pts, + "preview_length" to previewLength, + "onlines" to onlines, + "fields" to fields, + "events_limit" to eventsLimit, + "msgs_limit" to msgsLimit, + "max_msg_id" to maxMsgId + ), + base(LongpollHistoryResponse.serializer()) + ) + } /** * Returns media files from the dialog or group chat. @@ -279,16 +380,27 @@ interface IMessageService { * @return a list of photo, video, audio or doc objects depending on media_type parameter value * and additional next_from field containing new offset value. */ - @FormUrlEncoded - @POST("messages.getHistoryAttachments") fun getHistoryAttachments( - @Field("peer_id") peerId: Int, - @Field("media_type") mediaType: String?, - @Field("start_from") startFrom: String?, - @Field("count") count: Int?, - @Field("photo_sizes") photoSizes: Int?, - @Field("fields") fields: String? - ): Single> + peerId: Int, + mediaType: String?, + startFrom: String?, + count: Int?, + photoSizes: Int?, + fields: String? + ): Single> { + return rest.request( + "messages.getHistoryAttachments", + form( + "peer_id" to peerId, + "media_type" to mediaType, + "start_from" to startFrom, + "count" to count, + "photo_sizes" to photoSizes, + "fields" to fields + ), + base(AttachmentsHistoryResponse.serializer()) + ) + } /** * Sends a message. @@ -306,21 +418,35 @@ interface IMessageService { * @param stickerId Sticker id * @return sent message ID. */ - @FormUrlEncoded - @POST("messages.send") fun send( - @Field("random_id") randomId: Long?, - @Field("peer_id") peerId: Int?, - @Field("domain") domain: String?, - @Field("message") message: String?, - @Field("lat") latitude: Double?, - @Field("long") longitude: Double?, - @Field("attachment") attachment: String?, - @Field("forward_messages") forwardMessages: String?, - @Field("sticker_id") stickerId: Int?, - @Field("payload") payload: String?, - @Field("reply_to") reply_to: Int? - ): Single> + randomId: Long?, + peerId: Int?, + domain: String?, + message: String?, + latitude: Double?, + longitude: Double?, + attachment: String?, + forwardMessages: String?, + stickerId: Int?, + payload: String?, + reply_to: Int? + ): Single> { + return rest.request( + "messages.send", form( + "random_id" to randomId, + "peer_id" to peerId, + "domain" to domain, + "message" to message, + "lat" to latitude, + "long" to longitude, + "attachment" to attachment, + "forward_messages" to forwardMessages, + "sticker_id" to stickerId, + "payload" to payload, + "reply_to" to reply_to + ), baseInt + ) + } /** * Returns messages by their IDs. @@ -331,39 +457,67 @@ interface IMessageService { * NOTE: Messages are not truncated by default. Messages are truncated by words. * @return a list of message objects. */ - @FormUrlEncoded - @POST("messages.getById") fun getById( - @Field("message_ids") messageIds: String?, - @Field("preview_length") previewLength: Int? - ): Single>> + messageIds: String?, + previewLength: Int? + ): Single>> { + return rest.request( + "messages.getById", form( + "message_ids" to messageIds, + "preview_length" to previewLength + ), items(VKApiMessage.serializer()) + ) + } //https://vk.com/dev/messages.getConversations - @FormUrlEncoded - @POST("messages.getConversations") fun getDialogs( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("start_message_id") startMessageId: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + offset: Int?, + count: Int?, + startMessageId: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "messages.getConversations", form( + "offset" to offset, + "count" to count, + "start_message_id" to startMessageId, + "extended" to extended, + "fields" to fields + ), base(DialogsResponse.serializer()) + ) + } //https://vk.com/dev/messages.getConversationsById - @FormUrlEncoded - @POST("messages.getConversationsById") fun getConversationsById( - @Field("peer_ids") peerIds: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single>> + peerIds: String?, + extended: Int?, + fields: String? + ): Single>> { + return rest.request( + "messages.getConversationsById", + form( + "peer_ids" to peerIds, + "extended" to extended, + "fields" to fields + ), + base(ItemsProfilesGroupsResponse.serializer(VKApiConversation.serializer())) + ) + } - @FormUrlEncoded - @POST("messages.getLongPollServer") fun getLongpollServer( - @Field("need_pts") needPts: Int, - @Field("lp_version") lpVersion: Int - ): Single> + needPts: Int, + lpVersion: Int + ): Single> { + return rest.request( + "messages.getLongPollServer", + form( + "need_pts" to needPts, + "lp_version" to lpVersion + ), + base(VKApiLongpollServer.serializer()) + ) + } /** * Returns message history for the specified user or group chat. @@ -377,58 +531,106 @@ interface IMessageService { * 0 — return messages in reverse chronological order. * @return Returns a list of message objects. */ - @FormUrlEncoded - @POST("messages.getHistory") fun getHistory( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("peer_id") peerId: Int, - @Field("start_message_id") startMessageId: Int?, - @Field("rev") rev: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> - - @FormUrlEncoded - @POST("messages.getHistory") + offset: Int?, + count: Int?, + peerId: Int, + startMessageId: Int?, + rev: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "messages.getHistory", + form( + "offset" to offset, + "count" to count, + "peer_id" to peerId, + "start_message_id" to startMessageId, + "rev" to rev, + "extended" to extended, + "fields" to fields + ), + base(MessageHistoryResponse.serializer()) + ) + } + fun getJsonHistory( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("peer_id") peerId: Int - ): Single>> + offset: Int?, + count: Int?, + peerId: Int + ): Single>> { + return rest.request( + "messages.getHistory", form( + "offset" to offset, + "count" to count, + "peer_id" to peerId + ), items(VKApiJsonString.serializer()) + ) + } - @FormUrlEncoded - @POST("messages.getImportantMessages") fun getImportantMessages( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("start_message_id") startMessageId: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + offset: Int?, + count: Int?, + startMessageId: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "messages.getImportantMessages", + form( + "offset" to offset, + "count" to count, + "start_message_id" to startMessageId, + "extended" to extended, + "fields" to fields + ), + base(MessageImportantResponse.serializer()) + ) + } //https://vk.com/dev/messages.searchDialogs - @FormUrlEncoded - @POST("messages.searchConversations") fun searchConversations( - @Field("q") q: String?, - @Field("count") count: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> - - @FormUrlEncoded - @POST("messages.recogniseAudioMessage") + q: String?, + count: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "messages.searchConversations", + form( + "q" to q, + "count" to count, + "extended" to extended, + "fields" to fields + ), + base(ConversationsResponse.serializer()) + ) + } + fun recogniseAudioMessage( - @Field("message_id") message_id: Int?, - @Field("audio_message_id") audio_message_id: String? - ): Single> + message_id: Int?, + audio_message_id: String? + ): Single> { + return rest.request( + "messages.recogniseAudioMessage", form( + "message_id" to message_id, + "audio_message_id" to audio_message_id + ), baseInt + ) + } - @FormUrlEncoded - @POST("messages.setMemberRole") fun setMemberRole( - @Field("peer_id") peer_id: Int?, - @Field("member_id") member_id: Int?, - @Field("role") role: String? - ): Single> + peer_id: Int?, + member_id: Int?, + role: String? + ): Single> { + return rest.request( + "messages.setMemberRole", form( + "peer_id" to peer_id, + "member_id" to member_id, + "role" to role + ), baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INewsfeedService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INewsfeedService.kt index 8d113202e..41d633ca4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INewsfeedService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INewsfeedService.kt @@ -3,94 +3,143 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.Items import dev.ragnarok.fenrir.api.model.VKApiFeedList import dev.ragnarok.fenrir.api.model.response.* +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface INewsfeedService { +class INewsfeedService : IServiceRest() { /** * filters post, photo, video, topic, market, note */ //https://vk.com/dev/newsfeed.getComments - @FormUrlEncoded - @POST("newsfeed.getComments") fun getComments( - @Field("count") count: Int?, - @Field("filters") filters: String?, - @Field("reposts") reposts: String?, - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long?, - @Field("last_comments_count") lastCommentsCount: Int?, - @Field("start_from") startFrom: String?, - @Field("fields") fields: String?, - @Field("photo_sizes") photoSizes: Int? - ): Single> + count: Int?, + filters: String?, + reposts: String?, + startTime: Long?, + endTime: Long?, + lastCommentsCount: Int?, + startFrom: String?, + fields: String?, + photoSizes: Int? + ): Single> { + return rest.request( + "newsfeed.getComments", form( + "count" to count, + "filters" to filters, + "reposts" to reposts, + "start_time" to startTime, + "end_time" to endTime, + "last_comments_count" to lastCommentsCount, + "start_from" to startFrom, + "fields" to fields, + "photo_sizes" to photoSizes + ), base(NewsfeedCommentsResponse.serializer()) + ) + } //https://vk.com/dev/newsfeed.getMentions - @FormUrlEncoded - @POST("newsfeed.getMentions") fun getMentions( - @Field("owner_id") owner_id: Int?, - @Field("count") count: Int?, - @Field("offset") offset: Int?, - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long? - ): Single> + owner_id: Int?, + count: Int?, + offset: Int?, + startTime: Long?, + endTime: Long? + ): Single> { + return rest.request( + "newsfeed.getMentions", form( + "owner_id" to owner_id, + "count" to count, + "offset" to offset, + "start_time" to startTime, + "end_time" to endTime + ), base(NewsfeedCommentsResponse.serializer()) + ) + } //https://vk.com/dev/newsfeed.getLists - @FormUrlEncoded - @POST("newsfeed.getLists") fun getLists( - @Field("list_ids") listIds: String?, - @Field("extended") extended: Int? - ): Single>> + listIds: String?, + extended: Int? + ): Single>> { + return rest.request( + "newsfeed.getLists", form( + "list_ids" to listIds, + "extended" to extended + ), items(VKApiFeedList.serializer()) + ) + } //https://vk.com/dev/newsfeed.saveList - @FormUrlEncoded - @POST("newsfeed.saveList") fun saveList( - @Field("title") title: String?, - @Field("source_ids") source_ids: String? - ): Single> + title: String?, + source_ids: String? + ): Single> { + return rest.request( + "newsfeed.saveList", form( + "title" to title, + "source_ids" to source_ids + ), baseInt + ) + } //https://vk.com/dev/newsfeed.getBanned - @FormUrlEncoded - @POST("newsfeed.getBanned") fun getBanned( - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "newsfeed.getBanned", form( + "extended" to extended, + "fields" to fields + ), base(NewsfeedBanResponse.serializer()) + ) + } //https://vk.com/dev/newsfeed.deleteBan - @FormUrlEncoded - @POST("newsfeed.deleteBan") fun deleteBan( - @Field("user_ids") user_ids: String?, - @Field("group_ids") group_ids: String? - ): Single> + user_ids: String?, + group_ids: String? + ): Single> { + return rest.request( + "newsfeed.deleteBan", form( + "user_ids" to user_ids, + "group_ids" to group_ids + ), baseInt + ) + } //https://vk.com/dev/newsfeed.addBan - @FormUrlEncoded - @POST("newsfeed.addBan") fun addBan( - @Field("user_ids") user_ids: String?, - @Field("group_ids") group_ids: String? - ): Single> + user_ids: String?, + group_ids: String? + ): Single> { + return rest.request( + "newsfeed.addBan", form( + "user_ids" to user_ids, + "group_ids" to group_ids + ), baseInt + ) + } //https://vk.com/dev/newsfeed.ignoreItem - @FormUrlEncoded - @POST("newsfeed.ignoreItem") fun ignoreItem( - @Field("type") type: String?, - @Field("owner_id") owner_id: Int?, - @Field("item_id") item_id: Int? - ): Single> + type: String?, + owner_id: Int?, + item_id: Int? + ): Single> { + return rest.request( + "newsfeed.ignoreItem", form( + "type" to type, + "owner_id" to owner_id, + "item_id" to item_id + ), baseInt + ) + } //https://vk.com/dev/newsfeed.deleteList - @FormUrlEncoded - @POST("newsfeed.deleteList") - fun deleteList(@Field("list_id") list_id: Int?): Single> + fun deleteList(list_id: Int?): Single> { + return rest.request("newsfeed.deleteList", form("list_id" to list_id), baseInt) + } /** * Returns search results by statuses. @@ -107,19 +156,31 @@ interface INewsfeedService { * @param fields Additional fields of profiles and communities to return. * @return Returns the total number of posts and an array of wall objects */ - @FormUrlEncoded - @POST("newsfeed.search") fun search( - @Field("q") query: String?, - @Field("extended") extended: Int?, - @Field("count") count: Int?, - @Field("latitude") latitude: Double?, - @Field("longitude") longitude: Double?, - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long?, - @Field("start_from") startFrom: String?, - @Field("fields") fields: String? - ): Single> + query: String?, + extended: Int?, + count: Int?, + latitude: Double?, + longitude: Double?, + startTime: Long?, + endTime: Long?, + startFrom: String?, + fields: String? + ): Single> { + return rest.request( + "newsfeed.search", form( + "q" to query, + "extended" to extended, + "count" to count, + "latitude" to latitude, + "longitude" to longitude, + "start_time" to startTime, + "end_time" to endTime, + "start_from" to startFrom, + "fields" to fields + ), base(NewsfeedSearchResponse.serializer()) + ) + } /** * Returns data required to show newsfeed for the current user. @@ -156,52 +217,93 @@ interface INewsfeedService { * new_offset — Contains an offset parameter that is passed to get the next array of news. * next_from — Contains a from parameter that is passed to get the next array of news. */ - @FormUrlEncoded - @POST("newsfeed.get") operator fun get( - @Field("filters") filters: String?, - @Field("return_banned") returnBanned: Int?, - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long?, - @Field("max_photos") maxPhotoCount: Int?, - @Field("source_ids") sourceIds: String?, - @Field("start_from") startFrom: String?, - @Field("count") count: Int?, - @Field("fields") fields: String? - ): Single> + filters: String?, + returnBanned: Int?, + startTime: Long?, + endTime: Long?, + maxPhotoCount: Int?, + sourceIds: String?, + startFrom: String?, + count: Int?, + fields: String? + ): Single> { + return rest.request( + "newsfeed.get", form( + "filters" to filters, + "return_banned" to returnBanned, + "start_time" to startTime, + "end_time" to endTime, + "max_photos" to maxPhotoCount, + "source_ids" to sourceIds, + "start_from" to startFrom, + "count" to count, + "fields" to fields + ), base(NewsfeedResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("newsfeed.getByType") fun getByType( - @Field("feed_type") feed_type: String, - @Field("filters") filters: String?, - @Field("return_banned") returnBanned: Int?, - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long?, - @Field("max_photos") maxPhotoCount: Int?, - @Field("source_ids") sourceIds: String?, - @Field("start_from") startFrom: String?, - @Field("count") count: Int?, - @Field("fields") fields: String? - ): Single> + feed_type: String, + filters: String?, + returnBanned: Int?, + startTime: Long?, + endTime: Long?, + maxPhotoCount: Int?, + sourceIds: String?, + startFrom: String?, + count: Int?, + fields: String? + ): Single> { + return rest.request( + "newsfeed.getByType", form( + "feed_type" to feed_type, + "filters" to filters, + "return_banned" to returnBanned, + "start_time" to startTime, + "end_time" to endTime, + "max_photos" to maxPhotoCount, + "source_ids" to sourceIds, + "start_from" to startFrom, + "count" to count, + "fields" to fields + ), base(NewsfeedResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("newsfeed.getRecommended") fun getRecommended( - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long?, - @Field("max_photos") maxPhotoCount: Int?, - @Field("start_from") startFrom: String?, - @Field("count") count: Int?, - @Field("fields") fields: String? - ): Single> + startTime: Long?, + endTime: Long?, + maxPhotoCount: Int?, + startFrom: String?, + count: Int?, + fields: String? + ): Single> { + return rest.request( + "newsfeed.getRecommended", form( + "start_time" to startTime, + "end_time" to endTime, + "max_photos" to maxPhotoCount, + "start_from" to startFrom, + "count" to count, + "fields" to fields + ), base(NewsfeedResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("execute.getFeedLikes") fun getFeedLikes( - @Field("max_photos") maxPhotoCount: Int?, - @Field("start_from") startFrom: String?, - @Field("count") count: Int?, - @Field("fields") fields: String? - ): Single> + maxPhotoCount: Int?, + startFrom: String?, + count: Int?, + fields: String? + ): Single> { + return rest.request( + "execute.getFeedLikes", form( + "max_photos" to maxPhotoCount, + "start_from" to startFrom, + "count" to count, + "fields" to fields + ), base(NewsfeedResponse.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INotificationsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INotificationsService.kt index f9247ab0d..d11f80013 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INotificationsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/INotificationsService.kt @@ -2,38 +2,53 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.NotificationsResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import dev.ragnarok.fenrir.model.FeedbackVKOfficialList import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface INotificationsService { - @POST("notifications.markAsViewed") - fun markAsViewed(): Single> +class INotificationsService : IServiceRest() { + val markAsViewed: Single> + get() = rest.request("notifications.markAsViewed", null, baseInt) - @FormUrlEncoded - @POST("notifications.get") operator fun get( - @Field("count") count: Int?, - @Field("start_from") startFrom: String?, - @Field("filters") filters: String?, - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long? - ): Single> + count: Int?, + startFrom: String?, + filters: String?, + startTime: Long?, + endTime: Long? + ): Single> { + return rest.request( + "notifications.get", form( + "count" to count, + "start_from" to startFrom, + "filters" to filters, + "start_time" to startTime, + "end_time" to endTime + ), base(NotificationsResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("notifications.get") fun getOfficial( - @Field("count") count: Int?, - @Field("start_from") startFrom: Int?, - @Field("filters") filters: String?, - @Field("start_time") startTime: Long?, - @Field("end_time") endTime: Long?, - @Field("fields") fields: String? - ): Single> + count: Int?, + startFrom: Int?, + filters: String?, + startTime: Long?, + endTime: Long?, + fields: String? + ): Single> { + return rest.request( + "notifications.get", form( + "count" to count, + "start_from" to startFrom, + "filters" to filters, + "start_time" to startTime, + "end_time" to endTime, + "fields" to fields + ), base(FeedbackVKOfficialList.serializer()) + ) + } - @FormUrlEncoded - @POST("notifications.hide") - fun hide(@Field("query") query: String?): Single> + fun hide(query: String?): Single> { + return rest.request("notifications.hide", form("query" to query), baseInt) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPagesService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPagesService.kt index 351ee19ec..2d8f034bf 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPagesService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPagesService.kt @@ -2,22 +2,30 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.VKApiWikiPage import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IPagesService { +class IPagesService : IServiceRest() { //https://vk.com/dev/pages.get - @FormUrlEncoded - @POST("pages.get") operator fun get( - @Field("owner_id") ownerId: Int, - @Field("page_id") pageId: Int, - @Field("global") global: Int?, - @Field("site_preview") sitePreview: Int?, - @Field("title") title: String?, - @Field("need_source") needSource: Int?, - @Field("need_html") needHtml: Int? - ): Single> + ownerId: Int, + pageId: Int, + global: Int?, + sitePreview: Int?, + title: String?, + needSource: Int?, + needHtml: Int? + ): Single> { + return rest.request( + "pages.get", form( + "owner_id" to ownerId, + "page_id" to pageId, + "global" to global, + "site_preview" to sitePreview, + "title" to title, + "need_source" to needSource, + "need_html" to needHtml + ), base(VKApiWikiPage.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPhotosService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPhotosService.kt index 3180a48dc..9bc29cd00 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPhotosService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPhotosService.kt @@ -6,68 +6,105 @@ import dev.ragnarok.fenrir.api.model.response.DefaultCommentsResponse import dev.ragnarok.fenrir.api.model.response.UploadChatPhotoResponse import dev.ragnarok.fenrir.api.model.response.UploadOwnerPhotoResponse import dev.ragnarok.fenrir.api.model.server.* +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.GET -import retrofit2.http.POST -interface IPhotosService { +class IPhotosService : IServiceRest() { //https://vk.com/dev/photos.deleteAlbum - @FormUrlEncoded - @POST("photos.deleteAlbum") fun deleteAlbum( - @Field("album_id") albumId: Int, - @Field("group_id") groupId: Int? - ): Single> + albumId: Int, + groupId: Int? + ): Single> { + return rest.request( + "photos.deleteAlbum", form( + "album_id" to albumId, + "group_id" to groupId + ), baseInt + ) + } //https://vk.com/dev/photos.restore - @FormUrlEncoded - @POST("photos.restore") fun restore( - @Field("owner_id") ownerId: Int?, - @Field("photo_id") photoId: Int - ): Single> + ownerId: Int?, + photoId: Int + ): Single> { + return rest.request( + "photos.restore", form( + "owner_id" to ownerId, + "photo_id" to photoId + ), baseInt + ) + } //https://vk.com/dev/photos.delete - @FormUrlEncoded - @POST("photos.delete") fun delete( - @Field("owner_id") ownerId: Int?, - @Field("photo_id") photoId: Int - ): Single> + ownerId: Int?, + photoId: Int + ): Single> { + return rest.request( + "photos.delete", form( + "owner_id" to ownerId, + "photo_id" to photoId + ), baseInt + ) + } //https://vk.com/dev/photos.deleteComment - @FormUrlEncoded - @POST("photos.deleteComment") fun deleteComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int - ): Single> + ownerId: Int?, + commentId: Int + ): Single> { + return rest.request( + "photos.deleteComment", form( + "owner_id" to ownerId, + "comment_id" to commentId + ), baseInt + ) + } //https://vk.com/dev/photos.restoreComment - @FormUrlEncoded - @POST("photos.restoreComment") fun restoreComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int - ): Single> + ownerId: Int?, + commentId: Int + ): Single> { + return rest.request( + "photos.restoreComment", form( + "owner_id" to ownerId, + "comment_id" to commentId + ), baseInt + ) + } //https://vk.com/dev/photos.getComments - @FormUrlEncoded - @POST("photos.getComments") fun getComments( - @Field("owner_id") ownerId: Int?, - @Field("photo_id") photoId: Int, - @Field("need_likes") needLikes: Int?, - @Field("start_comment_id") startCommentId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("sort") sort: String?, - @Field("access_key") accessKey: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + ownerId: Int?, + photoId: Int, + needLikes: Int?, + startCommentId: Int?, + offset: Int?, + count: Int?, + sort: String?, + accessKey: String?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "photos.getComments", + form( + "owner_id" to ownerId, + "photo_id" to photoId, + "need_likes" to needLikes, + "start_comment_id" to startCommentId, + "offset" to offset, + "count" to count, + "sort" to sort, + "access_key" to accessKey, + "extended" to extended, + "fields" to fields + ), + base(DefaultCommentsResponse.serializer()) + ) + } /** * Edits a comment on a photo. @@ -89,14 +126,21 @@ interface IPhotosService { * List of comma-separated words * @return 1 */ - @FormUrlEncoded - @POST("photos.editComment") fun editComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int, - @Field("message") message: String?, - @Field("attachments") attachments: String? - ): Single> + ownerId: Int?, + commentId: Int, + message: String?, + attachments: String? + ): Single> { + return rest.request( + "photos.editComment", form( + "owner_id" to ownerId, + "comment_id" to commentId, + "message" to message, + "attachments" to attachments + ), baseInt + ) + } /** * Creates an empty photo album. @@ -114,17 +158,27 @@ interface IPhotosService { * 1 - commenting is disabled. * @return Returns an instance of photo album */ - @FormUrlEncoded - @POST("photos.createAlbum") fun createAlbum( - @Field("title") title: String?, - @Field("group_id") groupId: Int?, - @Field("description") description: String?, - @Field("privacy_view") privacyView: String?, - @Field("privacy_comment") privacyComment: String?, - @Field("upload_by_admins_only") uploadByAdminsOnly: Int?, - @Field("comments_disabled") commentsDisabled: Int? - ): Single> + title: String?, + groupId: Int?, + description: String?, + privacyView: String?, + privacyComment: String?, + uploadByAdminsOnly: Int?, + commentsDisabled: Int? + ): Single> { + return rest.request( + "photos.createAlbum", form( + "title" to title, + "group_id" to groupId, + "description" to description, + "privacy_view" to privacyView, + "privacy_comment" to privacyComment, + "upload_by_admins_only" to uploadByAdminsOnly, + "comments_disabled" to commentsDisabled + ), base(VKApiPhotoAlbum.serializer()) + ) + } /** * Edits information about a photo album. @@ -143,18 +197,29 @@ interface IPhotosService { * 1 - commenting is disabled. * @return 1 */ - @FormUrlEncoded - @POST("photos.editAlbum") fun editAlbum( - @Field("album_id") albumId: Int, - @Field("title") title: String?, - @Field("description") description: String?, - @Field("owner_id") ownerId: Int?, - @Field("privacy_view") privacyView: String?, - @Field("privacy_comment") privacyComment: String?, - @Field("upload_by_admins_only") uploadByAdminsOnly: Int?, - @Field("comments_disabled") commentsDisabled: Int? - ): Single> + albumId: Int, + title: String?, + description: String?, + ownerId: Int?, + privacyView: String?, + privacyComment: String?, + uploadByAdminsOnly: Int?, + commentsDisabled: Int? + ): Single> { + return rest.request( + "photos.editAlbum", form( + "album_id" to albumId, + "title" to title, + "description" to description, + "owner_id" to ownerId, + "privacy_view" to privacyView, + "privacy_comment" to privacyComment, + "upload_by_admins_only" to uploadByAdminsOnly, + "comments_disabled" to commentsDisabled + ), baseInt + ) + } /** * Allows to copy a photo to the "Saved photos" album @@ -164,181 +229,332 @@ interface IPhotosService { * @param accessKey special access key for private photos * @return Returns the created photo ID. */ - @FormUrlEncoded - @POST("photos.copy") fun copy( - @Field("owner_id") ownerId: Int, - @Field("photo_id") photoId: Int, - @Field("access_key") accessKey: String? - ): Single> + ownerId: Int, + photoId: Int, + accessKey: String? + ): Single> { + return rest.request( + "photos.copy", form( + "owner_id" to ownerId, + "photo_id" to photoId, + "access_key" to accessKey + ), baseInt + ) + } - @FormUrlEncoded - @POST("photos.createComment") fun createComment( - @Field("owner_id") ownerId: Int?, - @Field("photo_id") photoId: Int, - @Field("from_group") fromGroup: Int?, - @Field("message") message: String?, - @Field("reply_to_comment") replyToComment: Int?, - @Field("attachments") attachments: String?, - @Field("sticker_id") stickerId: Int?, - @Field("access_key") accessKey: String?, - @Field("guid") generatedUniqueId: Int? - ): Single> + ownerId: Int?, + photoId: Int, + fromGroup: Int?, + message: String?, + replyToComment: Int?, + attachments: String?, + stickerId: Int?, + accessKey: String?, + generatedUniqueId: Int? + ): Single> { + return rest.request( + "photos.createComment", form( + "owner_id" to ownerId, + "photo_id" to photoId, + "from_group" to fromGroup, + "message" to message, + "reply_to_comment" to replyToComment, + "attachments" to attachments, + "sticker_id" to stickerId, + "access_key" to accessKey, + "guid" to generatedUniqueId + ), baseInt + ) + } - @FormUrlEncoded - @POST("photos.getById") fun getById( - @Field("photos") photos: String?, - @Field("extended") extended: Int?, - @Field("photo_sizes") photo_sizes: Int? - ): Single>> + photos: String?, + extended: Int?, + photo_sizes: Int? + ): Single>> { + return rest.request( + "photos.getById", form( + "photos" to photos, + "extended" to extended, + "photo_sizes" to photo_sizes + ), baseList(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getUploadServer") fun getUploadServer( - @Field("album_id") albumId: Int, - @Field("group_id") groupId: Int? - ): Single> + albumId: Int, + groupId: Int? + ): Single> { + return rest.request( + "photos.getUploadServer", form( + "album_id" to albumId, + "group_id" to groupId + ), base(VKApiUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.saveOwnerPhoto") fun saveOwnerPhoto( - @Field("server") server: String?, - @Field("hash") hash: String?, - @Field("photo") photo: String? - ): Single> + server: String?, + hash: String?, + photo: String? + ): Single> { + return rest.request( + "photos.saveOwnerPhoto", + form( + "server" to server, + "hash" to hash, + "photo" to photo + ), + base(UploadOwnerPhotoResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("messages.setChatPhoto") - fun setChatPhoto(@Field("file") file: String?): Single> + fun setChatPhoto(file: String?): Single> { + return rest.request( + "messages.setChatPhoto", + form("file" to file), + base(UploadChatPhotoResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getOwnerPhotoUploadServer") - fun getOwnerPhotoUploadServer(@Field("owner_id") ownerId: Int?): Single> + fun getOwnerPhotoUploadServer(ownerId: Int?): Single> { + return rest.request( + "photos.getOwnerPhotoUploadServer", + form("owner_id" to ownerId), + base(VKApiOwnerPhotoUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getChatUploadServer") - fun getChatUploadServer(@Field("chat_id") chat_id: Int?): Single> + fun getChatUploadServer(chat_id: Int?): Single> { + return rest.request( + "photos.getChatUploadServer", + form("chat_id" to chat_id), + base(VKApiChatPhotoUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.saveWallPhoto") fun saveWallPhoto( - @Field("user_id") userId: Int?, - @Field("group_id") groupId: Int?, - @Field("photo") photo: String?, - @Field("server") server: Int, - @Field("hash") hash: String?, - @Field("latitude") latitude: Double?, - @Field("longitude") longitude: Double?, - @Field("caption") caption: String? - ): Single>> + userId: Int?, + groupId: Int?, + photo: String?, + server: Int, + hash: String?, + latitude: Double?, + longitude: Double?, + caption: String? + ): Single>> { + return rest.request( + "photos.saveWallPhoto", form( + "user_id" to userId, + "group_id" to groupId, + "photo" to photo, + "server" to server, + "hash" to hash, + "latitude" to latitude, + "longitude" to longitude, + "caption" to caption + ), baseList(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getWallUploadServer") - fun getWallUploadServer(@Field("group_id") groupId: Int?): Single> + fun getWallUploadServer(groupId: Int?): Single> { + return rest.request( + "photos.getWallUploadServer", + form("group_id" to groupId), + base(VKApiWallUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.save") fun save( - @Field("album_id") albumId: Int, - @Field("group_id") groupId: Int?, - @Field("server") server: Int, - @Field("photos_list") photosList: String?, - @Field("hash") hash: String?, - @Field("latitude") latitude: Double?, - @Field("longitude") longitude: Double?, - @Field("caption") caption: String? - ): Single>> + albumId: Int, + groupId: Int?, + server: Int, + photosList: String?, + hash: String?, + latitude: Double?, + longitude: Double?, + caption: String? + ): Single>> { + return rest.request( + "photos.save", form( + "album_id" to albumId, + "group_id" to groupId, + "server" to server, + "photos_list" to photosList, + "hash" to hash, + "latitude" to latitude, + "longitude" to longitude, + "caption" to caption + ), baseList(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.get") operator fun get( - @Field("owner_id") ownerId: Int?, - @Field("album_id") albumId: String?, - @Field("photo_ids") photoIds: String?, - @Field("rev") rev: Int?, - @Field("extended") extended: Int?, - @Field("photo_sizes") photoSizes: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + ownerId: Int?, + albumId: String?, + photoIds: String?, + rev: Int?, + extended: Int?, + photoSizes: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "photos.get", form( + "owner_id" to ownerId, + "album_id" to albumId, + "photo_ids" to photoIds, + "rev" to rev, + "extended" to extended, + "photo_sizes" to photoSizes, + "offset" to offset, + "count" to count + ), items(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getUserPhotos") fun getUserPhotos( - @Field("user_id") ownerId: Int?, - @Field("extended") extended: Int?, - @Field("sort") sort: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + ownerId: Int?, + extended: Int?, + sort: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "photos.getUserPhotos", form( + "user_id" to ownerId, + "extended" to extended, + "sort" to sort, + "offset" to offset, + "count" to count + ), items(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getAll") fun getAll( - @Field("owner_id") ownerId: Int?, - @Field("extended") extended: Int?, - @Field("photo_sizes") photo_sizes: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("no_service_albums") no_service_albums: Int?, - @Field("need_hidden") need_hidden: Int?, - @Field("skip_hidden") skip_hidden: Int? - ): Single>> + ownerId: Int?, + extended: Int?, + photo_sizes: Int?, + offset: Int?, + count: Int?, + no_service_albums: Int?, + need_hidden: Int?, + skip_hidden: Int? + ): Single>> { + return rest.request( + "photos.getAll", form( + "owner_id" to ownerId, + "extended" to extended, + "photo_sizes" to photo_sizes, + "offset" to offset, + "count" to count, + "no_service_albums" to no_service_albums, + "need_hidden" to need_hidden, + "skip_hidden" to skip_hidden + ), items(VKApiPhoto.serializer()) + ) + } - @get:GET("photos.getMessagesUploadServer") val messagesUploadServer: Single> + get() = rest.request( + "photos.getMessagesUploadServer", + null, + base(VKApiPhotoMessageServer.serializer()) + ) - @FormUrlEncoded - @POST("photos.saveMessagesPhoto") fun saveMessagesPhoto( - @Field("server") server: Int?, - @Field("photo") photo: String?, - @Field("hash") hash: String? - ): Single>> + server: Int?, + photo: String?, + hash: String? + ): Single>> { + return rest.request( + "photos.saveMessagesPhoto", form( + "server" to server, + "photo" to photo, + "hash" to hash + ), baseList(VKApiPhoto.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getAlbums") fun getAlbums( - @Field("owner_id") ownerId: Int?, - @Field("album_ids") albumIds: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("need_system") needSystem: Int?, - @Field("need_covers") needCovers: Int?, - @Field("photo_sizes") photoSizes: Int? - ): Single>> + ownerId: Int?, + albumIds: String?, + offset: Int?, + count: Int?, + needSystem: Int?, + needCovers: Int?, + photoSizes: Int? + ): Single>> { + return rest.request( + "photos.getAlbums", form( + "owner_id" to ownerId, + "album_ids" to albumIds, + "offset" to offset, + "count" to count, + "need_system" to needSystem, + "need_covers" to needCovers, + "photo_sizes" to photoSizes + ), items(VKApiPhotoAlbum.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getTags") fun getTags( - @Field("owner_id") ownerId: Int?, - @Field("photo_id") photo_id: Int?, - @Field("access_key") access_key: String? - ): Single>> + ownerId: Int?, + photo_id: Int?, + access_key: String? + ): Single>> { + return rest.request( + "photos.getTags", form( + "owner_id" to ownerId, + "photo_id" to photo_id, + "access_key" to access_key + ), baseList(VKApiPhotoTags.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.getAllComments") fun getAllComments( - @Field("owner_id") ownerId: Int?, - @Field("album_id") album_id: Int?, - @Field("need_likes") need_likes: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + ownerId: Int?, + album_id: Int?, + need_likes: Int?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "photos.getAllComments", form( + "owner_id" to ownerId, + "album_id" to album_id, + "need_likes" to need_likes, + "offset" to offset, + "count" to count + ), items(VKApiComment.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.search") fun search( - @Field("q") q: String?, - @Field("lat") lat_gps: Double?, - @Field("long") long_gps: Double?, - @Field("sort") sort: Int?, - @Field("radius") radius: Int?, - @Field("start_time") start_time: Long?, - @Field("end_time") end_time: Long?, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + q: String?, + lat_gps: Double?, + long_gps: Double?, + sort: Int?, + radius: Int?, + start_time: Long?, + end_time: Long?, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "photos.search", form( + "q" to q, + "lat" to lat_gps, + "long" to long_gps, + "sort" to sort, + "radius" to radius, + "start_time" to start_time, + "end_time" to end_time, + "offset" to offset, + "count" to count + ), items(VKApiPhoto.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPollsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPollsService.kt index ce3083664..1551bc819 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPollsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IPollsService.kt @@ -3,60 +3,97 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.VKApiPoll import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.PollUsersResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IPollsService { - @FormUrlEncoded - @POST("polls.create") +class IPollsService : IServiceRest() { fun create( - @Field("question") question: String?, - @Field("is_anonymous") isAnonymous: Int?, - @Field("is_multiple") isMultiple: Int?, - @Field("owner_id") ownerId: Int?, - @Field("add_answers") addAnswers: String? - ): Single> + question: String?, + isAnonymous: Int?, + isMultiple: Int?, + ownerId: Int?, + addAnswers: String? + ): Single> { + return rest.request( + "polls.create", form( + "question" to question, + "is_anonymous" to isAnonymous, + "is_multiple" to isMultiple, + "owner_id" to ownerId, + "add_answers" to addAnswers + ), base(VKApiPoll.serializer()) + ) + } //https://vk.com/dev/polls.deleteVote - @FormUrlEncoded - @POST("polls.deleteVote") fun deleteVote( - @Field("owner_id") ownerId: Int?, - @Field("poll_id") pollId: Int, - @Field("answer_id") answerId: Long, - @Field("is_board") isBoard: Int? - ): Single> + ownerId: Int?, + pollId: Int, + answerId: Long, + isBoard: Int? + ): Single> { + return rest.request( + "polls.deleteVote", form( + "owner_id" to ownerId, + "poll_id" to pollId, + "answer_id" to answerId, + "is_board" to isBoard + ), baseInt + ) + } //https://vk.com/dev/polls.addVote - @FormUrlEncoded - @POST("polls.addVote") fun addVote( - @Field("owner_id") ownerId: Int?, - @Field("poll_id") pollId: Int, - @Field("answer_ids") answerIds: String?, - @Field("is_board") isBoard: Int? - ): Single> + ownerId: Int?, + pollId: Int, + answerIds: String?, + isBoard: Int? + ): Single> { + return rest.request( + "polls.addVote", form( + "owner_id" to ownerId, + "poll_id" to pollId, + "answer_ids" to answerIds, + "is_board" to isBoard + ), baseInt + ) + } - @FormUrlEncoded - @POST("polls.getById") fun getById( - @Field("owner_id") ownerId: Int?, - @Field("is_board") isBoard: Int?, - @Field("poll_id") pollId: Int? - ): Single> + ownerId: Int?, + isBoard: Int?, + pollId: Int? + ): Single> { + return rest.request( + "polls.getById", form( + "owner_id" to ownerId, + "is_board" to isBoard, + "poll_id" to pollId + ), base(VKApiPoll.serializer()) + ) + } - @FormUrlEncoded - @POST("polls.getVoters") fun getVoters( - @Field("owner_id") ownerId: Int, - @Field("poll_id") pollId: Int, - @Field("is_board") isBoard: Int?, - @Field("answer_ids") answer_ids: String, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String? - ): Single>> + ownerId: Int, + pollId: Int, + isBoard: Int?, + answer_ids: String, + offset: Int?, + count: Int?, + fields: String?, + nameCase: String? + ): Single>> { + return rest.request( + "polls.getVoters", form( + "owner_id" to ownerId, + "poll_id" to pollId, + "is_board" to isBoard, + "answer_ids" to answer_ids, + "offset" to offset, + "count" to count, + "fields" to fields, + "name_case" to nameCase + ), baseList(PollUsersResponse.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt index 61a297e64..1cec90062 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt @@ -1,12 +1,10 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IStatusService { +class IStatusService : IServiceRest() { /** * Sets a new status for the current user. * @@ -14,10 +12,15 @@ interface IStatusService { * @param groupId Identifier of a community to set a status in. If left blank the status is set to current user. * @return 1 */ - @FormUrlEncoded - @POST("status.set") operator fun set( - @Field("text") text: String?, - @Field("group_id") groupId: Int? - ): Single> + text: String?, + groupId: Int? + ): Single> { + return rest.request( + "status.set", form( + "text" to text, + "group_id" to groupId + ), baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoreService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoreService.kt index 0e4fbf537..4ec92e9ec 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoreService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoreService.kt @@ -3,17 +3,23 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.VKApiStickerSetsData import dev.ragnarok.fenrir.api.model.VKApiStickersKeywords import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IStoreService { - @FormUrlEncoded - @POST("execute") - fun getStickers(@Field("code") code: String?): Single> +class IStoreService : IServiceRest() { + fun getStickers(code: String?): Single> { + return rest.request( + "execute", + form("code" to code), + base(VKApiStickerSetsData.serializer()) + ) + } - @FormUrlEncoded - @POST("execute") - fun getStickersKeywords(@Field("code") code: String?): Single> + fun getStickersKeywords(code: String?): Single> { + return rest.request( + "execute", + form("code" to code), + base(VKApiStickersKeywords.serializer()) + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUploadService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUploadService.kt index 42c531b4a..f48428fdf 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUploadService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUploadService.kt @@ -2,72 +2,69 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.upload.* +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single import okhttp3.MultipartBody -import retrofit2.http.Multipart -import retrofit2.http.POST -import retrofit2.http.Part -import retrofit2.http.Url -interface IUploadService { - @Multipart - @POST - fun uploadDocumentRx(@Url server: String?, @Part file: MultipartBody.Part): Single +class IUploadService : IServiceRest() { + fun uploadDocumentRx(server: String, file: MultipartBody.Part): Single { + return rest.doMultipartFormFullUrl(server, file, UploadDocDto.serializer()) + } - @Multipart - @POST - fun uploadAudioRx(@Url server: String?, @Part file: MultipartBody.Part): Single + fun uploadAudioRx(server: String, file: MultipartBody.Part): Single { + return rest.doMultipartFormFullUrl(server, file, UploadAudioDto.serializer()) + } - @Multipart - @POST fun remotePlayAudioRx( - @Url server: String?, - @Part file: MultipartBody.Part - ): Single> + server: String, + file: MultipartBody.Part + ): Single> { + return rest.doMultipartFormFullUrl(server, file, baseInt) + } - @Multipart - @POST fun uploadStoryRx( - @Url server: String?, - @Part file: MultipartBody.Part - ): Single> + server: String, + file: MultipartBody.Part + ): Single> { + return rest.doMultipartFormFullUrl(server, file, base(UploadStoryDto.serializer())) + } - @Multipart - @POST - fun uploadVideoRx(@Url server: String?, @Part file: MultipartBody.Part): Single + fun uploadVideoRx(server: String, file: MultipartBody.Part): Single { + return rest.doMultipartFormFullUrl(server, file, UploadVideoDto.serializer()) + } - @Multipart - @POST fun uploadOwnerPhotoRx( - @Url server: String?, - @Part photo: MultipartBody.Part - ): Single + server: String, + photo: MultipartBody.Part + ): Single { + return rest.doMultipartFormFullUrl(server, photo, UploadOwnerPhotoDto.serializer()) + } - @Multipart - @POST fun uploadChatPhotoRx( - @Url server: String?, - @Part photo: MultipartBody.Part - ): Single + server: String, + photo: MultipartBody.Part + ): Single { + return rest.doMultipartFormFullUrl(server, photo, UploadChatPhotoDto.serializer()) + } - @Multipart - @POST fun uploadPhotoToWallRx( - @Url server: String?, - @Part photo: MultipartBody.Part - ): Single + server: String, + photo: MultipartBody.Part + ): Single { + return rest.doMultipartFormFullUrl(server, photo, UploadPhotoToWallDto.serializer()) + } - @Multipart - @POST fun uploadPhotoToMessageRx( - @Url server: String?, - @Part photo: MultipartBody.Part - ): Single + server: String, + photo: MultipartBody.Part + ): Single { + return rest.doMultipartFormFullUrl(server, photo, UploadPhotoToMessageDto.serializer()) + } - @Multipart - @POST fun uploadPhotoToAlbumRx( - @Url server: String?, - @Part file1: MultipartBody.Part - ): Single + server: String, + file1: MultipartBody.Part + ): Single { + return rest.doMultipartFormFullUrl(server, file1, UploadPhotoToAlbumDto.serializer()) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUsersService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUsersService.kt index 17cd06f57..b92c7d15e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUsersService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUsersService.kt @@ -6,80 +6,137 @@ import dev.ragnarok.fenrir.api.model.response.StoryGetResponse import dev.ragnarok.fenrir.api.model.response.StoryResponse import dev.ragnarok.fenrir.api.model.response.UserWallInfoResponse import dev.ragnarok.fenrir.api.model.server.VKApiStoryUploadServer +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IUsersService { - @FormUrlEncoded - @POST("execute") +class IUsersService : IServiceRest() { fun getUserWallInfo( - @Field("code") code: String?, - @Field("user_id") userId: Int, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String? - ): Single> + code: String?, + userId: Int, + fields: String?, + nameCase: String? + ): Single> { + return rest.request( + "execute", form( + "code" to code, + "user_id" to userId, + "fields" to fields, + "name_case" to nameCase + ), base(UserWallInfoResponse.serializer()) + ) + } //https://vk.com/dev/users.getFollowers - @FormUrlEncoded - @POST("users.getFollowers") fun getFollowers( - @Field("user_id") userId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String? - ): Single>> - - @FormUrlEncoded - @POST("friends.getRequests") + userId: Int?, + offset: Int?, + count: Int?, + fields: String?, + nameCase: String? + ): Single>> { + return rest.request( + "users.getFollowers", form( + "user_id" to userId, + "offset" to offset, + "count" to count, + "fields" to fields, + "name_case" to nameCase + ), items(VKApiUser.serializer()) + ) + } + fun getRequests( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("extended") extended: Int?, - @Field("out") out: Int?, - @Field("fields") fields: String? - ): Single>> + offset: Int?, + count: Int?, + extended: Int?, + out: Int?, + fields: String? + ): Single>> { + return rest.request( + "friends.getRequests", form( + "offset" to offset, + "count" to count, + "extended" to extended, + "out" to out, + "fields" to fields + ), items(VKApiUser.serializer()) + ) + } //https://vk.com/dev/users.search - @FormUrlEncoded - @POST("users.search") fun search( - @Field("q") query: String?, - @Field("sort") sort: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("fields") fields: String?, - @Field("city") city: Int?, - @Field("country") country: Int?, - @Field("hometown") hometown: String?, - @Field("university_country") universityCountry: Int?, - @Field("university") university: Int?, - @Field("university_year") universityYear: Int?, - @Field("university_faculty") universityFaculty: Int?, - @Field("university_chair") universityChair: Int?, - @Field("sex") sex: Int?, - @Field("status") status: Int?, - @Field("age_from") ageFrom: Int?, - @Field("age_to") ageTo: Int?, - @Field("birth_day") birthDay: Int?, - @Field("birth_month") birthMonth: Int?, - @Field("birth_year") birthYear: Int?, - @Field("online") online: Int?, - @Field("has_photo") hasPhoto: Int?, - @Field("school_country") schoolCountry: Int?, - @Field("school_city") schoolCity: Int?, - @Field("school_class") schoolClass: Int?, - @Field("school") school: Int?, - @Field("school_year") schoolYear: Int?, - @Field("religion") religion: String?, - @Field("interests") interests: String?, - @Field("company") company: String?, - @Field("position") position: String?, - @Field("group_id") groupId: Int?, - @Field("from_list") fromList: String? - ): Single>> + query: String?, + sort: Int?, + offset: Int?, + count: Int?, + fields: String?, + city: Int?, + country: Int?, + hometown: String?, + universityCountry: Int?, + university: Int?, + universityYear: Int?, + universityFaculty: Int?, + universityChair: Int?, + sex: Int?, + status: Int?, + ageFrom: Int?, + ageTo: Int?, + birthDay: Int?, + birthMonth: Int?, + birthYear: Int?, + online: Int?, + hasPhoto: Int?, + schoolCountry: Int?, + schoolCity: Int?, + schoolClass: Int?, + school: Int?, + schoolYear: Int?, + religion: String?, + interests: String?, + company: String?, + position: String?, + groupId: Int?, + fromList: String? + ): Single>> { + return rest.request( + "users.search", form( + "q" to query, + "sort" to sort, + "offset" to offset, + "count" to count, + "fields" to fields, + "city" to city, + "country" to country, + "hometown" to hometown, + "university_country" to universityCountry, + "university" to university, + "university_year" to universityYear, + "university_faculty" to universityFaculty, + "university_chair" to universityChair, + "sex" to sex, + "status" to status, + "age_from" to ageFrom, + "age_to" to ageTo, + "birth_day" to birthDay, + "birth_month" to birthMonth, + "birth_year" to birthYear, + "online" to online, + "has_photo" to hasPhoto, + "school_country" to schoolCountry, + "school_city" to schoolCity, + "school_class" to schoolClass, + "school" to school, + "school_year" to schoolYear, + "religion" to religion, + "interests" to interests, + "company" to company, + "position" to position, + "group_id" to groupId, + "from_list" to fromList + ), items(VKApiUser.serializer()) + ) + } /** * Returns detailed information on users. @@ -97,80 +154,141 @@ interface IUsersService { * @return Returns a list of user objects. * A deactivated field may be returned with the value deleted or banned if a user has been suspended. */ - @FormUrlEncoded - @POST("users.get") operator fun get( - @Field("user_ids") userIds: String?, - @Field("fields") fields: String?, - @Field("name_case") nameCase: String? - ): Single>> + userIds: String?, + fields: String?, + nameCase: String? + ): Single>> { + return rest.request( + "users.get", form( + "user_ids" to userIds, + "fields" to fields, + "name_case" to nameCase + ), baseList(VKApiUser.serializer()) + ) + } - @FormUrlEncoded - @POST("stories.getPhotoUploadServer") - fun stories_getPhotoUploadServer(@Field("add_to_news") add_to_news: Int?): Single> + fun stories_getPhotoUploadServer(add_to_news: Int?): Single> { + return rest.request( + "stories.getPhotoUploadServer", + form("add_to_news" to add_to_news), + base(VKApiStoryUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("stories.getVideoUploadServer") - fun stories_getVideoUploadServer(@Field("add_to_news") add_to_news: Int?): Single> + fun stories_getVideoUploadServer(add_to_news: Int?): Single> { + return rest.request( + "stories.getVideoUploadServer", + form("add_to_news" to add_to_news), + base(VKApiStoryUploadServer.serializer()) + ) + } - @FormUrlEncoded - @POST("stories.save") - fun stories_save(@Field("upload_results") upload_results: String?): Single>> + fun stories_save(upload_results: String?): Single>> { + return rest.request( + "stories.save", + form("upload_results" to upload_results), + items(VKApiStory.serializer()) + ) + } - @POST("users.report") - @FormUrlEncoded fun report( - @Field("user_id") userId: Int?, - @Field("type") type: String?, - @Field("comment") comment: String? - ): Single> + userId: Int?, + type: String?, + comment: String? + ): Single> { + return rest.request( + "users.report", form( + "user_id" to userId, + "type" to type, + "comment" to comment + ), baseInt + ) + } - @POST("stories.get") - @FormUrlEncoded fun getStory( - @Field("owner_id") owner_id: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + owner_id: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "stories.get", form( + "owner_id" to owner_id, + "extended" to extended, + "fields" to fields + ), base(StoryResponse.serializer()) + ) + } - @POST("narratives.getFromOwner") - @FormUrlEncoded fun getNarratives( - @Field("owner_id") owner_id: Int, - @Field("offset") offset: Int?, - @Field("count") count: Int? - ): Single>> + owner_id: Int, + offset: Int?, + count: Int? + ): Single>> { + return rest.request( + "narratives.getFromOwner", form( + "owner_id" to owner_id, + "offset" to offset, + "count" to count + ), items(VKApiNarratives.serializer()) + ) + } - @POST("stories.getById") - @FormUrlEncoded fun getStoryById( - @Field("stories") stories: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + stories: String?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "stories.getById", form( + "stories" to stories, + "extended" to extended, + "fields" to fields + ), base(StoryGetResponse.serializer()) + ) + } - @POST("gifts.get") - @FormUrlEncoded fun getGifts( - @Field("user_id") user_id: Int?, - @Field("count") count: Int?, - @Field("offset") offset: Int? - ): Single>> + user_id: Int?, + count: Int?, + offset: Int? + ): Single>> { + return rest.request( + "gifts.get", form( + "user_id" to user_id, + "count" to count, + "offset" to offset + ), items(VKApiGift.serializer()) + ) + } - @POST("stories.search") - @FormUrlEncoded fun searchStory( - @Field("q") q: String?, - @Field("mentioned_id") mentioned_id: Int?, - @Field("count") count: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> - - @FormUrlEncoded - @POST("execute") + q: String?, + mentioned_id: Int?, + count: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "stories.search", form( + "q" to q, + "mentioned_id" to mentioned_id, + "count" to count, + "extended" to extended, + "fields" to fields + ), base(StoryResponse.serializer()) + ) + } + fun checkAndAddFriend( - @Field("code") code: String?, - @Field("user_id") user_id: Int? - ): Single> + code: String?, + user_id: Int? + ): Single> { + return rest.request( + "execute", form( + "code" to code, + "user_id" to user_id + ), baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt index 32eb5bc3d..0f9badb9d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt @@ -7,55 +7,74 @@ import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.ResolveDomailResponse import dev.ragnarok.fenrir.api.model.response.VKApiChatResponse import dev.ragnarok.fenrir.api.model.response.VKApiLinkResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.GET -import retrofit2.http.POST - -interface IUtilsService { - @FormUrlEncoded - @POST("utils.resolveScreenName") - fun resolveScreenName(@Field("screen_name") screenName: String?): Single> - - @FormUrlEncoded - @POST("utils.getShortLink") - fun getShortLink( - @Field("url") url: String?, - @Field("private") t_private: Int? - ): Single> - - @FormUrlEncoded - @POST("utils.getLastShortenedLinks") + +class IUtilsService : IServiceRest() { + fun resolveScreenName(screenName: String?): Single> { + return rest.request( + "utils.resolveScreenName", + form("screen_name" to screenName), + base(ResolveDomailResponse.serializer()) + ) + } + + fun getShortLink(url: String?, t_private: Int?): Single> { + return rest.request( + "utils.getShortLink", + form("url" to url, "private" to t_private), + base(VKApiShortLink.serializer()) + ) + } + fun getLastShortenedLinks( - @Field("count") count: Int?, - @Field("offset") offset: Int? - ): Single>> + count: Int?, + offset: Int? + ): Single>> { + return rest.request( + "utils.getLastShortenedLinks", form("count" to count, "offset" to offset), + items(VKApiShortLink.serializer()) + ) + } - @FormUrlEncoded - @POST("utils.deleteFromLastShortened") - fun deleteFromLastShortened(@Field("key") key: String?): Single> + fun deleteFromLastShortened(key: String?): Single> { + return rest.request( + "utils.deleteFromLastShortened", form("key" to key), + baseInt + ) + } - @FormUrlEncoded - @POST("utils.checkLink") - fun checkLink(@Field("url") url: String?): Single> + fun checkLink(url: String?): Single> { + return rest.request( + "utils.checkLink", form("url" to url), + base(VKApiCheckedLink.serializer()) + ) + } - @FormUrlEncoded - @POST("messages.joinChatByInviteLink") - fun joinChatByInviteLink(@Field("link") link: String?): Single> + fun joinChatByInviteLink(link: String?): Single> { + return rest.request( + "messages.joinChatByInviteLink", + form("link" to link), + base(VKApiChatResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("messages.getInviteLink") fun getInviteLink( - @Field("peer_id") peer_id: Int?, - @Field("reset") reset: Int? - ): Single> + peer_id: Int?, + reset: Int? + ): Single> { + return rest.request( + "messages.getInviteLink", + form("peer_id" to peer_id, "reset" to reset), + base(VKApiLinkResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("execute") - fun customScript(@Field("code") code: String?): Single> + fun customScript(code: String?): Single> { + return rest.request("execute", form("code" to code), baseInt) + } - @FormUrlEncoded - @GET("utils.getServerTime") - fun getServerTime(): Single> + fun getServerTime(): Single> { + return rest.request("utils.getServerTime", null, baseLong) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IVideoService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IVideoService.kt index e061bae13..6f4cde8be 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IVideoService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IVideoService.kt @@ -6,130 +6,225 @@ import dev.ragnarok.fenrir.api.model.VKApiVideoAlbum import dev.ragnarok.fenrir.api.model.response.BaseResponse import dev.ragnarok.fenrir.api.model.response.DefaultCommentsResponse import dev.ragnarok.fenrir.api.model.response.SearchVideoResponse +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IVideoService { - @FormUrlEncoded - @POST("video.getComments") +class IVideoService : IServiceRest() { fun getComments( - @Field("owner_id") ownerId: Int?, - @Field("video_id") videoId: Int, - @Field("need_likes") needLikes: Int?, - @Field("start_comment_id") startCommentId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("sort") sort: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + ownerId: Int?, + videoId: Int, + needLikes: Int?, + startCommentId: Int?, + offset: Int?, + count: Int?, + sort: String?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "video.getComments", form( + "owner_id" to ownerId, + "video_id" to videoId, + "need_likes" to needLikes, + "start_comment_id" to startCommentId, + "offset" to offset, + "count" to count, + "sort" to sort, + "extended" to extended, + "fields" to fields + ), base(DefaultCommentsResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("video.add") fun addVideo( - @Field("target_id") targetId: Int?, - @Field("video_id") videoId: Int?, - @Field("owner_id") ownerId: Int? - ): Single> + targetId: Int?, + videoId: Int?, + ownerId: Int? + ): Single> { + return rest.request( + "video.add", form( + "target_id" to targetId, + "video_id" to videoId, + "owner_id" to ownerId + ), baseInt + ) + } - @FormUrlEncoded - @POST("video.delete") fun deleteVideo( - @Field("video_id") videoId: Int?, - @Field("owner_id") ownerId: Int?, - @Field("target_id") targetId: Int? - ): Single> + videoId: Int?, + ownerId: Int?, + targetId: Int? + ): Single> { + return rest.request( + "video.delete", form( + "video_id" to videoId, + "owner_id" to ownerId, + "target_id" to targetId + ), baseInt + ) + } - @FormUrlEncoded - @POST("video.getAlbums") fun getAlbums( - @Field("owner_id") ownerId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("extended") extended: Int?, - @Field("need_system") needSystem: Int? - ): Single>> + ownerId: Int?, + offset: Int?, + count: Int?, + extended: Int?, + needSystem: Int? + ): Single>> { + return rest.request( + "video.getAlbums", form( + "owner_id" to ownerId, + "offset" to offset, + "count" to count, + "extended" to extended, + "need_system" to needSystem + ), items(VKApiVideoAlbum.serializer()) + ) + } - @FormUrlEncoded - @POST("video.getAlbumsByVideo") fun getAlbumsByVideo( - @Field("target_id") target_id: Int?, - @Field("owner_id") owner_id: Int?, - @Field("video_id") video_id: Int?, - @Field("extended") extended: Int? - ): Single>> + target_id: Int?, + owner_id: Int?, + video_id: Int?, + extended: Int? + ): Single>> { + return rest.request( + "video.getAlbumsByVideo", form( + "target_id" to target_id, + "owner_id" to owner_id, + "video_id" to video_id, + "extended" to extended + ), items(VKApiVideoAlbum.serializer()) + ) + } - @FormUrlEncoded - @POST("video.search") fun search( - @Field("q") query: String?, - @Field("sort") sort: Int?, - @Field("hd") hd: Int?, - @Field("adult") adult: Int?, - @Field("filters") filters: String?, - @Field("search_own") searchOwn: Int?, - @Field("offset") offset: Int?, - @Field("longer") longer: Int?, - @Field("shorter") shorter: Int?, - @Field("count") count: Int?, - @Field("extended") extended: Int? - ): Single> + query: String?, + sort: Int?, + hd: Int?, + adult: Int?, + filters: String?, + searchOwn: Int?, + offset: Int?, + longer: Int?, + shorter: Int?, + count: Int?, + extended: Int? + ): Single> { + return rest.request( + "video.search", form( + "q" to query, + "sort" to sort, + "hd" to hd, + "adult" to adult, + "filters" to filters, + "search_own" to searchOwn, + "offset" to offset, + "longer" to longer, + "shorter" to shorter, + "count" to count, + "extended" to extended + ), base(SearchVideoResponse.serializer()) + ) + } - @FormUrlEncoded - @POST("video.restoreComment") fun restoreComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int - ): Single> + ownerId: Int?, + commentId: Int + ): Single> { + return rest.request( + "video.restoreComment", form( + "owner_id" to ownerId, + "comment_id" to commentId + ), baseInt + ) + } - @FormUrlEncoded - @POST("video.deleteComment") fun deleteComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int - ): Single> + ownerId: Int?, + commentId: Int + ): Single> { + return rest.request( + "video.deleteComment", form( + "owner_id" to ownerId, + "comment_id" to commentId + ), baseInt + ) + } - @FormUrlEncoded - @POST("video.get") operator fun get( - @Field("owner_id") ownerId: Int?, - @Field("videos") videos: String?, - @Field("album_id") albumId: Int?, - @Field("count") count: Int?, - @Field("offset") offset: Int?, - @Field("extended") extended: Int? - ): Single>> + ownerId: Int?, + videos: String?, + albumId: Int?, + count: Int?, + offset: Int?, + extended: Int? + ): Single>> { + return rest.request( + "video.get", form( + "owner_id" to ownerId, + "videos" to videos, + "album_id" to albumId, + "count" to count, + "offset" to offset, + "extended" to extended + ), items(VKApiVideo.serializer()) + ) + } - @FormUrlEncoded - @POST("video.createComment") fun createComment( - @Field("owner_id") ownerId: Int?, - @Field("video_id") videoId: Int, - @Field("message") message: String?, - @Field("attachments") attachments: String?, - @Field("from_group") fromGroup: Int?, - @Field("reply_to_comment") replyToComment: Int?, - @Field("sticker_id") stickerId: Int?, - @Field("guid") uniqueGeneratedId: Int? - ): Single> + ownerId: Int?, + videoId: Int, + message: String?, + attachments: String?, + fromGroup: Int?, + replyToComment: Int?, + stickerId: Int?, + uniqueGeneratedId: Int? + ): Single> { + return rest.request( + "video.createComment", form( + "owner_id" to ownerId, + "video_id" to videoId, + "message" to message, + "attachments" to attachments, + "from_group" to fromGroup, + "reply_to_comment" to replyToComment, + "sticker_id" to stickerId, + "guid" to uniqueGeneratedId + ), baseInt + ) + } - @FormUrlEncoded - @POST("video.editComment") fun editComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int, - @Field("message") message: String?, - @Field("attachments") attachments: String? - ): Single> + ownerId: Int?, + commentId: Int, + message: String?, + attachments: String? + ): Single> { + return rest.request( + "video.editComment", form( + "owner_id" to ownerId, + "comment_id" to commentId, + "message" to message, + "attachments" to attachments + ), baseInt + ) + } - @FormUrlEncoded - @POST("video.edit") fun edit( - @Field("owner_id") ownerId: Int?, - @Field("video_id") video_id: Int, - @Field("name") name: String?, - @Field("desc") desc: String? - ): Single> + ownerId: Int?, + video_id: Int, + name: String?, + desc: String? + ): Single> { + return rest.request( + "video.edit", form( + "owner_id" to ownerId, + "video_id" to video_id, + "name" to name, + "desc" to desc + ), baseInt + ) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IWallService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IWallService.kt index edb474a2a..6447c830e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IWallService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IWallService.kt @@ -1,90 +1,149 @@ package dev.ragnarok.fenrir.api.services import dev.ragnarok.fenrir.api.model.response.* +import dev.ragnarok.fenrir.api.rest.IServiceRest import io.reactivex.rxjava3.core.Single -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.POST -interface IWallService { +class IWallService : IServiceRest() { //https://vk.com/dev/wall.search - @FormUrlEncoded - @POST("wall.search") fun search( - @Field("owner_id") ownerId: Int?, - @Field("domain") domain: String?, - @Field("query") query: String?, - @Field("owners_only") ownersOnly: Int?, - @Field("count") count: Int?, - @Field("offset") offset: Int?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> + ownerId: Int?, + domain: String?, + query: String?, + ownersOnly: Int?, + count: Int?, + offset: Int?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "wall.search", form( + "owner_id" to ownerId, + "domain" to domain, + "query" to query, + "owners_only" to ownersOnly, + "count" to count, + "offset" to offset, + "extended" to extended, + "fields" to fields + ), base(WallSearchResponse.serializer()) + ) + } //https://vk.com/dev/wall.edit - @FormUrlEncoded - @POST("wall.edit") fun edit( - @Field("owner_id") ownerId: Int?, - @Field("post_id") postId: Int?, - @Field("friends_only") friendsOnly: Int?, - @Field("message") message: String?, - @Field("attachments") attachments: String?, - @Field("services") services: String?, - @Field("signed") signed: Int?, - @Field("publish_date") publishDate: Long?, - @Field("lat") latitude: Double?, - @Field("long") longitude: Double?, - @Field("place_id") placeId: Int?, - @Field("mark_as_ads") markAsAds: Int? - ): Single> + ownerId: Int?, + postId: Int?, + friendsOnly: Int?, + message: String?, + attachments: String?, + services: String?, + signed: Int?, + publishDate: Long?, + latitude: Double?, + longitude: Double?, + placeId: Int?, + markAsAds: Int? + ): Single> { + return rest.request( + "wall.edit", form( + "owner_id" to ownerId, + "post_id" to postId, + "friends_only" to friendsOnly, + "message" to message, + "attachments" to attachments, + "services" to services, + "signed" to signed, + "publish_date" to publishDate, + "lat" to latitude, + "long" to longitude, + "place_id" to placeId, + "mark_as_ads" to markAsAds + ), base(WallEditResponse.serializer()) + ) + } //https://vk.com/dev/wall.pin - @FormUrlEncoded - @POST("wall.pin") fun pin( - @Field("owner_id") ownerId: Int?, - @Field("post_id") postId: Int - ): Single> + ownerId: Int?, + postId: Int + ): Single> { + return rest.request( + "wall.pin", form( + "owner_id" to ownerId, + "post_id" to postId + ), baseInt + ) + } //https://vk.com/dev/wall.unpin - @FormUrlEncoded - @POST("wall.unpin") fun unpin( - @Field("owner_id") ownerId: Int?, - @Field("post_id") postId: Int - ): Single> + ownerId: Int?, + postId: Int + ): Single> { + return rest.request( + "wall.unpin", form( + "owner_id" to ownerId, + "post_id" to postId + ), baseInt + ) + } //https://vk.com/dev/wall.repost - @FormUrlEncoded - @POST("wall.repost") fun repost( - @Field("object") `object`: String?, - @Field("message") message: String?, - @Field("group_id") groupId: Int?, - @Field("mark_as_ads") markAsAds: Int? - ): Single> + pobject: String?, + message: String?, + groupId: Int?, + markAsAds: Int? + ): Single> { + return rest.request( + "wall.repost", form( + "object" to pobject, + "message" to message, + "group_id" to groupId, + "mark_as_ads" to markAsAds + ), base(RepostReponse.serializer()) + ) + } //https://vk.com/dev/wall.post - @FormUrlEncoded - @POST("wall.post") fun post( - @Field("owner_id") ownerId: Int?, - @Field("friends_only") friendsOnly: Int?, - @Field("from_group") fromGroup: Int?, - @Field("message") message: String?, - @Field("attachments") attachments: String?, - @Field("services") services: String?, - @Field("signed") signed: Int?, - @Field("publish_date") publishDate: Long?, - @Field("lat") latitude: Double?, - @Field("long") longitude: Double?, - @Field("place_id") placeId: Int?, - @Field("post_id") postId: Int?, - @Field("guid") guid: Int?, - @Field("mark_as_ads") markAsAds: Int?, - @Field("ads_promoted_stealth") adsPromotedStealth: Int? - ): Single> + ownerId: Int?, + friendsOnly: Int?, + fromGroup: Int?, + message: String?, + attachments: String?, + services: String?, + signed: Int?, + publishDate: Long?, + latitude: Double?, + longitude: Double?, + placeId: Int?, + postId: Int?, + guid: Int?, + markAsAds: Int?, + adsPromotedStealth: Int? + ): Single> { + return rest.request( + "wall.post", form( + "owner_id" to ownerId, + "friends_only" to friendsOnly, + "from_group" to fromGroup, + "message" to message, + "attachments" to attachments, + "services" to services, + "signed" to signed, + "publish_date" to publishDate, + "lat" to latitude, + "long" to longitude, + "place_id" to placeId, + "post_id" to postId, + "guid" to guid, + "mark_as_ads" to markAsAds, + "ads_promoted_stealth" to adsPromotedStealth + ), base(PostCreateResponse.serializer()) + ) + } /** * Deletes a post from a user wall or community wall. @@ -94,12 +153,17 @@ interface IWallService { * @param postId ID of the post to be deleted * @return 1 */ - @FormUrlEncoded - @POST("wall.delete") fun delete( - @Field("owner_id") ownerId: Int?, - @Field("post_id") postId: Int - ): Single> + ownerId: Int?, + postId: Int + ): Single> { + return rest.request( + "wall.delete", form( + "owner_id" to ownerId, + "post_id" to postId + ), baseInt + ) + } /** * Restores a comment deleted from a user wall or community wall. @@ -109,12 +173,17 @@ interface IWallService { * @param commentId Comment ID. * @return 1 */ - @FormUrlEncoded - @POST("wall.restoreComment") fun restoreComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int - ): Single> + ownerId: Int?, + commentId: Int + ): Single> { + return rest.request( + "wall.restoreComment", form( + "owner_id" to ownerId, + "comment_id" to commentId + ), baseInt + ) + } /** * Deletes a comment on a post on a user wall or community wall. @@ -124,12 +193,17 @@ interface IWallService { * @param commentId Comment ID. * @return 1 */ - @FormUrlEncoded - @POST("wall.deleteComment") fun deleteComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int - ): Single> + ownerId: Int?, + commentId: Int + ): Single> { + return rest.request( + "wall.deleteComment", form( + "owner_id" to ownerId, + "comment_id" to commentId + ), baseInt + ) + } /** * Restores a post deleted from a user wall or community wall. @@ -140,12 +214,17 @@ interface IWallService { * @param postId ID of the post to be restored. * @return 1 */ - @FormUrlEncoded - @POST("wall.restore") fun restore( - @Field("owner_id") ownerId: Int?, - @Field("post_id") postId: Int - ): Single> + ownerId: Int?, + postId: Int + ): Single> { + return rest.request( + "wall.restore", form( + "owner_id" to ownerId, + "post_id" to postId + ), baseInt + ) + } /** * Edits a comment on a user wall or community wall. @@ -168,86 +247,146 @@ interface IWallService { * List of comma-separated words * @return 1 */ - @FormUrlEncoded - @POST("wall.editComment") fun editComment( - @Field("owner_id") ownerId: Int?, - @Field("comment_id") commentId: Int, - @Field("message") message: String?, - @Field("attachments") attachments: String? - ): Single> - - @FormUrlEncoded - @POST("wall.createComment") + ownerId: Int?, + commentId: Int, + message: String?, + attachments: String? + ): Single> { + return rest.request( + "wall.editComment", form( + "owner_id" to ownerId, + "comment_id" to commentId, + "message" to message, + "attachments" to attachments + ), baseInt + ) + } + fun createComment( - @Field("owner_id") ownerId: Int?, - @Field("post_id") postId: Int, - @Field("from_group") fromGroup: Int?, - @Field("message") message: String?, - @Field("reply_to_comment") replyToComment: Int?, - @Field("attachments") attachments: String?, - @Field("sticker_id") stickerId: Int?, - @Field("guid") generatedUniqueId: Int? - ): Single> + ownerId: Int?, + postId: Int, + fromGroup: Int?, + message: String?, + replyToComment: Int?, + attachments: String?, + stickerId: Int?, + generatedUniqueId: Int? + ): Single> { + return rest.request( + "wall.createComment", form( + "owner_id" to ownerId, + "post_id" to postId, + "from_group" to fromGroup, + "message" to message, + "reply_to_comment" to replyToComment, + "attachments" to attachments, + "sticker_id" to stickerId, + "guid" to generatedUniqueId + ), base(CommentCreateResponse.serializer()) + ) + } //https://vk.com/dev/wall.getComments - @FormUrlEncoded - @POST("wall.getComments") fun getComments( - @Field("owner_id") ownerId: Int?, - @Field("post_id") postId: Int, - @Field("need_likes") needLikes: Int?, - @Field("start_comment_id") startCommentId: Int?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("sort") sort: String?, - @Field("extended") extended: Int?, - @Field("thread_items_count") thread_items_count: Int?, - @Field("fields") fields: String? - ): Single> - - @FormUrlEncoded - @POST("wall.get") + ownerId: Int?, + postId: Int, + needLikes: Int?, + startCommentId: Int?, + offset: Int?, + count: Int?, + sort: String?, + extended: Int?, + thread_items_count: Int?, + fields: String? + ): Single> { + return rest.request( + "wall.getComments", form( + "owner_id" to ownerId, + "post_id" to postId, + "need_likes" to needLikes, + "start_comment_id" to startCommentId, + "offset" to offset, + "count" to count, + "sort" to sort, + "extended" to extended, + "thread_items_count" to thread_items_count, + "fields" to fields + ), base(DefaultCommentsResponse.serializer()) + ) + } + operator fun get( - @Field("owner_id") ownerId: Int?, - @Field("domain") domain: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("filter") filter: String?, - @Field("extended") extended: Int?, - @Field("fields") fields: String? - ): Single> - - @FormUrlEncoded - @POST("wall.getById") + ownerId: Int?, + domain: String?, + offset: Int?, + count: Int?, + filter: String?, + extended: Int?, + fields: String? + ): Single> { + return rest.request( + "wall.get", form( + "owner_id" to ownerId, + "domain" to domain, + "offset" to offset, + "count" to count, + "filter" to filter, + "extended" to extended, + "fields" to fields + ), base(WallResponse.serializer()) + ) + } + fun getById( - @Field("posts") ids: String?, - @Field("extended") extended: Int?, - @Field("copy_history_depth") copyHistoryDepth: Int?, - @Field("fields") fields: String? - ): Single> - - @POST("wall.reportComment") - @FormUrlEncoded + ids: String?, + extended: Int?, + copyHistoryDepth: Int?, + fields: String? + ): Single> { + return rest.request( + "wall.getById", form( + "posts" to ids, + "extended" to extended, + "copy_history_depth" to copyHistoryDepth, + "fields" to fields + ), base(PostsResponse.serializer()) + ) + } + fun reportComment( - @Field("owner_id") owner_id: Int?, - @Field("comment_id") comment_id: Int?, - @Field("reason") reason: Int? - ): Single> + owner_id: Int?, + comment_id: Int?, + reason: Int? + ): Single> { + return rest.request( + "wall.reportComment", form( + "owner_id" to owner_id, + "comment_id" to comment_id, + "reason" to reason + ), baseInt + ) + } - @POST("wall.reportPost") - @FormUrlEncoded fun reportPost( - @Field("owner_id") owner_id: Int?, - @Field("post_id") post_id: Int?, - @Field("reason") reason: Int? - ): Single> - - @POST("wall.subscribe") - @FormUrlEncoded - fun subscribe(@Field("owner_id") owner_id: Int?): Single> - - @POST("wall.unsubscribe") - @FormUrlEncoded - fun unsubscribe(@Field("owner_id") owner_id: Int?): Single> + owner_id: Int?, + post_id: Int?, + reason: Int? + ): Single> { + return rest.request( + "wall.reportPost", form( + "owner_id" to owner_id, + "post_id" to post_id, + "reason" to reason + ), baseInt + ) + } + + fun subscribe(owner_id: Int?): Single> { + return rest.request("wall.subscribe", form("owner_id" to owner_id), baseInt) + } + + fun unsubscribe(owner_id: Int?): Single> { + return rest.request("wall.unsubscribe", form("owner_id" to owner_id), baseInt) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/AttachmentsStorage.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/AttachmentsStorage.kt index 32022f24b..c330f534e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/AttachmentsStorage.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/AttachmentsStorage.kt @@ -128,7 +128,7 @@ internal class AttachmentsStorage(base: AppStorages) : AbsStorage(base), IAttach if (count > 0) { e.onComplete() } else { - e.onError(NotFoundException()) + e.tryOnError(NotFoundException()) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/CommentsStorage.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/CommentsStorage.kt index 02ebe976c..4fccc94e2 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/CommentsStorage.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/CommentsStorage.kt @@ -226,7 +226,7 @@ internal class CommentsStorage(base: AppStorages) : AbsStorage(base), ICommentsS if (id == null) { val uri = contentResolver.insert(commentsWithAccountUri, contentValues) if (uri == null) { - e.onError(DatabaseException("Result URI is null")) + e.tryOnError(DatabaseException("Result URI is null")) return@create } id = uri.pathSegments[1].toInt() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/ContactsUtils.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/ContactsUtils.kt index 10fc84ccd..ac780a8ec 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/ContactsUtils.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/ContactsUtils.kt @@ -122,7 +122,7 @@ object ContactsUtils { null ) if (cursor == null) { - it.onError(Throwable("Can't collect contact list!")) + it.tryOnError(Throwable("Can't collect contact list!")) return@create } if (cursor.count > 0) { @@ -137,7 +137,7 @@ object ContactsUtils { cursor.close() } if (contacts.isEmpty()) { - it.onError(Throwable("Can't collect contact list!")) + it.tryOnError(Throwable("Can't collect contact list!")) } it.onSuccess( kJson.encodeToString(ListSerializer(ContactData.serializer()), contacts) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/KeysPersistStorage.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/KeysPersistStorage.kt index d319dd6f6..e9a76bdc6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/KeysPersistStorage.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/KeysPersistStorage.kt @@ -34,7 +34,7 @@ internal class KeysPersistStorage(context: AppStorages) : AbsStorage(context), I val alreaadyExist = findKeyPairFor(pair.accountId, pair.sessionId) .blockingGet() if (alreaadyExist != null) { - e.onError(DatabaseException("Key pair with the session ID is already in the database")) + e.tryOnError(DatabaseException("Key pair with the session ID is already in the database")) return@create } val cv = ContentValues() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/MessagesStorage.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/MessagesStorage.kt index b727d6ae1..b4af949c5 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/MessagesStorage.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/MessagesStorage.kt @@ -504,7 +504,7 @@ internal class MessagesStorage(base: AppStorages) : AbsStorage(base), IMessagesS if (count > 0) { e.onComplete() } else { - e.onError(NotFoundException()) + e.tryOnError(NotFoundException()) } } } @@ -550,7 +550,7 @@ internal class MessagesStorage(base: AppStorages) : AbsStorage(base), IMessagesS if (count > 0) { e.onComplete() } else { - e.onError(NotFoundException()) + e.tryOnError(NotFoundException()) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicatePresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicatePresenter.kt index ed5267a09..9c4356a8e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicatePresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicatePresenter.kt @@ -60,10 +60,10 @@ class AudioDuplicatePresenter( if (bitrate != null) { v.onSuccess((bitrate.toLong() / 1000).toInt()) } else { - v.onError(Throwable("Can't receipt bitrate ")) + v.tryOnError(Throwable("Can't receipt bitrate ")) } } catch (e: RuntimeException) { - v.onError(e) + v.tryOnError(e) } } } @@ -122,13 +122,13 @@ class AudioDuplicatePresenter( if (bitrate != null) { v.onSuccess((bitrate.toLong() / 1000).toInt()) } else { - v.onError(Throwable("Can't receipt bitrate ")) + v.tryOnError(Throwable("Can't receipt bitrate ")) } } else { - v.onError(Throwable("Can't receipt bitrate ")) + v.tryOnError(Throwable("Can't receipt bitrate ")) } } catch (e: RuntimeException) { - v.onError(e) + v.tryOnError(e) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/MessagesRepository.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/MessagesRepository.kt index 42f751af5..2916e2f55 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/MessagesRepository.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/MessagesRepository.kt @@ -534,7 +534,7 @@ class MessagesRepository( val resp = b?.let { kJson.decodeFromStream(ChatJsonResponse.serializer(), it) } b?.close() if (resp == null || resp.page_title.isNullOrEmpty()) { - its.onError(Throwable("parsing error")) + its.tryOnError(Throwable("parsing error")) return@create } val ids = VKOwnIds().append(resp.messages) @@ -1577,7 +1577,12 @@ class MessagesRepository( try { `is`[0] = FileInputStream(file) return@flatMap networker.uploads() - .uploadDocumentRx(server.url, file.name, `is`[0]!!, null) + .uploadDocumentRx( + server.url ?: throw NotFoundException("upload url empty"), + file.name, + `is`[0]!!, + null + ) .doFinally(safelyCloseAction(`is`[0])) .flatMap { uploadDto -> docsApi diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt index eff4674a4..398d7160c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt @@ -40,6 +40,7 @@ import dev.ragnarok.fenrir.api.adapters.AbsAdapter.Companion.asPrimitiveSafe import dev.ragnarok.fenrir.api.adapters.AbsAdapter.Companion.has import dev.ragnarok.fenrir.api.model.VKApiUser import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.rest.HttpException import dev.ragnarok.fenrir.db.DBHelper import dev.ragnarok.fenrir.dialog.directauth.DirectAuthDialog import dev.ragnarok.fenrir.dialog.directauth.DirectAuthDialog.Companion.newInstance @@ -70,7 +71,6 @@ import dev.ragnarok.fenrir.util.ViewUtils.setupSwipeRefreshLayoutWithCurrentThem import dev.ragnarok.fenrir.util.rxutils.RxUtils import dev.ragnarok.fenrir.util.serializeble.json.* import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack -import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import io.reactivex.rxjava3.core.Single @@ -737,7 +737,7 @@ class AccountsFragment : BaseFragment(), View.OnClickListener, AccountAdapter.Ca .add( "device_id", Utils.getDeviceId(provideApplicationContext()) ) - return Includes.networkInterfaces.getVkRetrofitProvider().provideRawHttpClient(type) + return Includes.networkInterfaces.getVkRestProvider().provideRawHttpClient(type) .flatMap { client -> Single.create { emitter: SingleEmitter -> val request: Request = Request.Builder() @@ -747,18 +747,18 @@ class AccountsFragment : BaseFragment(), View.OnClickListener, AccountAdapter.Ca ) .post(bodyBuilder.build()) .build() - val call = client.newCall(request) + val call = client.build().newCall(request) emitter.setCancellable { call.cancel() } try { val response = call.execute() if (!response.isSuccessful) { - emitter.onError(HttpCodeException(response.code)) + emitter.tryOnError(HttpException(response.code)) } else { emitter.onSuccess(response) } response.close() } catch (e: Exception) { - emitter.onError(e) + emitter.tryOnError(e) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/local/audioslocal/AudioLocalRecyclerAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/local/audioslocal/AudioLocalRecyclerAdapter.kt index 43efe4da5..3c0c48fe6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/local/audioslocal/AudioLocalRecyclerAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/local/audioslocal/AudioLocalRecyclerAdapter.kt @@ -89,13 +89,13 @@ class AudioLocalRecyclerAdapter(private val mContext: Context, private var data: if (bitrate != null && fl != null) { v.onSuccess(Pair((bitrate.toLong() / 1000).toInt(), File(fl).length())) } else { - v.onError(Throwable("Can't receipt bitrate ")) + v.tryOnError(Throwable("Can't receipt bitrate ")) } } else { - v.onError(Throwable("Can't receipt bitrate ")) + v.tryOnError(Throwable("Can't receipt bitrate ")) } } catch (e: RuntimeException) { - v.onError(e) + v.tryOnError(e) } } } @@ -120,16 +120,16 @@ class AudioLocalRecyclerAdapter(private val mContext: Context, private var data: it.onComplete() return@create } else { - it.onError(Throwable("Can't strip metadata")) + it.tryOnError(Throwable("Can't strip metadata")) } } else { - it.onError(Throwable("Can't find file")) + it.tryOnError(Throwable("Can't find file")) } } else { - it.onError(Throwable("Can't find file")) + it.tryOnError(Throwable("Can't find file")) } } catch (e: RuntimeException) { - it.onError(e) + it.tryOnError(e) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt index 2f37eefd3..1ae93cbb4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt @@ -79,10 +79,10 @@ class AudioLocalServerRecyclerAdapter( if (bitrate != null) { v.onSuccess((bitrate.toLong() / 1000).toInt()) } else { - v.onError(Throwable("Can't receipt bitrate ")) + v.tryOnError(Throwable("Can't receipt bitrate ")) } } catch (e: RuntimeException) { - v.onError(e) + v.tryOnError(e) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatPresenter.kt index a797fe4d0..009b9b845 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatPresenter.kt @@ -2195,7 +2195,7 @@ class ChatPresenter( sInput[0] = FileInputStream(file) return@flatMap Includes.networkInterfaces.uploads() .uploadDocumentRx( - server.url, + server.url ?: throw NotFoundException("upload url empty"), if (filePath.isAnimated) filePath.animationName else file.name, sInput[0]!!, null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt index 4b0879736..1c7c2a508 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt @@ -13,6 +13,7 @@ import dev.ragnarok.fenrir.api.HttpLoggerAndParser.toRequestBuilder import dev.ragnarok.fenrir.api.HttpLoggerAndParser.vkHeader import dev.ragnarok.fenrir.api.ProxyUtil import dev.ragnarok.fenrir.api.model.VKApiUser +import dev.ragnarok.fenrir.api.rest.HttpException import dev.ragnarok.fenrir.domain.* import dev.ragnarok.fenrir.domain.Repository.owners import dev.ragnarok.fenrir.domain.Repository.walls @@ -30,7 +31,6 @@ import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.Utils.getCauseIfRuntime import dev.ragnarok.fenrir.util.Utils.singletonArrayList import dev.ragnarok.fenrir.util.rxutils.RxUtils.ignore -import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException import io.reactivex.rxjava3.core.Single import okhttp3.* import java.io.File @@ -715,7 +715,7 @@ class UserWallPresenter( try { val response = call.execute() if (!response.isSuccessful) { - emitter.onError(HttpCodeException(response.code)) + emitter.tryOnError(HttpException(response.code)) } else { val resp = response.body.string() val locale = Utils.appLocale @@ -772,12 +772,12 @@ class UserWallPresenter( ) ) } catch (e: ParseException) { - emitter.onError(e) + emitter.tryOnError(e) } } response.close() } catch (e: Exception) { - emitter.onError(e) + emitter.tryOnError(e) } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/GroupLongpoll.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/GroupLongpoll.kt index 6f15a768d..a95c49d69 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/GroupLongpoll.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/GroupLongpoll.kt @@ -64,7 +64,7 @@ internal class GroupLongpoll( if (validServer) { compositeDisposable.add( networker.longpoll() - .getGroupUpdates(server, key, ts, 25) + .getGroupUpdates(server ?: return, key, ts, 25) .fromIOToMain() .subscribe({ updates -> onUpdates(updates) }) { throwable -> onUpdatesGetError( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt index 880caac18..116d5eee3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt @@ -3,10 +3,9 @@ package dev.ragnarok.fenrir.service import android.content.Context import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.api.ApiException +import dev.ragnarok.fenrir.api.rest.HttpException import dev.ragnarok.fenrir.exception.NotFoundException import dev.ragnarok.fenrir.nonNullNoEmpty -import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException -import retrofit2.HttpException import java.net.SocketTimeoutException import java.net.UnknownHostException @@ -30,10 +29,11 @@ object ErrorLocalizer { context.getString(R.string.error_not_found_message) } is HttpException -> { - context.getString(R.string.vk_servers_error, throwable.code()) - } - is HttpCodeException -> { - context.getString(R.string.vk_servers_error, throwable.code) + if (throwable.code < 0) { + context.getString(R.string.client_rest_shutdown) + } else { + context.getString(R.string.vk_servers_error, throwable.code) + } } else -> throwable.message.nonNullNoEmpty({ it }, { throwable.toString() }) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt index d12c68a59..b9c72d9d6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt @@ -71,7 +71,12 @@ class AudioToMessageUploadable( val finalArtist = Artist val finalTrackName = TrackName return@flatMap networker.uploads() - .uploadAudioRx(server.url, filename, `is`, listener) + .uploadAudioRx( + server.url ?: throw NotFoundException("upload url empty"), + filename, + `is`, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> networker diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt index 4dba617e6..5eb802633 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt @@ -62,7 +62,12 @@ class AudioUploadable(private val context: Context, private val networker: INetw val finalArtist = Artist val finalTrackName = TrackName return@flatMap networker.uploads() - .uploadAudioRx(server.url, filename, `is`, listener) + .uploadAudioRx( + server.url ?: throw NotFoundException("upload url empty"), + filename, + `is`, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> networker diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt index 89e7ae2dd..83afc1043 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt @@ -38,7 +38,11 @@ class ChatPhotoUploadable(private val context: Context, private val networker: I try { `is` = UploadUtils.openStream(context, upload.fileUri, upload.size) networker.uploads() - .uploadChatPhotoRx(server.url, `is`!!, listener) + .uploadChatPhotoRx( + server.url ?: throw NotFoundException("upload url empty"), + `is`!!, + listener + ) .doFinally { safelyClose(`is`) } .flatMap { dto -> networker.vkDefault(accountId) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt index de20eb45d..ee5f9dbc4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt @@ -64,7 +64,12 @@ class DocumentUploadable( } val filename = UploadUtils.findFileName(context, uri) networker.uploads() - .uploadDocumentRx(server.url, filename, `is`, listener) + .uploadDocumentRx( + server.url ?: throw NotFoundException("upload url empty"), + filename, + `is`, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> networker diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/OwnerPhotoUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/OwnerPhotoUploadable.kt index 1c70c30c4..ddd937998 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/OwnerPhotoUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/OwnerPhotoUploadable.kt @@ -42,7 +42,11 @@ class OwnerPhotoUploadable( try { `is` = UploadUtils.openStream(context, upload.fileUri, upload.size) networker.uploads() - .uploadOwnerPhotoRx(server.url, `is`!!, listener) + .uploadOwnerPhotoRx( + server.url ?: throw NotFoundException("upload url empty"), + `is`!!, + listener + ) .doFinally { safelyClose(`is`) } .flatMap { dto -> networker.vkDefault(accountId) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2AlbumUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2AlbumUploadable.kt index 7dd3fa34b..ba51b3803 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2AlbumUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2AlbumUploadable.kt @@ -52,7 +52,11 @@ class Photo2AlbumUploadable( try { `is` = UploadUtils.openStream(context, upload.fileUri, upload.size) networker.uploads() - .uploadPhotoToAlbumRx(server.url, `is`!!, listener) + .uploadPhotoToAlbumRx( + server.url ?: throw NotFoundException("upload url empty"), + `is`!!, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> var latitude: Double? = null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt index dfdc0ed76..ee98c445d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt @@ -47,7 +47,9 @@ class Photo2MessageUploadable( try { `is` = UploadUtils.openStream(context, upload.fileUri, upload.size) networker.uploads() - .uploadPhotoToMessageRx(server.url, `is`!!, listener) + .uploadPhotoToMessageRx( + server.url ?: throw NotFoundException("upload url empty"), `is`!!, listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> networker.vkDefault(accountId) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt index 51153ff89..bbdcf0db0 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt @@ -46,7 +46,11 @@ class Photo2WallUploadable( try { `is` = UploadUtils.openStream(context, upload.fileUri, upload.size) networker.uploads() - .uploadPhotoToWallRx(server.url, `is`!!, listener) + .uploadPhotoToWallRx( + server.url ?: throw NotFoundException("upload url empty"), + `is`!!, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> networker.vkDefault(accountId) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/StoryUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/StoryUploadable.kt index 9d33b587a..e1fafc9df 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/StoryUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/StoryUploadable.kt @@ -56,7 +56,7 @@ class StoryUploadable(private val context: Context, private val networker: INetw val filename = UploadUtils.findFileName(context, uri) return@flatMap networker.uploads() .uploadStoryRx( - server.url, + server.url ?: throw NotFoundException("upload url empty"), filename, `is`, listener, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Video2WallUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Video2WallUploadable.kt index a04b2d90c..e3603f135 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Video2WallUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Video2WallUploadable.kt @@ -55,7 +55,12 @@ class Video2WallUploadable( } val filename = UploadUtils.findFileName(context, uri) networker.uploads() - .uploadVideoRx(server.url, filename, `is`, listener) + .uploadVideoRx( + server.url ?: throw NotFoundException("upload url empty"), + filename, + `is`, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> val video = Video().setId(dto.video_id).setOwnerId(dto.owner_id).setTitle( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoToMessageUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoToMessageUploadable.kt index 656c373a1..eed0a0bea 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoToMessageUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoToMessageUploadable.kt @@ -60,7 +60,12 @@ class VideoToMessageUploadable( context, uri ) networker.uploads() - .uploadVideoRx(server.url, filename, `is`, listener) + .uploadVideoRx( + server.url ?: throw NotFoundException("upload url empty"), + filename, + `is`, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> val video = Video().setId(dto.video_id).setOwnerId(dto.owner_id).setTitle( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoUploadable.kt index 6c03bd9ec..ac1bc9b11 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/VideoUploadable.kt @@ -56,7 +56,12 @@ class VideoUploadable(private val context: Context, private val networker: INetw } val filename = UploadUtils.findFileName(context, uri) return@flatMap networker.uploads() - .uploadVideoRx(server.url, filename, `is`, listener) + .uploadVideoRx( + server.url ?: throw NotFoundException("upload url empty"), + filename, + `is`, + listener + ) .doFinally(safelyCloseAction(`is`)) .flatMap { dto -> val result = UploadResult( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt index c0a369ef2..01d09d3a6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt @@ -14,7 +14,7 @@ object Mp3InfoHelper { .build() val response = builder.build().newCall(request).execute() if (!response.isSuccessful) { - it.onError( + it.tryOnError( Exception( "Server return " + response.code + " " + response.message @@ -25,7 +25,7 @@ object Mp3InfoHelper { response.body.close() response.close() if (length.isNullOrEmpty()) { - it.onError(Exception("Empty content length!")) + it.tryOnError(Exception("Empty content length!")) } length?.let { o -> it.onSuccess(o.toLong()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt deleted file mode 100644 index 408896f93..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization - -import dev.ragnarok.fenrir.api.model.response.VkResponse -import kotlinx.serialization.DeserializationStrategy -import okhttp3.ResponseBody -import retrofit2.Converter -import java.lang.reflect.Type - -internal class DeserializationStrategyConverter( - private val loader: DeserializationStrategy, - private val serializer: Serializer, - private val type: Type -) : Converter { - override fun convert(value: ResponseBody): T { - val result = serializer.fromResponseBody(loader, value) - if (result is VkResponse) { - result.error?.let { - it.type = type - it.serializer = serializer - } - } - return result - } -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Factory.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Factory.kt deleted file mode 100644 index 03ecde2ac..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Factory.kt +++ /dev/null @@ -1,100 +0,0 @@ -@file:JvmName("KotlinSerializationConverterFactory") - -package dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization - -import dev.ragnarok.fenrir.util.serializeble.json.Json -import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack -import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.Serializer.FromBytes -import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.Serializer.FromString -import kotlinx.serialization.BinaryFormat -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.StringFormat -import okhttp3.MediaType -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.RequestBody -import okhttp3.ResponseBody -import retrofit2.Converter -import retrofit2.Retrofit -import java.lang.reflect.Type - -@ExperimentalSerializationApi -internal class Factory( - private val contentType: MediaType, - private val serializer: Serializer -) : Converter.Factory() { - @Suppress("RedundantNullableReturnType") // Retaining interface contract. - override fun responseBodyConverter( - type: Type, - annotations: Array, - retrofit: Retrofit - ): Converter? { - val loader = serializer.serializer(type) - return DeserializationStrategyConverter(loader, serializer, type) - } - - @Suppress("RedundantNullableReturnType") // Retaining interface contract. - override fun requestBodyConverter( - type: Type, - parameterAnnotations: Array, - methodAnnotations: Array, - retrofit: Retrofit - ): Converter<*, RequestBody>? { - val saver = serializer.serializer(type) - return SerializationStrategyConverter(contentType, saver, serializer) - } -} - -/** - * Return a [Converter.Factory] which uses Kotlin serialization for string-based payloads. - * - * Because Kotlin serialization is so flexible in the types it supports, this converter assumes - * that it can handle all types. If you are mixing this with something else, you must add this - * instance last to allow the other converters a chance to see their types. - */ -@ExperimentalSerializationApi -@JvmName("create") -fun StringFormat.asConverterFactory(contentType: MediaType): Converter.Factory { - return Factory(contentType, FromString(this)) -} - -@ExperimentalSerializationApi -@JvmName("create") -fun StringFormat.asConverterFactory(): Converter.Factory { - return Factory("application/json; charset=UTF-8".toMediaType(), FromString(this)) -} - -@ExperimentalSerializationApi -@JvmName("create") -fun Json.asConverterFactory(): Converter.Factory { - return Factory("application/json; charset=UTF-8".toMediaType(), Serializer.FromJson(this)) -} - -@ExperimentalSerializationApi -fun jsonMsgPackConverterFactory(json: Json, msgPack: MsgPack): Converter.Factory { - return Factory( - "application/json; charset=UTF-8".toMediaType(), - Serializer.FromJsonMsgPack(json, msgPack) - ) -} - -@ExperimentalSerializationApi -@JvmName("create") -fun MsgPack.asConverterFactory(): Converter.Factory { - return Factory( - "application/x-msgpack; charset=utf-8".toMediaType(), - Serializer.FromMsgPack(this) - ) -} - -/** - * Return a [Converter.Factory] which uses Kotlin serialization for byte-based payloads. - * - * Because Kotlin serialization is so flexible in the types it supports, this converter assumes - * that it can handle all types. If you are mixing this with something else, you must add this - * instance last to allow the other converters a chance to see their types. - */ -@ExperimentalSerializationApi -@JvmName("create") -fun BinaryFormat.asConverterFactory(contentType: MediaType): Converter.Factory { - return Factory(contentType, FromBytes(this)) -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt deleted file mode 100644 index 6247c8944..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization - -import kotlinx.serialization.SerializationStrategy -import okhttp3.MediaType -import okhttp3.RequestBody -import retrofit2.Converter - -internal class SerializationStrategyConverter( - private val contentType: MediaType, - private val saver: SerializationStrategy, - private val serializer: Serializer -) : Converter { - override fun convert(value: T) = serializer.toRequestBody(contentType, saver, value) -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt deleted file mode 100644 index 838431ef2..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt +++ /dev/null @@ -1,122 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization - -import dev.ragnarok.fenrir.isMsgPack -import dev.ragnarok.fenrir.util.serializeble.json.Json -import dev.ragnarok.fenrir.util.serializeble.json.internal.JavaStreamSerialReader -import dev.ragnarok.fenrir.util.serializeble.json.internal.decodeByReader -import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack -import kotlinx.serialization.* -import okhttp3.MediaType -import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.ResponseBody -import java.lang.reflect.Type - -sealed class Serializer { - abstract fun fromResponseBody(loader: DeserializationStrategy, body: ResponseBody): T - abstract fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody - - protected abstract val format: SerialFormat - - @ExperimentalSerializationApi // serializer(Type) is not stable. - fun serializer(type: Type): KSerializer = format.serializersModule.serializer(type) - - class FromString(override val format: StringFormat) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - val string = body.string() - return format.decodeFromString(loader, string) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val string = format.encodeToString(saver, value) - return string.toRequestBody(contentType) - } - } - - class FromJsonMsgPack(override val format: Json, private val msgPack: MsgPack) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - if (body.isMsgPack()) { - return msgPack.decodeFromOkioStream(loader, body.source()) - } - return format.decodeByReader(loader, JavaStreamSerialReader(body.byteStream())) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val string = format.encodeToString(saver, value) - return string.toRequestBody(contentType) - } - } - - class FromJson(override val format: Json) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - return format.decodeByReader(loader, JavaStreamSerialReader(body.byteStream())) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val string = format.encodeToString(saver, value) - return string.toRequestBody(contentType) - } - } - - class FromMsgPack(override val format: MsgPack) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - return format.decodeFromOkioStream(loader, body.source()) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val bytes = format.encodeToByteArray(saver, value) - return bytes.toRequestBody(contentType, 0, bytes.size) - } - } - - class FromBytes(override val format: BinaryFormat) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - val bytes = body.bytes() - return format.decodeFromByteArray(loader, bytes) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val bytes = format.encodeToByteArray(saver, value) - return bytes.toRequestBody(contentType, 0, bytes.size) - } - } -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/BodyObservable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/BodyObservable.kt deleted file mode 100644 index 41c24e443..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/BodyObservable.kt +++ /dev/null @@ -1,98 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3 - -import dev.ragnarok.fenrir.api.HttpLoggerAndParser -import dev.ragnarok.fenrir.api.model.Params -import dev.ragnarok.fenrir.api.model.response.VkResponse -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import okhttp3.FormBody -import retrofit2.HttpException -import retrofit2.Response - -internal class BodyObservable(private val upstream: Observable>) : - Observable() { - override fun subscribeActual(observer: Observer) { - upstream.subscribe(BodyObserver(observer)) - } - - private class BodyObserver(observer: Observer) : - Observer> { - private val observer: Observer - private var terminated = false - override fun onSubscribe(disposable: Disposable) { - observer.onSubscribe(disposable) - } - - override fun onNext(response: Response) { - val body = response.body() - if (response.isSuccessful && body != null) { - if (body is VkResponse) { - body.error?.let { - val o: ArrayList = when (val stmp = response.raw().request.body) { - is FormBody -> { - val f = ArrayList(stmp.size) - for (i in 0 until stmp.size) { - val tmp = Params() - tmp.key = stmp.name(i) - tmp.value = stmp.value(i) - f.add(tmp) - } - f - } - is HttpLoggerAndParser.GzipFormBody -> { - val f = ArrayList(stmp.original.size) - f.addAll(stmp.original) - f - } - else -> { - ArrayList() - } - } - val tmp = Params() - tmp.key = "post_url" - tmp.value = response.raw().request.url.toString() - o.add(tmp) - it.requestParams = o - } - } - observer.onNext(body) - } else { - terminated = true - val t: Throwable = HttpException(response) - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - } - - override fun onComplete() { - if (!terminated) { - observer.onComplete() - } - } - - override fun onError(throwable: Throwable) { - if (!terminated) { - observer.onError(throwable) - } else { - // This should never happen! onNext handles and forwards errors automatically. - val broken: Throwable = AssertionError( - "This should never happen! Report as a bug with the full stacktrace." - ) - broken.initCause(throwable) - RxJavaPlugins.onError(broken) - } - } - - init { - this.observer = observer - } - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt deleted file mode 100644 index 8fc3b2b9f..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt +++ /dev/null @@ -1,75 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -internal class CallEnqueueObservable(private val originalCall: Call) : - Observable>() { - override fun subscribeActual(observer: Observer>) { - // Since Call is a one-shot type, clone it for each new observer. - val call = originalCall.clone() - val callback = CallCallback(call, observer) - observer.onSubscribe(callback) - if (!callback.isDisposed) { - call.enqueue(callback) - } - } - - private class CallCallback( - private val call: Call<*>, - private val observer: Observer> - ) : Disposable, Callback { - var terminated = false - - @Volatile - private var disposed = false - override fun onResponse(call: Call, response: Response) { - if (disposed) return - try { - observer.onNext(response) - if (!disposed) { - terminated = true - observer.onComplete() - } - } catch (t: Throwable) { - Exceptions.throwIfFatal(t) - if (terminated) { - RxJavaPlugins.onError(t) - } else if (!disposed) { - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - } - } - - override fun onFailure(call: Call, t: Throwable) { - if (call.isCanceled) return - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - - override fun dispose() { - disposed = true - call.cancel() - } - - override fun isDisposed(): Boolean { - return disposed - } - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt deleted file mode 100644 index 51884d9e9..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt +++ /dev/null @@ -1,59 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Call -import retrofit2.Response - -internal class CallExecuteObservable(private val originalCall: Call) : - Observable>() { - override fun subscribeActual(observer: Observer>) { - // Since Call is a one-shot type, clone it for each new observer. - val call = originalCall.clone() - val disposable = CallDisposable(call) - observer.onSubscribe(disposable) - if (disposable.isDisposed) { - return - } - var terminated = false - try { - val response = call.execute() - if (!disposable.isDisposed) { - observer.onNext(response) - } - if (!disposable.isDisposed) { - terminated = true - observer.onComplete() - } - } catch (t: Throwable) { - Exceptions.throwIfFatal(t) - if (terminated) { - RxJavaPlugins.onError(t) - } else if (!disposable.isDisposed) { - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - } - } - - private class CallDisposable(private val call: Call<*>) : Disposable { - @Volatile - private var disposed = false - override fun dispose() { - disposed = true - call.cancel() - } - - override fun isDisposed(): Boolean { - return disposed - } - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/Result.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/Result.kt deleted file mode 100644 index ff670cc90..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/Result.kt +++ /dev/null @@ -1,52 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3 - -import retrofit2.Response - -/** - * The result of executing an HTTP request. - */ -class Result private constructor( - private val response: Response?, - private val error: Throwable? -) { - /** - * The response received from executing an HTTP request. Only present when [.isError] is - * false, null otherwise. - */ - fun response(): Response? { - return response - } - - /** - * The error experienced while attempting to execute an HTTP request. Only present when [ ][.isError] is true, null otherwise. - * - * - * If the error is an [Throwable] then there was a problem with the transport to the - * remote server. Any other exception type indicates an unexpected failure and should be - * considered fatal (configuration error, programming error, etc.). - */ - fun error(): Throwable? { - return error - } - - /** - * `true` if the request resulted in an error. See [.error] for the cause. - */ - fun isError(): Boolean { - return error != null - } - - companion object { - // Guarding public API nullability. - fun error(error: Throwable?): Result { - if (error == null) throw NullPointerException("error == null") - return Result(null, error) - } - - // Guarding public API nullability. - fun response(response: Response?): Result { - if (response == null) throw NullPointerException("response == null") - return Result(response, null) - } - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/ResultObservable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/ResultObservable.kt deleted file mode 100644 index 7c161a4e0..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/ResultObservable.kt +++ /dev/null @@ -1,46 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Response - -internal class ResultObservable(private val upstream: Observable>) : - Observable>() { - override fun subscribeActual(observer: Observer>) { - upstream.subscribe(ResultObserver(observer)) - } - - private class ResultObserver(private val observer: Observer>) : - Observer> { - override fun onSubscribe(disposable: Disposable) { - observer.onSubscribe(disposable) - } - - override fun onNext(response: Response) { - observer.onNext(Result.response(response)) - } - - override fun onError(throwable: Throwable) { - try { - observer.onNext(Result.error(throwable)) - } catch (t: Throwable) { - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - return - } - observer.onComplete() - } - - override fun onComplete() { - observer.onComplete() - } - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt deleted file mode 100644 index 5cb3125a8..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Scheduler -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Call -import retrofit2.CallAdapter -import java.lang.reflect.Type - -internal class RxJava3CallAdapter( - private val responseType: Type, - private val scheduler: Scheduler?, - private val isAsync: Boolean, - private val isResult: Boolean, - private val isBody: Boolean, - private val isFlowable: Boolean, - private val isSingle: Boolean, - private val isMaybe: Boolean, - private val isCompletable: Boolean -) : CallAdapter { - override fun responseType(): Type { - return responseType - } - - override fun adapt(call: Call): Any { - val responseObservable = - if (isAsync) CallEnqueueObservable(call) else CallExecuteObservable(call) - var observable: Observable<*> - observable = if (isResult) { - ResultObservable(responseObservable) - } else if (isBody) { - BodyObservable(responseObservable) - } else { - responseObservable - } - if (scheduler != null) { - observable = observable.subscribeOn(scheduler) - } - if (isFlowable) { - // We only ever deliver a single value, and the RS spec states that you MUST request at least - // one element which means we never need to honor backpressure. - return observable.toFlowable(BackpressureStrategy.MISSING) - } - if (isSingle) { - return observable.singleOrError() - } - if (isMaybe) { - return observable.singleElement() - } - return if (isCompletable) { - observable.ignoreElements() - } else RxJavaPlugins.onAssembly(observable) - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt deleted file mode 100644 index eba747bdf..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt +++ /dev/null @@ -1,103 +0,0 @@ -package dev.ragnarok.fenrir.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.* -import retrofit2.CallAdapter -import retrofit2.Response -import retrofit2.Retrofit -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type - -class RxJava3CallAdapterFactory private constructor( - private val scheduler: Scheduler?, - private val isAsync: Boolean -) : CallAdapter.Factory() { - override fun get( - returnType: Type, annotations: Array, retrofit: Retrofit - ): CallAdapter<*, *>? { - val rawType = getRawType(returnType) - if (rawType == Completable::class.java) { - // Completable is not parameterized (which is what the rest of this method deals with) so it - // can only be created with a single configuration. - return RxJava3CallAdapter( - Void::class.java, scheduler, isAsync, - isResult = false, - isBody = true, - isFlowable = false, - isSingle = false, - isMaybe = false, - isCompletable = true - ) - } - val isFlowable = rawType == Flowable::class.java - val isSingle = rawType == Single::class.java - val isMaybe = rawType == Maybe::class.java - if (rawType != Observable::class.java && !isFlowable && !isSingle && !isMaybe) { - return null - } - var isResult = false - var isBody = false - val responseType: Type - if (returnType !is ParameterizedType) { - val name = - if (isFlowable) "Flowable" else if (isSingle) "Single" else if (isMaybe) "Maybe" else "Observable" - throw IllegalStateException( - name - + " return type must be parameterized" - + " as " - + name - + " or " - + name - + "" - ) - } - val observableType = getParameterUpperBound(0, returnType) - when (getRawType(observableType)) { - Response::class.java -> { - check(observableType is ParameterizedType) { "Response must be parameterized" + " as Response or Response" } - responseType = getParameterUpperBound(0, observableType) - } - Result::class.java -> { - check(observableType is ParameterizedType) { "Result must be parameterized" + " as Result or Result" } - responseType = getParameterUpperBound(0, observableType) - isResult = true - } - else -> { - responseType = observableType - isBody = true - } - } - return RxJava3CallAdapter( - responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false - ) - } - - companion object { - /** - * Returns an instance which creates asynchronous observables that run on a background thread by - * default. Applying `subscribeOn(..)` has no effect on instances created by the returned - * factory. - */ - fun create(): RxJava3CallAdapterFactory { - return RxJava3CallAdapterFactory(null, true) - } - - /** - * Returns an instance which creates synchronous observables that do not operate on any scheduler - * by default. Applying `subscribeOn(..)` will change the scheduler on which the HTTP calls - * are made. - */ - fun createSynchronous(): RxJava3CallAdapterFactory { - return RxJava3CallAdapterFactory(null, false) - } - - /** - * Returns an instance which creates synchronous observables that `subscribeOn(..)` the - * supplied `scheduler` by default. - */ - // Guarding public API nullability. - fun createWithScheduler(scheduler: Scheduler?): RxJava3CallAdapterFactory { - if (scheduler == null) throw NullPointerException("scheduler == null") - return RxJava3CallAdapterFactory(scheduler, false) - } - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/res/values-be/strings.xml b/app_fenrir/src/main/res/values-be/strings.xml index efc1c1cb3..d06b6752c 100644 --- a/app_fenrir/src/main/res/values-be/strings.xml +++ b/app_fenrir/src/main/res/values-be/strings.xml @@ -1217,6 +1217,7 @@ Адправіць код праз SMS Аб\'ект не знойдзены Сервера ВК упалі (HTTP: %1$d) + Запыт скасаваны Хуткі пошук Краіны Паведамленні diff --git a/app_fenrir/src/main/res/values-ru/strings.xml b/app_fenrir/src/main/res/values-ru/strings.xml index e6e399ba6..7c41b75b5 100644 --- a/app_fenrir/src/main/res/values-ru/strings.xml +++ b/app_fenrir/src/main/res/values-ru/strings.xml @@ -1219,6 +1219,7 @@ Отправить код через SMS Объект не найден Сервера ВК упали (HTTP: %1$d) + Запрос отменён Быстрый поиск Страны Сообщения diff --git a/app_fenrir/src/main/res/values/strings.xml b/app_fenrir/src/main/res/values/strings.xml index 932e786b1..b4fc70a3f 100644 --- a/app_fenrir/src/main/res/values/strings.xml +++ b/app_fenrir/src/main/res/values/strings.xml @@ -1453,6 +1453,7 @@ Send code via SMS Object not found VK servers down (HTTP: %1$d) + Request canceled Quick search Countries Messages diff --git a/app_filegallery/build.gradle b/app_filegallery/build.gradle index 7d9068abc..86e4394a4 100644 --- a/app_filegallery/build.gradle +++ b/app_filegallery/build.gradle @@ -108,7 +108,6 @@ dependencies { implementation project(path: ":viewpager2") implementation project(path: ":material") implementation project(path: ":preference") - implementation project(path: ":retrofit") implementation project(path: ":camera2") implementation("com.squareup.okhttp3:okhttp-android:$okhttpLibraryVersion") //implementation("com.squareup.okhttp3:logging-interceptor:$okhttpLibraryVersion") diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/App.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/App.kt index ec66011b7..12648debb 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/App.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/App.kt @@ -1,6 +1,7 @@ package dev.ragnarok.filegallery import android.app.Application +import android.os.Handler import androidx.appcompat.app.AppCompatDelegate import androidx.camera.core.ImageProcessingUtil import dev.ragnarok.fenrir.module.FenrirNative @@ -8,9 +9,12 @@ import dev.ragnarok.filegallery.activity.crash.CrashUtils import dev.ragnarok.filegallery.media.music.MusicPlaybackController import dev.ragnarok.filegallery.picasso.PicassoInstance import dev.ragnarok.filegallery.settings.Settings +import dev.ragnarok.filegallery.util.ErrorLocalizer import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.existfile.FileExistJVM import dev.ragnarok.filegallery.util.existfile.FileExistNative +import dev.ragnarok.filegallery.util.toast.CustomToast.Companion.createCustomToast +import io.reactivex.rxjava3.plugins.RxJavaPlugins class App : Application() { override fun onCreate() { @@ -44,6 +48,19 @@ class App : Application() { Utils.isCompressIncomingTraffic = Settings.get().main().isCompress_incoming_traffic Utils.currentParser = Settings.get().main().currentParser PicassoInstance.init(this) + RxJavaPlugins.setErrorHandler { + it.printStackTrace() + Handler(mainLooper).post { + if (Settings.get().main().isDeveloper_mode()) { + createCustomToast(this, null)?.showToastError( + ErrorLocalizer.localizeThrowable( + this, + it + ) + ) + } + } + } } companion object { diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRestProvider.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRestProvider.kt new file mode 100644 index 000000000..93c3b62b8 --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRestProvider.kt @@ -0,0 +1,8 @@ +package dev.ragnarok.filegallery.api + +import dev.ragnarok.filegallery.api.rest.SimplePostHttp +import io.reactivex.rxjava3.core.Single + +interface IOtherRestProvider { + fun provideLocalServerRest(): Single +} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRetrofitProvider.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRetrofitProvider.kt deleted file mode 100644 index cfadd5b21..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/IOtherRetrofitProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.ragnarok.filegallery.api - -import io.reactivex.rxjava3.core.Single - -interface IOtherRetrofitProvider { - fun provideLocalServerRetrofit(): Single -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/OtherRetrofitProvider.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/OtherRestProvider.kt similarity index 61% rename from app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/OtherRetrofitProvider.kt rename to app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/OtherRestProvider.kt index 43da4ceb6..165ab70bd 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/OtherRetrofitProvider.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/OtherRestProvider.kt @@ -3,34 +3,30 @@ package dev.ragnarok.filegallery.api import android.annotation.SuppressLint import dev.ragnarok.filegallery.Constants import dev.ragnarok.filegallery.api.HttpLoggerAndParser.serverHeader -import dev.ragnarok.filegallery.kJson +import dev.ragnarok.filegallery.api.rest.SimplePostHttp import dev.ragnarok.filegallery.nonNullNoEmpty import dev.ragnarok.filegallery.settings.ISettings.IMainSettings import dev.ragnarok.filegallery.util.UncompressDefaultInterceptor import dev.ragnarok.filegallery.util.Utils.firstNonEmptyString -import dev.ragnarok.filegallery.util.serializeble.msgpack.MsgPack -import dev.ragnarok.filegallery.util.serializeble.retrofit.kotlinx.serialization.jsonMsgPackConverterFactory -import dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3.RxJava3CallAdapterFactory import io.reactivex.rxjava3.core.Single import okhttp3.FormBody import okhttp3.Interceptor import okhttp3.MultipartBody import okhttp3.OkHttpClient -import retrofit2.Retrofit import java.util.concurrent.TimeUnit -class OtherRetrofitProvider @SuppressLint("CheckResult") constructor(private val mainSettings: IMainSettings) : - IOtherRetrofitProvider { - private val localServerRetrofitLock = Any() - private var localServerRetrofitInstance: RetrofitWrapper? = null +class OtherRestProvider @SuppressLint("CheckResult") constructor(private val mainSettings: IMainSettings) : + IOtherRestProvider { + private val localServerRestLock = Any() + private var localServerRestInstance: SimplePostHttp? = null private fun onLocalServerSettingsChanged() { - synchronized(localServerRetrofitLock) { - localServerRetrofitInstance?.cleanup() - localServerRetrofitInstance = null + synchronized(localServerRestLock) { + localServerRestInstance?.stop() + localServerRestInstance = null } } - private fun createLocalServerRetrofit(): Retrofit { + private fun createLocalServerRest(): SimplePostHttp { val localSettings = mainSettings.getLocalServer() val builder = OkHttpClient.Builder() .readTimeout(15, TimeUnit.SECONDS) @@ -67,35 +63,22 @@ class OtherRetrofitProvider @SuppressLint("CheckResult") constructor(private val HttpLoggerAndParser.adjust(builder) HttpLoggerAndParser.configureToIgnoreCertificates(builder) val url = firstNonEmptyString(localSettings.url, "https://debug.dev")!! - return Retrofit.Builder() - .baseUrl("$url/method/") - .addConverterFactory( - KCONVERTER_FACTORY - ) - .addCallAdapterFactory(RX_ADAPTER_FACTORY) - .client(builder.build()) - .build() + return SimplePostHttp("$url/method", builder) } - override fun provideLocalServerRetrofit(): Single { + override fun provideLocalServerRest(): Single { return Single.fromCallable { - if (localServerRetrofitInstance == null) { - synchronized(localServerRetrofitLock) { - if (localServerRetrofitInstance == null) { - localServerRetrofitInstance = - RetrofitWrapper.wrap(createLocalServerRetrofit()) + if (localServerRestInstance == null) { + synchronized(localServerRestLock) { + if (localServerRestInstance == null) { + localServerRestInstance = createLocalServerRest() } } } - localServerRetrofitInstance!! + localServerRestInstance!! } } - companion object { - private val KCONVERTER_FACTORY = jsonMsgPackConverterFactory(kJson, MsgPack()) - private val RX_ADAPTER_FACTORY = RxJava3CallAdapterFactory.create() - } - init { mainSettings.observeLocalServer() .subscribe { onLocalServerSettingsChanged() } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/RetrofitWrapper.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/RetrofitWrapper.kt deleted file mode 100644 index ab254d570..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/RetrofitWrapper.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.ragnarok.filegallery.api - -import retrofit2.Retrofit -import java.util.* - -class RetrofitWrapper private constructor( - private val retrofit: Retrofit, - private val withCaching: Boolean -) { - private val servicesCache: MutableMap, Any?>? = - if (withCaching) Collections.synchronizedMap(HashMap(4)) else null - - @Suppress("UNCHECKED_CAST") - fun create(serviceClass: Class): T { - if (!withCaching || servicesCache == null) { - return retrofit.create(serviceClass) - } - if (servicesCache.containsKey(serviceClass)) { - return servicesCache[serviceClass] as T - } - val service = retrofit.create(serviceClass) - servicesCache[serviceClass] = service - return service - } - - fun cleanup() { - servicesCache?.clear() - } - - companion object { - fun wrap(retrofit: Retrofit): RetrofitWrapper { - return RetrofitWrapper(retrofit, true) - } - - fun wrap(retrofit: Retrofit, withCaching: Boolean): RetrofitWrapper { - return RetrofitWrapper(retrofit, withCaching) - } - } - -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/LocalServerApi.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/LocalServerApi.kt index 6cf5dc7ec..ce8e959e6 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/LocalServerApi.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/LocalServerApi.kt @@ -222,7 +222,7 @@ internal class LocalServerApi(private val service: ILocalServerServiceProvider) } override fun remotePlayAudioRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher? diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/Networker.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/Networker.kt index d0fa2e8b8..827c88cad 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/Networker.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/impl/Networker.kt @@ -1,9 +1,8 @@ package dev.ragnarok.filegallery.api.impl import dev.ragnarok.filegallery.api.ILocalServerServiceProvider -import dev.ragnarok.filegallery.api.IOtherRetrofitProvider -import dev.ragnarok.filegallery.api.OtherRetrofitProvider -import dev.ragnarok.filegallery.api.RetrofitWrapper +import dev.ragnarok.filegallery.api.IOtherRestProvider +import dev.ragnarok.filegallery.api.OtherRestProvider import dev.ragnarok.filegallery.api.interfaces.ILocalServerApi import dev.ragnarok.filegallery.api.interfaces.INetworker import dev.ragnarok.filegallery.api.services.ILocalServerService @@ -11,21 +10,21 @@ import dev.ragnarok.filegallery.settings.ISettings.IMainSettings import io.reactivex.rxjava3.core.Single class Networker(settings: IMainSettings) : INetworker { - private val otherVkRetrofitProvider: IOtherRetrofitProvider + private val otherVkRestProvider: IOtherRestProvider override fun localServerApi(): ILocalServerApi { return LocalServerApi(object : ILocalServerServiceProvider { override fun provideLocalServerService(): Single { - return otherVkRetrofitProvider.provideLocalServerRetrofit() - .map { wrapper: RetrofitWrapper -> - wrapper.create( - ILocalServerService::class.java - ) + return otherVkRestProvider.provideLocalServerRest() + .map { + val ret = ILocalServerService() + ret.addon(it) + ret } } }) } init { - otherVkRetrofitProvider = OtherRetrofitProvider(settings) + otherVkRestProvider = OtherRestProvider(settings) } } \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/interfaces/ILocalServerApi.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/interfaces/ILocalServerApi.kt index 07c9cf6dd..8fa008a74 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/interfaces/ILocalServerApi.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/interfaces/ILocalServerApi.kt @@ -75,7 +75,7 @@ interface ILocalServerApi { @CheckResult fun remotePlayAudioRx( - server: String?, + server: String, filename: String?, `is`: InputStream, listener: PercentagePublisher? diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/HttpException.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/HttpException.kt new file mode 100644 index 000000000..4bc002ff9 --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/HttpException.kt @@ -0,0 +1,13 @@ +package dev.ragnarok.filegallery.api.rest + +class HttpException(val code: Int) : RuntimeException( + getMessage( + code + ) +) { + companion object { + private fun getMessage(code: Int): String { + return "HTTP $code" + } + } +} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/IServiceRest.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/IServiceRest.kt new file mode 100644 index 000000000..30d72b633 --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/IServiceRest.kt @@ -0,0 +1,77 @@ +package dev.ragnarok.filegallery.api.rest + +import dev.ragnarok.filegallery.api.model.Items +import dev.ragnarok.filegallery.api.model.response.BaseResponse +import dev.ragnarok.filegallery.kJson +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import okhttp3.FormBody +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.lang.ref.WeakReference + +abstract class IServiceRest { + private var restClient: WeakReference? = null + + val rest: SimplePostHttp + get() = restClient?.get() ?: throw HttpException(-1) + + fun addon(client: SimplePostHttp?) { + restClient = WeakReference(client) + } + + companion object { + val baseInt: KSerializer> + get() = BaseResponse.serializer(Int.serializer()) + + val baseLong: KSerializer> + get() = BaseResponse.serializer(Long.serializer()) + + val baseString: KSerializer> + get() = BaseResponse.serializer(String.serializer()) + + inline fun base(serial: KSerializer): KSerializer> { + return BaseResponse.serializer(serial) + } + + inline fun baseList(serial: KSerializer): KSerializer>> { + return BaseResponse.serializer(ListSerializer(serial)) + } + + inline fun items(serial: KSerializer): KSerializer>> { + return BaseResponse.serializer(Items.serializer(serial)) + } + + private fun toSerialStr(obj: Any?): String? { + return when (obj) { + is String -> { + obj + } + is Byte, is Short, is Int, is Long, is Float, is Double -> { + obj.toString() + } + is Boolean -> { + if (obj) "1" else "0" + } + else -> null + } + } + + inline fun jsonForm(obj: T, serial: KSerializer): RequestBody { + return kJson.encodeToString(serial, obj) + .toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) + } + + fun form(vararg pairs: Pair): FormBody { + val formBuilder = FormBody.Builder() + for ((first, second) in pairs) { + toSerialStr(second)?.let { + formBuilder.add(first, it) + } + } + return formBuilder.build() + } + } +} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/SimplePostHttp.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/SimplePostHttp.kt new file mode 100644 index 000000000..db03f7637 --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/rest/SimplePostHttp.kt @@ -0,0 +1,107 @@ +package dev.ragnarok.filegallery.api.rest + +import dev.ragnarok.filegallery.ifNonNull +import dev.ragnarok.filegallery.isMsgPack +import dev.ragnarok.filegallery.kJson +import dev.ragnarok.filegallery.util.serializeble.json.decodeFromStream +import dev.ragnarok.filegallery.util.serializeble.msgpack.MsgPack +import io.reactivex.rxjava3.core.Single +import kotlinx.serialization.KSerializer +import okhttp3.MultipartBody +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody + +class SimplePostHttp( + private val baseUrl: String?, + okHttpClient: OkHttpClient.Builder +) { + private val client = okHttpClient.build() + + fun stop() { + client.dispatcher.cancelAll() + } + + fun requestFullUrl( + url: String, + body: RequestBody?, + serial: KSerializer, + onlySuccessful: Boolean = true + ): Single { + return requestInternal( + url, + body, + serial, onlySuccessful + ) + } + + fun request( + methodOrFullUrl: String, + body: RequestBody?, + serial: KSerializer, + onlySuccessful: Boolean = true + ): Single { + return requestInternal( + if (baseUrl.isNullOrEmpty()) methodOrFullUrl else "$baseUrl/$methodOrFullUrl", + body, + serial, onlySuccessful + ) + } + + private fun requestInternal( + url: String, + body: RequestBody?, + serial: KSerializer, + onlySuccessful: Boolean + ): Single { + return Single.create { emitter -> + val request = Request.Builder() + .url( + url + ) + body.ifNonNull( + { request.post(it) }, { request.get() } + ) + val call = client.newCall(request.build()) + emitter.setCancellable { call.cancel() } + try { + val response = call.execute() + if (!response.isSuccessful && onlySuccessful) { + emitter.tryOnError(HttpException(response.code)) + } else { + val ret = if (response.body.isMsgPack()) MsgPack().decodeFromOkioStream( + serial, response.body.source() + ) else kJson.decodeFromStream( + serial, response.body.byteStream() + ) + emitter.onSuccess( + ret + ) + } + response.close() + } catch (e: Exception) { + emitter.tryOnError(e) + } + } + } + + fun doMultipartForm( + methodOrFullUrl: String, + part: MultipartBody.Part, + serial: KSerializer, onlySuccessful: Boolean = true + ): Single { + val requestBodyMultipart: RequestBody = + MultipartBody.Builder().setType(MultipartBody.FORM).addPart(part).build() + return request(methodOrFullUrl, requestBodyMultipart, serial, onlySuccessful) + } + + fun doMultipartFormFullUrl( + url: String, + part: MultipartBody.Part, + serial: KSerializer, onlySuccessful: Boolean = true + ): Single { + val requestBodyMultipart: RequestBody = + MultipartBody.Builder().setType(MultipartBody.FORM).addPart(part).build() + return requestFullUrl(url, requestBodyMultipart, serial, onlySuccessful) + } +} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/services/ILocalServerService.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/services/ILocalServerService.kt index dba0e977e..b1219320e 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/services/ILocalServerService.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/services/ILocalServerService.kt @@ -2,118 +2,175 @@ package dev.ragnarok.filegallery.api.services import dev.ragnarok.filegallery.api.model.Items import dev.ragnarok.filegallery.api.model.response.BaseResponse +import dev.ragnarok.filegallery.api.rest.IServiceRest import dev.ragnarok.filegallery.model.Audio import dev.ragnarok.filegallery.model.FileRemote import dev.ragnarok.filegallery.model.Photo import dev.ragnarok.filegallery.model.Video import io.reactivex.rxjava3.core.Single import okhttp3.MultipartBody -import retrofit2.http.* -interface ILocalServerService { - @FormUrlEncoded - @POST("audio.get") +class ILocalServerService : IServiceRest() { fun getAudios( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "audio.get", form( + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Audio.serializer()) + ) + } - @FormUrlEncoded - @POST("discography.get") fun getDiscography( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "discography.get", form( + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Audio.serializer()) + ) + } - @FormUrlEncoded - @POST("photos.get") fun getPhotos( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "photos.get", form( + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Photo.serializer()) + ) + } - @FormUrlEncoded - @POST("video.get") fun getVideos( - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "video.get", form( + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Video.serializer()) + ) + } - @FormUrlEncoded - @POST("audio.search") fun searchAudios( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("discography.search") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "audio.search", form( + "q" to query, + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Audio.serializer()) + ) + } + fun searchDiscography( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("video.search") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "discography.search", form( + "q" to query, + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Audio.serializer()) + ) + } + fun searchVideos( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("photos.search") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "video.search", form( + "q" to query, + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Video.serializer()) + ) + } + fun searchPhotos( - @Field("q") query: String?, - @Field("offset") offset: Int?, - @Field("count") count: Int?, - @Field("reverse") reverse: Int? - ): Single>> - - @FormUrlEncoded - @POST("update_time") - fun update_time(@Field("hash") hash: String?): Single> - - @FormUrlEncoded - @POST("delete_media") - fun delete_media(@Field("hash") hash: String?): Single> - - @FormUrlEncoded - @POST("get_file_name") - fun get_file_name(@Field("hash") hash: String?): Single> - - @FormUrlEncoded - @POST("update_file_name") + query: String?, + offset: Int?, + count: Int?, + reverse: Int? + ): Single>> { + return rest.request( + "photos.search", form( + "q" to query, + "offset" to offset, + "count" to count, + "reverse" to reverse + ), items(Photo.serializer()) + ) + } + + fun update_time(hash: String?): Single> { + return rest.request("update_time", form("hash" to hash), baseInt) + } + + fun delete_media(hash: String?): Single> { + return rest.request("delete_media", form("hash" to hash), baseInt) + } + + fun get_file_name(hash: String?): Single> { + return rest.request("get_file_name", form("hash" to hash), baseString) + } + fun update_file_name( - @Field("hash") hash: String?, - @Field("name") name: String? - ): Single> + hash: String?, + name: String? + ): Single> { + return rest.request( + "update_file_name", form( + "hash" to hash, + "name" to name + ), baseInt + ) + } - @FormUrlEncoded - @POST("fs.get") fun fsGet( - @Field("dir") dir: String? - ): Single>> + dir: String? + ): Single>> { + return rest.request("fs.get", form("dir" to dir), items(FileRemote.serializer())) + } - @FormUrlEncoded - @POST("rebootPC") fun rebootPC( - @Field("type") type: String? - ): Single> + type: String? + ): Single> { + return rest.request("rebootPC", form("type" to type), baseInt) + } - @Multipart - @POST fun remotePlayAudioRx( - @Url server: String?, - @Part file: MultipartBody.Part - ): Single> + server: String, + file: MultipartBody.Part + ): Single> { + return rest.doMultipartFormFullUrl(server, file, baseInt) + } } \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/db/impl/SearchRequestHelperStorage.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/db/impl/SearchRequestHelperStorage.kt index 94019ee16..20c4dff97 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/db/impl/SearchRequestHelperStorage.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/db/impl/SearchRequestHelperStorage.kt @@ -215,7 +215,7 @@ class SearchRequestHelperStorage internal constructor(context: Context) : override fun insertTagOwner(name: String?): Single { return Single.create { emitter: SingleEmitter -> if (name.trimmedIsNullOrEmpty()) { - emitter.onError(Throwable("require name not null!!!")) + emitter.tryOnError(Throwable("require name not null!!!")) } else { val nameClean = name.trim { it <= ' ' } val db = helper.writableDatabase @@ -241,7 +241,7 @@ class SearchRequestHelperStorage internal constructor(context: Context) : db.setTransactionSuccessful() } db.endTransaction() - emitter.onError(Throwable("require name not equals!!!")) + emitter.tryOnError(Throwable("require name not equals!!!")) return@create } val cv = ContentValues() @@ -259,7 +259,7 @@ class SearchRequestHelperStorage internal constructor(context: Context) : override fun updateNameTagOwner(id: Int, name: String?): Completable { return Completable.create { iti -> if (name.trimmedIsNullOrEmpty()) { - iti.onError(Throwable("require name not null!!!")) + iti.tryOnError(Throwable("require name not null!!!")) } else { val nameClean = name.trim { it <= ' ' } val db = helper.writableDatabase diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt index d4fd290da..d19435899 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt @@ -189,10 +189,10 @@ class FileManagerAdapter(private var context: Context, private var data: List File(it1).delete() } == true) { it.onComplete() } else { - it.onError(Throwable("Can't Delete File")) + it.tryOnError(Throwable("Can't Delete File")) } } else { item.file_path?.let { it1 -> deleteRecursive(it1) } if (item.file_path?.let { it1 -> File(it1).delete() } == true) { it.onComplete() } else { - it.onError(Throwable("Can't Delete Folder")) + it.tryOnError(Throwable("Can't Delete Folder")) } } }.fromIOToMain() diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt index 23fa7f0ba..34b1e34ad 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt @@ -73,10 +73,10 @@ class AudioLocalServerRecyclerAdapter( if (bitrate != null) { v.onSuccess((bitrate.toLong() / 1000).toInt()) } else { - v.onError(Throwable("Can't receipt bitrate ")) + v.tryOnError(Throwable("Can't receipt bitrate ")) } } catch (e: RuntimeException) { - v.onError(e) + v.tryOnError(e) } } } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/ErrorLocalizer.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/ErrorLocalizer.kt index 1b56b599d..f95ba997a 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/ErrorLocalizer.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/ErrorLocalizer.kt @@ -2,6 +2,7 @@ package dev.ragnarok.filegallery.util import android.content.Context import dev.ragnarok.filegallery.R +import dev.ragnarok.filegallery.api.rest.HttpException import dev.ragnarok.filegallery.nonNullNoEmpty import java.net.SocketTimeoutException import java.net.UnknownHostException @@ -9,12 +10,21 @@ import java.net.UnknownHostException object ErrorLocalizer { fun localizeThrowable(context: Context, throwable: Throwable?): String { throwable ?: return "null" - if (throwable is SocketTimeoutException) { - return context.getString(R.string.error_timeout_message) + return when (throwable) { + is SocketTimeoutException -> { + context.getString(R.string.error_timeout_message) + } + is UnknownHostException -> { + context.getString(R.string.error_unknown_host) + } + is HttpException -> { + if (throwable.code < 0) { + context.getString(R.string.client_rest_shutdown) + } else { + context.getString(R.string.internal_server_error, throwable.code) + } + } + else -> if (throwable.message.nonNullNoEmpty()) throwable.message!! else throwable.toString() } - if (throwable is UnknownHostException) { - return context.getString(R.string.error_unknown_host) - } - return if (throwable.message.nonNullNoEmpty()) throwable.message!! else throwable.toString() } } \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt deleted file mode 100644 index 994154f82..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/DeserializationStrategyConverter.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.kotlinx.serialization - -import kotlinx.serialization.DeserializationStrategy -import okhttp3.ResponseBody -import retrofit2.Converter - -internal class DeserializationStrategyConverter( - private val loader: DeserializationStrategy, - private val serializer: Serializer -) : Converter { - override fun convert(value: ResponseBody): T { - return serializer.fromResponseBody(loader, value) - } -} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Factory.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Factory.kt deleted file mode 100644 index 50f79747e..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Factory.kt +++ /dev/null @@ -1,100 +0,0 @@ -@file:JvmName("KotlinSerializationConverterFactory") - -package dev.ragnarok.filegallery.util.serializeble.retrofit.kotlinx.serialization - -import dev.ragnarok.filegallery.util.serializeble.json.Json -import dev.ragnarok.filegallery.util.serializeble.msgpack.MsgPack -import dev.ragnarok.filegallery.util.serializeble.retrofit.kotlinx.serialization.Serializer.FromBytes -import dev.ragnarok.filegallery.util.serializeble.retrofit.kotlinx.serialization.Serializer.FromString -import kotlinx.serialization.BinaryFormat -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.StringFormat -import okhttp3.MediaType -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.RequestBody -import okhttp3.ResponseBody -import retrofit2.Converter -import retrofit2.Retrofit -import java.lang.reflect.Type - -@ExperimentalSerializationApi -internal class Factory( - private val contentType: MediaType, - private val serializer: Serializer -) : Converter.Factory() { - @Suppress("RedundantNullableReturnType") // Retaining interface contract. - override fun responseBodyConverter( - type: Type, - annotations: Array, - retrofit: Retrofit - ): Converter? { - val loader = serializer.serializer(type) - return DeserializationStrategyConverter(loader, serializer) - } - - @Suppress("RedundantNullableReturnType") // Retaining interface contract. - override fun requestBodyConverter( - type: Type, - parameterAnnotations: Array, - methodAnnotations: Array, - retrofit: Retrofit - ): Converter<*, RequestBody>? { - val saver = serializer.serializer(type) - return SerializationStrategyConverter(contentType, saver, serializer) - } -} - -/** - * Return a [Converter.Factory] which uses Kotlin serialization for string-based payloads. - * - * Because Kotlin serialization is so flexible in the types it supports, this converter assumes - * that it can handle all types. If you are mixing this with something else, you must add this - * instance last to allow the other converters a chance to see their types. - */ -@ExperimentalSerializationApi -@JvmName("create") -fun StringFormat.asConverterFactory(contentType: MediaType): Converter.Factory { - return Factory(contentType, FromString(this)) -} - -@ExperimentalSerializationApi -@JvmName("create") -fun StringFormat.asConverterFactory(): Converter.Factory { - return Factory("application/json; charset=UTF-8".toMediaType(), FromString(this)) -} - -@ExperimentalSerializationApi -@JvmName("create") -fun Json.asConverterFactory(): Converter.Factory { - return Factory("application/json; charset=UTF-8".toMediaType(), Serializer.FromJson(this)) -} - -@ExperimentalSerializationApi -fun jsonMsgPackConverterFactory(json: Json, msgPack: MsgPack): Converter.Factory { - return Factory( - "application/json; charset=UTF-8".toMediaType(), - Serializer.FromJsonMsgPack(json, msgPack) - ) -} - -@ExperimentalSerializationApi -@JvmName("create") -fun MsgPack.asConverterFactory(): Converter.Factory { - return Factory( - "application/x-msgpack; charset=utf-8".toMediaType(), - Serializer.FromMsgPack(this) - ) -} - -/** - * Return a [Converter.Factory] which uses Kotlin serialization for byte-based payloads. - * - * Because Kotlin serialization is so flexible in the types it supports, this converter assumes - * that it can handle all types. If you are mixing this with something else, you must add this - * instance last to allow the other converters a chance to see their types. - */ -@ExperimentalSerializationApi -@JvmName("create") -fun BinaryFormat.asConverterFactory(contentType: MediaType): Converter.Factory { - return Factory(contentType, FromBytes(this)) -} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt deleted file mode 100644 index 2d1a9191b..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/SerializationStrategyConverter.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.kotlinx.serialization - -import kotlinx.serialization.SerializationStrategy -import okhttp3.MediaType -import okhttp3.RequestBody -import retrofit2.Converter - -internal class SerializationStrategyConverter( - private val contentType: MediaType, - private val saver: SerializationStrategy, - private val serializer: Serializer -) : Converter { - override fun convert(value: T) = serializer.toRequestBody(contentType, saver, value) -} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt deleted file mode 100644 index e6d3bc0a5..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/kotlinx/serialization/Serializer.kt +++ /dev/null @@ -1,122 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.kotlinx.serialization - -import dev.ragnarok.filegallery.isMsgPack -import dev.ragnarok.filegallery.util.serializeble.json.Json -import dev.ragnarok.filegallery.util.serializeble.json.internal.JavaStreamSerialReader -import dev.ragnarok.filegallery.util.serializeble.json.internal.decodeByReader -import dev.ragnarok.filegallery.util.serializeble.msgpack.MsgPack -import kotlinx.serialization.* -import okhttp3.MediaType -import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.ResponseBody -import java.lang.reflect.Type - -sealed class Serializer { - abstract fun fromResponseBody(loader: DeserializationStrategy, body: ResponseBody): T - abstract fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody - - protected abstract val format: SerialFormat - - @ExperimentalSerializationApi // serializer(Type) is not stable. - fun serializer(type: Type): KSerializer = format.serializersModule.serializer(type) - - class FromString(override val format: StringFormat) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - val string = body.string() - return format.decodeFromString(loader, string) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val string = format.encodeToString(saver, value) - return string.toRequestBody(contentType) - } - } - - class FromJsonMsgPack(override val format: Json, private val msgPack: MsgPack) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - if (body.isMsgPack()) { - return msgPack.decodeFromOkioStream(loader, body.source()) - } - return format.decodeByReader(loader, JavaStreamSerialReader(body.byteStream())) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val string = format.encodeToString(saver, value) - return string.toRequestBody(contentType) - } - } - - class FromJson(override val format: Json) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - return format.decodeByReader(loader, JavaStreamSerialReader(body.byteStream())) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val string = format.encodeToString(saver, value) - return string.toRequestBody(contentType) - } - } - - class FromMsgPack(override val format: MsgPack) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - return format.decodeFromOkioStream(loader, body.source()) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val bytes = format.encodeToByteArray(saver, value) - return bytes.toRequestBody(contentType, 0, bytes.size) - } - } - - class FromBytes(override val format: BinaryFormat) : Serializer() { - override fun fromResponseBody( - loader: DeserializationStrategy, - body: ResponseBody - ): T { - val bytes = body.bytes() - return format.decodeFromByteArray(loader, bytes) - } - - override fun toRequestBody( - contentType: MediaType, - saver: SerializationStrategy, - value: T - ): RequestBody { - val bytes = format.encodeToByteArray(saver, value) - return bytes.toRequestBody(contentType, 0, bytes.size) - } - } -} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/BodyObservable.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/BodyObservable.kt deleted file mode 100644 index c56f23d4c..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/BodyObservable.kt +++ /dev/null @@ -1,65 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.HttpException -import retrofit2.Response - -internal class BodyObservable(private val upstream: Observable>) : - Observable() { - override fun subscribeActual(observer: Observer) { - upstream.subscribe(BodyObserver(observer)) - } - - private class BodyObserver(observer: Observer) : - Observer> { - private val observer: Observer - private var terminated = false - override fun onSubscribe(disposable: Disposable) { - observer.onSubscribe(disposable) - } - - override fun onNext(response: Response) { - val body = response.body() - if (response.isSuccessful && body != null) { - observer.onNext(body) - } else { - terminated = true - val t: Throwable = HttpException(response) - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - } - - override fun onComplete() { - if (!terminated) { - observer.onComplete() - } - } - - override fun onError(throwable: Throwable) { - if (!terminated) { - observer.onError(throwable) - } else { - // This should never happen! onNext handles and forwards errors automatically. - val broken: Throwable = AssertionError( - "This should never happen! Report as a bug with the full stacktrace." - ) - broken.initCause(throwable) - RxJavaPlugins.onError(broken) - } - } - - init { - this.observer = observer - } - } -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt deleted file mode 100644 index c76ff48e1..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallEnqueueObservable.kt +++ /dev/null @@ -1,75 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -internal class CallEnqueueObservable(private val originalCall: Call) : - Observable>() { - override fun subscribeActual(observer: Observer>) { - // Since Call is a one-shot type, clone it for each new observer. - val call = originalCall.clone() - val callback = CallCallback(call, observer) - observer.onSubscribe(callback) - if (!callback.isDisposed) { - call.enqueue(callback) - } - } - - private class CallCallback( - private val call: Call<*>, - private val observer: Observer> - ) : Disposable, Callback { - var terminated = false - - @Volatile - private var disposed = false - override fun onResponse(call: Call, response: Response) { - if (disposed) return - try { - observer.onNext(response) - if (!disposed) { - terminated = true - observer.onComplete() - } - } catch (t: Throwable) { - Exceptions.throwIfFatal(t) - if (terminated) { - RxJavaPlugins.onError(t) - } else if (!disposed) { - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - } - } - - override fun onFailure(call: Call, t: Throwable) { - if (call.isCanceled) return - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - - override fun dispose() { - disposed = true - call.cancel() - } - - override fun isDisposed(): Boolean { - return disposed - } - } -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt deleted file mode 100644 index c16a6b5c9..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/CallExecuteObservable.kt +++ /dev/null @@ -1,59 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Call -import retrofit2.Response - -internal class CallExecuteObservable(private val originalCall: Call) : - Observable>() { - override fun subscribeActual(observer: Observer>) { - // Since Call is a one-shot type, clone it for each new observer. - val call = originalCall.clone() - val disposable = CallDisposable(call) - observer.onSubscribe(disposable) - if (disposable.isDisposed) { - return - } - var terminated = false - try { - val response = call.execute() - if (!disposable.isDisposed) { - observer.onNext(response) - } - if (!disposable.isDisposed) { - terminated = true - observer.onComplete() - } - } catch (t: Throwable) { - Exceptions.throwIfFatal(t) - if (terminated) { - RxJavaPlugins.onError(t) - } else if (!disposable.isDisposed) { - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - } - } - } - - private class CallDisposable(private val call: Call<*>) : Disposable { - @Volatile - private var disposed = false - override fun dispose() { - disposed = true - call.cancel() - } - - override fun isDisposed(): Boolean { - return disposed - } - } -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/Result.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/Result.kt deleted file mode 100644 index c6b7331e2..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/Result.kt +++ /dev/null @@ -1,52 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3 - -import retrofit2.Response - -/** - * The result of executing an HTTP request. - */ -class Result private constructor( - private val response: Response?, - private val error: Throwable? -) { - /** - * The response received from executing an HTTP request. Only present when [.isError] is - * false, null otherwise. - */ - fun response(): Response? { - return response - } - - /** - * The error experienced while attempting to execute an HTTP request. Only present when [ ][.isError] is true, null otherwise. - * - * - * If the error is an [Throwable] then there was a problem with the transport to the - * remote server. Any other exception type indicates an unexpected failure and should be - * considered fatal (configuration error, programming error, etc.). - */ - fun error(): Throwable? { - return error - } - - /** - * `true` if the request resulted in an error. See [.error] for the cause. - */ - fun isError(): Boolean { - return error != null - } - - companion object { - // Guarding public API nullability. - fun error(error: Throwable?): Result { - if (error == null) throw NullPointerException("error == null") - return Result(null, error) - } - - // Guarding public API nullability. - fun response(response: Response?): Result { - if (response == null) throw NullPointerException("response == null") - return Result(response, null) - } - } -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/ResultObservable.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/ResultObservable.kt deleted file mode 100644 index 8cd531c96..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/ResultObservable.kt +++ /dev/null @@ -1,46 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Observer -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.exceptions.CompositeException -import io.reactivex.rxjava3.exceptions.Exceptions -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Response - -internal class ResultObservable(private val upstream: Observable>) : - Observable>() { - override fun subscribeActual(observer: Observer>) { - upstream.subscribe(ResultObserver(observer)) - } - - private class ResultObserver(private val observer: Observer>) : - Observer> { - override fun onSubscribe(disposable: Disposable) { - observer.onSubscribe(disposable) - } - - override fun onNext(response: Response) { - observer.onNext(Result.response(response)) - } - - override fun onError(throwable: Throwable) { - try { - observer.onNext(Result.error(throwable)) - } catch (t: Throwable) { - try { - observer.onError(t) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(t, inner)) - } - return - } - observer.onComplete() - } - - override fun onComplete() { - observer.onComplete() - } - } -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt deleted file mode 100644 index 1b0b9e442..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Scheduler -import io.reactivex.rxjava3.plugins.RxJavaPlugins -import retrofit2.Call -import retrofit2.CallAdapter -import java.lang.reflect.Type - -internal class RxJava3CallAdapter( - private val responseType: Type, - private val scheduler: Scheduler?, - private val isAsync: Boolean, - private val isResult: Boolean, - private val isBody: Boolean, - private val isFlowable: Boolean, - private val isSingle: Boolean, - private val isMaybe: Boolean, - private val isCompletable: Boolean -) : CallAdapter { - override fun responseType(): Type { - return responseType - } - - override fun adapt(call: Call): Any { - val responseObservable = - if (isAsync) CallEnqueueObservable(call) else CallExecuteObservable(call) - var observable: Observable<*> - observable = if (isResult) { - ResultObservable(responseObservable) - } else if (isBody) { - BodyObservable(responseObservable) - } else { - responseObservable - } - if (scheduler != null) { - observable = observable.subscribeOn(scheduler) - } - if (isFlowable) { - // We only ever deliver a single value, and the RS spec states that you MUST request at least - // one element which means we never need to honor backpressure. - return observable.toFlowable(BackpressureStrategy.MISSING) - } - if (isSingle) { - return observable.singleOrError() - } - if (isMaybe) { - return observable.singleElement() - } - return if (isCompletable) { - observable.ignoreElements() - } else RxJavaPlugins.onAssembly(observable) - } -} \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt deleted file mode 100644 index ecfca1ca6..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/retrofit/rxjava3/RxJava3CallAdapterFactory.kt +++ /dev/null @@ -1,103 +0,0 @@ -package dev.ragnarok.filegallery.util.serializeble.retrofit.rxjava3 - -import io.reactivex.rxjava3.core.* -import retrofit2.CallAdapter -import retrofit2.Response -import retrofit2.Retrofit -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type - -class RxJava3CallAdapterFactory private constructor( - private val scheduler: Scheduler?, - private val isAsync: Boolean -) : CallAdapter.Factory() { - override fun get( - returnType: Type, annotations: Array, retrofit: Retrofit - ): CallAdapter<*, *>? { - val rawType = getRawType(returnType) - if (rawType == Completable::class.java) { - // Completable is not parameterized (which is what the rest of this method deals with) so it - // can only be created with a single configuration. - return RxJava3CallAdapter( - Void::class.java, scheduler, isAsync, - isResult = false, - isBody = true, - isFlowable = false, - isSingle = false, - isMaybe = false, - isCompletable = true - ) - } - val isFlowable = rawType == Flowable::class.java - val isSingle = rawType == Single::class.java - val isMaybe = rawType == Maybe::class.java - if (rawType != Observable::class.java && !isFlowable && !isSingle && !isMaybe) { - return null - } - var isResult = false - var isBody = false - val responseType: Type - if (returnType !is ParameterizedType) { - val name = - if (isFlowable) "Flowable" else if (isSingle) "Single" else if (isMaybe) "Maybe" else "Observable" - throw IllegalStateException( - name - + " return type must be parameterized" - + " as " - + name - + " or " - + name - + "" - ) - } - val observableType = getParameterUpperBound(0, returnType) - when (getRawType(observableType)) { - Response::class.java -> { - check(observableType is ParameterizedType) { "Response must be parameterized" + " as Response or Response" } - responseType = getParameterUpperBound(0, observableType) - } - Result::class.java -> { - check(observableType is ParameterizedType) { "Result must be parameterized" + " as Result or Result" } - responseType = getParameterUpperBound(0, observableType) - isResult = true - } - else -> { - responseType = observableType - isBody = true - } - } - return RxJava3CallAdapter( - responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false - ) - } - - companion object { - /** - * Returns an instance which creates asynchronous observables that run on a background thread by - * default. Applying `subscribeOn(..)` has no effect on instances created by the returned - * factory. - */ - fun create(): RxJava3CallAdapterFactory { - return RxJava3CallAdapterFactory(null, true) - } - - /** - * Returns an instance which creates synchronous observables that do not operate on any scheduler - * by default. Applying `subscribeOn(..)` will change the scheduler on which the HTTP calls - * are made. - */ - fun createSynchronous(): RxJava3CallAdapterFactory { - return RxJava3CallAdapterFactory(null, false) - } - - /** - * Returns an instance which creates synchronous observables that `subscribeOn(..)` the - * supplied `scheduler` by default. - */ - // Guarding public API nullability. - fun createWithScheduler(scheduler: Scheduler?): RxJava3CallAdapterFactory { - if (scheduler == null) throw NullPointerException("scheduler == null") - return RxJava3CallAdapterFactory(scheduler, false) - } - } -} \ No newline at end of file diff --git a/app_filegallery/src/main/res/values-be/strings.xml b/app_filegallery/src/main/res/values-be/strings.xml index 1aaa23d9c..5450477b1 100644 --- a/app_filegallery/src/main/res/values-be/strings.xml +++ b/app_filegallery/src/main/res/values-be/strings.xml @@ -250,4 +250,6 @@ Ліміт кэша карцінак у памяці Мова Сканаваць QR + Запыт скасаваны + Памылка сервера (HTTP: %1$d) diff --git a/app_filegallery/src/main/res/values-ru/strings.xml b/app_filegallery/src/main/res/values-ru/strings.xml index 1c7e312a2..fa0056737 100644 --- a/app_filegallery/src/main/res/values-ru/strings.xml +++ b/app_filegallery/src/main/res/values-ru/strings.xml @@ -250,4 +250,6 @@ Лимит кэша картинок в памяти Язык Сканировать QR + Запрос отменён + Ошибка сервера (HTTP: %1$d) diff --git a/app_filegallery/src/main/res/values/strings.xml b/app_filegallery/src/main/res/values/strings.xml index 42d1f8d94..b4140e1d8 100644 --- a/app_filegallery/src/main/res/values/strings.xml +++ b/app_filegallery/src/main/res/values/strings.xml @@ -377,4 +377,6 @@ In-Memory Image Cache Limit Language Scan QR + Request canceled + Server error (HTTP: %1$d) diff --git a/build.gradle b/build.gradle index 19188ca3b..4f4b60812 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ buildscript { ext.autoValueVersion = "1.10.1" //common libraries - ext.kotlin_version = "1.8.0-RC2" + ext.kotlin_version = "1.8.0" ext.kotlin_coroutines = "1.6.4" ext.kotlin_serializer = "1.4.1" ext.okhttpLibraryVersion = "5.0.0-alpha.11" diff --git a/compiled_native/libfenrir-release.aar b/compiled_native/libfenrir-release.aar index 77beafa129d043ef948376957dfb1267bb0747b3..05f632e03a82948b6e71bf4c4ebd218d34e95189 100644 GIT binary patch delta 9417709 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%0AgkiTCtJQ<2MJ`+&v2)pWDuFVx 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

nRd{Q<1ACX&a6|k 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